# Core Protocol Impact

[← Back to Building an Amendments I: Lifecycle and Impact on Core Protocol](/core-dev-bootcamp/module10.md)

***

### Introduction

The activation of an amendment has profound consequences on the XRPL protocol. Unlike simple software updates, an amendment modifies the fundamental rules that govern consensus, transaction validation, and ledger structure. These changes are irreversible and must be applied uniformly by all nodes on the network.

In this section, we will analyze the impact that an amendment like **Subscriptions** (XLS-0078) has on the core protocol: modifications to consensus rules, changes in transaction validation, compatibility considerations, and management of nodes that do not support an enabled amendment.

Understanding this impact is crucial for assessing the risks associated with an amendment and for planning deployment and migration strategies.

***

## Modification of Consensus Rules

### The Rules Object

The XRPL protocol encapsulates the set of active rules in a `Rules` object that is constructed for each ledger based on enabled amendments.

**Conceptual structure**:

```cpp
class Rules {
private:
    // Set of all enabled amendments
    std::set<uint256> enabledAmendments_;

    // Pre-calculation of commonly used rules
    bool fixQualityUpperBound_;
    bool fixTrustLinesToSelf_;
    bool flowCross_;
    bool ownerPaysFee_;
    // ... etc for each impacting amendment

public:
    // Check if an amendment is enabled
    bool enabled(uint256 const& amendment) const {
        return enabledAmendments_.count(amendment) > 0;
    }

    // Quick accessors for specific rules
    bool fixQualityUpperBound() const { return fixQualityUpperBound_; }
    bool flowCross() const { return flowCross_; }
    // ... etc
};
```

**Construction**: The `Rules` object is constructed from the `sfAmendments` field of the ledger:

```cpp
Rules makeRules(std::shared_ptr<ReadView const> const& ledger) {
    // Read amendments from ledger
    auto const amendments = ledger->read(keylet::amendments());

    std::set<uint256> enabled;
    if (amendments && amendments->isFieldPresent(sfAmendments)) {
        auto const& vec = amendments->getFieldV256(sfAmendments);
        enabled.insert(vec.begin(), vec.end());
    }

    return Rules(enabled);
}
```

### Impact of Subscriptions on Rules

When Subscriptions is enabled, several aspects of the protocol change:

**1. New transaction types**:

```cpp
// In Transactor::preflight()
NotTEC checkTransactionType(TxType type, Rules const& rules) {
    if (type == ttSUBSCRIPTION_SET ||
        type == ttSUBSCRIPTION_CLAIM ||
        type == ttSUBSCRIPTION_CANCEL)
    {
        if (!rules.enabled(featureSubscriptions))
            return temDISABLED;
    }
    return tesSUCCESS;
}
```

**2. New ledger entry types**:

```cpp
// In View::read()
if (entry->getType() == ltSUBSCRIPTION) {
    if (!rules.enabled(featureSubscriptions))
        return nullptr;  // Ignore if amendment not enabled
}
```

**3. New validation logic**:

```cpp
// In SubscriptionClaim::doApply()
TER SubscriptionClaim::doApply() {
    // This function can only be called if
    // featureSubscriptions is enabled
    assert(ctx_.view().rules().enabled(featureSubscriptions));

    // Logic specific to recurring subscriptions
    // ...
}
```

***

## Changes in Transaction Validation

### Validation Phases

Every XRPL transaction goes through several validation phases. Amendments can affect each of these phases.

**1. Preflight**: Basic syntactic validation (before applying to ledger)

```cpp
NotTEC SubscriptionSet::preflight(PreflightContext const& ctx) {
    // Check that the amendment is enabled
    if (!ctx.rules.enabled(featureSubscriptions))
        return temDISABLED;

    // Syntactic validation
    if (!ctx.tx.isFieldPresent(sfDestination))
        return temMALFORMED;

    if (!ctx.tx.isFieldPresent(sfAmount))
        return temMALFORMED;

    if (!ctx.tx.isFieldPresent(sfFrequency))
        return temMALFORMED;

    // Basic preflight validation
    auto const ret = preflight1(ctx);
    if (!isTesSuccess(ret))
        return ret;

    return preflight2(ctx);
}
```

**2. Preclaim**: Contextual validation (with ledger access but without modifying it)

```cpp
TER SubscriptionSet::preclaim(PreclaimContext const& ctx) {
    // Check that source account exists
    auto const account = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
    if (!account)
        return terNO_ACCOUNT;

    // Check that destination account exists
    auto const dest = ctx.view.read(keylet::account(ctx.tx[sfDestination]));
    if (!dest)
        return tecNO_DST;

    // Check sufficient funds (approximate)
    auto const balance = (*account)[sfBalance];
    auto const amount = ctx.tx[sfAmount].xrp();

    if (balance < amount)
        return tecUNFUNDED;

    return tesSUCCESS;
}
```

**3. DoApply**: Actual application to ledger

```cpp
TER SubscriptionSet::doApply() {
    // Create subscription entry
    auto const subKey = keylet::subscription(
        ctx_.tx[sfAccount],
        ctx_.tx[sfSubscriptionID]);

    auto sub = std::make_shared<SLE>(subKey);
    sub->setAccountID(sfAccount, ctx_.tx[sfAccount]);
    sub->setAccountID(sfDestination, ctx_.tx[sfDestination]);
    sub->setFieldAmount(sfAmount, ctx_.tx[sfAmount]);
    sub->setFieldU32(sfFrequency, ctx_.tx[sfFrequency]);
    sub->setFieldU32(sfNextPaymentTime, view().info().closeTime + frequency);

    // Add to ledger
    view().insert(sub);

    return tesSUCCESS;
}
```

### Backward Compatibility

**Before activation**: `ttSUBSCRIPTION_SET` transactions submitted before Subscriptions activation are rejected with `temDISABLED`.

**After activation**: These transactions become valid and can be processed.

**No backward compatibility for ledgers**: A ledger built after activation potentially contains `ltSUBSCRIPTION` objects that would not exist in a pre-activation ledger. A node that does not support Subscriptions cannot correctly validate such a ledger.

***

## Impact on Consensus

### LedgerHash Calculation

The hash of a ledger is calculated from all its components, including:

* The root hash of the accounts SHAMap
* The root hash of the transactions SHAMap
* The ledger index, close time, parent hash
* The `sfAmendments` and `sfMajorities` fields

**Importance**: If two nodes apply different rules (one with Subscriptions, the other without), they will build different ledgers with different hashes, which will prevent consensus.

### Consensus on Rules

XRPL consensus is not only about which transactions to include, but also about the **rules to apply**. All nodes must:

1. Agree on the parent ledger
2. Agree on transactions to include
3. **Agree on enabled amendments** (rules to apply)

**Protection mechanism**: If a node does not support an enabled amendment, it enters "amendment blocked" mode and ceases to participate in consensus, thus avoiding building an invalid ledger.

***

## Management of Unsupported Amendments

### Detection of Unsupported Amendment

When an amendment is enabled, the system immediately checks if the node supports it:

```cpp
// In Change::applyAmendment()
if (!ctx.app.getAmendmentTable().isSupported(amendment)) {
    JLOG(ctx.journal.fatal())
        << "Unsupported amendment " << amendment
        << " (" << amendmentName << ") activated!";

    // Block the server
    ctx.app.getOPs().setAmendmentBlocked();

    // Record expected moment
    ctx.app.getAmendmentTable().recordFirstUnsupported(
        ctx.view().info().closeTime);
}
```

### Amendment Blocked Mode

When a node enters "amendment blocked" mode:

**Consensus stopped**: The node ceases to propose and validate ledgers

```cpp
void NetworkOPsImp::setAmendmentBlocked() {
    amendmentBlocked_ = true;

    // Stop participating in consensus
    setMode(OperatingMode::DISCONNECTED);

    JLOG(j_.fatal())
        << "Server is amendment blocked. "
        << "Please upgrade to a newer version.";
}
```

**API still functional**: The node can still:

* Respond to RPC queries
* Synchronize ledgers from peers
* Provide historical data

But it cannot:

* Propose new ledgers
* Validate peer proposals
* Submit new transactions to the network

**Signaling via server\_info**:

```json
{
  "result": {
    "info": {
      "amendment_blocked": true,
      "build_version": "1.11.0",
      "server_state": "amendment_blocked",
      "validated_ledger": {
        "seq": 86652345
      }
    },
    "status": "success"
  }
}
```

### Prior Warnings

The system warns operators **before** an unsupported amendment is activated:

**firstUnsupportedExpected**: If an unsupported amendment has reached majority, the system calculates when it will be activated and emits warnings.

```cpp
auto const expected = app.getAmendmentTable().firstUnsupportedExpected();

if (expected) {
    auto const timeUntil = *expected - now();

    if (timeUntil < 24h) {
        JLOG(j_.fatal())
            << "Unsupported amendment will activate in "
            << std::chrono::duration_cast<std::chrono::hours>(timeUntil).count()
            << " hours. Please upgrade immediately!";
    }
    else if (timeUntil < 7days) {
        JLOG(j_.error())
            << "Unsupported amendment will activate in "
            << std::chrono::duration_cast<std::chrono::days>(timeUntil).count()
            << " days. Please upgrade soon.";
    }
}
```

***

## Forward Compatibility: Anticipating Changes

### Unsupported vs Obsolete

**Unsupported**: An amendment that this node does not recognize or for which it does not have implementation code.

**Obsolete**: A historical amendment that is no longer relevant but must remain in the code in case it was activated on a historical ledger.

```cpp
XRPL_FEATURE(Tickets, Supported::yes, VoteBehavior::Obsolete)
```

### Design for Forward Compatibility

rippled developers design code to anticipate future changes:

**1. Tolerant parsing**: Data structures ignore unknown fields rather than rejecting

```cpp
// If a new field is added by a future amendment
STObject obj = parseTransaction(data);

// Unknown fields are ignored, no error
// Allows old nodes to read new data
```

**2. Structure versioning**: Some structures include version fields

```cpp
if (obj.isFieldPresent(sfVersion)) {
    auto version = obj.getFieldU32(sfVersion);
    if (version > SUPPORTED_VERSION) {
        // Handle with caution or reject
    }
}
```

**3. Feature flags everywhere**: Each new code explicitly checks amendments

```cpp
if (rules.enabled(featureNewFeature)) {
    // New logic
} else {
    // Old logic
}
```

***

## Backward Compatibility: Supporting Old Ledgers

### Historical Rules

Even after an amendment is enabled, nodes must be able to validate historical ledgers that date from before activation.

**Reconstruction of historical Rules**:

```cpp
// To validate ledger 86000000 (before Subscriptions)
Rules historicalRules = makeRules(ledger86000000);

// Subscriptions is not enabled in these rules
assert(!historicalRules.enabled(featureSubscriptions));

// Apply transactions with old rules
for (auto const& tx : ledger86000000->txs()) {
    applyTransaction(tx, historicalRules);
}
```

### Retired Amendments

After an amendment has been active for at least 2 years, the pre-amendment code can be removed:

```cpp
// Before retirement
if (rules.enabled(featureMultiSign)) {
    // New multi-signature logic
} else {
    // Old logic (can be removed after 2 years)
}

// After retirement
// Always assume MultiSign is enabled
// No more conditional branch
```

**Marking in features.macro**:

```cpp
XRPL_RETIRE(MultiSign)
```

This indicates that:

* Pre-amendment code has been removed
* The identifier is deprecated
* New ledgers always assume the amendment is active

***

## Special Cases and Edge Cases

### Simultaneous Activation of Multiple Amendments

It is possible that multiple amendments reach their activation period at the same flag ledger.

**Application order**: Pseudo-transactions are ordered deterministically by amendment hash:

```cpp
std::map<uint256, std::uint32_t> actions;  // Map sorted by key

for (auto const& [hash, action] : actions) {
    // Amendments are activated in hash order
    applyAmendmentTransaction(hash, action);
}
```

**Dependencies between amendments**: Some amendments may depend on others. Code must manage these dependencies:

```cpp
// featureB depends on featureA
if (rules.enabled(featureB)) {
    assert(rules.enabled(featureA));  // Must be true
}
```

### Conflicting Amendments

It is theoretically possible to create two amendments that modify the same logic in incompatible ways.

**Prevention strategy**:

* Rigorous code review before release
* Integration tests with all amendments enabled
* Coordination between development teams

**Management if conflict detected**:

* Validators must withdraw their vote for one of the conflicting amendments
* Or a third corrective amendment must be developed

### Network Partition During Activation

If the network partitions at the moment of amendment activation, two scenarios are possible:

**1. Both partitions activate the amendment**: No problem, they will converge when the partition is resolved.

**2. One partition activates, the other doesn't**: Partitions will build incompatible ledgers. When the partition is resolved, the minority partition must abandon its chain and adopt the majority partition's chain (normal consensus).

***

## Migration Strategies

### Progressive Deployment

Node operators can update their software **before** an amendment is activated:

**Phase 1**: Code release with the new amendment (e.g., rippled 1.12.0 with Subscriptions)

* Nodes update but the amendment is not yet voted on

**Phase 2**: Validators begin voting

* Amendment is supported but not yet enabled
* Nodes not updated can still function

**Phase 3**: Amendment exceeds 80% and enters majority

* **2 weeks notice** for the last nodes not updated

**Phase 4**: Amendment activation

* Nodes not updated are blocked

### Read-Only Mode Nodes

Nodes that are not validators (simple fullhistory or API nodes) can choose to:

**1. Update quickly**: To continue following the network in real-time

**2. Stay in historical mode**: Serve only data up to the ledger preceding activation, and synchronize from other nodes for following ledgers without applying the rules themselves

**3. Depend on other nodes**: Relay requests to updated nodes

***

## Impact on Third-Party Applications

### Wallets and Exchanges

Applications that interact with XRPL must adapt their code when an amendment is activated:

**New transactions**: Support for `ttSUBSCRIPTION_SET`, `ttSUBSCRIPTION_CLAIM`, and `ttSUBSCRIPTION_CANCEL` in the UI

**New fields**: Parsing of fields `sfAccount`, `sfDestination`, `sfDestinationTag`, `sfAmount`, `sfFrequency`, `sfNextPaymentTime`, `sfExpiration`, etc.

**New business logic**: Management of recurring subscriptions (display, cancellation, claim, monitoring)

### APIs and Libraries

Libraries like `xrpl.js`, `xrpl-py` must be updated to support new features:

```javascript
// xrpl.js after Subscriptions
const tx = {
  TransactionType: 'SubscriptionSet',
  Account: 'rN7n7otQDd6FczFgLdlqtyMVrn3M1tXB7V',
  Destination: 'rLHzPsX6oXkzU9rFYvT1EZUcqPFiSv5xdJ',
  Amount: '100000000',  // 100 XRP max per period
  Frequency: 2592000,  // 30 days in seconds
  StartTime: 711232800  // Optional
};

await client.submit(tx);
```


---

# 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/module10/protocol-impact.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.
