XRPL Technology
How does transaction propagation work on XRPL?
Last updated:
Transaction propagation on the XRP Ledger uses an efficient flood-and-validate protocol that ensures rapid, reliable distribution of signed transactions across the entire network within seconds while preventing spam and maintaining security.
**Propagation Mechanism**
When a transaction is submitted to any XRPL node, it triggers a multi-phase propagation process:
```javascript
// Transaction propagation lifecycle
class TransactionPropagation {
async handleNewTransaction(tx, source) {
// Phase 1: Initial validation
const validation = await this.validateTransaction(tx);
if (!validation.valid) {
return { status: 'rejected', reason: validation.error };
}
// Phase 2: Check for duplicates
if (this.transactionPool.has(tx.hash)) {
return { status: 'already_seen' };
}
// Phase 3: Add to local transaction pool
this.transactionPool.add(tx);
// Phase 4: Broadcast to peers (excluding source)
await this.broadcastToPeers(tx, { exclude: source });
return { status: 'accepted', hash: tx.hash };
}
async broadcastToPeers(tx, options = {}) {
const peers = this.peerManager.getActivePeers();
const excludeSet = new Set(options.exclude ? [options.exclude] : []);
const broadcasts = [];
for (const peer of peers) {
if (excludeSet.has(peer.id)) continue;
broadcasts.push(
this.sendToPeer(tx, peer).catch(err => {
console.error(`Failed to send to ${peer.id}:`, err);
})
);
}
// Send in parallel
await Promise.all(broadcasts);
}
}
```
**Flood Algorithm**
XRPL uses a modified flooding algorithm optimized for consensus:
```python
# Transaction flooding process
class FloodProtocol:
def __init__(self):
self.seen_transactions = set() # Prevent loops
self.propagation_times = {} # Track metrics
def on_receive_transaction(self, tx, from_peer):
tx_hash = compute_hash(tx)
# Step 1: Check if already seen
if tx_hash in self.seen_transactions:
return # Don't re-propagate
# Step 2: Validate transaction
if not self.validate_transaction(tx):
self.mark_peer_unreliable(from_peer)
return
# Step 3: Mark as seen
self.seen_transactions.add(tx_hash)
self.propagation_times[tx_hash] = time.now()
# Step 4: Add to transaction pool
self.transaction_pool.add(tx)
# Step 5: Forward to all other peers
for peer in self.peers:
if peer != from_peer: # Don't send back to source
self.send_transaction(peer, tx)
def send_transaction(self, peer, tx):
message = {
'type': 'TRANSACTION',
'transaction': serialize(tx),
'hash': compute_hash(tx)
}
peer.send(message)
```
**Propagation Speed**
XRPL achieves sub-second transaction propagation:
```javascript
// Typical propagation timeline
const propagationTimeline = {
t0: 'Transaction submitted to node A',
t50ms: 'Node A validates and broadcasts to peers (10 nodes)',
t100ms: 'First hop peers validate and re-broadcast to their peers (100 nodes)',
t200ms: 'Second hop reaches most of network (1000+ nodes)',
t500ms: 'Transaction reaches all well-connected nodes',
t1000ms: 'Transaction known to entire network'
};
// Propagation metrics
async function measurePropagationSpeed(tx, network) {
const startTime = Date.now();
const nodeReceipts = new Map();
// Submit transaction
await network.submitTransaction(tx);
// Monitor when each node receives it
network.nodes.forEach(node => {
node.on('transaction_received', (txHash) => {
if (txHash === tx.hash) {
const elapsed = Date.now() - startTime;
nodeReceipts.set(node.id, elapsed);
}
});
});
// Wait for full propagation
await new Promise(resolve => setTimeout(resolve, 2000));
// Calculate statistics
const times = Array.from(nodeReceipts.values());
return {
min: Math.min(...times),
max: Math.max(...times),
median: calculateMedian(times),
p95: calculatePercentile(times, 95),
coverage: (nodeReceipts.size / network.nodes.length) * 100
};
}
// Typical results
const results = {
min: 20, // ms (direct peer)
max: 800, // ms (distant peer, slow connection)
median: 150, // ms
p95: 400, // ms
coverage: 99.5 // percent
};
```
**Validation During Propagation**
Each node validates before re-broadcasting:
```javascript
function validateBeforePropagation(tx) {
const checks = [
// 1. Structure validation
() => validateTransactionStructure(tx),
// 2. Signature verification
() => verifySignature(tx),
// 3. Fee check
() => validateFee(tx),
// 4. Sequence number
() => validateSequence(tx),
// 5. Field validation
() => validateTransactionFields(tx)
];
for (const check of checks) {
const result = check();
if (!result.valid) {
return result;
}
}
return { valid: true };
}
// Invalid transactions are NOT propagated
function handleInvalidTransaction(tx, peer, reason) {
console.log(`Rejected transaction ${tx.hash} from ${peer.id}: ${reason}`);
// Update peer quality score
peerManager.decreasePeerScore(peer.id, 10);
// If peer sends too many invalid transactions, disconnect
if (peerManager.getPeerScore(peer.id) < 20) {
peerManager.disconnectPeer(peer.id);
}
// Do NOT propagate
return;
}
```
**Anti-Spam Measures**
Propagation includes protection against spam:
```javascript
class SpamPrevention {
constructor() {
this.transactionCounts = new Map(); // Per-account
this.peerTransactionCounts = new Map(); // Per-peer
this.windowSize = 60000; // 1 minute
}
checkSpamLimits(tx, peer) {
const now = Date.now();
// Check per-account rate limit
const accountKey = tx.Account;
const accountCount = this.getRecentCount(accountKey, now);
if (accountCount > 100) { // Max 100 tx/minute per account
return {
allowed: false,
reason: 'Account rate limit exceeded'
};
}
// Check per-peer rate limit
const peerCount = this.getRecentCount(peer.id, now);
if (peerCount > 1000) { // Max 1000 tx/minute per peer
return {
allowed: false,
reason: 'Peer rate limit exceeded'
};
}
// Check transaction fee (economic spam prevention)
const minFee = this.calculateMinFee();
if (parseInt(tx.Fee) < minFee) {
return {
allowed: false,
reason: `Fee too low: ${tx.Fee} < ${minFee}`
};
}
return { allowed: true };
}
recordTransaction(tx, peer) {
const now = Date.now();
// Record for account
this.recordCount(tx.Account, now);
// Record for peer
this.recordCount(peer.id, now);
// Clean old records
this.cleanOldRecords(now);
}
}
```
**Transaction Pool Management**
Nodes maintain a transaction pool for pending transactions:
```javascript
class TransactionPool {
constructor() {
this.transactions = new Map(); // hash -> tx
this.byAccount = new Map(); // account -> [hashes]
this.byFee = new SortedSet(); // Sorted by fee
this.maxSize = 10000; // Maximum transactions
}
add(tx) {
// Check if pool is full
if (this.transactions.size >= this.maxSize) {
// Remove lowest fee transaction
const lowestFeeTx = this.byFee.first();
if (parseInt(tx.Fee) <= parseInt(lowestFeeTx.Fee)) {
return { accepted: false, reason: 'Pool full, fee too low' };
}
this.remove(lowestFeeTx.hash);
}
// Add to pool
this.transactions.set(tx.hash, {
transaction: tx,
received: Date.now(),
attempts: 0
});
// Index by account
if (!this.byAccount.has(tx.Account)) {
this.byAccount.set(tx.Account, []);
}
this.byAccount.get(tx.Account).push(tx.hash);
// Index by fee
this.byFee.add(tx);
return { accepted: true };
}
getAccountTransactions(account) {
const hashes = this.byAccount.get(account) || [];
return hashes.map(hash => this.transactions.get(hash).transaction);
}
getHighestFeeTransactions(count) {
return this.byFee.takeLast(count);
}
}
```
**Propagation Optimization**
```javascript
// Selective propagation based on peer quality
class SmartPropagation {
selectPeersForBroadcast(tx, allPeers) {
// Score peers based on multiple factors
const scored = allPeers.map(peer => ({
peer,
score: this.calculatePropagationScore(peer, tx)
}));
// Sort by score
scored.sort((a, b) => b.score - a.score);
// Select top peers (ensure redundancy but avoid waste)
const redundancyFactor = 0.3; // Broadcast to 30% of peers
const count = Math.max(
5, // Minimum 5 peers
Math.ceil(allPeers.length * redundancyFactor)
);
return scored.slice(0, count).map(s => s.peer);
}
calculatePropagationScore(peer, tx) {
let score = 100;
// Factor 1: Latency (prefer fast peers)
score -= peer.metrics.avgLatency / 10;
// Factor 2: Reliability
score *= peer.metrics.reliability;
// Factor 3: Bandwidth
score *= (1 - peer.metrics.bandwidthUtilization);
// Factor 4: Geographic diversity
score *= this.getDiversityBonus(peer);
// Factor 5: Validator status (prefer validators)
if (peer.isValidator) {
score *= 1.5;
}
return score;
}
}
```
**Monitoring and Metrics**
```javascript
class PropagationMonitor {
constructor() {
this.metrics = {
totalTransactions: 0,
propagatedTransactions: 0,
rejectedTransactions: 0,
avgPropagationTime: 0,
propagationTimes: []
};
}
recordPropagation(tx, startTime) {
const elapsed = Date.now() - startTime;
this.metrics.totalTransactions++;
this.metrics.propagatedTransactions++;
this.metrics.propagationTimes.push(elapsed);
// Update rolling average
this.metrics.avgPropagationTime = (
this.metrics.avgPropagationTime * 0.95 + elapsed * 0.05
);
}
getStatistics() {
return {
total: this.metrics.totalTransactions,
propagated: this.metrics.propagatedTransactions,
rejected: this.metrics.rejectedTransactions,
successRate: this.metrics.propagatedTransactions / this.metrics.totalTransactions,
avgTime: this.metrics.avgPropagationTime,
p50: this.calculatePercentile(50),
p95: this.calculatePercentile(95),
p99: this.calculatePercentile(99)
};
}
}
```
**Comparison to Other Systems**
**Bitcoin:**
- INV/GETDATA pattern (request-response)
- Slower propagation (~6-10 seconds)
- Compact block relay for efficiency
- No immediate validation before propagation
**Ethereum:**
- Direct transaction forwarding
- Faster propagation (~2-5 seconds)
- Transaction pool gossiping
- Multiple propagation strategies (full, hash-only)
**XRPL:**
- Immediate flood with validation
- Fastest propagation (~0.5-1 second)
- Quality-based peer selection
- Explicit spam prevention
**Best Practices for Applications**
```javascript
const xrpl = require('xrpl');
// Submit transaction with propagation awareness
async function submitWithConfirmation(client, signedTx) {
// Step 1: Submit
const submitResult = await client.submit(signedTx.tx_blob);
if (submitResult.result.engine_result !== 'tesSUCCESS') {
throw new Error(`Submit failed: ${submitResult.result.engine_result}`);
}
console.log('Transaction propagated to initial node');
// Step 2: Monitor propagation
// Wait for transaction to appear in validated ledger
const validated = await client.request({
command: 'tx',
transaction: signedTx.hash,
min_ledger: submitResult.result.validated_ledger_index,
max_ledger: submitResult.result.validated_ledger_index + 10
});
if (validated.result.validated) {
console.log('Transaction validated and propagated network-wide');
return validated.result;
} else {
throw new Error('Transaction not validated');
}
}
```
XRPL's transaction propagation system achieves rapid, reliable network-wide distribution while maintaining security and preventing spam, enabling the fast finality that distinguishes XRPL from traditional blockchain systems.
Was this helpful?