# Homework 2: Negative UNL Engineering

[← Back to Consensus II: UNLs and Ledger Amendments](/core-dev-bootcamp/module09.md)

***

### Objective

This homework provides hands-on experience with **XRPL's Negative UNL (Universal Node List) mechanism**. You will create test scenarios that disable and re-enable validators, observing how the network maintains consensus despite validator failures.

**Format**: Written report (PDF or Markdown) with test code, execution output, and analysis.

***

### Background

The Negative UNL mechanism allows the network to temporarily disable poorly performing validators without manual intervention:

**Key Concepts:**

* **Flag Ledgers**: Updates occur every 256 ledgers
* **ToDisable**: Validators marked for disabling via `ttUNL_MODIFY` transactions
* **ToReEnable**: Disabled validators marked for re-enabling when recovered
* **Scoring**: Validators scored based on validation participation using `validationFRESHNESS`
* **Thresholds**: Low/high watermarks determine disable/re-enable actions

**Transaction Types:**

* **ttUNL\_MODIFY**: Special transaction type for UNL modifications
* Only one ToDisable per flag ledger
* Only one ToReEnable per flag ledger
* Transactions processed automatically at flag ledgers

***

### Task

Create a comprehensive test function that simulates the complete validator lifecycle: disable, operation while disabled, and re-enable.

#### Requirements

1. **Test File Setup**
   * Navigate to `rippled/src/test/app/`
   * Open `LedgerMaster_test.cpp` or create new test file
   * Locate the `testNegativeUNL()` function for reference (\~line 100+)
   * Understand the test helper functions
2. **Phase 1: Initial Setup** (15 minutes)
   * Create test function `testValidatorLifecycle()`
   * Create 5 validator public keys
   * Initialize genesis ledger with `featureNegativeUNL` enabled
   * Create tracking structure: `hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq`
   * Add logging to track progress
3. **Phase 2: Flag Ledger Progression** (20 minutes)
   * Build ledgers until first flag ledger (sequence 256)
   * Verify `l->isFlagLedger()` returns true
   * Call `l->updateNegativeUNL()` on flag ledger
   * Verify initial state: empty nUNL, no ToDisable, no ToReEnable
   * Use `negUnlSizeTest()` helper for verification
4. **Phase 3: Validator Disabling** (25 minutes)
   * Create `ttUNL_MODIFY` transaction for validator 0 (ToDisable)
   * Create second disable transaction for validator 1 (should fail)
   * Create re-enable transaction for validator 0 (should fail - not in nUNL yet)
   * Apply transactions using `OpenView`
   * Verify only first disable succeeds
   * Verify ToDisable field is set
5. **Phase 4: Process Disable** (20 minutes)
   * Progress 256 ledgers to next flag ledger
   * Verify ToDisable persists during progression
   * Call `updateNegativeUNL()` on new flag ledger
   * Verify validator 0 is now in nUNL
   * Verify ToDisable field is cleared
   * Record validator 0's disable ledger sequence
6. **Phase 5: Complex Scenarios** (20 minutes)
   * Test disabling already-disabled validator (should fail)
   * Test disabling new validator 2 (should succeed)
   * Test re-enabling validator not in nUNL (should fail)
   * Test re-enabling same validator as ToDisable (should fail)
   * Test re-enabling validator 0 from nUNL (should succeed)
   * Verify final state has both ToDisable and ToReEnable set
7. **Phase 6: Complete Lifecycle** (15 minutes)
   * Progress to next flag ledger
   * Process both ToDisable and ToReEnable
   * Verify validator 2 added to nUNL
   * Verify validator 0 removed from nUNL
   * Verify tracking map is accurate

***

### Implementation Guide

**Test Function Skeleton:**

```cpp
void testValidatorLifecycle()
{
    testcase("Validator Lifecycle - Disable and Re-enable");

    std::cout << "=== Starting Validator Lifecycle Test ===" << std::endl;

    // Setup
    jtx::Env env(*this, jtx::supported_amendments() | featureNegativeUNL);
    std::vector<PublicKey> publicKeys = createPublicKeys(5);

    auto l = std::make_shared<Ledger>(
        create_genesis,
        env.app().config(),
        std::vector<uint256>{},
        env.app().getNodeFamily());

    hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq;

    // Phase 1: Build to first flag ledger
    std::cout << "\n=== Building to First Flag Ledger ===" << std::endl;
    // ... your code here

    // Phase 2: Test disable transactions
    std::cout << "\n=== Testing Validator Disable ===" << std::endl;
    // ... your code here

    // Phase 3: Process disable
    std::cout << "\n=== Processing Disable ===" << std::endl;
    // ... your code here

    // Phase 4: Test complex scenarios
    std::cout << "\n=== Testing Complex Scenarios ===" << std::endl;
    // ... your code here

    // Phase 5: Complete lifecycle
    std::cout << "\n=== Completing Lifecycle ===" << std::endl;
    // ... your code here

    std::cout << "\n=== Test Complete ===" << std::endl;
}
```

**Helper Functions Available:**

```cpp
// Create disable/enable transaction
auto tx = createTx(bool isToDisable, LedgerIndex seq, PublicKey const& key);

// Apply transaction and test result
bool result = applyAndTestResult(Env& env, OpenView& view, STTx const& tx, bool shouldSucceed);

// Test nUNL state
bool ok = negUnlSizeTest(Ledger const& l, size_t expectedSize, bool hasToDisable, bool hasToReEnable);

// Verify tracking map
bool verified = VerifyPubKeyAndSeq(Ledger const& l, hash_map<PublicKey, uint32_t> const& map);
```

***

### Deliverable

A written report containing:

* **Complete Test Code**:
  * Full `testValidatorLifecycle()` function implementation
  * Comments explaining each phase
  * Proper use of helper functions
  * BEAST\_EXPECT assertions at key points
* **Test Execution Output**:
  * Console output from running your test
  * Show all 6 phases completing
  * Show success/failure of each transaction
  * Show nUNL state changes
* **Analysis Table**:

  | Ledger Sequence | Event              | nUNL Size | ToDisable   | ToReEnable  |
  | --------------- | ------------------ | --------- | ----------- | ----------- |
  | 256 (1st flag)  | Initial            | 0         | None        | None        |
  | 256             | Disable TX         | 0         | Validator 0 | None        |
  | 512 (2nd flag)  | Process Disable    | 1         | None        | None        |
  | 512             | Complex TXs        | 1         | Validator 2 | Validator 0 |
  | 768 (3rd flag)  | Complete Lifecycle | 1         | None        | None        |
* **Questions Answered**:
  1. Why can only one ToDisable transaction be processed per flag ledger?
  2. Why can't a validator be disabled before reaching a flag ledger?
  3. What happens to the ToDisable field after it's processed?
  4. Can a validator be in both ToDisable and ToReEnable simultaneously?
  5. How does the 256-ledger interval affect network responsiveness to validator failures?
  6. What would happen if you tried to disable all validators?

***

### Advanced Challenges (Optional)

1. **Scoring System**: Implement validator scoring based on `validationFRESHNESS`
2. **Automatic Disable**: Trigger automatic disable when score falls below threshold
3. **Multiple Cycles**: Test disabling and re-enabling the same validator multiple times
4. **Edge Cases**: Test with only 2 validators, or with all but one disabled

***

### Tips

* Use `std::cout` liberally to track progress through phases
* The `BEAST_EXPECT()` macro is your test assertion
* Flag ledgers are at sequences 256, 512, 768, 1024, etc.
* `updateNegativeUNL()` must be called on each flag ledger
* OpenView allows transaction simulation before applying to ledger
* Read the existing `testNegativeUNL()` function for patterns
* Build and run: `cmake --build . --target rippled --parallel 10 && ./rippled --unittest=LedgerMaster`

***

### Learning Goals

By completing this homework, you should be able to:

* Write consensus tests using the jtx framework
* Understand the Negative UNL transaction lifecycle
* Observe automatic validator management mechanisms
* Explain flag ledger processing
* Predict nUNL state changes based on transactions
* Debug consensus test failures
* Verify complex multi-phase consensus scenarios

***

### Reference Materials

* **Negative UNL Implementation**: `rippled/src/xrpld/app/misc/NegativeUNLVote.cpp`
* **Test Framework**: `rippled/src/test/jtx/`
* **Ledger Interface**: `rippled/src/xrpld/ledger/ReadView.h`


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.xrpl-commons.org/core-dev-bootcamp/module09/homework2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
