Background
This document describes the problem that the original FastPay solves, the distributed systems guarantees it provides, and how we adapt the protocol for the Tempo blockchain. Our protocol, Allegro, takes inspiration from the FastPay paper by Baudet, Danezis, and Sonnino [1].
The Problem
Blockchain-based settlement systems face a fundamental latency problem. Traditional consensus protocols require multiple rounds of communication to establish a total ordering of transactions. Even optimized BFT protocols achieve finality in seconds. This latency is acceptable for large-value transfers but unsuitable for point-of-sale retail payments.
The core idea is that payment transactions have special semantics. Payments are commutative. Crediting an account with 10 USD then 20 USD produces the same result as crediting 20 USD then 10 USD. This property enables a weaker primitive that provides safety without total ordering.
Byzantine Consistent Broadcast
FastPay builds on Byzantine Consistent Broadcast rather than atomic commit. Consensus establishes a total order across all transactions with multiple communication rounds. Consistent Broadcast provides per-account ordering with a single round-trip. This reduces latency and increases throughput through account parallelism.
Consistent Broadcast guarantees four properties. Validity ensures that if a correct user broadcasts a message, all correct authorities eventually deliver it. No duplication ensures each message is delivered at most once. Integrity ensures delivered messages were actually sent by the claimed sender. Consistency ensures that if two correct authorities deliver messages for the same sequence number, they deliver the same message.
The critical observation is that consistency does not require agreement on a global order. Authorities only need to agree on what happened for each specific account and sequence number pair.
Security Properties
FastPay provides the following guarantees under Byzantine faults.
Safety ensures no units of value are created or destroyed. The sum of all certified transfers cannot exceed the funds deposited. This holds as long as fewer than f+1 out of 3f+1 total authorities are compromised.
Authenticity ensures only the owner of an account can authorize transfers. Each transfer order requires the account owner’s signature. Authorities verify signatures before signing certificates.
Availability ensures correct users can always transfer funds. As long as 2f+1 authorities are honest and reachable, a correct user can obtain a certificate for any valid transfer.
Threat Model
FastPay operates under the following assumptions. At most f authorities out of 3f+1 total may be Byzantine. The adversary may arbitrarily delay and reorder messages, but messages are eventually delivered. Availability holds only for users who follow the protocol without equivocation.
When up to f authorities are Byzantine, safety and liveness are preserved. Byzantine authorities can refuse to sign or provide incorrect responses. They cannot forge certificates or cause double-spending.
When more than f authorities are Byzantine, safety may be violated. With f+1 or more Byzantine authorities, they can collude to sign conflicting transfers for the same contention key.
User Equivocation
A user who signs two different transfers with the same sequence number may lock their account. Authorities refuse to sign either transfer once they detect the conflict. The account remains locked until one transfer achieves a certificate, which may never happen if authorities are split.
This failure mode affects only the equivocating user. Other users and their funds are unaffected. The protocol does not require trusted users for safety. It only requires non-equivocating behavior for availability of the user’s own account.
Network Partition
During a network partition, users may experience temporary loss of liveness. Users cannot obtain certificates if they cannot reach 2f+1 authorities. Safety is preserved during the partition. No conflicting certificates can form because no quorum is reachable.
When the partition heals, normal operation resumes. Any partially signed transfers can be completed. No special recovery protocol is required.
Mempool and Consensus
FastPay operates as a layer above the mempool but below consensus. The mempool propagates raw transactions through the network without ordering guarantees. The FastPay layer issues certificates attesting to specific account, sequence number, and effects tuples. Consensus orders transactions into blocks for final settlement.
The key benefit is that recipients can accept certificates as payment confirmation without waiting for block finality. The certificate is cryptographic proof that settlement will occur. The certified transaction cannot be rejected at the consensus layer.
Allegro: FastPay for Tempo
Allegro adapts the FastPay protocol for Tempo, an EVM-compatible blockchain built on reth and commonware. The adaptation leverages Tempo-specific features while preserving the core protocol guarantees.
2D Nonce Model
Tempo uses two-dimensional nonces instead of a single sequence counter. Each account has a mapping from nonce_key to sequence number. The first dimension identifies a nonce sequence. The second dimension is the sequence number within that key.
This maps naturally to FastPay’s contention model. The contention key becomes the tuple (sender, nonce_key, nonce_seq). Allegro transactions use dedicated nonce keys prefixed with 0x5b. This avoids interference with regular user transactions on other nonce keys.
Sub-block Transactions
Tempo blocks include a sub-block section for validator-specific transactions. Transactions with nonce keys starting with 0x5b are routed to this section. They bypass the public mempool.
Allegro leverages this for certified transaction settlement. The user obtains a certificate from validator sidecars. The sidecar forwards the certified transaction to the sub-block pipeline. The transaction appears in the next block and settlement completes.
TIP-20 Payment Restriction
Allegro restricts transactions to TIP-20 payment tokens. These are addresses prefixed with 0x20c0. This restriction ensures Allegro handles only payment semantics. Arbitrary contract execution is not permitted. This preserves the commutative properties that make consistent broadcast sufficient.
Chained Payments
Allegro supports chained spending where a recipient spends received funds immediately. The recipient includes the parent Quorum Certificate hash in their transaction. Validators credit the recipient with the incoming amount before validating their outgoing payment.
Alice pays Bob 10 USD and obtains QC1. Bob presents QC1 as parent_qc_hash in his payment to Carol. Validators credit Bob with 10 USD from QC1 before validating his payment. Bob pays Carol 10 USD and obtains QC2. Both payments complete before either settles on-chain.
Quorum Certificate Threshold
A Quorum Certificate requires 2f+1 signatures out of 3f+1 total validators. Any two quorums overlap by at least one honest validator. This prevents conflicting certificates from forming. Even with f Byzantine validators refusing to sign, 2f+1 honest validators can form a quorum.
For the demonstration with 2 validators, the threshold is 2-of-2 where f equals 0. Production deployments use larger committees with appropriate thresholds.
References
[1] Baudet, M., Danezis, G., and Sonnino, A. “FastPay: High-Performance Byzantine Fault Tolerant Settlement.” arXiv:2003.11506, 2020. https://arxiv.org/abs/2003.11506
Related Documentation
See System Architecture for the component design.
See Validator Sidecar for certificate issuance details.
See Tempo Integration for blockchain-specific details.
System Architecture
Allegro is a preconfirmation payment system for the Tempo blockchain. Users complete chained payments before block finalization using validator-signed certificates that aggregate into Quorum Certificates.
Overview
The system consists of four main component layers. User clients construct and submit transactions to validator sidecars. Sidecars validate transactions and issue certificates. The aggregator backend collects certificates from multiple sidecars and assembles Quorum Certificates. The web frontend provides a browser interface for payments.
flowchart TB
subgraph Clients["User Clients"]
Alice
Bob
Carol
end
subgraph Validators["Validator Sidecars"]
Dave["Dave Sidecar"]
Edgar["Edgar Sidecar"]
end
subgraph Aggregator["Backend"]
Backend["fastpay-backend"]
REST["REST API"]
end
subgraph Frontend["Web"]
Web["React App"]
end
Clients --> Validators
Dave <--> Edgar
Backend --> Validators
REST --> Backend
Web --> REST
This diagram shows the primary data flows between components. User clients submit transactions directly to validator sidecars. Sidecars gossip certificates among themselves. The backend fans out requests to all sidecars and aggregates responses. The web frontend communicates with the backend through REST endpoints.
Payment Flow
A payment progresses through several stages from submission to finality. The sender constructs a FastPayTx containing the payment details, nonce, and expiry. Validator sidecars validate the transaction and return signed certificates. Once enough certificates are collected, they form a Quorum Certificate.
The QC threshold determines how many validator certificates are required. In the demo with two validators, the threshold is 2 of 2. In production deployments, the threshold follows Byzantine fault tolerance requirements at 2f+1 where f is the maximum number of faulty validators tolerated.
sequenceDiagram
participant Sender
participant Sidecar1
participant Sidecar2
participant Recipient
Sender->>Sidecar1: SubmitFastPay(tx)
Sender->>Sidecar2: SubmitFastPay(tx)
Sidecar1-->>Sender: ValidatorCertificate
Sidecar2-->>Sender: ValidatorCertificate
Sender->>Sender: Assemble QC
Recipient->>Sidecar1: GetBulletinBoard
Sidecar1-->>Recipient: Certificates
Recipient->>Recipient: Verify QC
This sequence shows the happy path for a single payment. The sender submits to multiple sidecars in parallel. Each sidecar validates and returns a certificate. The sender assembles the Quorum Certificate locally. The recipient can discover the payment through the bulletin board.
Chained Payments
Recipients can spend received funds before block settlement using parent QC references. Bob receives a payment from Alice and obtains the QC. Bob includes the QC hash as parent_qc_hash in a new payment to Carol. Sidecars validate the parent QC and credit Bob with the incoming amount.
This enables instant chained payments without waiting for on-chain confirmation. The contention key (sender, nonce_key, nonce_seq) prevents double-spending at the Allegro layer.
Crate Structure
| Crate | Purpose |
|---|---|
fastpay-types | Core traits and ID types |
fastpay-crypto | Ed25519 signatures and canonical hashing |
fastpay-proto | Generated protobuf types and gRPC stubs |
fastpay-user-client | Wallet, transaction builder, certificate manager |
fastpay-sidecar | Validator gRPC service |
fastpay-sidecar-mock | In-memory mock for testing |
fastpay-backend | Aggregator with REST API |
demo | End-to-end demo binary |
The crates form a layered dependency structure. fastpay-types sits at the bottom with no internal dependencies. fastpay-crypto implements the traits from fastpay-types. fastpay-proto generates wire types from protobuf definitions. Higher-level crates depend on these foundations.
Related Documentation
See Validator Sidecar for the gRPC service implementation.
See Aggregator Backend for the REST API and certificate aggregation.
See User Client for the Rust client library.
See Tempo Integration for blockchain-specific details.
See Demo Scenario for the end-to-end demonstration.
User Client
The fastpay-user-client crate provides a Rust library for wallet interactions with Allegro. It handles transaction construction, certificate collection, QC assembly, and wallet state management.
Overview
The client library coordinates the full payment lifecycle. Users construct transactions with the builder API. The transport layer fans out requests to validators. The certificate manager collects and validates responses. The wallet tracks nonces and pending transactions.
The library compiles to both native Rust and WebAssembly. Native builds use tokio and tonic for async and gRPC. WASM builds use wasm-bindgen-futures for browser compatibility.
Core Components
FastPayClient
The FastPayClient struct is the main entry point. It coordinates transaction submission, certificate collection, and QC assembly.
#![allow(unused)]
fn main() {
let client = FastPayClient::new(
transport,
wallet,
cert_manager,
chain_id,
threshold,
default_nonce_key,
cert_parser,
).with_sender_private_key(sender, private_key)?;
}
The constructor accepts pluggable components for transport, wallet, and certificate management. The with_sender_private_key method registers signing keys for transaction construction.
Key Methods
The send_payment method performs an atomic payment flow.
#![allow(unused)]
fn main() {
let qc = client.send_payment(
sender,
recipient,
amount,
asset,
Expiry::MaxBlockHeight(100),
).await?;
}
This method reserves a nonce, builds the transaction, fans out to validators, collects certificates, and assembles the QC. It returns the complete Quorum Certificate on success.
The send_payment_with_parent method enables chained payments. It accepts a parent QC reference that credits the sender with received funds.
#![allow(unused)]
fn main() {
let qc = client.send_payment_with_parent(
sender,
recipient,
amount,
asset,
expiry,
&parent_qc,
).await?;
}
This method attaches the parent QC hash to the transaction. Validators validate the parent and credit the sender accordingly.
Transaction Builder
The TxBuilder constructs FastPayTx messages with a fluent API.
#![allow(unused)]
fn main() {
let built = TxBuilder::new(chain_id)
.with_payment(sender, recipient, amount, asset)
.with_sender_private_key(private_key)
.with_nonce(nonce_key)
.with_expiry(Expiry::MaxBlockHeight(100))
.build()?;
}
The builder validates inputs and computes hashes. It signs the underlying Tempo transaction using the provided private key. The build method returns a BuiltTx containing the protobuf message, tx_hash, and effects_hash.
Nonce Management
The builder tracks nonce sequences per key. Each call to with_nonce with the same key increments the sequence. This prevents nonce reuse within a session.
#![allow(unused)]
fn main() {
let tx1 = builder.clone().with_nonce(key).build()?; // seq = 0
let tx2 = builder.clone().with_nonce(key).build()?; // seq = 1
}
For cross-session persistence, use WalletState nonce reservation.
Clients should reuse a fixed set of nonce keys rather than generating new keys per transaction. Creating new nonce keys incurs TIP-1000 state creation costs. See Tempo Integration for details on nonce key economics.
Certificate Manager
The CertManager validates and stores certificates from validators.
#![allow(unused)]
fn main() {
let cert_manager = CertManager::<_, _, SimpleAssembler>::new(verify_ctx, threshold);
cert_manager.collect_certificate(tx_hash, effects_hash, cert)?;
let qc = cert_manager.assemble_qc(tx_hash, effects_hash)?;
}
The manager verifies each certificate against the committee verification context. It rejects certificates from unknown validators or mismatched epochs. Duplicate certificates from the same signer are ignored.
Verification Context
The verification context binds certificate validation to a specific committee.
#![allow(unused)]
fn main() {
let verify_ctx = VerificationContext {
chain_id: 1337,
domain_tag: "tempo.fastpay.cert.v1".to_string(),
protocol_version: 1,
epoch: 1,
committee: validator_pubkeys,
};
}
The committee maps validator IDs to public keys. Certificates from validators not in the committee are rejected.
Wallet State
The WalletState struct manages nonces, balances, and pending transactions.
#![allow(unused)]
fn main() {
let mut wallet = WalletState::<MultiCertQC>::new(address, CacheLimits::default());
let seq = wallet.reserve_next_nonce(nonce_key);
wallet.add_pending_tx(pending);
wallet.record_qc(qc);
}
Nonce reservation prevents double-spending across restarts. The wallet journals state changes for crash recovery.
Nonce Reservation
The reserve_next_nonce method atomically claims the next sequence number. Reserved nonces can be released on failure or committed on success.
#![allow(unused)]
fn main() {
let seq = wallet.reserve_next_nonce(key);
// On success:
wallet.commit_reserved_nonce(key, seq);
// On failure:
wallet.release_reserved_nonce(key, seq);
}
This pattern ensures nonces are not reused even if the process crashes between reservation and submission.
Cache Management
The wallet enforces memory bounds through cache limits.
#![allow(unused)]
fn main() {
let limits = CacheLimits {
max_pending_txs: 100,
max_cached_certs: 1000,
max_cached_qcs: 100,
};
}
The prune_caches method removes settled transactions and old certificates when limits are exceeded.
Transport Layer
The transport trait abstracts network communication with sidecars.
#![allow(unused)]
fn main() {
#[async_trait(?Send)]
pub trait SidecarTransport {
async fn submit_fastpay(&self, request: Request, meta: RequestMeta) -> Result<Response>;
async fn get_bulletin_board(&self, request: Request) -> Result<Response>;
async fn get_validator_info(&self) -> Result<Response>;
async fn get_chain_head(&self) -> Result<Response>;
}
}
The ?Send bound enables WASM compatibility where futures are not Send.
Implementations
Three transport implementations are provided.
The MockTransport wraps an in-memory MockSidecar for testing. No network calls are made.
The GrpcTransport uses tonic for native gRPC calls. It supports automatic retry with exponential backoff.
The MultiValidatorTransport wraps multiple transports for parallel fan-out.
#![allow(unused)]
fn main() {
let transport = MultiValidatorTransport::new(vec![
MockTransport::new(sidecar_dave),
MockTransport::new(sidecar_edgar),
]);
}
Fan-out requests are sent in parallel. All responses are collected before returning.
Error Handling
The FastPayClientError enum covers all error cases.
| Variant | Cause |
|---|---|
Transport | Network or sidecar communication failure |
Builder | Invalid transaction parameters |
CertManager | Certificate validation failure |
Wallet | Nonce or state error |
Rejected | Validator rejected the transaction |
ThresholdNotMet | Insufficient certificates for QC |
Errors include context for debugging. Transport errors distinguish between timeouts, unavailable services, and validation failures.
WASM Compatibility
The library uses feature flags for platform-specific dependencies.
[features]
default = ["native"]
native = ["tokio", "tonic"]
wasm = ["wasm-bindgen-futures", "getrandom/js"]
WASM builds exclude native-only transports. The MockTransport works in both environments for testing.
Related Documentation
See System Architecture for the overall system design.
See Validator Sidecar for the gRPC service the client communicates with.
See Tempo Integration for transaction format details.
Backend
The backend is a third-party service that fans out requests to multiple validator sidecars. It collects certificates, assembles Quorum Certificates, and exposes a REST API for web clients.
Purpose
The backend simplifies client integration by providing a single endpoint. Clients submit transactions once to the backend. The backend forwards the request to all configured sidecars in parallel. It collects responses and returns the aggregated result.
The backend also maintains a local certificate cache. This reduces load on individual sidecars for bulletin board queries. The cache uses LRU eviction to bound memory usage.
gRPC API
The backend implements the FastPayAggregator service defined in proto/aggregator.proto.
SubmitFastPay
rpc SubmitFastPay(SubmitFastPayRequest) returns (SubmitFastPayResponse);
Fans out the request to all sidecar endpoints. Returns all received certificates and any rejections. The client can determine if quorum threshold was reached from the certificate count.
GetBulletinBoard
rpc GetBulletinBoard(GetBulletinBoardRequest) returns (GetBulletinBoardResponse);
Pulls certificates from all sidecars and merges the results. Also serves certificates from the local cache. Deduplicates by (tx_hash, effects_hash, signer).
GetQuorumCertificate
rpc GetQuorumCertificate(GetQuorumCertificateRequest) returns (GetQuorumCertificateResponse);
Refreshes certificates from sidecars for the given tx_hash. Groups certificates by effects_hash to find the dominant outcome. Assembles the QC from the group with the most validators. Returns the QC with computed qc_hash.
GetTxStatus
rpc GetTxStatus(GetTxStatusRequest) returns (GetTxStatusResponse);
Returns all certificates for a transaction. Includes the lifecycle stage based on certificate count. A transaction with threshold certificates is in stage CERTIFIED.
GetChainHead
rpc GetChainHead(GetChainHeadRequest) returns (GetChainHeadResponse);
Polls all sidecars and returns the highest observed block height. Uses this to provide consistent expiry checking across validators.
REST API
The backend exposes HTTP endpoints for web client integration.
Encoding Conventions
The REST API follows these encoding rules for frontend compatibility.
| Type | Encoding |
|---|---|
| Bytes | Hex string with 0x prefix |
| uint64 | Decimal string for precision |
| Stage | One of ACCEPTED, CERTIFIED, QUEUED_ONCHAIN, INCLUDED, FINALIZED |
The client_request_id field enables idempotent request handling. Duplicate requests with the same ID return the cached response.
Submit Raw Transaction
POST /api/v1/submit-raw-tx
Content-Type: application/json
{
"chain_id": 1337,
"tempo_tx_hex": "0x...",
"nonce_key_hex": "0x5b...",
"expiry": { "max_block_height": 100 },
"client_request_id": "req-123"
}
Accepts a pre-signed Tempo transaction. The backend decodes the signed EVM transaction to extract payment metadata before forwarding to sidecars.
EVM Transaction Decoding
The backend performs the following steps on the tempo_tx_hex bytes:
- Decode the signed transaction using
TxEnvelope::decode_2718_exact() - Recover the sender address from the ECDSA signature
- Validate the recipient has the TIP-20 payment prefix (
0x20c0...) - Parse the ERC-20
transfer(address,uint256)calldata (selector0xa9059cbb) - Extract recipient address and transfer amount
The decoded sender, recipient, amount, and asset are included as overlay metadata in the FastPayTx. Sidecars independently decode and verify these fields match.
Pass-Through Nonce
The nonce_seq is extracted directly from the EVM transaction’s nonce field. The frontend sets the nonce when signing; the backend passes it through without maintaining a counter. This prevents divergence between the signed transaction nonce and the Allegro protocol nonce.
The backend fans out to sidecars and returns the transaction hash with certificate status.
{
"tx_hash": "0x...",
"stage": "ACCEPTED",
"cert_count": 0,
"qc_formed": false
}
Rejected transactions return an error response.
{
"reject": {
"code": "INSUFFICIENT_FUNDS",
"message": "balance check failed"
}
}
Chain Head
GET /api/v1/chain/head
Returns the current chain state from the highest responding sidecar.
{
"block_height": "12345",
"block_hash": "0x...",
"unix_millis": "1700000000000"
}
This endpoint provides chain state for clients to check expiry conditions. The block_height and unix_millis fields use decimal strings for uint64 precision.
Transaction Status
GET /api/v1/tx/{tx_hash}/status
Returns the certificate count and lifecycle stage for a transaction.
{
"tx_hash": "0x...",
"stage": "CERTIFIED",
"cert_count": 2,
"qc_formed": true,
"qc_hash": "0x..."
}
The qc_formed field indicates whether threshold certificates have been collected. When true, qc_hash contains the hash of the assembled Quorum Certificate. Clients poll this endpoint to track payment progress.
State Management
The backend maintains a certificate cache with bounded memory usage.
Certificate Ingestion
Certificates arrive through sidecar responses and are stored by tx_hash. Each transaction maps to a set of certificates keyed by effects_hash and signer. This structure supports detecting conflicting validator votes.
LRU Eviction
The cache tracks certificate timestamps for LRU eviction. When max_total_certs is exceeded, the oldest certificates are removed. Per-transaction limits prevent any single transaction from consuming excessive memory.
Chain Head Tracking
The backend tracks the highest observed block height across all sidecars. This provides consistent chain state for status queries.
Upstream Fanout
The backend uses parallel requests to minimize latency.
#![allow(unused)]
fn main() {
async fn fanout_submit_fastpay(endpoints: &[String], request: Request) -> Vec<Response> {
let futures = endpoints.iter().map(|ep| submit_to_endpoint(ep, &request));
join_all(futures).await
}
}
Each request has a configurable timeout. Timed-out requests return an error code rather than blocking the response. The client receives all successful responses even if some sidecars are slow.
QC Assembly
Quorum Certificate assembly handles potentially conflicting validator votes.
- Group certificates by
effects_hash - Count validators in each group
- Select the group with the most validators
- Break ties using lexicographic ordering of
effects_hash - Compute
qc_hashover the selected certificates
The resulting QC contains certificates from validators who agree on the transaction effects.
Configuration
| Option | Description |
|---|---|
--grpc-addr | Listen address for gRPC server |
--rest-addr | Listen address for REST server |
--sidecars | Comma-separated sidecar endpoints |
--threshold | Certificate threshold for QC |
--timeout-ms | Per-sidecar request timeout |
--max-certs | Maximum cached certificates |
Related Documentation
See System Architecture for the overall system design.
See Validator Sidecar for the upstream gRPC services.
See Demo Scenario for the end-to-end payment flow.
Validator Sidecar
The validator sidecar is a gRPC service that manages Allegro certificates for a single validator. It validates incoming transactions, issues signed certificates, and maintains a bulletin board for certificate discovery.
Purpose
Each validator runs a sidecar alongside their Tempo node. The sidecar receives payment transactions from user clients. It validates each transaction against local state and signs a certificate if valid. Certificates are stored locally and gossiped to peer sidecars.
The sidecar enforces the contention model that prevents double-spending. A validator signs at most one certificate per contention key. The contention key is the tuple (sender, nonce_key, nonce_seq).
gRPC API
The sidecar implements the FastPaySidecar service defined in proto/sidecar.proto.
SubmitFastPay
rpc SubmitFastPay(SubmitFastPayRequest) returns (SubmitFastPayResponse);
Validates a FastPayTx and returns a ValidatorCertificate on success. The request includes the transaction and any parent QCs for chained payments. The response contains either a certificate or a rejection with error code.
SubmitFastPayStream
rpc SubmitFastPayStream(stream SubmitFastPayRequest) returns (stream SubmitFastPayEvent);
Bidirectional streaming variant of SubmitFastPay. The client sends transaction requests and receives lifecycle events. Each event includes the current block_height for expiry tracking.
| Event Type | Description |
|---|---|
| ACCEPTED | Transaction passed initial validation |
| Certificate | Validator certificate issued |
| CERTIFIED | QC threshold reached (if applicable) |
Streaming reduces round-trips for clients submitting multiple transactions.
GetBulletinBoard
rpc GetBulletinBoard(GetBulletinBoardRequest) returns (GetBulletinBoardResponse);
Returns stored certificates matching the filter criteria. Clients can filter by tx_hash, address, or since_unix_millis for incremental sync. The response includes certificates from this validator and any gossiped certificates from peers.
GetValidatorInfo
rpc GetValidatorInfo(GetValidatorInfoRequest) returns (GetValidatorInfoResponse);
Returns the validator identity including name, public key, and gossip peer list. Clients use this to build the committee verification context.
GetChainHead
rpc GetChainHead(GetChainHeadRequest) returns (GetChainHeadResponse);
Returns the current chain state including block height, block hash, and timestamp. Clients use this to check expiry conditions and reconcile pending transactions.
State Management
The sidecar maintains several data structures for validation and storage.
Balances and Overlay
Base balances are seeded from chain state or configuration. The overlay tracks pending debits from in-flight transactions. Available balance is computed as base balance plus overlay adjustments.
#![allow(unused)]
fn main() {
available = base_balance + overlay_delta
}
The overlay becomes negative as the sender submits payments. Parent QC credits are computed by extracting the EffectsSummary from the parent certificate.
Equivocation Guard
The signed_txs map prevents signing conflicting transactions. Each entry maps a contention key to the tx_hash that was signed. If a new transaction arrives with the same contention key but different hash, validation fails.
Certificate Store
Certificates are stored by tx_hash. Each transaction may have certificates from multiple validators due to gossip. The store deduplicates by signer ID to prevent double-counting.
Nonce Sequences
The nonce_sequences map tracks the next expected nonce for each (sender, nonce_key) pair. Transactions must use exactly the next sequence number. Gaps and out-of-order submissions are rejected.
Request Idempotency
The sidecar caches responses by client_request_id to support idempotent retries. If a client resubmits a request with the same ID, the cached response is returned without re-validation. The cache uses LRU eviction when max_request_cache is exceeded.
Safety Limits
The sidecar enforces memory bounds to prevent resource exhaustion.
| Limit | Default | Purpose |
|---|---|---|
max_total_certs | 10,000 | Certificate store capacity |
max_known_qcs | 4,096 | QC cache capacity |
max_request_cache | 8,192 | Idempotency cache size |
max_bulletin_board_response | 1,000 | Max certificates per query |
max_pending_evm_txs | 4,096 | RETH forwarding queue |
When limits are exceeded, the oldest entries are evicted using LRU ordering.
Validation Flow
Transaction validation proceeds through several checks.
- Decode the
tempo_txbytes and recover the sender from the signature - Validate the overlay payment metadata matches the decoded transaction
- Check expiry against current chain head
- Check the contention key has not been signed for a different transaction
- Validate nonce sequence is exactly the expected next value
- Compute available balance including parent QC credits
- Verify balance covers the payment amount
If all checks pass, the sidecar signs a certificate.
Certificate Message Format
The validator signs a typed message binding the certificate to the transaction and its effects.
preimage = CERT_PREIMAGE_TAG || domain_tag || protocol_version || chain_id || epoch || tx_hash || effects_hash
cert_message = sha256(preimage)
The CERT_PREIMAGE_TAG is the string "tempo.fastpay.cert.preimage.v1". The domain_tag identifies the FastPay protocol version. The chain_id and epoch prevent cross-chain and cross-epoch replay.
The sender, nonce_key, nonce_seq, and expiry are committed indirectly through the tx_hash and effects_hash computations rather than directly in the certificate preimage.
The certificate includes the tx_hash, effects_hash, validator identity, and Ed25519 signature over the cert message.
Replay Protection
The certificate preimage includes chain_id and epoch to prevent replay attacks. The chain_id ensures certificates cannot be replayed across different networks. The epoch binds certificates to a specific validator set.
The expiry field in the transaction prevents indefinite replay. Sidecars reject transactions with expired timestamps or block heights. This bounds the window during which a certificate remains valid.
Gossip Protocol
Sidecars synchronize certificates through pull-based gossip. Each sidecar periodically calls GetBulletinBoard on its configured peers. The since_unix_millis parameter enables incremental sync.
#![allow(unused)]
fn main() {
loop {
for peer in peers {
let certs = peer.get_bulletin_board(since_last_sync);
ingest_peer_certs(certs);
}
sleep(gossip_interval);
}
}
Ingested certificates are validated and deduplicated by signer. The gossip interval and peer list are configurable at startup.
RETH Node Integration
The sidecar optionally connects to a RETH node via WebSocket for chain state and transaction forwarding. This feature is enabled by providing --reth-ws-url at startup.
Block Subscription
The sidecar subscribes to new block headers through the WebSocket connection. Each block updates the local chain head for expiry checking. Confirmed transactions are cleared from the overlay to free balance for new payments.
sequenceDiagram
participant Sidecar
participant RETH as RETH Node
Sidecar->>RETH: eth_subscribe(newHeads)
RETH-->>Sidecar: Block header
Sidecar->>Sidecar: Update chain head
Sidecar->>RETH: eth_getTransactionReceipt (batch)
RETH-->>Sidecar: Receipts
Sidecar->>Sidecar: Clear confirmed txs from overlay
Transaction Forwarding
After issuing a certificate, the sidecar forwards the signed EVM transaction to the RETH mempool. This enables validators to include certified transactions in blocks.
#![allow(unused)]
fn main() {
// Forwarding occurs after successful certification
reth.send_raw_transaction(raw_evm_tx).await?;
}
The forwarding queue has bounded capacity. If RETH is unavailable, transactions accumulate until the queue limit is reached.
Reconnection
Both the block subscription and transaction submission tasks implement exponential backoff reconnection. The base delay is 500ms with a maximum of 30 seconds. Each task reconnects independently to maximize availability.
Configuration
The sidecar binary accepts configuration through command-line arguments or environment variables.
| Option | Description |
|---|---|
--grpc-addr | Listen address for gRPC server |
--validator-key | Path to Ed25519 private key |
--chain-id | Tempo chain identifier |
--epoch | Current validator epoch |
--peers | Comma-separated list of peer sidecar endpoints |
--gossip-interval | Interval between gossip pulls |
--reth-ws-url | Optional RETH WebSocket endpoint for chain integration |
--max-pending-txs | RETH forwarding queue capacity (default 4096) |
Related Documentation
See System Architecture for the overall system design.
See Aggregator Backend for the service that aggregates across sidecars.
See Tempo Integration for how sidecars interact with Tempo nodes.
Tempo Integration
Allegro integrates with the Tempo blockchain through its sub-block transaction system. This document covers the 2D nonce model, sub-block routing, and how Allegro layers on top of existing Tempo infrastructure.
2D Nonce System
Tempo uses a two-dimensional nonce model instead of a single sequential counter. Each account can maintain multiple independent nonce sequences identified by a nonce_key.
| nonce_key value | Behavior |
|---|---|
0 | Protocol nonce with standard Ethereum behavior |
1 to 2^256-2 | User-defined parallel sequences |
U256::MAX | Expiring nonces |
0x5b... prefix | Reserved for sub-block transactions |
This model allows multiple in-flight transactions without nonce conflicts. Allegro uses dedicated nonce keys to avoid interfering with regular user transactions.
Nonce Key Economics
Creating a new nonce key incurs state creation costs under TIP-1000. Each new key requires approximately 5,000 gas for the initial storage slot. Clients should reuse a fixed set of Allegro nonce keys rather than generating random keys per transaction.
The recommended pattern is to allocate one or a few nonce keys for Allegro and increment the sequence number within each key. This amortizes the state creation cost across many transactions.
Sub-block Transactions
Transactions with a nonce_key starting with 0x5b are routed through the sub-block system. These transactions bypass the public mempool and are included through validator sub-blocks.
The nonce_key encodes routing information in its bytes.
Byte position: [31] [30-16] [15-0]
Content: 0x5b PartialValidatorKey Application data
Byte 31 contains the sub-block prefix. Bytes 16 through 30 contain the first 15 bytes of the validator’s public key. Bytes 0 through 15 are available for application use.
The PartialValidatorKey routes the transaction to the correct validator’s sub-block queue. The Tempo node matches this prefix against configured validator keys.
Sub-block Flow
When a transaction arrives at the Tempo node RPC, routing occurs based on the nonce_key.
sequenceDiagram
participant Client
participant RPC as Tempo RPC
participant Actor as SubblocksActor
participant Proposer
Client->>RPC: eth_sendRawTransaction
RPC->>RPC: Check nonce_key prefix
alt 0x5b prefix matches validator
RPC->>Actor: Route to sub-block channel
Actor->>Actor: Build SignedSubBlock
Actor->>Proposer: Broadcast sub-block
Proposer->>Proposer: Include in block
else No match
RPC->>RPC: Reject or use normal mempool
end
This diagram shows the routing decision at the RPC layer. Matching transactions are sent to the SubblocksActor for inclusion in the validator’s sub-block.
Gas Allocation
Tempo allocates a portion of block gas to sub-blocks.
| Pool | Allocation | Purpose |
|---|---|---|
| Shared pool | 10% of block gas | Divided among all validators |
| Non-shared pool | 90% of block gas | Normal transactions |
| Gas incentive | Unused sub-block gas | Rewards for proposers |
Each validator receives an equal share of the shared pool. The formula is shared_gas / num_validators. Unused gas flows to the gas incentive pool.
Allegro Layer
Allegro adds a preconfirmation layer on top of the sub-block system. Validators issue certificates before submitting transactions to the chain. Users can rely on certificates without waiting for block inclusion.
The integration points are as follows.
- Allegro sidecars validate transactions and issue certificates
- Certified transactions are submitted through the sub-block system
- The
0x5bprefix routes transactions to validator sub-blocks - Block inclusion provides final settlement
This architecture separates preconfirmation from settlement. Certificates provide instant confirmation while the sub-block system handles ordering and inclusion.
Transaction Format
Allegro transactions wrap Tempo transactions with additional metadata. The tempo_tx field contains signed EVM transaction bytes. The overlay field provides payment metadata for validation.
#![allow(unused)]
fn main() {
struct FastPayTx {
chain_id: u64,
tempo_tx: Vec<u8>, // Signed EVM transaction
nonce_key: [u8; 32], // 2D nonce key
nonce_seq: u64, // Sequence within key
expiry: Expiry, // Block height or timestamp
parent_qc_hash: [u8; 32], // For chained payments
tempo_tx_format: TempoTxFormat, // Transaction format discriminator
overlay: OverlayMetadata, // Payment metadata for validation
}
}
The tempo_tx_format field identifies the encoding format of tempo_tx. Currently only EVM_OPAQUE_BYTES_V1 is supported, representing standard Ethereum transaction encoding.
The overlay field contains payment metadata that sidecars validate against the decoded tempo_tx. This includes the sender, recipient, amount, and asset. Sidecars independently decode the EVM transaction and verify the overlay matches before signing.
#![allow(unused)]
fn main() {
struct OverlayMetadata {
payment: PaymentIntent, // Sender, recipient, amount, asset
}
}
The tempo_tx bytes use standard Ethereum encoding. Allegro currently supports ERC-20 transfer calls to TIP-20 payment addresses.
TIP-20 Payment Format
TIP-20 tokens use an address prefix to indicate payment compatibility. Addresses starting with 0x20c0 are recognized as TIP-20 payment tokens.
Token address: 0x20c0...
Call data: transfer(address,uint256) with selector 0xa9059cbb
Allegro validates that the transaction target has the TIP-20 prefix. This restricts Allegro to payment transactions and prevents arbitrary contract execution.
SDK Integration
The tempo-alloy Rust crate provides Tempo-specific transaction types. Allegro uses alloy for transaction encoding and signature recovery. The user client constructs signed transactions using these libraries.
#![allow(unused)]
fn main() {
let tx = TxLegacy {
chain_id: Some(chain_id),
nonce: 0,
gas_price: 1,
gas_limit: 80_000,
to: token_address.into(),
value: U256::ZERO,
input: Bytes::from(transfer_calldata),
};
let signed = signer.sign_transaction_sync(&tx)?;
}
This example shows transaction construction with alloy. The signed bytes are included in the FastPayTx for submission to sidecars.
Related Documentation
See System Architecture for the overall Allegro design.
See Validator Sidecar for certificate issuance.
See Demo Scenario for the end-to-end demonstration.
Demo Scenario
This document describes the Allegro demonstration scenario showing chained payments completing before block finalization.
Overview
The demo involves five actors completing two chained payments within a single block interval. Alice pays Bob, then Bob immediately spends those funds to pay Carol. Both payments achieve QC-cleared status before the next block height advances.
sequenceDiagram
participant Alice
participant Validators
participant Bob
participant Carol
participant Chain
Note over Alice,Chain: Block N (preconfirmation)
Alice->>Validators: Pay Bob $10
Validators-->>Alice: Certificates
Alice->>Alice: Form QC1
Bob->>Validators: Pay Carol $10 (parent: QC1)
Validators-->>Bob: Certificates
Bob->>Bob: Form QC2
Carol->>Carol: Verify QC2
Chain->>Chain: Block N+1 settles
Both payments complete with QC confirmation before the block height advances, demonstrating the preconfirmation property.
Actors
Three user clients participate in the payment flow.
| Actor | Starting Balance | Role |
|---|---|---|
| Alice | $15 | Initiates first payment |
| Bob | $5 | Receives from Alice, pays Carol |
| Carol | $5 | Receives final payment |
Two validators run Allegro sidecars.
| Validator | Role |
|---|---|
| Dave | Issues certificates, runs sidecar |
| Edgar | Issues certificates, runs sidecar |
The Allegro service aggregates certificates and provides the web UI.
Payment Flow
The demo executes two payments in sequence.
- Alice pays Bob 10 USD
- Bob pays Carol 10 USD using the QC from payment 1 as parent
After both payments complete, the balances are Alice 5 USD, Bob 5 USD, Carol 15 USD. Bob’s balance remains 5 USD because he received 10 USD and immediately spent 10 USD.
Demo Configuration
The Tempo chain runs with a 5-second block time for visual clarity. This provides enough time to observe QC formation before block inclusion.
[chain]
block_time_seconds = 5
The demo uses a 2-of-2 threshold. Both Dave and Edgar must sign for a QC to form.
Detailed Information Flow
This diagram shows the complete flow with sidecars wired to Tempo nodes and the Allegro service aggregating certificates.
sequenceDiagram autonumber participant A as Alice Client participant B as Bob Client participant C as Carol Client participant S as Allegro Service participant D as Dave Sidecar participant E as Edgar Sidecar participant ND as Dave Tempo Node participant NE as Edgar Tempo Node Note over D: Sidecar wired to node Note over E: Sidecar wired to node Note over S: Mirrors validator bulletin boards S->>D: Subscribe/Poll BulletinBoard S->>E: Subscribe/Poll BulletinBoard D-->>S: Certificates (CD) E-->>S: Certificates (CE) A->>D: Submit FastPayTx T1 (A to B $10, nonce_key=FP, seq=a1, expiry) A->>E: Submit FastPayTx T1 D->>ND: Read base state (balances/nonces) and apply overlay checks E->>NE: Read base state (balances/nonces) and apply overlay checks D-->>A: CD(T1) E-->>A: CE(T1) D-->>S: CD(T1) E-->>S: CE(T1) B->>S: Get certificates for T1 S-->>B: CE(T1)+CD(T1) B->>B: Assemble QC1 B->>D: Submit FastPayTx T2 (B to C $10 + QC1) B->>E: Submit FastPayTx T2 (B to C $10 + QC1) D->>ND: Enqueue T1/T2 into sub-block tx pipeline E->>NE: Enqueue T1/T2 into sub-block tx pipeline D-->>S: CD(T2) E-->>S: CE(T2) S->>S: Poll validators for block height and track QC-clear vs height C->>S: Query status / view UI S-->>C: QC2 cleared before next block
The key observation is that Carol sees the QC-cleared payment before the block height increments. This demonstrates the preconfirmation property.
Verification Steps
The demo UI displays the following to verify correct operation.
- Current block height from chain polling
- List of Allegro transactions with their stage (ACCEPTED, CERTIFIED, INCLUDED)
- Timestamp showing QC formation occurred before block height change
The successful demo shows both T1 and T2 reach CERTIFIED stage while the block height remains constant.
Related Documentation
See System Architecture for the overall system design.
See Aggregator Backend for the Allegro service implementation.
See Tempo Integration for sub-block transaction routing.