Skip to main content
digital payment systems cryptography banking protocols and blockchain internals

RTGS vs DNS: Consistency and Finality Models

8 min read Chapter 9 of 21

RTGS vs DNS: Consistency and Finality Models

Settlement systems face the same fundamental tradeoffs as any distributed system: consistency vs availability vs partition tolerance. But in payments, the consequences of getting these tradeoffs wrong are measured in billions of dollars of risk exposure.

This section examines settlement through the lens of distributed systems theory — a framing that will be immediately recognizable if you’ve worked with database replication, consensus protocols, or eventually-consistent systems.

Settlement Finality: The Strongest Consistency Guarantee

Settlement finality means a payment is irrevocable — it cannot be reversed, unwound, or clawed back, even if the sender subsequently becomes insolvent. This is the strongest consistency property in financial systems, and it has legal force: the EU’s Settlement Finality Directive (98/26/EC) explicitly protects settled payments from insolvency proceedings.

In distributed systems terms, finality is equivalent to linearizability — once a payment is settled, every observer agrees on the new state, and no future operation can contradict it.

from enum import Enum
from dataclasses import dataclass
from datetime import datetime

class FinalityModel(Enum):
    """
    Settlement finality models mapped to distributed systems concepts.
    """
    IMMEDIATE = "immediate"         # RTGS: linearizable, synchronous
    DEFERRED = "deferred"           # DNS: eventually consistent, batched
    PROBABILISTIC = "probabilistic" # Blockchain: confidence increases over time
    CONDITIONAL = "conditional"     # Pre-funded: final upon condition verification

@dataclass
class FinalityProperties:
    model: FinalityModel
    latency: str              # Time from instruction to finality
    reversibility_window: str # Window during which reversal is possible
    counterparty_risk: str    # Risk exposure during settlement
    legal_protection: str     # Legal framework governing finality
    
SETTLEMENT_COMPARISON = {
    "RTGS (Fedwire)": FinalityProperties(
        model=FinalityModel.IMMEDIATE,
        latency="< 1 second",
        reversibility_window="None — immediately final",
        counterparty_risk="Zero — central bank intermediation",
        legal_protection="Federal Reserve Regulation J"
    ),
    "DNS (ACH)": FinalityProperties(
        model=FinalityModel.DEFERRED,
        latency="End of day / next business day",
        reversibility_window="Until settlement window closes",
        counterparty_risk="Full amount until settlement",
        legal_protection="Clearing house rules + national law"
    ),
    "Blockchain (Bitcoin)": FinalityProperties(
        model=FinalityModel.PROBABILISTIC,
        latency="~60 minutes (6 confirmations)",
        reversibility_window="Theoretically: never fully final",
        counterparty_risk="Decreases exponentially with confirmations",
        legal_protection="None — code-governed"
    ),
    "Real-time (FedNow)": FinalityProperties(
        model=FinalityModel.IMMEDIATE,
        latency="< 20 seconds",
        reversibility_window="None — immediately final",
        counterparty_risk="Zero — Federal Reserve settlement",
        legal_protection="Federal Reserve operating rules"
    ),
}

Liquidity-Saving Mechanisms

RTGS requires each bank to have sufficient balance to cover every outgoing payment at the moment it’s submitted. This creates enormous liquidity demands. Modern RTGS systems incorporate liquidity-saving mechanisms (LSMs) that reduce these demands without sacrificing finality:

Bilateral Offsetting

If Bank A owes Bank B €10M and Bank B simultaneously owes Bank A €7M, the system offsets these payments and settles only the net €3M.

Multilateral Offsetting

Extends bilateral offsetting to cycles involving three or more banks. The algorithm finds closed loops of queued payments that can offset each other:

from collections import defaultdict
from decimal import Decimal
from typing import Optional

class MultilateralOffsettingEngine:
    """
    Find payment cycles that can be settled with minimal liquidity.
    
    This implements a simplified version of the offsetting algorithm
    used in systems like TARGET2 and CHIPS.
    
    The algorithm finds strongly connected components in the payment
    graph and settles cycles where all participants have sufficient
    queued inflows to cover their outflows.
    """
    
    def __init__(self):
        self._queue: list[dict] = []
    
    def add_queued_payment(
        self, sender: str, receiver: str, amount: Decimal
    ):
        self._queue.append({
            "sender": sender,
            "receiver": receiver,
            "amount": amount
        })
    
    def find_offsettable_cycles(self) -> list[list[dict]]:
        """
        Find cycles in the payment graph that can be offset.
        
        A cycle A→B→C→A can be offset if the minimum amount in
        the cycle is used as the offset amount. Each participant
        saves that amount in liquidity.
        
        Example:
          A owes B: €10M
          B owes C: €8M
          C owes A: €5M
          
        Offset amount: €5M (minimum in cycle)
        After offsetting:
          A owes B: €5M (saved €5M in liquidity)
          B owes C: €3M (saved €5M)
          C owes A: €0M (fully offset)
        """
        # Build adjacency graph
        graph: dict[str, dict[str, Decimal]] = defaultdict(
            lambda: defaultdict(Decimal)
        )
        for payment in self._queue:
            graph[payment["sender"]][payment["receiver"]] += payment["amount"]
        
        cycles = []
        visited = set()
        
        # DFS to find cycles (simplified — production uses Tarjan's algorithm)
        for start_node in graph:
            cycle = self._find_cycle_from(graph, start_node, visited)
            if cycle:
                cycles.append(cycle)
        
        return cycles
    
    def _find_cycle_from(
        self,
        graph: dict,
        start: str,
        visited: set,
        path: list | None = None,
        path_set: set | None = None
    ) -> Optional[list]:
        if path is None:
            path = [start]
            path_set = {start}
        
        for neighbor in graph.get(start, {}):
            if neighbor == path[0] and len(path) > 2:
                # Found a cycle
                return path
            if neighbor not in path_set:
                path.append(neighbor)
                path_set.add(neighbor)
                result = self._find_cycle_from(
                    graph, neighbor, visited, path, path_set
                )
                if result:
                    return result
                path.pop()
                path_set.discard(neighbor)
        
        return None
    
    def execute_offsetting(self, cycle: list[str]) -> Decimal:
        """
        Execute multilateral offsetting on a payment cycle.
        
        Returns the total liquidity saved.
        """
        # Find the minimum amount in the cycle
        min_amount = Decimal("Infinity")
        for i in range(len(cycle)):
            sender = cycle[i]
            receiver = cycle[(i + 1) % len(cycle)]
            
            total_owed = sum(
                p["amount"] for p in self._queue
                if p["sender"] == sender and p["receiver"] == receiver
            )
            min_amount = min(min_amount, total_owed)
        
        # Reduce all payments in the cycle by the offset amount
        liquidity_saved = min_amount * len(cycle)
        
        for i in range(len(cycle)):
            sender = cycle[i]
            receiver = cycle[(i + 1) % len(cycle)]
            remaining = min_amount
            
            for payment in self._queue:
                if (payment["sender"] == sender and 
                    payment["receiver"] == receiver and 
                    remaining > 0):
                    reduction = min(payment["amount"], remaining)
                    payment["amount"] -= reduction
                    remaining -= reduction
        
        # Remove fully offset payments
        self._queue = [p for p in self._queue if p["amount"] > 0]
        
        return liquidity_saved

Hybrid Settlement: The Modern Approach

Modern systems like CHIPS (Clearing House Interbank Payments System) blend RTGS and DNS:

  1. Continuous bilateral offsetting throughout the day
  2. Queued payments are held when insufficient balance exists
  3. Periodic multilateral netting runs every few minutes to resolve queued payments
  4. RTGS settlement for urgent payments that bypass the queue
class HybridSettlementEngine:
    """
    Hybrid RTGS/DNS settlement with continuous liquidity optimization.
    
    This models the approach used by CHIPS, which settles $1.8 trillion
    daily using only $3-5 billion in pre-funded balances — a liquidity
    efficiency of 99.7%.
    """
    
    def __init__(self, rtgs_engine: 'RTGSSettlementEngine'):
        self._rtgs = rtgs_engine
        self._offsetting = MultilateralOffsettingEngine()
        self._stats = {
            "total_submitted": Decimal(0),
            "settled_gross": Decimal(0),
            "settled_offset": Decimal(0),
            "liquidity_saved": Decimal(0),
        }
    
    def submit_payment(
        self, sender: str, receiver: str, amount: Decimal,
        priority: str = "normal"
    ) -> str:
        """
        Submit a payment for hybrid settlement.
        
        Priority levels:
        - "urgent": Settles immediately via RTGS (higher cost)
        - "normal": Enters the offsetting queue
        """
        self._stats["total_submitted"] += amount
        
        if priority == "urgent":
            # Immediate RTGS settlement
            result = self._rtgs.process_payment(RTGSPayment(
                payment_id=f"URG-{sender}-{receiver}",
                sender_bank=sender,
                receiver_bank=receiver,
                amount=amount,
                currency="USD",
                value_date=datetime.utcnow()
            ))
            if result == SettlementStatus.SETTLED:
                self._stats["settled_gross"] += amount
            return result.value
        
        # Queue for offsetting
        self._offsetting.add_queued_payment(sender, receiver, amount)
        return "queued"
    
    def run_netting_cycle(self):
        """
        Execute a netting cycle — find and settle offsettable groups.
        
        In CHIPS, this runs every few minutes. Each cycle can settle
        hundreds of payments using minimal liquidity.
        """
        cycles = self._offsetting.find_offsettable_cycles()
        
        for cycle in cycles:
            saved = self._offsetting.execute_offsetting(cycle)
            self._stats["liquidity_saved"] += saved
            self._stats["settled_offset"] += saved
    
    def get_efficiency_report(self) -> dict:
        """
        Calculate settlement efficiency metrics.
        
        CHIPS typically achieves 97-99% netting efficiency,
        meaning only 1-3% of submitted value requires actual
        liquidity (pre-funded balances).
        """
        total = self._stats["total_submitted"]
        if total == 0:
            return {"efficiency": "N/A"}
        
        return {
            "total_submitted": total,
            "settled_via_offsetting": self._stats["settled_offset"],
            "settled_via_rtgs": self._stats["settled_gross"],
            "netting_efficiency": float(
                self._stats["settled_offset"] / total * 100
            ),
            "liquidity_required_pct": float(
                self._stats["settled_gross"] / total * 100
            ),
        }

CAP Theorem Applied to Settlement

Payment settlement systems make explicit CAP tradeoffs:

RTGS systems choose CP (Consistency + Partition tolerance):

  • Consistency: every participant sees the same balance at all times
  • Partition tolerance: the system continues operating during network partitions (albeit with reduced throughput — queued payments wait)
  • Availability is sacrificed: if the central bank system is down, no payments settle

DNS systems lean toward AP (Availability + Partition tolerance):

  • Availability: payments are accepted throughout the day regardless of balance
  • Partition tolerance: clearing can continue even if some participants are temporarily unreachable
  • Consistency is deferred: actual balances aren’t known until the settlement window

Blockchain systems offer eventual consistency with probabilistic finality:

  • Each new block increases the probability that a transaction is final
  • The system is available (any node can accept transactions) and partition-tolerant (network splits heal automatically)
  • But consistency is probabilistic — a sufficiently powerful attacker can reverse recent transactions

This framing helps you evaluate any settlement system. When someone proposes a “real-time blockchain settlement system with instant finality,” you can immediately identify the tradeoff they’re making: they’re either sacrificing decentralization (using a permissioned validator set), availability (requiring all validators to be online), or honesty about their finality guarantees.