Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overview

Telcoin Network is an EVM-compatible, public blockchain secured by GSMA Operator Member Mobile Network Operators globally using proof of stake consensus. The system serves as underlying blockchain architecture securing private property rights over digital assets and enabling on-chain protocols and applications such as TELx.

This documentation is designed to help familiarize builders with the Telcoin Network. It gives background on the tech behind the Telcoin Network, contains a full list of methods supported by the network, and provides some tutorials for developing some initial projects.

Brief run downs of the docs sections are given below:

  • Getting Started - Guides to help developers start building with Telcoin Network.
    • Reading Blockchain Data - An overview of the simplest types of blockchain interactions.
    • Dapp Development - Let’s start building decentralized apps!
  • Networks and RPC Endpoints - Reference documentation for connecting to Telcoin Network.
  • RPC Methods - Reference documentation providing information on interacting with nodes.

Windows users

Windows users are strongly advised to use WSL2. Start with these instructions on installing WSL2 and then follow this tutorial to set it up to work with VS Code (If VS Code is your preferred IDE).

WSL2 gives a linux style environment and avoids some compatibility issues with Windows systems.

Warning

All commands throughout the documentation will be focused on use with Unix-like (e.g. WSL2) systems.

Architecture

Key Components

Telcoin Network is a modular blockchain. This is in contrast to many traditional (monolithic blockchains). While a monolithic blockchain performs a number of functions on a single layer, a modular blockchain separates the blockchain’s services into separate modules, each specializing in one aspect of the blockchain’s offering. This allows for greater flexibility, efficiency, and scalability.

The separate modules, or layers, within Telcoin Network are introduced below.

Consensus Layer

The consensus layer ensures agreement between validator nodes on the state of their common ledger. It handles transactions broadcast to the network by checking each transaction is valid and is ordered correctly. In this way, it is critical to the smooth and secure operation of the blockchain without the need of a centralized party.

Two key technologies, Narwhal and Bullshark, contribute to Telcoin Network’s consensus layer. Narwhal is primarily concerned with the mempool - the initial handling of transactions which are broadcast to the network, while Bullshark handles the finalizing and recording of transactions within the Settlement Layer.

Execution Layer

The Execution Layer acts as the blockchain’s processing unit, analogous to the central processing unit (CPU) of a computer. It processes user initiated transactions, which can range from sending the network’s native token, TEL, between accounts, to the more complex execution of smart contracts that govern decentralized applications (dApps). Telcoin Network uses the Ethereum Virtual Machine (EVM) for its execution layer, meaning it can run any smart contracts present on other EVM chains.

Data Availability Layer

The Data Availability Layer guarantees that all necessary data for validating transactions is readily accessible to all network participants. Telcoin Network utilizes a scalable number of ‘workers’ which share collections of transactions, thus ensuring the data required for processing (execution) and validating (consensus) is available to all nodes. This mechanism supports the data availability layer by making sure that all necessary information for confirming the state of the blockchain is accessible, enabling nodes to execute and validate transactions accurately.

Settlement Layer

The Settlement Layer is akin to the blockchain’s ledger, ensuring the permanence and immutability of transactions. This layer serves as the ultimate source of truth within the blockchain, providing finality and certainty to all operations.

Bullshark is integrated with Narwhal to finalize transactions and ensure they are immutably recorded on the blockchain. It employs a Byzantine Fault Tolerant (BFT) consensus algorithm, optimized for finality and security. Bullshark is responsible for reaching agreement on the state of the blockchain, ensuring all nodes are synchronized and that transactions are irrevocably settled. Settlement happens quickly and efficiently, without the concept of “forks” or “reorganizations”.

Native Token

Overview

Native tokens are what blockchain protocols require users to pay for transaction processing (gas). The native token for Telcoin Network is Telcoin (TEL).

Challenges

Telcoin originated as an ERC-20 token on Ethereum.

The industry is rife with examples of new chains launching their own native tokens, then wrapping them. But other than Polygon and MATIC, there isn’t a good example of an ERC-20 becoming a native token. Polygon’s approach is really complicated, partially because they maintain state with Ethereum via StateSync.

Polygon validators continuously monitor a contract on Ethereum chain called StateSender. Each time a registered contract on Ethereum chain calls this contract, it emits an event. Using this event Polygon validators relay the data to another contract on Polygon chain. This state sync mechanism is used to send data from Ethereum to Polygon.

Additionally, Polygon validators send the transaction hash, namely checkpoint, of each transaction on the PoS chain to Ethereum on a regular basis. You can use this to validate any transaction that took place on Polygon. Once a transaction has been verified to have occurred on the PoS chain, the corresponding action can then be executed on Ethereum.

These two mechanisms can be used together to enable two-way data (state) transfer between Ethereum and Polygon. source

Telcoin Network does not intend to maintain state with Ethereum in the same way. Telcoin Network aims to be an EVM-compatible layer 1 blockchain that uses the ERC-20 Telcoin as its native token. TN is different because core state for the protocol is managed on TN, not Ethereum. The protocol only supports bridging assets, and does not intend to sync state with Ethereum other than passing messages or bridging wrapped tokens.

Bridging

Telcoin already exists on Ethereum, so it must be bridged to Telcoin Network for the network to have access to TEL (the native currency).

Parallelization

It’s possible to optimistically parallelize transactions that do not affect the same state (ie - on-chain programs). However, if bridged Telcoin remains in a smart contract then it becomes nearly impractical to parallelize TEL ↔︎ TEL transfers. As a native token, balances are managed by the node directly so multiple transactions can be processed at the same time which increases transaction throughput. Thus, there is compelling motivation to figure out how to bridge the ERC-20 version of TEL from Ethereum while also using it like ETH (state managed by protocol - not on-chain).

To elaborate, Telcoin and other ERC-20s belong to an address. Ether also belongs to an address. However, when ETH is transferred between accounts, the nodes update the database with that address and a value for Ether. When ERC-20s are transferred, the smart program’s state is updated and the nodes update the database with that address and the storage bytes. State on-chain must be synchronously executed to ensure valid roots are calculated during the state change.

Solution

Telcoin is bridged as an ERC-20 on Ethereum to a wrapped version on Telcoin Network. The wrapped version is not the native token (wTEL), but can easily be converted to the native token TEL.

Axelar

Axelar supports cross-chain transfer of some native tokens via their wrapped ERC-20 versions.

Bridge ERC-20 TEL as ERC-20 wTEL on Telcoin Network

If wTEL on Telcoin Network is bridged, how can users convert it to the native currency TEL without having any TEL for gas?

Axelar supports this through a “Swap Contract”.

The native EVM cross-chain asset transfer feature uses Axelar’s Deposit Service, an ancillary infrastructure contract deployed on all supported EVM chains.

For transactions sending native tokens from an EVM chain, a deposit address is generated by the user or dApp that is set up to receive the native tokens, after which the service kicks in to convert (or “wrap”) the tokens into their ERC-20 equivalent before sending to the users’ specified wallets on the destination chain.

For transactions sending tokens back to their native EVM chain, where users receive native tokens in their wallets, the following flow applies. The funds are first sent from the source chain to a “Swap Contract” on the destination chain. Axelar services then kick in to “unwrap” the tokens before delivering native assets to the intended recipient.

Implementation

Telcoin Network’s TEL could behave like ETH on Ethereum (ie - state managed directly, not through smart program) by bridging the ERC-20 TEL from Ethereum to a wrapped ERC-20 on Telcoin Network then using WETH9 to withdraw the fund to the user’s address.

Axelar’s Interchain Token mechanism would lock/release ERC-20 TEL on Ethereum and mint/burn wrapped TEL (wTEL) on Telcoin Network. Telcoin Network would then support users unwrapping the wTEL to withdraw TEL (on TN) as the native token.

Link to Axelar contract implementation

// @dev This function is used for delegate call by DepositReceiver
// Context: msg.sender == AxelarDepositService, this == DepositReceiver
function receiveAndUnwrapNative(address refundAddress, address recipient) external {
    address wrappedTokenAddress = wrappedToken();
    address refund = IAxelarDepositService(msg.sender).refundToken();

    if (refund != address(0)) {
        if (refundAddress == address(0)) refundAddress = msg.sender;
        _safeTransfer(refund, refundAddress, IERC20(refund).balanceOf(address(this)));
        return;
    }

    uint256 amount = IERC20(wrappedTokenAddress).balanceOf(address(this));

    if (wrappedTokenAddress == address(0)) revert WrappedTokenNotSupported();
    if (amount == 0) revert NothingDeposited();

    // Unwrapping the token into native currency and sending it to the recipient
    IWETH9(wrappedTokenAddress).withdraw(amount);
    (bool sent, ) = recipient.call{ value: amount }('');

    if (!sent) revert NativeTransferFailed();
}

Note: Ensure our implementation of IWETH9 emits a log for bridged TEL. These logs should be used to prove the amount of Telcoin.

Security Considerations

Axelar’s bridge is risky for an early Layer 1 because it provides an easy and efficient escape route for thieves. Ethereum benefited immensely from the fact that it wasn’t bridged “from” anywhere (unlike TN) because after the DAO hack, validators opted to recover funds by forking the network. If a substantial hack took place and Axelar (or any bridge) was a fundamental piece of TN, the affected state would be impossible to rectify without significant changes to both bridged chains. The effect of Axelar’s chain changes would ripple through all other connected ecosystems. It’s impossible to consider forking TN as a realistic solution after an attack unless bridging off TN takes time.

Recoverable Wrapped Tokens

One idea for TN to safeguard the protocol from early exploits tanking TEL is to extend the ERC-20 interface to support asset recovery with the “ERC-20R”. CircleCI has an audited solution for wrapped tokens:

This proposed mechanism revolves around recoverable wrapper tokens. Users can wrap their ERC-20 asset by locking them in the recoverable wrapper token contract in order to receive an equal number of wrapper tokens in return. Wrapping the tokens protects users from thefts, and still maintains most of the utility found in the token’s base form. One core difference, however, is that recoverable wrapper tokens can be recovered back to the sender within a certain time window post-transaction (say, 24 hours). Consequently, each user will have two distinct balances of the token: a settled balance (non-recoverable) and an unsettled balance (recoverable). Only settled tokens may be unwrapped back into their base form.

Building beyond the thesis put forth in the ERC20R paper, we present multiple configuration sets, or ways to implement and design a recoverable wrapper token, each with their own use cases and attributes. For instance, one version is an arbitrated wrapper appropriate for a targeted, designated ecosystem with a trusted governance. Another version offers more of a cancellable send button, where transactions simply take a longer, custom period of time to settle than the chain’s finality. Different configuration sets can still be interoperable with each other, as long as they conform to a shared interface. We provide the IERC20R contract as this interface.

The bridged version of TEL on Telcoin Network would initially be deposited as wTEL then immediately swapped and withdrawn to the user’s account. However, when users initialize a deposit to wTEL they maintain an “unsettled balance” (recoverable) until a certain amount of time has passed.

This seems possible with Axelar’s current implementation because the bridge wraps tokens first on the source chain:

// @dev This method is meant to be called directly by user to send native token cross-chain
function sendNative(string calldata destinationChain, string calldata destinationAddress) external payable {
    address wrappedTokenAddress = wrappedToken();
    uint256 amount = msg.value;

    if (amount == 0) revert NothingDeposited();

    // Wrapping the native currency and into WETH-like token
    IWETH9(wrappedTokenAddress).deposit{ value: amount }();
    // Not doing safe approval as gateway will revert anyway if approval fails
    // We expect allowance to always be 0 at this point
    IWETH9(wrappedTokenAddress).approve(gateway, amount);
    // Sending the token trough the gateway
    IAxelarGateway(gateway).sendToken(destinationChain, destinationAddress, wrappedSymbol(), amount);
}

Note: The wTEL deposit and approve methods should work together to cause this to fail. Although it’s not ideal, in the worst case: user tries to send native through bridge and it fails first time. User comes back after recovery period ends and is able to send again. We can mitigate this poor UX by warning users to wrap their own TEL first and wait for the recovery period. We should also try to work with Axelar to support a recoverable wrapped ERC-20s.

Validator Issuance

Validators are issued native TEL rewards at the end of each epoch, weighted by the number of consensus blocks they led. The current TEL issuance is inside a governance safe. TN needs this TEL bridged at genesis if possible. If not possible, then we should consider manual issuance and upgrading the protocol once bridging is complete.

Security

Security Features

Telcoin Network ensures security of digital assets to asset owners. This is achieved through the following key principles:

  • Private signature keys are required to authorize any transaction on the Telcoin Network.
  • Transactions interacting with a particular asset can only be authorized by the owner of the given asset.
  • Smart contracts contain predefined rules which govern the use of particular assets. These rules are created by the smart contract creator and are written in the Solidity language.
  • Upon transaction finalization, the changes to the assets it operates on or creates will be persisted to the Telcoin Network ledger. Future transactions will then be able to operate on these assets.
  • Telcoin Network’s security is preserved even when a small proportion of validators are adversarial.
  • Every Telcoin Network operation can be audited to validate that assets have been correctly processed. This boosts transparency but also implies all network actions are publicly visible. Users may wish to maintain privacy by using multiple different addresses.
  • Block construction is handled exclusively by known validators, preventing anonymous MEV attacks.

Security Architecture

Telcoin Network is run by a set of distinct validators run by Global Systems for Mobile Communications Association (GSMA) members. They run Telcoin Network software that allows them to reach consensus on valid transactions within the network. Users can stake their native TEL tokens on validator nodes, increasing that validator’s staking power. The use of Byzantine fault tolerant broadcast and consensus ensures Telcoin Network remains secure so long as over 2/3 of the network’s total stake is controlled by honest and non adversarial validators.

Keys

A transaction on the Telcoin Network is only valid if signed with the private key which controls the assets operated on by the transaction. This private key must be kept safe by the user and never shared with other parties. This ensures that even an adversarial validator cannot control assets not owned by them.

Each private key corresponds to a public address to which assets can be sent. Sending assets to a specific address automatically creates that address. It is therefore as though all possible addresses exist from the genesis block.

Smart Contracts

The behaviour of all non native assets on Telcoin Network is defined by a smart contract. Telcoin Network has a few built in system contracts, but also enables anyone to write and deploy their own smart contracts.

This permissionless property enables huge innovation but requires more care to be taken by users; It is recommended users only interact with smart contracts they trust, have been audited by individuals they trust, and they understand the logic of. Smart contracts are immutable, allowing them to be audited by third parties and enabling greater degrees of trust.

Finality

The Byzantine fault tolerant consensus mechanism “Bullshark” ensures transactions on Telcoin Network are confirmed quickly. Successful transactions are immediately finalized, resulting in no unexpected reorganizations of chain data.

Gas must be paid, in the form of TEL, to pay for the compute required to process a transaction. Transactions can be successful or may be aborted - either due to conditions within the smart contract not being met or due to an insufficient amount of gas being paid. Aborted transactions will have no effect on the state of the blockchain, but will still require some gas to execute. This also protects the system against denial of service attacks.

Censorship Resistance

Telcoin Network uses the Delegated Proof of Stake model to select validators. Validators are run by GSMA members and other Mobile Network Operators (MNOs) and allow users to stake their TEL on validators, sharing in revenue from gas fee income. In the rare case of a validator demonstrating poor reliability, the validator and the stakers who have staked to it will receive reduced rewards. The user’s stake will always be safe, and cannot be confiscated by any network user, including the validators with whom they have staked. This mechanism ensures that Telcoin Network will continue to be run to the highest standards of reliability, honesty and liveness.

Transaction lifecycle

  1. Transaction creation - A user creates a transaction. This is typically done through a wallet interface or decentralized application (dApp) user interface. An example may be transferring TEL to another user. The user will use the user interface off the dapp or wallet to signal their intentions. They will then sign their transaction using their private key (most likely through their wallet). The signed transaction is now immutable and ready to be sent to a validator.

  2. Transaction batching - Upon receipt of the transaction, validators check the validity of the transaction. It does this by ensuring the transaction has:

    1. A valid user signature.
    2. Been initiated by a user who has access to all assets the transaction is trying to control. In this case, the user must have at least the amount of TEL they are sending (+ gas) present in their wallet.
    3. Correctly encoded data, if the transaction interacts with a smart contract.
    4. Been sent with sufficient gas.

    If the above conditions are met, the validator places the transaction in the pending pool. When the validator’s worker is ready to propose the next batch, valid transactions are pulled from the transaction pool until a gas limit of 30 million is reached or there are no more transactions in the validators pool. The worker then broadcasts the sealed batch to the workers of the other committee validators, which validate the batch and acknowledge it. Once a supermajority (2f+1 by voting power) of the committee acknowledges the batch, its digest is handed to the validator’s primary for inclusion in consensus.

  3. Transaction finalization - The validator’s primary includes the digests of batches that reached quorum in its next header and broadcasts the header to the other validators. Validators ensure the validity of the header and its batches before returning a signed vote. Once votes from a supermajority of the committee are collected, the primary assembles them into a certificate and broadcasts it to the network. Certificates form a DAG that the Bullshark protocol deterministically orders: when a leader certificate gains enough support, its sub-DAG is committed as consensus output, which every node then executes - each batch becoming a block in the EVM. At this point, the transaction is executed and is irreversible. It is finalized.

Blocks are only produced from committed consensus output, so there are no forks or reorganizations. For this reason, Telcoin Network does not have any concept of pending blocks.

Consensus Layer

Incorporating Bullshark, a DAG-based Byzantine Fault Tolerant consensus mechanism, into the blockchain architecture alongside Narwhal’s DAG-based mempool represents a forward-thinking approach to distributed ledger technology. Bullshark distinguishes itself by being the first Directed Acyclic Graph (DAG) based asynchronous Byzantine Atomic Broadcast protocol, optimizing for common synchronous cases while ensuring robustness in asynchronous environments. Telcoin Network implements Bullshark’s partially synchronous variant, which preserves safety even when the network is asynchronous while relying on periods of synchrony for liveness.

Narwhal: The DAG-based Mempool

Narwhal’s innovative mempool design uses a Directed Acyclic Graph (DAG) structure for efficient transaction management prior to confirmation. This design choice facilitates a more scalable and efficient transaction dissemination process compared to traditional linear mempools.

Key Features:

Parallel Processing: The DAG structure allows for concurrent transaction processing, significantly boosting throughput.

Increased Efficiency: By organizing transactions in a DAG, Narwhal mitigates the inefficiencies of linear mempools, enhancing overall network performance.

Scalability: The design inherently supports scalability, adept at managing high transaction volumes without compromising on performance or resource efficiency.

Bullshark: Byzantine Atomic Broadcast

Bullshark represents a groundbreaking step in consensus mechanisms, offering a secure and efficient way to achieve consensus in distributed networks. As a Byzantine Atomic Broadcast protocol based on a DAG, Bullshark is tailored for high-performance, distributed systems, with optimizations for both synchronous and asynchronous operations.

Key Features:

Safety Under Asynchrony: Bullshark’s safety guarantees do not depend on timing assumptions. Telcoin Network runs the protocol’s partially synchronous variant, which remains safe through network delays and timing attacks, requiring only periods of synchrony to make progress.

Optimized for Synchronous Cases: While Bullshark tolerates asynchrony, it is optimized for the more common synchronous cases, ensuring high throughput and low latency during normal operations.

Security in Asynchronous Networks: Bullshark maintains strong security guarantees, even in asynchronous settings, ensuring the integrity and reliability of the consensus process amidst a wide range of network conditions.

Further Reading

  • Danezis, G., Kogias, E. K., Sonnino, A., & Spiegelman, A. (2021). Narwhal and Tusk: A DAG-based Mempool and Efficient BFT Consensus. ArXiv:2105.11827 [Cs]. http://arxiv.org/abs/2105.11827
  • Spiegelman, A., Giridharan, N., Sonnino, A., & Kokoris-Kogias, L. (2022). Bullshark: DAG BFT Protocols Made Practical. ArXiv:2201.05677 [Cs]. https://arxiv.org/pdf/2201.05677

Peer-to-peer network

Every Telcoin Network node runs the same software and executes every transaction. Nodes differ only in what they do on the peer-to-peer network: which topics they publish on, which they subscribe to, and how they hand accepted transactions to the committee.

This page is for RPC providers, dapp and indexer operators, bridge partners, and validators. It describes the three node roles, the swarms a node runs, and how the chain id isolates one network from every other Telcoin Network deployment. The P2P Network section covers each subsystem in detail.

The swarm and subscription logic lives in crates/network-libp2p/src/consensus.rs, and the per-epoch wiring that decides a node’s role lives in crates/node/src/manager/node/start_epoch.rs.

Node roles

A node’s role is chosen at every epoch boundary and can change mid-epoch. A node is an Observer if it is not in the entering committee. The role is derived from committee membership and is not selectable by a flag; to take a validator out of consensus, exit it on chain. Otherwise it starts the epoch as CvvActive, optimistically assuming it is caught up, and is demoted to CvvInactive later if that turns out to be false. An existing CvvInactive state is sticky: it is carried into the next epoch unchanged until the node finishes catching up.

The current role is readable live over RPC as tn_nodeMode, so callers can observe transient states such as a restarted validator catching up.

CvvActive

An active committee validator is fully synced and voting. It publishes certificates on tn-primary-{chain_id}, a signed consensus result for each committed sub-dag on tn-consensus-output-{chain_id}, and epoch votes on tn-epoch-vote-{chain_id}. Its workers subscribe to their own batch topic, tn-worker-{chain_id}-{worker_id}, so batch bodies are prefetched into a local cache before the vote path needs them.

Batches are not gossiped to reach quorum: a worker reports each sealed batch directly to every committee peer over the worker request-response protocol and waits for a quorum of acknowledgements; only then does it publish the batch digest on the batch topic. See Request-response for that exchange and Gossip for the topics and their publisher allowlists.

CvvInactive

A node in this mode is staked and in the committee but behind the rest of it, typically after a restart or a failure mid-epoch. It does not run consensus; it follows consensus output through state sync until it is past the garbage-collection window, then promotes itself back to CvvActive and restarts the epoch. The promotion is guarded: the node must reach the same consensus block number as the network’s latest known header, and that header’s commit timestamp must be less than five seconds old, so a stale record cannot promote a node that is not actually caught up.

An inactive validator stays subscribed to its worker batch topic: warming the batch cache it will shortly vote against is useful work, and a mode change does not clear that cache, so the warm-up survives promotion.

Observer

An observer is any node that is not in the current committee. It follows consensus output only and never votes.

An observer unsubscribes from the worker batch topic. Batch bodies already arrive inside the verified consensus output and epoch packs it downloads through sync streams, so digest gossip would only fetch the same bytes a second time.

An observer also does not gossip the transactions it accepts; it forwards each one over JSON-RPC to the committee validator whose committee slot owns the sender, using endpoints discovered from validator records on the DHT. Routing by sender keeps a single account’s transactions on one validator, which preserves nonce ordering. Delivery is best-effort on a background task, so batch production is never blocked by a slow or unreachable validator.

Note

If no committee validator has advertised a JSON-RPC endpoint, an observer cannot forward at all. The batch is refused rather than dropped, and its transactions stay pending in the pool for a later attempt.

Swarms and epoch interfaces

A node runs one libp2p swarm for its primary and one more for each configured worker. Every swarm speaks QUIC and runs as a critical task for the life of the process; see Transport. Each worker’s swarm is fully separate, so a batch gossiped by worker k of one validator only ever reaches worker k of the others.

Epoch boundaries do not rebuild swarms; they rebuild the network interface layered on top of them. Each epoch refreshes the previous, current, and next committee membership, re-dials committee peers, and re-subscribes to the gossip topics so their publisher allowlists track the rotation. Listeners bind only on the first epoch of the process.

Because the swarm outlives the epoch, subscription decisions are two-sided rather than skipped. A node that subscribed to a batch topic in one epoch stays subscribed into every later epoch unless it explicitly unsubscribes, which is why an observer issues an unsubscribe rather than simply not subscribing.

Chain namespacing

The chain id comes from genesis and is stamped onto the network configuration at startup. It is not an operator tunable: it is never serialized to the config file, so genesis remains the single source of truth.

Every gossip topic embeds it:

tn-primary-{chain_id}                 certificates
tn-consensus-output-{chain_id}        signed consensus results
tn-epoch-vote-{chain_id}              epoch-boundary votes
tn-worker-{chain_id}-{worker_id}      batch digests

So does every wire protocol, including gossipsub’s own protocol id, which is the one family that would otherwise be shared across chains:

gossipsub        /tn-meshsub-{chain_id}/1.1.0 and /1.0.0
request-response /tn-primary-{chain_id}/0.0.2
                 /tn-worker-{worker_id}-{chain_id}/0.0.2
kademlia         /tn-primary-kad-{chain_id}/0.0.1
                 /tn-worker-{worker_id}-kad-{chain_id}/0.0.1
sync streams     /tn-primary-sync-{chain_id}/0.0.1
                 /tn-worker-{worker_id}-sync-{chain_id}/0.0.1
peer exchange    /tn-primary-peer-exchange-{chain_id}/0.0.1
                 /tn-worker-{worker_id}-peer-exchange-{chain_id}/0.0.1

Two nodes on different chains therefore fail protocol negotiation before exchanging anything, and never share a gossip mesh. The remaining protocol families sit at /0.0.1; only request-response has been bumped to /0.0.2, for a wire-format change that shifted encoded enum discriminants.

Where to read more

  • Transport — QUIC, connection limits, and the protocol id families above.
  • Gossip — topics, publisher allowlists, message validation, and re-propagation.
  • Discovery — the DHT, node records, and how committee endpoints are found each epoch.
  • Peers — scoring, penalties, bans, and the peer exchange sent on a graceful disconnect.
  • Request-response — header votes, batch reports, and epoch record requests.
  • Sync streams — the bulk transfer path for consensus output, epoch packs, and batches.

Source of truth

BehaviorCode
Role definitions and helperscrates/consensus/primary/src/consensus_bus.rs (NodeMode), crates/types/src/primary/node_mode.rs
Role selection, per-epoch subscriptions, swarm interfacescrates/node/src/manager/node/start_epoch.rs (identify_node_mode, should_subscribe_batch_topic)
Per-role consensus following and rejoincrates/consensus/executor/src/subscriber.rs (spawn_subscriber, catch_up_rejoin_consensus)
Gossip topic names and chain idcrates/config/src/network.rs (LibP2pConfig)
Wire protocol idscrates/network-libp2p/src/types.rs
Swarm construction and QUIC transportcrates/network-libp2p/src/consensus.rs (ConsensusNetwork::new)
Process-lifetime swarmscrates/node/src/manager/node.rs (spawn_node_networks)
Observer transaction forwardingcrates/consensus/worker/src/worker.rs (Worker::disburse_txns)

Update this page when those files change.

P2P Network

How Telcoin Network nodes behave on the wire: a QUIC-only transport, four permissioned gossip topics, a persistent Kademlia DHT of signed node records, a reputation-scored peer manager, two request-response protocols, and a bulk-sync stream behavior. It is written for RPC providers, dapp and indexer operators, bridge partners, and validators who need to know what a node dials, what it accepts, and what it rejects.

Transport covers the QUIC-only transport, node identities, listener addresses, and the full protocol-ID reference table. Gossip covers the four gossip topics, their committee publisher allowlists, and message validation, while Discovery covers Kademlia, signed node records, and the six ways a node finds the committee. Peers covers peer reputation scoring, connection limits, and bans. Request-Response covers the consensus RPC and peer-exchange protocols and their wire format, and Sync Streams covers the bulk-transfer stream behavior used for catch-up. Network Architecture has the node-role overview that frames all of them.

Transport

Telcoin Network nodes speak libp2p over QUIC and nothing else.

The validated handshake-start accounting contract describes the process budget API and the transport integration and calibration required before enabling it. This page states what a node opens on the wire, the two keys that identify it, where its listener addresses come from, and the full set of chain-namespaced protocol IDs its substreams negotiate.

It is written for RPC providers, dapp and indexer operators, bridge partners, and validators who need to predict what a node dials, what it accepts, and why a peer that looks reachable can still never exchange a message.

The swarm and its QUIC settings live in crates/network-libp2p/src/consensus.rs. The protocol IDs live in crates/network-libp2p/src/types.rs, and the transport defaults live in crates/config/src/network.rs.

The early identity refusal decision records why an additional TLS admission check is not currently selected and the admission and measurement prerequisites for reconsidering it.

QUIC only

The swarm is built with a single transport: QUIC v1 over UDP. There is no TCP listener and no TCP dialer, so a peer that offers only TCP is unreachable.

QUIC carries TLS 1.3 and multiplexes streams itself, so the node installs no noise handshake and no yamux muxer. The libp2p dependency is compiled with only the request-response, gossipsub, tokio, QUIC, macros, and kad features, which is the mechanical reason none of the usual alternatives are available at runtime.

Two behaviours operators commonly expect are also absent. There is no mDNS: a node never discovers peers by LAN broadcast. There is no identify protocol: a node does not learn a peer’s addresses or supported protocols from an identify exchange. Everything a node knows about a peer comes from the signed records in the DHT and from peer exchange, covered in Discovery.

Each node runs the primary and every worker as fully isolated swarms in one process. Seven behaviours compose each swarm: the peer manager, a per-peer connection limiter, gossipsub, the consensus request-response protocol, the peer-exchange request-response protocol, Kademlia, and the bulk-sync stream behaviour.

Connection parameters

The QUIC and swarm rows below are the shipped defaults, read from the node’s network config file with any missing field falling back to the value shown. The per-peer connection ceiling is not a config field: it is fixed in code and identical on every node.

SettingDefaultEffect
QUIC handshake timeout65 sCeiling on the initial handshake
QUIC max idle timeout30 sInactivity before the connection is torn down
QUIC keep-alive interval5 sInactivity before a keep-alive packet is sent
Max concurrent streams10,000Incoming bidirectional streams a remote peer may hold open on one connection
Max stream data50 MiBUnacknowledged bytes in flight on a single stream
Max connection data100 MiBUnacknowledged bytes in flight across all streams of a connection
Swarm idle connection timeout65 sHow long the swarm keeps an idle connection alive
Established connections per peer8Concurrent connections one peer may hold, inbound and outbound combined

The effective handshake ceiling is the smaller of the handshake timeout and the max idle timeout, so a handshake in practice has 30 seconds, not 65. The keep-alive interval is set well below the idle timeout on purpose: a healthy but quiet connection is refreshed roughly six times before it would expire. The swarm’s own idle timeout is deliberately not the binding constraint — the strategy is to let QUIC keep connections alive rather than add another swarm behaviour for it.

The per-peer ceiling of 8 is generous headroom. A peer needs at most one inbound and one outbound connection at a time, because this node dials only peers it is not already connected to. The remaining slack absorbs reconnection churn while bounding a hostile peer to a fixed, small number of connections instead of an unbounded fan-out. Peer counts are tracked by distinct identity, so without this ceiling one peer could open connections until the operating system ran out of descriptors. Peers covers the peer-count admission gate that sits alongside it.

Losing every listener is fatal. When the last listener closes, the swarm returns an error rather than continuing, and the node stops. A Telcoin Network node does not limp along unreachable.

Two keys, two identities

A node carries two distinct keys, and confusing them is the most common source of “I can see it but I can’t reach it”.

The libp2p network key is the transport identity. It produces the peer ID a node is dialed at, it terminates the QUIC handshake, and it signs every gossip message the node publishes — gossipsub runs in signed-authenticity mode, so an unsigned message is not a valid message. The primary and each worker hold separate network keys, because each runs its own swarm. Worker network keys are derived deterministically from the node’s BLS keypair and a fixed seed, so worker 0’s peer ID is stable across restarts and reinstalls — that identity is advertised on-chain and cached in other nodes’ DHT stores, so it must not change.

The primary’s BLS key is the consensus identity. It signs headers, votes, and certificates, it is the key registered on-chain, and it is the DHT record key under which a node is discoverable. Both the primary’s record and every worker’s record are stored under the primary’s BLS key.

The practical consequence: you look a node up by BLS key and you connect to it by peer ID, and neither value is derivable from the other by a third party. Signed node records are what bind the two together, and Discovery describes how they are published and verified.

Listener addresses

Listener addresses are set once, at key generation. keytool generate --external-primary-addr takes a single multiaddr for the primary and --external-worker-addrs takes a comma-separated list for the workers. Both also read from the environment at generation time, as TN_EXTERNAL_PRIMARY_ADDR and TN_EXTERNAL_WORKER_ADDRS.

An address must be QUIC v1 over UDP, in the form /ip4/HOST/udp/PORT/quic-v1. Do not append a /p2p/ component: the tool derives it from the network public key and appends it, and supplying a different one is an error. When either flag is omitted, the address defaults to /ip4/127.0.0.1/udp/PORT/quic-v1 on a free port, which is useful only for local test networks. The result is written to the node’s info file and is the address advertised to peers.

Two environment variables override the address a node binds at runtime: PRIMARY_LISTENER_MULTIADDR and WORKER_LISTENER_MULTIADDR. Each is read once, when its swarm first starts listening, and the node appends the /p2p/ component the same way key generation does. WORKER_LISTENER_MULTIADDR applies to worker 0 only. Higher worker ids always bind their configured address, because one variable cannot name several distinct listeners.

These variables change only what the node binds. The external address advertised to peers still comes from the node’s info file, so an override that does not match what was generated makes the node reachable at one address and discoverable at another.

Listener settings and bootstrap peers

Operators arriving from other clients go looking for --p2p-port or --listen-addr. Neither flag exists; --bootstrap-peers is available to override bootstrap dial hints for the current process. The node command’s flags cover the named chain, bootstrap dial hints, a consensus-metrics socket, an instance number, state export and its retention limit, the re-pack monitor, unused-port mode, a healthcheck port, a node name, and a tracing endpoint, plus a global data directory and the JSON-RPC server arguments.

Listener addresses come from the generated node info and the two override variables above. Bootstrap peers come from the network config’s bootstrap_peers map, falling back to the genesis committee when the map is empty. The --bootstrap-peers flag replaces that map for the current process; an explicit {} selects the genesis fallback. See Bootstrap peers for the map format and examples. The transport settings in the table above are read from the network config file in the data directory, where any field may be omitted to take its default. The chain id is not an operator tunable at all: it is stamped onto the network config from genesis at startup and is deliberately never written to the config file.

Protocol IDs

Every wire protocol is namespaced by the genesis chain id and by role, so a node advertises a distinct name per (chain, role) pair. {chain_id} is the genesis chain id and {worker_id} is the worker id.

PurposePrimaryWorker
Gossipsub/tn-meshsub-{chain_id}same prefix
Request/response/tn-primary-{chain_id}/0.0.2/tn-worker-{worker_id}-{chain_id}/0.0.2
Peer exchange/tn-primary-peer-exchange-{chain_id}/0.0.1/tn-worker-{worker_id}-peer-exchange-{chain_id}/0.0.1
Kademlia/tn-primary-kad-{chain_id}/0.0.1/tn-worker-{worker_id}-kad-{chain_id}/0.0.1
Bulk sync/tn-primary-sync-{chain_id}/0.0.1/tn-worker-{worker_id}-sync-{chain_id}/0.0.1

The gossipsub row is a prefix, not a complete id. Gossipsub appends its own versions, so a node on adiri, chain id 2017, advertises:

/tn-meshsub-2017/1.1.0
/tn-meshsub-2017/1.0.0

Gossipsub is the one protocol that would otherwise be shared across chains, because it negotiates its own /meshsub name independently of the names below it. Namespacing the topics alone would keep the messages apart but still let cross-chain peers open a gossip substream; folding the chain id into the protocol id closes that gap. Gossip topics covers the topics that ride on the mesh, Request-Response covers what rides on /0.0.2, and Sync Streams covers the bulk-sync protocol.

Why request-response is at /0.0.2

The request-response protocol is the only one above /0.0.1. Issue #739 deleted request and response variants that had been kept alive on the wire during a rollout: the streamed epoch, partial epoch, and consensus-output requests, the missing-certificates request, the worker streamed-batch request, and their acknowledgements and replies.

Those variants were dead but positional. Deleting them shifts the BCS discriminants of every variant that followed, so a message a /0.0.1 node encodes decodes as a different message on a /0.0.2 node. The version bump landed in the same change for exactly that reason: a /0.0.2 node never negotiates request-response with a /0.0.1 peer, so the two never exchange a stale discriminant. Kademlia, bulk sync, and peer exchange were untouched and stay at /0.0.1.

Warning

A node still on /0.0.1 looks healthy against a /0.0.2 network. Gossip, discovery, and bulk sync all negotiate normally, because those protocol names did not change. Only request-response silently fails to negotiate, which surfaces as a node that has peers but never completes a direct request.

Mismatches never negotiate

A chain-id mismatch, a role mismatch, or a worker-id mismatch all produce the same outcome: the substream is never negotiated. This is structural, not a check that runs after connecting. A primary and a worker advertise different names, so they cannot talk to each other even inside the same process. Two nodes on different chains advertise different names for every protocol, so they cannot talk at all. Worker k of one validator reaches only worker k of the others.

The failure mode is quiet by design. There is no error message describing the mismatch, because there is nothing to reject — the two peers simply have no protocol in common.

Source of truth

BehaviorCode
QUIC transport, swarm behaviours, per-peer connection ceiling, fatal listener losscrates/network-libp2p/src/consensus.rs
Protocol IDs and the gossipsub prefixcrates/network-libp2p/src/types.rs
QUIC and swarm defaults, chain-id namespacingcrates/config/src/network.rs
Network and BLS key derivationcrates/config/src/keys.rs
--external-primary-addr and --external-worker-addrscrates/telcoin-network-cli/src/keytool/generate.rs
PRIMARY_LISTENER_MULTIADDR and WORKER_LISTENER_MULTIADDRcrates/node/src/manager/node/start_epoch.rs
Node command flagscrates/telcoin-network-cli/src/node.rs

This page mirrors those files. Update this page when those files change.

Established connection calibration

The optional process_budget in the YAML network-config file allocates established QUIC resources across the primary and every configured worker swarm. Production limits and deployment qualification remain pending issue #1433. Omitting this section preserves the existing eight connections per peer and existing QUIC settings.

Allocation

The following is an arithmetic example, not a calibrated production recommendation:

process_budget:
  swarm_count: 2
  max_established_connections: 144
  max_established_connections_per_peer: 8
  max_inbound_streams: 4096
  max_receive_credit_bytes: 1073741824

For S swarms and process connection ceiling C, each swarm receives floor(C / S) connections. Let A = S * floor(C / S). Each connection receives at most floor(max_inbound_streams / A) incoming bidirectional streams and floor(max_receive_credit_bytes / A) bytes of connection receive credit. Remainders stay unused. Transport values saturate at u32::MAX, and explicitly lower quic_config settings remain lower. Stream receive credit cannot exceed the effective connection receive credit. In this example there are 72 connections per swarm, 28 streams per connection and 7,456,540 bytes of credit per connection. These are capacities, not observed usage.

The node checks swarm_count against one primary plus every configured worker before spawning any swarm. Include workers currently inactive on chain, since their swarms still run. Changing the worker count requires recalculating the allocation and restarting the node. An allocation too small to give each swarm one connection, or each connection one stream/byte, fails startup.

Inbound and outbound connections, committee members, trusted peers and ordinary peers all consume the same established limits. No peer bypass is installed in the connection-limit behaviour. Peer admission and trust rules remain separate. Swarms cannot borrow each other’s allocation, which preserves primary connection capacity when workers fill their allocation. This does not reserve service within a swarm: hostile peers can occupy its slots, and bulk traffic can still delay votes or epoch records. The inbound service class metrics show this delay, but nothing limits it. Admission policy and message scheduling need joint calibration.

Receive credit is advertised protocol capacity. It is not RSS or a bound on application buffers, tasks, CPU, locally initiated streams, pre-admission handshake state, or transient transport state before an established connection is accepted. Measure those independently. TCP is not configured by the current consensus swarm builder. Other network servers in the process require separate headroom.

Inbound service classes

Each swarm sends inbound requests and gossip to the application through one bounded queue. The swarm puts each inbound message in one service class:

ClassMessages
votePrimary vote requests (critical)
epoch_recordPrimary epoch record requests (critical)
certificate_syncCertificate catch-up requests on the request-response protocol (bulk)
batchWorker batch reports. ReportBatch is the 2f+1 quorum-ack request, so it is critical on worker swarms
gossipGossip messages
otherPeer exchange, stream catch-up, batch fetch and all other requests

No current primary request uses certificate_sync. Certificate catch-up and batch fetch use the stream protocol, and the swarm cannot see their class before it forwards them, so they are other. Thus other is not a low-priority class.

The classes only label the metrics below. They do not change admission, queue space or scheduling. A message is shed, whatever its class, for one of three reasons. queue_full: the queue is full. unsubscribed: no application task receives from a regular queue. The primary network event queue does not use this reason. admission: the application drops the request at its admission check. A shed request gets no response: the swarm drops the response channel, libp2p closes the stream, and the requester gets a stream error at once. It does not wait for its request timeout. The swarm does not schedule by priority, so unanswered requests of any class can fill the queue and make the swarm shed votes.

Observations

The connection metrics below have the configured network label (primary, worker-0, etc.). The rejection and denial counters also have a reason label:

MetricMeaning
tn_network_established_connectionsCurrent established connections across both directions
tn_network_established_connection_limitPer-swarm ceiling; zero means the legacy unbounded total
tn_network_inbound_streams_per_connection_limitEffective incoming stream capacity per connection
tn_network_receive_credit_per_connection_bytesEffective advertised credit per connection
tn_network_connection_limit_rejections_totalConnections refused by a connection limit. reason: pending_incoming, pending_outgoing, established_incoming, established_outgoing, established_per_peer, established_total or unknown
tn_network_inbound_connections_denied_totalInbound connections denied by a limit. reason: pending_incoming_limit, established_per_peer_limit, established_total_limit or other_limit

Connection occupancy updates after swarm event processing. Scrapes can miss short peaks, so record the sampling interval and use transport tracing when measuring peak streams or retained buffers. The connection count times the credit ceiling describes configured capacity on established connections, not bytes currently retained. Sum that product over every swarm on the same node. Never interpret missing samples as zero. The connection gauges do not give active-stream occupancy.

The service class metrics also have a class label with the six values above. The shed counter also has a reason label (queue_full, unsubscribed or admission). The failure counter also has an outcome label (timeout, omitted, closed, io or unsupported). The node registers every class, reason and outcome series at zero when the swarm starts, so a missing series means that the metric is not exported, not zero.

MetricMeaning
tn_network_inbound_requests_pendingInbound requests sent to the application that wait for a response
tn_network_inbound_request_service_secondsTime from sending a request to the application to sending its response. Answered requests only
tn_network_inbound_requests_shed_totalInbound messages dropped before service, by the swarm or at application admission
tn_network_inbound_requests_failed_totalInbound requests sent to the application that got no response

The exporter can render the service time as a summary (quantiles with _sum and _count) or as buckets. These metrics give queue occupancy and service time by class, not network round-trip time. An admission shed also counts as an omitted failure, so do not add the shed and failure counters.

Reproducible calibration record

Use ten validators, one primary and one worker swarm per node, on 8 CPU / 32 GiB hosts as the reference topology. Repeat for other supported worker counts. Record committee size, trusted peer population, admission exemptions, RTT/loss, storage, database size, catch-up distance, load generator revision/commands and the baseline node revision. Keep consensus, execution and storage headroom explicit in the process CPU/RSS/state budgets. Agree vote and epoch-record latency, persistence and catch-up regression thresholds with maintainers before selecting production limits.

tools/network-budget/capture.py records observations for an externally driven workload. It does not start validators, generate hostile traffic, instrument QUIC internals, or certify acceptance. Create a JSON manifest with these fields:

FieldRequired content
revisionFull 40-character commit of the node build
build_commandExact toolchain, flags and build command
topologyInteger validators, workers_per_node, cpus_per_node, ram_bytes_per_node
nodesOne object per validator, with unique name and metrics_url
artifactsPaths relative to the manifest for binaries, lockfile, all node configs, topology and threshold decisions
workloadExact commands, load generator revision, duration, baseline identity and traffic/topology details
decisionsAgreed thresholds, process headroom and rationale, or an explicit pending decision

For example, a node entry is {"name":"validator-0","metrics_url":"http://validator-0:9100/metrics"}. Hash inputs are streamed; the tool records hashes and paths, so archive the corresponding artifacts with the run. Capture the source tree including any patch used to build a dirty revision.

python3 tools/network-budget/capture.py calibration.json results/baseline --phase baseline --samples 600
python3 tools/network-budget/capture.py candidate.json results/catch-up --phase catch-up --samples 600
python3 -m unittest discover -s tools/network-budget -p 'test_*.py'

Use a fresh output directory for every run. The collector saves provenance, timestamped JSONL observations and scrape errors. It accepts only fixed resource metric names and topology-bounded labels, limits each HTTP response to 8 MiB, and exits nonzero if any scrape fails. Missing metrics and swarm labels are listed explicitly. If the exporter does not expose reth_process_resident_memory_bytes or reth_process_cpu_seconds_total, collect RSS and CPU with the deployment’s OS/container observer and archive those traces separately. Include task counts, active streams and retained buffers from dedicated tracing. The collector’s result always leaves acceptance pending. Each node entry can also set rpc_url. The collector then reads eth_blockNumber at each sample for persistence and catch-up, and records a missing value as missing, never as zero.

Harness, evaluation and derivation

tools/network-budget/harness.py runs the phases over ssh from a JSON inventory. plan prints every command and runs nothing. setup generates keys and genesis on the hosts. run stages the network config for one build, starts the nodes and calls capture.py. Only the candidate build writes process_budget, and the harness refuses a template that already sets it. The hostile and mixed phases need a load generator command in the inventory: generator, with a {target} placeholder, and hostile_targets. The generator must print one JSON object. No generator ships with this tool. Without one, the harness does not run these phases, and their thresholds stay pending.

Before the capture starts, run polls each metrics_url until it answers, for at most ready_timeout_secs (default 60). stop sends SIGTERM and waits for at most stop_timeout_secs (default 60). Then it sends SIGKILL. It keeps the pid file until the process exits, and it fails if the process is still alive. begin refuses a pid file that names a live process, and it appends to node.log after a start marker. In the reconnect phase, phase.json records an expected-down window for each restarted node. run ignores scrape failures only when all failures are accounted for inside those windows. An unexplained nonzero collector exit remains a failure.

evaluate.py gives pass, fail or pending for each threshold. Missing data is pending, never zero. A threshold is pending when any manifest node or swarm lacks the metric and class it needs in a phase. Counter deltas and CPU rates require at least two samples per series. A record with a scrape error, missing metrics or missing swarms also makes the phase pending outside an expected-down window. Scrapes overlapping those windows are excluded from measurements. Each service p99 is the worst (node, network) pair. critical-failures does not include the reconnect phase, because planned restarts close in-flight streams. critical-sheds includes it. It exits nonzero on any fail, and acceptance always stays “pending maintainer decision”. derive.py proposes a process_budget from the baseline honest phases: the peak times the headroom, divided like the node allocation. Its transport input is a JSON file with peaks from tracing. It refuses to run when an input is missing or has a gap that keeps evaluate.py pending. thresholds.proposed.json holds the proposed thresholds. Each threshold has the status “proposed” and a rationale that cites the Parameters defaults. Maintainers accept or change them in review.

python3 tools/network-budget/harness.py inventory.json plan --output results
python3 tools/network-budget/harness.py inventory.json run --build baseline --phase steady --output results
python3 tools/network-budget/evaluate.py tools/network-budget/thresholds.proposed.json results
python3 tools/network-budget/derive.py results transport.json --headroom 1.5 --output proposed-budget.json

Run and archive the following phases against baseline and candidate builds with identical state:

  1. Honest steady state, sustained catch-up and reconnect overlap. Measure peaks and tails for all swarms, including trusted peers and configured inactive workers.
  2. Hostile attempts to fill connection and stream ceilings from both one identity and many identities. Confirm rejection counters rise, occupancy stays within allocations, and closing connections allows recovery. Include committee/trusted identities where the test deployment permits it.
  3. Mixed catch-up and ceiling pressure while publishing votes and epoch records. Measure service occupancy and tail latency by message class, persistence throughput and catch-up completion.
  4. Repeat after reconnects and at every supported worker count. Account for handshake/transient state independently from established connection capacity.

Compare each workload with its pinned baseline and agreed thresholds. Record maximum connections, concurrent streams, credit, retained bytes, tasks, RSS, CPU and service occupancy separately. Leave missing measurements and threshold decisions pending. Only a maintainer-approved derivation and passing workload regressions can select defaults or complete #1433.

Validated handshake-start accounting

tn_network_libp2p::admission provides the accounting contract for issue #1434. It does not install a budget, change the shipped transport, or select production rates. The current libp2p pending-inbound swarm hook supplies an address without explicit address-validation evidence. Calling the accountant there would not establish the required boundary before expensive handshake work.

Transport integration contract

The future transport adapter must verify an address-bound QUIC Retry token, construct ValidatedSource from that result, and call HandshakeStartBudget::admit once before starting the expensive handshake. Merely receiving a packet, an address, a claimed PeerId, or a swarm event cannot supply ValidationProvenance::QuicRetry. The provenance type records a trusted in-process assertion; it does not verify tokens or make an arbitrary caller trustworthy. A changed source address needs its own validation.

Install one explicit policy at process startup. Give the same HandshakeStartBudget reference to the primary and every worker transport. Additional listeners and restarted swarms obtain the installed reference through process(); a second installation fails. Proceed only on Ok(StartOutcome::Admitted). Every other outcome or accounting error rejects the start. No PeerId or address allowlist bypasses any allowance. In particular, Retry proves reachability, not committee membership. This contract supplies no priority class or exemption for authenticated traffic.

Accounting and bounded state

Each admitted start atomically spends one process, source, and prefix allowance. A source key ignores ports and peer identities, canonicalizes IPv4-mapped IPv6 to IPv4, and groups IPv4 by /24 and native IPv6 by /64. Pending-occupancy integration must reuse SourceKey and SourcePrefix so it has identical normalization, while keeping its reservations separate. Closing a connection releases occupancy; it never refunds a handshake start. Those prefix widths are the accounting contract, not calibrated production rate values.

StartRate takes a burst and a duration to replenish one start. Debt uses monotonic time with checked arithmetic, retaining fractional refill time. The process allowance starts empty, including after a full restart. After one aggregate refill interval one start becomes eligible; after a full burst duration the whole burst becomes eligible. Per-source and per-prefix allowances start full but remain inside the physical process allowance. Ordinary reconnect bursts need no attacker to hit an undersized policy.

Each source/prefix table has a separate explicit capacity. Each entry has one debt record and one ordered expiry record. Admission and eviction take O(log capacity) work without scanning the table. Only completely refilled debt may be evicted. If every entry still has debt, a new key is rejected without changing any allowance. Existing keys can still spend their remaining credit. Table churn cannot discard debt to regain an early burst.

Policy replacement is rejected until all process, source, and prefix debt has refilled. A successful replacement clears only refilled entries, preserves counters, and starts the new aggregate allowance empty. Continuous traffic can therefore defer a policy change; operators must quiesce admission to guarantee an update. There is no reset or refund API. Future occupancy integration must also respect live reservations when transitioning policy.

snapshot() exports six saturating process counters and two retained-entry counts. StartOutcome::label() supplies the fixed metric label vocabulary; the accountant emits no per-attempt logs and exports no source/identity labels. A metrics adapter should export one process snapshot, rather than summing duplicate snapshots from every swarm. A poisoned accounting lock fails closed with PolicyError::Unavailable.

Calibration and remaining acceptance work

The deterministic tests exercise the contract using synthetic rates. They cover shared NATs and mapped addresses, IPv4 and IPv6 prefixes, concurrent callers, bounded churn, refill boundaries, restarts, policy changes, and fixed-cardinality telemetry. They are not a QUIC reconnect benchmark, proof of the transport boundary, or hardware qualification.

Before enabling a production policy:

  1. Integrate the minimum Retry transport build at the validated pre-handshake boundary. Share normalized keys and transition rules with pending-occupancy accounting.
  2. Measure honest reconnect bursts with primary and worker swarms, the supported worker count, shared NATs, multiple prefixes, restarts, and policy changes.
  3. Agree on peer population, network conditions, CPU/state budgets, and reconnect failure/time bounds, then select explicit rates, bursts, and table capacities.
  4. Repeat reconnect acceptance with those values. Table saturation is deliberately fail-closed and does not guarantee fairness under unlimited source diversity.
  5. Qualify the validator profile on representative hardware with host firewall enforcement disabled. Include source diversity and compare received traffic with generator limits. Treat the larger hub/shared-NAT profile as separate capacity work.

The accounting contract alone does not complete those acceptance criteria or close #1434.

Early identity refusal

Decision

Keep identity refusal at the existing established-connection checks. Do not add an earlier TLS admission callback for #1435. This is the issue’s explicit no-change outcome, reviewed against Telcoin Network 5736cc30 on 2026-09-24.

An earlier check can only save work for identities the admission policy already rejects. The current inbound identity check rejects self-connections and banned peers. It does not require an identity to belong to a committee, trusted-peer set, or bootstrap set. Moving that check into TLS would therefore still admit a fresh, unbanned identity at an otherwise admissible address with available connection capacity. It would not establish an authoritative admission policy.

No current-provider benchmark was run for this decision, and no handshake CPU, latency, or throughput savings are asserted. Potential savings on existing refusal cases remain unmeasured. The decision is to avoid adding a second policy enforcement point without a separately defined and tested admission contract or evidence that its benefit justifies the maintenance cost.

This decision does not complete the independent launch requirement for authoritative admission. That requirement needs its own implementation and tests. QUIC Retry, connection limits, and this investigation’s closure do not satisfy it.

Source evidence

The following observations are static source checks, not deployment or performance qualification:

BoundaryObserved behaviorSource
Established inbound identityRejects the local PeerId and banned peers, then returns a connection handler. There is no committee/trusted/bootstrap membership check in this hook.PeerManager::handle_established_inbound_connection
Established connection countThe connection-limit behavior caps connections per PeerId. A new identity has a separate count.connection_limits
QUIC configurationTelcoin configures the QUIC transport through with_quic_config. The pinned transport builds its TLS configurations using libp2p-tls; its TLS configuration fields are private.Telcoin swarm construction, libp2p-quic configuration
TLS providerlibp2p-tls selects the AWS-LC provider for both client and server configurations and installs its certificate verifier.libp2p-tls configuration
Certificate and transcript checksThe verifier parses the presented certificate and verifies the TLS 1.3 handshake signature. An early refusal must not turn a claimed identity into authenticated identity.libp2p-tls verifier

The lockfile pins libp2p 0.57.0, libp2p-quic 0.14.0, libp2p-tls 0.7.0, quinn 0.11.9, quinn-proto 0.11.18, and rustls 0.23.37. These versions and the provider must accompany any later profile. A result from a different provider or dependency revision is not evidence for this baseline.

The established identity hook is only one part of connection management. Address screening, capacity checks, scoring, and subsequent disconnection still apply as described in Peers. This decision does not characterize every unknown peer as guaranteed a usable connection.

Requirements before reconsidering

Authoritative admission first

Define and test the established-connection admission contract separately before introducing an optimization of that contract. Its tests must settle:

  • The allowed identities for each node role, including how committee, trusted, and bootstrap sets combine and behave across committee rotation.
  • How policy revisions become visible to connection admission, including a revision that changes while a handshake is in progress.
  • Whether existing live connections survive a policy change, and how any required disconnections are enforced.
  • The behavior for stale or missing policy inputs, the launch fallback, the explicit condition that closes that fallback, and recovery after stale input.

The established gate must remain authoritative if an earlier refusal is added. Both enforcement points must use the same contract. An early pass is not a reservation or permission to bypass the current policy at connection admission.

One shared handshake profile

Use one current-provider baseline and workload definition with the certificate-verification investigation and the key-exchange investigation. Record the Telcoin commit, lockfile, provider, build options, hardware, instrumentation, workload counts, and raw results so the three investigations do not attribute the same saved work to different changes.

Measure these boundaries and cases separately:

DimensionRequired evidence
Before a peer certificate is availableListener and handshake work already performed before an identity-based refusal could run. Do not credit this work as saved by a certificate-stage check.
After a peer certificate is availableIdentity extraction and policy lookup cost, certificate/extension verification, TLS transcript verification, and remaining work until the established gate. Measure the candidate’s actual refusal point.
Refusal frequencyCounts and reasons for policy refusals, including fresh unknown identities, self-connections, banned peers, and capacity refusals. Keep malformed authentication separate from policy denial.
Full and resumed handshakesCounts, CPU cost, and latency for each path. Determine whether resumption occurs with the shipped configuration; report an unexercised path explicitly rather than assuming coverage.
Honest operationInitial connections and realistic reconnects, including restart and committee rotation, with acceptance rate and latency distributions as well as refused-handshake cost.

Estimate the benefit using measured refusal frequencies and the work actually avoided after the candidate check, including the extra lookup cost on accepted connections. Set an acceptance threshold before comparing the candidate with the baseline. Publish the result even if it supports keeping this no-change decision. A Retry result alone supplies neither identity admission nor evidence of savings from earlier identity refusal.

Conditions for a later implementation

If the admission contract and profile justify a change, require all of the following before shipping it:

  • Treat an identity extracted before signature verification as untrusted. A claimed allowed identity without valid proof must receive no authentication, trusted scheduling, or quota exemption.
  • Preserve certificate, libp2p identity-extension, and TLS transcript verification for accepted peers. Test invalid proofs claiming an allowed identity as well as valid allowed and valid refused identities.
  • Apply current admission policy to resumed connections, including sessions created before a ban, committee rotation, or other policy revision. Test stale input fallback and recovery on the applicable full and resumed paths.
  • Demonstrate the measured benefit while preserving the required stock-peer interoperability matrix and honest reconnect behavior. Coordinate that coverage with the QUIC interoperability work.
  • Record the owner, upstream baseline, regression coverage, and upgrade checks for every additionally modified TLS or transport crate. Do not assume the existing QUIC tuning callback exposes a certificate-verifier hook.

Gossip

Telcoin Network nodes flood four gossipsub topics: certificates, consensus output, epoch votes, and worker batch digests. All four are permissioned. A node accepts a gossip message only when the publisher’s BLS public key is on that topic’s allowlist for the current epoch, and it rejects everything else before re-propagating it.

This page is for RPC providers, dapp and indexer operators, bridge partners, and validators. It gives the topic names, who may publish on each, how a publisher is authenticated, the message size limit, and which peer is penalized when a message is rejected.

The implementation lives in crates/network-libp2p/src/consensus.rs and crates/config/src/network.rs. Transport covers the QUIC transport these topics ride on, Discovery covers the signed node records that resolve publisher identities, and Peers covers the reputation system that the gossip penalties feed.

The four topics

TopicAuthorized publishersPayload
tn-primary-{chain_id}current committeeCertificate
tn-consensus-output-{chain_id}previous, current, and next committeeConsensusResult
tn-epoch-vote-{chain_id}previous, current, and next committeeEpochVote
tn-worker-{chain_id}-{worker_id}current committeebatch digest, (Epoch, BlockHash)

Topics are identity-hashed, so the strings above are exactly what appears on the wire. On the Adiri testnet, whose chain id is 2017, a single-worker node uses these four names:

tn-primary-2017
tn-consensus-output-2017
tn-epoch-vote-2017
tn-worker-2017-0

The gossipsub protocol id is chain-scoped for the same reason the topics are: the builder yields /tn-meshsub-2017/1.1.0 and /tn-meshsub-2017/1.0.0. Without that, meshsub would be the one wire protocol two chains still shared.

What “permissioned” means

Each subscribed topic carries an allowlist of BLS public keys. Verification of an inbound message reads that entry and resolves the message author’s libp2p peer id to a BLS key:

entry absent          -> topic not subscribed here          -> reject
entry is an allowlist -> accept only if the author's resolved BLS key is in it
entry is None         -> subscribed, any publisher accepted

The None case is a real capability of the mechanism: it makes a topic permissionless.

Note

No production topic uses it. All four topics are subscribed with an explicit committee allowlist, on every node, on every epoch. A message from a peer outside the allowlist is rejected and is never re-propagated by an honest node.

Rejection is not the same as ignoring. A rejected message is dropped and, where a culprit can be identified, costs that peer reputation.

Topic scoping

Topics are scoped by chain and, for batch digests, by worker id. They are never scoped by epoch. Epoch scoping happens instead by re-subscribing to the same topic name at the start of every epoch, which overwrites the stored allowlist with the new committee’s keys.

Subscriptions live on a process-lifetime swarm, so a topic subscribed in one epoch stays subscribed until it is explicitly dropped. Unsubscribing must therefore remove the allowlist entry, not merely stop reading. An absent entry means “not subscribed here, reject”, which is the correct state for a topic this node has left. A stale entry left behind would pin the topic to whichever committee was current when the node last subscribed, and honest publishers from every later committee would be rejected as unauthorized.

Why two topics span three committees

Certificates and batch digests are current-epoch traffic, so the current committee is the whole allowlist.

Epoch votes and consensus output are epoch-boundary traffic. An epoch’s closing vote and its final consensus output are authored by the outgoing committee and gossipped into the next epoch. A current-committee-only allowlist would reject exactly those in-flight boundary messages during rotation, and stop re-propagating them, stalling certification of the epoch that just closed. A validator rotating in may likewise start publishing slightly early. Widening the allowlist to the previous, current, and next committee also makes it agree with the window the peer manager already uses for validator penalty exemption, so the propagation-authorization window and the penalty-exemption window cannot disagree. Peers that were never on a committee remain excluded.

The authentication chain

An accepted gossip message has passed three independent checks, in this order.

  1. Message authenticity. Gossipsub runs in Signed authenticity mode, so every message carries the source peer id and a signature over the message made with that peer’s libp2p network key. This proves the author owns the network key, and nothing more.
  2. Identity resolution. The peer manager maps a libp2p peer id to a BLS public key only through a signed node record it has verified, and the record’s signature is bound to a (chain, role) domain so a record signed for one network never verifies on another. A record is accepted only when the network key it advertises matches the identity that published it, so relaying someone else’s record never lets the relayer claim that identity. Discovery covers the record itself, and Peers covers the mapping.
  3. Allowlist membership. The resolved BLS key must appear in the topic’s allowlist for the current epoch.

The chain has no shortcuts. A peer with no resolved BLS identity fails step 3 on every restricted topic, regardless of how well-formed its message is.

Message size

MAX_GOSSIP_MESSAGE_SIZE is 12,000 bytes, sized to hold the largest legitimate gossip payload, a Certificate.

It is a network-wide protocol constant, not a per-node tunable, and it is deliberately not readable from operator configuration. The reason is the penalty path. An oversized message fatally bans the peer that relayed it, and that attribution is sound only when every honest node applies the identical bound. If one operator raised the limit locally, that node would forward payloads its neighbours consider oversized, and those honest neighbours would ban it. If an operator lowered it, that node would ban honest peers forwarding legitimate traffic.

The bound is enforced symmetrically. A node refuses to publish a message larger than the limit rather than emitting one its peers must reject, so on the first hop the originator is never the victim of its own ban.

Penalty attribution

Rejection never propagates the message. The reason for the rejection decides only which peer, if any, loses reputation.

  • Oversized payload: the relayer’s fault. An honest node never originates an oversized message and, under strict validation, never forwards one, so delivering one is misbehavior by the peer that delivered it. The relaying peer takes a fatal penalty.
  • Unauthorized author: the author’s fault. A forwarder that relayed content authored by someone else is not accountable for that content. The resolved author takes the fatal penalty and the relayer takes none.

If the accountable identity has not resolved — a peer can be connected and relaying before its node record arrives — the node skips the penalty entirely rather than charging whichever peer happens to be in hand. The same skip covers an anonymous message and the case where this node’s committee view lags a neighbour’s. Accepted messages carry both the relayer and the author onward, as optional identities, so a fault that only deep validation can surface is still charged to the author rather than the forwarder.

Validation configuration

Gossipsub runs with strict validation mode, manual message validation, and a one-second heartbeat interval. Manual validation is what allows the allowlist check to run before a message is re-propagated: the node reports accept or reject explicitly, and a rejected message is never forwarded.

Gossipsub’s own peer scoring is deliberately left disabled. The peer manager is the authoritative reputation system, and running gossipsub scoring alongside it would double-count the same misbehavior — once as a gossipsub score decay and again as a peer-manager penalty — with two sets of thresholds that could disagree about when to disconnect. Two gossipsub events do feed the peer manager directly: a peer that does not support gossipsub is fatally penalized, and a peer that persistently fails to keep up with the mesh takes a mild one.

Topic re-validation in the application layer

Passing network-layer verification is not the end of the check. Every application handler re-reads the topic name on the message it received and confirms it matches the topic that payload type belongs on. A certificate delivered on the epoch-vote topic, or a batch digest delivered on a different worker’s topic, is a mismatch.

A mismatch is a fatal ban, charged to the author rather than the relayer, because the declared topic is part of the content the author produced. The check exists because the two layers are configured independently: the network layer knows only that the publisher was allowlisted for whichever topic the message declared, not that the payload belongs there.

Worker batch subscription is node-mode gated

The batch digest topic is the only one whose subscription depends on the node’s role for the epoch. Committee validators subscribe, which warms the batch cache the voting path reads. Observers unsubscribe.

Observers gain nothing from the digest announcements: batch bodies already reach them inside the verified consensus output and epoch packs they download, so prefetching would refetch bytes already in flight. The decision is two-sided rather than a simple skip, for the reason above — a node that subscribed as a validator in one epoch stays subscribed after it becomes an observer unless the subscription is explicitly dropped, and skipping would also skip the only refresh of that topic’s allowlist. See Network architecture for the node roles this gate reads.

Source of truth

BehaviorCode
Topic namescrates/config/src/network.rs (LibP2pConfig topic constructors)
Message size limitcrates/config/src/network.rs (MAX_GOSSIP_MESSAGE_SIZE)
Gossipsub configurationcrates/network-libp2p/src/consensus.rs (ConsensusNetwork::new)
Allowlist check and subscribe/unsubscribecrates/network-libp2p/src/consensus.rs (verify_gossip, NetworkCommand::Subscribe)
Penalty attributioncrates/network-libp2p/src/consensus.rs (RejectReason::penalty)
Per-epoch subscription and allowlistscrates/node/src/manager/node/start_epoch.rs (spawn_primary_network_for_epoch, spawn_worker_network_for_epoch)
Application-layer topic re-validationcrates/consensus/primary/src/network/handler.rs, crates/consensus/worker/src/network/handler.rs

This page mirrors those files. Update this page when those files change.

Discovery

Telcoin Network nodes find each other through a Kademlia DHT of signed node records. A record maps a validator’s BLS public key to the network key and address its swarm is listening on, so a node holding a committee’s key list can resolve every member to a dialable peer.

This page is for RPC providers, dapp and indexer operators, bridge partners, and validators who need to know how a node locates the committee, what it admits into its routing table, and what it rejects. Every value below is the constant the node compiles with.

The record format and its signing domain live in crates/network-libp2p/src/types.rs. The discovery loop lives in crates/network-libp2p/src/consensus.rs. The persistent DHT store lives in crates/network-libp2p/src/kad.rs. Transport covers the protocols these queries negotiate and Peers covers the reputation scoring that discovery feeds.

The node record

A node record is keyed on the primary’s BLS public key, on the primary network and on every worker network alike. One key therefore resolves an entire node: the primary swarm and each worker swarm publish their own record under the same key, on their own DHT.

The value is a signed NetworkInfo: the swarm’s network public key, its advertised multiaddrs, a publication timestamp, and optional RPC endpoint information. Only worker records carry RPC info, and only for validators that choose to advertise it publicly. The timestamp is what lets a node keep the freshest record when several arrive for the same key.

Domain separation

The bytes a record’s BLS signature covers are wider than the bytes on the wire:

signed_payload = label            # "telcoin-network/node-record/v1"
               || chain_id        # from genesis
               || role            # 0 = primary, 1 = worker
               || worker_id       # 0 for a primary record
               || NetworkInfo     # the only part transmitted

The label, chain id, role, and worker id form a domain that is never transmitted. A verifier rebuilds the domain from its own network identity, so a record verifies only where it was meant to be published. A validly signed worker record does not verify on the primary DHT, a worker 0 record does not verify on worker 1, and a record signed for one chain does not verify on another. This is what closes cross-role and cross-chain record replay.

The trailing v1 is the signed-payload schema version. Records signed by software predating the domain do not verify and are dropped after an upgrade, including at startup when the persisted store is reloaded.

What validation requires

An inbound record is accepted only when all of the following hold.

  1. The record key parses as a BLS public key.
  2. The signature verifies against that key over the domain the verifier rebuilt for itself.
  3. The record advertises at most 1 multiaddr. An honest node publishes exactly one address per record, so a longer list is only an attempt to write attacker-chosen bytes into the peer entry.
  4. The record’s publisher equals the peer id derived from the network public key inside the record. Without this, a peer could republish someone else’s stale record under its own connection.

A record that fails any of these earns the sender a Fatal penalty, which bans immediately. A record that is valid but older than the one already stored is ignored without penalty: republication after a restart is normal.

Passing validation is not the same as entering the resolution cache. A record learned from a DHT query is cached only when its BLS key belongs to the previous, current, or next committee. A peer that pushes its own record over its own authenticated connection has its key-to-peer-id binding confirmed so the live connection survives, but a non-committee key never enters the committee resolution cache.

Kademlia configuration

  • Mode::Server, unconditionally. Every node serves DHT queries, including observers and RPC nodes. A node that only consumed the DHT would take routing capacity without returning any.
  • Manual k-bucket inserts. libp2p never adds a peer to the routing table on its own; a peer is added when the node’s own peer manager registers the connection, and removed when it disconnects or is banned. Discovery and reputation therefore share one admission decision.
  • k-bucket size 20, the standard Kademlia replication parameter.
  • Record TTL 48 hours, applied to both records and provider records.
  • Republication every 12 hours, comfortably inside the TTL so a node’s own record never lapses.
  • 60 second query timeout. A closest-peers query that times out still yields the peers it reached, and those are kept: queries are slowest exactly when the node is short of peers and needs them most.
  • FilterBoth record filtering. No inbound record or provider record reaches the store without first passing through the validation above. This makes the node’s own handler the sole write path for peer-supplied data.

The record store

The store is persistent, backed by the consensus database rather than memory, so a restart does not erase what the node learned. Rows are namespaced by the same role discriminant and worker id that the signing domain uses, so a primary and each of its workers keep entirely separate record and provider tables in the shared database.

Sizing bounds the node’s DHT storage duty at 1024 records, a 65 KiB maximum value size, 1024 provided keys, and 20 providers per key. Provider records carry the same 48 hour TTL, which means a saturated provider table expires nothing for two days; the eviction scan that runs when the table is full is therefore throttled to at most once per 60 seconds so a full table cannot be turned into a full-table scan per inbound message.

Rows whose bytes no longer decode — after a schema change or on-disk corruption — are purged at startup instead of failing the read.

Six ways a node finds the committee

  1. Bootstrap peers. The first contacts come from the committee’s own declared bootstrap servers, read from committee state at startup, not from a CLI flag or a static file the operator maintains. The primary network takes each server’s primary address; a worker network takes that server’s entry for its own worker id, and skips servers that run fewer workers. Bootstrap peers are registered once per process, before committee membership is applied, and are pinned so committee rotation never evicts them.
  2. Trusted and explicit peers. Operator-provisioned peers are pinned the same way and are penalty-exempt: severe and fatal penalties are recorded but never applied, so a trusted peer cannot be scored out of the peer set. See Peers for what exemption covers.
  3. Record lookup on missing authorities. When the committee changes, or when a caller asks for validator RPC endpoints, the node issues a DHT record lookup for every committee key it cannot yet resolve. Polling callers report the same key as missing on every call, so lookups are deduped against in-flight queries: at most one live query per key, re-armed as soon as that query ends. Without the dedupe, one unreachable authority would fan out into duplicate queries for as long as it stayed unreachable.
  4. Closest-peers walk on the discovery heartbeat. The peer manager’s heartbeat runs every 30 seconds. When the pool of dialable discovery candidates is below its target, the node issues a closest-peers query for a random key, which is the standard Kademlia way to sample unfamiliar regions of the routing table. Candidates are dialed at random from the pool until the connection target is met.
  5. Peer exchange on graceful disconnect. A node that is over its peer target disconnects gracefully and sends a peer-exchange map first, which the receiver folds into its discovery pool. Exchange results fill spare capacity only, never displacing existing entries, which keeps DHT-discovered peers ahead of hearsay from a departing connection.
  6. Direct record push to first-time peers. On a first connection the node pushes its own record straight to the new peer, so that peer can resolve its BLS key immediately instead of waiting up to 12 hours for the next republication. The de-dup set behind this is a 10,000-entry LRU, so a peer that reconnects repeatedly is pushed to once, not once per connection.

Every candidate from any source is filtered before it is dialed: never the node’s own identity, at most one address, at least one valid IP, no banned IP, and not already connected or dialing.

Why query results are not written back

A record that arrives as the answer to a lookup is promoted only into the in-memory resolution cache. It is deliberately not written to the persistent store.

The node needs the resolution, not the record. Storing a third-party record would enlist libp2p’s replication job to republish it on every replication run, roughly hourly, turning every node into an hourly replicator of records it was never asked to serve. It would also spend the store’s record cap — the node’s actual DHT storage duty — on query traffic. Nothing is lost, because peers push their own records on first connect, and that is the path that legitimately fills the store.

Rate limits on inbound writes

Two inbound message types write to the consensus database, and both are rate-limited per source before any signature is verified or any row is touched.

MessageBudgetWindowOver budget
PutRecord30 per source60s fixeddropped, Medium penalty
AddProvider5 per provider60s slidingdropped, Medium penalty

Each admitted PutRecord costs a roughly 1 ms BLS verification plus a database write on the same task that relays consensus gossip, and a self-signed record from an unbanned peer passes every other check, so without a budget one peer could starve the event loop with valid records. Honest traffic sits far below both numbers: a node refreshes its record on the 12 hour republication cadence and libp2p’s roughly hourly replication run, which is a handful of messages per minute even after a restart. Each admitted AddProvider costs a row decode, merge, re-encode, and a physical database commit, and repeating it for a key already stored skips the store’s capacity gate entirely; honest traffic peaks at about one message per peer per minute.

Both window maps are capped at 1024 tracked sources. At capacity, a source that is not already tracked is denied rather than evicting a tracked one. The alternative would hand an attacker a reset button: cycle fresh peer identities until your own tracking entry is evicted, then start a fresh budget.

A Medium penalty bans a peer after roughly ten occurrences; Peers has the full scoring model.

Epoch startup

Process startup initializes swarm listeners, registers bootstrap peers and starts bootstrap dials before the epoch loop. Epoch setup refreshes committee membership and starts committee dials without waiting for peers. A process-lifetime monitor samples the primary and every configured worker swarm every five seconds, with a one-second bound on each established-peer probe. When enabled with --healthcheck, GET /health/network returns a cached versioned JSON snapshot: 503 until every configured swarm has an established peer, then 200. Connectivity can recover while the node remains running; this route does not certify consensus, sync readiness or RPC transaction acceptance.

Bootstrap and committee dials retry with backoff doubling to a cap of 120 seconds. Each dial outcome wait is bounded to one second, including command admission; timing out does not cancel the swarm’s transport dial. An already-dialing reply means the attempt is still pending, so retries continue until a dial succeeds or the peer is already connected. A retry task gives up after repeated failures only once at least one other peer is established, so a completely isolated node keeps trying its remaining routes in. Startup dial tasks survive epoch turnover; committee dials started by epoch setup use the epoch task manager and are cancelled at its boundary.

Note

Reaching a peer is only the first step. A connection still has to clear the peer manager’s admission checks and, for gossip, the topic publisher allowlists described in Gossip.

Source of truth

BehaviorCode
Record format, signing domain, validationcrates/network-libp2p/src/types.rs (NodeRecord, RecordDomain)
Kademlia configuration, discovery loop, record pushcrates/network-libp2p/src/consensus.rs
Persistent record and provider store, sizingcrates/network-libp2p/src/kad.rs (KadStore)
Inbound rate limits, discovery heartbeat, peer poolscrates/network-libp2p/src/peers/manager.rs
Advertised address capcrates/network-libp2p/src/peers/peer.rs (MAX_MULTIADDRS_PER_PEER)
TTL, republication interval, k-bucket size, peer targetscrates/config/src/network.rs
Process-lifetime swarm setup, bootstrap registration and dialscrates/node/src/manager/node.rs
Per-epoch committee refresh and dialscrates/node/src/manager/node/start_epoch.rs
Bounded dial retries and isolation recoverycrates/node/src/network_dial.rs
Cached network reachability and health routescrates/node/src/network_readiness.rs, crates/node/src/health.rs

This page mirrors those files. Update this page when those files change.

Peers

Every Telcoin Network node keeps a reputation score for every peer it has met. Malformed responses, failed validation, protocol abuse, and DHT flooding all report a penalty into that one score, and a peer whose score falls far enough is disconnected and then banned. Reputation is the only defense that touches every subsystem, so it is also the answer to “why did that node stop talking to me”.

This page is for RPC providers, dapp and indexer operators, bridge partners, and validators. It documents the score model, the penalty magnitudes, the connection ceilings a node enforces, the two separate ban tables, how IP bans are derived, and the metrics that expose all of it.

The implementation lives in crates/network-libp2p/src/peers/manager.rs, crates/network-libp2p/src/peers/score.rs, and crates/network-libp2p/src/peers/all_peers.rs, with every default in crates/config/src/network.rs. Gossip, Discovery, Request-Response, and Sync Streams describe the subsystems that report the penalties.

The score model

A peer starts at 0.0 and is clamped to the range [-100.0, 100.0]. The score decays exponentially toward zero with a half-life of 300 seconds:

decay_factor = e^(-ln(2) / 300 * seconds_since_last_update)
new_score    = old_score * decay_factor

Decay applies equally to positive and negative scores, so a peer that behaves for ten minutes has shed roughly three quarters of any penalty it accumulated. A peer’s reputation is derived from its current score every time it is read, never stored, so an operator who changes the thresholds changes behavior immediately.

Penalties and thresholds

Four penalty severities exist, and the number of variants is kept deliberately small:

PenaltyScore changeMeaning
Mild−1.0An error that is very unlikely to be malicious.
Medium−5.0An error that is probably not malicious but is not free.
Severe−10.0Not necessarily malicious, but will not be tolerated.
Fatalset to −100.0 (the floor)Unforgivable; bans on the first occurrence.

Two thresholds act on the resulting score, and both trigger at or below their value:

  • −20.0 — disconnect. The node closes the connection.
  • −50.0 — ban. The node closes the connection, blacklists the peer in gossipsub, removes it from the Kademlia routing table, and refuses future connections.

From a fresh score of 0.0, ignoring decay, that is 50 Mild, 10 Medium, or 5 Severe penalties before a ban. Penalties are not debounced: each report is evaluated immediately and any one of them can be the report that crosses a threshold.

Warning

Crossing the ban threshold pushes the peer’s decay clock forward by 30 minutes. A banned score does not decay during that window, so the lockout is real time served rather than a formality — the score only begins recovering after the 30 minutes elapse, and the peer becomes reconnectable once decay lifts it back above −50.0.

What makes a peer important

The peer manager has no validator/observer enum. It has an identity and two independent bases for trust.

An identity is either Confirmed, carrying the peer’s BLS public key, or Unidentified, carrying only its libp2p peer id. A peer first seen as unidentified is re-keyed onto its confirmed identity the moment its BLS key is learned.

The two trust bases are kept separate on purpose:

  • Operator allowlisting is sticky. It is set when the peer record is constructed, from the node’s own trusted and bootstrap configuration, and epoch rotation never alters it.
  • Validator membership is derived live from the previous, current, and next committee slots. It is never stored on the peer record, so it cannot drift out of sync with rotation, and a validator rotating out of the committee can never strip operator trust.

Either basis makes a peer “important”, which means four things: it is exempt from the score model entirely, it is skipped by heartbeat pruning, it is allowed to connect past the connection ceiling, and it is added as a gossipsub explicit peer when it connects. A Severe or Fatal penalty suppressed for an important peer is logged as a warning, because an exempt peer misbehaving badly enough to earn one is operationally significant.

When a peer enters a tracked committee its score is primed to the maximum of 100.0, any ban is forgiven, and its observed IPs are cleared from the per-IP ban counter. Priming the score means that if it later rotates out and re-enters the score model, it starts from a clean maximum rather than a stale value.

Connection limits

Every limit derives from one target, which itself derives from Kademlia’s bucket size K_VALUE of 20:

target_num_peers        = (K_VALUE / 2) + K_VALUE     = (20 / 2) + 20    = 30
max_peers               = ceil(30 * (1 + 0.3))                           = 39
max_outbound_dialing    = ceil(30 * (1 + 0.3 + 0.2 / 2))                 = 42
max_priority_peers      = ceil(30 * (1 + 0.3 + 0.2))                     = 45
target_outbound_peers   = ceil(30 * 0.3)                                 =  9
min_outbound_only_peers = ceil(30 * 0.2)                                 =  6
max_discovery_peers     = 30 * 2                                         = 60

Three of these are enforced on the connection path today. An inbound connection is refused once connected-or-dialing peers reach 39. An outbound connection is refused once connected peers reach 42, the higher ceiling reflecting that this node chose to dial. The discovery candidate pool is trimmed to 60. An important peer bypasses both connection ceilings. The remaining three values — 45 priority peers, 9 target outbound, and 6 minimum outbound-only — are configured and resolvable but are not read by any production code path at present.

A separate per-peer ceiling caps concurrent established connections from a single peer at 8. A peer needs at most one inbound and one outbound connection at a time, so eight is headroom for reconnection churn while bounding a hostile peer to a fixed, small fan-out.

Each swarm also caps pending inbound connections (handshakes that are not yet established) at 1024. This cap is a memory bound only, not an admission policy. QUIC Retry, enabled by default, validates the source address before a handshake can occupy a slot. If retry_unvalidated_incoming is disabled as an operator rollback, a forged QUIC Initial datagram can hold a slot for the 10 second transport timeout, so a small cap would let a cheap flood refuse honest peers. Refusals show in inbound_connections_denied_total.

Dial attempts time out after 15 seconds; a peer still dialing past that is marked disconnected, because dialing peers count against the inbound limit.

The heartbeat

The heartbeat runs every 30 seconds and does all periodic maintenance. It times out stale dials, decays every non-exempt peer’s score, unbans peers whose decayed score has recovered, updates the peer-count gauges, releases expired DHT rate-limit budgets, and refills the discovery pool. It can never penalize a peer: penalties only arrive from the application layer.

The heartbeat then prunes back to the target of 30 connected peers. Candidates are shuffled first, then sorted by score and then by Kademlia routability, so the lowest-scoring peers that do not participate in routing are dropped first and equal scores are broken without bias. Important peers are filtered out of the candidate list entirely. Pruned peers are disconnected with a peer-exchange payload so they can find other nodes, and are temporarily banned so they do not immediately reconnect.

Two ban tables

A node maintains two ban tables that never merge, because they answer different questions.

Reputation bans live with the peer record. They are earned by score, they carry the peer’s observed IP addresses into the per-IP ban counter, they blacklist the peer in gossipsub, and they end only when decay lifts the score back above the ban threshold. At most 100 reputation bans are retained; when that fills, the oldest are pruned and their peers unbanned, keeping the freshest bans.

Temporary bans live in a separate LRU cache keyed by peer id, with a 600-second timeout and a cap of 100 entries. They are applied for excess-peer disconnects, for reputation disconnects, and alongside reputation bans, and they prevent reconnection at the swarm level without touching the peer’s stored state. They also outlive the peer record, so a peer pruned from the database can still be refused on the basis of its earlier temporary ban. Because the cache is only swept on the heartbeat, the effective ban duration is quantized to the 30-second interval.

Keeping them separate is what lets a peer be refused a connection while its stored reputation is still healthy — an over-capacity node turning peers away is not accusing them of anything. Committee members are lifted out of the temporary-ban cache on every rotation so a follow-up dial can reach them.

IP bans

An IP address is banned once more than one banned peer is associated with it.

The per-IP counter is fed only by IPs observed on real inbound and outbound connections. Self-advertised addresses from signed peer records are never counted, and this is load-bearing: an advertised address is attacker-controlled, so counting it would let a peer get a third party’s IP banned by simply claiming it. The two address sets are deliberately independent — the advertised multiaddr set is capped at 1 entry per peer and drives dialing and peer exchange, while the observed-IP set drives ban accounting and nothing else.

The observed set is capped at 16 IPs per peer, enough for dual-stack, DHCP renewal, and mobile roaming. At the cap a new IP is refused rather than evicting an old one. That keeps the set growing monotonically, which matters because per-IP counts are incremented from the set when a ban starts and decremented from it when the ban ends: an eviction between those two reads would strand a count and leave an IP banned after its peer was unbanned, penalizing every honest peer sharing it.

Committee members have their observed IPs removed from the counter when they join, so a validator’s address cannot be blocked during its initial connection storm.

Layered refusal

The peer manager is the first behaviour in the node’s libp2p behaviour list, and the per-peer connection limiter is second. Behaviour callbacks run in declaration order and short-circuit on the first denial, so a banned peer is rejected before gossipsub, request-response, or Kademlia register any per-peer state for it, and an over-cap connection is denied before those behaviours allocate anything either.

Refusal happens at several points along the same connection: on the pending outbound dial, on the pending inbound connection whose source IP is checked against the IP ban list, and again on each established connection. As a final backstop, a banned peer that nonetheless reaches the PeerConnected event is refused registration and disconnected outright, rather than being added to the routing table where it would drive a redial loop.

Why committee records never expire

The map that resolves a validator’s BLS public key to its network address has no TTL, by design. An expiry-driven resolution failure would be a consensus liveness bug: a validator whose record aged out mid-epoch would simply stop being recognized by its peers.

The map is bounded structurally instead of by time. An entry survives only if it is pinned — an operator-provisioned trusted, bootstrap, or explicit peer, whose count is set by node configuration — or if its key still sits in one of the three tracked committee slots. Every committee rotation prunes the rest. Records restored from local persistence at startup are deliberately not pinned, and records learned from the DHT are cached only for keys already in a tracked committee slot, so neither path can grow the map without bound.

Metrics

Every series below is exported under the tn_network prefix and carries a network label whose value is primary or worker-{worker_id}, so read each series per label — the primary and worker swarms are independent.

MetricWhat it tells you
connected_peersPeers currently connected, sampled each heartbeat. Compare against the target of 30 and the ceiling of 39.
known_peersCommittee members with a resolved network record. A value below the committee size means discovery is still chasing keys.
discovery_peersDial candidates held in the discovery pool, capped at 60. Persistently low means discovery is starved.
banned_peersSize of the temporary-ban cache, not the reputation-ban table. Rises during excess-peer churn.
peers_banned_totalCumulative reputation bans. This is the flow that matches the ban threshold; banned_peers is a different stock.
peer_penalties_totalPenalties applied, labelled severity with mild, medium, severe, or fatal. The leading indicator for bans.
connections_established_totalConnections established, labelled direction with in or out.
connections_closed_totalConnections closed, all directions.
dial_failures_totalFailed dial attempts.
inbound_connections_denied_totalInbound connections refused by a connection limit, labelled reason with pending_incoming_limit or established_per_peer_limit. A sustained pending_incoming_limit rate means the pending ceiling is full.
external_addr_confirmed1 once any external address is confirmed, meaning NAT traversal is possible. Stuck at 0 means inbound connectivity is unlikely.
gossip_published_totalGossip messages published by this node.
gossip_received_totalGossip messages received from peers.
gossip_rejected_totalGossip messages that failed publisher verification. Sustained non-zero means a peer is publishing to a topic it is not authorized for.
add_provider_rate_limited_totalInbound DHT provider announcements dropped by the per-source rate limit.
outbound_requests_pendingOutbound requests in flight.
outbound_request_failures_totalOutbound request failures, labelled kind with the failure cause.
px_disconnects_pendingGraceful peer-exchange disconnects awaiting the peer’s acknowledgement.

Source of truth

BehaviorCode
Score decay, penalty magnitudes, thresholdscrates/network-libp2p/src/peers/score.rs
Penalty severities, trust basis, peer identitycrates/network-libp2p/src/peers/types.rs
Per-peer state, observed IPs, exemptioncrates/network-libp2p/src/peers/peer.rs
Reputation bans, committee slots, pruningcrates/network-libp2p/src/peers/all_peers.rs
Heartbeat, connection limits, temporary bans, known_peerscrates/network-libp2p/src/peers/manager.rs
Per-IP ban counter and thresholdcrates/network-libp2p/src/peers/banned.rs
Temporary-ban LRU cachecrates/network-libp2p/src/peers/cache.rs
Connection denial orderingcrates/network-libp2p/src/peers/behavior.rs, crates/network-libp2p/src/consensus.rs
Metric names and labelscrates/network-libp2p/src/metrics.rs
PeerConfig and ScoreConfig defaultscrates/config/src/network.rs

This page mirrors those files. Update this page when those files change.

Local launch committee inventory

Issue #1477 requires validators to find one another without a hub or an existing peer database. The operator-owned committee_peers map in the datadir’s YAML network-config file is the authoritative launch inventory. Distribute the same complete map to every validator before starting the launch network.

Committee membership still comes from chain state. The map binds a BLS key to the expected libp2p network identity and dial address on the primary and every worker swarm. libp2p authenticates the remote transport key when connecting. Adding an inventory entry grants no committee membership, gossip publishing permission, or operator trust classification.

Provisioning

Use the same shape as committee.yaml’s bootstrap_servers, with an explicit workers list indexed by worker ID. Include every launch validator, including the local validator, and every on-chain worker. Keep primary, worker 0 and worker 1 keys distinct. A two-worker entry looks like this:

committee_peers:
  <validator-BLS-public-key>:
    primary:
      network_key: <primary-network-public-key>
      network_address: /ip4/192.0.2.10/udp/9000/quic-v1
    workers:
      - network_key: <worker-0-network-public-key>
        network_address: /ip4/192.0.2.10/udp/9001/quic-v1
      - network_key: <worker-1-network-public-key>
        network_address: /ip4/192.0.2.10/udp/9002/quic-v1

Replace the placeholders and documentation addresses with operator-provided keys and reachable addresses. Repeat the entry for every validator. The genesis committee’s bootstrap inventory can be copied as a starting point, but verify that it contains every launch validator and that its endpoints and worker count are current. An empty or absent committee_peers map preserves discovery-based startup for existing deployments. It does not qualify a hub-independent Closed launch.

Startup validates local advertised keys, network-key uniqueness across validators and swarms, and any /p2p/<PeerId> address component before spawning networks. A swarm that the local entry lists must match the local identity. Failures include the affected BLS key and swarm in the diagnostic.

Coverage is fail-closed at genesis (epoch 0) only. At genesis, the map must list every committee member with the on-chain worker count, or startup stops. After launch, the committee can change. A new validator or a higher worker count that the map does not list does not stop a restart. Startup logs a warning for each gap, and discovery finds those swarms. Add the missing entries in the next coordinated configuration update. The local worker count sets no requirement for the entries of other validators, so a node can stage a spare worker before governance raises the count.

Selection, union and conflicts

Bootstrap selection is unchanged: a CLI override takes precedence over bootstrap_peers in network-config, which takes precedence over the genesis bootstrap set. A selected empty map falls back to genesis; a selected nonempty map replaces genesis in full.

After that selection, startup installs bootstrap hints and then committee hints on the primary and every worker. Unrelated bootstrap and trusted peers remain present, including non-committee hubs. The committee map is independent of the bootstrap replacement rule, so selecting hubs as bootstrap peers cannot erase validator bindings.

  • Repeated entries with the same BLS/network binding are idempotent. The committee inventory’s address takes precedence over a bootstrap or cached address for that binding.
  • Repeated BLS keys within the YAML committee map are rejected, including identical duplicates, so an earlier operator entry is never silently lost.
  • Different network keys for the same BLS key and swarm are errors. A network key assigned to two BLS keys or to different validator swarms is also an error.
  • A configured bootstrap or trusted entry that contradicts the inventory rejects the swarm’s whole seed batch before inserting any committee hint. Correct the configuration before restarting.
  • A cached peer record that contradicts the inventory does not stop startup. Such a record comes from the peer database or from discovery, and any peer can write one. Startup drops the record, logs a warning, and uses the inventory binding. No manual cleanup is necessary.
  • A cached signed record with the same binding is kept. It takes the inventory address and keeps its signed timestamp and RPC metadata.
  • Startup dials each BLS key once per swarm across the bootstrap/committee union, and never dials the local validator. Existing bootstrap and committee retry behavior remains in effect.

Launch invariant and manual changes

For the process lifetime, each seeded BLS key keeps its configured network key and address on each swarm. Signed discovery can refresh RPC metadata but cannot silently change those launch bindings. Chain committee updates still control membership and authorization, including during epoch transitions. Seeded hints survive those transitions so direct dialing needs no hub lookup. The inventory can stay in place after launch. Discovery finds validators that join later until the inventory lists them.

Until automated rotation is qualified, coordinate changes manually:

  1. Arrange the validator’s new address or network keys with all launch operators. Keep the old endpoints available during the coordinated change.
  2. Update the corresponding primary or worker entry in every distributed network-config. Update that validator’s node configuration and key material at the same time. Reconcile matching bootstrap and trusted entries too.
  3. Stop the affected deployment and restart validators with the updated inventory. Startup drops cached peer records that contradict the new bindings. Preserve execution and consensus data.
  4. With all hubs unavailable, verify direct connections to every other committee member on the primary and each configured worker. Repeat on a node with freshly provisioned peer storage. Enable Closed only after its admission policy and this connectivity check have both been qualified.

When a validator joins the committee or governance raises the worker count, add the new entry or worker to every distributed network-config in the next coordinated update. Until then, a restart logs the gap and uses discovery.

The regression test committee_seeding_connects_every_swarm_without_hubs_after_fresh_restart starts all fixture validators on the primary and two worker swarms without a hub. It checks direct connectivity, discards every swarm and peer database, and repeats from the same serialized inventory. Peer-manager regressions pin the union, atomic rejection of configured conflicts, removal of contradictory cached records, preservation of matching signed records, and fixed launch bindings.

Request-response

Telcoin Network runs two independent libp2p request-response behaviours on every node. The consensus RPC carries votes, batch reports, and epoch records between peers of the same role. A dedicated peer-exchange protocol carries the goodbye a node sends before it disconnects. Both share one codec and one bounded read path; they differ in what they carry and in how their failures are scored.

This page is for RPC providers, dapp and indexer operators, bridge partners, and validators who need to know what a node accepts on these protocols, what it rejects, and which rejections cost a peer reputation.

The implementation lives in the libp2p networking crate: the codec and its read-path bounds, the protocol identifiers and message plumbing, and the behaviour wiring and failure handling.

The two protocols

The consensus RPC is namespaced by role, worker index, and chain id, and is currently at protocol version /0.0.2. A primary advertises /tn-primary-{chain_id}/0.0.2; worker k advertises /tn-worker-{k}-{chain_id}/0.0.2. Both sides support the protocol fully, so any node can be requester or responder. Transport explains the /0.0.2 version bump and the full protocol-identifier table.

The peer-exchange goodbye is a separate behaviour on its own name, still at /0.0.1: /tn-primary-peer-exchange-{chain_id}/0.0.1 and /tn-worker-{k}-peer-exchange-{chain_id}/0.0.1. Its request is the disconnecting node’s exchange map and its response is an empty acknowledgement. Splitting it out means the exchange map no longer has to ride inside the consensus request enums, and a node that cannot negotiate the dedicated protocol falls back to the variant still embedded in those enums. A node holds at most 10 goodbyes in flight and waits at most 3 seconds for the acknowledgement before disconnecting anyway.

Wire format

Every message on both protocols, in both directions, is framed identically:

[4-byte LE uncompressed_len][4-byte LE compressed_len][snappy-framed BCS]

The two length prefixes are little-endian u32. They do not count toward the maximum message size; only the decoded payload does. The payload is BCS-serialized and then snappy-compressed in framed format.

Read-path bounds

The decoder validates before it commits memory, in this order.

  1. The declared uncompressed length is checked against the maximum message size before anything is allocated. The default maximum is 1 MiB. A prefix larger than that is rejected at the header, so no buffer is sized from an attacker’s number.
  2. The declared compressed length is checked against snappy’s maximum expansion for that uncompressed size. Snappy bounds how far compressed output can grow relative to its input, so a compressed length past that bound cannot describe the uncompressed length the peer just declared. Catching it at the header means a lying prefix is rejected before the body read starts rather than after it.
  3. The body is streamed in 8 KiB chunks. Committed memory tracks bytes that actually arrived rather than the declared prefix. A peer cannot make a node reserve a megabyte by claiming one: a peer that declares a large body and then withholds it stalls holding only what it sent, until the request timeout reaps the stream. An early end of stream is an error, not a short read.
  4. Decompression is bounded to the validated uncompressed length, so a malformed or hostile snappy stream cannot expand past the maximum message size.
  5. The final decompressed length must equal the declared length exactly. A mismatch in either direction is rejected before deserialization is attempted.

Only after all five checks does the decoder hand the bytes to BCS. A failure at any step surfaces as an I/O error on the stream, which the failure table below classifies.

Configuration

SettingValue
Request timeout, inbound and outbound10 seconds
Concurrent streams per connection100
Maximum message size1 MiB (operator-tunable as max_rpc_message_size)

The timeout and stream cap are libp2p’s defaults and are not overridden. The message size is read from node configuration and applies to the consensus RPC and the goodbye protocol alike.

Message inventory

Primary requests are Vote, PeerExchange, and EpochRecord. Vote carries the proposed header and any parent certificates the requester thinks the responder may be missing. EpochRecord carries an optional epoch number and an optional digest; with neither set, the responder returns the latest epoch record it has.

Primary responses are Vote, MissingParents, EpochRecord, PeerExchange, Error, and RecoverableError. MissingParents is the responder saying it could not verify the header’s parents and naming the digests it needs before it can vote.

Worker requests are ReportBatch and PeerExchange. Worker responses are ReportBatch (an acceptance with no payload), PeerExchange, Error, and RecoverableError.

A response of the wrong shape for the request is a permanent error at the requester, never a retryable condition.

Permanent and recoverable errors

Both roles split application-layer failures into two response variants, and the distinction is load-bearing.

Error is a permanent rejection. The responder evaluated the request and refused it on its merits: an invalid header or batch, a decode failure, a malformed request, a peer that is not in the committee, an unavailable epoch, a batch epoch mismatch, a duplicate batch. An identical request would be refused the same way, so the requester must not retry it.

RecoverableError is a transient responder-side condition: a momentary batch-store write failure, internal channel pressure during an epoch transition, an internal timeout, a database read or commit failure. The request is likely to succeed later, so the requester retries rather than giving up on the peer. A worker retries a batch report up to three times with backoff when the response is recoverable, and stops immediately when it is permanent. The primary’s vote path retries a recoverable response up to six more times at 250 ms intervals before treating it as permanent. Without the split, a peer that hit a one-off internal error would be indistinguishable from a peer that deliberately rejected the payload.

Note

One epoch mismatch is deliberately classified recoverable rather than permanent. When a peer reports an invalid-epoch header and the peer’s epoch is exactly one ahead of ours, the responder returns RecoverableError, because that is a race at the epoch boundary and not a fault. Every other epoch mismatch, including a peer more than one epoch ahead or any epoch behind, is a permanent Error.

Failure classification and penalties

Transport-level failures are not the peer’s fault and carry no penalty. Charging them would ban honest peers on ordinary wide-area conditions: with several requests in flight at disconnect time, one penalty each would compound into an immediate ban. Peers explains what each penalty level costs and how many occurrences reach a ban.

FailureOutboundInbound
Dial failureNoneNot applicable
Connection closedNoneNone
TimeoutMildNone
I/O error of a transport-flap kindNoneNone
I/O error of any other kindMediumMedium
Unsupported protocolsNoneNone
Response omissionNot applicableNone
Any peer-exchange goodbye failureNoneNone

A transport-flap I/O error is a connection reset, connection abort, timed-out read, unexpected end of file, broken pipe, or interrupted call. Anything else is very likely a codec violation, which is why it is the one failure kind that carries a Medium penalty in both directions.

UnsupportedProtocols carries no penalty in either direction, and that exemption is deliberate. Failing to negotiate a common protocol is honest version or role skew: the peer runs an older release, a different role’s protocol set, or a different chain id. Penalizing it would turn a chain-id or version split into a network partition, because the nodes that most need to find each other again during a rolling upgrade would ban each other first. The node logs a warning for operator visibility instead.

Peer-exchange goodbye failures are never penalized in either direction. A goodbye precedes a disconnect, so there is no relationship left to protect. The one failure that changes course is an outbound UnsupportedProtocols, which triggers the fallback to the embedded exchange variant.

Application-layer admission control

Below the network layer, the primary and worker handlers cap the work a peer can commission. Load beyond a cap is shed, not queued: the requester gets a fast denial and tries another peer.

LimitValueWhat it bounds
Pending sync requests per peer2A single peer’s in-flight sync streams, so it cannot fill the global slots and starve other peers. Enforced separately by the primary and the worker.
Concurrent epoch stream operations5Pending plus active epoch streams on a primary, across all peers.
Concurrent epoch-record serves5Concurrent EpochRecord request-response serves on a primary. Its own budget, separate from epoch streams, so an epoch-record flood and the stream paths cannot starve one another.
Concurrent batch stream operations5Pending plus active batch streams on a worker, across all peers.
Concurrent shed tasks8Short-lived tasks that write a denial to a stream refused at admission. Enforced separately by the primary and the worker, so total sync-task fan-out is the admitted cap plus this.
Consensus-result tallies20Distinct pending consensus results a primary tracks while tallying signatures. A floor, not a ceiling: the effective cap is the larger of this and the committee size.
Epoch votes deduplicated1024Distinct verified epoch votes retained for ingress deduplication, so a replayed vote is dropped before a second signature verification.
Batch digests per request500Digests a peer may name in one sync batch request. Sized well above the realistic maximum and roughly sixty-six times below what would fit in a 1 MiB message.

The last three bound the gossip and sync ingress paths of the same handlers rather than the request-response arms, but they are the caps that decide how much work one peer’s traffic can commission from a node, so operators size against them together.

Source of truth

BehaviorCode
Wire format, read-path bounds, chunked body readcrates/network-libp2p/src/codec.rs
Protocol identifiers and versionscrates/network-libp2p/src/types.rs (NetworkType)
Permanent and recoverable error variantscrates/network-libp2p/src/error.rs (NetworkError, RpcFailure)
Behaviour config, failure classification, penaltiescrates/network-libp2p/src/consensus.rs
Message size, goodbye budget and timeoutcrates/config/src/network.rs (LibP2pConfig)
Primary message variants and error classificationcrates/consensus/primary/src/network/message.rs
Worker message variants and error classificationcrates/consensus/worker/src/network/message.rs
Primary admission-control limitscrates/consensus/primary/src/network/mod.rs
Worker admission-control limitscrates/consensus/worker/src/network/mod.rs

This page mirrors those files. Update this page when those files change.

Sync streams

Telcoin Network moves bulk data — sealed batches, epoch pack files, missing certificates, single consensus outputs — over a dedicated stream protocol rather than over request-response. A node opens one substream, writes its request as the first frame, and reads the response off that same stream until the responder ends it. There is no separate handshake and no digest correlation: the request travels in the stream that carries the answer.

This page is for validators, RPC providers, dapp and indexer operators, and bridge partners who need to know how a node catches up and what it accepts while doing so. It covers the exchange shape, the frame tags, the request discriminants, and the limits that bound an open.

The implementation lives in crates/network-libp2p/src/stream/, which holds the custom libp2p behaviour and connection handler, and crates/network-libp2p/src/sync/, which holds the frame layer and the typed requests. Request-response covers the RPC path this one deliberately avoids, and Transport has the full protocol-ID reference table.

Why this is not request-response

The request-response codec caps a single message at 1 MiB by default. That bound is right for a vote, a header, or a peer-exchange map, and wrong for catch-up. A single missing-certificates exchange is allowed to stream up to 64 MiB of encoded certificates, and an epoch pack is chunked at 256 KiB per frame precisely because the whole pack can run to hundreds of megabytes. Expressing those transfers as request-response would mean hundreds of correlated RPCs, each needing its own pending-request entry on both sides.

The stream path is a custom behaviour and connection handler, not libp2p’s libp2p-stream. Writing it in-tree is what makes the open budget, the per-peer inbound rate limit, and the failure classification below possible. Each node advertises exactly one stream protocol, chosen by its role and namespaced by chain id:

primary   /tn-primary-sync-{chain_id}/0.0.1
worker    /tn-worker-{worker_id}-sync-{chain_id}/0.0.1

A worker advertises only its own worker index, so worker 0 of one validator never negotiates a sync stream with worker 1 of another. The chain id in the protocol name means a node on one chain fails negotiation against a node on another rather than exchanging data with it.

The exchange

1. requester opens a substream on the role's sync protocol
2. requester writes  Req(<typed request>)    <- always the first frame
3. responder writes  Ack                     <- accepted
                 or  Deny(<reason>)          <- declined; stream closes
4. responder writes  Data(<bytes>) ...       <- zero or more payload frames
5. responder writes  End                     <- orderly finish
                 or  Err(<reason>)           <- aborted after an error
6. both sides close the stream

The requester waits 5 seconds for the first response frame. A peer that negotiates the protocol but never answers trips that timeout, and the requester moves to the next peer instead of stalling the whole catch-up. On the responding side, a peer that opens a stream and never sends its request frame is dropped after 5 seconds, so it cannot hold an admission slot by staying silent.

Frame tags

Every frame is a self-contained encoded unit, and the enum discriminant is the frame tag.

tag   frame           payload
----  --------------  -------------------------------------------
 0    Req(request)    the typed request for this role
 1    Ack             none
 2    Deny(reason)    0 = AtCapacity, 1 = Unavailable
 3    Data(bytes)     an opaque chunk of response payload
 4    End             none
 5    Err(reason)     0 = Internal, 1 = Malformed

Data payload encoding is defined by each exchange, not by the frame layer. The frame layer only guarantees that a Data frame arrives whole or not at all.

The typed request inside a Req frame carries its own discriminant, one table per role:

WorkerSyncRequest                    on /tn-worker-{worker_id}-sync-{chain_id}/0.0.1
tag   variant              fields
----  -------------------  ------------------------------------
 0    Batches              batch_digests, epoch
PrimarySyncRequest                   on /tn-primary-sync-{chain_id}/0.0.1
tag   variant              fields
----  -------------------  ------------------------------------
 0    EpochPack            epoch
 1    MissingCertificates  exclusive_lower_bound, skip_rounds
 2    EpochPackPartial     epoch, last_consensus_number
 3    ConsensusOutput      number

Note that the request tags are not in source-declaration reading order by name: MissingCertificates is 1 and EpochPackPartial is 2, because the partial variant was added after the missing-certificates one and appended rather than inserted.

Variant order is the wire format

Warning

Reordering, inserting, or removing a variant in any of these enums changes the bytes a deployed peer sees, without changing a single type signature. The code still compiles, the round-trip tests still pass, and a peer running the old build decodes the wrong variant from the same tag byte. This is silent corruption, not a build break.

The protocol version in the name (0.0.1) is the only thing that can signal such a change, so a variant reorder requires a version bump and the negotiation failure that comes with it. New variants may be appended at the end, because appending leaves every existing tag where it was.

Two tests pin the mapping: one asserts the exact frame tag bytes, the other asserts the leading discriminant byte of each request variant. A reorder that would otherwise pass every behavioural test fails those.

Frame encoding

Each frame is a single codec unit and inherits the encoding described in Request-response: a length-prefixed, snappy-compressed BCS message, read against the bytes that actually arrive rather than against the length a peer declares. The difference is the size bound. Request-response applies one fixed 1 MiB cap to every message, while each sync exchange supplies its own frame cap: a worker batch exchange sizes it from the epoch’s maximum batch size, and a primary epoch-pack exchange sizes it from the 256 KiB pack chunk. The cap bounds one frame, so no peer can force an oversized allocation, but it does not bound the transfer, which is what makes bulk catch-up possible on this path.

An Err frame is a successful read. It reports that the responder gave up, which is different from the transport failing underneath the exchange, and the two are handled differently.

Deny is backpressure, not a fault

A responder admits a bounded number of concurrent sync streams: five per role, and at most two in-flight sync streams from any one peer. Past that it does not drop the stream. It writes Deny(AtCapacity) and closes.

That distinction matters to a requester’s wall-clock cost. A dropped stream costs the requester its full 5-second ack timeout before it can try elsewhere; an explicit Deny costs it one round trip. A node catching up against a busy committee therefore keeps moving instead of serializing on timeouts, and a busy responder sheds load without being scored for it.

Deny(Unavailable) is the other normal case: the peer speaks the protocol and has capacity but does not hold the requested data.

The shed path is itself bounded. Writing a Deny costs a short-lived task, and at most eight of those run at once; beyond that budget the stream is dropped without a Deny, and the requester sees a reset and retries elsewhere.

Limits

LimitValueWhat it bounds
Pending outbound opens1024Opens buffered across the whole behaviour; past this an open is rejected immediately rather than queued
Total open budget15sDial, connection establishment, and substream negotiation combined; on expiry the caller gets a timeout
Substream negotiation10sA single outbound substream negotiation, enforced by libp2p
Stale sweep1sHow often expired pending opens are failed and cleared
Inbound streams per peer256 per secondInbound sync streams accepted from one peer per one-second window; the next one is refused
Pending outbound per connection256Opens buffered by one connection handler before it sheds

The 15-second and 10-second budgets nest: the total open budget covers a dial that may itself have to establish a connection, while the negotiation timeout applies only once a connection exists. An open that is dispatch-ready when its peer disconnects is re-queued for dialing rather than handed to a handler that no longer exists.

Failure classification

Stream failures are classified into a taxonomy that mirrors the request-response one, so comparable misbehaviour scores comparably on both paths:

failure               penalty   rationale
--------------------  --------  -----------------------------------------
DialFailure           none      transport-level; not the peer's fault
Timeout               mild      a stalled open
UnsupportedProtocol   none      honest version or role skew
Io (transport flap)   none      reset, abort, EOF, broken pipe, interrupt
Io (other)            medium    a genuine protocol or codec violation
InboundRateLimited    medium    abuse of the inbound stream path

A failed negotiation is not even reported to the behaviour. Probing whether a peer speaks the sync protocol is a normal thing to do during a rolling upgrade, and a probe that comes back negative should cost nothing.

These penalties are classified and logged, not enforced. Every classified failure produces a log line recording the penalty it would incur, and nothing is applied to the peer’s score. Enforcement is gated on telemetry confirming the classification does not fire on healthy peers. Until it is turned on, a peer cannot be banned by anything on this page — see Peers for the scoring that is enforced.

Source of truth

BehaviorCode
Frame tags, deny and error reasons, frame read/writecrates/network-libp2p/src/sync/frame.rs
Typed request enums and their pinned discriminantscrates/network-libp2p/src/sync/request.rs
Open budget, stale sweep, inbound rate limit, pending-open capcrates/network-libp2p/src/stream/behavior.rs
Negotiation timeout, per-connection pending cap, error classificationcrates/network-libp2p/src/stream/handler.rs
Advertised upgrade, failure taxonomy and penalty mappingcrates/network-libp2p/src/stream/upgrade.rs
Penalty reporting (metrics-only, not enforced)crates/network-libp2p/src/consensus.rs (process_stream_event)
Sync protocol IDscrates/network-libp2p/src/types.rs (sync_protocol)
Shared codec boundscrates/network-libp2p/src/codec.rs
Per-exchange frame and transfer capscrates/consensus/primary/src/network/sync_codec.rs, crates/consensus/worker/src/network/handle.rs

This page mirrors those files. Update this page when those files change.

Getting Started

Guides for connecting to Telcoin Network and building on it. Because the network is EVM-compatible, everything here uses standard Ethereum tooling; if you have built on Ethereum, the workflow will be familiar.

Start with Reading Blockchain Data to query the network using cURL, programmatic JSON-RPC requests, or client libraries. Dapp Development covers development tooling. Faucet explains how to get testnet TEL, and Hardware Requirements lists what is needed to run a node.

Bootstrap peers

Bootstrap peers are dial hints used to discover the network. Configure them in network-config under the node’s Telcoin data directory, without editing the genesis committee file. They do not grant committee membership or permission to publish gossip; those decisions come from chain state.

Add a bootstrap_peers map keyed by the peer’s BLS public key. Each entry contains the primary endpoint and a nonempty workers list in worker ID order (worker 0 first). Use the same base58 key encodings and multiaddresses as the peer’s advertised node information. Replace the placeholders in this example:

bootstrap_peers:
  <BLS_PUBLIC_KEY>:
    primary:
      network_address: /ip4/192.0.2.10/udp/9000/quic-v1
      network_key: <PRIMARY_NETWORK_PUBLIC_KEY>
    workers:
      - network_address: /ip4/192.0.2.10/udp/9001/quic-v1
        network_key: <WORKER_0_NETWORK_PUBLIC_KEY>
      - network_address: /ip4/192.0.2.10/udp/9002/quic-v1
        network_key: <WORKER_1_NETWORK_PUBLIC_KEY>

A nonempty map replaces the entire genesis bootstrap set. Include every seed you want the node to dial. Entries are never merged with genesis, so omitted genesis peers can be deliberately excluded. An absent bootstrap_peers key or an empty map (bootstrap_peers: {}) keeps the genesis fallback. Existing config files continue to load unchanged. The legacy single worker: {...} entry is also accepted and becomes a one-element worker list when serialized again.

For a process-only override, pass the map as YAML or JSON to --bootstrap-peers. For example, save the map itself (without the enclosing bootstrap_peers: key) in bootstrap-peers.yaml and run:

telcoin-network node --bootstrap-peers "$(cat bootstrap-peers.yaml)"

The CLI map takes precedence over network-config and is never written back to disk. An explicitly empty CLI map restores the genesis fallback even when network-config contains custom peers:

telcoin-network node --bootstrap-peers '{}'

Invalid map entries fail during CLI parsing or config loading. Valid but unreachable endpoints can prevent the node from joining the network and cause the bootstrap readiness wait to time out. Changes take effect after a restart.

Validator production operations

This page defines the production controls that sit around the node software. The node does not configure the host firewall, distribute releases, or provide an HSM or remote BLS signer.

Network topology

Use separate network roles so a public service cannot directly expose a consensus validator:

RoleExposurePurpose
Core validatorPrivate networkConsensus primary and worker traffic
Sentry or gatewayPublic P2P, selected RPCAbsorb public connections and provide stable entry points
ObserverPublic P2P and optional RPCRead-only access without a consensus vote
ManagementPrivate operator networkMetrics, logs, SSH, deployment, and backup access

Core validators should accept primary and worker traffic only from the approved validator and sentry address set. Do not expose validator RPC, metrics, health, or management ports to the public internet.

Permissionless observers need public entry points. Provide those through sentries, bootstrap nodes, or RPC gateways. Avoid opening every core validator to anonymous peers simply to support discovery.

Firewall configuration

The node has no default consensus ports: it takes them from the primary and worker addresses recorded in node-info.yaml at key generation. By convention, validators use UDP 49590 for the primary network and UDP 49594 for the worker network. RPC and metrics ports are TCP and should remain private unless a dedicated gateway protects them.

Apply these controls outside the node process:

  • allow core validator P2P ingress only from the current validator and sentry address set;
  • allow management and metrics ingress only from operator networks;
  • give public observers and gateways their own security group or firewall policy;
  • restrict core validator egress to approved peers, DNS, time sources, telemetry, and release infrastructure where the platform supports it;
  • log rejected traffic and alert on sustained scans, unexpected destinations, and connection exhaustion.

Treat DHT records, peer exchange messages, and advertised RPC endpoints as untrusted network data. Never use them to add firewall rules or cloud security group entries.

Distribute validator and sentry addresses through an authenticated operator channel. A production address manifest should include the network, epoch or activation time, peer identity, IP addresses, ports, expiry, and signer set. Stage additions before removals, verify connectivity from every validator, and retain the previous manifest for rollback.

Advertising an RPC endpoint

Validators should advertise a JSON-RPC endpoint. Observers do not gossip the transactions they accept. They forward each one over JSON-RPC to the endpoint advertised by the committee validator whose slot owns the sender’s account, and fall back to another validator’s endpoint when that one has none or cannot be reached (see Observer). A validator without an endpoint receives no forwarded transactions, and if no committee validator advertises one, observers cannot forward at all.

Serve the endpoint from a gateway, as Network topology recommends: bind the node’s RPC server to a private interface that only the gateway can reach, terminate TLS on the gateway, and keep the validator host off the public internet. A TLS reverse proxy on the validator host itself, in front of an RPC server on loopback (--http.addr 127.0.0.1, the default), also works, but it exposes the validator host to the internet. Either way, advertise the gateway’s or proxy’s https:// URL, never the node’s own RPC ports:

telcoin-network keytool set-rpc \
    --datadir /var/lib/telcoin \
    --http https://rpc.validator.example.com/

keytool generate validator --rpc-http <URL> records the same field at key generation. The node reads the endpoint at startup, so restart it after a change.

Choose the URL with these rules:

  • use https://: the node also accepts http://, but observers then send signed transactions in cleartext;
  • use a public hostname or address: observers refuse loopback, private-use (RFC 1918), link-local, unique-local, shared-address-space and unspecified addresses, and the localhost and .local names, so an endpoint on one of them receives no forwarded transactions;
  • make sure the hostname resolves to a public address: the check reads the host as written and does not resolve DNS, so a hostname that resolves to a private address passes it, and observers then dial that address, fail to reach it, and forward nothing to this validator;
  • rate-limit the gateway or proxy, because the endpoint is published in the validator’s node record and anyone can find it.

Networks where one operator runs every validator, such as a single-host test network that advertises 127.0.0.1, can let observers dial private addresses with allow_private_forward_targets. Production networks leave it off.

BLS key custody

The current CLI stores the BLS12-381 key in node-keys/bls.kw when a passphrase is used. The file uses AES-256-GCM-SIV with PBKDF2-HMAC-SHA256. The node decrypts the key into process memory to sign consensus messages.

For production today:

  • use env, stdin, or ask passphrase mode and never use no-passphrase;
  • keep the data directory on an encrypted volume with owner-only access;
  • isolate the validator process and host from build jobs, developer tools, and public RPC services;
  • retrieve the passphrase from an operator secret system at startup and avoid shell history, command arguments, and long-lived environment files;
  • keep encrypted offline backups and test recovery on an isolated replacement host;
  • monitor changes to node-info.yaml, node-keys/, startup arguments, and process ownership.

Consumer hardware wallets, phones, and Chromebooks do not currently implement the node’s BLS signing interface. Do not assume they can custody or use the validator key without a dedicated integration.

For stronger isolation, add a reviewed remote signer or HSM interface before mainnet. The interface should expose only the required domain-separated BLS operations, authenticate the validator client, rate limit requests, produce audit logs, and keep private key bytes outside the node host. This requires protocol integration and failure-mode testing. It is not available in the current binary.

BLS identity rotation affects committee registration and peer identity. Treat it as a coordinated protocol and governance operation, not a local file replacement.

Release and network update process

There is no automatic binary release or node update channel in this repository. Operators currently build from source. Before a mainnet rollout, define one signed release manifest containing:

  • the release version, source commit, and every first-party submodule commit;
  • a SHA-256 digest for each immutable artifact and container image;
  • build provenance and an SBOM;
  • supported network and configuration schema versions;
  • any fork epoch or activation condition;
  • rollback compatibility and the final safe rollback point;
  • signatures from the required release owners.

Verify the manifest and artifact digest on the target host before installation. Record the installed digest and node version in operator inventory.

Use this rollout order:

  1. Test upgrade and rollback on an isolated network using production configuration shapes.
  2. Upgrade an observer or sentry canary and check sync, RPC, metrics, and peer behavior.
  3. Upgrade one validator and observe at least one normal consensus cycle.
  4. Continue in stake-weighted batches without taking enough stake offline to break quorum.
  5. Compare every node’s running version, configuration digest, peer set, and chain progress with the release manifest.
  6. Stop before the rollback cutoff if acceptance checks fail, restore the prior artifact and configuration, and verify recovery.

For an emergency patch, name an incident owner, a release owner, and an independent verifier. Freeze unrelated rollout changes, state the affected versions and activation deadline, use the same signed manifest and digest checks, and record each validator’s completion. A protocol fork needs a separately approved activation plan and explicit readiness evidence from enough validator stake.

Capacity monitoring

Resource use rises with network load, and some limits are fixed in the release. Watch these signals on every validator and observer. Hardware requirements explains the model behind each one. The alert levels are starting points; tune them after a week of baseline data.

SignalWhere to read itStarting alertWhat a rising trend meansWhat to do
CPU pressure/proc/pressure/cpu, some avg60Above 20 for 10 minutesRunnable threads are waiting for a coreCheck the engine backlog first. If execution lags, move to a CPU with faster single-thread performance. If RPC load causes it, serve public RPC from an observer instead.
Memory pressure/proc/pressure/memory, full avg60Above 1The kernel is reclaiming pages the node needs, so all its threads stallAdd RAM. On RPC nodes, lower --rpc-cache.max-blocks. Do not add swap.
IO pressure/proc/pressure/io, full avg60Above 10Execution is waiting on diskMove to faster storage or raise the volume’s provisioned IOPS, then check for throttling.
Disk throttlingThe provider’s volume metrics (GCP reports throttled read and write operations and bytes per disk); iostat -x queue size and awaitAny sustained throttlingThe volume has reached its provisioned IOPS or throughputRaise the volume limits or move to local NVMe.
Batch cache occupancyLive data in <datadir>/consensus-db/cache from an MDBX statistic, for example mdbx_stat -ef from the libmdbx tools (pages used minus free pages, times the page size), against the 1 GiB maximum. Not the file size (see below)768 MiB of live dataBatches are not leaving the cache: consensus is not committing them, or their eviction is failing (see the next row)Tell the Telcoin Association network team. Hardware does not raise this limit.
Batch cache fulltn_storage_write_failures_total{env="cache"}, which counts failed write operations by op: insert (a bare insert), begin (a transaction that could not start, whose writes then go out one at a time, so it loses no write), txn_insert (an insert inside a transaction), commit (a transaction commit, which loses every write in it), remove and clear. Or the node log: an error from target layered_db_runner that starts DB Insert or DB TXN Insert and names a cache table, for example DB Insert node_batches_cache or DB Insert our_node_batches_cache followed by the MDBX errorAny occurrenceThe cache reached its 1 GiB maximum. The node keeps running but holds each batch it could not write in memory until the batch is evicted or the epoch closes, so resident memory climbs. A rising remove or commit count means deletes, including evictions, are not freeing space in the fileAlert. Expect resident memory to grow while failures continue, and tell the Telcoin Association network team.
Engine backlogtn_engine_queued_outputs, 0 to 84 or more for 5 minutesExecution is falling behind consensusAt 8 the engine queue is full and outputs back up into the 64-slot channel in front of it. Check CPU and IO pressure and tn_engine_execution_duration_seconds.
Resident memoryreth_process_resident_memory_bytesAbove 70% of RAMQueued outputs, RPC caches or the transaction pool are growingCompare with the engine backlog and the RPC request rate. A climb right after a restart is replay and should fall once the node catches up.

Pressure stall information needs a kernel built with PSI support, which mainline Linux added in 4.20. If /proc/pressure is missing, the running kernel lacks it or has it disabled.

The batch cache is an MDBX file that never shrinks. Committed batches are evicted when their consensus output, already saved to the epoch pack, is forwarded to execution, and the rest are removed at each epoch close. The file keeps its size either way, so du shows the highest level the file has reached on that datadir, a high-water mark, not how full the cache is now. In the 2026-09 benchmark the file reached its 1 GiB maximum on every c3 validator and on four of ten e2 validators (the rest stopped at 960 MiB) while each 20-minute epoch carried only about 0.5 GB of batch data. The cache now holds batches that are not yet committed, not an epoch’s committed batch data (see Per-epoch batch-cache ceiling). Read live occupancy from MDBX instead. The release exports no metric for it. Summed over all validators since the epoch started, tn_worker_batch_size_bytes gives a rough estimate of the batch data a node has written to its cache, and on a node that stayed in sync the estimate runs low. The histogram records each batch once, when its worker reports it to the primary, but a node writes each of its own batches to the cache twice and also writes every peer batch it validates, including ones that failed quorum. It is not live occupancy, because committed batches are evicted.

Resident memory includes pages of the memory-mapped databases that the process has touched, so it rises slowly as the database working set grows. Alert on how fast it climbs during load and after restarts, not only on the level.

Required evidence before mainnet

  • firewall policy tested from allowed and denied source networks;
  • signed address manifest distribution and rollback rehearsal;
  • BLS backup and isolated recovery rehearsal;
  • signed artifact verification on every target platform;
  • canary, phased rollout, and rollback rehearsal;
  • quorum dashboard based on validator stake and running version;
  • incident contacts and an out-of-band coordination channel.

Reading Blockchain Data

Since blockchains only require payment (gas) to change their state, reading from a blockchain is completely free.

Note

‘State’ is the term used to describe the values of all variables stored within the blockchain.

When we read from the blockchain, we simply observe the state of the blockchain, and do not change it. We are therefore not required to pay any gas fees for this action.

Since we do not have to pay gas, we do not need to hold any gas, and we therefore do not even need a wallet! (This will come later).

cURL requests

Certain nodes running the Telcoin Network have a Remote Procedure Call (RPC) endpoint. These endpoints are a type of Application Programming Interface (API) which allows users to execute a procedure ( a piece of code) on the network as if it occurred locally.

RPCs have a few key components:

  • Client - The system initiating the RPC request - i.e. the user/ developer’s machine
  • Server - The system that receives and processes the RPC request - i.e. the Telcoin Network node.
  • Protocol - Defines the rules and conventions for data exchange between the client and server - i.e. the Telcoin network source code running on the Telcoin Network node.

Using cURL to Make RPC Calls

cURL is a command-line tool used to transfer data to and from a server using various protocols, including HTTP, HTTPS, FTP, and more. In the context of RPC, cURL can be used to send HTTP POST requests to an RPC server and receive responses. This is what we will demonstrate here.

Warning

All commands throughout the documentation will be focused on use with Unix-like (e.g. macOS, WSL2) systems.

eth_blockNumber

The eth_blockNumber method is one of the simplest RPC calls to make. From the documentation we see it takes no arguments (parameters) - the ‘parameters’ section shows None, and it returns one value representing the current block number of the network.

Request

To query this method, we can simply copy the code from the ‘Request’ section of the page:

curl https://rpc.adiri.tel \
-X POST \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}'

Let’s break down this command:

  • curl https://rpc.adiri.tel: Specifies the URL of the Ethereum RPC endpoint. This is the URL that one of the Telcoin Network Adiri Testnet nodes is accessible from.
  • -X POST: Indicates that the request is a POST request.
  • -H "Content-Type: application/json": Sets the content type of the request to JSON.
  • --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}': Provides the JSON data for the request. This JSON object includes:
    • "jsonrpc": "2.0": The version of the JSON-RPC protocol being used.
    • "method": "eth_blockNumber": The RPC method being called.
    • "params": []: An empty array, as this method does not require parameters.
    • "id": 83: An arbitrary identifier for the request. This could be any numerical value.

Executing the command

paste the above command into your command line and press ‘Enter’. You should receive a response.

Response

The response should look something like:

{
"jsonrpc":"2.0",
"result":"0x7328cb", //7547083
"id":83
}

Note:

  • "jsonrpc": "2.0": Confirms the JSON-RPC protocol version.
  • "result": "0x7328cb": The hexadecimal value of the current block number.
  • "id": 83: The ID of the request, matching the one sent.

Programmatic Requests

We can also write a simple script to make cURL requests. This will allow us to build around the RPC responses. This will make it easier to call different endpoints with different arguments and handle data that we receive from the RPC calls.

First, let’s set up the typescript project.

  1. check node and npm (node package manager) versions:

In your terminal, enter:

node --version
npm --version

It should give something like:

v18.17.1
10.1.0

If these packages are not installed, run:

sudo apt install nodejs npm
  1. Create a project directory and navigate into it:
mkdir telSimpleCalls
cd telSimpleCalls
  1. Open a new VsCode (or alternative IDE) window of your directory:
code .
  1. Initialise a new project:
npm init -y
  1. As we will need typescript and axios packages, we install them using:
npm install typescript axios
  1. Initialise the TypeScript configuration:
npx tsc --init

This command creates a tsconfig.json file in your project directory. You can leave the settings as default.

You should now have your TypeScript project set up! Let’s get into writing some scripts!

eth_blockNumber

Building the Script

  1. Create a TypeScript file in the project folder you created above:
touch index.ts
  1. Open the file and paste the following code in:
import axios from "axios";

const rpcUrl = "https://rpc.adiri.tel";
const data = {
    jsonrpc: "2.0",
    method: "eth_blockNumber",
    params: [],
    id: 1,
  };

const headers = {
    "Content-Type": "application/json",
  };

async function fetchData() {
    const response = await axios.post(rpcUrl, data, { headers });
    console.log(response.data);
}

fetchData();
  1. Let’s run through what each line does:
import axios from "axios";

This line imports the Axios library, which we installed earlier using npm. It is used for making HTTP requests (similar to curl).

const rpcUrl = "https://rpc.adiri.tel";

This creates a new variable named rpcUrl to which we assign the url of the Telcoin RPC server.

const data = {
    jsonrpc: "2.0",
    method: "eth_blockNumber",
    params: [],
    id: 1,
};

This creates an object named data where we include the data for our request. This layout is standard for JSON-RPC requests within Ethereum Virtual Machine (EVM) based networks (of which Telcoin Network is one). The individual fields are:

  • jsonrpc: Specifies the JSON-RPC protocol version, which is 2.0 for EVM requests.
  • method: Indicates the JSON-RPC method being called, eth_blockNumber in this case. This method returns the number of the most recent block.
  • params: An array of parameters to be passed to the method. As eth_blockNumber takes no input arguments, parameters is left as an empty list.
  • id: A unique identifier for the request. This is used to match responses with requests when multiple JSON-RPC calls are made asynchronously. This can be set to any integer.
const headers = {
    "Content-Type": "application/json",
};

This is used to inform the server that the request body format is JSON. This is always the case with RPC calls.

async function fetchData() {
        const response = await axios.post(rpcUrl, data, { headers });
        console.log(response.data);
}

This is the main function to fetch the desired data. Note that it is labelled as async. This allows us to use the await keyword to handle promises asynchronously. Also note it takes no arguments.

The main thing this does is use axios.post to make a POST request to the RPC URL. It sends the data object we created earlier and includes the headers. This is all required because that’s just how HTTP requests work!

Upon running the line const response = await axios.post(rpcUrl, data, { headers }), axios posts the content to the RPC node and immediately receives a promise. This response will eventually resolve to the response of the HTTP request. The await keyword pauses execution of the script until the promise resolves and returns the desired information. This requested information, which in our case is the blocknumber, is saved to a variable named response.

response is another object containing a number of key-value pairs. Of these keys, we are interested in the data key, so we access this directly using response.data. This is then printed to the console (or terminal in our case).

fetchData();

Finally, fetch data is called. This runs the code within the fetchData function.

Running the Script

To run the script, we first compile the TypeScript to JavaScript by running:

npx tsc

This should create a new file named index.js in your root folder. We then run this with the command:

node index.js

Response

The console should give a response that looks something like:

{ jsonrpc: '2.0', result: '0x789f67', id: 1 }

Here we see the RPC node returns:

  • The JSON-RPC protocol version, which matches the one we specified.
  • The ‘result’ which in this case is the hexadecimal value representing the current integer block number of Telcoin Network.
  • The id, which we had specified as 1 in our request.

If this worked, well done! We’ve covered some fundamentals of getting started in blockchain development. Now we can look at some more involved requests.

If it didn’t, feel free to reach out to one of us in the Telcoin discord.

eth_call

Here we demonstrate how to use eth_call using TypeScript scripts.

Method Parameters

From the RPC method reference we see eth_call takes a number of arguments, some of which are optional. These are recreated below:

Object - The transaction call object:

  • from: DATA, 20 bytes - (optional) The address the transaction is sent from.
  • to: DATA, 20 bytes - The address the transaction is directed to.
  • gas: QUANTITY - (optional) Hexadecimal value of the gas provided for the transaction execution.
  • eth_call consumes zero gas, but this parameter may be needed by some executions.
  • gasPrice: QUANTITY - (optional) Hexadecimal value of the gasPrice used for each paid gas.
  • maxPriorityFeePerGas: QUANTITY - Hexadecimal maximum fee, in Wei, the sender is willing to pay per gas above the base fee.
  • maxFeePerGas: QUANTITY - Hexadecimal maximum total fee (base fee + priority fee), in Wei, the sender is willing to pay per gas.
  • value: QUANTITY - Hexadecimal of the value sent with this transaction.
  • data: DATA - (optional) Hash of the method signature and encoded parameters. See Ethereum contract ABI specification.

block parameter: QUANTITY|TAG [Required] - Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized. See the default block parameter.

From this description, we can see we are expected to pass two variables:

  • an object, comprised of some or all of the keys within it (e.g. from, to), and their associated values.
  • the block parameter

Selecting Parameters

For this example, we will only use eth_call to read some data. As reading state from the blockchain does not involve changing state, we do not need gas. As a result, we will not include the gas , maxPriorityFeePerGas, maxFeePerGas or gasPrice parameters. Similarly, reading state from the blockchain does not require an account. Therefore, we are not required to provide a from argument to the method call. As we are not writing any values to the blockchain we can also omit the value parameter.

Thus, we only need to provide values for:

  • to, the address of the contract with which we wish to interact.
  • data, the information specifying the manner in which we interact with the contract.
  • block parameter, the block we would like to read from.

Updating Request Script

We can begin by amending our code from the previous example to prepare for this new method call.

Previously, our script was:

import axios from "axios";

const rpcUrl = "https://rpc.adiri.tel";
const data = {
    jsonrpc: "2.0",
    method: "eth_blockNumber",
    params: [],
    id: 1,
  };

const headers = {
    "Content-Type": "application/json",
  };

async function fetchData() {
    const response = await axios.post(rpcUrl, data, { headers });
    console.log(response.data);
}

fetchData();

As we will still be using axios, calling the same URL and using the same protocol (JSON), most of our script remains the same as before. The only changes occur in the data variable, where we want to use the eth_call method instead of eth_blockNumber, and we will need to pass some information into params.

Let’s start by making these changes to the data variable. The new data variable will be of the form:

const data = {
      jsonrpc: "2.0",
      method: "eth_call",
      params: [
        {
          to: "",
          data: "",
        },
        "latest",
      ],
      id: 1,
    };

Here we see we have the toand datakeys in our parameters object, and we will be querying at the latest block. We have also updated the method we are calling to eth_call. We now need to find values for toand data.

For this example, we will consider the Telcoin AUD token which has contract address 0xc9593289e95938FC4170B3Fc51dbcCa0A46b4486. This will be our value for our to parameter.

Function Selector

Telcoin stablecoins are modified ERC-20 tokens. For this example we will use the ERC-20 getter function ‘name()’ to demonstrate the use of eth_call.

Since we know ‘name()’ is a function on the smart contract, and we see it takes no arguments, we can pass this into the data field of our parameters object. There is one slight caveat - the EVM will not understand the function call ‘name()’, but will only accept the ‘0x’ prefixed first four bytes (8 characters in hexadecimal) of the Keccak-256 hash of name(). These four bytes (with the ‘0x’ prefix) are known as the function selector. Let’s run through this:

The function selector for name() can be computed from its signature ("name()"), which hashes to:

06fdde0383f15d582d1a74511486c9ddf862a882fb7904b3d9fe9b8b8e58a796

(This can be computed using an online Keccak-256 hash calculator such as this one). Taking the first 4 bytes (8 characters) gives:

06fdde03

and adding the ‘0x’ prefix gives:

0x06fdde03

This is the string we place in the data field.

Updated Data Variable

Bringing this all together, our new data variable should be:

const data = {
      jsonrpc: "2.0",
      method: "eth_call",
      params: [
        {
          to: "0xc9593289e95938FC4170B3Fc51dbcCa0A46b4486",
          data: "0x06fdde03",
        },
        "latest",
      ],
      id: 1,
    };

Updated Script

Our script should be:

import axios from "axios";

const rpcUrl = "https://rpc.adiri.tel";
const data = {
      jsonrpc: "2.0",
      method: "eth_call",
      params: [
        {
          to: "0xc9593289e95938FC4170B3Fc51dbcCa0A46b4486",
          data: "0x06fdde03",
        },
        "latest",
      ],
      id: 1,
    };

const headers = {
    "Content-Type": "application/json",
  };

async function fetchData() {
    const response = await axios.post(rpcUrl, data, { headers });
    console.log(response.data);
}

fetchData();

Running the Script

To run the script, we first compile the TypeScript to JavaScript by running:

npx tsc

Then run this with the command:

node index.js

Result

The console should give the following response:

{
  jsonrpc: '2.0',
  result: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b54656c636f696e20415544000000000000000000000000000000000000000000',
  id: 1
}

Here we see the RPC node returns:

  • The JSON-RPC protocol version, which matches the one we specified.
  • The ‘result’ which in this case is the hexadecimal value representing the name of the token at the contract address 0xc9593289e95938FC4170B3Fc51dbcCa0A46b4486.
  • The id, which we had specified as 1 in our request.

Decoding the Result

The result received from the RPC node is:

0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b54656c636f696e20415544000000000000000000000000000000000000000000

This is likely not the expected result - we expected ‘Telcoin AUD’ as the output. We need to decode this string and see what we get. Starting from the most significant byte (the leftmost) we get:

‘0x’ prefix

The 0x prefix indicates the following string is in hexadecimal format. From this point on, the data is organised into 32 byte (64 character) chunks.

Offset

The first 32 bytes symbolise the offset of the value. In this case, the 32 bytes after the 0x prefix are: 0000000000000000000000000000000000000000000000000000000000000020. This value, converted from hexadecimal, is 32 (it contains a ‘2’ in the ’16’s column, signifying 32). This means that the actual data starts in 32 bytes time (i.e. at byte 64). The 32 bytes in between this offset value and the actual data gives us the length of the data.

Length

The next 32 bytes, 000000000000000000000000000000000000000000000000000000000000000b, tell us the data we are interested in is 11 bytes long.

Actual data

Reading 11 bytes (22 characters) from byte 64 (the next byte along) gives: 54656c636f696e20415544. This can be converted directly from hex to a string using an online tool such as this one. It translates to ‘Telcoin AUD’, exactly as expected!

The remaining zeroes in the response are just padding to ensure each section of data is 32 bytes long.

Conclusion

And there we have it! We’ve learned to use scripts to query more advanced data straight from the blockchain with very few dependencies. We’ve also learned how to decode data we receive from RPC nodes.

Libraries

Ethers.js

Ethers.js is a library which abstracts away many of the complexities associated with interacting with EVM blockchains.

Here we will run through the eth_call example in two different ways, this time using ethers.js.

First, we need to install ethers into our project.

Installation

Simply install ethers using node package manager (npm):

npm install ethers

Setup

Create a new script named ‘ethers_call.ts’ and open it.

We begin by importing ethers into our script. It is easiest to import everything from ethers to begin with. This is done with the line:

import { ethers } from "ethers";

From here, we need to connect to the RPC node (ethers still needs to know where to get the information we need from). This introduces the idea of a provider. A Provider is a read-only connection to the blockchain. Ethers also has another object, the Signer, which uses an RPC node along with a private key to allow write access to the blockchain. We will cover this later.

We create a provider instance with the line:

const provider = new ethers.JsonRpcProvider("https://rpc.adiri.tel");

This establishes the connection to the RPC node and gives us an object on which we can call ethers functions.

Simple use

A simple function to start is just getting the block number. If we write the script:

import { ethers } from "ethers";

// Set up the provider to connect to an Ethereum node
const provider = new ethers.JsonRpcProvider("https://rpc.adiri.tel");

async function fetchData() {
  console.log(await provider.getBlockNumber());
}
fetchData();

and run it, we should print the current block number to the console. Note it prints a decimal integer value, rather than the hexadecimal we got when calling the node using HTTP requests. Ethers handles the conversion automatically.

Using call()

Our eth_call example becomes slightly simpler too:

import { ethers } from "ethers";

// Set up the provider to connect to an Ethereum node
const provider = new ethers.JsonRpcProvider("https://rpc.adiri.tel");

async function fetchData() {
  const result = await provider.call({
    to: "0xc9593289e95938FC4170B3Fc51dbcCa0A46b4486",
    data: "0x06fdde03",
  });

  // Log the result
  console.log("Response Data:", result);
}

fetchData();

This, when run should give the response:

0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b54656c636f696e20415544000000000000000000000000000000000000000000 as before.

Directly calling functions

There is, however, an alternative approach to getting the name of the token, or calling any other read function on a given contract. This involves creating an ethers Contract instance, which requires a contract address, a provider and the contract Application Binary Interface (ABI). We have the first two, and the third is available here. The ABI provides ethers with important context relating to a contract’s functionality and how to interact with it.

Handling the ABI

In your project’s root folder, create a file named ‘ABI.json’ and copy and paste the ABI from the documentation link. The ABI is produced by the solidity compiler and is derived from the solidity smart contract code.

We can then import this information to our script using the built in fs (File System) and path modules:

const abiPath = path.join(__dirname, 'ABI.json');
const abiJson = fs.readFileSync(abiPath, 'utf8');
const abi = JSON.parse(abiJson);

In the above 3 lines we:

  1. create the string representing the path to the ABI.json file
  2. Read the data stored within the ABI.json file
  3. convert the read data to JSON format for use with ethers.

Creating the Contract Object

We now have all the components required to create an ethers Contract object which we can do in the following way:

// Create the contract instance with the provider
  const contract = new ethers.Contract(
    "0xc9593289e95938FC4170B3Fc51dbcCa0A46b4486",
    abi,
    provider
  );

Here we have provided the contract address, ABI and provider to create a new instance of a Contract object which we save to the variable named contract. More information can be found in the relevant section of the documentation.

All that is left is for us to use the contract. We can do this easily by calling the function names as they appear on the smart contract. In our case, we know the token inherits from the ERC 20 token standard. It will therefore have a getter function named name() (this is the one we have been calling up to now). To get the name of the token we write:

const name = await contract.name();

Building the Script

Putting all of these elements together might give us something like:

import { ethers } from "ethers";
import fs from "fs";
import path from "path";

const provider = new ethers.JsonRpcProvider("https://rpc.adiri.tel");

async function fetchData() {
  const abiPath = path.join(__dirname, "ABI.json"); // Adjust the path as necessary
  const abiJson = fs.readFileSync(abiPath, "utf8");
  const abi = JSON.parse(abiJson);

  const contract = new ethers.Contract(
    "0xc9593289e95938FC4170B3Fc51dbcCa0A46b4486",
    abi,
    provider
  );

  const name = await contract.name();
  console.log("Contract Name:", name);
}

fetchData();

Feel free to overwrite the previous code from the ‘Using call()’ section.

Running the Script

We run this with:

npx tsc

which compiles our typescript scripts to JavaScript, followed by:

node ethers_call.js

Response

The script should return:

Contract Name: Telcoin AUD

Note the string has been automatically converted from hexadecimal.

Reading Decimals

It is also very easy to call other functions on the contract - we just change the function name. For example, we can now easily query how many decimals this contract uses, by replacing:

const name = await contract.name();

with:

const name = await contract.decimals();

recompiling and rerunning the script gives:

Contract Name: 6n

Showing the contract uses 6 decimals. This is correct.

Conclusion

Hopefully this series of tutorials has demonstrated how to query a blockchain, and the advantages of using libraries to gather data rather than raw HTTP requests. This should serve as useful context as you continue to learn about Telcoin Network!

Dapp Development

Telcoin Network runs a standard EVM, so dapps are built the same way as on Ethereum: write contracts in Solidity or Vyper, test them locally, and deploy over JSON-RPC.

Development Tools introduces Hardhat and Foundry, the two main development environments, and what each is suited for.

Development Tools

Now that we have an understanding of interacting with the blockchain, we can start to think about dapp development. There are two main tools used by developers in the web3 space; Hardhat and Foundry.

Hardhat

Hardhat builds on Ethers.js’ functionality and introduces more components which together provide a full development environment. Some of the main highlights include:

  • Scripting and testing in TypeScript/ JavaScript, making it useful for integration with front ends.
  • Provides the ability to create local forks of networks at previous or current blocks. This allows developers to test their contracts as if they were deployed on Telcoin Network - similar to a sandbox style environment.
  • Testing using chai and mocha for clean test code.

These features help Hardhat excel at contract deployment, and integration testing.

Hardhat has a thorough walkthrough and setup available here.

Foundry

Foundry is an extremely fast testing environment. Contrary to Hardhat, tests are written in solidity (rather than Type/JavaScript) and it also provides advanced testing features such as fuzzing and invariant testing.

Foundry has useful documentation on getting started here.

Faucet

1. Visit the Telcoin Network TEL Faucet

Visit https://telcoin.network/faucet on a desktop browser.

Mobile users may need to follow a different procedure based on their mobile wallet application. For example, MetaMask users must access the faucet webpage from the MetaMask application browser in order to interact with the faucet.

2. Press “Connect Wallet” Button

Select the wallet you would like to connect.

If your preferred wallet isn’t on the list, use WalletConnect to see additional supported wallets.

You may need to select “View All” to find wallets that are installed as browser extension or desktop applications. Mobile-based wallets are also supported through WalletConnect.

3. Request Tokens

Click on the “Request Tokens” button. The faucet will then send a specific amount of testnet tokens to the connected wallet’s address.

Users may need to import tokens to see updated balances in their wallets. If large amounts of testnet tokens are needed for your project, please email support@telcoin.org for further assistance!

4. Check Your Wallet

Open your wallet, and you should see the testnet tokens with an increased balance.

Telcoin Network: Adiri Testnet Details

You will be prompted to add the new network information. Enter the following details:

  • Network Name: Telcoin Network
  • RPC URLs:
    • https://adiri.tel
    • https://rpc.adiri.tel
    • https://node1.telcoin.network
    • https://node2.telcoin.network
    • https://node3.telcoin.network
  • Chain ID: 2017
  • Currency Symbol: TEL
  • Block Explorer URL: https://telscan.io (alternate: https://www.telscan.xyz)

Important Points to Remember

  • Testnet Tokens: The tokens received from the faucet are for testnet use only and have no real-world value.
  • Exploration and Learning: Use these tokens to explore the features and functionalities of the Telcoin Network, test smart contracts, or try out dApps in a risk-free environment.
  • Limited Quantity: There is a limit to the number of tokens you can request within a specific timeframe to ensure fair distribution among all users.
  • Developers sending multiple transactions in a row should connect to an RPC node directly using: https://node1.telcoin.network, https://node2.telcoin.network, or https://node3.telcoin.network

If you encounter any issues or need further assistance, feel free to email us at support@telcoin.org

Hardware requirements

GSMA MNOs have the exclusive right to operate validator nodes and earn network fees. This section is provided to support Telcoin Association’s authorized validator node operators. Telcoin (TEL) issuance is provided to Telcoin Network validators to incentivize the growth of a secure, compliant, efficient financial platform.

Purpose of node specifications

Minimum hardware, connectivity, and hosting requirements are specified to maintain consistent and efficient performance across Telcoin Network. These ensure that all nodes meet a foundational standard, contributing positively to the overall network’s health.

Pre-installation review

Operators are required to submit detailed specifications of their intended node setup to Telcoin Network development team for approval before installation. Please email support@telcoin.org with your proposed hardware specifications.

Summary

Each figure on this page comes from a code constant, from the resource model in How these numbers were derived, or from the benchmark described in that section. The benchmark ran on 2026-09-23 on two GCP fleets, and its results are in the benchmark report, also committed as bench/reports/2026-09-bench-10v.html in the tn-transaction-generator repository. On this page, e2 means the e2-custom-4-8192 fleet (4 vCPU, which is 2 physical cores, and 8 GB; run r20260923-0708) and c3 means the c3-highcpu-8 fleet (8 vCPU, which is 4 physical cores, and 16 GB; run r20260923-0515). Each cell below says whether its figure is measured, modelled or provisional, and its note gives the run and the sizing.json field or the formula behind it. The validator CPU and memory cells also name the measured single-worker figure they double (see Why validator CPU and memory are doubled). Benchmark sources and code references are listed at the end of the page.

RoleTierCPU (physical cores, PassMark single-thread)RAMStorage (capacity, sustained IOPS, MB/s, DWPD)Network
ValidatorMinimum8 physical cores (twice the measured 4) 1 232 GB ECC (twice the measured 16 GB) 3 22 TB TLC NVMe, 10,000 write IOPS, 300 MB/s, 2.4 DWPD (modelled from measured demand) 4200 Mbps symmetric (measured demand, modelled margin) 5
ValidatorRecommended16 physical cores, PassMark 3,500 or higher (twice the minimum; PassMark modelled) 6 264 GB ECC (twice the minimum) 7 24 TB TLC NVMe, 20,000 IOPS, 500 MB/s, 1.2 DWPD (modelled from measured demand) 81 Gbps (measured demand, modelled margin) 5
ValidatorHeadroomAs recommended (modelled) 964 GB ECC (as recommended) 9 212 TB, otherwise as recommended (modelled) 101 Gbps (modelled) 9
Observer (follower)Minimum2 physical cores (measured) 118 GB (measured) 11As validator minimum (measured demand) 1250 Mbps (measured demand, provisional figure kept) 13
Observer (follower)Recommended4 physical cores (measured) 1416 GB (measured) 14As validator recommended (measured demand) 1250 Mbps (measured demand, provisional figure kept) 13
Observer (follower)Headroom4 physical cores (modelled) 916 GB (modelled) 912 TB, as validator headroom (modelled) 1050 Mbps (modelled) 13
Observer (public RPC)Minimum4 physical cores (modelled) 1516 GB (modelled) 15As validator minimum (measured demand) 1250 Mbps plus RPC traffic (RPC not measured) 15
Observer (public RPC)Recommended8 physical cores (provisional, RPC not measured) 1532 GB (modelled) 15As validator recommended (measured demand) 12Sized for RPC traffic (not measured) 15
Observer (public RPC)Headroom8 or more physical cores, scaled to RPC load (not measured) 1564 GB (modelled) 1512 TB, as validator headroom (modelled) 10Sized for RPC traffic (not measured) 15

Every validator tier also needs a p95 round-trip time well below 1 s to at least 7 of the 10 committee members (see Networking).

The tiers mean:

  • Minimum: the smallest configuration that kept up with the benchmark’s mixed phase without memory or IO stalls, with enough memory for the restart replay peak described under Memory.
  • Recommended: enough margin that p95 use of CPU, memory, disk IOPS and bandwidth stays below half of capacity at the benchmark load, plus one year of storage growth at the per-epoch batch-cache ceiling (about 260 TPS with the 6-hour epochs mainnet and testnet use). Storage keeps that figure as its planning load, although the batch cache no longer limits throughput to it.
  • Headroom: sized for the same planning load, including restart replay at that load and three years of storage growth. That load is under a thirteenth of what the benchmark ran, so the recommended CPU, memory and network already cover it. Headroom beyond Recommended is about storage growth and RPC caching, not throughput.

Those rules describe the single-worker validators the benchmark ran, and Single-worker figures from the benchmark lists what they gave: 4 physical cores and 16 GB minimum, 32 GB recommended. The validator CPU and memory tiers above are twice that, a margin for the worker count that governance is expected to raise above one over the next 12 months. The minimum tier is twice the measured single-worker minimum, and the recommended tier is twice the minimum. Storage and network keep their derived figures, and the observer tiers are not doubled. Why validator CPU and memory are doubled says what each worker adds, and Expected service life says how long each tier lasts.

Neither fleet’s 100 GB network-attached volume ran free of IO stalls (see Storage), so every tier’s disk figures come from measured demand plus margin, not from a tested volume.

Cloud vCPUs are usually hyperthreads, so 8 vCPUs are 4 physical cores, and the minimum tier’s 8 physical cores are a 16-vCPU instance. Compare physical cores when reading this table.

Single-worker figures from the benchmark

The tier rules applied to the benchmark’s validators, which ran one worker each, give these figures. The summary doubles the CPU and memory and carries storage and network over unchanged.

TierCPU (physical cores)RAMIn the summary
Minimum4 (measured) 116 GB (measured) 38 cores, 32 GB ECC
Recommended4, PassMark 3,500 or higher (cores measured, PassMark modelled) 632 GB (measured) 716 cores, 64 GB ECC
HeadroomAs recommended (modelled) 932 GB (modelled) 9As recommended, 64 GB ECC

Why validator CPU and memory are doubled

The benchmark ran one worker per validator, the count adiri runs today 16. The worker count is an on-chain parameter. At each epoch entry the node reads WorkerConfigs at the previous epoch’s closing block and builds the committee with that count 17, and the multi-worker fork lets governance raise it above one 16. The Telcoin Association expects to raise it over the next 12 months, to as many as four workers per validator. The benchmark did not measure a multi-worker node, so the validator CPU and memory tiers carry a margin instead of a measurement: the minimum tier is twice the single-worker minimum, and the recommended tier is twice the minimum 2.

Each worker adds its own copy of these components 18:

  • A transaction pool with its own maintenance tasks, under the same --txpool.* limits as the first, so the pool bound grows by 60 MB per worker 19.
  • A batch builder that seals batches from that pool, and a batch validator for the batches other validators send. Batch validation recovers signatures on the process-wide pool of core count minus two threads 20, next to execution’s own recovery work.
  • A JSON-RPC server on its own ports 21.
  • A libp2p swarm with its own listen address and UDP port, its own network key and its own batch gossip topic, kept for the life of the process 22. Its quorum waiter sends each batch it seals to the other N − 1 committee members 23, so peer connections grow to about W · (N − 1) for W workers, plus the primary’s. Open one worker port per configured worker in the firewall.

These do not change with the worker count:

  • Execution. One engine executes one consensus output at a time on one blocking thread 24, and a header carries at most 10 batch digests in total, shared across the node’s workers in round-robin order 25. More workers let a validator seal batches in parallel, and an output can carry more blocks than the benchmark’s outputs did, but never more than the header cap allows.
  • The batch cache. There is one cache environment per process, keyed by batch hash with no worker id 26, so all workers share its 1 GiB maximum.
  • Storage growth and network bandwidth, which follow committed batch bytes, so the tiers size them for the same planning load. Their tier figures are not doubled.

The doubling is a provisioning margin, not a measurement. The per-worker terms are small next to a 32 GB host, and the c3 validators carried 5,384 TPS, about twenty times the 6-hour ceiling, at 25% CPU and 52% memory at p95 with one worker, so twice that host covers a second worker with room to spare. Buy the recommended tier for a host that will run more than two. Four workers need a multi-worker benchmark before this page can size them, and the page will be re-derived from it before the count gets there.

Expected service life

Storage is the resource that runs out. CPU and memory are sized for far more than the planning load.

The table gives the time to fill each tier’s capacity from the growth formula under Storage, with idle growth included: about 8 GB a day at the 6-hour ceiling and 3.2 GB a day at 100 TPS of the benchmark mix. β_reth is a lower bound on growth, so these are the longest each capacity can be expected to last.

TierCapacityAt 100 TPS, benchmark mixAt the 6-hour ceiling, about 260 TPS
Minimum2 TBAbout 1.7 yearsAbout 8 months
Recommended4 TBAbout 3.4 yearsAbout 16 months
Headroom12 TBAbout 10 yearsAbout 4 years

Today’s networks add about 0.07 TB a year, so at today’s load a drive reaches the end of its rated life before it fills. The DWPD ratings in the summary are twice the write rate modelled at the ceiling, so a drive that meets them uses at most half of its rated endurance over its warranty period, usually five years, even at the ceiling.

CPU and memory are not expected to be the limit in any tier. The minimum tier is twice a host that carried 5,384 TPS, about twenty times the 6-hour ceiling, with its engine queue at 3 or below and memory pressure at zero. Two things could exhaust that margin: more workers than the doubling was sized for, or a release that raises the batch-cache ceiling toward the thousands of TPS the benchmark ran. A multi-worker benchmark is due before the worker count reaches four, and a ceiling change ships as a release, so this page will be re-derived before either matters.

For a three-year hardware cycle, the recommended tier holds at loads up to about 100 TPS of the benchmark mix. At the ceiling its disk fills in about 16 months, so plan a storage upgrade then or start on the headroom tier.

Provisional estimates

These estimates come from the model and from the networks running today. They were made before the benchmark ran, and the single-worker figures above replace them; the summary then doubles the validator CPU and memory.

RoleCPURAMStorageNetwork
Validator, minimum4 physical cores16 GB2 TB TLC NVMe, at least 15,000 sustained IOPS200 Mbps
Validator, recommended8 physical cores, PassMark single-thread 4000 or higher32 GB4 TB TLC NVMe rated for at least 1 DWPD1 Gbps
Observer, follower4 physical cores8 to 16 GBSame as a validator50 Mbps
Observer, public RPC8 physical cores32 GBSame as a validatorSized for RPC traffic

The benchmark moved these provisional figures, before the multi-worker doubling:

  • Recommended validator CPU fell from 8 to 4 physical cores, and its PassMark floor from 4,000 to 3,500.
  • The minimum disk changed from 15,000 sustained IOPS to 10,000 sustained write IOPS plus a 300 MB/s throughput floor, and needs a 2.4 DWPD rating.
  • The recommended disk gained a 20,000 IOPS and 500 MB/s floor, and its endurance rating rose from 1 to 1.2 DWPD.
  • The follower observer minimum fell from 4 to 2 physical cores and settled at 8 GB, with 16 GB recommended.

Validator RAM (16 and 32 GB for one worker), storage capacity (2 and 4 TB) and network (200 Mbps and 1 Gbps) held.

Earlier versions of this page asked validators for 16 cores / 32 threads, 128 GB of RAM and 4 to 7.5 TB of NVMe, and observers for 8 cores / 16 threads and 16 to 32 GB. Those figures were not derived from measurement. Against them, validator RAM moved from 128 GB to 32 GB minimum and 64 GB recommended, CPU from 16 cores and 32 threads to 8 physical cores minimum and 16 recommended, and storage from 4 to 7.5 TB to 2 to 4 TB, sized for the batch-cache ceiling of about 260 TPS. The benchmark alone would have set 16 and 32 GB and 4 physical cores; the rest is the multi-worker margin.

Validator

CPU

Architecture: x86-64.

Block execution is sequential. The engine executes one consensus output at a time on a single blocking thread 24, and transactions inside a block run in order. Signature recovery for an output’s transactions runs in parallel 27 on a thread pool sized to the core count minus two 20, and incoming batches are checked on the same pool 28. Single-thread speed therefore sets how fast a node executes. Extra cores help with signature recovery and with the networking, database and RPC work that runs next to execution, and once the worker count rises above one, with the batch builder and batch validator that each worker adds (see Why validator CPU and memory are doubled).

Execution speed also limits consensus speed:

  • A validator votes on a header only after it has executed the block that the header names as its author’s latest executed block 29.
  • A node holds back each commit until it has executed the block the leader had executed when it proposed 30.
  • A header needs votes from ⌊2N/3⌋ + 1 committee members to become a certificate, which is 7 of 10 31.

Rounds therefore advance at the pace of the 7th fastest executor in a committee of 10. A validator slower than that falls behind on its own and does not slow the others. If 4 or more validators are slow, the committee runs at their pace.

The benchmark shows the limit. The e2 validators (2 physical cores) used 44% of their vCPUs on average and 55% at p95, and execution could not keep up with consensus: every e2 validator’s engine queue reached 7 or 8 outputs, against a limit of 8, and pending pools on seven of the ten peaked at 9,480 to 9,850 transactions, near the pool’s default limit of 10,000. The c3 validators (4 physical cores) used 13% on average and 25% at p95, their engine queues never held more than 3 outputs, and the fleet sustained 5,384 TPS against 3,569 on e2. Total CPU percentage understates an execution bottleneck, because one saturated execution thread is only 25% of a 4-vCPU host. Watch the engine queue instead (see Capacity monitoring).

Buy single-thread speed first, then core count. A higher-clocked 16-core part does more for a validator than a 32-core part at a lower clock.

Memory

Steady state does not size memory. On devnet, validators run in 2.2 to 3.6 GB with the node process at about 1.6 GB resident. Memory is sized by two events that hold many consensus outputs at once:

  • Overload: execution falls behind consensus and outputs queue up.
  • Restart replay: a node that stopped mid-epoch re-executes every output it committed but had not executed. Replay has no memory gate. It streams each missed output toward the engine and relies on channel depth alone for backpressure 32.

Under the benchmark load, validators used:

FleetMemory used, p95Node RSS, p95Anonymous RSS, peakFile-backed RSS, peakMemory pressure (PSI some avg10), peak
e2, 8 GB81%7.15 GB5.6 to 6.4 GB1.7 to 1.8 GB0.9 to 4%
c3, 16 GB52%10.8 GB4.1 to 8.1 GB2.1 to 3.1 GB0

The first two columns are the worst validator in each fleet (gcp_by_role.validator.memory_percent_used.p95_max_node, telcoin_rss_bytes.p95_max_node). The last three are the range across validators from the on-node sampler (sampler_by_node.*.rss_anon_kb.run_max, rss_file_kb.run_max, psi_mem_some_avg10.run_max). No node was OOM-killed. On e2 the node filled 7.2 of 8 GB and the kernel was reclaiming pages it needed, so 8 GB fails the minimum rule even though no node ran out. On c3 memory pressure stayed at zero. Its 52% at p95 is just over the half-capacity line, so the single-worker recommended figure is 32 GB, which the summary doubles to 64 GB. Anonymous memory on one c3 validator reached 8.1 GB, more than an 8 GB host holds in total. c3 RSS is higher partly because the node maps its databases into memory and those file-backed pages stay resident while RAM is free, and partly because anonymous memory rose with the higher load.

The model is:

M ≈ (M_base + M_dag + M_pool + M_out + M_notif + M_rpc + M_thr) · (1 + α) + PC_hot
TermWhat it holdsWhat bounds it
M_baseBinary, allocator arenas, static tablesMeasured at idle: 0.3 to 0.5 GB on the fresh benchmark chain before load 33; about 1.6 GB on long-running devnet nodes
M_dagConsensus DAG: certificates for up to 50 rounds per member. Headers carry batch digests, not batch bodiesGarbage-collection depth of 50 rounds 34
M_poolTransaction pool--txpool.pending-max-size, --txpool.basefee-max-size, --txpool.queued-max-size, 20 MB each by default 35
M_outConsensus outputs waiting for or under executionSee below
M_notifCanonical-chain notifications held for slow subscribers (RPC subscriptions, pool maintenance, ExEx)256 notifications 36
M_rpcRPC response caches--rpc-cache.* flags 37; small when RPC is closed to the public
M_thrThread stacks and per-thread buffersCore count minus two for the parallel pool 20, plus async runtime threads
αAllocator fragmentationNot measured: the benchmark did not collect allocator statistics. Measured RSS includes it
PC_hotPage cache that keeps the hot part of the execution database off diskAbout 3 GB at the benchmark’s chain size, growing with state (see below) 38

M_out dominates under overload and replay:

M_out ≈ 2 · N · b_h · S_b · (k_q · I + k_exec)
  • 2 · N · b_h · S_b is the batch data in one consensus output. Leaders are elected on even rounds and commit once f+1 certificates in the next round reference them 39, so an output usually covers two rounds of headers from N validators. Each header carries b_h batches (at most 10, and a header is proposed once 5 are ready 40 41) of S_b bytes (at most 1 MB or 30 million gas per batch 42).
  • k_q is memory per byte of a queued output: not measured separately (see below).
  • k_exec is the extra memory per byte for the output under execution (recovered transactions, EVM state, trie updates): not measured separately.
  • I is the number of outputs held at once. It is 1 or 2 in normal operation. Under overload or replay it reaches about 73: 64 in the channel from the epoch manager to the engine 43, 8 in the engine queue 44, and 1 executing 24.

At the protocol maximum (N = 10, b_h = 10, S_b = 1 MB) one output could hold 200 MB, and 73 of them 14.6 GB. A second, lower limit applies. Replay never crosses an epoch boundary 32, so it holds at most one epoch’s batch data. At the tiers’ planning load, the per-epoch batch-cache ceiling with 6-hour epochs, that is about 1 GiB of batch data, times k_q, on top of the execution working set.

The benchmark could not separate k_q and k_exec from the rest of resident memory. Its consensus outputs averaged 1.0 to 1.25 MB of batch data and never exceeded 5.5 MB (prometheus.tn_primary_consensus_output_bytes), so even a full 73-output backlog held under 0.4 GB of batch data, too little to isolate. It measured the replay peak directly instead. After a 5-minute outage at full load, the restarted e2 validator peaked at 7.0 GB resident with its engine queue at the limit of 8 45. That is the same level as the e2 validators that never stopped (7.2 GB), so at this load the replay peak is the execution working set, not queued outputs. The restarted c3 validator did not resume execution before the run ended (see Benchmark), so there is no c3 replay figure.

PC_hot shows in disk reads. The c3 validators held 2.1 to 3.1 GB of file-backed pages and read almost nothing from disk (5 IOPS at p95). The e2 validators held 1.7 to 1.8 GB, and five of the ten read 1,800 to 3,000 IOPS at p95, most likely page-cache misses. So PC_hot is about 3 GB at the benchmark’s chain size, and it grows with state.

Disable swap. A swapping validator keeps running but executes slowly, and because votes wait on execution it drags on the committee instead of failing visibly. Use ECC memory. Cloud instance hosts generally have it already; on bare metal, specify ECC DIMMs.

Storage

Every node is an archive node. The node refuses to start with any pruning configuration 46, so disk use only grows. Plan capacity from the growth formula, not from the size of the chain today.

Path under the datadirContentsGrowth
db, static_filesExecution database and static filesβ_reth per transaction
consensus-db/epochsConsensus packs: committed outputs with their batches, one directory per epochβ_pack per transaction
consensus-db/cacheBatch cacheFixed 1 GiB maximum, reused each epoch
consensus-db/epochCurrent epoch’s consensus tablesFixed 512 MiB maximum, cleared each epoch

Paths are set in crates/config 47, and the two fixed maximum sizes in crates/storage 48.

Daily growth is:

G_day = 86400 · λ · (β_reth + β_pack) + G_idle
  • λ is sustained transactions per second.
  • β_reth is bytes added to the execution database and static files per transaction: 251 on e2 and 257 on c3, a lower bound 49.
  • β_pack is bytes added to consensus packs per transaction: 92 on e2 and 94 on c3, also a lower bound 50.
  • G_idle is growth per day with no user transactions: about 0.2 GB (modelled from adiri at idle).

Together the β values are about 345 bytes per transaction. Both are measured on the benchmark mix, whose average transaction is about 190 bytes in a batch (188 on c3, about 190 on e2) 51. Both fleets agree to within 3%. β_reth is a lower bound because the execution database file db/mdbx.dat kept the same 4.3 GB size from node start to teardown in every run: MDBX allocates that file in large steps, so growth inside it did not show, and only static-file growth was counted. The storage growth planning table applies the formula to other loads.

Execution reads state on its single thread, so random-read latency adds directly to execution time. The consensus database also syncs to disk on every commit in production builds 52.

Disk demand at p95 on the busiest validator was 53:

FleetRead IOPSWrite IOPSWrite throughputIO pressure (PSI some avg10), peak
e22,9905,120137 MB/s15 to 37%
c352,440190 MB/s47 to 60%

c3 wrote more bytes in fewer, larger IOs, and its extra memory kept reads in page cache. Disk was the tightest resource in both fleets. On e2, write IOPS ran at 57% to 62% of the volume’s 9,000 IOPS limit, and IO pressure was the highest pressure signal on every node. On c3, every validator’s p95 write rate sat between 181 and 190 MB/s, about 75% of the 240 MiB/s per-VM cap, and IO pressure was around 50% at p95, so the volume was the tightest resource on c3 as well. Size sustained IOPS from these p95 figures and write throughput from the c3 plateau, with the same 2x margin as the recommended tier: 2 × (2,990 + 5,120) is 16,220 IOPS, so the recommended tier asks for 20,000, and twice the c3 plateau is 380 MB/s, so it asks for 500. Quote sustained figures, not burst. Endurance is covered under Storage: TLC over QLC.

Per-epoch batch-cache ceiling

Every batch a validator creates or receives is written to the batch cache in consensus-db/cache 54. The cache is an MDBX environment with a fixed 1 GiB maximum 48. A batch leaves it when the consensus output that commits it, already saved to the epoch’s pack, is forwarded to execution, and a batch that is never committed leaves when the epoch closes 55. So the cache holds batches waiting to be committed and those that never will be, not the epoch’s committed batch data.

Mainnet and testnet run 6-hour epochs 56. New genesis files default to 8 hours unless --epoch-duration-in-secs is set 57. Dividing 1 GiB by the epoch length gives the ceiling. Before committed batches were evicted, it capped the committee’s sustained batch rate; now it caps only the rate at which batches can be left uncommitted over an epoch:

Epoch lengthBatch bytes per secondTPS at 110 BTPS at 188 to 192 B (benchmark mix, measured)TPS at 250 BTPS at 400 B
6 h (mainnet, testnet)49,710452259 to 264199124
8 h (CLI default)37,283339194 to 19814993
1 h298,2622,7111,553 to 1,5861,193746
20 min (benchmark)894,7858,1344,660 to 4,7593,5792,237

The 110, 250 and 400 B columns count raw transaction bytes. The benchmark measured about 190 B of batch data per transaction for its mix, which includes batch encoding 51. At that size the 6-hour ceiling is about 260 TPS (259 to 264), and an 8-hour epoch gives about 200. These are still upper bounds, because MDBX page overhead inside the cache is not included.

Filling the cache does not stop the node, but it moves new batches into memory. The node reaches the cache through a layered database: an insert writes the batch to an in-memory layer, returns, and leaves the MDBX write to a background thread, which drops the in-memory copy once the write succeeds 58. When the MDBX file is full the write fails, and the background thread logs an error that starts DB Insert node_batches_cache (target layered_db_runner), counts it in tn_storage_write_failures_total 59, sets a failure flag and keeps the batch in memory. Only a durability check (persist) reads that flag, and the worker never makes one, so it keeps sealing batches. Each batch that could not be written stays in RAM until it is evicted or the epoch closes, so resident memory climbs while the file is full. Evictions free space inside the file, but on a full file an eviction can share its MDBX transaction with a failed insert and be lost with it. A lost eviction is not retried, so its batches hold their space until the epoch closes. Alert on the counter or the log line (see Capacity monitoring). The worker’s FatalDBFailure error fires only when the insert call itself fails, for example because the database thread has gone. More hardware does not raise the ceiling because the size is a compiled-in constant. A release with a larger cache or a shorter epoch length does.

The tiers use the 6-hour ceiling as their planning load for chain growth, although this release does not enforce it. At that load a committee carries about 4.3 GB of batch data per day, 1.57 TB per year. Disk growth is that amount times m = (β_reth + β_pack) / 190 B, which is about 1.8 on the benchmark mix, plus G_idle. That gives about 7.8 GB a day, or with G_idle about 2.9 TB a year and 8.8 TB over three years (lower bounds, because β_reth is). m is close to 1.8 for transfer-heavy and contract-heavy loads too, so this figure holds whatever the mix. The recommended and headroom tiers use it. A committee that sustains more batch data fills disks sooner (see Storage growth planning).

At 20-minute epochs the ceiling is in the thousands of TPS, so a benchmark with short epochs measures hardware limits, not this ceiling. The benchmark stayed below its own 20-minute ceiling: its epochs carried about 0.52 to 0.55 GB of batch data on average. The cache file still reached its 1 GiB maximum on every c3 validator and on four of the ten e2 validators (the other six stopped at 960 MiB), because MDBX never shrinks the file 48. A re-run on 2026-09-29 (r20260929-1647) with faster disks raised throughput enough to fill the cache inside a 20-minute epoch: every validator logged DB Insert node_batches_cache: environment map size limit reached in the last minutes of two epochs. Both runs used a release that kept every batch until the epoch closed. Evicting committed batches frees pages inside the file for reuse, but the file keeps its size. File size is a high-water mark, not live occupancy (see Capacity monitoring).

Networking

Open the UDP ports and apply the firewall policy in Validator production operations. Use a static public IP address.

Per-node ingress is modelled as:

ingress ≈ λ · S_tx · (1 + N_fetch) + gossip overhead
  • λ · S_tx is transaction data per second. Each batch author sends the batch body directly to every other committee member 23, so a validator receives each transaction once.
  • N_fetch counts extra copies, fetched when a direct send was missed and the node pulls the batch before voting or executing.
  • Gossip overhead is batch digests, headers, votes and certificates. It grows with committee size and round rate, not with λ.

Egress is similar. A validator sends each of its own batches to N − 1 peers, so with load spread evenly its egress is about λ · S_tx · (N − 1) / N, plus gossip, plus what it serves to observers and syncing nodes.

At 1,000 TPS of 250-byte transactions, λ · S_tx is 250 KB/s, about 2 Mbps. Steady-state bandwidth is small. The 200 Mbps minimum is sized for catch-up and for serving consensus output and epoch packs to syncing nodes, which move whole epochs at a time.

In the benchmark, p95 ingress on the busiest validator was 24 Mbps on e2 and 21 Mbps on c3, both on the validator restarted by the kill test; the other validators received 6 to 17 Mbps 5. That matches the ingress model at the benchmark’s rate. p95 egress was 56 Mbps on e2 and 86 Mbps on c3, far above the model. Each validator also served JSON-RPC to its in-zone load generator, which fetched every block and all of its receipts, and the benchmark did not separate RPC traffic from peer traffic. Most of the gap is likely that RPC traffic, so a validator with closed RPC sends less.

Latency matters more than bandwidth. Each round is a header broadcast, votes back, and a certificate broadcast, so about 1.5 round trips to the quorum. Headers are proposed between 1 s and 2.5 s apart by default 60. Keep p95 round-trip time to at least 7 of the 10 committee members well below 1 s. The benchmark spans five GCP regions on three continents but did not measure round-trip time between them. Geography still showed: the two validators in australia-southeast1 had their batches included later than the others, and senders that submitted through them saw p95 latency of 325 to 350 s, against 20 to 27 s through the European validators.

Operating system

Supported: Linux LTS releases (Debian 11+, Ubuntu 20.04+, Red Hat Enterprise Linux 8). Use a kernel with pressure stall information (mainline 4.20 or later) so the checks in Capacity monitoring work.

  • Disable swap (see Memory).
  • Run time sync (chrony or systemd-timesyncd). A validator waits out a header timestamped up to max_header_time_drift_tolerance ahead of its own clock before voting: 250 ms by default, 1 s in a data directory first started by a binary from before sub-second timestamps, and rounded up to whole seconds in epochs before the sub-second timestamp fork. A header further ahead gets no vote yet and costs its proposer nothing, and the proposer retries; only a header more than the tolerance plus vote_timeout ahead (5.25 s at the defaults) is rejected, and its proposer penalized 61. Clock error within the tolerance delays votes by up to that error. Error beyond it holds back a validator’s votes on honest headers, or other validators’ votes on its own headers, until the difference shrinks, and error beyond the tolerance plus vote_timeout makes a validator reject honest headers or have its own rejected.

Email support@telcoin.org to confirm hardware specifications before purchasing any equipment.

Observer

An observer is any node that is not in the current committee. The node picks its role at startup from committee membership 62; to take a validator out of consensus, exit it on chain. An observer receives every consensus output, executes every block, and keeps the same archive data as a validator. It does not vote. Transactions sent to an observer’s RPC are sealed into a local batch and forwarded to the JSON-RPC endpoints that validators advertise 63.

Observers suit developers building on Telcoin Network, businesses that want to verify transactions and account state themselves, auditors and researchers who need direct access to chain data, service providers that submit transactions directly, and community members who want to verify the network.

There are two profiles:

  • A follower verifies the chain for its operator and serves little or no public RPC.
  • A public RPC observer serves JSON-RPC to outside users. Its CPU and memory are sized by RPC traffic, not by consensus.

The benchmark ran one follower in each fleet and sent it no RPC load. It was restarted 25 minutes into the mixed phase and caught up: its static files ended each run the same size as the validators’.

CPU

Architecture: x86-64 or ARM64.

Observers execute the same blocks on the same single thread as validators, so single-thread speed decides whether they keep up. An observer is not on the voting path. If it falls behind, it catches up without slowing the committee. RPC calls such as eth_call and eth_estimateGas run beside execution and scale with core count, which is why the public RPC profile has more cores.

The e2 observer (2 physical cores) used 22% of its vCPUs on average and 27% at p95, and its engine queue reached the limit of 8 at times. The c3 observer (4 physical cores) used 8% on average and 13% at p95, and its queue never held more than 3 outputs 11 14.

Memory

The consensus terms are smaller than on a validator. Observers do not subscribe to batch gossip 64 and have no batches of their own to cache. They queue and replay outputs the same way, so M_out applies unchanged.

In the benchmark the observer held 3.7 GB resident at p95 in both fleets, which was 38% of memory on e2 and 15% on c3. Memory pressure peaked at 1% on e2 and stayed at zero on c3.

On a public RPC observer the main memory risk is RPC caching. The --rpc-cache.* flags bound it 37:

FlagDefaultHolds
--rpc-cache.max-blocks5000Full blocks
--rpc-cache.max-receipts2000Receipts, per block
--rpc-cache.max-headers1000Headers
--rpc-cache.max-concurrent-db-requests512Concurrent database reads behind the cache
--rpc-cache.max-cached-tx-hashes30000Transaction hash lookups

The block, receipt and header limits count entries, not bytes. Each block comes from one batch 65, and a batch can be up to 1 MB, so 5000 cached blocks can hold up to 5 GB of transaction data before decoding overhead. At the benchmark’s average batch of about 64 KB, the same 5000 blocks hold about 0.3 GB. If resident memory grows with RPC load, lower --rpc-cache.max-blocks first. Disable swap on observers too.

Storage

An observer stores the same execution database and consensus packs as a validator, so use the same capacity, IOPS and endurance figures. In the benchmark the observer wrote 64 MB/s (e2) and 174 MB/s (c3) at p95, at 4,770 and 1,790 write IOPS 12. It does not write batches into the batch cache from gossip, and its cache file reached only 64 to 192 MiB against 1 GiB on validators.

Networking

A follower downloads consensus output and epoch packs and forwards transactions it receives. The steady-state bandwidth is λ · S_tx plus consensus headers. In the benchmark the follower received 2.7 Mbps (e2) and 4.5 Mbps (c3) at p95 and sent under 1 Mbps 13. A public RPC observer adds its RPC traffic, which depends on its users. Observers need outbound access to each validator’s advertised JSON-RPC endpoint to forward transactions.

Operating system

Linux as for validators. macOS Sequoia 15+ also runs an observer, for development. The pressure stall checks in Capacity monitoring are Linux-only.

Storage growth planning

The table applies G_day = 86400 · λ · 345 B, the benchmark mix’s measured β_reth + β_pack, and scales it by workload. Transfer-heavy loads use 0.6 times that, because a native transfer is about 110 bytes in a batch against the mix’s 190. Contract-heavy loads use 1.3 times, which is about 250 bytes in a batch. Both multipliers are modelled. G_idle adds about 0.07 TB a year at any load and is left out. Every disk figure is a lower bound, because β_reth is.

LoadWorkloadBatch bytes per transactionDisk per dayDisk after 1 yearDisk after 3 yearsUnder the 6 h batch-cache ceiling?
100 TPSTransfer-heavyAbout 110 B1.8 GB0.65 TB1.96 TBYes
100 TPSBenchmark mixAbout 190 B3.0 GB1.09 TB3.26 TBYes
100 TPSContract-heavyAbout 250 B3.9 GB1.41 TB4.24 TBYes
259 to 264 TPSBenchmark mixAbout 190 B7.8 GB2.85 TB8.55 TBAt the ceiling
500 TPSTransfer-heavyAbout 110 B8.9 GB3.26 TB9.79 TBNo
500 TPSBenchmark mixAbout 190 B14.9 GB5.44 TB16.3 TBNo
500 TPSContract-heavyAbout 250 B19.4 GB7.07 TB21.2 TBNo
1000 TPSTransfer-heavyAbout 110 B17.9 GB6.53 TB19.6 TBNo
1000 TPSBenchmark mixAbout 190 B29.8 GB10.9 TB32.6 TBNo
1000 TPSContract-heavyAbout 250 B38.8 GB14.1 TB42.4 TBNo

The last column compares the batch bytes per transaction with the 6-hour ceiling of 49,710 batch bytes per second. Rows marked “No” are above the planning load the tiers are sized for, so at those loads disks fill sooner than Expected service life says.

Worked example for 100 TPS of the benchmark mix over one year, with idle growth:

G_day      = 86400 · 100 · 345 B + 0.2 GB   = 3.18 GB
disk, 1 y  = 365 · G_day                    = 1.16 TB

Storage: TLC over QLC

When setting up nodes for Telcoin Network, storage selection affects node performance and drive life. We recommend TLC (Triple-Level Cell) NVMe drives over QLC (Quad-Level Cell).

Performance

TLC drives sustain higher write speeds and better random I/O, and they hold that performance under continuous load. QLC drives slow down sharply once their write cache fills, which leads to uneven execution times and late votes during busy periods.

Durability and lifespan

TLC drives typically support 1,000 to 3,000 P/E (program/erase) cycles, 3 to 5 times the endurance of QLC. QLC drives generally support 100 to 1,000 cycles, and continuous node writes wear them out early.

Endurance from the measured write rate

A node writes continuously even when idle. Convert the measured write rate to drive writes per day:

W_day = write rate (bytes/s) · 86400
DWPD  = W_day / capacity
TBW   = W_day · 365 · years

Devnet validators write 8 to 9 MB/s at the host level under light load. At 8.5 MB/s, W_day is 734 GB. That is 0.37 DWPD on a 2 TB drive and 0.18 DWPD on a 4 TB drive, and 1,340 TB written over five years. Compare TBW with the drive’s rated endurance, and buy a drive rated for at least twice the computed DWPD.

The benchmark’s write rate at load was 137 MB/s (e2) and 190 MB/s (c3) at p95 53. At 190 MB/s a drive takes 16.4 TB a day, 4.1 DWPD on a 4 TB drive, but the ratings below are sized for the planning load, not for the benchmark’s load. Per landed transaction, the node process wrote 23 to 46 KB on e2 validators and 61 to 71 KB on c3 validators, about 70 to 200 times what the data directory grew 66. At the 6-hour ceiling of about 260 TPS that is 6 to 19 MB/s on top of the idle 4 to 9 MB/s, about 0.9 to 2.4 TB a day. That is 0.2 to 0.6 DWPD on a 4 TB drive and 0.4 to 1.2 DWPD on a 2 TB drive (modelled). Twice those figures gives the ratings in the summary: 1.2 DWPD for 4 TB and 2.4 DWPD for 2 TB.

Blockchain-specific requirements

Node storage takes continuous writes from incoming batches, block execution, state updates and chain history. QLC drives cost less up front, but their performance limits and shorter life make TLC the cheaper and more reliable choice over the life of a node.

How these numbers were derived

Model

The CPU, memory, storage and network sections each state the model used for that resource. The inputs are code constants (listed under Sources and code references), the benchmark load (λ and the average transaction size), and a small number of measured coefficients (α, k_q, k_exec, β_reth, β_pack, G_idle, PC_hot, cache bytes per transaction). The benchmark measured β_reth, β_pack, PC_hot and cache bytes per transaction, and the replay peak in place of k_q and k_exec. α was not measured, and G_idle comes from adiri.

Benchmark

ItemSetting
Topology10 validators and 1 observer; 2 validators per zone, one zone in each of us-west2, australia-southeast1, northamerica-northeast1, europe-west4 and europe-west2; the observer in us-west2
Load10 load generators, one next to each validator in its zone, 600 senders each (6,000 in total)
Transaction types13: native transfer, batched self-send, contract deploy, WTEL wrap and unwrap, stablecoin transfer and approve, faucet drip, Uniswap v2 swap, add liquidity and remove liquidity, Uniswap v3 swap, Uniswap v4 swap
Phases90 s preamble, 2 min warmup, 40 min mixed phase, 3 min cooldown
Restart testsOne validator killed with SIGKILL 15 minutes into the mixed phase, held down for 5 minutes, then restarted (replay); the observer restarted at 25 minutes
Epoch length20 minutes, so the mixed phase crosses about two epoch boundaries
Fleetse2-custom-4-8192 (4 vCPU, 8 GB), run r20260923-0708; c3-highcpu-8 (8 vCPU, 16 GB), run r20260923-0515
Disk100 GB pd-ssd boot disk per node
Node imageus-docker.pkg.dev/telcoin-network/tn-public/adiri:v0.1.0-devnet
Load generatortn-transaction-generator, image blast-v2-3644adc
ReportBenchmark report; committed copy bench/reports/2026-09-bench-10v.html in tn-transaction-generator

Results:

Resulte2c3
Sustained mixed TPS (best 60 s)3,5695,384
Landed of accepted7.35M of 7.38M8.08M of 8.12M
Reverted00
Latency, submit to block timestamp, p50 / p95 / p997.6 s / 42 s / 361 s7.7 s / 31 s / 507 s

Over 99.9% of the transactions in the window were the benchmark’s own. The generators kept transaction pools full by design, so the latency figures measure queueing under saturation, not the protocol’s inclusion time. Blocks have a 30 million gas limit and pack by each transaction’s gas limit. The mix averaged about 77,000 gas limit against 49,000 gas used per transaction, so about a third of each block’s capacity was reserved but unused.

Limits of the benchmark:

  • pd-ssd IOPS and throughput scale with volume size, so a 100 GB volume is throttled well below local NVMe. The volume allows 9,000 IOPS each way and 288 MiB/s (6,000 IOPS plus 30 per GB; 240 MiB/s plus 0.48 per GB). GCP also caps pd-ssd at 15,000 IOPS and 240 MiB/s per VM, both for E2 with 2 to 7 vCPUs and for C3 with 8 vCPUs, so the real throughput limit was 240 MiB/s on both fleets, and the c3 plateau of 190 MB/s (181 MiB/s) was about 75% of it. GCP’s disk throttling metric was not available in the project, so throttling was not recorded directly. Disk figures in the summary are measured demand plus margin, not the volume’s capacity.
  • 100 GB holds a 40-minute run but says nothing about capacity. Capacity comes from the growth formula.
  • 20-minute epochs keep the run far from the 6-hour batch-cache ceiling (see above).
  • Egress includes JSON-RPC traffic to the in-zone load generators (see Networking).
  • β_reth misses growth inside the execution database file (see Storage).
  • In the e2 run, bench-validator-03 in australia-southeast1 fell behind at 08:15Z and ran as a follower for 53 minutes. Its worker could not report one batch and re-sealed it 1,457 times, and the node’s process never restarted. Consensus stayed live with 9 batch producers. The failure is tracked separately.
  • In the c3 run, bench-validator-05, restarted after the 5-minute kill, had not resumed execution when the run ended 30 minutes later. Its static files did not grow and it reported no execution or batch metrics after the restart, so the c3 run had 9 executing validators from the kill at 06:20Z and gives no replay figure. This also needs its own investigation.
  • An earlier e2 run, r20260923-0215, is superseded. A load-generator bug re-sent pending transactions, so about a third of its batch content was duplicates (1.33 executed transactions per landed transaction). No sizing figure on this page comes from it.

Networks running today

Both networks carry little load, so these figures are a floor:

NetworkMachineCPURAM usedDisk writes
Devnet validators, 24 he2-custom-4-8192 (4 vCPU, 8 GB)8 to 12%2.2 to 3.6 GB, single-sample spikes to 5.5 to 6.4 GB; node resident memory about 1.6 GB8 to 9 MB/s
Adiri validators, idle, 6 h epochsc3-highcpu-8 (8 vCPU, 16 GB)1.3%6.4 GB flat, one node at 9.5 GB3.7 MB/s

The node’s idle footprint fits in 8 GB, and it writes several MB/s to disk even when idle, which is why endurance matters.

Confidence

FigureSourceConfidence
Code constants (1 GiB batch cache, 64 + 8 output queue, 10 batches per header, 1 MB batch, 6 h mainnet epoch, 8 h CLI default)CodeExact for this release
Batch-cache TPS ceilingConstants and the measured 190 B per transactionModelled from a measured input; MDBX page overhead not included
CPU, RAM, disk and network use at benchmark loadBenchmark, both fleetsMeasured
Single-worker minimum and recommended CPU and RAMBenchmark and the tier rulesMeasured
Validator CPU and RAM tiers in the summaryTwice the single-worker figuresModelled margin for the multi-worker rollout, not measured
PassMark floorJudgement; CPU scores were not recordedModelled
Disk and network figures in every tierMeasured demand plus the tier marginsModelled from measured demand
Replay memory peakBenchmark, one e2 validator, one restartMeasured, single sample
β_reth, β_packBenchmark mixed phaseMeasured lower bounds for the benchmark mix
PC_hot, M_baseOn-node samplerMeasured proxies
α, k_q, k_execNoneNot measured
G_idleAdiri at idleModelled
Storage growth table and workload multipliersFormula with measured βModelled
Endurance at the ceilingMeasured writes per transaction, scaled to the ceilingModelled
Headroom tierModel at the batch-cache ceiling loadModelled
Follower network figureMeasured demand; 50 Mbps kept from the provisional tableMeasured demand, provisional figure
Public RPC observer CPU, memory and networkNo RPC load in the benchmarkModelled or provisional
Round-trip time between regionsNot collectedNot measured
Provisional estimatesModel and running networksSuperseded by the summary

Sources and code references

Notes whose names start with b- cite the benchmark: the run, and the field in that run’s sizing.json unless another file is named. doubling states the multi-worker rule, and the rest cite code.


  1. Measured, r20260923-0708 (e2) and r20260923-0515 (c3). gcp_by_role.validator.cpu_utilization: e2 validators ran 44% mean (mean_avg_nodes), 55% p95 (p95_max_node) and 66% max of 4 vCPU; c3 validators ran 13%, 25% and 36% of 8 vCPU. prometheus.tn_engine_queued_outputs: every e2 validator reached 7 or 8 queued outputs, against a limit of 8, while no c3 validator passed 3. prometheus.tn_batch_builder_pending_pool_transactions: seven e2 pools peaked at 9,480 to 9,850 transactions, near the default limit of 10,000. The 2-core e2 hosts were execution-bound, so they fail the minimum rule; the 4-core c3 hosts pass it on CPU. The single-worker minimum is therefore 4 physical cores, and the summary doubles it to 8. ↩ ↩2

  2. Modelled margin, not measured. The benchmark ran one worker per validator. The summary’s validator minimum is twice the single-worker minimum and its recommended tier is twice that minimum, to cover the worker count governance is expected to raise over the next 12 months (see Why validator CPU and memory are doubled). Storage, network and observer figures are not doubled. ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  3. Measured, r20260923-0708 and r20260923-0515. e2 (8 GB): gcp_by_role.validator.memory_percent_used.p95_max_node 81%, telcoin_rss_bytes.p95_max_node 7.15 GB, sampler_by_node.*.psi_mem_some_avg10.run_max 0.9 to 4%, replay peak 7.0 GB. c3 (16 GB): 52%, 10.8 GB, PSI memory 0. 8 GB left no room for the replay peak, so the single-worker minimum is 16 GB, and the summary doubles it to 32 GB. ↩ ↩2

  4. Modelled from measured demand. The minimum takes twice the e2 p95 write IOPS (5,120, gcp_by_role.validator.disk_write_ops_per_sec.p95_max_node) and sets throughput above the c3 plateau of 190 MB/s (disk_write_bytes_per_sec.p95_max_node), because both fleets’ 100 GB volumes showed IO stalls. 2 TB holds about 8 months of growth at the 6-hour ceiling (about 8 GB a day with idle growth). 2.4 DWPD is twice the modelled 0.4 to 1.2 DWPD at the ceiling (see Endurance). ↩

  5. Measured demand, r20260923-0708 and r20260923-0515, gcp_by_role.validator.network_sent_bytes_per_sec.p95_max_node and network_received_bytes_per_sec.p95_max_node: out 56 Mbps (e2) and 86 Mbps (c3), in 24 and 21 Mbps. 200 Mbps symmetric clears the measured p95 with room for catch-up. 1 Gbps keeps p95 under a tenth of capacity and leaves room for catch-up and serving epoch packs, which the benchmark did not measure. Egress includes RPC traffic to the load generator. ↩ ↩2 ↩3

  6. Cores measured, r20260923-0515: 25% p95 and 36% max of 8 vCPU (gcp_by_role.validator.cpu_utilization) on 4 physical cores, under the half-capacity line, with the engine queue at 3 or below, so the single-worker recommended figure is 4 physical cores; the summary’s 16 is twice the doubled minimum. The PassMark figure is modelled: the benchmark did not record CPU models or scores, and 3,500 is a floor chosen for single-thread speed, not a measurement. ↩ ↩2

  7. Measured, r20260923-0515: 16 GB ran at 52% p95 (gcp_by_role.validator.memory_percent_used.p95_max_node), just over half of capacity, so the single-worker recommended figure is 32 GB; the summary’s 64 GB is twice the doubled minimum. ↩ ↩2

  8. Modelled from measured demand. 20,000 sustained IOPS is above twice the e2 p95 read plus write IOPS (2 × (2,990 + 5,120) = 16,220; disk_read_ops_per_sec.p95_max_node, disk_write_ops_per_sec.p95_max_node). 500 MB/s is above twice the c3 write plateau (380 MB/s). 4 TB covers one year at the 6-hour ceiling (2.9 TB) with room to spare. 1.2 DWPD is twice the modelled 0.2 to 0.6 DWPD at the ceiling. ↩

  9. Modelled. Headroom is sized for the planning load, the 6-hour batch-cache ceiling of about 260 TPS, under a thirteenth of the e2 fleet’s 3,569 TPS. The recommended CPU, memory and network cover that load, and replay at it holds at most about 1 GiB of batch data. Headroom therefore adds storage (and, for public RPC observers, memory), not throughput. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  10. Modelled: G_day = 86400 · λ · 345 B + G_idle at the ceiling is about 8 GB a day, 8.8 TB over three years (a lower bound). 12 TB holds that with about a third to spare. ↩ ↩2 ↩3

  11. Measured, r20260923-0708, gcp_by_role.observer: the e2 observer (2 physical cores, 8 GB) used 22% mean and 27% p95 CPU, 38% of memory at p95 (3.7 GB resident), and peaked at 1% memory pressure. It fell behind at times (engine queue at 8) but caught up after its restart. ↩ ↩2 ↩3

  12. Measured demand, gcp_by_role.observer.disk_write_bytes_per_sec.p95_max_node and disk_write_ops_per_sec.p95_max_node: 64 MB/s at 4,770 IOPS (e2) and 174 MB/s at 1,790 IOPS (c3), the same order as a validator. An observer keeps the same archive data, so it uses the validator disk figures. ↩ ↩2 ↩3 ↩4 ↩5

  13. Measured demand, gcp_by_role.observer.network_received_bytes_per_sec.p95_max_node and network_sent_bytes_per_sec.p95_max_node: 2.7 Mbps (e2) and 4.5 Mbps (c3) in, under 1 Mbps out. 50 Mbps is the provisional figure, kept because it is more than ten times the measured demand. Initial sync from genesis was not measured. ↩ ↩2 ↩3 ↩4

  14. Measured, r20260923-0515, gcp_by_role.observer: the c3 observer (4 physical cores, 16 GB) used 13% CPU and 15% memory at p95 with its engine queue at 3 or below. The e2 shape already meets the half-capacity rule on CPU and memory, but its execution fell behind at times, so the recommended tier uses the c3 shape. ↩ ↩2 ↩3

  15. Modelled; the benchmark sent no RPC load to the observer. Minimum: the follower’s recommended CPU, and the follower’s 8 GB plus up to 5 GB for the default RPC caches (5000 blocks of up to 1 MB), rounded to 16 GB. Recommended 32 GB and headroom 64 GB leave room for concurrent RPC work and for raising the cache limits. The 8-core CPU figure is the provisional estimate, kept until RPC load is measured. Network is the follower’s 50 Mbps plus RPC traffic. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  16. crates/types/src/forks.rs:474 (MULTI_WORKERS_FORK_EPOCH, an adiri placeholder at the time of writing), crates/types/src/forks.rs:528-532 (mainnet builds run the multi-worker layout from genesis), crates/types/src/forks.rs:138 (the live adiri WorkerConfigs read numWorkers() == 1 on 2026-08-18), crates/node/src/manager/node.rs:112-125 (check_worker_count_fork rejects more than one worker before the fork). ↩ ↩2

  17. crates/node/src/manager/node.rs:492-513 (read_num_workers_at_epoch_entry reads WorkerConfigs at the block before the epoch’s first block), crates/tn-reth/src/env/epoch.rs:597-615 (the getAllWorkerConfigs system call), crates/tn-reth/src/system_calls.rs:296-299 (the count is a uint16), crates/types/src/committee.rs:1078-1079 (number_of_workers). ↩

  18. crates/node/src/manager/node/start_epoch.rs:608-628 (one pool, RPC server, validator and network per on-chain worker, in id order), crates/node/src/engine/inner.rs:155-182 (pool and RPC server), crates/node/src/engine/inner.rs:250-260 (batch validator), crates/node/src/manager/node/run_epoch.rs:397-430 (batch builder per worker). ↩

  19. crates/tn-reth/src/txn_pool.rs:312 (each pool takes the full txpool config); the 20 MB per-subpool defaults are in 35. ↩

  20. crates/telcoin-network-cli/src/node.rs:208-216 (global pool size is available cores minus 2, at least 1). ↩ ↩2 ↩3

  21. crates/tn-reth/src/env/rpc.rs:62 (WORKER_PORT_STRIDE = 200), crates/tn-reth/src/env/rpc.rs:137-144 (worker 0 keeps the configured ports; worker k shifts its HTTP port down by 200·k and its WebSocket port up by 400·k). ↩

  22. crates/node/src/manager/node.rs:170-233 (prepare_worker_networks requires a distinct listen address and key per worker), crates/node/src/manager/node.rs:1496-1541 (one long-running swarm per configured worker), crates/node/src/manager/node/start_epoch.rs:1053-1054 (batch topic per worker). ↩

  23. crates/consensus/worker/src/quorum_waiter.rs:136-137. ↩ ↩2

  24. crates/engine/src/lib.rs:66-68 (one pending execution task), crates/engine/src/lib.rs:159 (execution on a blocking thread). ↩ ↩2 ↩3

  25. crates/types/src/primary/header.rs:34 (the payload maps batch digest to worker id), crates/types/src/primary/header.rs:166-171 (a header with more than MAX_HEADER_NUM_OF_BATCHES digests is rejected), crates/consensus/primary/src/proposer.rs:715-749 (header slots are shared across workers round-robin, up to the cap in total). ↩

  26. crates/node/src/manager/node.rs:703-717 (one consensus database per process, shared across epochs), crates/storage/src/lib.rs:121-128 (cache tables keyed by batch hash only); the maximum size is in 48. ↩

  27. crates/tn-reth/src/env/execution.rs:167-181. ↩

  28. crates/batch-validator/src/validator.rs:170. ↩

  29. crates/consensus/primary/src/network/handler.rs:912-915. ↩

  30. crates/consensus/primary/src/consensus/state.rs:584-585. ↩

  31. crates/types/src/committee.rs:1191-1192. ↩

  32. crates/node/src/manager/node/start_epoch.rs:146-175 (replay loop; lines 147-155 refuse to cross an epoch boundary). ↩ ↩2

  33. Measured, r20260923-0708 and r20260923-0515, sampler CSVs (sampler/*.csv, rss_anon_kb) in the idle window after funding and before load: 0.4 to 0.5 GB per validator, 0.3 GB before funding, 0.2 GB on the observer. The chain was new, so long-running nodes sit higher. ↩

  34. crates/types/src/primary/mod.rs:52 (MAX_GC_DEPTH = 50), crates/config/src/node.rs:338-339 (default gc depth), crates/storage/src/consensus_pack.rs:4212-4214 (batches per output bound). ↩

  35. crates/tn-reth/src/cli.rs:88; default from reth v1.11.3 crates/transaction-pool/src/config.rs:18. ↩ ↩2

  36. reth v1.11.3 crates/chain-state/src/in_memory.rs:28, used through crates/tn-reth/src/env/helpers.rs:87-88. ↩

  37. crates/tn-reth/src/rpc_server_args.rs:234-236; defaults from reth v1.11.3 crates/rpc/rpc-server-types/src/constants.rs:115-127. ↩ ↩2

  38. Measured proxy, sampler_by_node.*.rss_file_kb.run_max against gcp_by_node.*.disk_read_ops_per_sec (p95): c3 validators held 2.1 to 3.1 GB of file-backed pages and read at most 5 IOPS; e2 validators held 1.7 to 1.8 GB and five of ten read 1,800 to 3,000 IOPS. ↩

  39. crates/consensus/primary/src/consensus/bullshark.rs:137-138 (even rounds), crates/consensus/primary/src/consensus/bullshark.rs:201-212 (f+1 support from round r+1). ↩

  40. crates/types/src/primary/mod.rs:74 (MAX_HEADER_NUM_OF_BATCHES = 10). ↩

  41. crates/config/src/node.rs:317-319 (proposal threshold 5). ↩

  42. crates/types/src/worker/sealed_batch.rs:207-208 (30,000,000 gas), crates/types/src/worker/sealed_batch.rs:220-221 (1,000,000 bytes), crates/config/src/node.rs:350-352 (batch sealed after 1 s). ↩

  43. crates/node/src/manager/node.rs:82 (TO_ENGINE_CAPACITY = 64), crates/node/src/manager/node.rs:861. ↩

  44. crates/engine/src/lib.rs:52 (MAX_QUEUED_OUTPUTS = 8), crates/engine/src/lib.rs:271. ↩

  45. Measured, r20260923-0708, bench-validator-05 killed at 08:30:12Z and restarted at 08:35:11Z: prometheus.reth_process_resident_memory_bytes peaked at 6.9 GB and the sampler’s rss_total_kb at 7.0 GB, with tn_engine_queued_outputs at 8. The validators that never stopped peaked at 7.2 GB (telcoin_rss_bytes.max_max_node). ↩

  46. crates/tn-reth/src/cli.rs:517 (ensure_archive_mode). ↩

  47. crates/config/src/traits.rs:156 (consensus-db/epochs), crates/config/src/traits.rs:189 (consensus-db), crates/config/src/traits.rs:193 (db). ↩

  48. crates/storage/src/lib.rs:177 (EPOCH_MAX = 512 MB), crates/storage/src/lib.rs:179 (CACHE_MAX = 1024 MB), crates/storage/src/lib.rs:186-191, crates/storage/src/mdbx/database.rs:228-234 (fixed maximum size), crates/storage/src/mdbx/database.rs:234 (shrink_threshold: Some(0): the file never shrinks). ↩ ↩2 ↩3 ↩4

  49. Measured, storage_growth.beta_reth_bytes_per_tx.median: 251 (r20260923-0708) and 257 (r20260923-0515) bytes per landed transaction over the mixed window. db/mdbx.dat stayed at 4,295,995,405 bytes on every node in every run (sampler db_bytes), so this counts static_files growth only. ↩

  50. Measured, storage_growth.beta_pack_bytes_per_tx.median: 92 (r20260923-0708) and 94 (r20260923-0515). Packs for the open epoch are folded in at epoch close, so the figure is a lower bound when the window ends mid-epoch. ↩

  51. Measured from Prometheus over each fleet’s mixed window: batch bytes divided by batch transactions (tn_worker_batch_size_bytes, tn_worker_batch_transactions) is 188 B on c3 (r20260923-0515) and about 190 B on e2 (r20260923-0708), about 340 transactions and 63 to 65 KiB per batch. The e2 figure excludes bench-validator-03’s re-sealed batches and is approximated from the histogram means. The superseded e2 run r20260923-0215 measured 192 B. ↩ ↩2

  52. crates/storage/src/mdbx/database.rs:189-190. ↩

  53. Measured, gcp_by_role.validator.disk_*_per_sec.p95_max_node: e2 2,990 read and 5,120 write IOPS (5,570 max) at 137 MB/s (145 max); c3 5 read and 2,440 write IOPS at 190 MB/s. IO pressure from sampler_by_node.*.psi_io_some_avg10.run_max. Volume limits from each run’s limits section and the report. ↩ ↩2

  54. crates/storage/src/lib.rs:123-128 (TableHint::Cache tables); writers at crates/consensus/worker/src/network/handler.rs:269, crates/consensus/worker/src/network/handler.rs:301, crates/consensus/worker/src/batch_fetcher.rs:253, crates/consensus/worker/src/worker.rs:421. ↩

  55. crates/node/src/manager/node/run_epoch.rs:602 and crates/node/src/manager/node/run_epoch.rs:1077-1091 (process_output evicts the output’s batches from NodeBatchesCache and OurNodeBatchesCache in one transaction before forwarding it; a failure is logged and not retried), crates/consensus/executor/src/subscriber.rs:174-203, crates/consensus/executor/src/subscriber.rs:317-348, crates/consensus/executor/src/subscriber.rs:698-705 and crates/state-sync/src/lib.rs:128-132 (each output is saved and persisted to the pack before it is sent on), crates/node/src/manager/node/close_epoch.rs:892 (the epoch-close clear), crates/consensus/worker/src/network/handler.rs:167-173 (late gossip for an evicted batch fetches and caches it again until the epoch closes). ↩

  56. chain-configs/testnet/genesis.yaml:103 (ConsensusRegistry epoch-duration slot, 0x5460 = 21,600 s). Mainnet launches with the same 6-hour epochs; the mainnet genesis in this repository (chain-configs/mainnet/genesis.yaml:99) still encodes 86,400 s at the time of writing and is being regenerated. Epoch length is a genesis parameter (--epoch-duration-in-secs), not a node setting, so operators cannot change it. ↩

  57. crates/telcoin-network-cli/src/genesis/mod.rs:93. ↩

  58. crates/storage/src/composite_db.rs:44 (the cache database runs in cache mode, full_memory = false), crates/storage/src/layered_db.rs:555-560 (insert writes the in-memory layer and queues the MDBX write), crates/storage/src/layered_db.rs:292-333 (a failed MDBX write is logged at error level, latches commit_failed and keeps the in-memory copy), crates/storage/src/layered_db.rs:368-383 and crates/storage/src/layered_db.rs:635-653 (only the persist barrier reads the latch), crates/consensus/worker/src/worker.rs:355-359 and crates/consensus/worker/src/worker.rs:421-424 (FatalDBFailure only when the insert call returns an error). ↩

  59. crates/storage/src/layered_db.rs:780-841 (tn_storage.write_failures_total, which Prometheus exports as tn_storage_write_failures_total, and its env and op labels), crates/storage/src/layered_db.rs:467-471 (every env and op series is registered at zero when the database opens), crates/tn-metrics/src/recorder.rs:110 (the exporter renders . as _), crates/storage/src/composite_db.rs:42-44 (env is epoch, kad or cache), crates/storage/src/layered_db.rs:268-291, crates/storage/src/layered_db.rs:308-312, crates/storage/src/layered_db.rs:320-325 and crates/storage/src/layered_db.rs:335-363 (each failed commit, staged insert, bare insert, remove and clear is counted), crates/storage/src/layered_db.rs:259-264 (a transaction that cannot start is counted under begin and loses no write, because its writes go out one at a time), crates/storage/src/layered_db.rs:243-247 (a bare insert sent while a transaction is open rides that transaction), crates/storage/src/layered_db.rs:193-198 (a failed commit discards every write the transaction carried). ↩

  60. crates/config/src/node.rs:325-331 (2.5 s maximum, 1 s minimum). ↩

  61. crates/consensus/primary/src/network/handler.rs:768-830 (check_header_lead: a lead within the tolerance is passed on to be waited out at 778-788, a lead within the tolerance plus vote_timeout gets a retryable answer with no penalty at 789-817, and a larger lead is rejected at 818-828), crates/consensus/primary/src/network/handler.rs:878-880 (a deferral is answered on arrival), crates/consensus/primary/src/network/handler.rs:1020-1033 (the wait before voting), crates/consensus/primary/src/error/network.rs:227-229 (severe penalty for the rejection), crates/config/src/network.rs:448-449 (max_header_time_drift_tolerance), crates/config/src/network.rs:486 (250 ms default), crates/config/src/node.rs:333-335 (5 s vote_timeout default). The node writes its network config only when the file is missing (crates/config/src/traits.rs:76-83), so a data directory first started by an older binary keeps the 1 that binary wrote, read as 1 s. crates/telcoin-network-cli/README.md, section max_header_time_drift_tolerance (network-config), shows how to check and change it. ↩

  62. crates/node/src/manager/node.rs:961-964, crates/telcoin-network-cli/src/node.rs:347-358 (the --observer flag is removed and rejected). ↩

  63. crates/consensus/worker/src/worker.rs:337-339, crates/consensus/worker/src/worker.rs:281-323. ↩

  64. crates/consensus/worker/src/network/handler.rs:138-144. ↩

  65. crates/engine/src/payload_builder.rs:194-196. ↩

  66. Measured, sampler_by_node.*.growth_mixed.io_write_bytes over the mixed window, divided by storage_growth.chain_tx_in_window and by the data directory’s growth, excluding the restarted validator. The process write rate is storage_growth.telcoin_process_write_bytes_per_sec: 56 MB/s median on e2 and 163 MB/s on c3. The scaling to 260 TPS is modelled. ↩

Networks and RPC Endpoints

Note

The public RPC URLs listed below are rate limited and do not support WebSocket connections: a WebSocket upgrade to these URLs gets HTTP 405. If such functionality is required, please reach out to the team.

Adiri Testnet

ParameterValue
Network NameTelcoin Network Adiri Testnet
Chain ID0x7e1 (2017)
Currency SymbolTEL
Explorershttps://telscan.io/, https://www.telscan.xyz/
Public RPC URLhttps://rpc.adiri.tel

Unstable Testnet

Adiri Testnet is the only network currently deployed. While Adiri Testnet is intended to be as stable as possible, the protocol is still under heavy development and may be reset from time to time before mainnet is launched to incorporate breaking changes.

Developer Notes

The public RPC endpoint at https://rpc.adiri.tel uses a load balancer and is primarily useful for single transactions and read queries.

Developers should prefer to connect directly with a node when running scripts or multiple consecutive transactions. Finality happens after a few seconds (up to 10s on the current testnet), and some nodes may not have updated state until then.

Developers should use Chainlist (include testnets and search “adiri”) for an up-to-date list of supported RPC nodes for public use. For more developer questions, please email support@telcoin.org.

Mainnet

Mainnet has not launched. It will use chain ID 0x1e7 (487) and the public RPC URL https://rpc.telcoin.network. That hostname does not serve mainnet before launch, so check that eth_chainId returns 0x1e7 before sending anything through it.

Namespaces

Telcoin Network nodes serve the Ethereum JSON-RPC interface over HTTP, WebSocket and IPC, so wallets, dapps and tooling built for Ethereum can read the chain and submit transactions without changes.

Methods are grouped into namespaces. A namespace is the prefix before the underscore in a method name: eth_call belongs to eth, tn_getCurrentEpoch to tn. An operator chooses which namespaces each transport serves with --http.api and --ws.api; Enabling Namespaces explains the flags, the defaults and how to check what a node serves.

NamespaceOn by defaultCost to serveWhat it covers
ethyeslowEthereum-compatible chain, account, transaction, simulation and filter methods
netyeslowChain id, listening state and the worker network’s peer count
web3yeslowClient version and Keccak-256 hashing
rpcyestrivialThe namespaces the transport serves
tnyeslow to moderate (blocking reads are capped at 64 concurrent requests)Telcoin Network consensus, epoch, validator and staking data
debugno, only when namedhighRaw block and transaction data and geth-style EVM tracing
traceno, only when namedhighParity-style call traces, replays and trace filters

debug and trace re-execute historical transactions against archive state. One request can cost far more than an eth_* call, so they are never part of the default set or of all.

Node role--http.api / --ws.apiNotes
Core validatoreth,net,web3Serve RPC only on a private or gateway interface; see network topology. Keep eth: observers forward eth_sendRawTransaction to a committee validator’s advertised RPC endpoint.
Public RPC (observer)eth,net,web3,rpc,tnThe default set; no flag is needed.
Private archive or indexereth,net,web3,rpc,tn,debug,traceKeep behind authentication or a method allowlist, and size --rpc.max-tracing-requests for the host.

The JSON-RPC methods are documented per namespace in the sections below. Each page lists the parameters, the result, and a curl request with its response.

Enabling Namespaces

A Telcoin Network node serves JSON-RPC over three transports, and each transport serves its own set of namespaces. This page covers the flags that choose them, the limits that keep the expensive ones in check, and how to confirm what a node actually serves.

Transports

TransportEnabled byListens onNamespaces
HTTP--http--http.addr (default 127.0.0.1), --http.port (default 8545)--http.api
WebSocket--ws--ws.addr (default 127.0.0.1), --ws.port (default 8546)--ws.api
IPCon unless --ipcdisable--ipcpath (default /tmp/tn.ipc)always the default set; there is no --ipc.api

Subscriptions (eth_subscribe) need a WebSocket or IPC connection.

A node that runs more than one worker starts one RPC server per worker. Worker 0 uses the ports and IPC path above; every other worker binds its own derived ports and IPC path, which the node logs at startup (worker rpc endpoints resolved). Every worker’s server serves the same namespace selection.

Choosing namespaces

--http.api and --ws.api take a comma-separated list of namespace names. The names a node accepts are eth, net, web3, rpc, tn, debug and trace.

ValueNamespaces served
flag not giveneth, net, web3, rpc, tn
alleth, net, web3, rpc, tn
a list, for example eth,net,web3exactly the listed namespaces
nonenothing

The rules behind the table:

  • A list is served exactly as written. --http.api eth,net,web3 serves no tn_* methods; add tn to the list to keep them.
  • debug and trace are served only when named in the list; all never includes them. Naming either one logs a warning at startup.
  • Names are lowercase and case-sensitive: TN or Eth is not recognised. Only all and none are accepted in any case.
  • A name the node does not serve (admin, txpool, ots, a misspelling) is dropped from the selection with a startup warning that names it and lists the supported names. The node still starts and serves the rest of the list.
  • all and none must be the whole value. When the first entry is all or none the rest of the list is ignored, so all,debug serves the default set without debug. Write the full list instead: eth,net,web3,rpc,tn,debug.
  • HTTP and WebSocket are configured separately. A namespace named only in --ws.api is not served over HTTP.

HTTP and WebSocket on one port

When HTTP and WebSocket listen on the same address and port, one server answers both protocols, so the two selections must be identical, and so must --http.corsdomain and --ws.origins when both are set. The RPC server refuses to start otherwise. A missing flag counts as the default set, so on a shared port give --http.api and --ws.api the same list, or leave both out: --http.api eth,net,web3 with no --ws.api does not start.

Why debug and trace are off by default

Telcoin Network nodes keep the full chain history, so every block since genesis can be re-executed. debug_trace* and trace_* methods do exactly that: a single block trace, or a trace_filter over a range of blocks, replays every transaction in the EVM and can take seconds of CPU time and a large response.

If you serve them:

  • run them on a separate node from your public RPC and from any validator;
  • keep the endpoint behind authentication (--rpc.jwtsecret) or a proxy that allows only the methods you need;
  • set --rpc.max-tracing-requests low (2 to 4 on a shared host) and --rpc.max-trace-filter-blocks small;
  • cap response size with --rpc.max-response-size.

Some debug_* methods are registered but do nothing; debug lists them.

Limits and access flags

FlagDefaultEffect
--rpc.max-tracing-requestsCPU cores minus 2, at least 2Concurrent tracing requests, shared by debug_trace*, debug_executionWitness*, trace_* and eth_callBundle; further requests wait for a free slot
--rpc.max-trace-filter-blocks100Largest block range one trace_filter request may cover
--rpc.max-blocks-per-filter100000 (0 = no limit)Largest block range for eth_getLogs and log filters
--rpc.max-logs-per-response20000 (0 = no limit)Most logs one eth_getLogs or filter response may return
--rpc.gascap50000000Gas limit for eth_call, eth_estimateGas and the debug and trace call methods
--rpc.max-response-size160 (MB)Largest response the server sends
--rpc.max-connections500Concurrent RPC connections
--http.corsdomainnoneOrigins allowed to call the HTTP server from a browser
--ws.originsnoneOrigins allowed to open a WebSocket connection
--rpc.jwtsecretnoneHex-encoded secret; when set, HTTP and WebSocket requests need a JWT bearer token

Examples

These show only the RPC flags; add them to the telcoin-network node command you already run.

A public RPC node (an observer) serving the default set on all interfaces:

--http --http.addr 0.0.0.0 --ws --ws.addr 0.0.0.0

A validator serving the minimum on its private interface. Keep eth: observers forward eth_sendRawTransaction to a committee validator’s advertised RPC endpoint, and leave tn to non-validating nodes:

--http --http.addr 10.0.0.5 --http.api eth,net,web3

A local development node with every namespace, including tracing:

--http --http.api eth,net,web3,rpc,tn,debug,trace

Verify

rpc_modules lists the namespaces a transport serves, when rpc is part of its selection:

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"rpc_modules","params":[],"id":1}'
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": { "eth": "1.0", "net": "1.0", "rpc": "1.0", "tn": "1.0", "web3": "1.0" }
}

A namespace that is not served answers every one of its methods with the JSON-RPC “method not found” error:

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["0x..."],"id":1}'
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": { "code": -32601, "message": "Method not found" }
}

The node builds the rpc_modules answer once, for the first transport whose selection includes rpc, in the order HTTP, WS, IPC, and every transport then returns that list. When HTTP and WS both serve rpc with different selections, the answer on WS and IPC is HTTP’s list. Tracked in #1588.

Upgrading from earlier releases

Earlier releases served tn on every enabled transport whatever --http.api or --ws.api said, including none. tn is now selected like the other namespaces:

  • an explicit list must name tn to keep the tn_* methods, for example --http.api eth,net,web3,tn;
  • none serves nothing;
  • with no --http.api or --ws.api flag, or with all, the transport now also serves rpc_modules.
  • with HTTP and WebSocket on the same port, a list on only one of the two flags no longer starts, because the other transport now resolves to the default set; give both flags the same list.

eth

The eth namespace is the Ethereum-compatible core of the API: chain and block data, account state, transaction submission and receipts, call simulation, gas and fee quotes, and log filters. It is on by default and every node role should serve it; validators need it because observers forward eth_sendRawTransaction to a committee validator’s advertised RPC endpoint.

Telcoin Network changes a few of these methods so they agree with how the network prices transactions. eth_gasPrice, eth_maxPriorityFeePerGas, the next-block entry of eth_feeHistory, and simulations at latest or pending (eth_call, eth_estimateGas, eth_createAccessList) use the worker’s current epoch base fee rather than the latest header’s, and eth_sendRawTransaction honours the operator’s --rpc.txfeecap.

The methods with a page are listed in the sidebar, with the log filter methods under Filter Methods.

Served methods without a page yet

The node also serves these eth methods. They behave as in reth unless noted.

MethodNotes
eth_blobBaseFee
eth_callBundleShares the tracing request limit (--rpc.max-tracing-requests)
eth_callMany
eth_fillTransactionMissing fee fields are filled from the worker’s epoch base fee
eth_getAccount
eth_getAccountInfo
eth_getHeaderByHash
eth_getHeaderByNumber
eth_getRawTransactionByBlockHashAndIndex
eth_getRawTransactionByBlockNumberAndIndex
eth_getRawTransactionByHash
eth_getTransactionBySenderAndNonce
eth_sendRawTransactionSyncGuarded by --rpc.txfeecap like eth_sendRawTransaction
eth_simulateV1Block count capped by --rpc.max-simulate-blocks
eth_subscribe, eth_unsubscribeWebSocket and IPC only

These are registered but cannot do useful work on Telcoin Network:

MethodResult
eth_signTransaction, eth_signTypedDataError: the node holds no signing accounts (see eth_sign)
eth_coinbase, eth_mining, eth_getWork, eth_submitWorkError: unimplemented
eth_hashrateAlways 0x0
eth_submitHashrateAlways false
eth_getBlockAccessListByBlockHash, eth_getBlockAccessListByBlockNumberError: unimplemented

eth_accounts

Parameters

None

Returns

Array of DATA, 20 Bytes - Addresses owned by the client.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": []
}

source

eth_blockNumber

Parameters

None

Returns

QUANTITY - Hexadecimal value of the current block number the client is on.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}'

Result

{
"jsonrpc":"2.0",
"result":"0x7328cb", //7547083
"id":83
}

source

eth_call

Parameters

Object - The transaction call object:

  • from: DATA, 20 bytes - (optional) The address the transaction is sent from.
  • to: DATA, 20 bytes - The address the transaction is directed to.
  • gas: QUANTITY - (optional) Hexadecimal value of the gas provided for the transaction execution.
  • eth_call consumes zero gas, but this parameter may be needed by some executions.
  • gasPrice: QUANTITY - (optional) Hexadecimal value of the gasPrice used for each paid gas.
  • maxPriorityFeePerGas: QUANTITY - Hexadecimal maximum fee, in Wei, the sender is willing to pay per gas above the base fee.
  • maxFeePerGas: QUANTITY - Hexadecimal maximum total fee (base fee + priority fee), in Wei, the sender is willing to pay per gas.
  • value: QUANTITY - Hexadecimal of the value sent with this transaction.
  • data: DATA - (optional) Hash of the method signature and encoded parameters. See Ethereum contract ABI specification.

block parameter: QUANTITY|TAG [Required] - Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized. See the default block parameter.

Returns

DATA - The return value of the executed contract

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_call","params":[{
  "to":"0x4392743B97C46c6Aa186A7f3D0468fbF177ee70F",
  "gas":"0x76c0",
  "gasPrice":"0x0",
  "value":"0x0",
  "data":"0x06fdde03"},
  "latest"],"id":1}'

Result

{
"jsonrpc":"2.0",
"result":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b54656c636f696e20415544000000000000000000000000000000000000000000", // Telcoin AUD
"id":1
}

source

eth_chainId

Parameters

None

Returns

chainId - Hexadecimal value as a string representing the integer of the current chain id.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":62}'

Result

{
  "id":62,
  "jsonrpc": "2.0",
  "result": "0x7e1"
}

source

eth_createAccessList

Creates an EIP-2930 access list that you can include in a transaction.

Parameters

from: DATA, 20 bytes - (optional) The address the transaction is sent from.

to: DATA, 20 bytes - The address the transaction is directed to.

gas: QUANTITY - (optional) Hexadecimal value of the gas provided for the transaction execution.

gasPrice: QUANTITY - (optional) Hexadecimal value of the gasPrice , in Wei, provided by the sender. The default is 0. used only in non-EIP-1559 transactions.

maxPriorityFeePerGas: QUANTITY - (optional) Hexadecimal of the maximum fee, in Wei, the sender is willing to pay per gas above the base fee. If used, must specify maxFeePerGas .

maxFeePerGas: QUANTITY - (optional) Hexadecimal of the maximum total fee (base fee + priority fee), in Wei, the sender is willing to pay per gas. If used, must specify maxPriorityFeePerGas .

value: QUANTITY - Hexadecimal of the value sent with this transaction.

data: DATA - (optional) Hash of the method signature and encoded parameters. See Ethereum contract ABI specification.

block parameter: QUANTITY|TAG [Required] - Hexadecimal value of the block number, or one of the string tags latest, earliest, safe, or finalized. See the default block parameter.

Returns

Access list object with the following fields:

accessList - A list of objects with the following fields:

  • address: DATA, 20 bytes - Addresses to be accessed by the transaction.
  • storageKeys: DATA - Storage keys to be accessed by the transaction.

gasUsed: QUANTITY - Hexadecimal string representing the approximate gas cost for the transaction if the access list is included.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_createAccessList","params":[{
        "from": "0xaeA8F8f781326bfE6A7683C2BD48Dd6AA4d3Ba63",
        "data": "0x608060806080608155"},
        "finalized"],"id":1}'

Result

{
  jsonrpc: '2.0',
  result: { accessList: [ {address: '0x45e5af19ae13aac19215266edfab04f8db6f2c53',storageKeys: ['0x0000000000000000000000000000000000000000000000000000000000000081']} ], gasUsed: '0x12e92' },
  id: 1
}

source

eth_estimateGas

Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain. Note that the estimate may be significantly more than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.

Parameters

from: DATA, 20 bytes - (optional) The address the transaction is sent from.

to: DATA, 20 bytes - (optional) The address the transaction is directed to.

gas: QUANTITY - (optional) Hexadecimal value of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.

gasPrice: QUANTITY - (optional) Hexadecimal value of the gasPrice used for each paid gas.

maxPriorityFeePerGas: QUANTITY - (optional) Hexadecimal value of the gasPrice , in Wei, provided by the sender. The default is 0. used only in non-EIP-1559 transactions.

maxFeePerGas: QUANTITY - (optional) Hexadecimal of the maximum total fee (base fee + priority fee), in Wei, the sender is willing to pay per gas. If used, must specify maxPriorityFeePerGas .

value: QUANTITY - (optional) Hexadecimal of the value sent with this transaction.

data: DATA - (optional) Hash of the method signature and encoded parameters. See Ethereum contract ABI specification.

block parameter: QUANTITY|TAG [Required] - Hexadecimal block number, or one of the string tags latest, earliest, pending, safe, or finalized. See the default block parameter.

If no gas limit is specified geth uses the block gas limit from the pending block as an upper bound. As a result the returned estimate might not be enough to executed the call/transaction when the amount of gas is higher than the pending block gas limit.

Returns

QUANTITY - Hexadecimal of the estimate of the amount of gas used for the given transaction

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_estimateGas","params":[{
        "from": "0x9cE564c7d09f88E7d8233Cdd3A4d7AC42aBFf3aC",
        "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        "value": "0x0"}],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0x5208" // 21000
}

source

eth_feeHistory

Parameters

QUANTITY - Hexadecimal number of blocks in the requested range. Between 1 and 1024 blocks can be requested in a single query. If blocks in the specified block range are not available, then only the fee history for available blocks is returned.

QUANTITY|TAG - Hexadecimal representing the highest number block of the requested range, or one of the string tags latest or earliest.

Array of QUANTITY - (optional) A monotonically increasing list of integer percentile values to sample from each block’s effective priority fees per gas in ascending order, weighted by gas used.

Returns

oldestBlock: QUANTITY - Hexadecimal of the lowest number block of the returned range.

baseFeePerGas: Array of QUANTITY - An array of hexadecimal block base fees per gas, including an extra block value. The extra value is the next block after the newest block in the returned range. Returns zeroes for blocks created before EIP-1559.

gasUsedRatio: Array of QUANTITY - An array of hexadecimal block gas used ratios. These are calculated as the ratio of gasUsed and gasLimit.

reward: Array of QUANTITY - An array of hexadecimal effective priority fee per gas data points from a single block. All zeroes are returned if the block is empty.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_feeHistory","params":[
         "0x5",
         "latest",
         [20, 30]],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": {
    baseFeePerGas: [ '0x7', '0x7', '0x7', '0x7', '0x7', '0x7' ],
    gasUsedRatio: [ 0, 0, 0, 0, 0 ],
    oldestBlock: '0x667017',
    reward: [ ["0x0","0x0"], ["0x0","0x0"],
      ["0x0","0x0"],["0x0","0x0"],
      ["0x0","0x0"] ]
  }
}

source

eth_gasPrice

Parameters

None

Returns

QUANTITY- Hexadecimal integer of the current gas price in wei.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":73}'

Result

{
  "id":73,
  "jsonrpc": "2.0",
  "result": "0x3b9aca07" // 1000000007 Wei
}

source

eth_getBalance

Parameters

DATA, 20 Bytes - Address to check for balance.

QUANTITY|TAG - Hexadecimal integer block number, or the string "latest", "earliest", "safe", or "finalized", see the default block parameter

Returns

QUANTITY - Hexadecimal of the current account balance in wei.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x3da87b1c3743bd2da60df2ef1bc6f26ef9da6086", "latest"],"id":1}'

Result

{
"jsonrpc":"2.0",
"result":"0xfbea2ff545df4530c6", // 4.6470077e+21
"id":1
}

source

eth_getBlockByHash

Parameters

DATA, 32 Bytes - Hash of a block.

Boolean - If true it returns the full transaction objects, if false only the hashes of the transactions.

Returns

Object - A block object, or null when no block was found:

  • number: QUANTITY - Hexadecimal of the block number. null when its pending block.
  • hash: DATA, 32 Bytes - Hash of the block. null when its pending block.
  • parentHash: DATA, 32 Bytes - Hash of the parent block.
  • nonce: DATA, 8 Bytes - Hash of the generated proof-of-work. null when its pending block.
  • sha3Uncles: DATA, 32 Bytes - SHA3 of the uncles data in the block.
  • logsBloom: DATA, 256 Bytes - The bloom filter for the logs of the block. null when its pending block.
  • transactionsRoot: DATA, 32 Bytes - The root of the transaction trie of the block.
  • stateRoot: DATA, 32 Bytes - The root of the final state trie of the block.
  • receiptsRoot: DATA, 32 Bytes - The root of the receipts trie of the block.
  • miner: DATA, 20 Bytes - The address of the beneficiary to whom the mining rewards were given.
  • difficulty: QUANTITY - Hexadecimal of the difficulty for this block.
  • totalDifficulty: QUANTITY - Hexadecimal of the total difficulty of the chain until this block.
  • extraData: DATA - the “extra data” field of this block.
  • size: QUANTITY - Hexadecimal of the size of this block in bytes.
  • gasLimit: QUANTITY - Hexadecimal of the maximum gas allowed in this block.
  • gasUsed: QUANTITY - Hexadecimal of the total used gas by all transactions in this block.
  • timestamp: QUANTITY - Hexadecimal of the unix timestamp for when the block was collated.
  • transactions: Array - Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter.
  • uncles: Array - Array of uncle hashes.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getBlockByHash","params":["0x5fb9fff1f0f914360abfefb0eac9c7d4fc733007c62c7caf1860d4052776b7d7", false],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": {
    "hash": "0x5fb9fff1f0f914360abfefb0eac9c7d4fc733007c62c7caf1860d4052776b7d7",
    "parentHash": "0xdaf2bac916ac308e3f5eb077570a24aba29e951ce7306eae52b3e11ba1a7394f",
    "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
    "miner": "0x3333333333333333333333333333333333333333",
    "stateRoot": "0x9bfa443af733083d3c0eb602112b393683f405cb29b670741b65cb70ea858255",
    "transactionsRoot": "0x47bfe0736bb49e246b2aacec935e632885ec2a6067b11d657637646f391251a7",
    "receiptsRoot": "0x5b6c99267f9131834e759cff4f9d3c16568c844ee89fe6f192c149537648b075",
    "logsBloom": "0x00200000040000000000000280000028000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000008000000200000000000000000000000000000000400000000020000000010000002000800000000000000000000000010000000400000000000000000000000000000000000000000000000080000004000000000000100000000000000000000000040200100000000000000000020000000000000000002000000000000200000000000000000000000001000000000000020000080000000020000000000000000000000000000000000000001000000000000",
    "difficulty": "0x0",
    "number": "0x608eed",
    "gasLimit": "0x1c9c380",
    "gasUsed": "0x1d30a",
    "timestamp": "0x667b285d",
    "extraData": "0x",
    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "nonce": "0x0000000000000000",
    "baseFeePerGas": "0x7",
    "totalDifficulty": "0x0",
    "uncles": [],
    "transactions": [
      "0x7e7e6d8a61ff36ab337b42b6ed2edf34761f928a110aba857aae97121d35371b"
    ],
    "size": "0x378"
  },
  "id": 1
}

source

eth_getBlockByNumber

Parameters

QUANTITY|TAG - Hexadecimal block number, or the string "earliest", "latest", "safe" or "finalized", as in the default block parameter.

Boolean - If true it returns the full transaction objects, if false only the hashes of the transactions.

Returns

Object - A block object, or null when no block was found:

  • number: QUANTITY - Hexadecimal of the block number. null when its pending block.
  • hash: DATA, 32 Bytes - Hash of the block. null when its pending block.
  • parentHash: DATA, 32 Bytes - Hash of the parent block.
  • nonce: DATA, 8 Bytes - Hash of the generated proof-of-work. null when its pending block.
  • sha3Uncles: DATA, 32 Bytes - SHA3 of the uncles data in the block.
  • logsBloom: DATA, 256 Bytes - The bloom filter for the logs of the block. null when its pending block.
  • transactionsRoot: DATA, 32 Bytes - The root of the transaction trie of the block.
  • stateRoot: DATA, 32 Bytes - The root of the final state trie of the block.
  • receiptsRoot: DATA, 32 Bytes - The root of the receipts trie of the block.
  • miner: DATA, 20 Bytes - The address of the beneficiary to whom the mining rewards were given.
  • difficulty: QUANTITY - Hexadecimal of the difficulty for this block.
  • totalDifficulty: QUANTITY - Hexadecimal of the total difficulty of the chain until this block.
  • extraData: DATA - The “extra data” field of this block.
  • size: QUANTITY - Hexadecimal size of this block in bytes.
  • gasLimit: QUANTITY - Hexadecimal of the maximum gas allowed in this block.
  • gasUsed: QUANTITY - Hexadecimal of the total used gas by all transactions in this block.
  • timestamp: QUANTITY - Hexadecimal of the unix timestamp for when the block was collated.
  • transactions: Array - Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter.
  • uncles: Array - Array of uncle hashes.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x608EED", false],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": {
    "hash": "0x5fb9fff1f0f914360abfefb0eac9c7d4fc733007c62c7caf1860d4052776b7d7",
    "parentHash": "0xdaf2bac916ac308e3f5eb077570a24aba29e951ce7306eae52b3e11ba1a7394f",
    "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
    "miner": "0x3333333333333333333333333333333333333333",
    "stateRoot": "0x9bfa443af733083d3c0eb602112b393683f405cb29b670741b65cb70ea858255",
    "transactionsRoot": "0x47bfe0736bb49e246b2aacec935e632885ec2a6067b11d657637646f391251a7",
    "receiptsRoot": "0x5b6c99267f9131834e759cff4f9d3c16568c844ee89fe6f192c149537648b075",
    "logsBloom": "0x00200000040000000000000280000028000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000008000000200000000000000000000000000000000400000000020000000010000002000800000000000000000000000010000000400000000000000000000000000000000000000000000000080000004000000000000100000000000000000000000040200100000000000000000020000000000000000002000000000000200000000000000000000000001000000000000020000080000000020000000000000000000000000000000000000001000000000000",
    "difficulty": "0x0",
    "number": "0x608eed",
    "gasLimit": "0x1c9c380",
    "gasUsed": "0x1d30a",
    "timestamp": "0x667b285d",
    "extraData": "0x",
    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "nonce": "0x0000000000000000",
    "baseFeePerGas": "0x7",
    "totalDifficulty": "0x0",
    "uncles": [],
    "transactions": [
      "0x7e7e6d8a61ff36ab337b42b6ed2edf34761f928a110aba857aae97121d35371b"
    ],
    "size": "0x378"
  },
  "id": 1
}

source

eth_getBlockReceipts

Parameters

blockNumber: QUANTITY|TAG [Required] - Hexadecimal block number, or one of the string tags: "latest" , "earliest", "finalized", "safe".

Returns

result: Array - Array of receipt objects for every transaction in the block, or null when there is no corresponding block. Each receipt has the following fields:

  • transactionHash: DATA, 32 Bytes - Hash of the transaction.
  • transactionIndex: QUANTITY - Hexadecimal of the transactions index position in the block.
  • blockHash: DATA, 32 Bytes - Hash of the block where this transaction was in.
  • blockNumber: QUANTITY - Hexadecimal block number where this transaction was in.
  • from: DATA, 20 Bytes - Address of the sender.
  • to: DATA, 20 Bytes - Address of the receiver. null when its a contract creation transaction.
  • cumulativeGasUsed: QUANTITY - Hexadecimal of the total amount of gas used when this transaction was executed in the block.
  • effectiveGasPrice: QUANTITY - Hexadecimal of the sum of the base fee and tip paid per unit of gas.
  • gasUsed: QUANTITY - Hexadecimal of the amount of gas used by this specific transaction alone.
  • contractAddress: DATA, 20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null.
  • logs: Array - Array of log objects, which this transaction generated.
  • logsBloom: DATA, 256 Bytes - Bloom filter for light clients to quickly retrieve related logs.
  • status: QUANTITY - Either 0x1 (success) or 0x0 (failure).
  • type: QUANTITY - Hexadecimal of the transaction type, 0x0 for legacy transactions, 0x1 for access list types, 0x2 for dynamic fees.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getBlockReceipts","params":["0x3FE51"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": [
    {
      "transactionHash": "0x82756da4e315a0135c3a10bf056af829e9c0270c3fd2127791dd2498cf577111",
      "transactionIndex": "0x0",
      "blockHash": "0x8e69cf10fe1b0dbb7213406f58ea99904b685532d7c3604d3e1be9684bda6d4f",
      "blockNumber": "0x3fe51",
      "cumulativeGasUsed": "0xe9994",
      "gasUsed": "0xe9994",
      "effectiveGasPrice": "0x17bfac7c00",
      "from": "0xdec366b889a53b93cfa561076c03c18b0b4d6c93",
      "to": null,
      "contractAddress": "0xf9d26a8da3fccf2c65ab6ee19bf35aa08a0326d6",
      "logs": [
        {
          "address": "0xf9d26a8da3fccf2c65ab6ee19bf35aa08a0326d6",
          "topics": [
            "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2"
          ],
          "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff",
          "blockHash": "0x8e69cf10fe1b0dbb7213406f58ea99904b685532d7c3604d3e1be9684bda6d4f",
          "blockNumber": "0x3fe51",
          "transactionHash": "0x82756da4e315a0135c3a10bf056af829e9c0270c3fd2127791dd2498cf577111",
          "transactionIndex": "0x0",
          "logIndex": "0x0",
          "removed": false
        }
      ],
      "logsBloom": "0x00000000000000000000000000000000000000000000006000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      "status": "0x1",
      "type": "0x0"
    }
  ],
  "id": 1
}

source

eth_getBlockTransactionCountByHash

Parameters

DATA, 32 Bytes - Hash of a block

Returns

QUANTITY - Hexadecimal of the number of transactions in this block.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getBlockTransactionCountByHash","params":["0x5fb9fff1f0f914360abfefb0eac9c7d4fc733007c62c7caf1860d4052776b7d7"],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0x1" // 1
}

source

eth_getBlockTransactionCountByNumber

Parameters

QUANTITY|TAG - Hexadecimal block number, or the string "earliest", "latest", "safe" or "finalized"

Returns

QUANTITY - Hexadecimal number of transactions in this block.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getBlockTransactionCountByNumber","params":["0x608EED"],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0x1" // 1
}

source

eth_getCode

Parameters

DATA, 20 Bytes - Address

QUANTITY|TAG - Hexadecimal integer block number, or the string "latest", "earliest", "safe" or "finalized", see the default block parameter

Returns

DATA - The code from the given address.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getCode","params":["0x5eb15fedcca9cb9c5424167e329fd56905ffe0e3", "latest"],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0x60806040526004361061014f5760003560e01c80638803dbee116100b6578063c45a01551161006f578063c45a015514610a10578063d06ca61f14610a25578063ded9382a14610ada578063e8e3370014610b4d578063f305d71914610bcd578063fb3bdb4114610c1357610188565b80638803dbee146107df578063ad5c464814610875578063ad615dec146108a6578063af2979eb146108dc578063b6f9de951461092f578063baa2abde146109b357610188565b80634a25d94a116101085780634a25d94a146104f05780635b0d5984146105865780635c11d795146105f9578063791ac9471461068f5780637ff36ab51461072557806385f8c259146107a957610188565b806302751cec1461018d578063054d50d4146101f957806318cbafe5146102415780631f00ca74146103275780632195995c146103dc57806338ed17391461045a57610188565b3661018857336001600160a01b037f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5161461018657fe5b005b600080fd5b34801561019957600080fd5b506101e0600480360360c08110156101b057600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a00135610c97565b6040805192835260208301919091528051918290030190f35b34801561020557600080fd5b5061022f6004803603606081101561021c57600080fd5b5080359060208101359060400135610db1565b60408051918252519081900360200190f35b34801561024d57600080fd5b506102d7600480360360a081101561026457600080fd5b813591602081013591810190606081016040820135600160201b81111561028a57600080fd5b82018360208201111561029c57600080fd5b803590602001918460208302840111600160201b831117156102bd57600080fd5b91935091506001600160a01b038135169060200135610dc6565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156103135781810151838201526020016102fb565b505050509050019250505060405180910390f35b34801561033357600080fd5b506102d76004803603604081101561034a57600080fd5b81359190810190604081016020820135600160201b81111561036b57600080fd5b82018360208201111561037d57600080fd5b803590602001918460208302840111600160201b8311171561039e57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506110f3945050505050565b3480156103e857600080fd5b506101e0600480360361016081101561040057600080fd5b506001600160a01b038135811691602081013582169160408201359160608101359160808201359160a08101359091169060c08101359060e081013515159060ff6101008201351690610120810135906101400135611129565b34801561046657600080fd5b506102d7600480360360a081101561047d57600080fd5b813591602081013591810190606081016040820135600160201b8111156104a357600080fd5b8201836020820111156104b557600080fd5b803590602001918460208302840111600160201b831117156104d657600080fd5b91935091506001600160a01b038135169060200135611223565b3480156104fc57600080fd5b506102d7600480360360a081101561051357600080fd5b813591602081013591810190606081016040820135600160201b81111561053957600080fd5b82018360208201111561054b57600080fd5b803590602001918460208302840111600160201b8311171561056c57600080fd5b91935091506001600160a01b03813516906020013561136e565b34801561059257600080fd5b5061022f60048036036101408110156105aa57600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a08101359060c081013515159060ff60e082013516906101008101359061012001356114fa565b34801561060557600080fd5b50610186600480360360a081101561061c57600080fd5b813591602081013591810190606081016040820135600160201b81111561064257600080fd5b82018360208201111561065457600080fd5b803590602001918460208302840111600160201b8311171561067557600080fd5b91935091506001600160a01b038135169060200135611608565b34801561069b57600080fd5b50610186600480360360a08110156106b257600080fd5b813591602081013591810190606081016040820135600160201b8111156106d857600080fd5b8201836020820111156106ea57600080fd5b803590602001918460208302840111600160201b8311171561070b57600080fd5b91935091506001600160a01b03813516906020013561189d565b6102d76004803603608081101561073b57600080fd5b81359190810190604081016020820135600160201b81111561075c57600080fd5b82018360208201111561076e57600080fd5b803590602001918460208302840111600160201b8311171561078f57600080fd5b91935091506001600160a01b038135169060200135611b21565b3480156107b557600080fd5b5061022f600480360360608110156107cc57600080fd5b5080359060208101359060400135611e74565b3480156107eb57600080fd5b506102d7600480360360a081101561080257600080fd5b813591602081013591810190606081016040820135600160201b81111561082857600080fd5b82018360208201111561083a57600080fd5b803590602001918460208302840111600160201b8311171561085b57600080fd5b91935091506001600160a01b038135169060200135611e81565b34801561088157600080fd5b5061088a611f7a565b604080516001600160a01b039092168252519081900360200190f35b3480156108b257600080fd5b5061022f600480360360608110156108c957600080fd5b5080359060208101359060400135611f9e565b3480156108e857600080fd5b5061022f600480360360c08110156108ff57600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a00135611fab565b6101866004803603608081101561094557600080fd5b81359190810190604081016020820135600160201b81111561096657600080fd5b82018360208201111561097857600080fd5b803590602001918460208302840111600160201b8311171561099957600080fd5b91935091506001600160a01b03813516906020013561212c565b3480156109bf57600080fd5b506101e0600480360360e08110156109d657600080fd5b506001600160a01b038135811691602081013582169160408201359160608101359160808201359160a08101359091169060c001356124b8565b348015610a1c57600080fd5b5061088a6126fc565b348015610a3157600080fd5b506102d760048036036040811015610a4857600080fd5b81359190810190604081016020820135600160201b811115610a6957600080fd5b820183602082011115610a7b57600080fd5b803590602001918460208302840111600160201b83111715610a9c57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550612720945050505050565b348015610ae657600080fd5b506101e06004803603610140811015610afe57600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a08101359060c081013515159060ff60e0820135169061010081013590610120013561274d565b348015610b5957600080fd5b50610baf6004803603610100811015610b7157600080fd5b506001600160a01b038135811691602081013582169160408201359160608101359160808201359160a08101359160c0820135169060e00135612861565b60408051938452602084019290925282820152519081900360600190f35b610baf600480360360c0811015610be357600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a0013561299d565b6102d760048036036080811015610c2957600080fd5b81359190810190604081016020820135600160201b811115610c4a57600080fd5b820183602082011115610c5c57600080fd5b803590602001918460208302840111600160201b83111715610c7d57600080fd5b91935091506001600160a01b038135169060200135612c42565b6000808242811015610cde576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b610d0d897f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a58a8a8a308a6124b8565b9093509150610d1d898685612fc4565b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b0316632e1a7d4d836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b158015610d8357600080fd5b505af1158015610d97573d6000803e3d6000fd5b50505050610da58583613118565b50965096945050505050565b6000610dbe848484613210565b949350505050565b60608142811015610e0c576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b6001600160a01b037f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a51686866000198101818110610e4657fe5b905060200201356001600160a01b03166001600160a01b031614610e9f576040805162461bcd60e51b815260206004820152601d60248201526000805160206144fa833981519152604482015290519081900360640190fd5b610efd7f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061330092505050565b91508682600184510381518110610f1057fe5b60200260200101511015610f555760405162461bcd60e51b815260040180806020018281038252602b815260200180614540602b913960400191505060405180910390fd5b610ff386866000818110610f6557fe5b905060200201356001600160a01b031633610fd97f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8a8a6000818110610fa757fe5b905060200201356001600160a01b03168b8b6001818110610fc457fe5b905060200201356001600160a01b031661344c565b85600081518110610fe657fe5b602002602001015161350c565b61103282878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250309250613669915050565b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b0316632e1a7d4d8360018551038151811061107157fe5b60200260200101516040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b1580156110af57600080fd5b505af11580156110c3573d6000803e3d6000fd5b505050506110e884836001855103815181106110db57fe5b6020026020010151613118565b509695505050505050565b60606111207f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b84846138af565b90505b92915050565b60008060006111597f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8f8f61344c565b9050600087611168578c61116c565b6000195b6040805163d505accf60e01b815233600482015230602482015260448101839052606481018c905260ff8a16608482015260a4810189905260c4810188905290519192506001600160a01b0384169163d505accf9160e48082019260009290919082900301818387803b1580156111e257600080fd5b505af11580156111f6573d6000803e3d6000fd5b505050506112098f8f8f8f8f8f8f6124b8565b809450819550505050509b509b9950505050505050505050565b60608142811015611269576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b6112c77f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061330092505050565b915086826001845103815181106112da57fe5b6020026020010151101561131f5760405162461bcd60e51b815260040180806020018281038252602b815260200180614540602b913960400191505060405180910390fd5b61132f86866000818110610f6557fe5b6110e882878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250613669915050565b606081428110156113b4576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b6001600160a01b037f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a516868660001981018181106113ee57fe5b905060200201356001600160a01b03166001600160a01b031614611447576040805162461bcd60e51b815260206004820152601d60248201526000805160206144fa833981519152604482015290519081900360640190fd5b6114a57f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b898888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506138af92505050565b915086826000815181106114b557fe5b60200260200101511115610f555760405162461bcd60e51b81526004018080602001828103825260278152602001806144d36027913960400191505060405180910390fd5b6000806115487f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8d7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a561344c565b9050600086611557578b61155b565b6000195b6040805163d505accf60e01b815233600482015230602482015260448101839052606481018b905260ff8916608482015260a4810188905260c4810187905290519192506001600160a01b0384169163d505accf9160e48082019260009290919082900301818387803b1580156115d157600080fd5b505af11580156115e5573d6000803e3d6000fd5b505050506115f78d8d8d8d8d8d611fab565b9d9c50505050505050505050505050565b804281101561164c576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b6116c18585600081811061165c57fe5b905060200201356001600160a01b0316336116bb7f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8989600081811061169e57fe5b905060200201356001600160a01b03168a8a6001818110610fc457fe5b8a61350c565b6000858560001981018181106116d357fe5b905060200201356001600160a01b03166001600160a01b03166370a08231856040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b15801561173857600080fd5b505afa15801561174c573d6000803e3d6000fd5b505050506040513d602081101561176257600080fd5b505160408051602088810282810182019093528882529293506117a49290918991899182918501908490808284376000920191909152508892506139e7915050565b8661185682888860001981018181106117b957fe5b905060200201356001600160a01b03166001600160a01b03166370a08231886040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b15801561181e57600080fd5b505afa158015611832573d6000803e3d6000fd5b505050506040513d602081101561184857600080fd5b50519063ffffffff613cf216565b10156118935760405162461bcd60e51b815260040180806020018281038252602b815260200180614540602b913960400191505060405180910390fd5b5050505050505050565b80428110156118e1576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b6001600160a01b037f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5168585600019810181811061191b57fe5b905060200201356001600160a01b03166001600160a01b031614611974576040805162461bcd60e51b815260206004820152601d60248201526000805160206144fa833981519152604482015290519081900360640190fd5b6119848585600081811061165c57fe5b6119c28585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152503092506139e7915050565b604080516370a0823160e01b815230600482015290516000916001600160a01b037f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a516916370a0823191602480820192602092909190829003018186803b158015611a2c57600080fd5b505afa158015611a40573d6000803e3d6000fd5b505050506040513d6020811015611a5657600080fd5b5051905086811015611a995760405162461bcd60e51b815260040180806020018281038252602b815260200180614540602b913960400191505060405180910390fd5b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b0316632e1a7d4d826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b158015611aff57600080fd5b505af1158015611b13573d6000803e3d6000fd5b505050506118938482613118565b60608142811015611b67576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031686866000818110611b9e57fe5b905060200201356001600160a01b03166001600160a01b031614611bf7576040805162461bcd60e51b815260206004820152601d60248201526000805160206144fa833981519152604482015290519081900360640190fd5b611c557f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b3488888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061330092505050565b91508682600184510381518110611c6857fe5b60200260200101511015611cad5760405162461bcd60e51b815260040180806020018281038252602b815260200180614540602b913960400191505060405180910390fd5b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031663d0e30db083600081518110611ce957fe5b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b158015611d1c57600080fd5b505af1158015611d30573d6000803e3d6000fd5b50505050507f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031663a9059cbb611d957f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8989600081811061169e57fe5b84600081518110611da257fe5b60200260200101516040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050602060405180830381600087803b158015611df957600080fd5b505af1158015611e0d573d6000803e3d6000fd5b505050506040513d6020811015611e2357600080fd5b5051611e2b57fe5b611e6a82878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250613669915050565b5095945050505050565b6000610dbe848484613d42565b60608142811015611ec7576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b611f257f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b898888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506138af92505050565b91508682600081518110611f3557fe5b6020026020010151111561131f5760405162461bcd60e51b81526004018080602001828103825260278152602001806144d36027913960400191505060405180910390fd5b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a581565b6000610dbe848484613e32565b60008142811015611ff1576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b612020887f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a589898930896124b8565b604080516370a0823160e01b815230600482015290519194506120a492508a9187916001600160a01b038416916370a0823191602480820192602092909190829003018186803b15801561207357600080fd5b505afa158015612087573d6000803e3d6000fd5b505050506040513d602081101561209d57600080fd5b5051612fc4565b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b0316632e1a7d4d836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561210a57600080fd5b505af115801561211e573d6000803e3d6000fd5b505050506110e88483613118565b8042811015612170576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b0316858560008181106121a757fe5b905060200201356001600160a01b03166001600160a01b031614612200576040805162461bcd60e51b815260206004820152601d60248201526000805160206144fa833981519152604482015290519081900360640190fd5b60003490507f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561226057600080fd5b505af1158015612274573d6000803e3d6000fd5b50505050507f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031663a9059cbb6122d97f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8989600081811061169e57fe5b836040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050602060405180830381600087803b15801561232957600080fd5b505af115801561233d573d6000803e3d6000fd5b505050506040513d602081101561235357600080fd5b505161235b57fe5b60008686600019810181811061236d57fe5b905060200201356001600160a01b03166001600160a01b03166370a08231866040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156123d257600080fd5b505afa1580156123e6573d6000803e3d6000fd5b505050506040513d60208110156123fc57600080fd5b5051604080516020898102828101820190935289825292935061243e9290918a918a9182918501908490808284376000920191909152508992506139e7915050565b87611856828989600019810181811061245357fe5b905060200201356001600160a01b03166001600160a01b03166370a08231896040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b15801561181e57600080fd5b60008082428110156124ff576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b600061252c7f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8c8c61344c565b604080516323b872dd60e01b81523360048201526001600160a01b03831660248201819052604482018d9052915192935090916323b872dd916064808201926020929091908290030181600087803b15801561258757600080fd5b505af115801561259b573d6000803e3d6000fd5b505050506040513d60208110156125b157600080fd5b50506040805163226bf2d160e21b81526001600160a01b03888116600483015282516000938493928616926389afcb44926024808301939282900301818787803b1580156125fe57600080fd5b505af1158015612612573d6000803e3d6000fd5b505050506040513d604081101561262857600080fd5b508051602090910151909250905060006126428e8e613ede565b509050806001600160a01b03168e6001600160a01b031614612665578183612668565b82825b90975095508a8710156126ac5760405162461bcd60e51b815260040180806020018281038252602681526020018061451a6026913960400191505060405180910390fd5b898610156126eb5760405162461bcd60e51b81526004018080602001828103825260268152602001806144606026913960400191505060405180910390fd5b505050505097509795505050505050565b7f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b81565b60606111207f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8484613300565b600080600061279d7f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8e7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a561344c565b90506000876127ac578c6127b0565b6000195b6040805163d505accf60e01b815233600482015230602482015260448101839052606481018c905260ff8a16608482015260a4810189905260c4810188905290519192506001600160a01b0384169163d505accf9160e48082019260009290919082900301818387803b15801561282657600080fd5b505af115801561283a573d6000803e3d6000fd5b5050505061284c8e8e8e8e8e8e610c97565b909f909e509c50505050505050505050505050565b600080600083428110156128aa576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b6128b88c8c8c8c8c8c613fbc565b909450925060006128ea7f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8e8e61344c565b90506128f88d33838861350c565b6129048c33838761350c565b806001600160a01b0316636a627842886040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050602060405180830381600087803b15801561295c57600080fd5b505af1158015612970573d6000803e3d6000fd5b505050506040513d602081101561298657600080fd5b5051949d939c50939a509198505050505050505050565b600080600083428110156129e6576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b612a148a7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a58b348c8c613fbc565b90945092506000612a667f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8c7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a561344c565b9050612a748b33838861350c565b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031663d0e30db0856040518263ffffffff1660e01b81526004016000604051808303818588803b158015612acf57600080fd5b505af1158015612ae3573d6000803e3d6000fd5b50505050507f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031663a9059cbb82866040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050602060405180830381600087803b158015612b6857600080fd5b505af1158015612b7c573d6000803e3d6000fd5b505050506040513d6020811015612b9257600080fd5b5051612b9a57fe5b806001600160a01b0316636a627842886040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050602060405180830381600087803b158015612bf257600080fd5b505af1158015612c06573d6000803e3d6000fd5b505050506040513d6020811015612c1c57600080fd5b5051925034841015612c3457612c3433853403613118565b505096509650969350505050565b60608142811015612c88576040805162461bcd60e51b815260206004820152601860248201526000805160206145c3833981519152604482015290519081900360640190fd5b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031686866000818110612cbf57fe5b905060200201356001600160a01b03166001600160a01b031614612d18576040805162461bcd60e51b815260206004820152601d60248201526000805160206144fa833981519152604482015290519081900360640190fd5b612d767f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b888888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506138af92505050565b91503482600081518110612d8657fe5b60200260200101511115612dcb5760405162461bcd60e51b81526004018080602001828103825260278152602001806144d36027913960400191505060405180910390fd5b7f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031663d0e30db083600081518110612e0757fe5b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b158015612e3a57600080fd5b505af1158015612e4e573d6000803e3d6000fd5b50505050507f00000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a56001600160a01b031663a9059cbb612eb37f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8989600081811061169e57fe5b84600081518110612ec057fe5b60200260200101516040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050602060405180830381600087803b158015612f1757600080fd5b505af1158015612f2b573d6000803e3d6000fd5b505050506040513d6020811015612f4157600080fd5b5051612f4957fe5b612f8882878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250613669915050565b81600081518110612f9557fe5b6020026020010151341115611e6a57611e6a3383600081518110612fb557fe5b60200260200101513403613118565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b178152925182516000946060949389169392918291908083835b602083106130415780518252601f199092019160209182019101613022565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146130a3576040519150601f19603f3d011682016040523d82523d6000602084013e6130a8565b606091505b50915091508180156130d65750805115806130d657508080602001905160208110156130d357600080fd5b50515b6131115760405162461bcd60e51b815260040180806020018281038252602d81526020018061456b602d913960400191505060405180910390fd5b5050505050565b604080516000808252602082019092526001600160a01b0384169083906040518082805190602001908083835b602083106131645780518252601f199092019160209182019101613145565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d80600081146131c6576040519150601f19603f3d011682016040523d82523d6000602084013e6131cb565b606091505b505090508061320b5760405162461bcd60e51b81526004018080602001828103825260348152602001806144076034913960400191505060405180910390fd5b505050565b60008084116132505760405162461bcd60e51b815260040180806020018281038252602b815260200180614598602b913960400191505060405180910390fd5b6000831180156132605750600082115b61329b5760405162461bcd60e51b81526004018080602001828103825260288152602001806144866028913960400191505060405180910390fd5b60006132af856103e563ffffffff61423016565b905060006132c3828563ffffffff61423016565b905060006132e9836132dd886103e863ffffffff61423016565b9063ffffffff61429316565b90508082816132f457fe5b04979650505050505050565b6060600282511015613359576040805162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f504154480000604482015290519081900360640190fd5b815167ffffffffffffffff8111801561337157600080fd5b5060405190808252806020026020018201604052801561339b578160200160208202803683370190505b50905082816000815181106133ac57fe5b60200260200101818152505060005b6001835103811015613444576000806133fe878685815181106133da57fe5b60200260200101518786600101815181106133f157fe5b60200260200101516142e2565b9150915061342084848151811061341157fe5b60200260200101518383613210565b84846001018151811061342f57fe5b602090810291909101015250506001016133bb565b509392505050565b600080600061345b8585613ede565b604080516bffffffffffffffffffffffff19606094851b811660208084019190915293851b81166034830152825160288184030181526048830184528051908501206001600160f81b031960688401529a90941b9093166069840152607d8301989098527f97fe61384d0aed6f145eff143d6773691f987c4147f3a1503ed6305a96d87a56609d808401919091528851808403909101815260bd909201909752805196019590952095945050505050565b604080516001600160a01b0385811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b17815292518251600094606094938a169392918291908083835b602083106135915780518252601f199092019160209182019101613572565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146135f3576040519150601f19603f3d011682016040523d82523d6000602084013e6135f8565b606091505b5091509150818015613626575080511580613626575080806020019051602081101561362357600080fd5b50515b6136615760405162461bcd60e51b81526004018080602001828103825260318152602001806143d66031913960400191505060405180910390fd5b505050505050565b60005b60018351038110156138a95760008084838151811061368757fe5b602002602001015185846001018151811061369e57fe5b60200260200101519150915060006136b68383613ede565b50905060008785600101815181106136ca57fe5b60200260200101519050600080836001600160a01b0316866001600160a01b0316146136f8578260006136fc565b6000835b91509150600060028a510388106137135788613754565b6137547f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b878c8b6002018151811061374757fe5b602002602001015161344c565b90506137817f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b888861344c565b6001600160a01b031663022c0d9f84848460006040519080825280601f01601f1916602001820160405280156137be576020820181803683370190505b506040518563ffffffff1660e01b815260040180858152602001848152602001836001600160a01b03166001600160a01b0316815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561382f578181015183820152602001613817565b50505050905090810190601f16801561385c5780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b15801561387e57600080fd5b505af1158015613892573d6000803e3d6000fd5b50506001909901985061366c975050505050505050565b50505050565b6060600282511015613908576040805162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f504154480000604482015290519081900360640190fd5b815167ffffffffffffffff8111801561392057600080fd5b5060405190808252806020026020018201604052801561394a578160200160208202803683370190505b509050828160018351038151811061395e57fe5b60209081029190910101528151600019015b8015613444576000806139a08786600186038151811061398c57fe5b60200260200101518786815181106133f157fe5b915091506139c28484815181106139b357fe5b60200260200101518383613d42565b8460018503815181106139d157fe5b6020908102919091010152505060001901613970565b60005b600183510381101561320b57600080848381518110613a0557fe5b6020026020010151858460010181518110613a1c57fe5b6020026020010151915091506000613a348383613ede565b5090506000613a647f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b858561344c565b9050600080600080846001600160a01b0316630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b158015613aa557600080fd5b505afa158015613ab9573d6000803e3d6000fd5b505050506040513d6060811015613acf57600080fd5b5080516020909101516001600160701b0391821693501690506000806001600160a01b038a811690891614613b05578284613b08565b83835b91509150613b66828b6001600160a01b03166370a082318a6040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b15801561181e57600080fd5b9550613b73868383613210565b945050505050600080856001600160a01b0316886001600160a01b031614613b9d57826000613ba1565b6000835b91509150600060028c51038a10613bb8578a613bec565b613bec7f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b898e8d6002018151811061374757fe5b604080516000808252602082019283905263022c0d9f60e01b835260248201878152604483018790526001600160a01b038086166064850152608060848501908152845160a48601819052969750908c169563022c0d9f958a958a958a9591949193919260c486019290918190849084905b83811015613c76578181015183820152602001613c5e565b50505050905090810190601f168015613ca35780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b158015613cc557600080fd5b505af1158015613cd9573d6000803e3d6000fd5b50506001909b019a506139ea9950505050505050505050565b80820382811115611123576040805162461bcd60e51b815260206004820152601560248201527464732d6d6174682d7375622d756e646572666c6f7760581b604482015290519081900360640190fd5b6000808411613d825760405162461bcd60e51b815260040180806020018281038252602c8152602001806143aa602c913960400191505060405180910390fd5b600083118015613d925750600082115b613dcd5760405162461bcd60e51b81526004018080602001828103825260288152602001806144866028913960400191505060405180910390fd5b6000613df16103e8613de5868863ffffffff61423016565b9063ffffffff61423016565b90506000613e0b6103e5613de5868963ffffffff613cf216565b9050613e286001828481613e1b57fe5b049063ffffffff61429316565b9695505050505050565b6000808411613e725760405162461bcd60e51b81526004018080602001828103825260258152602001806144ae6025913960400191505060405180910390fd5b600083118015613e825750600082115b613ebd5760405162461bcd60e51b81526004018080602001828103825260288152602001806144866028913960400191505060405180910390fd5b82613ece858463ffffffff61423016565b81613ed557fe5b04949350505050565b600080826001600160a01b0316846001600160a01b03161415613f325760405162461bcd60e51b815260040180806020018281038252602581526020018061443b6025913960400191505060405180910390fd5b826001600160a01b0316846001600160a01b031610613f52578284613f55565b83835b90925090506001600160a01b038216613fb5576040805162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f414444524553530000604482015290519081900360640190fd5b9250929050565b6040805163e6a4390560e01b81526001600160a01b03888116600483015287811660248301529151600092839283927f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b9092169163e6a4390591604480820192602092909190829003018186803b15801561403657600080fd5b505afa15801561404a573d6000803e3d6000fd5b505050506040513d602081101561406057600080fd5b50516001600160a01b0316141561411357604080516364e329cb60e11b81526001600160a01b038a81166004830152898116602483015291517f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b9092169163c9c65396916044808201926020929091908290030181600087803b1580156140e657600080fd5b505af11580156140fa573d6000803e3d6000fd5b505050506040513d602081101561411057600080fd5b50505b6000806141417f000000000000000000000000a3e9bf36ff51ce14a25a2cf4b4086cbcf1df781b8b8b6142e2565b91509150816000148015614153575080155b1561416357879350869250614223565b6000614170898484613e32565b90508781116141c357858110156141b85760405162461bcd60e51b81526004018080602001828103825260268152602001806144606026913960400191505060405180910390fd5b889450925082614221565b60006141d0898486613e32565b9050898111156141dc57fe5b8781101561421b5760405162461bcd60e51b815260040180806020018281038252602681526020018061451a6026913960400191505060405180910390fd5b94508793505b505b5050965096945050505050565b600081158061424b5750508082028282828161424857fe5b04145b611123576040805162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6d756c2d6f766572666c6f7760601b604482015290519081900360640190fd5b80820182811015611123576040805162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6164642d6f766572666c6f7760601b604482015290519081900360640190fd5b60008060006142f18585613ede565b50905060008061430288888861344c565b6001600160a01b0316630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561433a57600080fd5b505afa15801561434e573d6000803e3d6000fd5b505050506040513d606081101561436457600080fd5b5080516020909101516001600160701b0391821693501690506001600160a01b038781169084161461439757808261439a565b81815b9099909850965050505050505056fe556e697377617056324c6962726172793a20494e53554646494349454e545f4f55545055545f414d4f554e545472616e7366657248656c7065723a3a7472616e7366657246726f6d3a207472616e7366657246726f6d206661696c65645472616e7366657248656c7065723a3a736166655472616e736665724554483a20455448207472616e73666572206661696c6564556e697377617056324c6962726172793a204944454e544943414c5f414444524553534553556e69737761705632526f757465723a20494e53554646494349454e545f425f414d4f554e54556e697377617056324c6962726172793a20494e53554646494349454e545f4c4951554944495459556e697377617056324c6962726172793a20494e53554646494349454e545f414d4f554e54556e69737761705632526f757465723a204558434553534956455f494e5055545f414d4f554e54556e69737761705632526f757465723a20494e56414c49445f50415448000000556e69737761705632526f757465723a20494e53554646494349454e545f415f414d4f554e54556e69737761705632526f757465723a20494e53554646494349454e545f4f55545055545f414d4f554e545472616e7366657248656c7065723a3a736166655472616e736665723a207472616e73666572206661696c6564556e697377617056324c6962726172793a20494e53554646494349454e545f494e5055545f414d4f554e54556e69737761705632526f757465723a20455850495245440000000000000000a26469706673582212205168459737228e7afa3127ef9cc1e57ff202b6d61791c718f26f5058c7900a4e64736f6c63430006060033"
}

source

eth_getLogs

Parameters

Object - The filter options:

  • fromBlock: QUANTITY|TAG - (optional, default: "latest") Hexadecimal block number, or "latest" for the last mined block, "safe" for the latest safe block, "finalized" for the latest finalized block, or "earliest" for not yet mined transactions.
  • toBlock: QUANTITY|TAG - (optional, default: "latest") Hexadecimal block number, or "latest" for the last mined block, "safe" for the latest safe block, "finalized" for the latest finalized block, or "earliest" for not yet mined transactions.
  • address: DATA|Array, 20 Bytes - (optional) Contract address or a list of addresses from which logs should originate.
  • topics: Array of DATA, - (optional) Array of 32 Bytes DATA topics. Topics are order-dependent. Each topic can also be an array of DATA with “or” options.
  • blockhash: DATA, 32 Bytes - (optional) Per EIP-234, blockHash restricts the logs returned to the single block with the 32-byte hash blockHash. Using blockHash is equivalent to fromBlock = toBlock = the block number with hash blockHash. If blockHash is present in the filter criteria, then neither fromBlock nor toBlock are allowed.

Returns

Array - Array of log objects, or an empty array if nothing has changed since last poll.

For filters created with eth_newBlockFilter the return are block hashes (DATA, 32 Bytes), e.g. ["0x3454645634534..."].

For filters created with eth_newPendingTransactionFilter the return are transaction hashes (DATA, 32 Bytes), e.g. ["0x6345343454645..."].

For filters created with eth_newFilter logs are objects with following params:

  • removed: TAG - true when the log was removed, due to a chain reorganization. false if its a valid log.
  • logIndex: QUANTITY - Hexadecimal log index position in the block. null when its pending log.
  • transactionIndex: QUANTITY - Hexadecimal of the transactions index position log was created from. null when its pending log.
  • transactionHash: DATA, 32 Bytes - Hash of the transactions this log was created from. null when its pending log.
  • blockHash: DATA, 32 Bytes - Hash of the block where this log was in. null when its pending. null when its pending log.
  • blockNumber: QUANTITY - Hexadecimal of the block number where this log was in. null when its pending. null when its pending log.
  • address: DATA, 20 Bytes - Address from which this log originated.
  • data: DATA - Contains zero or more 32 Bytes non-indexed arguments of the log.
  • topics: Array of DATA - Array of 0 to 4 32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.)

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{
          "fromBlock": "0x608EED",
          "toBlock": "0x608EEE",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
          ]
        }],"id":74}'

Result

{
  "jsonrpc": "2.0",
  "id": 74,
  "result": [
    {
      "address": "0xa2c07c15173c183771ffad40c2e972f97e0bce64",
      "topics": [
        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x0000000000000000000000003da87b1c3743bd2da60df2ef1bc6f26ef9da6086",
        "0x000000000000000000000000e2005c7718f3849f39c65b8ea93c7a7030aec463"
      ],
      "data": "0x00000000000000000000000000000000000000000000000001a6fd80655b3000",
      "blockHash": "0x5fb9fff1f0f914360abfefb0eac9c7d4fc733007c62c7caf1860d4052776b7d7",
      "blockNumber": "0x608eed",
      "transactionHash": "0x7e7e6d8a61ff36ab337b42b6ed2edf34761f928a110aba857aae97121d35371b",
      "transactionIndex": "0x0",
      "logIndex": "0x0",
      "removed": false
    },
    {
      "address": "0x80d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5",
      "topics": [
        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x000000000000000000000000e2005c7718f3849f39c65b8ea93c7a7030aec463",
        "0x0000000000000000000000005eb15fedcca9cb9c5424167e329fd56905ffe0e3"
      ],
      "data": "0x0000000000000000000000000000000000000000000000027215848ae3242d0b",
      "blockHash": "0x5fb9fff1f0f914360abfefb0eac9c7d4fc733007c62c7caf1860d4052776b7d7",
      "blockNumber": "0x608eed",
      "transactionHash": "0x7e7e6d8a61ff36ab337b42b6ed2edf34761f928a110aba857aae97121d35371b",
      "transactionIndex": "0x0",
      "logIndex": "0x1",
      "removed": false
    },
    {
      "address": "0x80d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5",
      "topics": [
        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x0000000000000000000000005eb15fedcca9cb9c5424167e329fd56905ffe0e3",
        "0x0000000000000000000000000000000000000000000000000000000000000000"
      ],
      "data": "0x0000000000000000000000000000000000000000000000027215848ae3242d0b",
      "blockHash": "0x5fb9fff1f0f914360abfefb0eac9c7d4fc733007c62c7caf1860d4052776b7d7",
      "blockNumber": "0x608eed",
      "transactionHash": "0x7e7e6d8a61ff36ab337b42b6ed2edf34761f928a110aba857aae97121d35371b",
      "transactionIndex": "0x0",
      "logIndex": "0x4",
      "removed": false
    }
  ]
}

source

eth_getProof

Parameters

address: DATA, 20 Bytes - address to check for balance.

storageKeys: Arrayof DATA - array of 32 bytes storage keys to be proofed and included.

blockParameter: QUANTITY|TAG - hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized. See the default block parameter.

Returns

  • balance: QUANTITY-Hexadecimal of the current balance in wei.
  • codeHash: DATA, 32 Bytes - Hash of the code of the account.
  • nonce: QUANTITY - The nonce of the account.
  • storageHash: DATA, 32 bytes - The SHA3 of the StorageRoot. All storage will deliver a Merkle proof starting with this rootHash.
  • accountProof: ArrayofDATA - Array of RLP-serialized MerkleTree-Nodes, starting with the stateRoot-Node, following the path of the SHA3 (address) as key.
  • storageProof: ArrayofDATA - Array of storage-entries as requested. Each entry is an object with these properties:
    • key: DATA- The requested storage key.
    • value: QUANTITY- The storage value.
    • proof: Arrayof DATA - Array of RLP-serialized MerkleTree-Nodes, starting with the storageHash-Node, following the path of the SHA3 (key) as path.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getProof","params":[
        "0x4392743B97C46c6Aa186A7f3D0468fbF177ee70F",
        ["0x86386409a65c1a7f963bc51852fa7ecbdb9cad2cec464de22ee4591e1622b46b"],
        "latest"]
        ,"id":1}'

Result

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32603,
    "message": "internal blocking task error"
  },
  "id": 1
}

source

eth_getStorageAt

Parameters

DATA, 20 Bytes - Address of the storage.

QUANTITY - Hexadecimal of the position in the storage.

QUANTITY|TAG - Hexadecimal block number, or the string "latest", "earliest", "safe", "finalized", see the default block parameter

Returns

DATA - The value at this storage position.

Example

In this case we consider the deployed eAUD token at address 0x4392743B97C46c6Aa186A7f3D0468fbF177ee70F . From the token’s documentation, we see the 'DECIMALS_SLOT' is at address 0x86386409a65c1a7f963bc51852fa7ecbdb9cad2cec464de22ee4591e1622b46b. Getting the storage at this address should give us the decimal value of the token.

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0", "method": "eth_getStorageAt", "params": [
        "0x4392743B97C46c6Aa186A7f3D0468fbF177ee70F",
        "0x86386409a65c1a7f963bc51852fa7ecbdb9cad2cec464de22ee4591e1622b46b",
        "latest"], "id": 1}'

Result

{
  "jsonrpc": "2.0",
  "result": "0x0000000000000000000000000000000000000000000000000000000000000006", //6
  "id": 1
}

source

eth_getTransactionByBlockHashAndIndex

Parameters

DATA, 32 Bytes - Hash of a block.

QUANTITY - Hexadecimal of the transaction index position.

Returns

Object - A transaction object, or null when no transaction was found:

  • blockHash: DATA, 32 Bytes - Hash of the block where this transaction was in. null when its pending.
  • blockNumber: QUANTITY - Hexadecimal block number where this transaction was in. null when its pending.
  • from: DATA, 20 Bytes - Address of the sender.
  • gas: QUANTITY - Hexadecimal gas provided by the sender.
  • gasPrice: QUANTITY - Hexadecimal gas price provided by the sender in Wei.
  • hash: DATA, 32 Bytes - Hash of the transaction.
  • input: DATA - The data send along with the transaction.
  • nonce: QUANTITY - The number of transactions made by the sender prior to this one.
  • to: DATA, 20 Bytes - Address of the receiver. null when its a contract creation transaction.
  • transactionIndex: QUANTITY - Hexadecimal of the transactions index position in the block. null when its pending.
  • value: QUANTITY - Value transferred in Wei.
  • v: QUANTITY - ECDSA recovery id
  • r: QUANTITY - ECDSA signature r
  • s: QUANTITY - ECDSA signature s

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getTransactionByBlockHashAndIndex","params":[
        "0x3874ed915e305246b75c0d388827f2d23df451ac901c07618154bcf03e5ac519",
        "0x0"]
        ,"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": {
    "hash": "0xadbfc093b90e69b6fdf3a843f03ef65c5c247e0593d3853b53d37c825e81339a",
    "nonce": "0x2bd",
    "blockHash": "0x3874ed915e305246b75c0d388827f2d23df451ac901c07618154bcf03e5ac519",
    "blockNumber": "0x608225",
    "transactionIndex": "0x0",
    "from": "0x3da87b1c3743bd2da60df2ef1bc6f26ef9da6086",
    "to": "0x5eb15fedcca9cb9c5424167e329fd56905ffe0e3",
    "value": "0x8203404a3db680000",
    "gasPrice": "0xb2d05e07",
    "gas": "0x29810",
    "input": "0x7ff36ab5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000003da87b1c3743bd2da60df2ef1bc6f26ef9da608600000000000000000000000000000000000000000000000000000000667b16b7000000000000000000000000000000000000000000000000000000000000000200000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5000000000000000000000000a2c07c15173c183771ffad40c2e972f97e0bce64",
    "r": "0xd527419716bc6eb25e74420a7a28e6f3793ead979c8ca6ed8b1b27245470c83d",
    "s": "0x6bef1d101db08d829080d43b823a4896e5971f5794aaef508ea2cffea52ef9c1",
    "v": "0xfe5",
    "chainId": "0x7e1",
    "type": "0x0"
  },
  "id": 1
}

source

eth_getTransactionByBlockNumberAndIndex

Parameters

QUANTITY|TAG - Hexadecimal block number, or the string "earliest", "latest", "safe" or "finalized", as in the default block parameter.

QUANTITY - Hexadecimal of the transaction index position.

Returns

Object - A transaction object, or null when no transaction was found:

  • blockHash: DATA, 32 Bytes - Hash of the block where this transaction was in. null when its pending.
  • blockNumber: QUANTITY - Hexadecimal Block number where this transaction was in. null when its pending.
  • from: DATA, 20 Bytes - Address of the sender.
  • gas: QUANTITY - Hexadecimal gas provided by the sender.
  • gasPrice: QUANTITY - Hexadecimal gas price provided by the sender in Wei.
  • hash: DATA, 32 Bytes - Hash of the transaction.
  • input: DATA - The data send along with the transaction.
  • nonce: QUANTITY - Hexadecimal number of transactions made by the sender prior to this one.
  • to: DATA, 20 Bytes - Address of the receiver. null when its a contract creation transaction.
  • transactionIndex: QUANTITY - Hexadecimal of the transaction’s index position in the block. null when its pending.
  • value: QUANTITY - Hexadecimal value transferred in Wei.
  • v: QUANTITY - ECDSA recovery id
  • r: QUANTITY - ECDSA signature r
  • s: QUANTITY - ECDSA signature s

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getTransactionByBlockNumberAndIndex","params":[
        "0x6038C1",
        "0x0"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": {
    "hash": "0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d",
    "nonce": "0x2ba",
    "blockHash": "0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb",
    "blockNumber": "0x6038c1",
    "transactionIndex": "0x0",
    "from": "0x3da87b1c3743bd2da60df2ef1bc6f26ef9da6086",
    "to": "0x5eb15fedcca9cb9c5424167e329fd56905ffe0e3",
    "value": "0x121782564b00000",
    "gasPrice": "0xb2d05e07",
    "gas": "0x29810",
    "input": "0x7ff36ab5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000003da87b1c3743bd2da60df2ef1bc6f26ef9da608600000000000000000000000000000000000000000000000000000000667ab007000000000000000000000000000000000000000000000000000000000000000200000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5000000000000000000000000a2c07c15173c183771ffad40c2e972f97e0bce64",
    "r": "0xfe383e41c568fdfd569c910598b51654e0869cb9a01fe6867c6281aa2ef070b1",
    "s": "0xac20158ff00add9d35d448249b48a35b5bb04a87fb39dccc05bf653ef0aa7",
    "v": "0xfe5",
    "chainId": "0x7e1",
    "type": "0x0"
  },
  "id": 1
}

source

eth_getTransactionByHash

Parameters

DATA, 32 Bytes - Hash of a transaction

Returns

Object - A transaction object, or null when no transaction was found:

  • blockHash: DATA, 32 Bytes - Hash of the block where this transaction was in. null when its pending.
  • blockNumber: QUANTITY - Hexadecimal block number where this transaction was in. null when its pending.
  • from: DATA, 20 Bytes - Address of the sender.
  • gas: QUANTITY - Hexadecimal of the gas provided by the sender.
  • gasPrice: QUANTITY - Hexadecimal of the gas price provided by the sender in Wei.
  • hash: DATA, 32 Bytes - hash of the transaction.
  • input: DATA - the data send along with the transaction.
  • nonce: QUANTITY - Hexadecimal number of transactions made by the sender prior to this one.
  • to: DATA, 20 Bytes - address of the receiver. null when its a contract creation transaction.
  • transactionIndex: QUANTITY - Hexadecimal of the transaction’s index position in the block. null when its pending.
  • value: QUANTITY - Hexadecimal value transferred in Wei.
  • v: QUANTITY - ECDSA recovery id
  • r: QUANTITY - ECDSA signature r
  • s: QUANTITY - ECDSA signature s

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": {
    "hash": "0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d",
    "nonce": "0x2ba",
    "blockHash": "0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb",
    "blockNumber": "0x6038c1",
    "transactionIndex": "0x0",
    "from": "0x3da87b1c3743bd2da60df2ef1bc6f26ef9da6086",
    "to": "0x5eb15fedcca9cb9c5424167e329fd56905ffe0e3",
    "value": "0x121782564b00000",
    "gasPrice": "0xb2d05e07",
    "gas": "0x29810",
    "input": "0x7ff36ab5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000003da87b1c3743bd2da60df2ef1bc6f26ef9da608600000000000000000000000000000000000000000000000000000000667ab007000000000000000000000000000000000000000000000000000000000000000200000000000000000000000080d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5000000000000000000000000a2c07c15173c183771ffad40c2e972f97e0bce64",
    "r": "0xfe383e41c568fdfd569c910598b51654e0869cb9a01fe6867c6281aa2ef070b1",
    "s": "0xac20158ff00add9d35d448249b48a35b5bb04a87fb39dccc05bf653ef0aa7",
    "v": "0xfe5",
    "chainId": "0x7e1",
    "type": "0x0"
  },
  "id": 1
}

source

eth_getTransactionCount

Parameters

DATA, 20 Bytes - Address.

QUANTITY|TAG -Hexadecimal block number, or the string "latest", "earliest", "safe" or "finalized", see the default block parameter

Returns

QUANTITY - Hexadecimal of the number of transactions send from this address.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":[
        "0x3da87b1c3743bd2da60df2ef1bc6f26ef9da6086",
        "latest"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": "0x2c3", //707
  "id": 1
}

source

eth_getTransactionReceipt

Parameters

DATA, 32 Bytes - Hash of a transaction

Returns

Object - A transaction receipt object, or null when no receipt was found:

  • transactionHash: DATA, 32 Bytes - Hash of the transaction.
  • transactionIndex: QUANTITY - Hexadecimal of the transactions index position in the block.
  • blockHash: DATA, 32 Bytes - Hash of the block where this transaction was in.
  • blockNumber: QUANTITY - Hexadecimal block number where this transaction was in.
  • from: DATA, 20 Bytes - Address of the sender.
  • to: DATA, 20 Bytes - Address of the receiver. null when its a contract creation transaction.
  • cumulativeGasUsed : QUANTITY - Hexadecimal of the total amount of gas used when this transaction was executed in the block.
  • effectiveGasPrice : QUANTITY - Hexadecimal of the sum of the base fee and tip paid per unit of gas.
  • gasUsed : QUANTITY - Hexadecimal of the amount of gas used by this specific transaction alone.
  • contractAddress : DATA, 20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null.
  • logs: Array - Array of log objects, which this transaction generated.
  • logsBloom: DATA, 256 Bytes - Bloom filter for light clients to quickly retrieve related logs.
  • type: QUANTITY - Hexadecimal of the transaction type, 0x0 for legacy transactions, 0x1 for access list types, 0x2 for dynamic fees.

It also returns either :

  • root : DATA 32 bytes of post-transaction stateroot (pre Byzantium)
  • status: QUANTITY either 0x1(success) or 0x0 (failure)

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": {
    "transactionHash": "0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d",
    "transactionIndex": "0x0",
    "blockHash": "0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb",
    "blockNumber": "0x6038c1",
    "cumulativeGasUsed": "0x1a771",
    "gasUsed": "0x1a771",
    "effectiveGasPrice": "0xb2d05e07",
    "from": "0x3da87b1c3743bd2da60df2ef1bc6f26ef9da6086",
    "to": "0x5eb15fedcca9cb9c5424167e329fd56905ffe0e3",
    "contractAddress": null,
    "logs": [
      {
        "address": "0x80d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5",
        "topics": [
          "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
          "0x0000000000000000000000000000000000000000000000000000000000000000",
          "0x0000000000000000000000005eb15fedcca9cb9c5424167e329fd56905ffe0e3"
        ],
        "data": "0x0000000000000000000000000000000000000000000000000121782564b00000",
        "blockHash": "0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb",
        "blockNumber": "0x6038c1",
        "transactionHash": "0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d",
        "transactionIndex": "0x0",
        "logIndex": "0x0",
        "removed": false
      },
      {
        "address": "0x80d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5",
        "topics": [
          "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
          "0x0000000000000000000000005eb15fedcca9cb9c5424167e329fd56905ffe0e3",
          "0x000000000000000000000000e2005c7718f3849f39c65b8ea93c7a7030aec463"
        ],
        "data": "0x0000000000000000000000000000000000000000000000000121782564b00000",
        "blockHash": "0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb",
        "blockNumber": "0x6038c1",
        "transactionHash": "0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d",
        "transactionIndex": "0x0",
        "logIndex": "0x1",
        "removed": false
      },
      {
        "address": "0xa2c07c15173c183771ffad40c2e972f97e0bce64",
        "topics": [
          "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
          "0x000000000000000000000000e2005c7718f3849f39c65b8ea93c7a7030aec463",
          "0x0000000000000000000000003da87b1c3743bd2da60df2ef1bc6f26ef9da6086"
        ],
        "data": "0x0000000000000000000000000000000000000000000000000000bf80c3c72242",
        "blockHash": "0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb",
        "blockNumber": "0x6038c1",
        "transactionHash": "0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d",
        "transactionIndex": "0x0",
        "logIndex": "0x2",
        "removed": false
      },
      {
        "address": "0xe2005c7718f3849f39c65b8ea93c7a7030aec463",
        "topics": [
          "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"
        ],
        "data": "0x0000000000000000000000000000000000000000000000d4b594d815912d0b3e0000000000000000000000000000000000000000000000008d2445e5c1532614",
        "blockHash": "0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb",
        "blockNumber": "0x6038c1",
        "transactionHash": "0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d",
        "transactionIndex": "0x0",
        "logIndex": "0x3",
        "removed": false
      },
      {
        "address": "0xe2005c7718f3849f39c65b8ea93c7a7030aec463",
        "topics": [
          "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822",
          "0x0000000000000000000000005eb15fedcca9cb9c5424167e329fd56905ffe0e3",
          "0x0000000000000000000000003da87b1c3743bd2da60df2ef1bc6f26ef9da6086"
        ],
        "data": "0x0000000000000000000000000000000000000000000000000121782564b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bf80c3c72242",
        "blockHash": "0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb",
        "blockNumber": "0x6038c1",
        "transactionHash": "0xbd3a14fcf09f3f9e5ca951f2bfedbc7237fe1cd0d68673231f1333eed801830d",
        "transactionIndex": "0x0",
        "logIndex": "0x4",
        "removed": false
      }
    ],
    "logsBloom": "0x00200000040000000000000280000028000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000008000000200000000000000000000000000000000400000000020000000010000002000800000000000000000000000010000000400000000000000000000000000000000000000000000000080000004000000000000100000000000000000000000040200100000000000000000020000000000000000002000000000000200000000000000000000000001000000000000020000080000000020000000000000000000000000000000000000001000000000000",
    "status": "0x1",
    "type": "0x0"
  },
  "id": 1
}

source

eth_getUncleByBlockHashAndIndex

Telcoin Network’s consensus produces no uncle blocks, so this method always returns null. It is served for Ethereum API compatibility.

Parameters

DATA, 32 Bytes - Hash of a block.

QUANTITY - Hexadecimal of the uncle’s index position.

Returns

Object - A block object, or null when no block was found:

  • number: QUANTITY - Hexadecimal of the block number. null when its pending block.
  • hash: DATA, 32 Bytes - Hash of the block. null when its pending block.
  • parentHash: DATA, 32 Bytes - Hash of the parent block.
  • nonce: DATA, 8 Bytes - Hash of the generated proof-of-work. null when its pending block.
  • sha3Uncles: DATA, 32 Bytes - SHA3 of the uncles data in the block.
  • logsBloom: DATA, 256 Bytes - The bloom filter for the logs of the block. null when its pending block.
  • transactionsRoot: DATA, 32 Bytes - The root of the transaction trie of the block.
  • stateRoot: DATA, 32 Bytes - The root of the final state trie of the block.
  • receiptsRoot: DATA, 32 Bytes - The root of the receipts trie of the block.
  • miner: DATA, 20 Bytes - The address of the beneficiary to whom the mining rewards were given.
  • difficulty: QUANTITY - Hexadecimal of the difficulty for this block.
  • totalDifficulty: QUANTITY - Hexadecimal of the total difficulty of the chain until this block.
  • extraData: DATA - The “extra data” field of this block.
  • size: QUANTITY - Hexadecimal size of this block in bytes.
  • gasLimit: QUANTITY - Hexadecimal of the maximum gas allowed in this block.
  • gasUsed: QUANTITY - Hexadecimal of the total used gas by all transactions in this block.
  • timestamp: QUANTITY - Hexadecimal of the unix timestamp for when the block was collated.
  • transactions: Array - Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter.
  • uncles: Array - Array of uncle hashes.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getUncleByBlockHashAndIndex","params":["0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb", "0x0"],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": null
}

source

eth_getUncleByBlockNumberAndIndex

Telcoin Network’s consensus produces no uncle blocks, so this method always returns null. It is served for Ethereum API compatibility.

Parameters

QUANTITY|TAG - Hexadecimal of a block number, or the string "earliest", "latest", "pending", "safe", "finalized", as in the default block parameter.

QUANTITY - Hexadecimal of the uncle’s index position.

Returns

Object - A block object, or null when no block was found:

  • number: QUANTITY - Hexadecimal of the block number. null when its pending block.
  • hash: DATA, 32 Bytes - Hash of the block. null when its pending block.
  • parentHash: DATA, 32 Bytes - Hash of the parent block.
  • nonce: DATA, 8 Bytes - Hash of the generated proof-of-work. null when its pending block.
  • sha3Uncles: DATA, 32 Bytes - SHA3 of the uncles data in the block.
  • logsBloom: DATA, 256 Bytes - The bloom filter for the logs of the block. null when its pending block.
  • transactionsRoot: DATA, 32 Bytes - The root of the transaction trie of the block.
  • stateRoot: DATA, 32 Bytes - The root of the final state trie of the block.
  • receiptsRoot: DATA, 32 Bytes - The root of the receipts trie of the block.
  • miner: DATA, 20 Bytes - The address of the beneficiary to whom the mining rewards were given.
  • difficulty: QUANTITY - Hexadecimal of the difficulty for this block.
  • totalDifficulty: QUANTITY - Hexadecimal of the total difficulty of the chain until this block.
  • extraData: DATA - The “extra data” field of this block.
  • size: QUANTITY - Hexadecimal of the size of this block in bytes.
  • gasLimit: QUANTITY - Hexadecimal of the maximum gas allowed in this block.
  • gasUsed: QUANTITY - Hexadecimal of the total used gas by all transactions in this block.
  • timestamp: QUANTITY - Hexadecimal of the unix timestamp for when the block was collated.
  • transactions: Array - Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter.
  • uncles: Array - Array of uncle hashes.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getUncleByBlockNumberAndIndex","params":["0x29c", "0x0"],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": null
}

source

eth_getUncleCountByBlockHash

Telcoin Network’s consensus produces no uncle blocks, so this method returns 0x0 for every existing block (null when the block is not found). It is served for Ethereum API compatibility.

Parameters

DATA, 32 Bytes - Hash of a block

Returns

QUANTITY - Hexadecimal of the number of uncles in this block.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getUncleCountByBlockHash","params":["0x908539f74911930cb0e7201a43dbcce8743a58f1d49e2bc271159fab3c6cb8fb"],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0x0"
}

source

eth_getUncleCountByBlockNumber

Telcoin Network’s consensus produces no uncle blocks, so this method returns 0x0 for every existing block (null when the block is not found). It is served for Ethereum API compatibility.

Parameters

QUANTITY|TAG - Hexadecimal of a block number, or the string "latest", "earliest", "safe" or "finalized", see the default block parameter

Returns

QUANTITY - Hexadecimal of the number of uncles in this block.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getUncleCountByBlockNumber","params":["0x6038C1"],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0x0"
}

source

eth_maxPriorityFeePerGas

Parameters

None

Returns

QUANTITY - Hexadecimal of the priority fee, in wei, needed to be included in a block.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_maxPriorityFeePerGas","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": "0x3b9aca00",
  "id": 1
}

source

eth_protocolVersion

Parameters

None

Returns

QUANTITY - Hexadecimal of the current Telcoin Network protocol version. Telcoin Network nodes currently report protocol version 1.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_protocolVersion","params":[],"id":67}'

Result

{
  "id":67,
  "jsonrpc": "2.0",
  "result": "0x1"
}

source

eth_sendRawTransaction

Submits a pre-signed transaction to the network. This is the only way to send a transaction through a Telcoin Network node: nodes hold no user keys, so transactions must be signed client-side and submitted as raw bytes.

A node that is not part of the current committee accepts the transaction into its local pool and then forwards it to a committee validator’s advertised RPC endpoint on your behalf. Forwarding is best-effort and routes by sender address, so all of one account’s transactions converge on the same validator and nonce ordering is preserved. Submitting to any Telcoin Network node therefore works the same way.

Telcoin Network nodes additionally guard this method with a transaction fee cap (--rpc.txfeecap, in TEL; 0 disables the cap and is the default). When an operator sets a cap, the transaction’s maximum possible fee - maxFeePerGas * gasLimit, plus the blob fee bound for EIP-4844 transactions - must not exceed it. An over-cap transaction is rejected before it reaches the pool with a JSON-RPC error (code -32000) of the form:

tx fee (7000000 wei) exceeds the configured cap (200000 wei)

Parameters

DATA, The signed transaction data.

Returns

DATA, 32 Bytes - The transaction hash.

Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"
}

source

eth_sendTransaction

Warning

Not usable on Telcoin Network

The eth_sendTransaction JSON-RPC method asks the node to sign the transaction with the from account’s key. Telcoin Network nodes do not store user private keys and configure no RPC signing accounts, so every call to this method fails with the unknown account error shown below.

Sign transactions client-side and submit them with eth_sendRawTransaction instead.

Parameters

Object - The transaction object

  • from: DATA, 20 Bytes - The address the transaction is sent from.
  • to: DATA, 20 Bytes - (optional when creating new contract) The address the transaction is directed to.
  • gas: QUANTITY - (optional, default: 90000) Integer of the gas provided for the transaction execution. It will return unused gas.
  • gasPrice: QUANTITY - (optional, default: To-Be-Determined) Hexadecimal of the gasPrice used for each paid gas.
  • value: QUANTITY - (optional) Hexadecimal of the value sent with this transaction.
  • input: DATA - The compiled code of a contract OR the hash of the invoked method signature and encoded parameters.
  • nonce: QUANTITY - (optional) Hexadecimal nonce value. This allows to overwrite your own pending transactions that use the same nonce.

Returns

On Telcoin Network this method never returns a transaction hash. Because the node has no signer for any from address, every call returns a JSON-RPC error (code -32602) with the message unknown account.

Use eth_sendRawTransaction to submit a transaction signed client-side, and eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{
    "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
    "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
    "gas": "0x76c0",
    "gasPrice": "0x9184e72a000",
    "value": "0x9184e72a",
    "input": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
  }],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "error": {
    "code": -32602,
    "message": "unknown account"
  }
}

source

eth_sign

Warning

Not usable on Telcoin Network

The eth_sign JSON-RPC method asks the node to sign a message with the given account’s key. Telcoin Network nodes do not store user private keys and configure no RPC signing accounts, so every call to this method fails with the unknown account error shown below.

Sign messages client-side with your own wallet or key management instead.

Parameters

DATA, 20 Bytes - Address

DATA, N Bytes - Message to sign

Returns

On Telcoin Network this method never returns a signature. Because the node has no signer for any address, every call returns a JSON-RPC error (code -32602) with the message unknown account.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_sign","params":["0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", "0xdeadbeaf"],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "error": {
    "code": -32602,
    "message": "unknown account"
  }
}

source

eth_syncing

Parameters

None

Returns

Boolean - Telcoin Network nodes always return false.

Standard Ethereum clients return either a sync-status object (startingBlock, currentBlock, highestBlock) or false when not syncing. Telcoin Network nodes execute consensus output directly and always report false here, even while a node is still catching up to the rest of the network.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": false
}

source

Filter Methods

Telcoin Network supports the following filter methods over HTTP. Filters are held in the memory of the node that created them, so subsequent polls must go to the same node.

Filters that are not polled using eth_getFilterChanges will be automatically expired after five minutes of inactivity.

eth_getFilterChanges

Polling method for a filter, which returns an array of logs which occurred since last poll. Filter must be created by calling either eth_newFilter or eth_newBlockFilter.

Parameters

QUANTITY - the filter id.

Returns

Array of DATA - Array of log objects, or an empty array if nothing has changed since last poll.

For filters created with eth_newBlockFilter the return are block hashes (DATA, 32 Bytes), e.g. ["0x3454645634534..."].

For filters created with eth_newPendingTransactionFilter the return are transaction hashes (DATA, 32 Bytes), e.g. ["0x6345343454645..."].

For filters created with eth_newFilter logs are objects with following params:

  • removed: TAG - true when the log was removed, due to a chain reorganization. false if its a valid log.
  • logIndex: QUANTITY - Hexadecimal of the log index position in the block. null when its pending log.
  • transactionIndex: QUANTITY - Hexadecimal of the transaction’s index position log was created from. null when its pending log.
  • transactionHash: DATA, 32 Bytes - Hash of the transactions this log was created from. null when its pending log.
  • blockHash: DATA, 32 Bytes - Hash of the block where this log was in. null when its pending. null when its pending log.
  • blockNumber: QUANTITY - Hexadecimal of the block number this log was in. null when its pending. null when its pending log.
  • address: DATA, 20 Bytes - Address from which this log originated.
  • data: DATA - Contains zero or more 32 Bytes non-indexed arguments of the log.
  • topics: Array of DATA - Array of 0 to 4 32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.)

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getFilterChanges","params":["0x26c221936b2025a44f60294273d7bbaa"],"id":73}'

Result

{
"jsonrpc":"2.0",
"result":[
    {"address":"0x80d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5",
    "topics":[
        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x0000000000000000000000000000000000000000000000000000000000000000",
        "0x0000000000000000000000005eb15fedcca9cb9c5424167e329fd56905ffe0e3"],
    "data":"0x000000000000000000000000000000000000000000000002f1cd8944b8700000",
    "blockHash":"0x59380d589f6d0f833518978956b81aa5248a497a73fb8ea1bf8a08b6abc6656b",
    "blockNumber":"0x60387d",
    "transactionHash":"0x0c52053dbf5ad86686ac00cc3858726967c33ab5eddaa889d306663f8610d659",
    "transactionIndex":"0x0",
    "logIndex":"0x0",
    "removed":false},
    {"address":
    "0x80d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5",
    "topics":[
        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x0000000000000000000000005eb15fedcca9cb9c5424167e329fd56905ffe0e3",
        "0x000000000000000000000000e2005c7718f3849f39c65b8ea93c7a7030aec463"],
    "data":"0x000000000000000000000000000000000000000000000002f1cd8944b8700000",
    "blockHash":"0x59380d589f6d0f833518978956b81aa5248a497a73fb8ea1bf8a08b6abc6656b",
    "blockNumber":"0x60387d",
    "transactionHash":"0x0c52053dbf5ad86686ac00cc3858726967c33ab5eddaa889d306663f8610d659",
    "transactionIndex":"0x0",
    "logIndex":"0x1",
    "removed":false},
    {"address":"0xa2c07c15173c183771ffad40c2e972f97e0bce64",
    "topics":[
        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x000000000000000000000000e2005c7718f3849f39c65b8ea93c7a7030aec463",
        "0x0000000000000000000000003da87b1c3743bd2da60df2ef1bc6f26ef9da6086"],
    "data":"0x00000000000000000000000000000000000000000000000001f9b3795d77f3a5",
    "blockHash":"0x59380d589f6d0f833518978956b81aa5248a497a73fb8ea1bf8a08b6abc6656b",
    "blockNumber":"0x60387d",
    "transactionHash":"0x0c52053dbf5ad86686ac00cc3858726967c33ab5eddaa889d306663f8610d659",
    "transactionIndex":"0x0",
    "logIndex":"0x2",
    "removed":false}],
    "id":73
    }

source

eth_getFilterLogs

Parameters

QUANTITY - The filter id.

Returns

ArrayofDATA - Array of log objects, or an empty array if nothing has changed since last poll.

For filters created with eth_newBlockFilter the return are block hashes (DATA, 32 Bytes), e.g. ["0x3454645634534..."].

For filters created with eth_newPendingTransactionFilter the return are transaction hashes (DATA, 32 Bytes), e.g. ["0x6345343454645..."].

For filters created with eth_newFilter logs are objects with following params:

  • removed: TAG - true when the log was removed, due to a chain reorganization. false if its a valid log.
  • logIndex: QUANTITY - Hexadecimal of the log index position in the block. null when its pending log.
  • transactionIndex: QUANTITY - Hexadecimal of the transactions index position log was created from. null when its pending log.
  • transactionHash: DATA, 32 Bytes - Hash of the transactions this log was created from. null when its pending log.
  • blockHash: DATA, 32 Bytes - Hash of the block where this log was in. null when its pending. null when its pending log.
  • blockNumber: QUANTITY - Hexadecimal of the block number where this log was in. null when its pending. null when its pending log.
  • address: DATA, 20 Bytes - Address from which this log originated.
  • data: DATA - Contains zero or more 32 Bytes non-indexed arguments of the log.
  • topics: Array of DATA - Array of 0 to 4 32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.)

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_getFilterLogs","params":["0x26c221936b2025a44f60294273d7bbaa"],"id":1}'

Result

{
"jsonrpc":"2.0",
"result":[
    {"address":"0x80d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5",
    "topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x0000000000000000000000000000000000000000000000000000000000000000",
        "0x0000000000000000000000005eb15fedcca9cb9c5424167e329fd56905ffe0e3"],
    "data":"0x000000000000000000000000000000000000000000000002f1cd8944b8700000",
    "blockHash":"0x59380d589f6d0f833518978956b81aa5248a497a73fb8ea1bf8a08b6abc6656b",
    "blockNumber":"0x60387d",
    "transactionHash":"0x0c52053dbf5ad86686ac00cc3858726967c33ab5eddaa889d306663f8610d659",
    "transactionIndex":"0x0",
    "logIndex":"0x0",
    "removed":false},
    {"address":"0x80d1f6dafc9c13e9d19aedf75e3c1e2586d4a2a5",
    "topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x0000000000000000000000005eb15fedcca9cb9c5424167e329fd56905ffe0e3",
        "0x000000000000000000000000e2005c7718f3849f39c65b8ea93c7a7030aec463"],
    "data":"0x000000000000000000000000000000000000000000000002f1cd8944b8700000",
    "blockHash":"0x59380d589f6d0f833518978956b81aa5248a497a73fb8ea1bf8a08b6abc6656b",
    "blockNumber":"0x60387d",
    "transactionHash":"0x0c52053dbf5ad86686ac00cc3858726967c33ab5eddaa889d306663f8610d659",
    "transactionIndex":"0x0",
    "logIndex":"0x1",
    "removed":false},
    {"address":"0xa2c07c15173c183771ffad40c2e972f97e0bce64",
    "topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x000000000000000000000000e2005c7718f3849f39c65b8ea93c7a7030aec463",
        "0x0000000000000000000000003da87b1c3743bd2da60df2ef1bc6f26ef9da6086"],
    "data":"0x00000000000000000000000000000000000000000000000001f9b3795d77f3a5",
    "blockHash":"0x59380d589f6d0f833518978956b81aa5248a497a73fb8ea1bf8a08b6abc6656b",
    "blockNumber":"0x60387d",
    "transactionHash":"0x0c52053dbf5ad86686ac00cc3858726967c33ab5eddaa889d306663f8610d659",
    "transactionIndex":"0x0",
    "logIndex":"0x2",
    "removed":false}],
    "id":1
    }

source

eth_newBlockFilter

Parameters

None

Returns

DATA - A filter id.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_newBlockFilter","params":[],"id":73}'

Result

{
  "jsonrpc": "2.0",
  "result": "0x6f3ccb09d7153d2bba6b59f8f0ede3b1",
  "id": 73
}

source

eth_newFilter

Creates a filter object, based on filter options, to notify when the state changes (logs). To check if the state has changed, call eth_getFilterChanges.

A note on specifying topic filters: Topics are order-dependent. A transaction with a log with topics [A, B] will be matched by the following topic filters:

  • [] “anything”
  • [A] “A in first position (and anything after)”
  • [null, B] “anything in first position AND B in second position (and anything after)”
  • [A, B] “A in first position AND B in second position (and anything after)”
  • [[A, B], [A, B]] “(A OR B) in first position AND (A OR B) in second position (and anything after)”

Parameters

Object - The filter options:

  • fromBlock: QUANTITY|TAG - (optional, default: "latest") Hexadecimal of a block number, or "latest" for the last mined block, "safe" for the latest safe block, "finalized" for the latest finalized block, or "earliest" for not yet mined transactions.
  • toBlock: QUANTITY|TAG - (optional, default: "latest") Hexadecimal of a block number, or "latest" for the last mined block, "safe" for the latest safe block, "finalized" for the latest finalized block, or "earliest" for not yet mined transactions.
  • address: DATA|Array, 20 Bytes - (optional) Contract address or a list of addresses from which logs should originate.
  • topics: Array of DATA, - (optional) Array of 32 Bytes DATA topics. Topics are order-dependent. Each topic can also be an array of DATA with “or” options.

Returns

DATA - A filter id.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{
      "jsonrpc": "2.0","method": "eth_newFilter","params": [
        {
          "fromBlock": "0x60387D",
          "toBlock": "0x60387E",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
          ]
        }
      ],
      "id": 1
    }'

Result

{
  "jsonrpc": "2.0",
  "result": "0x26c221936b2025a44f60294273d7bbaa",
  "id": 1
}

source

eth_newPendingTransactionFilter

Creates a filter in the node, to notify when new pending transactions arrive. To check if the state has changed, call eth_getFilterChanges.

Parameters

None

Returns

DATA - A filter id.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_newPendingTransactionFilter","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "result": "0x38d257a1aab91606194ba7211d4ce26",
  "id": 1
}

source

eth_uninstallFilter

Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additionally Filters timeout when they aren’t requested with eth_getFilterChanges for a period of time.

Parameters

DATA - The filter id.

Returns

Boolean - true if the filter was successfully uninstalled, otherwise false.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"eth_uninstallFilter","params":["0x26c221936b2025a44f60294273d7bbaa"],"id":73}'

Result

{
  "jsonrpc": "2.0",
  "result": true,
  "id": 73
}

source

net

The net namespace reports basic facts about the node’s network: the chain id, whether the node is listening, and how many peers it is connected to. It is on by default and costs little to serve, since every answer comes from memory. Every node role can serve it, including a validator’s minimal eth,net,web3 selection; see Enabling Namespaces for the flags.

Telcoin Network does not run devp2p, so two methods differ from Ethereum clients: net_peerCount counts the peers connected to the node’s worker network, and net_listening always returns true.

Example values are illustrative; field names and types match what the node returns.

MethodDescription
net_listeningAlways true
net_peerCountNumber of peers connected to the node’s worker network, refreshed every 15 seconds
net_versionChain id as a decimal string

net_listening

On Telcoin Network this method always returns true, whether or not the node has any peers. Use net_peerCount to see how many peers the node is connected to.

Parameters

None

Returns

Boolean - true when listening, otherwise false.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"net_listening","params":[],"id":67}'

Result

{
    "id":67,
    "jsonrpc":"2.0",
    "result":true
}

source

net_peerCount

On Telcoin Network this is the number of peers connected to the node’s worker network, not devp2p peers: Telcoin Network does not run devp2p. The node refreshes the count every 15 seconds, so a new or dropped connection can take up to 15 seconds to show, and the count reads 0x0 while the node is starting up, until the worker network is running.

Parameters

None

Returns

QUANTITY - integer of the number of connected peers.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}'

Result

{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0x3" //3
}

source

net_version

On Telcoin Network this is the chain id as a decimal string: "2017" on adiri. It is the same number eth_chainId returns in hex (0x7e1).

Parameters

None

Returns

String - The chain id in decimal.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "2017"
}

source

web3

The web3 namespace has two methods: one returns the node’s version string, the other hashes data with Keccak-256. It is on by default and costs little to serve. Every node role can serve it, including a validator’s minimal eth,net,web3 selection; see Enabling Namespaces for the flags.

On Telcoin Network web3_clientVersion returns the Telcoin Network release and commit the node was built from, not a geth or reth client string.

Example values are illustrative; field names and types match what the node returns.

MethodDescription
web3_clientVersionThe node’s Telcoin Network version and commit sha
web3_sha3Keccak-256 hash of the given data

web3_clientVersion

On Telcoin Network this returns the node’s Telcoin Network version string, <crate version> (<git commit sha>), not a geth or reth client string. A node built from the repository’s Docker image, as adiri is, reports the full 40-character commit sha, as in the example below; a binary built with cargo from a git checkout reports the short sha.

Parameters

None

Returns

String - The node’s version string.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0.1.0 (5736cc30012c5ff25913898e318a74df308f13d9)"
}

source

web3_sha3

Parameters

DATA - The data to hash.

Returns

DATA, 32 Bytes - The Keccak-256 hash of the data. This is the hash the EVM’s KECCAK256 opcode computes, not the standardized SHA3-256.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad" // keccak256("hello world")
}

source

rpc

The rpc namespace has one method, rpc_modules, which lists the namespaces the node serves. It is on by default and costs almost nothing to serve, since the node builds the answer once when the RPC server starts. Public RPC nodes and archive nodes should keep it so clients and operators can check what a node serves; a validator’s minimal eth,net,web3 selection leaves it out. See Enabling Namespaces for the flags.

Example values are illustrative; field names and types match what the node returns.

MethodDescription
rpc_modulesThe namespaces the node serves, each with the version "1.0"

rpc_modules

Lists the namespaces the node serves. Each value is the fixed string "1.0"; it does not track any namespace’s version.

This method answers only on a transport whose selection includes rpc. The default set (eth, net, web3, rpc, tn) includes it, so a node started without --http.api or --ws.api, or with all, serves it. An explicit list that leaves rpc out, such as eth,net,web3, does not, and the method then returns the -32601 “Method not found” error. See Enabling Namespaces.

The node builds the rpc_modules answer once, for the first transport whose selection includes rpc, in the order HTTP, WS, IPC, and every transport then returns that list. When HTTP and WS both serve rpc with different selections, the answer on WS and IPC is HTTP’s list. Tracked in #1588.

Parameters

None

Returns

Object - One entry per namespace: the key is the namespace name and the value is the string "1.0".

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"rpc_modules","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "eth": "1.0",
    "net": "1.0",
    "rpc": "1.0",
    "tn": "1.0",
    "web3": "1.0"
  }
}

source

tn

The tn namespace serves Telcoin Network data that has no eth equivalent: the node’s identity and consensus participation mode, consensus headers and signed epoch records, the millisecond commit time of execution blocks, and validator, committee and staking state from the ConsensusRegistry system contract at 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1. Registry methods read the contract at the node’s canonical tip and take no block parameter.

tn is on by default: a node serves it unless --http.api or --ws.api names a list without it. The cost to serve it is low to moderate. Most methods answer from memory or the node’s database; registry methods run a read-only contract call against the latest state. The blocking work behind these methods is capped at 64 concurrent requests per RPC server, and requests beyond that queue rather than fail. Public RPC nodes (observers) should serve it; see Enabling Namespaces.

Warning

Keep tn off validators

Validators should not expose the tn namespace publicly. A tn_getBlockTimestampMillis request for a block from a sealed epoch can open that epoch’s consensus pack, and the node opens packs into a small cache it shares with state sync and peer epoch serving. The lookup runs under a tight concurrency bound, but public callers can still churn that cache.

Omit tn from the validator’s --http.api and --ws.api and serve the namespace from non-validating nodes instead; see Enabling Namespaces.

Errors use these codes: a registry call that reverts is reported as eth_call reports one, with code 3, a message that starts with execution reverted, and the ABI-encoded revert data in data; -32001 (Not Found.) means the node does not have the requested record; -32602 is an invalid parameter; -32603 (internal error) is a failure inside the node, whose detail is only logged on the node.

Example values are illustrative; field names and types match what the node returns.

MethodDescription
Node
tn_infoThe node’s publicly available identity and connection information: name, ids, public keys and network addresses
tn_nodeModeThe node’s current consensus participation mode, read live at call time
Consensus
tn_latestConsensusHeaderThe latest consensus header
tn_latestHeaderDeprecated alias for tn_latestConsensusHeader
tn_epochRecordThe epoch record and its certificate for an epoch number, from the node’s database
tn_epochRecordByHashThe epoch record and its certificate for an epoch record hash, from the node’s database
tn_getBlockTimestampMillisThe consensus commit time, in milliseconds, of an execution block
tn_genesisThe chain genesis
Epoch and registry
tn_getCurrentEpochThe current epoch number from the ConsensusRegistry
tn_getCurrentEpochInfoThe current epoch’s on-chain info from the ConsensusRegistry: committee, issuance, start block height and duration
tn_getEpochInfoOn-chain info for an epoch the ConsensusRegistry still holds in its window of recent epochs
tn_getNextCommitteeSizeThe committee size for the next epoch
Validators
tn_getValidatorsAll validators with the requested status; "Any" returns validators of every status
tn_getCommitteeValidatorsThe committee validators for an epoch
tn_getValidatorThe ValidatorInfo record for a validator address
tn_getBlsPubkeyThe BLS public key for a validator address
tn_getCommitteeBlsPubkeysThe BLS public keys for the committee of an epoch
tn_isValidatorWhether a BLS public key belongs to a known validator
tn_isDelegatedWhether a validator’s stake originates from a delegator
tn_isRetiredWhether a validator is permanently retired
Staking
tn_getRewardsThe claimable rewards accrued for a validator address
tn_getBalanceBreakdownA validator’s outstanding balance, initial stake and rewards
tn_getCurrentStakeVersionThe current stake config version
tn_undistributedIssuanceThe issuance not yet distributed to validators
tn_delegationDigestThe EIP-712 digest a validator signs to accept a delegation
tn_proofOfPossessionMessageThe BLS12-381 proof-of-possession message a validator signs

tn_delegationDigest

Returns the EIP-712 digest a validator signs to accept a delegation. The node computes it by calling the ConsensusRegistry contract’s delegationDigest view at the canonical tip; nothing is signed or submitted. The validator signs the digest, and the delegator passes that signature to delegateStake (see Delegated Staking).

Besides the four parameters, the digest commits to the current epoch’s stake version and to the validator’s delegation nonce. If the stake version changes at an epoch boundary, or the validator advances its nonce with increaseNonce(), before the delegation is submitted, the signature no longer verifies and a new digest is needed.

Parameters

DATA, 96 bytes - The validator’s compressed BLS12-381 public key. Any other length, including the 192-byte uncompressed encoding, reverts with the registry’s InvalidBLSPubkey() error.

DATA, 20 bytes - The validator’s execution address. It must hold a ConsensusNFT; otherwise the call reverts with the registry’s InvalidTokenId(uint256) error.

DATA, 20 bytes - The delegator’s address: the account that will send the delegateStake transaction.

QUANTITY - The deadline, a Unix timestamp in seconds. delegateStake rejects the signature once the block timestamp is past it. A JSON number is also accepted.

Returns

DATA, 32 bytes - The EIP-712 digest for the validator to sign.

A revert is reported as eth_call reports one: error code 3, a message that starts with execution reverted, and the ABI-encoded revert data in data.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_delegationDigest","params":["0xb1f5f47e63566357362e2157f1b6860df78c08e98f7f5df8e0e2cf837cdea6420c0698dc123f187ed170acfe5c138bf11202b69c3ce2c4f5c2de7fa58d993931164fc3011118c62f0fb1b9cf723f1e55a410a9f77e8cd41576c9e4f8d0adf8ba", "0x3518b301b86ceb53b5a3dff62e55cd43ef59d024", "0x1111111111111111111111111111111111111111", "0x6b49d200"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x556d539e57d044188fc1d7f4acf049df9037f59fb738985df77475199fb2fb28"
}

tn_epochRecord

Returns the epoch record for an epoch together with the certificate that signs it. Epoch records form the epoch chain: when an epoch ends, the record of that epoch fixes its committee, the committee chosen for the next epoch, and the last execution block and consensus header of the epoch, and the outgoing committee signs it at the start of the next epoch. Syncing nodes use the chain to learn each epoch’s committee without execution state; see Canonical Updates.

The record is read from the node’s consensus database, not from contract state. tn_getEpochInfo serves a different view of an epoch: the ConsensusRegistry contract’s entry, which the contract keeps only for a short ring buffer of recent epochs. A record exists only once its epoch has ended, so the current epoch has none. The method returns the error -32001 “Not Found.” when the node has no record for the epoch, or has the record but not yet a certificate for it. tn_epochRecordByHash looks a record up by its digest instead.

Parameters

Number - The epoch number, as a JSON number. A hex string is rejected with -32602 “Invalid params”.

Returns

Array - A two-element array: the epoch record object, then the epoch certificate object. Field names are snake_case, and consensus-layer keys, digests and signatures are base58 strings.

The epoch record:

  • epoch: Number - The epoch the record is for.
  • committee: Array - The BLS public keys of the epoch’s committee, base58-encoded (96-byte compressed form).
  • next_committee: Array - The BLS public keys of the committee chosen for the next epoch, in the same encoding.
  • parent_hash: String - Digest of the previous epoch’s record, 32 bytes, base58-encoded. All zero bytes (11111111111111111111111111111111) for epoch 0.
  • final_state: Object - The last execution block of the epoch: number (Number) and hash (DATA, 32 Bytes).
  • final_consensus: Object - The last consensus header of the epoch: number (Number) and hash (String, base58).

The epoch certificate:

  • epoch_hash: String - The digest of the record above, 32 bytes, base58-encoded. This is the key tn_epochRecordByHash takes.
  • signature: String - The aggregate BLS signature of the committee members that signed the record, base58-encoded.
  • signed_authorities: String - Which members of committee signed, as a bitmap of their positions in that list: a serialized roaring bitmap, base58-encoded. A valid certificate carries signatures from more than two thirds of the committee.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_epochRecord","params":[600],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "epoch": 600,
      "committee": [
        "pDmpE29YEhr93MPVPCGkWx3BsCcow4oExmxv5viJz2GTjsaXwc3YwKxE1CFdVuTVSudsFDutFSVmtgF98Abs56JZPfGQs6GzbqXDFkGA1eZx2edkwfP2Q6eRLo8coLhvTNj",
        "24JaKrebALhvhXxsAabjd5ZK22k1W4zKwGepKD5pcobQwBkZyiruYyhRM3hkLap8Ssc4qUdcRUJSrs2389hUoV7G83HKYrmkz2pTzCJiQURV5vtkrh8gFRKBQWKFTWLWah9s",
        "26L3gmkBzxAxEwvVuDrxEmKf2XwYGnYj1EQSiZSX3PBAvRa6Z5tegfmDAGHTsBwdtjFuAd7ayQEemE1AUE5ajSSK8wn3uz1MY5vjbGdeh3RoUv1TKLfbNTTULq9qdtaSTHhL",
        "rZdhyVGJAtz7kyFtFARWVVcw5DB3o9E4GvPN6zuKfUhUBGFRvEK2PgEV44CJt9wu9GMyssFQBFXRknrenuhJixo9KCADrZzHdtcvAwAYDmcLc7Ugh9epGc5LRZC5TK2hnYA",
        "26L1XwWTr6bBNnxJmDJXJgTqMAyD4ExkqwPYYJqDD3gob3jzejypQoewqRmqb9D2Yudk7zjcPqaTzFcEtXhrYXUtNx2ZYeLKC8V77LL2J136Hvw8eCGXpadGAbymThMhWYAa"
      ],
      "next_committee": [
        "pDmpE29YEhr93MPVPCGkWx3BsCcow4oExmxv5viJz2GTjsaXwc3YwKxE1CFdVuTVSudsFDutFSVmtgF98Abs56JZPfGQs6GzbqXDFkGA1eZx2edkwfP2Q6eRLo8coLhvTNj",
        "24JaKrebALhvhXxsAabjd5ZK22k1W4zKwGepKD5pcobQwBkZyiruYyhRM3hkLap8Ssc4qUdcRUJSrs2389hUoV7G83HKYrmkz2pTzCJiQURV5vtkrh8gFRKBQWKFTWLWah9s",
        "26L3gmkBzxAxEwvVuDrxEmKf2XwYGnYj1EQSiZSX3PBAvRa6Z5tegfmDAGHTsBwdtjFuAd7ayQEemE1AUE5ajSSK8wn3uz1MY5vjbGdeh3RoUv1TKLfbNTTULq9qdtaSTHhL",
        "rZdhyVGJAtz7kyFtFARWVVcw5DB3o9E4GvPN6zuKfUhUBGFRvEK2PgEV44CJt9wu9GMyssFQBFXRknrenuhJixo9KCADrZzHdtcvAwAYDmcLc7Ugh9epGc5LRZC5TK2hnYA",
        "26L1XwWTr6bBNnxJmDJXJgTqMAyD4ExkqwPYYJqDD3gob3jzejypQoewqRmqb9D2Yudk7zjcPqaTzFcEtXhrYXUtNx2ZYeLKC8V77LL2J136Hvw8eCGXpadGAbymThMhWYAa"
      ],
      "parent_hash": "EAXPstRk52BS7ggYetfzxDsC5oei4NL6rAtKcji4acUc",
      "final_state": {
        "number": 500810,
        "hash": "0x785da075f575faf3701e75214315e1aa7806dcc434b009673a3e76f7d21eec1d"
      },
      "final_consensus": {
        "number": 7056884,
        "hash": "2HqGsiq25eKZ8qRLb9Scx29TuZWE1r9LYtjouM5hnTxU"
      }
    },
    {
      "epoch_hash": "GDEpTMo1S4W2M1NajbaA6Vbf6AUndzWXTvR9J7RhEf7m",
      "signature": "7MsdyrYbUGbUXaaC2BjjtgR4PnP3swUQAgLEasfosvs4KFeCtLTTZCUGraKeHXJE7e",
      "signed_authorities": "2nLqe24nocoseuuBygc8VW6FW3oXreb3pseX"
    }
  ]
}

tn_epochRecordByHash

Returns the epoch record with the given digest together with the certificate that signs it. It answers like tn_epochRecord, which looks the record up by epoch number; use this method to follow the epoch chain from a record’s parent_hash or from a certificate’s epoch_hash.

The record is read from the node’s consensus database, not from contract state; tn_getEpochInfo serves the ConsensusRegistry contract’s entry for an epoch, which the contract keeps only for a short ring buffer of recent epochs. The method returns the error -32001 “Not Found.” when the node has no record with this digest, or has the record but not yet a certificate for it.

Parameters

String - The epoch record’s digest, 32 bytes, base58-encoded, as it appears in a certificate’s epoch_hash or the next record’s parent_hash. A 0x hex string is rejected with -32602 “Invalid params”.

Returns

Array - A two-element array: the epoch record object, then the epoch certificate object, with the fields described on tn_epochRecord.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_epochRecordByHash","params":["GDEpTMo1S4W2M1NajbaA6Vbf6AUndzWXTvR9J7RhEf7m"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "epoch": 600,
      "committee": [
        "pDmpE29YEhr93MPVPCGkWx3BsCcow4oExmxv5viJz2GTjsaXwc3YwKxE1CFdVuTVSudsFDutFSVmtgF98Abs56JZPfGQs6GzbqXDFkGA1eZx2edkwfP2Q6eRLo8coLhvTNj",
        // ... four more BLS public keys
      ],
      "next_committee": [
        "pDmpE29YEhr93MPVPCGkWx3BsCcow4oExmxv5viJz2GTjsaXwc3YwKxE1CFdVuTVSudsFDutFSVmtgF98Abs56JZPfGQs6GzbqXDFkGA1eZx2edkwfP2Q6eRLo8coLhvTNj",
        // ... four more BLS public keys
      ],
      "parent_hash": "EAXPstRk52BS7ggYetfzxDsC5oei4NL6rAtKcji4acUc",
      "final_state": {
        "number": 500810,
        "hash": "0x785da075f575faf3701e75214315e1aa7806dcc434b009673a3e76f7d21eec1d"
      },
      "final_consensus": {
        "number": 7056884,
        "hash": "2HqGsiq25eKZ8qRLb9Scx29TuZWE1r9LYtjouM5hnTxU"
      }
    },
    {
      "epoch_hash": "GDEpTMo1S4W2M1NajbaA6Vbf6AUndzWXTvR9J7RhEf7m",
      "signature": "7MsdyrYbUGbUXaaC2BjjtgR4PnP3swUQAgLEasfosvs4KFeCtLTTZCUGraKeHXJE7e",
      "signed_authorities": "2nLqe24nocoseuuBygc8VW6FW3oXreb3pseX"
    }
  ]
}

tn_genesis

Returns the genesis the node’s chain specification was built from: the chain configuration and the initial state, in the layout of an Ethereum genesis file. The initial state includes Telcoin Network’s system contracts, such as the ConsensusRegistry at 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1, with their bytecode and storage, so the response is large (about 140 KB on adiri).

Parameters

None

Returns

Object - The genesis object:

  • config: Object - The chain configuration: chainId and the block number or timestamp at which each Ethereum fork activates.
  • nonce: QUANTITY - The genesis header nonce.
  • timestamp: QUANTITY - The genesis header timestamp, in seconds since the Unix epoch.
  • extraData: DATA - The genesis header extra data.
  • gasLimit: QUANTITY - The genesis header gas limit.
  • difficulty: QUANTITY - The genesis header difficulty.
  • mixHash: DATA, 32 Bytes - The genesis header mix hash.
  • coinbase: DATA, 20 bytes - The genesis header beneficiary.
  • alloc: Object - The initial accounts, keyed by address. Each account has balance (QUANTITY, in wei) and, when the genesis sets them, nonce (QUANTITY), code (DATA) and storage (Object mapping 32-byte slots to 32-byte values).
  • baseFeePerGas: QUANTITY - The genesis header base fee. Present only when the genesis sets it, as are excessBlobGas, blobGasUsed, number and parentHash.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_genesis","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "config": {
      "chainId": 2017,
      "homesteadBlock": 0,
      "daoForkSupport": false,
      "eip150Block": 0,
      "eip155Block": 0,
      "eip158Block": 0,
      "byzantiumBlock": 0,
      "constantinopleBlock": 0,
      "petersburgBlock": 0,
      "istanbulBlock": 0,
      "berlinBlock": 0,
      "londonBlock": 0,
      "shanghaiTime": 0,
      "cancunTime": 0,
      "pragueTime": 0,
      "terminalTotalDifficulty": 0,
      "terminalTotalDifficultyPassed": true
    },
    "nonce": "0x0",
    "timestamp": "0x69fceea7",
    "extraData": "0x",
    "gasLimit": "0x1c9c380",
    "difficulty": "0x0",
    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "coinbase": "0x0000000000000000000000000000000000000000",
    "alloc": {
      "0x00000000000000000000000000000000000007e1": {
        "nonce": "0x0",
        "balance": "0x0",
        "code": "0xfe"
      },
      "0x0407c96c91afe937c50058fa870a4a745fa9142e": {
        "balance": "0xc9f2c9cd04674edea40000000"
      }
      // ... 35 more accounts, including the system contracts
    },
    "baseFeePerGas": "0x7"
  }
}

tn_getBalanceBreakdown

Returns the balance breakdown (outstanding balance, initial stake, rewards) for a given validator address, read from the ConsensusRegistry contract at the node’s canonical tip. The contract returns three unnamed uint256 values; the node names them in the object below. For an address that was never a validator, outstandingBalance and rewards are 0x0 and initialStake is the stake amount of stake version 0.

Parameters

DATA, 20 bytes - The validator’s execution address.

Returns

Object - The balance breakdown:

  • outstandingBalance: QUANTITY - Hexadecimal of the validator’s balance held by the registry, in wei: its stake plus the rewards it has not claimed.
  • initialStake: QUANTITY - Hexadecimal of the stake amount of the validator’s stake version, in wei; see How Staking Works.
  • rewards: QUANTITY - Hexadecimal of the claimable rewards accrued, in wei: the part of outstandingBalance above initialStake, or 0x0 if there is none. The same value tn_getRewards returns.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getBalanceBreakdown","params":["0x3518b301b86ceb53b5a3dff62e55cd43ef59d024"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "outstandingBalance": "0x3cb42afc2b7a0162dfb99", // about 4,586,648 TEL
    "initialStake": "0xd3c21bcecceda1000000", // 1,000,000 TEL
    "rewards": "0x2f78093f3eab2752dfb99" // about 3,586,648 TEL
  }
}

tn_getBlockTimestampMillis

Returns the consensus commit time, in milliseconds, of an execution block. An execution block keeps a whole-second timestamp; the millisecond commit time lives in the consensus header the block was executed from, which the block references through its parentBeaconBlockRoot. This method pairs the two.

Returns null for a block this node does not know: an unknown number or hash, safe and finalized before the first block is finalized, and heights below a snapshot-restored node’s restored header window. A known block whose consensus header is missing from local storage, for example because the node does not have that epoch’s consensus pack, returns the error -32001 “Not Found.” rather than null. A known block whose epoch’s consensus pack is on disk but cannot be read returns -32603 “internal error”; the server then answers “internal error” for 30 seconds for any block of that epoch it has not already resolved, without reading the pack again, so a repaired pack is picked up within that time.

Validators should not expose the tn namespace publicly. A request for a block from a sealed epoch can open that epoch’s consensus pack, and the storage layer opens packs synchronously into a small cache it shares with state sync and with serving epochs to peers. The lookup runs off the async runtime and under a tight concurrency bound, but public callers can still churn that cache until storage opens and evicts packs without blocking. Serve the namespace from non-validating nodes instead, and leave tn out of the validator’s --http.api and --ws.api; see Enabling Namespaces.

Parameters

QUANTITY|TAG|DATA - Hexadecimal block number, a 32-byte block hash, or one of the string tags latest, earliest, pending, safe or finalized, as in the default block parameter. pending resolves as latest.

Returns

Object - The block’s timestamps, or null when the node does not know the block:

  • blockNumber: QUANTITY - The execution block’s number.
  • blockHash: DATA, 32 Bytes - The execution block’s hash.
  • timestamp: QUANTITY - The execution block’s timestamp in whole seconds, as the EVM reports it.
  • timestampMillis: QUANTITY - The commit time of the block’s consensus header, in milliseconds since the Unix epoch, read from the header’s committed sub-DAG rather than computed from timestamp. For genesis, which has no consensus header, timestamp times 1000.
  • subSecond: Boolean - Whether the consensus header’s leader epoch commits with millisecond resolution. false for leader epochs before the sub-second timestamp fork, whose commit times are whole seconds (timestampMillis is then a multiple of 1000), and for genesis.
  • consensusNumber: QUANTITY - The number of the consensus header the block was executed from. Absent for genesis.
  • consensusDigest: DATA, 32 Bytes - The digest of that consensus header, which is the block’s parentBeaconBlockRoot. Absent for genesis. It is hex here; consensus-layer methods such as tn_latestConsensusHeader print digests in base58.

Normally timestamp equals timestampMillis / 1000 rounded down. It does not when execution raised the block’s timestamp to its parent’s, which the node counts in the evm_timestamp_clamped_total metric: timestampMillis then stays the consensus commit time and is below timestamp times 1000. Execution raises a block’s timestamp only from the sub-second timestamp fork on, and then only after a consensus regression or for an epoch-0 commit made while the validators’ clocks lag the genesis timestamp.

timestampMillis does not decrease within an epoch, except from genesis to block 1 if the validators’ clocks lagged the genesis timestamp at launch: genesis reports timestamp times 1000 and block 1 its earlier commit time. Blocks executed from one consensus output share a value. Across an epoch boundary only timestamp is guaranteed not to decrease: the blocks of an epoch’s first commit can report up to 998 ms less than the previous epoch’s last block, within the same whole second.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getBlockTimestampMillis","params":["0x7a44a"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "blockNumber": "0x7a44a",
    "blockHash": "0x785da075f575faf3701e75214315e1aa7806dcc434b009673a3e76f7d21eec1d",
    "timestamp": "0x6ac5af2b",
    "timestampMillis": "0x1a114343ff8",
    "subSecond": false,
    "consensusNumber": "0x6badf4",
    "consensusDigest": "0x132b90286336db32aec099b223b4343155f25487e286a8d4185a5c681e71c141"
  }
}

tn_getBlsPubkey

Returns the BLS public key for a given validator address, read from the ConsensusRegistry contract at the node’s canonical tip. The key is the compressed BLS12-381 public key the validator registered when it staked. An address with no registered key reverts with the registry’s BlsPubkeyNotFound(address) error.

Parameters

DATA, 20 bytes - The validator’s execution address.

Returns

DATA, 96 bytes - The validator’s compressed BLS12-381 public key.

A revert is reported as eth_call reports one: error code 3, a message that starts with execution reverted, and the ABI-encoded revert data in data.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getBlsPubkey","params":["0x3518b301b86ceb53b5a3dff62e55cd43ef59d024"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0xb1f5f47e63566357362e2157f1b6860df78c08e98f7f5df8e0e2cf837cdea6420c0698dc123f187ed170acfe5c138bf11202b69c3ce2c4f5c2de7fa58d993931164fc3011118c62f0fb1b9cf723f1e55a410a9f77e8cd41576c9e4f8d0adf8ba"
}

tn_getCommitteeBlsPubkeys

Returns the BLS public keys for the committee of a given epoch, read from the ConsensusRegistry contract at the node’s canonical tip. The keys are in the order the registry stores the committee, the same order tn_getCommitteeValidators returns, so the n-th key belongs to the n-th validator there.

The registry keeps committees only for a window around the current epoch: the three epochs before it, the current epoch, and the next two. An epoch outside that window reverts with the registry’s InvalidEpoch(uint32) error.

Parameters

Number - The epoch number, as a JSON number. A hex string is rejected with error -32602.

Returns

Array of DATA, 96 bytes each - The committee members’ compressed BLS12-381 public keys.

A revert is reported as eth_call reports one: error code 3, a message that starts with execution reverted, and the ABI-encoded revert data in data.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getCommitteeBlsPubkeys","params":[601],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    "0x89142f700382ba353262037ee9d72e43d56dfc6001618dee906b5dd2955c24955a0af1e16a65e064fb5a9602c3ba4846073fd2f422ae52f2e328a1b7d6e42a27cac5dc731ecaf3711ef2b727b226f0b29bad2fe890c453a6f3b23d5396b8483c",
    "0xb1f5f47e63566357362e2157f1b6860df78c08e98f7f5df8e0e2cf837cdea6420c0698dc123f187ed170acfe5c138bf11202b69c3ce2c4f5c2de7fa58d993931164fc3011118c62f0fb1b9cf723f1e55a410a9f77e8cd41576c9e4f8d0adf8ba",
    "0xb7d7220480a5df0d1d3d622426d667151a091b430f329546067dba93da38697f5307fd9729f79089fe1ae6179658e0230d00059cc2349f9308b8eaff0b79309cdd2f8ed0568340f504909d643c435bc2d98123e11c6e9a125547a78c69855463",
    "0x8fe0fa048858f10a610a75242de0532b1df341f7bdd11b28872ca0c128487e5422baa945926ebd3b75e7f350590288630a9c930031fdcd9b2ce76ac7833090378e05f8578a7ce437c6c0cf99496c83ef5ee1c1b5a5e580bd8c922070c2234e73",
    "0xb7d6a84c181082e78d93f8b203208e4e1b3a2b99dc66879bc86d73dcae4bc7a1162aac775b64d8c121c4318ffd2954590f81dd1f91583a3d1a3b260df8a1e08b4a6a56f6f1974901d0b5823c821214eeacce9b5780cae0856bc6a50a7599320f"
  ]
}

tn_getCommitteeValidators

Returns the committee validators for the given epoch, read from the ConsensusRegistry contract at the node’s canonical tip.

The registry keeps committees only for a window around the current epoch: the three epochs before it, the current epoch, and the next two, whose committees are already chosen. An epoch outside that window reverts with the registry’s InvalidEpoch(uint32) error. Each object is the validator’s record as it is now, not as it was during that epoch, so a past committee member that has since begun to exit shows its current status. tn_getCommitteeBlsPubkeys returns the same committee’s BLS keys in the same order.

Parameters

Number - The epoch number, as a JSON number. A hex string is rejected with error -32602.

Returns

Array - The epoch’s committee in the order the registry stores it, one object per validator:

  • validatorAddress: DATA, 20 bytes - The validator’s execution address, which also identifies its ConsensusNFT.
  • activationEpoch: Number - The epoch in which the validator became, or will become, active.
  • exitEpoch: Number - The epoch in which the validator exited; 0 until it begins to exit and 4294967295 while it is PendingExit.
  • currentStatus: String - The validator’s current status, for example "Active" or "PendingExit".
  • isRetired: Boolean - Whether the validator is permanently retired.
  • stakeVersion: Number - The stake version the validator staked under or last upgraded to; see How Staking Works.
  • region: Number - The geographic region governance assigned to the validator, 0 when unspecified. The node does not use it.

The numeric fields are JSON numbers, not hex strings.

A revert is reported as eth_call reports one: error code 3, a message that starts with execution reverted, and the ABI-encoded revert data in data.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getCommitteeValidators","params":[601],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "validatorAddress": "0x0033a370616805b1fd275b7ffab83fc41d665ccb",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    },
    {
      "validatorAddress": "0x3518b301b86ceb53b5a3dff62e55cd43ef59d024",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    },
    {
      "validatorAddress": "0x7489025dfbaad94f2366d88a62989147d9c8b5d3",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    },
    {
      "validatorAddress": "0x89dab9f6fdc569c1bcdbd6493f25b7040b55dc79",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    },
    {
      "validatorAddress": "0xefaacf04b92298a88200aa50aa6bb7bfce587b17",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    }
  ]
}

tn_getCurrentEpoch

Returns the current epoch number as the ConsensusRegistry contract records it, read from the state at this node’s canonical tip, the newest block it has executed. The value moves to the next epoch when the node executes the last block of an epoch, whose concludeEpoch system call starts the next epoch in the registry; see Epoch Boundaries. A node that is still catching up reports the epoch of its own tip, which can be behind the epoch of the consensus headers it has already seen through tn_latestConsensusHeader.

Parameters

None

Returns

Number - The current epoch number, as a JSON number.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getCurrentEpoch","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": 601
}

tn_getCurrentEpochInfo

Returns the ConsensusRegistry contract’s entry for the current epoch: its committee, issuance, starting block and duration. The entry is read from the contract state at this node’s canonical tip, the newest block it has executed, so a node that is still catching up reports the epoch of its own tip, and the answer moves to the next epoch when the node executes the block that closes the current one.

This is contract state. It is distinct from tn_epochRecord, which serves the consensus layer’s signed epoch records from the node’s database and has no record for the current epoch until it ends. tn_getEpochInfo returns the same entry for another epoch.

Parameters

None

Returns

Object - The epoch info object:

  • committee: Array - The execution addresses (DATA, 20 bytes) of the epoch’s committee members.
  • epochIssuance: QUANTITY - The TEL issuance, in wei, the epoch distributes among the validators that led consensus commits during it; see Epoch Boundaries.
  • blockHeight: Number - The execution block height at which the epoch started and its committee became active.
  • epochId: Number - The epoch number.
  • epochDuration: Number - The epoch’s duration in seconds, set at the start of the epoch from the registry’s stake configuration.
  • stakeVersion: Number - The stake configuration version used for the epoch’s reward calculations.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getCurrentEpochInfo","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "committee": [
      "0x0033a370616805b1fd275b7ffab83fc41d665ccb",
      "0x3518b301b86ceb53b5a3dff62e55cd43ef59d024",
      "0x7489025dfbaad94f2366d88a62989147d9c8b5d3",
      "0x89dab9f6fdc569c1bcdbd6493f25b7040b55dc79",
      "0xefaacf04b92298a88200aa50aa6bb7bfce587b17"
    ],
    "epochIssuance": "0x576f23131f3c2780000",
    "blockHeight": 500811,
    "epochId": 601,
    "epochDuration": 21600,
    "stakeVersion": 0
  }
}

tn_getCurrentStakeVersion

Returns the current stake config version, read from the ConsensusRegistry contract at the node’s canonical tip. This is the stake version the current epoch was stamped with when it began, the same stakeVersion that tn_getCurrentEpochInfo reports. A version governance creates with upgradeStakeVersion() takes effect at the next epoch, so until then this method still returns the previous version. How Staking Works describes stake versions.

Parameters

None

Returns

Number - The stake version, a JSON number from 0 to 255.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getCurrentStakeVersion","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": 0
}

tn_getEpochInfo

Returns the ConsensusRegistry contract’s entry for an epoch: its committee, issuance, starting block and duration, read from the contract state at this node’s canonical tip, the newest block it has executed.

The contract keeps these entries only for a ring buffer of epochs around the current one: the three epochs before it, the current epoch, and the next two. A request for an epoch outside that window reverts with the registry’s InvalidEpoch(uint32) error, reported as eth_call reports a revert: error code 3, a message that starts with execution reverted, and the ABI-encoded revert data in data. An entry for an epoch that has not started yet reports blockHeight 0. For epochs that have ended, tn_epochRecord serves the consensus layer’s epoch records from the node’s database, signed by each epoch’s committee and not limited to recent epochs.

Parameters

Number - The epoch number, as a JSON number. A hex string is rejected with -32602 “Invalid params”.

Returns

Object - The epoch info object, with the fields described on tn_getCurrentEpochInfo: committee, epochIssuance, blockHeight, epochId, epochDuration and stakeVersion.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getEpochInfo","params":[600],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "committee": [
      "0x0033a370616805b1fd275b7ffab83fc41d665ccb",
      "0x3518b301b86ceb53b5a3dff62e55cd43ef59d024",
      "0x7489025dfbaad94f2366d88a62989147d9c8b5d3",
      "0x89dab9f6fdc569c1bcdbd6493f25b7040b55dc79",
      "0xefaacf04b92298a88200aa50aa6bb7bfce587b17"
    ],
    "epochIssuance": "0x576f23131f3c2780000",
    "blockHeight": 500659,
    "epochId": 600,
    "epochDuration": 21600,
    "stakeVersion": 0
  }
}

tn_getNextCommitteeSize

Returns the committee size for the next epoch, as the ConsensusRegistry contract records it in the state at this node’s canonical tip, the newest block it has executed. When the protocol selects the next committee at an epoch boundary, it shuffles the eligible validators and trims the list to this size; see Epoch Boundaries.

Parameters

None

Returns

Number - The number of validators in the next epoch’s committee, as a JSON number.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getNextCommitteeSize","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": 5
}

tn_getRewards

Returns the claimable rewards accrued for a given validator address, read from the ConsensusRegistry contract at the node’s canonical tip. The registry counts as rewards whatever the validator’s balance holds above the stake amount of its stake version, so the value is 0x0 before any rewards accrue and for an address that was never a validator. tn_getBalanceBreakdown returns the balance and stake amount this is computed from; How Staking Works explains how rewards are distributed.

Parameters

DATA, 20 bytes - The validator’s execution address.

Returns

QUANTITY - Hexadecimal of the accrued rewards in wei.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getRewards","params":["0x3518b301b86ceb53b5a3dff62e55cd43ef59d024"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x2f78093f3eab2752dfb99" // about 3,586,648 TEL
}

tn_getValidator

Returns the ValidatorInfo record for a given validator address, read from the ConsensusRegistry contract at the node’s canonical tip.

The address must hold a ConsensusNFT or belong to a retired validator; any other address reverts with the registry’s InvalidTokenId(uint256) error. An address that holds a ConsensusNFT but has not staked yet returns a record with currentStatus "Undefined" and the numeric fields at 0. A retired validator’s record is kept after its ConsensusNFT is gone and reports currentStatus "Any" with isRetired true.

Parameters

DATA, 20 bytes - The validator’s execution address.

Returns

Object - The validator’s record:

  • validatorAddress: DATA, 20 bytes - The validator’s execution address, which also identifies its ConsensusNFT.
  • activationEpoch: Number - The epoch in which the validator became, or will become, active.
  • exitEpoch: Number - The epoch in which the validator exited; 0 until it begins to exit and 4294967295 while it is PendingExit.
  • currentStatus: String - The validator’s status: "Undefined", "Staked", "PendingActivation", "Active", "PendingExit", "Exited", or "Any" once retired.
  • isRetired: Boolean - Whether the validator is permanently retired; a retired address can never rejoin.
  • stakeVersion: Number - The stake version the validator staked under or last upgraded to; see How Staking Works.
  • region: Number - The geographic region governance assigned to the validator, 0 when unspecified. The node does not use it.

The numeric fields are JSON numbers, not hex strings.

A revert is reported as eth_call reports one: error code 3, a message that starts with execution reverted, and the ABI-encoded revert data in data.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getValidator","params":["0x3518b301b86ceb53b5a3dff62e55cd43ef59d024"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "validatorAddress": "0x3518b301b86ceb53b5a3dff62e55cd43ef59d024",
    "activationEpoch": 0,
    "exitEpoch": 0,
    "currentStatus": "Active",
    "isRetired": false,
    "stakeVersion": 0,
    "region": 0
  }
}

tn_getValidators

Returns the validators the ConsensusRegistry contract holds under one status, read at the node’s canonical tip. Pass "Any" to return validators of every status; "Undefined" reverts on-chain.

The registry keeps one set of validators per status and has no query for "Any", so the node builds that answer itself: it reads the Staked, PendingActivation, Active, PendingExit and Exited sets against the same block and concatenates them in that order. A validator is in exactly one set at a time. Retired validators are in no set, so neither a single status nor "Any" returns them; tn_getValidator still answers for a retired address.

Parameters

String - The validator status by name: "Staked", "PendingActivation", "Active", "PendingExit", "Exited" or "Any". Names are case-sensitive, and a number is rejected as an invalid parameter. "Undefined" reverts with the registry’s InvalidStatus(uint8) error; an unrecognised name reverts with empty revert data.

Returns

Array - The validators with the requested status, in the registry’s set order, one object each:

  • validatorAddress: DATA, 20 bytes - The validator’s execution address, which also identifies its ConsensusNFT.
  • activationEpoch: Number - The epoch in which the validator became, or will become, active.
  • exitEpoch: Number - The epoch in which the validator exited; 0 until it begins to exit and 4294967295 while it is PendingExit.
  • currentStatus: String - The validator’s status: "Staked", "PendingActivation", "Active", "PendingExit" or "Exited".
  • isRetired: Boolean - Whether the validator is permanently retired; always false here, since retired validators are in no status set.
  • stakeVersion: Number - The stake version the validator staked under or last upgraded to; see How Staking Works.
  • region: Number - The geographic region governance assigned to the validator, 0 when unspecified. The node does not use it.

The numeric fields are JSON numbers, not hex strings. An empty status set returns [].

A revert is reported as eth_call reports one: error code 3, a message that starts with execution reverted, and the ABI-encoded revert data in data.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_getValidators","params":["Active"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "validatorAddress": "0x0033a370616805b1fd275b7ffab83fc41d665ccb",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    },
    {
      "validatorAddress": "0x89dab9f6fdc569c1bcdbd6493f25b7040b55dc79",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    },
    {
      "validatorAddress": "0x3518b301b86ceb53b5a3dff62e55cd43ef59d024",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    },
    {
      "validatorAddress": "0xefaacf04b92298a88200aa50aa6bb7bfce587b17",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    },
    {
      "validatorAddress": "0x7489025dfbaad94f2366d88a62989147d9c8b5d3",
      "activationEpoch": 0,
      "exitEpoch": 0,
      "currentStatus": "Active",
      "isRetired": false,
      "stakeVersion": 0,
      "region": 0
    }
  ]
}

tn_info

Returns the identity a node publishes about itself: the chain it belongs to, its software version and name, its BLS public key and authority id, the execution address that receives its rewards, and the network keys and external addresses peers use to reach its primary and worker. The answer does not change while the node runs; the node’s current consensus participation mode is served by tn_nodeMode instead.

Each worker runs its own RPC server, and worker_network_key and worker_external_address belong to the worker whose server answered. Every other field is the same on all of a node’s workers.

Parameters

None

Returns

Object - The node information object. Unlike most tn results, its field names are snake_case:

  • chain_id: Number - The chain id, as a JSON number.
  • version: String - The node’s software version, <crate version> (<git commit sha>).
  • name: String - The node’s name. Defaults to node- followed by the base58 encoding of the first 8 bytes of the BLS public key; the operator can set another name.
  • bls_public_key: String - The node’s BLS12-381 public key, base58 encoding of its 96-byte compressed form.
  • authority_id: String - The node’s consensus authority id, a 32-byte hash of its BLS public key, base58-encoded.
  • execution_address: DATA, 20 bytes - The address that receives the node’s rewards when it serves in a committee.
  • primary_network_key: String - The public key of the node’s primary network identity, base58 encoding of the libp2p protobuf key encoding.
  • worker_network_key: String - The public key of this worker’s network identity, in the same encoding.
  • primary_external_address: String - The multiaddr peers use to reach the node’s primary.
  • worker_external_address: String - The multiaddr peers use to reach this worker.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_info","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "chain_id": 2017,
    "version": "0.1.0 (5736cc30012c5ff25913898e318a74df308f13d9)",
    "name": "node-WmSeweurYc6",
    "bls_public_key": "24JaKrebALhvhXxsAabjd5ZK22k1W4zKwGepKD5pcobQwBkZyiruYyhRM3hkLap8Ssc4qUdcRUJSrs2389hUoV7G83HKYrmkz2pTzCJiQURV5vtkrh8gFRKBQWKFTWLWah9s",
    "authority_id": "G6A8BRn31vofiVH8KZzETW2kcPsbomNTQYMgvZg52jTg",
    "execution_address": "0x3518b301b86ceb53b5a3dff62e55cd43ef59d024",
    "primary_network_key": "4XTTMJFzopXoTVayf2m4wE69GCqMXW3wUnimiU3CdK5zHL36j",
    "worker_network_key": "4XTTM5m63vU9mmcv8yAmFEpc946PWotoFWDb4TyqRz7ayKJUJ",
    "primary_external_address": "/ip4/34.102.122.126/udp/49590/quic-v1/p2p/12D3KooWSwK9yPLNtDr116cX74FS4Ehs2Nx2fWAUjpnsGrNkNGKd",
    "worker_external_address": "/ip4/34.102.122.126/udp/49594/quic-v1/p2p/12D3KooWESQQ5KghAFnUwWJq7niJuVjrLDooNzypjmRfwsySMXhC"
  }
}

tn_isDelegated

Returns true if the validator’s stake originates from a delegator, read from the ConsensusRegistry contract at the node’s canonical tip. A validator is delegated when a delegator staked on its behalf with delegateStake (see Delegated Staking); the delegator, not the validator, then receives the stake and rewards when the validator unstakes. An address with no delegation, including one that was never a validator, returns false.

Parameters

DATA, 20 bytes - The validator’s execution address.

Returns

Boolean - true if the registry records a delegator for this validator, otherwise false.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_isDelegated","params":["0x3518b301b86ceb53b5a3dff62e55cd43ef59d024"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": false
}

tn_isRetired

Returns true if the validator is permanently retired, read from the ConsensusRegistry contract at the node’s canonical tip. A validator retires when it unstakes, when governance burns its ConsensusNFT, or when slashing takes its stake to zero; a retired address can never rejoin. An address that still holds a ConsensusNFT, or that was never a validator, returns false. The zero address reverts with the registry’s InvalidTokenId(uint256) error.

Parameters

DATA, 20 bytes - The validator’s execution address.

Returns

Boolean - true if the validator is retired, otherwise false.

A revert is reported as eth_call reports one: error code 3, a message that starts with execution reverted, and the ABI-encoded revert data in data.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_isRetired","params":["0x3518b301b86ceb53b5a3dff62e55cd43ef59d024"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": false
}

tn_isValidator

Returns true if the BLS public key belongs to a known validator, read from the ConsensusRegistry contract at the node’s canonical tip. A key is known when a validator registered it by staking and that validator has not retired; the validator’s status is otherwise not checked, so a Staked or Exited validator’s key also returns true. The input is never rejected: a key that is not exactly 96 bytes, or that no validator registered, returns false.

Parameters

DATA, 96 bytes - The compressed BLS12-381 public key. The 192-byte uncompressed encoding is not accepted here and returns false.

Returns

Boolean - true if a validator that has not retired registered this key, otherwise false.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_isValidator","params":["0xb1f5f47e63566357362e2157f1b6860df78c08e98f7f5df8e0e2cf837cdea6420c0698dc123f187ed170acfe5c138bf11202b69c3ce2c4f5c2de7fa58d993931164fc3011118c62f0fb1b9cf723f1e55a410a9f77e8cd41576c9e4f8d0adf8ba"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": true
}

tn_latestConsensusHeader

Returns the newest consensus header this node has seen. Consensus headers form the consensus chain: each one records a sub-DAG that BFT consensus committed, in order, and execution blocks are built from them. A validator in the committee reports the header its own consensus just committed; a node that follows consensus reports the newest header it has received and verified, which can be ahead of the blocks it has executed while it catches up. Until the node has seen a header after starting, the result is a placeholder header with number 0.

The result is served from memory and does not read consensus storage. tn_latestHeader is a deprecated alias that returns the same object. Header digests, authority ids and signatures in this object are base58 strings; execution block hashes, batch digests, randomness and extra are 0x hex.

Parameters

None

Returns

Object - The consensus header object. Its field names are snake_case:

  • parent_hash: String - Digest of the previous consensus header, 32 bytes, base58-encoded.
  • sub_dag: Object - The committed sub-DAG:
    • headers: Array - The headers this commit orders, in commit order. The last one is the leader’s. Each header object has:
      • author: String - Authority id of the validator that created the header, base58-encoded (see authority_id in tn_info).
      • round: Number - The consensus round of the header.
      • epoch: Number - The epoch the header was created in.
      • created_at: Number - When the header was created, in whole seconds since the Unix epoch.
      • payload: Array - The transaction batches the header includes, each a two-element array: the batch digest (DATA, 32 Bytes) and the id of the worker that produced it (Number).
      • parents: Array - Digests of the header’s parent certificates from the previous round, base58-encoded.
      • latest_execution_block: Object - The latest execution block the author had when it built the header: number (Number) and hash (DATA, 32 Bytes).
      • seed_signature: String - The author’s BLS signature over the epoch seed message for this round, base58-encoded; it feeds the epoch seed chain that shuffles the next committee. Present for epochs where the seed-signature fork is active: every epoch on mainnet builds, epoch 383 onward on adiri.
      • created_at_millis: Number - The millisecond part of created_at, 0 to 999. Present only for epochs where the sub-second timestamp fork is active: every epoch on mainnet builds, and on adiri from a fork epoch that is not yet scheduled.
    • reputation_scores: Object - Leader reputation scores: scores_per_authority maps each authority id (base58) to its score (Number), and final_of_schedule (Boolean) is true when these are the last scores of the current leader schedule before they reset.
    • commit_timestamp: Number - The commit time, in whole seconds since the Unix epoch.
    • randomness: DATA, 32 Bytes - The epoch seed chain value as of this commit. For epochs before the seed-signature fork, a hash of the leader certificate’s aggregate signature instead.
    • commit_timestamp_millis: Number - The millisecond part of commit_timestamp, 0 to 999. Present only when the sub-second timestamp fork is active for the leader’s epoch, as for created_at_millis. tn_getBlockTimestampMillis reports the combined commit time for an execution block.
  • number: Number - The header’s height in the consensus chain.
  • extra: DATA, 32 Bytes - Reserved; currently always zero.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_latestConsensusHeader","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "parent_hash": "F7V4oK3SzU4Sh3TgjKtacJDqVvKG4xeBJoci5pWDM8cr",
    "sub_dag": {
      "headers": [
        {
          "author": "AFRwTgoeRcEDVUu6QLXkMs8qPouYtEdLmWu2HR6YRBuJ",
          "round": 8718,
          "epoch": 601,
          "created_at": 1791348194,
          "payload": [],
          "parents": [
            "6ubuD1mhZQnNioC6uZHHcdXT4Hqn9W42o1qQbTWeGy6b",
            "CDq3gKsxwGffYso6XZ9nC4XGC2WpmmGecs9owzGZ7i2h",
            "CiJR3e7SBw59gvCrsd71eTCHthiRa4dX2tjyh3Zhfwzn",
            "Dpj3DBvzEm65sj5wWTKSuJvj6bBdjU2eT2uQFPLSvEff"
          ],
          "latest_execution_block": {
            "number": 500829,
            "hash": "0x73bebb21d61d379e5a38510fe5cbf093a32c409778b04dff54832e97039ed894"
          },
          "seed_signature": "5qed98kzkQFKe2RrPm9Rt3qHtQe8D3dCQnFRj7FS38wCVEMExhRqYEccyUasBNCdyj"
        },
        // ... seven more headers ...
        {
          "author": "G6A8BRn31vofiVH8KZzETW2kcPsbomNTQYMgvZg52jTg", // the leader
          "round": 8720,
          "epoch": 601,
          "created_at": 1791348196,
          "payload": [],
          "parents": [
            "33Rkr46KvuYguKGpyztWdVFTJgKMQ7HMZJqSxq5VvBVB",
            "4FCmMaftVgW1LjFHSg4vaaQnQUeLnyp3DZ6mMSDwnxLd",
            "DRuiwXGYkP3tbqAkNm9ZW2X62GLoVtaGBuMRpAQVGoPm",
            "FF5fxaN4dYHQASC3hFpCLVVgf9qLsWbe4z1x8aJv6BZq"
          ],
          "latest_execution_block": {
            "number": 500829,
            "hash": "0x73bebb21d61d379e5a38510fe5cbf093a32c409778b04dff54832e97039ed894"
          },
          "seed_signature": "7PCVZwBmeVzPNdA9JP2oBhcHD58cKpQR7SEvXeuJYVZXWT7oGE7ALYFZS5GbARcTXT"
        }
      ],
      "reputation_scores": {
        "scores_per_authority": {
          "4LSGzE7N43UMvJoyTPTzvjjc9my2XvT6qjMDo8axRLgM": 49,
          "AFRwTgoeRcEDVUu6QLXkMs8qPouYtEdLmWu2HR6YRBuJ": 30,
          "AhVqH6fD3qLKDhrA3zSDQs9jaLR1h5uQ9QzYc6bCpfaH": 49,
          "FfsRxvnfYtW7umk9AGfH24BgNYnbkJ6tn2paTXJse99d": 29,
          "G6A8BRn31vofiVH8KZzETW2kcPsbomNTQYMgvZg52jTg": 39
        },
        "final_of_schedule": false
      },
      "commit_timestamp": 1791348196,
      "randomness": "0x1c10702d249a3a8b5ae183c4f2c790dca5de311e900dd72fe6b4e695d5148358"
    },
    "number": 7061244,
    "extra": "0x0000000000000000000000000000000000000000000000000000000000000000"
  }
}

tn_latestHeader

Deprecated alias of tn_latestConsensusHeader: it returns the same consensus header object, the newest one this node has seen. Use tn_latestConsensusHeader in new code.

Parameters

None

Returns

Object - The consensus header object, with the same fields as tn_latestConsensusHeader: parent_hash, sub_dag, number and extra.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_latestHeader","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "parent_hash": "F7V4oK3SzU4Sh3TgjKtacJDqVvKG4xeBJoci5pWDM8cr",
    "sub_dag": { ... }, // as in tn_latestConsensusHeader
    "number": 7061244,
    "extra": "0x0000000000000000000000000000000000000000000000000000000000000000"
  }
}

tn_nodeMode

Returns the node’s current consensus participation mode: whether it is a committee member voting in consensus, a committee member catching up, or an observer that follows consensus output. The mode is read when the request arrives rather than fixed at startup like tn_info, so a caller can see a transient mode such as CvvInactive while a restarted validator catches up.

Parameters

None

Returns

String - One of:

  • "CvvActive" - A fully synced validator voting in the current committee.
  • "CvvInactive" - A staked validator in the current committee that is following consensus output to catch up after a failure during the epoch. The mode is transient: the node returns to CvvActive once it has synced past the consensus garbage-collection window.
  • "Observer" - A node that follows consensus output and is not in the current committee. It may or may not be staked.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_nodeMode","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "Observer"
}

tn_proofOfPossessionMessage

Returns the BLS12-381 proof-of-possession message a validator signs: intentPrefix(3) || compressedBlsPubkey(96) || validatorAddress(20), 119 bytes in all. The validator’s signature over this message is the proof of possession it submits when it stakes; see How to Stake. The node builds the message itself with the same routine validators sign with, which the ConsensusRegistry contract reproduces byte for byte on-chain, so the method reads no chain state and signs or submits nothing.

The method expects the 96-byte compressed BLS public key. The 192-byte uncompressed encoding is also accepted and normalized to the compressed form, so both yield the same message.

Parameters

DATA, 96 or 192 bytes - The validator’s BLS12-381 public key, compressed (96 bytes) or uncompressed (192 bytes).

DATA, 20 bytes - The validator’s execution address.

Returns

DATA, 119 bytes - The message to sign: the 3-byte Telcoin Network proof-of-possession intent, the compressed public key, and the address.

A key of any other length fails with error -32602 and the message invalid BLS pubkey: expected 96 bytes (compressed) or 192 bytes (uncompressed), got <length>. A key of the right length that does not decode to a valid BLS12-381 public key also fails with -32602, with a message that starts with invalid 96-byte compressed BLS pubkey.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_proofOfPossessionMessage","params":["0xb1f5f47e63566357362e2157f1b6860df78c08e98f7f5df8e0e2cf837cdea6420c0698dc123f187ed170acfe5c138bf11202b69c3ce2c4f5c2de7fa58d993931164fc3011118c62f0fb1b9cf723f1e55a410a9f77e8cd41576c9e4f8d0adf8ba", "0x3518b301b86ceb53b5a3dff62e55cd43ef59d024"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x000000b1f5f47e63566357362e2157f1b6860df78c08e98f7f5df8e0e2cf837cdea6420c0698dc123f187ed170acfe5c138bf11202b69c3ce2c4f5c2de7fa58d993931164fc3011118c62f0fb1b9cf723f1e55a410a9f77e8cd41576c9e4f8d0adf8ba3518b301b86ceb53b5a3dff62e55cd43ef59d024"
}

tn_undistributedIssuance

Returns the issuance the ConsensusRegistry contract has not yet distributed to validators, read from the contract state at this node’s canonical tip, the newest block it has executed. Rewards are distributed at each epoch boundary by the applyIncentives system call; see Epoch Boundaries.

Parameters

None

Returns

QUANTITY - The undistributed issuance, in wei.

Example

Request

curl https://rpc.adiri.tel \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"tn_undistributedIssuance","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x3"
}

debug

The debug namespace serves raw RLP and EIP-2718 encodings of headers, blocks, transactions and receipts, geth-style EVM traces of blocks, transactions and simulated calls, execution witnesses, and a few chain-state helpers. It is off by default: a node serves it only when debug is named in --http.api or --ws.api, and all never includes it. The cost to serve it is high, because the tracing and witness methods re-execute historical blocks against archive state, and one request can take seconds of CPU time and return a large response. Serve it only from a private archive or indexer node behind authentication or a method allowlist, never from a validator or a public RPC endpoint; see Enabling Namespaces.

debug_traceBlock, debug_traceBlockByHash, debug_traceBlockByNumber, debug_traceTransaction, debug_traceCall, debug_traceCallMany, debug_executionWitness and debug_executionWitnessByBlockHash each hold a permit from the node’s tracing pool while they run. The pool size is --rpc.max-tracing-requests, and the same pool serves trace_* and eth_callBundle; a request that finds no free permit waits for one.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Example values are illustrative; field names and types match what the node returns.

MethodDescription
debug_chainConfigThe chain configuration from the node’s genesis
debug_codeByHashContract bytecode for a code hash
debug_dbGetContract bytecode for a 0x63-prefixed code-hash database key
debug_executionWitnessExecution witness of a block, by number or tag
debug_executionWitnessByBlockHashExecution witness of a block, by hash
debug_getBadBlocksAlways [] on Telcoin Network
debug_getRawBlockRLP-encoded block
debug_getRawHeaderRLP-encoded block header
debug_getRawReceiptsEIP-2718 encoded receipts of a block
debug_getRawTransactionEIP-2718 encoded transaction, by hash
debug_getRawTransactionsEIP-2718 encoded transactions of a block
debug_stateRootWithUpdatesState root and trie updates for a hashed state applied on top of a block
debug_traceBlockTrace every transaction of an RLP-encoded block
debug_traceBlockByHashTrace every transaction of a block, by hash
debug_traceBlockByNumberTrace every transaction of a block, by number or tag
debug_traceCallTrace a call simulated on a block’s state
debug_traceCallManyTrace bundles of calls simulated on a block’s state
debug_traceTransactionTrace one transaction

Not supported

The node registers more debug_* method names than the ones above, and none of the others does useful work on Telcoin Network. debug_getBadBlocks is served but always returns [], because the execution engine never reports a bad block (tracked in #1590).

Methods that return an error

MethodResponse
debug_getBlockAccessListError -32603 unimplemented
debug_traceChainError -32603 unimplemented

Methods registered as no-ops

These 41 methods accept their parameters and return success without doing anything. Each returns null, except debug_seedHash, which returns the zero hash 0x0000000000000000000000000000000000000000000000000000000000000000. A success response does not mean the action happened. debug_setHead in particular returns null and leaves the chain head where it was, so a script that calls it to rewind the node reports success while the node keeps its chain.

MethodWhat geth does with it
debug_accountRangePages through the accounts at a block
debug_backtraceAtSets a logging backtrace location
debug_blockProfileWrites a block profile to disk
debug_chaindbCompactCompacts the key-value database
debug_chaindbPropertyReturns a property of the key-value database
debug_cpuProfileWrites a CPU profile to disk
debug_dbAncientReads a blob from the ancient store
debug_dbAncientsCounts the items in the ancient store
debug_dumpBlockDumps the accounts at a block
debug_freeOSMemoryForces garbage collection
debug_freezeClientFreezes the client temporarily
debug_gcStatsReturns garbage-collection statistics
debug_getAccessibleStateFinds the first block with state on disk
debug_getModifiedAccountsByHashLists the accounts changed between two blocks, by hash
debug_getModifiedAccountsByNumberLists the accounts changed between two blocks, by number
debug_goTraceWrites a Go runtime trace to disk
debug_intermediateRootsReturns the state root after each transaction of a block
debug_memStatsReturns runtime memory statistics
debug_mutexProfileWrites a mutex profile to disk
debug_preimageReturns the preimage of a Keccak-256 hash
debug_printBlockPretty-prints a block
debug_seedHashReturns the Ethash seed hash of a block (here always the zero hash)
debug_setBlockProfileRateSets the block profiling rate
debug_setGCPercentSets the garbage-collection target percentage
debug_setHeadRewinds the local chain head to a block number
debug_setMutexProfileFractionSets the mutex profiling rate
debug_setTrieFlushIntervalSets how often in-memory state tries are flushed to disk
debug_stacksPrints the stacks of all goroutines
debug_standardTraceBadBlockToFileWrites a trace of a bad block to a file
debug_standardTraceBlockToFileWrites a trace of a block to a file
debug_startCPUProfileStarts CPU profiling to a file
debug_startGoTraceStarts a Go runtime trace to a file
debug_stopCPUProfileStops CPU profiling
debug_stopGoTraceStops the Go runtime trace
debug_storageRangeAtPages through a contract’s storage at a transaction
debug_traceBadBlockTraces a block from the bad-block list
debug_verbositySets the log level
debug_vmoduleSets a per-module log pattern
debug_writeBlockProfileWrites a goroutine blocking profile to a file
debug_writeMemProfileWrites an allocation profile to a file
debug_writeMutexProfileWrites a mutex profile to a file

Not registered

MethodResponse
debug_executePayloadError -32601 “Method not found”: reth declares it in a separate API that the node never registers

Tracked in #1591.

debug_chainConfig

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Returns the chain configuration from the node’s genesis: the chain id and the block numbers and timestamps at which each Ethereum fork activates.

Parameters

None

Returns

Object - The config section of the genesis. Every number is a JSON number, not a hex string, and a fork the genesis does not configure is omitted:

  • chainId: Number - The chain id.
  • homesteadBlock, eip150Block, eip155Block, eip158Block, byzantiumBlock, constantinopleBlock, petersburgBlock, istanbulBlock, berlinBlock, londonBlock: Number - The block at which each block-numbered fork activates.
  • shanghaiTime, cancunTime, pragueTime: Number - The timestamp at which each time-based fork activates.
  • daoForkSupport: Boolean - Always present; false on Telcoin Network.
  • terminalTotalDifficulty: Number - The terminal total difficulty, when configured.
  • terminalTotalDifficultyPassed: Boolean - Always present.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_chainConfig","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "chainId": 2017,
    "homesteadBlock": 0,
    "daoForkSupport": false,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "istanbulBlock": 0,
    "berlinBlock": 0,
    "londonBlock": 0,
    "shanghaiTime": 0,
    "cancunTime": 0,
    "pragueTime": 0,
    "terminalTotalDifficulty": 0,
    "terminalTotalDifficultyPassed": true
  }
}

source

debug_codeByHash

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Returns the contract bytecode whose Keccak-256 hash is the given code hash, the value eth_getProof reports as an account’s codeHash.

Parameters

DATA, 32 Bytes - The code hash.

block parameter: QUANTITY|TAG|DATA - (optional) Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized; a 32-byte block hash is also accepted. See the default block parameter. Defaults to latest.

Returns

DATA - The bytecode, or null when the node has no code with that hash.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_codeByHash","params":["0xfe74fcea823036dfc874205a4198185eedae92b256d956cbf805c6c0dc2fd184","latest"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x60806040525f80546001600160a01b03169035632cf35bc960e11b01602657805f5260205ff35b365f80375f80365f845af490503d5f803e80603f573d5ffd5b503d5ff3fea164736f6c634300081a000a"
}

source

debug_dbGet

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Only contract-code keys are supported: the key must be 0x63 followed by the 32-byte code hash; any other key returns an invalid-params error.

The method mirrors geth’s raw database lookup for the one key type above. It reads the latest state and returns what debug_codeByHash returns for the same code hash.

Parameters

String - The key: 0x63 followed by the 32-byte code hash, 66 hex digits after 0x in all. A key without the 0x prefix is read as raw bytes, not as hex. A key that is not valid hex fails with error -32602 Invalid hex key, a key of the wrong length with -32602 Key must be 33 bytes, got <length>, and a key with another first byte with -32602 Key prefix must be 0x63.

Returns

DATA - The bytecode, or null when the node has no code with that hash.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_dbGet","params":["0x63fe74fcea823036dfc874205a4198185eedae92b256d956cbf805c6c0dc2fd184"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x60806040525f80546001600160a01b03169035632cf35bc960e11b01602657805f5260205ff35b365f80375f80365f845af490503d5f803e80603f573d5ffd5b503d5ff3fea164736f6c634300081a000a"
}

source

debug_executionWitness

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Warning

Witness of an epoch-closing block

On a block that closes an epoch, the witness comes from a re-execution that runs the epoch-closing applyIncentives call with the node’s current rewards counter rather than the counts the block distributed, so the witness for such a block can differ from the block’s real execution. Other blocks are unaffected. Tracked in #1589.

Re-executes a block from its parent’s state, system calls included, and returns what a stateless client needs to run the block again: the trie nodes, contract code, account and slot keys, and headers the execution and the state-root computation read. debug_executionWitnessByBlockHash does the same for a block hash.

Parameters

block parameter: QUANTITY|TAG [Required] - Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized. See the default block parameter. A block the node does not have fails with error -32001 and a message such as block not found: 0x5.

Returns

Object - The execution witness:

  • state: Array - DATA elements: the RLP-encoded account-trie and storage-trie nodes the execution and the state-root computation read.
  • codes: Array - DATA elements: the bytecode of every contract the execution loaded or created.
  • keys: Array - DATA elements: the unhashed keys behind the trie paths, 20-byte addresses for accounts and 32-byte slots for storage.
  • headers: Array - DATA elements: RLP-encoded headers from the oldest block the execution read with BLOCKHASH up to the parent block. When the block never runs BLOCKHASH, this is the parent header alone.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_executionWitness","params":["0x1"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "state": [
      "0x..." // RLP-encoded trie nodes, abbreviated
    ],
    "codes": [
      "0x..." // contract bytecode, abbreviated
    ],
    "keys": [
      "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
      "0x0000000000000000000000000000000000000000"
      // and the other accounts and storage slots the block touched
    ],
    "headers": [
      "0xf9025ba00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a097d6d56ffab6b8f3167576431dcf70eabc1f0605385d7249e581911908b89fb9a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080808401c9c38080846ac5cdc480a0000000000000000000000000000000000000000000000000000000000000000088000000000000000007a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // header of block 0x0, the parent
    ]
  }
}

source

debug_executionWitnessByBlockHash

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Warning

Witness of an epoch-closing block

On a block that closes an epoch, the witness comes from a re-execution that runs the epoch-closing applyIncentives call with the node’s current rewards counter rather than the counts the block distributed, so the witness for such a block can differ from the block’s real execution. Other blocks are unaffected. Tracked in #1589.

Re-executes a block from its parent’s state, system calls included, and returns what a stateless client needs to run the block again: the trie nodes, contract code, account and slot keys, and headers the execution and the state-root computation read. debug_executionWitness does the same for a block number or tag.

Parameters

DATA, 32 Bytes - The block hash. A block the node does not have fails with error -32001 and the message block not found: hash 0x….

Returns

Object - The execution witness:

  • state: Array - DATA elements: the RLP-encoded account-trie and storage-trie nodes the execution and the state-root computation read.
  • codes: Array - DATA elements: the bytecode of every contract the execution loaded or created.
  • keys: Array - DATA elements: the unhashed keys behind the trie paths, 20-byte addresses for accounts and 32-byte slots for storage.
  • headers: Array - DATA elements: RLP-encoded headers from the oldest block the execution read with BLOCKHASH up to the parent block. When the block never runs BLOCKHASH, this is the parent header alone.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_executionWitnessByBlockHash","params":["0x1e0ed1376b6f8cc5deb9749a96c1207765ae9cd09ff46fe6200ca5b3f2913e8f"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "state": [
      "0x..." // RLP-encoded trie nodes, abbreviated
    ],
    "codes": [
      "0x..." // contract bytecode, abbreviated
    ],
    "keys": [
      "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
      "0x0000000000000000000000000000000000000000"
      // and the other accounts and storage slots the block touched
    ],
    "headers": [
      "0xf9025ba00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a097d6d56ffab6b8f3167576431dcf70eabc1f0605385d7249e581911908b89fb9a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080808401c9c38080846ac5cdc480a0000000000000000000000000000000000000000000000000000000000000000088000000000000000007a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // header of block 0x0, the parent
    ]
  }
}

source

debug_getBadBlocks

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Warning

Always empty on Telcoin Network

Always returns an empty array on Telcoin Network: reth fills this list from engine events that report invalid blocks, and the Telcoin Network execution engine does not emit those events. Tracked in #1590.

Parameters

None

Returns

Array - The recent bad blocks the node has seen, newest first. On Telcoin Network this is always []. In reth each entry is an object with block (the block as eth_getBlockByHash returns it with full transactions), hash and rlp.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_getBadBlocks","params":[],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": []
}

source

debug_getRawBlock

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Returns a whole block as RLP-encoded bytes.

Parameters

block parameter: QUANTITY|TAG|DATA [Required] - Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized; a 32-byte block hash is also accepted. See the default block parameter.

Returns

DATA - The RLP-encoded block: the header, the transactions, the (empty) ommers list and the (empty) withdrawals list. A block the node does not have fails with error -32001 and a message such as block not found: 0x5 or block not found: hash 0x….

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_getRawBlock","params":["0x1"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0xf902cdf9025da05f4827e84833a6da2a79b60373cdb82b5142ef13b1f56ac4790f2c6cfe3112cda04ef7ab685b6104388fca116acbe551cfa24018eb490cfcb7d0a26146a09ae25f94342b9c7c28d3f8680675ab13cf5f215897ae9f95a010620e8dae725248ad5102bde4780354184a4c99b67d31cc857879b93b56c12aa00a7b8d761adb0cfb421ea88e77c2e06ac53a9e9923353ca9b92f6fa92ee033f1a0f78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080018401c9c380825208846ac5cdc480a01fdc430e2e6fef312e3e6a5a122420cd8b5b825bd853869f9d3d9793721140a088000000000000000107a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a047b526d2ce612759ecbd3a2152384a7bf714b83b377323d1b931678148ef4584a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855f869b86702f8648207e18080078252089400000000000000000000000000000000000000006480c080a094e74c15812b53176f8000333c19c62852cf6f4e465adc9ce79f467f13a35736a005b01594de729950fc2153a8768ee98c0c2eb20b6e8c501903ca540083890429c0c0"
}

source

debug_getRawHeader

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Returns a block header as RLP-encoded bytes.

Parameters

block parameter: QUANTITY|TAG|DATA [Required] - Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized; a 32-byte block hash is also accepted. See the default block parameter. When the node has no pending block, the pending tag fails with error -32603 Pending block not supported.

Returns

DATA - The RLP-encoded header. For a block the node does not have, the result is 0x (empty bytes), not an error.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_getRawHeader","params":["0x1"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0xf9025da05f4827e84833a6da2a79b60373cdb82b5142ef13b1f56ac4790f2c6cfe3112cda04ef7ab685b6104388fca116acbe551cfa24018eb490cfcb7d0a26146a09ae25f94342b9c7c28d3f8680675ab13cf5f215897ae9f95a010620e8dae725248ad5102bde4780354184a4c99b67d31cc857879b93b56c12aa00a7b8d761adb0cfb421ea88e77c2e06ac53a9e9923353ca9b92f6fa92ee033f1a0f78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080018401c9c380825208846ac5cdc480a01fdc430e2e6fef312e3e6a5a122420cd8b5b825bd853869f9d3d9793721140a088000000000000000107a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a047b526d2ce612759ecbd3a2152384a7bf714b83b377323d1b931678148ef4584a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}

source

debug_getRawReceipts

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Returns the receipts of a block in their EIP-2718 binary encoding, in transaction order.

Parameters

block parameter: QUANTITY|TAG|DATA [Required] - Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized; a 32-byte block hash is also accepted. See the default block parameter.

Returns

Array - One DATA element per receipt: the transaction type byte (none for a legacy transaction) followed by the RLP list of status, cumulative gas used, logs bloom and logs. A block without transactions, or a block the node does not have, returns [].

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_getRawReceipts","params":["0x1"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    "0x02f9010801825208b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0" // type 0x02, status 1, cumulative gas 0x5208, empty bloom, no logs
  ]
}

source

debug_getRawTransaction

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Returns a transaction in its EIP-2718 binary encoding: the type byte followed by the RLP payload, or the bare RLP for a legacy transaction.

Parameters

DATA, 32 Bytes - The transaction hash.

Returns

DATA - The EIP-2718 encoded transaction, or null when the node does not know the transaction.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_getRawTransaction","params":["0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x02f8648207e18080078252089400000000000000000000000000000000000000006480c080a094e74c15812b53176f8000333c19c62852cf6f4e465adc9ce79f467f13a35736a005b01594de729950fc2153a8768ee98c0c2eb20b6e8c501903ca540083890429" // EIP-1559 transfer of 100 wei
}

source

debug_getRawTransactions

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Returns every transaction of a block in its EIP-2718 binary encoding, in block order.

Parameters

block parameter: QUANTITY|TAG|DATA [Required] - Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized; a 32-byte block hash is also accepted. See the default block parameter.

Returns

Array - One DATA element per transaction, each encoded as debug_getRawTransaction returns it. A block without transactions, or a block the node does not have, returns [].

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_getRawTransactions","params":["0x1"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    "0x02f8648207e18080078252089400000000000000000000000000000000000000006480c080a094e74c15812b53176f8000333c19c62852cf6f4e465adc9ce79f467f13a35736a005b01594de729950fc2153a8768ee98c0c2eb20b6e8c501903ca540083890429"
  ]
}

source

debug_stateRootWithUpdates

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Applies a set of account and storage changes, keyed by hashed address and hashed slot, on top of a block’s state and returns the resulting state root together with the trie updates that root computation produced. Nothing is written to the node’s database.

Parameters

Object - The hashed post state. Both fields are required; pass {} for an empty map:

  • accounts: Object - Keyed by the Keccak-256 hash of an address. Each value is null for a destroyed account, or an object with nonce (Number, a decimal JSON number), balance (QUANTITY) and bytecode_hash (DATA, 32 Bytes, or null for an account without code).
  • storages: Object - Keyed by the Keccak-256 hash of an address. Each value is an object with wiped (Boolean, whether the account’s existing storage is cleared first) and storage (an object keyed by the Keccak-256 hash of a slot, with QUANTITY values).

block parameter: QUANTITY|TAG|DATA - (optional) Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized; a 32-byte block hash is also accepted. See the default block parameter. Defaults to latest.

Returns

Array - Two elements:

  1. DATA, 32 Bytes - The state root after the changes. With an empty hashed state it is the block’s own state root.
  2. Object - The trie updates, with snake_case field names:
    • account_nodes: Object - Updated account-trie branch nodes, keyed by the node’s packed nibble path as hex without 0x. Each node has state_mask, tree_mask and hash_mask (numbers), hashes (an array of 32-byte hashes) and root_hash (a 32-byte hash or null).
    • removed_nodes: Array - Removed account-trie node paths, as hex strings without 0x.
    • storage_tries: Object - Keyed by hashed address. Each value has is_deleted (Boolean), storage_nodes and removed_nodes, in the same forms as the account-trie fields.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_stateRootWithUpdates","params":[{"accounts":{},"storages":{}},"latest"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    "0x10620e8dae725248ad5102bde4780354184a4c99b67d31cc857879b93b56c12a", // state root of the latest block
    {
      "account_nodes": {},
      "removed_nodes": [],
      "storage_tries": {}
    }
  ]
}

source

debug_traceBlock

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Decodes an RLP-encoded block, re-executes every transaction in it, and returns one trace per transaction. The block itself does not need to be in the node’s database, but its parent does: the node starts from the parent’s state, applies the block’s pre-transaction system calls, and then traces the transactions in order. A block whose parent the node does not have fails with error -32001 block not found: hash <parent hash>.

Tracing the genesis block (0x0) fails with error -32001 block not found: hash 0x0000000000000000000000000000000000000000000000000000000000000000: the tracer looks up the genesis block’s parent, whose hash is zero. trace_block returns [] for genesis instead.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Parameters

DATA - The RLP-encoded block, as debug_getRawBlock returns it. Bytes that do not decode as a block fail with error -32603.

Object - (optional) The tracing options, as for debug_traceTransaction: tracer, tracerConfig, and the struct logger fields enableMemory, disableStack, disableStorage and enableReturnData. When omitted, the struct logger runs with its defaults.

Returns

Array - One object per transaction, in block order:

  • result: Object - The tracer’s output for the transaction, in the shape debug_traceTransaction describes.
  • txHash: DATA, 32 Bytes - The transaction hash.

A block without transactions returns []. If any transaction fails to trace, the whole request fails with an error.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_traceBlock","params":["0xf902cdf9025da05f4827e84833a6da2a79b60373cdb82b5142ef13b1f56ac4790f2c6cfe3112cda04ef7ab685b6104388fca116acbe551cfa24018eb490cfcb7d0a26146a09ae25f94342b9c7c28d3f8680675ab13cf5f215897ae9f95a010620e8dae725248ad5102bde4780354184a4c99b67d31cc857879b93b56c12aa00a7b8d761adb0cfb421ea88e77c2e06ac53a9e9923353ca9b92f6fa92ee033f1a0f78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080018401c9c380825208846ac5cdc480a01fdc430e2e6fef312e3e6a5a122420cd8b5b825bd853869f9d3d9793721140a088000000000000000107a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a047b526d2ce612759ecbd3a2152384a7bf714b83b377323d1b931678148ef4584a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855f869b86702f8648207e18080078252089400000000000000000000000000000000000000006480c080a094e74c15812b53176f8000333c19c62852cf6f4e465adc9ce79f467f13a35736a005b01594de729950fc2153a8768ee98c0c2eb20b6e8c501903ca540083890429c0c0",{"tracer":"callTracer"}],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "result": {
        "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
        "gas": "0x5208",
        "gasUsed": "0x5208",
        "to": "0x0000000000000000000000000000000000000000",
        "input": "0x",
        "value": "0x64",
        "type": "CALL"
      },
      "txHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e"
    }
  ]
}

source

debug_traceBlockByHash

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Re-executes every transaction of a block the node holds, identified by hash, and returns one trace per transaction. It behaves as debug_traceBlockByNumber does.

Tracing the genesis block (0x0) fails with error -32001 block not found: hash 0x0000000000000000000000000000000000000000000000000000000000000000: the tracer looks up the genesis block’s parent, whose hash is zero. trace_block returns [] for genesis instead.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Parameters

DATA, 32 Bytes - The block hash. A block the node does not have fails with error -32001 and the message block not found: hash 0x….

Object - (optional) The tracing options, as for debug_traceTransaction: tracer, tracerConfig, and the struct logger fields enableMemory, disableStack, disableStorage and enableReturnData. When omitted, the struct logger runs with its defaults.

Returns

Array - One object per transaction, in block order:

  • result: Object - The tracer’s output for the transaction, in the shape debug_traceTransaction describes.
  • txHash: DATA, 32 Bytes - The transaction hash.

A block without transactions returns []. If any transaction fails to trace, the whole request fails with an error.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_traceBlockByHash","params":["0x1e0ed1376b6f8cc5deb9749a96c1207765ae9cd09ff46fe6200ca5b3f2913e8f",{"tracer":"callTracer"}],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "result": {
        "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
        "gas": "0x5208",
        "gasUsed": "0x5208",
        "to": "0x0000000000000000000000000000000000000000",
        "input": "0x",
        "value": "0x64",
        "type": "CALL"
      },
      "txHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e"
    }
  ]
}

source

debug_traceBlockByNumber

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Re-executes every transaction of a block the node holds and returns one trace per transaction. The node starts from the parent block’s state, applies the block’s pre-transaction system calls, and then traces the transactions in order, each on the state the previous one left.

Tracing the genesis block (0x0) fails with error -32001 block not found: hash 0x0000000000000000000000000000000000000000000000000000000000000000: the tracer looks up the genesis block’s parent, whose hash is zero. trace_block returns [] for genesis instead.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Parameters

block parameter: QUANTITY|TAG [Required] - Hexadecimal block number, or one of the string tags latest, earliest, safe, or finalized. See the default block parameter. A block the node does not have fails with error -32001 and a message such as block not found: 0x5.

Object - (optional) The tracing options, as for debug_traceTransaction: tracer, tracerConfig, and the struct logger fields enableMemory, disableStack, disableStorage and enableReturnData. When omitted, the struct logger runs with its defaults.

Returns

Array - One object per transaction, in block order:

  • result: Object - The tracer’s output for the transaction, in the shape debug_traceTransaction describes.
  • txHash: DATA, 32 Bytes - The transaction hash.

A block without transactions returns []. If any transaction fails to trace, the whole request fails with an error.

With the struct logger, each result is a struct log object; for the example block it is {"failed":false,"gas":21000,"returnValue":"0x","structLogs":[]}.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":["0x1",{"tracer":"callTracer"}],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "result": {
        "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
        "gas": "0x5208",
        "gasUsed": "0x5208",
        "to": "0x0000000000000000000000000000000000000000",
        "input": "0x",
        "value": "0x64",
        "type": "CALL"
      },
      "txHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e"
    }
  ]
}

source

debug_traceCall

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Warning

Fee fields at latest and pending

At latest and pending this method prices the call against the block header’s base fee, not the worker’s current epoch base fee that eth_call and eth_estimateGas use. A request that sets gasPrice, maxFeePerGas or maxPriorityFeePerGas can therefore be accepted by eth_call and rejected here, or the reverse; requests without fee fields are unaffected. Tracked in #1587.

Runs a call as eth_call does, without creating a transaction, and returns the trace the chosen tracer builds. Without txIndex the call runs on the state after the given block. With txIndex the node starts from the block’s parent state, applies the block’s pre-transaction system calls, replays the block’s transactions before that index, and runs the call on top.

Parameters

Object - The transaction call object:

  • from: DATA, 20 bytes - (optional) The address the call is sent from.
  • to: DATA, 20 bytes - The address the call is directed to.
  • gas: QUANTITY - (optional) Hexadecimal value of the gas provided for the call.
  • gasPrice: QUANTITY - (optional) Hexadecimal value of the gasPrice used for each paid gas.
  • maxPriorityFeePerGas: QUANTITY - (optional) Hexadecimal maximum fee, in Wei, the sender is willing to pay per gas above the base fee.
  • maxFeePerGas: QUANTITY - (optional) Hexadecimal maximum total fee (base fee + priority fee), in Wei, the sender is willing to pay per gas.
  • value: QUANTITY - (optional) Hexadecimal of the value sent with the call.
  • data: DATA - (optional) Hash of the method signature and encoded parameters. See Ethereum contract ABI specification.

block parameter: QUANTITY|TAG|DATA - (optional) Hexadecimal block number, or one of the string tags latest, pending, earliest, safe, or finalized; a 32-byte block hash is also accepted. See the default block parameter. Defaults to latest.

Object - (optional) The tracing options: the fields debug_traceTransaction takes (tracer, tracerConfig, enableMemory, disableStack, disableStorage, enableReturnData), plus:

  • stateOverrides: Object - (optional) Account overrides applied before the call, keyed by address, each with any of balance, nonce, code, and state or stateDiff.
  • blockOverrides: Object - (optional) Block environment overrides, such as number, time, gasLimit, coinbase and baseFee.
  • txIndex: QUANTITY - (optional) Run the call on the state just before the block’s transaction at this index. An index at or past the block’s transaction count fails with error -32602 tx_index <index> out of bounds for block with <count> transactions.

Returns

Object - The tracer’s output for the call, in the shapes debug_traceTransaction describes.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_traceCall","params":[{
  "from":"0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
  "to":"0x0000000000000000000000000000000000000000",
  "gas":"0x5208",
  "value":"0x64"},
  "latest",
  {"tracer":"callTracer"}],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
    "gas": "0x5208",
    "gasUsed": "0x5208",
    "to": "0x0000000000000000000000000000000000000000",
    "input": "0x",
    "value": "0x64",
    "type": "CALL"
  }
}

source

debug_traceCallMany

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Warning

Fee fields at latest and pending

At latest and pending this method prices the call against the block header’s base fee, not the worker’s current epoch base fee that eth_call and eth_estimateGas use. A request that sets gasPrice, maxFeePerGas or maxPriorityFeePerGas can therefore be accepted by eth_call and rejected here, or the reverse; requests without fee fields are unaffected. Tracked in #1587.

Runs bundles of calls in sequence on one block’s state, without creating transactions, and returns a trace for every call. Each call sees the state changes of the calls before it, across bundles. Each bundle after the first runs with the block number one higher and the timestamp 12 seconds later than the bundle before it.

When all of the block’s transactions are replayed (the default), the bundles run on the state after the block. With a smaller transactionIndex the node starts from the block’s parent state and replays that many of the block’s transactions first; this path does not apply the block’s pre-transaction system calls.

Parameters

Array - The bundles, run in order. Each bundle is an object:

  • transactions: Array - Transaction call objects, with the fields debug_traceCall takes.
  • blockOverride: Object - (optional) Block environment overrides for this bundle’s calls, such as number, time, gasLimit, coinbase and baseFee.

Object - (optional) The state context:

  • blockNumber: QUANTITY|TAG|DATA - (optional) The block to run on: a hexadecimal block number, a string tag such as latest or pending, or a 32-byte block hash. Defaults to latest.
  • transactionIndex: Number - (optional) How many of the block’s transactions to replay before the bundles, as a JSON integer. -1, the default, replays all of them.

Object - (optional) The tracing options: the fields debug_traceTransaction takes (tracer, tracerConfig, enableMemory, disableStack, disableStorage, enableReturnData), plus stateOverrides, which is applied once, before the first call. blockOverrides and txIndex are ignored here; use each bundle’s blockOverride and the state context instead.

Returns

Array - One array per bundle, each holding one trace per call in the bundle, in the shapes debug_traceTransaction describes. An empty bundle list fails with error -32602 bundles are empty.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_traceCallMany","params":[
  [{"transactions":[{
   "from":"0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
   "to":"0x0000000000000000000000000000000000000000",
   "gas":"0x5208",
   "value":"0x64"}]}],
  {"blockNumber":"latest","transactionIndex":-1},
  {"tracer":"callTracer"}],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    [
      {
        "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
        "gas": "0x5208",
        "gasUsed": "0x5208",
        "to": "0x0000000000000000000000000000000000000000",
        "input": "0x",
        "value": "0x64",
        "type": "CALL"
      }
    ]
  ]
}

source

debug_traceTransaction

Note

Served only when debug is named in --http.api or --ws.api; see Enabling Namespaces.

Re-executes a mined transaction and returns the trace the chosen tracer builds. The node starts from the state before the transaction’s block, applies the block’s pre-transaction system calls, replays the transactions that precede it in the block, and then traces the transaction itself.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Parameters

DATA, 32 Bytes - The transaction hash.

Object - (optional) The tracing options. When omitted, the struct logger runs with its defaults:

  • tracer: String - (optional) The tracer to run: callTracer, flatCallTracer, prestateTracer, 4byteTracer, muxTracer, noopTracer or erc7562Tracer, or the source code of a JavaScript tracer. Without it, the struct logger records every executed opcode.
  • tracerConfig: Object - (optional) Settings for the chosen tracer, for example {"onlyTopCall":true} or {"withLog":true} for callTracer, or {"diffMode":true} for prestateTracer.
  • enableMemory: Boolean - (optional) Struct logger only: record memory at each step. Defaults to false.
  • disableStack: Boolean - (optional) Struct logger only: leave out the stack at each step. Defaults to false.
  • disableStorage: Boolean - (optional) Struct logger only: leave out the storage slots each SLOAD and SSTORE touches. Defaults to false.
  • enableReturnData: Boolean - (optional) Struct logger only: record the last call’s return data at each step. Defaults to false.

Returns

Object - The tracer’s output. Two common shapes:

The struct logger (no tracer) returns:

  • failed: Boolean - Whether the transaction failed.
  • gas: Number - Gas used, as a decimal JSON number.
  • returnValue: DATA - The transaction’s return data.
  • structLogs: Array - One object per executed opcode with pc, op, gas, gasCost and depth (decimal numbers except op), plus stack, memory, storage and returnData when the options above enable them. A plain value transfer runs no code, so for the example transaction the struct logger returns {"failed":false,"gas":21000,"returnValue":"0x","structLogs":[]}.

callTracer returns the top-level call frame:

  • from: DATA, 20 Bytes - The caller.
  • gas: QUANTITY - Gas available to the call.
  • gasUsed: QUANTITY - Gas used by the call, including intrinsic gas for the top-level frame.
  • to: DATA, 20 Bytes - The callee, or the new contract for CREATE and CREATE2; omitted when there is none.
  • input: DATA - The call data.
  • output: DATA - The return data; omitted when there is none.
  • error: String - The error, if the call failed; omitted otherwise.
  • revertReason: String - The decoded revert reason, if the call reverted; omitted otherwise.
  • calls: Array - Child call frames of the same shape; omitted when there are none.
  • logs: Array - Logs emitted by the call, with tracerConfig {"withLog":true}; omitted when there are none.
  • value: QUANTITY - The value transferred.
  • type: String - The call type, such as CALL, STATICCALL, DELEGATECALL, CREATE or CREATE2.

A hash the node does not know fails with error -32001 transaction not found.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e",{"tracer":"callTracer"}],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
    "gas": "0x5208",
    "gasUsed": "0x5208",
    "to": "0x0000000000000000000000000000000000000000",
    "input": "0x",
    "value": "0x64",
    "type": "CALL"
  }
}

source

trace

The trace namespace serves Parity-style traces, the format of the Parity client (later OpenEthereum) that Erigon and reth also serve. A trace is a flat list of the call frames a transaction ran, each with its position in the call tree, its input and value, and its gas and output. The namespace also replays transactions with a VM instruction trace or a state diff, simulates calls and signed transactions without sending them, and searches a block range for traces by sender and receiver with trace_filter.

trace is off by default: a node serves it only when trace is named in --http.api or --ws.api, and all does not include it. The cost to serve it is high. Every method executes transactions in the EVM, and most of them re-execute historical blocks against archive state: trace_block, trace_replayBlockTransactions and trace_blockOpcodeGas replay a whole block, and trace_filter replays every block in its range. Serve it only from a private archive or indexer node, behind authentication or a method allowlist, and not from validators or public RPC nodes. See Enabling Namespaces for the flags.

Every trace_* request holds a permit from the node’s tracing permit pool while it runs, and waits for one when none is free. The pool’s size is --rpc.max-tracing-requests, and the pool is shared by the debug_trace* methods, the trace_* methods and eth_callBundle. trace_filter holds two permits and traces up to --rpc.max-tracing-requests blocks at a time. --rpc.max-trace-filter-blocks (default 100) caps how far apart a trace_filter request’s fromBlock and toBlock may be.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Telcoin Network has no block or uncle rewards, so these traces never include reward entries.

trace_call, trace_callMany, trace_rawTransaction, trace_replayTransaction and trace_replayBlockTransactions take a list of trace types and return each type requested: trace (the call frames), vmTrace (every executed instruction with its gas cost, stack pushes, and memory and storage writes) and stateDiff (the balance, nonce, code and storage changes of every account the transaction touched). A type that was not requested comes back as [] (trace) or null (vmTrace, stateDiff).

These traces describe the same execution as debug_traceTransaction with the callTracer, in a different shape. The callTracer returns one nested call frame per transaction with an upper-case type such as CALL. A Parity-style trace is a flat array in which each frame carries a traceAddress (its path in the call tree), a subtraces count, a lower-case type and callType, and the block and transaction it belongs to; blockNumber and transactionPosition are JSON numbers, not hex strings. The two also count gas differently: the callTracer includes the transaction’s intrinsic gas and Parity-style traces do not. For a plain transfer with a 21,000 gas limit, the callTracer reports gas and gasUsed 0x5208, while trace_transaction reports action.gas 0x0 and result.gasUsed 0x0.

Example values are illustrative; field names and types match what the node returns.

MethodDescription
trace_blockThe traces of every transaction in a block
trace_blockOpcodeGasHow often each opcode ran, and the gas it used, in every transaction of a block
trace_callExecutes a call without creating a transaction and returns the requested traces
trace_callManyExecutes a sequence of calls, each on the state the previous ones left, and returns their traces
trace_filterThe traces in a block range whose sender or receiver matches the filter
trace_getOne trace of a transaction, selected by its index
trace_rawTransactionExecutes a signed raw transaction without sending it and returns the requested traces
trace_replayBlockTransactionsReplays every transaction in a block and returns the requested traces
trace_replayTransactionReplays a transaction and returns the requested traces
trace_transactionAll traces of a transaction
trace_transactionOpcodeGasHow often each opcode ran, and the gas it used, in a transaction

trace_block

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Returns the Parity-style traces of every transaction in a block, in block order, by re-executing the block. Each transaction contributes its call frames in the order trace_transaction lists them.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Telcoin Network has no block or uncle rewards, so these traces never include reward entries.

Parameters

QUANTITY|TAG|DATA - Hexadecimal block number, a 32-byte block hash, or one of the string tags latest, earliest, safe or finalized. See the default block parameter.

Returns

Array - The traces of the block’s transactions, or null when the node does not know the block. A block without transactions, including the genesis block (0x0), returns []. Each trace is an object:

  • action: Object - What the frame did. For a call: from (DATA, 20 bytes, the caller), callType (call, callcode, delegatecall or staticcall), gas (QUANTITY, the gas available to the frame), input (DATA), to (DATA, 20 bytes) and value (QUANTITY, in wei). For a create: from, gas, init (DATA, the init code), value and creationMethod (create or create2). For a suicide (a SELFDESTRUCT): address, balance and refundAddress. The top-level frame’s gas is the transaction’s gas limit minus its intrinsic gas, so it is 0x0 for a plain transfer sent with a 21,000 gas limit.
  • blockHash: DATA, 32 Bytes - Hash of the block that holds the transaction.
  • blockNumber: Number - Number of that block, as a JSON number rather than a hex string.
  • error: String - The error the frame failed with. Absent when the frame succeeded.
  • result: Object - The frame’s outcome. For a call: gasUsed (QUANTITY) and output (DATA). For a create: address (DATA, 20 bytes, the new contract), code (DATA, the deployed code) and gasUsed. null for a suicide, and can be null for a frame that failed. The top-level frame’s gasUsed leaves out the intrinsic gas, so it is 0x0 for a plain transfer.
  • subtraces: Number - How many frames this frame called directly.
  • traceAddress: Array - The frame’s path in the call tree, as JSON numbers: [] for the top-level frame, [0] for its first child, [0, 1] for that child’s second child.
  • transactionHash: DATA, 32 Bytes - Hash of the transaction.
  • transactionPosition: Number - The transaction’s index in the block, as a JSON number.
  • type: String - The kind of frame: call, create or suicide.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_block","params":["0x1"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "action": {
        "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
        "callType": "call",
        "gas": "0x0",
        "input": "0x",
        "to": "0x0000000000000000000000000000000000000000",
        "value": "0x64"
      },
      "blockHash": "0x1e0ed1376b6f8cc5deb9749a96c1207765ae9cd09ff46fe6200ca5b3f2913e8f",
      "blockNumber": 1,
      "result": {
        "gasUsed": "0x0",
        "output": "0x"
      },
      "subtraces": 0,
      "traceAddress": [],
      "transactionHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e",
      "transactionPosition": 0,
      "type": "call"
    }
  ]
}

source

trace_blockOpcodeGas

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Replays every transaction in a block and returns, for each transaction, how many times each opcode ran and the gas those executions used. Each transaction’s entry has the shape trace_transactionOpcodeGas returns.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Parameters

QUANTITY|TAG|DATA - Hexadecimal block number, a 32-byte block hash, or one of the string tags latest, earliest, safe or finalized. See the default block parameter.

Returns

Object - The block’s opcode usage, or null when the node does not know the block:

  • blockHash: DATA, 32 Bytes - Hash of the block.
  • blockNumber: Number - Number of the block, as a JSON number rather than a hex string.
  • transactions: Array - One entry per transaction, in execution order:
    • transactionHash: DATA, 32 Bytes - Hash of the transaction.
    • opcodeGas: Array - One entry per opcode the transaction executed, in no particular order, each with opcode (String, the opcode name), count (Number, how many times it ran) and gasUsed (Number, the gas those executions used together).

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_blockOpcodeGas","params":["0x1"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "blockHash": "0x1e0ed1376b6f8cc5deb9749a96c1207765ae9cd09ff46fe6200ca5b3f2913e8f",
    "blockNumber": 1,
    "transactions": [
      {
        "transactionHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e",
        "opcodeGas": [] // a plain transfer executes no opcodes
      }
    ]
  }
}

source

trace_call

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Warning

Fee fields at latest and pending

At latest and pending this method prices the call against the block header’s base fee, not the worker’s current epoch base fee that eth_call and eth_estimateGas use. A request that sets gasPrice, maxFeePerGas or maxPriorityFeePerGas can therefore be accepted by eth_call and rejected here, or the reverse; requests without fee fields are unaffected. Tracked in #1587.

Executes a call on the state of a block without creating a transaction, and returns the trace types requested: the call frames, the executed instructions, the state changes, or any combination. Nothing is sent or stored.

Parameters

Object - The transaction call object, as for eth_call:

  • from: DATA, 20 bytes - (optional) The address the call is sent from.
  • to: DATA, 20 bytes - The address the call is directed to. Leave it out to simulate a contract creation.
  • gas: QUANTITY - (optional) The gas limit for the call. The top-level trace’s action.gas is this limit minus the call’s intrinsic gas.
  • gasPrice: QUANTITY - (optional) The price paid per unit of gas, for a legacy-style call.
  • maxFeePerGas: QUANTITY - (optional) The most, in wei, the sender pays per unit of gas, base fee and priority fee together.
  • maxPriorityFeePerGas: QUANTITY - (optional) The most, in wei, the sender pays per unit of gas above the base fee.
  • value: QUANTITY - (optional) The value sent with the call, in wei.
  • input: DATA - (optional) The call data. data is accepted as an alias.

Array - The trace types to return, any of "trace", "vmTrace" and "stateDiff".

QUANTITY|TAG|DATA - (optional) The block whose state the call runs on: a hexadecimal block number, a 32-byte block hash, or one of the string tags latest, earliest, safe or finalized. See the default block parameter. Defaults to latest.

Object - (optional) State overrides, keyed by account address. Each entry may set balance, nonce, code, state (replaces the account’s whole storage) or stateDiff (overrides single storage slots).

Object - (optional) Block overrides for the block the call runs in, such as number, time, gasLimit, coinbase or baseFee.

Returns

Object - The requested traces:

  • output: DATA - The return data of the top-level call; 0x for a plain transfer.
  • stateDiff: Object - With stateDiff requested, the state changes keyed by account address; otherwise null. Each account has balance, code, nonce and storage (keyed by slot). Each value is "=" when unchanged, {"+": value} when created, {"-": value} when removed, or {"*": {"from": old, "to": new}} when changed.
  • trace: Array - With trace requested, the call frames, top-level first; otherwise []. Each frame has type, action, error (only when the frame failed), result, subtraces and traceAddress, with the meanings trace_transaction gives them, and no block or transaction fields. The top-level frame’s action.gas and result.gasUsed leave out the intrinsic gas, so for a plain transfer with a 21,000 gas limit both are 0x0.
  • vmTrace: Object - With vmTrace requested, the instructions the top-level call executed; otherwise null. It has code (DATA, the code that ran) and ops, one entry per instruction with cost (Number, its gas cost), pc (Number, the program counter), ex (its effects: used, push for the stack items it pushed, mem for a memory write and store for a storage write), sub (the nested vmTrace of a CALL or CREATE, or null), and, when present, op (the opcode name) and idx.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_call","params":[{
  "from":"0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
  "to":"0x0000000000000000000000000000000000000000",
  "gas":"0x5208",
  "value":"0x64"},
  ["trace"],
  "latest"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "output": "0x",
    "stateDiff": null,
    "trace": [
      {
        "type": "call",
        "action": {
          "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
          "callType": "call",
          "gas": "0x0",
          "input": "0x",
          "to": "0x0000000000000000000000000000000000000000",
          "value": "0x64"
        },
        "result": {
          "gasUsed": "0x0",
          "output": "0x"
        },
        "subtraces": 0,
        "traceAddress": []
      }
    ],
    "vmTrace": null
  }
}

source

trace_callMany

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Warning

Fee fields at latest and pending

At latest and pending this method prices the call against the block header’s base fee, not the worker’s current epoch base fee that eth_call and eth_estimateGas use. A request that sets gasPrice, maxFeePerGas or maxPriorityFeePerGas can therefore be accepted by eth_call and rejected here, or the reverse; requests without fee fields are unaffected. Tracked in #1587.

Executes a sequence of calls on the state of a block, each one on top of the state changes of the calls before it, and returns the trace types requested for each call. This traces dependent transactions, such as an approval followed by a transfer, without sending them. Unlike trace_call, this method takes no state or block overrides.

Parameters

Array - The calls, in execution order. Each call is a two-element array: the transaction call object, with the fields trace_call lists, and the trace types to return for that call, any of "trace", "vmTrace" and "stateDiff".

QUANTITY|TAG|DATA - (optional) The block whose state the first call runs on: a hexadecimal block number, a 32-byte block hash, or one of the string tags latest, earliest, pending, safe or finalized. See the default block parameter. Defaults to pending.

Returns

Array - One object per call, in the order of the calls. Each object has:

  • output: DATA - The return data of the top-level call; 0x for a plain transfer.
  • stateDiff: Object - With stateDiff requested, the state changes keyed by account address; otherwise null. Each account has balance, code, nonce and storage (keyed by slot). Each value is "=" when unchanged, {"+": value} when created, {"-": value} when removed, or {"*": {"from": old, "to": new}} when changed.
  • trace: Array - With trace requested, the call frames, top-level first; otherwise []. Each frame has type, action, error (only when the frame failed), result, subtraces and traceAddress, with the meanings trace_transaction gives them, and no block or transaction fields. The top-level frame’s action.gas and result.gasUsed leave out the intrinsic gas, so for a plain transfer with a 21,000 gas limit both are 0x0.
  • vmTrace: Object - With vmTrace requested, the instructions the top-level call executed; otherwise null. It has code (DATA, the code that ran) and ops, one entry per instruction with cost (Number, its gas cost), pc (Number, the program counter), ex (its effects: used, push for the stack items it pushed, mem for a memory write and store for a storage write), sub (the nested vmTrace of a CALL or CREATE, or null), and, when present, op (the opcode name) and idx.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_callMany","params":[[
  [{"from":"0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7","to":"0x0000000000000000000000000000000000000000","gas":"0x5208","value":"0x64"},["trace"]],
  [{"from":"0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7","to":"0x0000000000000000000000000000000000000000","gas":"0x5208","value":"0x64"},["trace"]]],
  "latest"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "output": "0x",
      "stateDiff": null,
      "trace": [
        {
          "type": "call",
          "action": {
            "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
            "callType": "call",
            "gas": "0x0",
            "input": "0x",
            "to": "0x0000000000000000000000000000000000000000",
            "value": "0x64"
          },
          "result": {
            "gasUsed": "0x0",
            "output": "0x"
          },
          "subtraces": 0,
          "traceAddress": []
        }
      ],
      "vmTrace": null
    },
    {
      "output": "0x",
      "stateDiff": null,
      "trace": [
        {
          "type": "call",
          "action": {
            "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
            "callType": "call",
            "gas": "0x0",
            "input": "0x",
            "to": "0x0000000000000000000000000000000000000000",
            "value": "0x64"
          },
          "result": {
            "gasUsed": "0x0",
            "output": "0x"
          },
          "subtraces": 0,
          "traceAddress": []
        }
      ],
      "vmTrace": null
    }
  ]
}

source

trace_filter

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Returns the Parity-style traces in a block range whose sender or receiver matches the filter, much as eth_getLogs does for logs. The node keeps no index of trace addresses: it re-executes every transaction in every block of the range and applies the address filter to the traces afterwards, so a narrow address filter does not make a request cheaper. A request holds two permits from the tracing permit pool and traces up to --rpc.max-tracing-requests blocks at a time.

toBlock minus fromBlock may be at most --rpc.max-trace-filter-blocks (default 100). A wider range fails with -32602 and the message Block range too large; currently limited to 100 blocks; the message says 100 whatever the configured limit is. Because fromBlock defaults to 0x0 and toBlock to the latest block, a request that sets neither field fails this check once the latest block number is above 100.

A fromBlock or toBlock past the node’s latest block fails with -32001 and a block not found message, and a fromBlock greater than toBlock fails with -32602 and the message invalid parameters: fromBlock cannot be greater than toBlock.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Telcoin Network has no block or uncle rewards, so these traces never include reward entries.

Parameters

Object - The filter object. A field not listed here fails the request with -32602.

  • fromBlock: QUANTITY - (optional) The first block to trace, as a hexadecimal block number; tags such as latest are not accepted. Defaults to 0x0.
  • toBlock: QUANTITY - (optional) The last block to trace, inclusive, as a hexadecimal block number. Defaults to the latest block.
  • fromAddress: Array - (optional) Sender addresses (DATA, 20 bytes each) to match: a call’s from, a create’s from, or the contract a suicide destroyed.
  • toAddress: Array - (optional) Receiver addresses (DATA, 20 bytes each) to match: a call’s to, the contract a create deployed, or a suicide’s refundAddress.
  • mode: String - (optional) How the two address lists combine: union (the default) or intersection. With union a trace matches when its sender is in fromAddress or its receiver is in toAddress; when only one list is given only that list is checked, and with neither list every trace matches. With intersection a trace matches only when both its sender and its receiver match, and an empty or missing list matches any address.
  • after: Number - (optional) How many matching traces to skip, as a JSON number rather than a hex string.
  • count: Number - (optional) The most matching traces to return, as a JSON number rather than a hex string.

Returns

Array - The matching traces in block order. Each trace is an object:

  • action: Object - What the frame did. For a call: from (DATA, 20 bytes, the caller), callType (call, callcode, delegatecall or staticcall), gas (QUANTITY, the gas available to the frame), input (DATA), to (DATA, 20 bytes) and value (QUANTITY, in wei). For a create: from, gas, init (DATA, the init code), value and creationMethod (create or create2). For a suicide (a SELFDESTRUCT): address, balance and refundAddress. The top-level frame’s gas is the transaction’s gas limit minus its intrinsic gas, so it is 0x0 for a plain transfer sent with a 21,000 gas limit.
  • blockHash: DATA, 32 Bytes - Hash of the block that holds the transaction.
  • blockNumber: Number - Number of that block, as a JSON number rather than a hex string.
  • error: String - The error the frame failed with. Absent when the frame succeeded.
  • result: Object - The frame’s outcome. For a call: gasUsed (QUANTITY) and output (DATA). For a create: address (DATA, 20 bytes, the new contract), code (DATA, the deployed code) and gasUsed. null for a suicide, and can be null for a frame that failed. The top-level frame’s gasUsed leaves out the intrinsic gas, so it is 0x0 for a plain transfer.
  • subtraces: Number - How many frames this frame called directly.
  • traceAddress: Array - The frame’s path in the call tree, as JSON numbers: [] for the top-level frame, [0] for its first child, [0, 1] for that child’s second child.
  • transactionHash: DATA, 32 Bytes - Hash of the transaction.
  • transactionPosition: Number - The transaction’s index in the block, as a JSON number.
  • type: String - The kind of frame: call, create or suicide.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_filter","params":[{"fromBlock":"0x0","toBlock":"0x1"}],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "action": {
        "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
        "callType": "call",
        "gas": "0x0",
        "input": "0x",
        "to": "0x0000000000000000000000000000000000000000",
        "value": "0x64"
      },
      "blockHash": "0x1e0ed1376b6f8cc5deb9749a96c1207765ae9cd09ff46fe6200ca5b3f2913e8f",
      "blockNumber": 1,
      "result": {
        "gasUsed": "0x0",
        "output": "0x"
      },
      "subtraces": 0,
      "traceAddress": [],
      "transactionHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e",
      "transactionPosition": 0,
      "type": "call"
    }
  ]
}

source

trace_get

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Returns one Parity-style trace of a mined transaction, selected by its index in the list trace_transaction returns: index 0 is the top-level call. The node traces the whole transaction to answer, so the cost is the same as trace_transaction.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Parameters

DATA, 32 Bytes - The transaction hash.

Array - The trace index, as a one-element list. Each index may be a hex string ("0x0"), a decimal string or a JSON number. Only one index is supported: a list with no index or with more than one returns null.

Returns

Object - The trace at that index, or null when the node does not know the transaction, when the index is past the transaction’s last trace, or when the list does not hold exactly one index:

  • action: Object - What the frame did. For a call: from (DATA, 20 bytes, the caller), callType (call, callcode, delegatecall or staticcall), gas (QUANTITY, the gas available to the frame), input (DATA), to (DATA, 20 bytes) and value (QUANTITY, in wei). For a create: from, gas, init (DATA, the init code), value and creationMethod (create or create2). For a suicide (a SELFDESTRUCT): address, balance and refundAddress. The top-level frame’s gas is the transaction’s gas limit minus its intrinsic gas, so it is 0x0 for a plain transfer sent with a 21,000 gas limit.
  • blockHash: DATA, 32 Bytes - Hash of the block that holds the transaction.
  • blockNumber: Number - Number of that block, as a JSON number rather than a hex string.
  • error: String - The error the frame failed with. Absent when the frame succeeded.
  • result: Object - The frame’s outcome. For a call: gasUsed (QUANTITY) and output (DATA). For a create: address (DATA, 20 bytes, the new contract), code (DATA, the deployed code) and gasUsed. null for a suicide, and can be null for a frame that failed. The top-level frame’s gasUsed leaves out the intrinsic gas, so it is 0x0 for a plain transfer.
  • subtraces: Number - How many frames this frame called directly.
  • traceAddress: Array - The frame’s path in the call tree, as JSON numbers: [] for the top-level frame, [0] for its first child, [0, 1] for that child’s second child.
  • transactionHash: DATA, 32 Bytes - Hash of the transaction.
  • transactionPosition: Number - The transaction’s index in the block, as a JSON number.
  • type: String - The kind of frame: call, create or suicide.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_get","params":["0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e",["0x0"]],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "action": {
      "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
      "callType": "call",
      "gas": "0x0",
      "input": "0x",
      "to": "0x0000000000000000000000000000000000000000",
      "value": "0x64"
    },
    "blockHash": "0x1e0ed1376b6f8cc5deb9749a96c1207765ae9cd09ff46fe6200ca5b3f2913e8f",
    "blockNumber": 1,
    "result": {
      "gasUsed": "0x0",
      "output": "0x"
    },
    "subtraces": 0,
    "traceAddress": [],
    "transactionHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e",
    "transactionPosition": 0,
    "type": "call"
  }
}

source

trace_rawTransaction

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Warning

Fee fields at latest and pending

At latest and pending this method checks the transaction’s fee cap against the block header’s base fee, not the worker’s current epoch base fee that the transaction pool and eth_call use. A signed transaction always carries a fee cap, so a transaction the node would accept can fail here with a fee-cap error, or the reverse. Tracing against a numbered block avoids the difference. Tracked in #1587.

Executes a signed raw transaction on the state of a block and returns the trace types requested, without sending it to the transaction pool. The node recovers the sender from the signature, so the transaction must be signed; to trace an unsigned call, use trace_call.

The example traces the transfer that block 1 holds against the state at block 0x0, which is the state block 1 executed it on.

Parameters

DATA - The signed transaction, EIP-2718 encoded, as eth_sendRawTransaction takes it.

Array - The trace types to return, any of "trace", "vmTrace" and "stateDiff".

QUANTITY|TAG|DATA - (optional) The block whose state the transaction runs on: a hexadecimal block number, a 32-byte block hash, or one of the string tags latest, earliest, safe or finalized. See the default block parameter. Defaults to latest.

Returns

Object - The requested traces:

  • output: DATA - The return data of the top-level call; 0x for a plain transfer.
  • stateDiff: Object - With stateDiff requested, the state changes keyed by account address; otherwise null. Each account has balance, code, nonce and storage (keyed by slot). Each value is "=" when unchanged, {"+": value} when created, {"-": value} when removed, or {"*": {"from": old, "to": new}} when changed.
  • trace: Array - With trace requested, the call frames, top-level first; otherwise []. Each frame has type, action, error (only when the frame failed), result, subtraces and traceAddress, with the meanings trace_transaction gives them, and no block or transaction fields. The top-level frame’s action.gas and result.gasUsed leave out the intrinsic gas, so for a plain transfer with a 21,000 gas limit both are 0x0.
  • vmTrace: Object - With vmTrace requested, the instructions the top-level call executed; otherwise null. It has code (DATA, the code that ran) and ops, one entry per instruction with cost (Number, its gas cost), pc (Number, the program counter), ex (its effects: used, push for the stack items it pushed, mem for a memory write and store for a storage write), sub (the nested vmTrace of a CALL or CREATE, or null), and, when present, op (the opcode name) and idx.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_rawTransaction","params":["0x02f8648207e18080078252089400000000000000000000000000000000000000006480c080a094e74c15812b53176f8000333c19c62852cf6f4e465adc9ce79f467f13a35736a005b01594de729950fc2153a8768ee98c0c2eb20b6e8c501903ca540083890429",["trace"],"0x0"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "output": "0x",
    "stateDiff": null,
    "trace": [
      {
        "type": "call",
        "action": {
          "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
          "callType": "call",
          "gas": "0x0",
          "input": "0x",
          "to": "0x0000000000000000000000000000000000000000",
          "value": "0x64"
        },
        "result": {
          "gasUsed": "0x0",
          "output": "0x"
        },
        "subtraces": 0,
        "traceAddress": []
      }
    ],
    "vmTrace": null
  }
}

source

trace_replayBlockTransactions

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Replays every transaction in a block and returns the trace types requested for each one, in block order. Each entry has the shape trace_replayTransaction returns, plus the transaction’s hash.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Telcoin Network has no block or uncle rewards, so these traces never include reward entries.

Parameters

QUANTITY|TAG|DATA - Hexadecimal block number, a 32-byte block hash, or one of the string tags latest, earliest, safe or finalized. See the default block parameter.

Array - The trace types to return, any of "trace", "vmTrace" and "stateDiff".

Returns

Array - One object per transaction, or null when the node does not know the block. A block without transactions returns []. Each object has:

  • output: DATA - The return data of the top-level call; 0x for a plain transfer.
  • stateDiff: Object - With stateDiff requested, the state changes keyed by account address; otherwise null. Each account has balance, code, nonce and storage (keyed by slot). Each value is "=" when unchanged, {"+": value} when created, {"-": value} when removed, or {"*": {"from": old, "to": new}} when changed.
  • trace: Array - With trace requested, the call frames, top-level first; otherwise []. Each frame has type, action, error (only when the frame failed), result, subtraces and traceAddress, with the meanings trace_transaction gives them, and no block or transaction fields. The top-level frame’s action.gas and result.gasUsed leave out the intrinsic gas, so for a plain transfer with a 21,000 gas limit both are 0x0.
  • vmTrace: Object - With vmTrace requested, the instructions the top-level call executed; otherwise null. It has code (DATA, the code that ran) and ops, one entry per instruction with cost (Number, its gas cost), pc (Number, the program counter), ex (its effects: used, push for the stack items it pushed, mem for a memory write and store for a storage write), sub (the nested vmTrace of a CALL or CREATE, or null), and, when present, op (the opcode name) and idx.
  • transactionHash: DATA, 32 Bytes - Hash of the replayed transaction.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_replayBlockTransactions","params":["0x1",["trace"]],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "output": "0x",
      "stateDiff": null,
      "trace": [
        {
          "type": "call",
          "action": {
            "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
            "callType": "call",
            "gas": "0x0",
            "input": "0x",
            "to": "0x0000000000000000000000000000000000000000",
            "value": "0x64"
          },
          "result": {
            "gasUsed": "0x0",
            "output": "0x"
          },
          "subtraces": 0,
          "traceAddress": []
        }
      ],
      "vmTrace": null,
      "transactionHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e"
    }
  ]
}

source

trace_replayTransaction

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Replays a mined transaction and returns the trace types requested: the call frames, the executed instructions, the state changes, or any combination. The node re-executes the transaction’s block up to and including the transaction. A transaction the node does not know fails with -32001 and the message transaction not found.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Parameters

DATA, 32 Bytes - The transaction hash.

Array - The trace types to return, any of "trace", "vmTrace" and "stateDiff".

Returns

Object - The requested traces:

  • output: DATA - The return data of the top-level call; 0x for a plain transfer.
  • stateDiff: Object - With stateDiff requested, the state changes keyed by account address; otherwise null. Each account has balance, code, nonce and storage (keyed by slot). Each value is "=" when unchanged, {"+": value} when created, {"-": value} when removed, or {"*": {"from": old, "to": new}} when changed.
  • trace: Array - With trace requested, the call frames, top-level first; otherwise []. Each frame has type, action, error (only when the frame failed), result, subtraces and traceAddress, with the meanings trace_transaction gives them, and no block or transaction fields. The top-level frame’s action.gas and result.gasUsed leave out the intrinsic gas, so for a plain transfer with a 21,000 gas limit both are 0x0.
  • vmTrace: Object - With vmTrace requested, the instructions the top-level call executed; otherwise null. It has code (DATA, the code that ran) and ops, one entry per instruction with cost (Number, its gas cost), pc (Number, the program counter), ex (its effects: used, push for the stack items it pushed, mem for a memory write and store for a storage write), sub (the nested vmTrace of a CALL or CREATE, or null), and, when present, op (the opcode name) and idx.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_replayTransaction","params":["0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e",["trace"]],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "output": "0x",
    "stateDiff": null,
    "trace": [
      {
        "type": "call",
        "action": {
          "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
          "callType": "call",
          "gas": "0x0",
          "input": "0x",
          "to": "0x0000000000000000000000000000000000000000",
          "value": "0x64"
        },
        "result": {
          "gasUsed": "0x0",
          "output": "0x"
        },
        "subtraces": 0,
        "traceAddress": []
      }
    ],
    "vmTrace": null
  }
}

source

trace_transaction

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Returns every call frame of a mined transaction as a flat list of Parity-style traces, the top-level call first. The node re-executes the transaction’s block up to and including the transaction to build them.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Parameters

DATA, 32 Bytes - The transaction hash.

Returns

Array - The transaction’s traces, or null when the node does not know the transaction. Each trace is an object:

  • action: Object - What the frame did. For a call: from (DATA, 20 bytes, the caller), callType (call, callcode, delegatecall or staticcall), gas (QUANTITY, the gas available to the frame), input (DATA), to (DATA, 20 bytes) and value (QUANTITY, in wei). For a create: from, gas, init (DATA, the init code), value and creationMethod (create or create2). For a suicide (a SELFDESTRUCT): address, balance and refundAddress. The top-level frame’s gas is the transaction’s gas limit minus its intrinsic gas, so it is 0x0 for a plain transfer sent with a 21,000 gas limit.
  • blockHash: DATA, 32 Bytes - Hash of the block that holds the transaction.
  • blockNumber: Number - Number of that block, as a JSON number rather than a hex string.
  • error: String - The error the frame failed with. Absent when the frame succeeded.
  • result: Object - The frame’s outcome. For a call: gasUsed (QUANTITY) and output (DATA). For a create: address (DATA, 20 bytes, the new contract), code (DATA, the deployed code) and gasUsed. null for a suicide, and can be null for a frame that failed. The top-level frame’s gasUsed leaves out the intrinsic gas, so it is 0x0 for a plain transfer.
  • subtraces: Number - How many frames this frame called directly.
  • traceAddress: Array - The frame’s path in the call tree, as JSON numbers: [] for the top-level frame, [0] for its first child, [0, 1] for that child’s second child.
  • transactionHash: DATA, 32 Bytes - Hash of the transaction.
  • transactionPosition: Number - The transaction’s index in the block, as a JSON number.
  • type: String - The kind of frame: call, create or suicide.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_transaction","params":["0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "action": {
        "from": "0xb14d3c4f5fbfbcfb98af2d330000d49c95b93aa7",
        "callType": "call",
        "gas": "0x0",
        "input": "0x",
        "to": "0x0000000000000000000000000000000000000000",
        "value": "0x64"
      },
      "blockHash": "0x1e0ed1376b6f8cc5deb9749a96c1207765ae9cd09ff46fe6200ca5b3f2913e8f",
      "blockNumber": 1,
      "result": {
        "gasUsed": "0x0",
        "output": "0x"
      },
      "subtraces": 0,
      "traceAddress": [],
      "transactionHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e",
      "transactionPosition": 0,
      "type": "call"
    }
  ]
}

source

trace_transactionOpcodeGas

Note

Served only when trace is named in --http.api or --ws.api; see Enabling Namespaces.

Replays a mined transaction and returns, for each opcode it executed, how many times the opcode ran and the gas those executions used. The node re-executes the transaction’s block up to and including the transaction. A plain transfer to an address without code runs no EVM instructions, so its list is empty.

Traces cover the block’s transactions only. The system calls a block runs before its transactions (EIP-4788 beacon root, EIP-2935 block hashes) and the epoch-closing system calls (applyIncentives and concludeEpoch) are not transactions, so no tracer shows them.

Parameters

DATA, 32 Bytes - The transaction hash.

Returns

Object - The transaction’s opcode usage, or null when the node does not know the transaction:

  • transactionHash: DATA, 32 Bytes - Hash of the transaction.
  • opcodeGas: Array - One entry per opcode the transaction executed, in no particular order:
    • opcode: String - The opcode name, such as PUSH1 or SSTORE.
    • count: Number - How many times the opcode ran.
    • gasUsed: Number - The gas all those executions used together, as a JSON number.

Example

Request

curl http://localhost:8545 \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{"jsonrpc":"2.0","method":"trace_transactionOpcodeGas","params":["0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e"],"id":1}'

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "transactionHash": "0xa794c7eb915c26fdb77242159053c1a36241bcd016b671fb07abe3fa9050805e",
    "opcodeGas": [] // a plain transfer executes no opcodes
  }
}

source

Reference

Supporting reference material for the network.

Stablecoin Contracts provides the ERC-20 ABI for the stablecoins deployed on Telcoin Network. Adiri covers the origin of the testnet’s name.

Stablecoin Contracts

More information can be found on github.

ABI

[
    {
      "inputs": [],
      "name": "AccessControlBadConfirmation",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        },
        {
          "internalType": "bytes32",
          "name": "neededRole",
          "type": "bytes32"
        }
      ],
      "name": "AccessControlUnauthorizedAccount",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "target",
          "type": "address"
        }
      ],
      "name": "AddressEmptyCode",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "AddressInsufficientBalance",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "user",
          "type": "address"
        }
      ],
      "name": "AlreadyBlacklisted",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "Blacklisted",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "ECDSAInvalidSignature",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "length",
          "type": "uint256"
        }
      ],
      "name": "ECDSAInvalidSignatureLength",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "bytes32",
          "name": "s",
          "type": "bytes32"
        }
      ],
      "name": "ECDSAInvalidSignatureS",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "allowance",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "needed",
          "type": "uint256"
        }
      ],
      "name": "ERC20InsufficientAllowance",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "sender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "balance",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "needed",
          "type": "uint256"
        }
      ],
      "name": "ERC20InsufficientBalance",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "approver",
          "type": "address"
        }
      ],
      "name": "ERC20InvalidApprover",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "receiver",
          "type": "address"
        }
      ],
      "name": "ERC20InvalidReceiver",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "sender",
          "type": "address"
        }
      ],
      "name": "ERC20InvalidSender",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        }
      ],
      "name": "ERC20InvalidSpender",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "deadline",
          "type": "uint256"
        }
      ],
      "name": "ERC2612ExpiredSignature",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "signer",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        }
      ],
      "name": "ERC2612InvalidSigner",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "FailedInnerCall",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "currentNonce",
          "type": "uint256"
        }
      ],
      "name": "InvalidAccountNonce",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidInitialization",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "user",
          "type": "address"
        }
      ],
      "name": "NotBlacklisted",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "NotInitializing",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "token",
          "type": "address"
        }
      ],
      "name": "SafeERC20FailedOperation",
      "type": "error"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "address",
          "name": "user",
          "type": "address"
        }
      ],
      "name": "AddedBlacklist",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Approval",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [],
      "name": "EIP712DomainChanged",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "version",
          "type": "uint64"
        }
      ],
      "name": "Initialized",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "address",
          "name": "user",
          "type": "address"
        }
      ],
      "name": "RemovedBlacklist",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "role",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "previousAdminRole",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "newAdminRole",
          "type": "bytes32"
        }
      ],
      "name": "RoleAdminChanged",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "role",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "account",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "sender",
          "type": "address"
        }
      ],
      "name": "RoleGranted",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "role",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "account",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "sender",
          "type": "address"
        }
      ],
      "name": "RoleRevoked",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Transfer",
      "type": "event"
    },
    {
      "inputs": [],
      "name": "BLACKLISTER_ROLE",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "BURNER_ROLE",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "DEFAULT_ADMIN_ROLE",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "DOMAIN_SEPARATOR",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "MINTER_ROLE",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "SUPPORT_ROLE",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "user",
          "type": "address"
        }
      ],
      "name": "addBlackList",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        }
      ],
      "name": "allowance",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "approve",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "balanceOf",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "user",
          "type": "address"
        }
      ],
      "name": "blacklisted",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "burn",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "burnFrom",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "decimals",
      "outputs": [
        {
          "internalType": "uint8",
          "name": "",
          "type": "uint8"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "eip712Domain",
      "outputs": [
        {
          "internalType": "bytes1",
          "name": "fields",
          "type": "bytes1"
        },
        {
          "internalType": "string",
          "name": "name",
          "type": "string"
        },
        {
          "internalType": "string",
          "name": "version",
          "type": "string"
        },
        {
          "internalType": "uint256",
          "name": "chainId",
          "type": "uint256"
        },
        {
          "internalType": "address",
          "name": "verifyingContract",
          "type": "address"
        },
        {
          "internalType": "bytes32",
          "name": "salt",
          "type": "bytes32"
        },
        {
          "internalType": "uint256[]",
          "name": "extensions",
          "type": "uint256[]"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "contract ERC20PermitUpgradeable",
          "name": "token",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "destination",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "erc20Rescue",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes32",
          "name": "role",
          "type": "bytes32"
        }
      ],
      "name": "getRoleAdmin",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes32",
          "name": "role",
          "type": "bytes32"
        },
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "grantRole",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes32",
          "name": "role",
          "type": "bytes32"
        },
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "hasRole",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "name_",
          "type": "string"
        },
        {
          "internalType": "string",
          "name": "symbol_",
          "type": "string"
        },
        {
          "internalType": "uint8",
          "name": "decimals_",
          "type": "uint8"
        }
      ],
      "name": "initialize",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "mint",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "mintTo",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "name",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        }
      ],
      "name": "nonces",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "deadline",
          "type": "uint256"
        },
        {
          "internalType": "uint8",
          "name": "v",
          "type": "uint8"
        },
        {
          "internalType": "bytes32",
          "name": "r",
          "type": "bytes32"
        },
        {
          "internalType": "bytes32",
          "name": "s",
          "type": "bytes32"
        }
      ],
      "name": "permit",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "user",
          "type": "address"
        }
      ],
      "name": "removeBlackList",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes32",
          "name": "role",
          "type": "bytes32"
        },
        {
          "internalType": "address",
          "name": "callerConfirmation",
          "type": "address"
        }
      ],
      "name": "renounceRole",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes32",
          "name": "role",
          "type": "bytes32"
        },
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "revokeRole",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes4",
          "name": "interfaceId",
          "type": "bytes4"
        }
      ],
      "name": "supportsInterface",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "symbol",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "totalSupply",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "transfer",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "transferFrom",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]

Adiri

The name “Adiri” for testnet is inspired by two rich sources of meaning: the intriguing region on Saturn’s moon Titan, and the peaceful paradise from Melanesian mythology.

Titan’s atmosphere is dense and complex, with methane clouds, thick layers of haze, and liquid hydrocarbons that flow freely on the moon (mainly methane and ethane). Adiri is a prominent region in this atmospheric tapestry. For astronomers, “Adiri” refers to a bright, extensive cloud-covered region that stands out due to its albedo. Albedo features are regions that reflect light differently from their surroundings, making them stand out in visual observations. This bright spot amidst Titan’s dense and complex atmosphere symbolizes clarity and stability.

In Melanesian mythology, “Adiri” is often depicted as a paradise—a tranquil, peaceful afterlife where souls find rest. This mythological association adds a layer of serene beauty and otherworldly quality to the name, echoing the testnet’s goal to provide a stable and clear environment for testing amidst the often turbulent and complex industry.

Adiri Testnet aims to rise above the clouds engulfing other chains to offer transparency and security. Our goal is to create a network that, like the bright region on Titan, stands out amongst its surroundings and like the mythological paradise, offers a tranquil space for development and innovation.

Staking

How validator staking works on Telcoin Network. Membership is permissioned: governance issues a ConsensusNFT to approved operators, who stake TEL through the ConsensusRegistry contract and earn a share of each epoch’s issuance for the consensus work they perform.

How Staking Works covers stake versions, delegation, rewards, and the validator lifecycle. Why Membership Model explains the reasoning behind permissioned membership. How to Stake is the step-by-step operator guide, and Future Direction discusses validator scalability limits and planned changes.

How Staking Works

Stake Versions

Every validator joins the network under a specific stake version. Each version defines a StakeConfig:

ParameterDescription
stakeAmountExact TEL a validator must stake to join
minWithdrawAmountMinimum claimable reward threshold
epochIssuanceTotal TEL distributed as rewards per epoch
epochDurationLength of each epoch in seconds

Governance can create new versions by calling upgradeStakeVersion(). The new version takes effect next epoch. Validators who already staked retain the version they joined under by default – their stakeVersion is set at stake time and does not change automatically.

A validator (or its delegator) can move to a newer version in-place by calling requestStakeVersionChange(validatorAddress, targetVersion). Upgrades are one-way – only to a strictly newer version – and are available while the validator is Staked, PendingActivation, or Active. A Staked validator moves at once. For a validator in service (PendingActivation or Active) the request is queued and settles at an epoch boundary: an increase at the end of the epoch in which it was requested, a decrease one epoch later. If the new version requires more stake, the caller must send the exact deficit with the request, and the registry holds it until the change settles. If it requires less, the surplus goes to the reward recipient: a Staked validator’s surplus is paid at once, and a queued change credits it for withdrawal with claimRefund(). cancelStakeVersionChange(validatorAddress) withdraws a queued request and returns any deficit paid.

How Rewards Are Calculated

At each epoch boundary, applyIncentives() distributes the epoch’s issuance across validators. Each validator’s reward is determined by their weight:

weight = stakeAmount * consensusHeaderCount

Where stakeAmount comes from the validator’s own current stake version, and consensusHeaderCount is how many consensus headers they produced that epoch.

Each validator’s share of the epoch issuance is:

reward = (epochIssuance * weight) / totalWeight

Where totalWeight is the sum of all validators’ weights. Any issuance left undistributed by integer rounding rolls over into the next epoch’s reward pool.

In a committee where all validators share the same stake version, the stakeAmount cancels out and rewards depend purely on header count. But in a mixed committee – where validators joined under different versions with different stake amounts – the version affects reward weight directly.

Example 1: Mixed Committee After a Stake Increase

Governance raises the required stake from 100,000 TEL to 250,000 TEL.

Configuration:

  • Version 0: stakeAmount = 100,000 TEL
  • Version 1: stakeAmount = 250,000 TEL
  • Epoch issuance: 10,000 TEL

Committee (4 validators, each producing 10 headers):

ValidatorVersionStakeHeadersWeight
Av0100,000101,000,000
Bv0100,000101,000,000
Cv1250,000102,500,000
Dv1250,000102,500,000
Total7,000,000

Reward distribution:

ValidatorCalculationReward
A10,000 * 1,000,000 / 7,000,000~1,428.57 TEL
B10,000 * 1,000,000 / 7,000,000~1,428.57 TEL
C10,000 * 2,500,000 / 7,000,000~3,571.43 TEL
D10,000 * 2,500,000 / 7,000,000~3,571.43 TEL

Version 1 validators earn 2.5x more per header than version 0 validators, directly proportional to the stake ratio (250,000 / 100,000).

Example 2: Mixed Committee After a Stake Decrease

Governance lowers the required stake from 200,000 TEL to 50,000 TEL to reduce barriers to entry.

Configuration:

  • Version 0: stakeAmount = 200,000 TEL
  • Version 1: stakeAmount = 50,000 TEL
  • Epoch issuance: 10,000 TEL

Committee (4 validators, each producing 10 headers):

ValidatorVersionStakeHeadersWeight
Av0200,000102,000,000
Bv0200,000102,000,000
Cv150,00010500,000
Dv150,00010500,000
Total5,000,000

Reward distribution:

ValidatorCalculationReward
A10,000 * 2,000,000 / 5,000,0004,000 TEL
B10,000 * 2,000,000 / 5,000,0004,000 TEL
C10,000 * 500,000 / 5,000,0001,000 TEL
D10,000 * 500,000 / 5,000,0001,000 TEL

Legacy version 0 validators earn 4x more per header than version 1 validators. Their higher initial stake gives them greater weight in the distribution even though newer validators paid less to join.

Example 3: Performance Differences in a Mixed Committee

Stake versions set the baseline weight, but header production still matters. A high-performing validator on a lower stake version can out-earn an underperforming validator on a higher version.

Configuration:

  • Version 0: stakeAmount = 100,000 TEL
  • Version 1: stakeAmount = 200,000 TEL
  • Epoch issuance: 10,000 TEL

Committee (3 validators with varying performance):

ValidatorVersionStakeHeadersWeight
Av0100,000303,000,000
Bv1200,000102,000,000
Cv1200,000204,000,000
Total9,000,000

Reward distribution:

ValidatorCalculationReward
A (v0)10,000 * 3,000,000 / 9,000,000~3,333.33 TEL
B (v1)10,000 * 2,000,000 / 9,000,000~2,222.22 TEL
C (v1)10,000 * 4,000,000 / 9,000,000~4,444.44 TEL

Validator A on version 0 (lower stake) earns more than Validator B on version 1 (higher stake) because A produced 3x the headers. Performance and stake version both contribute to weight.

Example 4: Uniform Committee (Single Version)

When all validators share the same version, stakeAmount cancels out entirely and rewards are distributed purely by header count.

Configuration:

  • Version 0: stakeAmount = 100,000 TEL
  • Epoch issuance: 10,000 TEL

Committee (3 validators):

ValidatorVersionStakeHeadersWeight
Av0100,000151,500,000
Bv0100,000101,000,000
Cv0100,000252,500,000
Total5,000,000

Reward distribution:

ValidatorCalculationReward
A10,000 * 1,500,000 / 5,000,0003,000 TEL
B10,000 * 1,000,000 / 5,000,0002,000 TEL
C10,000 * 2,500,000 / 5,000,0005,000 TEL

With a uniform version, the stake amount is irrelevant to relative distribution. Rewards are directly proportional to the number of headers produced.

Key Takeaways

  • A validator’s stake version is set at the time of staking and only changes if the validator (or its delegator) opts into an in-place upgrade to a newer version via requestStakeVersionChange().
  • In a uniform committee (all same version), rewards depend only on consensus header production.
  • In a mixed committee, validators on higher-stake versions earn proportionally more per header.
  • If the stake increases, new validators have higher weight and earn more per header than legacy validators.
  • If the stake decreases, legacy validators retain their higher weight advantage over newer, lower-stake validators.
  • Performance still matters: a high-performing validator on a lower stake version can out-earn an underperforming validator on a higher version.
  • The epochIssuance from the current epoch’s StakeConfig determines the total reward pool, regardless of individual validator versions.

Why Membership Model

Why This Approach

More nodes, not bigger nodes

The fundamental argument is about what economic behavior the staking model encourages.

In a stake-weighted system, an operator with 1,000,000 TEL and a 100,000 TEL minimum stake has no reason to run 10 nodes. Running one node with 1,000,000 TEL staked produces the same (or better, after infrastructure savings) return. The network gets one node.

In the membership model, that same operator can only stake 100,000 TEL per node. To deploy all their capital productively, they launch 10 validators – each requiring governance approval, its own keys, its own infrastructure, and its own ConsensusNFT. The network gets 10 nodes. Each of those nodes independently produces consensus headers, serves the network, and strengthens its resilience against downtime and partitions.

This is not a theoretical distinction. The validator set size directly determines the network’s fault tolerance threshold, geographic distribution potential, and resistance to correlated failures. A network with 100 nodes operated by 20 entities is materially more robust than one with 20 nodes operated by 20 entities where some happen to have larger stakes.

Performance-based differentiation

Equal-weight models avoid the concentration problem but introduce a free-rider problem. The membership model solves both: the fixed stake prevents concentration, while the header-count weight rewards active participation. Validators who invest in reliable infrastructure, maintain high uptime, and stay current on protocol upgrades earn more. They are incentivized because the contributed more to the network, not because they contributed more.

Version system as a governance lever

The stake version system gives governance a tool to adjust the network’s economics without hard forks. If the network needs to raise the barrier to entry (e.g., to increase economic security), governance increments the version with a higher stake amount. If it needs to lower barriers to accelerate growth, it can reduce the stake. Existing validators are not retroactively affected, but earn rewards based on the proportion they have staked relative to all other committee members. This creates a stable, predictable environment for current operators while preserving flexibility for the network’s future.

Permissioned context

Telcoin Network validators are mobile network operators vetted by governance. This is not an open-entry permissionless system where Sybil resistance must come purely from economic cost. The ConsensusNFT whitelist provides the Sybil resistance layer. The stake serves as an economic commitment and alignment mechanism. “Well known” validators act as the sole defense against malicious actors. This context makes a fixed-stake membership model viable where it might not be in a permissionless network with anonymous node operators.

Approaches Considered

1. Pure Stake-Weighted Model

In a traditional proof-of-stake system, validators lock variable amounts of stake and earn rewards proportional to the amount staked. More stake means more influence over consensus and a larger share of rewards.

How it works: A validator with 1,000,000 TEL staked earns 10x the rewards of a validator with 100,000 TEL. Block production probability, committee selection, and voting power all scale with stake.

Pros:

  • Well-understood model with extensive research and battle-tested implementations (Ethereum, Cosmos, Solana)
  • Economic security scales directly with total value locked — attacking the network requires acquiring proportional stake
  • Flexible entry — validators can participate at various capital levels
  • Capital efficiency — validators maximize returns from a single node without additional infrastructure

Cons:

  • Incentivizes concentration, not growth. A validator with 1,000,000 TEL earns the same total reward whether they run one node or ten. Since each additional node requires infrastructure costs (hardware, bandwidth, operations), the rational choice is to run as few nodes as possible and concentrate stake. This directly works against network decentralization.
  • Rewards capital over contribution. A well-capitalized validator running a mediocre node earns more than a high-performing validator with less capital. The network gets fewer, richer nodes rather than more, better nodes.
  • Barriers compound over time. Early validators accumulate rewards faster, increasing their stake advantage. New entrants face a widening gap, discouraging participation and further concentrating the validator set.
  • Validator count stagnates. There is no economic reason for an operator to launch additional nodes when they could simply add stake to an existing one. The total node count is bounded by the number of distinct operators, not their available capital.

2. Equal-Weight Model

Every active validator earns an identical share of epoch rewards regardless of stake amount or any other factor. One validator, one equal share.

How it works: If there are 20 validators and the epoch issuance is 10,000 TEL, each validator receives 500 TEL.

Pros:

  • Simplest possible reward scheme with no room for gaming
  • No advantage to capital concentration
  • Perfectly egalitarian – every node is equally valued

Cons:

  • No performance incentive. A validator producing minimal headers earns the same as one producing many. There is no reward for reliability, uptime, or active consensus participation.
  • Free-rider problem. Validators can coast on minimal participation while collecting full rewards, degrading network quality without economic consequence.
  • No economic differentiation across stake versions. If governance adjusts the required stake over time, validators who committed more capital receive no acknowledgment of that higher commitment in their reward share.

3. Membership Model with Version-Weighted Rewards (TN Solution)

Validators stake a fixed amount determined by the current stake version to gain membership. Rewards are distributed based on the product of each validator’s initial stake amount (from their version) and their consensus header count.

How it works: Each validator’s reward weight is stakeAmount * consensusHeaderCount. In a uniform committee this reduces to pure performance-based distribution. In a mixed committee (validators on different stake versions), higher-stake versions earn proportionally more per header.

Pros:

  • Directly incentivizes more nodes. Since each node requires its own fixed stake and earns independently, an operator who wants to maximize rewards must run more nodes. There is no way to “stack” additional capital onto a single node. The only path to higher total earnings is launching additional validators, each with their own ConsensusNFT, keys, and stake. This translates capital directly into network growth.
  • Rewards performance. Header count is the differentiating factor among same-version validators. Reliable, high-uptime nodes earn more. This aligns economic incentives with network health.
  • Version-aware fairness. When governance adjusts stake requirements, validators who committed more capital under a higher-stake version are weighted accordingly in reward distribution. This acknowledges their larger economic commitment without introducing variable staking.
  • Governance flexibility. Stake amounts, issuance rates, and epoch parameters can be tuned per version without protocol changes. The network can adapt its economics as it matures.

Cons:

  • Mixed-version committees create unequal per-header reward rates, which may be perceived as unfair by validators on lower-stake versions.
  • Version upgrades are one-way. A validator can move to a newer version in-place via requestStakeVersionChange() (topping up the exact stake deficit if the requirement increased), but can never downgrade to an older version.
  • The permissioned model (ConsensusNFT) means governance is a bottleneck for onboarding, which limits how quickly new nodes can be added even when operators are willing.

Summary

ModelIncentivizes More NodesRewards PerformancePrevents ConcentrationGovernance Flexibility
Stake-weightedNoIndirectlyNoLimited
Equal-weightNeutralNoYesLimited
Membership + version-weightedYesYesYesYes

The membership model turns capital into nodes rather than into weight on existing nodes. For a network whose primary growth objective is expanding the validator set, this is the correct incentive structure.

How to Stake

Overview

Becoming an active validator requires three on-chain transactions:

  1. Governance Approval - Governance issues a ConsensusNFT to whitelist operator’s validator address.
  2. Stake - Operator submits the compressed BLS public key (96 bytes), the compressed proof of possession (48 bytes), and the stake amount.
  3. Activate - Once the node is synced, operator submits an activation transaction to become eligible for inclusion in future committees at the next epoch boundary.

Prerequisites

Hardware Requirements

Size the validator host from Hardware Requirements.

Network Requirements

  • Static public IP address
  • Open UDP ports for P2P communication: the primary and worker ports you pass at key generation (49590 and 49594 by convention; the node has no default ports)
  • Reliable, low-latency internet connection

Software Requirements

  • telcoin-network binary built and installed
  • Access to an Ethereum wallet with sufficient TEL for staking

Step 1: Generate Validator Keys

Generate BLS keys and node identity information using the CLI.

telcoin-network keytool generate validator \
  --address <ADDRESS_FOR_TEL_REWARDS> \
  --external-primary-addr /ip4/<YOUR_PUBLIC_IP>/udp/<PRIMARY_PORT>/quic-v1 \
  --external-worker-addrs /ip4/<YOUR_PUBLIC_IP>/udp/<WORKER_PORT>/quic-v1 \
  --datadir /path/to/node/data

Parameters:

  • --address - The validator’s Ethereum-style address (receives block rewards). The BLS proof of possession commits to this address, so it must be the same address that governance whitelists with the ConsensusNFT.
  • --external-primary-addr - Public multiaddr for primary P2P network
  • --external-worker-addrs - Public multiaddr(s) for worker P2P network
  • --datadir - Directory to store node data and keys

BLS Key Passphrase:

Operators will be prompted to enter a passphrase to encrypt their BLS key on the local filesystem. Alternatively, set the passphrase via:

  • Environment variable: TN_BLS_PASSPHRASE
  • Stdin: Use --bls-passphrase-source stdin
  • Interactive prompt: Use --bls-passphrase-source ask

This command generates:

  • BLS keypair (encrypted with your passphrase)
  • Network keypairs for P2P communication
  • node-info.yaml containing your public keys and proof of possession

Step 2: Request Governance Approval

Before staking, your validator address must be whitelisted by governance.

  1. Submit the validator’s ECDSA address to governance for approval
  2. Governance performs off-chain verification
  3. Upon approval, governance calls mint(validatorAddress) on the ConsensusRegistry contract

ConsensusRegistry Address: 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1

Operators can verify their whitelist status by checking if they own a ConsensusNFT at the registry address.

# Check NFT balance (returns 1 if whitelisted, 0 if not)
cast call 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "balanceOf(address)(uint256)" \
  <VALIDATOR_ADDRESS> \
  --rpc-url <RPC_URL>

Step 3: Stake

Once whitelisted, submit the stake transaction to the ConsensusRegistry.

Contract Function

function stake(
    bytes calldata blsPubkey,
    ProofOfPossession memory proofOfPossession
) external payable

Parameters:

  • blsPubkey - The compressed BLS public key (96 bytes)
  • proofOfPossession - Struct with one field:
    • signature - The compressed proof of possession (48 bytes), which binds the BLS key to the validator address

Value: Send exactly the stakeAmount of the current epoch’s stake version. Read the version with getCurrentStakeVersion(), then its config with stakeConfig(uint8). On the Adiri testnet the amount is 1,000,000 TEL today.

Reading Your Keys

The node’s BLS public key and proof of possession are stored in node-info.yaml after key generation:

bls_public_key: <compressed-96-byte-key>
proof_of_possession: <signature>

The file stores both values in base58. keytool export-staking-args reads them and prints the two stake() arguments in hex, or the complete transaction calldata with --calldata. It reads only node-info.yaml and never the BLS key, but the binary still requires a passphrase source for every keytool command except set-rpc: with the default env source and no TN_BLS_PASSPHRASE it exits with “passphrase is required”. Pass --bls-passphrase-source no-passphrase. keytool also writes log lines to stdout, so add the global -q flag when capturing its output in a variable.

Example Using Cast

# Read the current epoch's stake version
cast call 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "getCurrentStakeVersion()(uint8)" \
  --rpc-url <RPC_URL>

# Read that version's config; the first value is the stake amount in wei
cast call 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "stakeConfig(uint8)(uint256,uint256,uint256,uint32)" \
  <STAKE_VERSION> \
  --rpc-url <RPC_URL>

# Build the stake(bytes,(bytes)) calldata from node-info.yaml
CALLDATA=$(telcoin-network -q --bls-passphrase-source no-passphrase \
  keytool export-staking-args \
  --node-info /path/to/node/data/node-info.yaml \
  --calldata)

# Submit stake transaction
cast send 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "$CALLDATA" \
  --value <STAKE_AMOUNT> \
  --trezor \
  --rpc-url <RPC_URL>

Do not read the amount from getCurrentStakeConfig(). It returns the newest config governance has written, and in an epoch where governance writes a new version, that config only takes effect at the next epoch. stake() checks the value against the current epoch’s version.

After staking, the validator’s status changes to Staked.

Step 4: Start and Sync The Node

Start the validator node and wait for it to sync with the network. This step can be started before staking is complete.

telcoin-network node \
  --datadir /path/to/node/data \
  --chain adiri \
  --http

--chain adiri loads the Adiri testnet genesis built into the binary. Build the binary with the adiri feature: without it, the node refuses to start on the Adiri chain (chain ID 2017).

Passphrase options:

  • Environment: TN_BLS_PASSPHRASE=<passphrase> telcoin-network node ...
  • Stdin: echo "<passphrase>" | telcoin-network --bls-passphrase-source stdin node ...
  • Interactive: telcoin-network --bls-passphrase-source ask node ...

Verify Sync Status

There is no dedicated syncing RPC method (eth_syncing always returns false on Telcoin Network). Compare the node’s latest block number against a public RPC endpoint:

# local node
curl -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  http://localhost:8545

# network
curl -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  <RPC_URL>

Wait until the node’s block number matches the network’s and keeps advancing with it before proceeding.

Step 5: Activate

Once synced, submit an activation transaction to signal readiness for committee participation. This does not guarantee immediate inclusion. It indicates the validator is ready to be included in future committee selection process starting at the next epoch boundary.

Contract Function

function activate() external

This function must be called from the validator address (the address that owns the ConsensusNFT and submitted the stake).

Example Using Cast

cast send 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "activate()" \
  --trezor \
  --rpc-url <RPC_URL>

After activation:

  1. Node status changes to PendingActivation
  2. At the next epoch boundary, node status changes to Active
  3. Node becomes eligible for committee selection in subsequent epochs

Validator Lifecycle States

StatusDescription
UndefinedAddress has ConsensusNFT but has not staked
StakedValidator has staked but not yet activated
PendingActivationValidator called activate(), waiting for epoch boundary
ActiveValidator is active and eligible for committee selection
PendingExitValidator requested exit, waiting for committee obligations to complete
ExitedValidator has exited and can unstake after one epoch

Delegated Staking (Institutional Onboarding)

For institutional or white-glove onboarding scenarios, a delegator can stake on behalf of a validator.

Contract Function

function delegateStake(
    bytes calldata blsPubkey,
    ProofOfPossession memory proofOfPossession,
    address validatorAddress,
    bytes calldata validatorEIP712Signature,
    uint256 deadline
) external payable

Additional Parameters:

  • validatorAddress - The address that owns the ConsensusNFT
  • validatorEIP712Signature - EIP-712 signature from the validator authorizing the delegation
  • deadline - Unix timestamp after which the signature is rejected with DelegationExpired

Obtaining the Delegation Digest

The validator must sign an EIP-712 typed data message. Get the digest to sign:

function delegationDigest(
    bytes memory blsPubkey,
    address validatorAddress,
    address delegator,
    uint256 deadline
) external view returns (bytes32)

The validator signs this digest, and the delegator includes the signature and the same deadline when calling delegateStake. The digest also covers the current epoch’s stake version and the validator’s delegation nonce, so the signature stops working if either changes before delegateStake runs. A validator can revoke a signature it has given by calling increaseNonce().

Note: Governance-initiated delegations (where msg.sender is the contract owner) do not require the validator’s EIP-712 signature.

Exiting and Unstaking

Begin Exit

To leave the active validator set:

cast send 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "beginExit()" \
  --trezor \
  --rpc-url <RPC_URL>

Your status changes to PendingExit. The protocol will automatically exit you once you’re no longer needed for any committee assignments (current epoch + 2 future epochs).

Unstake

The validator address or its delegator can call unstake. The registry accepts it in two cases:

  • the validator is Staked (it staked but never activated), or
  • the validator is Exited and at least one epoch has passed since its exit epoch (currentEpoch >= exitEpoch + 1).
cast send 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "unstake(address,bool)" \
  <VALIDATOR_ADDRESS> \
  false \
  --trezor \
  --rpc-url <RPC_URL>

This returns your initial stake plus any accrued rewards to either:

  • The validator address (if self-staked)
  • The delegator address (if delegated staking was used)

If the recipient rejects the transfer, the amount is credited instead, and the recipient withdraws it with claimRefund().

The second argument is acceptRewardShortfall. With false, the call reverts if the Issuance contract cannot cover the accrued rewards. With true, rewards are paid up to the Issuance balance and the unpaid rewards are forfeited; the stake portion is not reduced by the reward shortfall. (A validator slashed below its stake amount gets back only its remaining balance either way.)

Unstaking retires the validator address for good: governance cannot issue it a ConsensusNFT again.

Claim Rewards Without Exiting

Active validators can claim accrued rewards without exiting:

cast send 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "claimStakeRewards(address)" \
  <VALIDATOR_ADDRESS> \
  --trezor \
  --rpc-url <RPC_URL>

Checking Validator Status

Query your validator information:

cast call 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "getValidator(address)" \
  <VALIDATOR_ADDRESS> \
  --rpc-url <RPC_URL>

Query current epoch information:

cast call 0x07E17e17E17e17E17e17E17E17E17e17e17E17e1 \
  "getCurrentEpochInfo()" \
  --rpc-url <RPC_URL>

Troubleshooting

Common Errors

ErrorCauseSolution
InvalidTokenIdAddress holds no ConsensusNFT: not whitelisted yet, or already retiredRequest governance approval first; a retired address cannot be whitelisted again
InvalidStatusWrong validator state for operationCheck current status with getValidator()
InvalidStakeAmountIncorrect stake value sentSend stakeAmount from stakeConfig(getCurrentStakeVersion())
InvalidProofOfPossessionBLS signature verification failedThe proof was usually signed for a different address. Re-sign it for the staking address with keytool generate pop --address <ADDRESS>, then export the staking arguments again
DuplicateBLSPubkeyBLS key already registeredIf this address already staked with this key, the earlier stake succeeded; check getValidator(). Otherwise: a registered key is never released, not even after unstaking. Generate a new validator’s keys in a new datadir, and never overwrite the keys of a validator that has staked

Key Management

  • Lost passphrase: BLS keys cannot be recovered without the passphrase. Before staking, generate new keys. After staking, the registered key cannot be replaced: exit and unstake from the validator address, then onboard a new validator address with new keys.
  • Regenerating keys: Never run keytool generate validator --force for a validator that has staked. The registry binds the BLS key to that validator for good and has no way to replace it, so --force overwrites the only key that can sign for the validator. If the proof of possession names the wrong address, re-sign it with keytool generate pop --address <ADDRESS> instead. It keeps every key and changes only execution_address and proof_of_possession, but it writes the whole node-info.yaml again, so a legacy worker: map is saved as a workers: list. It signs with the BLS key, so run it with --datadir set to the node’s data directory and the passphrase source that decrypts the key. The --bls-passphrase-source no-passphrase used for export-staking-args above fails for an encrypted key.
  • Backing up keys: Securely backup the contents of your data directory, especially the encrypted key files.

Future Direction

Validator Scalability Limits

The ConsensusRegistry contract imposes a practical upper limit on the number of validators that can participate in the network. This limit is not defined by an explicit constant but emerges from gas constraints on storage-intensive loops that iterate over all existing validators. Under the current design and its 100M system-call gas budget, the network can support on the order of 8,000 validators before critical operations risk exceeding gas limits (under the historical 30M budget, the ceiling was approximately 2,500-3,000).

This is an accepted tradeoff. The contract is designed for a permissioned validator set of mobile network operators (MNOs), of which there are roughly 1,000 worldwide.

The Bottleneck: _getValidators()

The root cause is _getValidators(), an internal function that scans every existing ConsensusNFT to find validators matching a given status. It relies on ERC721Enumerable’s tokenByIndex(), which performs a cold storage read (SLOAD) per token.

function _getValidators(ValidatorStatus status) internal view returns (ValidatorInfo[] memory) {
    ValidatorInfo[] memory validatorsMatched = new ValidatorInfo[]((totalSupply()));

    for (uint256 i; i < validatorsMatched.length; ++i) {
        address validatorAddress = _getAddress(tokenByIndex(i));  // SLOAD
        ValidatorInfo storage current = validators[validatorAddress];  // SLOAD(s)
        ...
    }
    ...
}

For each validator in the loop:

OperationApproximate Gas
tokenByIndex(i) – read from _allTokens[i]2,100 (cold SLOAD)
Read validators[addr] struct fields2,100-4,200
Copy matched struct to memory1,500-3,000
Per-validator total~5,000-7,000

On top of per-validator storage costs, Solidity’s memory allocation cost grows quadratically. The function allocates a memory array sized to totalSupply(), and each ValidatorInfo consumes 224 bytes in memory (seven fields at 32 bytes each). BLS pubkeys live in a separate blsPubkeys mapping and are not copied by this function.

Where _getValidators() Is Called

The function appears on every critical path:

CallerContextTimes Called
concludeEpoch()System call (epoch boundary)3 times
beginExit()Validator transaction1 time
setNextCommitteeSize()Governance transaction1 time
burn() via _consensusBurn()Governance transaction1 time

concludeEpoch() – The Tightest Constraint

concludeEpoch() is the most gas-constrained path because it calls _getValidators() three times in a single transaction:

  1. _getValidators(PendingActivation) inside _updateValidatorQueue() – to activate queued validators
  2. _getValidators(PendingExit) inside _updateValidatorQueue() – to finalize exits for validators no longer in any committee
  3. _getValidators(Active) after queue updates – to validate the future committee size against total eligible validators

This function executes as a system call with a hardcoded gas limit of 100,000,000 (100M), raised from the historical 30M for epoch-boundary headroom. The protocol sets this limit (SYSTEM_CALL_GAS_LIMIT) in crates/tn-reth/src/evm/mod.rs when constructing the system call transaction.

After the first _getValidators() call, subsequent calls benefit from warm storage (100 gas per SLOAD instead of 2,100), reducing repeat costs. Even so, the combined gas scales linearly with validator count and quadratically with memory allocation.

Estimated Gas for concludeEpoch()

ValidatorsStorage ReadsMemory (3 arrays)Estimated Total
500~3.5M~0.2M~4M
1,000~7M~0.7M~8M
1,500~10.5M~1.5M~12M
2,000~14M~2.5M~17M
2,500~17.5M~4M~22M
3,000~21M~5.5M~27M
~3,200~22.5M~6.5M~30M

These estimates cover the three _getValidators() calls. Under the historical 30M budget, ~3,200 validators marked the hard ceiling; against the current 100M budget, this model extrapolates to a ceiling on the order of 8,000 validators (the quadratic memory growth keeps it below a proportional 3.3x). The remaining work in concludeEpoch() – sorting enforcement, epoch info updates, committee membership checks for pending exits – adds further overhead, which tightens the practical limit below the theoretical maximum.

applyIncentives() – Separate System Call

applyIncentives() executes as its own system call with a separate 100M gas budget. It iterates over the rewardInfos array (one entry per committee member, not all validators), so it scales with committee size rather than total validator count. The uint16 nextCommitteeSize field caps committee size at 65,535, well within gas limits for this function.

beginExit() – Validator Transaction

beginExit() calls _getValidators(Active) once. As a regular transaction (not a system call), it is subject to the block gas limit. A single pass over all validators consumes roughly 6M gas at 1,000 validators, which is feasible under standard block gas limits but becomes a concern as validator count grows.

Fixed Type Limits

Beyond gas constraints, several storage types impose hard ceilings:

FieldTypeMaximum ValueImplication
nextCommitteeSizeuint1665,535Maximum committee size per epoch
stakeVersionuint8255Maximum number of stake version upgrades
currentEpochuint32~4.3 billionEffectively unlimited for epoch count

The uint8 stakeVersion is the most notable hard limit. After 255 version upgrades, no further stake configuration changes can be made without a contract upgrade.

Why This Is Acceptable Today

The contract comment on the _getValidators() function states the design rationale directly:

“There are ~1000 total MNOs in the world so SLOAD loops should not run out of gas”

Telcoin Network validators are permissioned mobile network operators approved by governance via ConsensusNFT minting. The total addressable validator set is bounded by the number of MNOs globally (~1,000), placing the network well within the validator gas ceiling.

At 1,000 validators, concludeEpoch() consumes approximately 8M gas out of its 100M budget – roughly 8% utilization with comfortable headroom.

Future Approaches to Support More Validators

If the network’s growth objectives expand beyond the current MNO-focused validator set, several approaches could raise or eliminate the validator ceiling.

Increase the System Call Gas Limit

The simplest change, and one the protocol has already exercised once: the system-call gas limit was raised from 30M to 100M. Raising it further would proportionally increase the validator ceiling. The value participates in consensus – a binary running with a different limit diverges on any system call that needs more gas than the smaller budget allows – so every validator must upgrade in lockstep before an epoch close can depend on a new value. System calls execute at zero gas price and do not count against the block’s gas limit, so increasing this value has no impact on user transaction throughput. The tradeoff is longer execution time per epoch boundary, which affects block production latency.

Replace ERC721Enumerable with Direct Tracking

The ERC721Enumerable extension maintains an _allTokens array that enables tokenByIndex() but adds storage overhead on every mint and burn. Replacing it with a purpose-built data structure – such as a simple address[] of active validators maintained alongside the existing validators mapping – would eliminate the indirection through tokenByIndex() and reduce per-validator storage reads from ~3 SLOADs to ~1.

This change would also allow the contract to avoid iterating over validators that have exited but not yet unstaked (whose NFTs still exist in the enumerable set), further reducing unnecessary loop iterations.

Maintain Validator Sets by Status

Rather than filtering all validators by status at query time, the contract could maintain separate storage arrays per status (e.g., activeValidators[], pendingActivationQueue[], pendingExitQueue[]). Each state transition would move the validator between arrays. This converts _getValidators() from O(totalSupply) to O(relevantSet), which is dramatically cheaper when only a small subset of validators is in a given status.

The cost is additional bookkeeping on state transitions (swaps and pops on storage arrays), but these operations are constant-time per transition and occur infrequently relative to the epoch-boundary queries.

Move Validator Enumeration Off-Chain

The protocol client already has full visibility into the validator set through its own state. The on-chain _getValidators() calls serve two purposes: queue processing (activations and exits) and committee size validation. Both could be moved partially or fully to the protocol layer in Rust, with the contract receiving pre-computed results via system call parameters rather than re-deriving them from storage.

For example, concludeEpoch() could accept the list of validators to activate and exit as calldata parameters (validated against on-chain state) instead of scanning storage to discover them. This would reduce the function to O(queue size) rather than O(total validators).

Paginated or Incremental Queue Processing

Instead of processing all pending activations and exits in a single concludeEpoch() call, the protocol could process them incrementally across multiple system calls or in batches. A cursor-based pattern would allow _updateValidatorQueue() to resume where it left off if gas runs low, spreading the work across multiple transactions within the same block.

SSTORE2 for Static Validator Data

Each validator’s 96-byte compressed BLS pubkey is stored in a dedicated blsPubkeys mapping, which consumes 4 storage slots (length + 3 data slots). Since BLS public keys are immutable after staking, they could be written once using SSTORE2 (storing data as contract bytecode via CREATE) and read back with EXTCODECOPY. Bytecode reads cost 2,600 gas for the first access plus 3 gas per byte, which is cheaper than multiple SLOADs for large data and avoids the per-slot overhead.

The pubkey is already excluded from ValidatorInfo, so _getValidators() never touches it; this optimization targets the pubkey read paths instead, such as getBlsPubkey() and getCommitteeBlsPubkeys().

Summary

ConstraintCurrent LimitBound By
Practical validator ceiling~8,000concludeEpoch() gas (100M system call)
Committee size65,535uint16 nextCommitteeSize
Stake versions255uint8 stakeVersion
Target validator set~1,000 MNOsNetwork design

The current design prioritizes simplicity and correctness for a known-bounded validator set. The gas ceiling is well above the target population of ~1,000 MNOs, providing roughly 8x headroom. If the network evolves to require a larger validator set, the most effective path is a combination of maintaining per-status validator arrays and moving enumeration logic to the protocol layer, which together would effectively eliminate the gas-based ceiling.

Basefees

Telcoin Network Base Fee Model

Overview

Telcoin Network (TN) implements an EIP-1559-style base fee mechanism adapted for its epoch-based consensus architecture. The core pricing algorithm is identical to Ethereum’s EIP-1559, but the adjustment boundary is per-epoch rather than per-block.

Key Concepts

Epochs, not blocks: TN consensus operates in epochs. Each epoch consists of multiple batches (analogous to blocks) produced by workers. The base fee is fixed for the duration of an epoch and only adjusts at epoch boundaries.

Gas target: Each epoch has a defined gas target representing the ideal gas consumption across all batches. This target functions identically to Ethereum’s block gas target and is the point at which the base fee remains unchanged.

Uniform pricing within an epoch: All transactions within a given epoch are priced at the same base_fee_per_gas, regardless of when during the epoch they are submitted. There is no intra-epoch fee variance.

Base Fee Adjustment Algorithm

At each epoch boundary, the protocol compares the epoch’s total gas consumed against the gas target:

  • Gas used = target → base fee remains unchanged
  • Gas used > target → base fee increases for the next epoch
  • Gas used < target → base fee decreases for the next epoch

The adjustment formula follows EIP-1559:

if gas_used > gas_target:
    base_fee_delta = parent_base_fee * (gas_used - gas_target) / gas_target / MAX_CHANGE_DENOMINATOR
    next_base_fee = parent_base_fee + max(base_fee_delta, 1)

if gas_used < gas_target:
    base_fee_delta = parent_base_fee * (gas_target - gas_used) / gas_target / MAX_CHANGE_DENOMINATOR
    next_base_fee = parent_base_fee - base_fee_delta

Where MAX_CHANGE_DENOMINATOR bounds the maximum rate of change per epoch (the same constant as Ethereum’s EIP-1559). The base fee has a protocol-defined minimum floor and cannot drop below it.

Implications for Bridge Integrations

  1. Fee estimation: Because the base fee is constant within an epoch, a single eth_gasPrice or eth_getBaseFee call gives you the exact base fee for all transactions until the current epoch ends. There is no block-to-block volatility to account for within an epoch.
  2. Fee predictability: The maximum base fee increase between consecutive epochs is bounded by the EIP-1559 max change denominator. Fee spikes are smoothed across epoch boundaries rather than block boundaries, which generally provides more stable pricing over short time horizons.
  3. Transaction pricing: TN transactions use the same maxFeePerGas / maxPriorityFeePerGas fields as Ethereum EIP-1559 transactions. The base_fee_per_gas portion is not burned – it is credited to the chain’s base-fee address for governance processing – and the priority fee goes to the batch producer.
  4. Epoch transitions: If a transaction is submitted near an epoch boundary and lands in the next epoch, it will be priced at the new epoch’s base fee. Standard EIP-1559 maxFeePerGas protections apply: the transaction will not execute if the new base fee exceeds the sender’s maxFeePerGas.

Summary

PropertyEthereumTelcoin Network
Fee adjustment boundaryPer blockPer epoch
Adjustment algorithmEIP-1559EIP-1559 (same formula)
Fee stability within boundary1 blockEntire epoch
Gas target basisSingle blockEpoch aggregate
Transaction typeEIP-1559 (type 2)EIP-1559 (type 2)
Base fee floor0 (effectively)Protocol-defined minimum

Gas Limit Penalty

Why Penalties Exist

Telcoin Network uses a consensus-first architecture: transactions are ordered by consensus before they are executed. This means the actual gas a transaction consumes is unknown at the time it enters a batch. The network must rely on the gas limit declared by the sender to reserve space in each batch.

This creates a denial-of-service vector. A malicious or careless user can set an extremely high gas limit on a cheap transaction, consuming most of a batch’s capacity while using almost none of it. The batch ends up nearly empty in terms of useful work, but no other transactions could fit.

To counter this, TN applies a quadratic penalty on transactions whose gas limit is grossly inflated relative to their actual usage. The penalty deducts a portion of the unused gas refund and redirects it to the chain’s base-fee address for governance processing. This economically disincentivizes gas limit inflation without affecting well-estimated transactions.

How It Works

The 10% Rule

The penalty only activates when a transaction uses less than 10% of its declared gas limit. Any transaction that uses 10% or more of its gas limit pays zero penalty.

Additionally, transactions with a gas limit at or below 210,000 are always exempt, regardless of usage ratio. This protects simple transfers and low-cost interactions from unintended penalties.

Quadratic Scaling

When the penalty does activate, it scales quadratically based on how far below the 10% threshold the usage falls. The further below 10% usage a transaction is, the more aggressively the penalty grows.

In plain terms:

  • At 9.9% usage, the penalty is negligible
  • At 5% usage, roughly 25% of unused gas is penalized
  • At 2% usage, roughly 64% of unused gas is penalized
  • At 0.1% usage, over 95% of unused gas is penalized

The quadratic curve means the penalty ramps up gently near the threshold and steeply for extreme over-estimation, targeting the worst offenders while leaving borderline cases mostly unaffected.

The Formula

For transactions above the minimum gas limit threshold and below 10% usage:

usage_ratio = gas_used / gas_limit
inefficiency = 0.10 - usage_ratio
penalty_gas = (inefficiency^2 / 0.10^2) * unused_gas

Where:

  • gas_used is the actual gas consumed (before EVM refunds like SSTORE clearing)
  • gas_limit is the gas limit declared by the sender
  • unused_gas = gas_limit - gas_used (computed from the same pre-refund gas_used)
  • The result is clamped so the penalty never exceeds unused_gas

Note on EVM refunds: The penalty calculation uses pre-refund gas (actual execution cost) to determine the usage ratio, so SSTORE refunds do not artificially inflate the penalty. The standard EVM refund is still applied when computing the user’s gas reimbursement.

Where the Fees Go

When a transaction executes on TN, fees are distributed as follows:

Fee ComponentRecipientDescription
Priority fee (tip)Batch producer (validator)(effective_gas_price - base_fee) * gas_used
Base feeBase-fee addressbase_fee * gas_used
Gas limit penaltyBase-fee addressQuadratic penalty on unused gas (if applicable)
RemainderRefunded to senderUnused gas minus any penalty

The penalty is deducted from what would otherwise be refunded to the transaction sender. It does not increase the total fee paid beyond the sender’s declared gas_limit * effective_gas_price – it only reduces the refund.

Example Penalty Table

Gas LimitGas UsedUsage %Unused GasPenalty GasPenalty % of Unused
21,00021,000100%000%
210,00021,00010%189,00000%
420,00021,0005%399,00099,75025%
1,000,00021,0002.1%979,000610,99362.4%
5,000,00021,0000.42%4,979,0004,569,54691.8%
10,000,00021,0000.21%9,979,0009,564,28295.8%
30,000,00021,0000.07%29,979,00029,560,76298.6%

How to Avoid Penalties

  1. Use accurate gas estimation. Call eth_estimateGas before submitting transactions and set the gas limit based on the estimate. A reasonable buffer (e.g., 1.5x-2x the estimate) will not trigger penalties as long as the final usage stays above 10% of the gas limit.
  2. Understand the 10x safe zone. As a rule of thumb, if your gas limit is no more than 10x your actual gas consumption, you will never pay a penalty. A simple transfer using ~21,000 gas can safely use a gas limit up to 210,000 with zero penalty.
  3. Small transactions are exempt. Any transaction with a gas limit of 210,000 or less is exempt from penalties entirely, regardless of usage ratio.
  4. Do not hardcode inflated gas limits. Setting a gas limit to the maximum batch gas without estimating actual usage is the primary behavior this penalty targets. Bridge contracts and relayers should always estimate gas for each message rather than using a static high value.

Summary

PropertyValue
Penalty thresholdUsage below 10% of gas limit
ScalingQuadratic (gentle near threshold, steep at extremes)
Minimum gas limit for penalty> 210,000
Penalty destinationBase-fee address (for governance processing)
Maximum penaltyCannot exceed unused gas
Safe multiplierUp to 10x estimated gas = zero penalty

For the exact formula, worked examples, and guidance for wallets and integrators on detecting the charge, see Gas Over-Reservation Penalty.

Gas over-reservation penalty

Telcoin Network charges a penalty when a transaction reserves far more gas than it uses. This page is for transaction senders, wallet developers, and integrators. It explains when the penalty applies, how much it costs, where the funds go, and how to detect it.

Contributor documentation lives in the “Fee economics” section of crates/tn-reth/README.md. The implementation lives in crates/tn-reth/src/evm/utils.rs and crates/tn-reth/src/evm/handler.rs.

Why the penalty exists

Telcoin Network orders transactions in consensus before it executes them. Batch capacity is reserved by each transaction’s gas limit, not by its final gas usage. Without a penalty, a sender could set a very large gas limit, use almost none of it, and consume batch capacity for free. The penalty makes large over-reservation expensive. Issue #424 has the design history.

When the penalty applies

The penalty applies only when both conditions hold:

  1. The gas limit is greater than 210,000. This threshold is MIN_GAS_LIMIT_THRESHOLD, ten times the 21,000 minimum transaction cost.
  2. The transaction uses less than 10 percent of its gas limit.

If either condition fails, the penalty is zero and the sender receives the normal unused-gas refund.

Three details matter for exact accounting:

  • “Gas used” for the penalty is the gas spent before EVM refunds (for example SSTORE clearing refunds). The receipt field gasUsed is measured after EVM refunds, so the two values can differ.
  • The penalty does not depend on transaction success. A reverted transaction that meets both conditions also pays it.
  • System transactions are exempt.

The formula

The node computes the penalty in gas units with u128 integer math (fixed-point precision 10^9):

usage_ratio_scaled = (10^9 * gas_spent) / gas_limit        # integer division
if usage_ratio_scaled >= 10^8:                             # usage >= 10%
    penalty_gas = 0
else:
    unused_gas  = gas_limit - gas_spent
    penalty_gas = ((10^8 - usage_ratio_scaled)^2 * unused_gas) / 10^16

gas_spent is the pre-refund gas. The penalty grows quadratically as usage falls below 10 percent. It approaches full confiscation of the unused gas for extreme over-estimates. It never exceeds the unused gas, so the refund never goes below zero. Integer-only math keeps the result identical on every node. The result changes account balances, so it is consensus-critical.

The charge in wei is:

penalty_wei = penalty_gas * effective_gas_price

Examples

Gas LimitGas UsedUsage %Unused GasPenalty GasPenalty % of Unused
21,00021,000100%000%
210,00021,00010%189,00000%
420,00021,0005%399,00099,75025%
1,000,00021,0002.1%979,000610,99362.4%
5,000,00021,0000.42%4,979,0004,569,54691.8%
10,000,00021,0000.21%9,979,0009,564,28295.8%
30,000,00021,0000.07%29,979,00029,560,76298.6%

These rows mirror the reference table on calculate_gas_penalty. The unit test test_gas_penalty_calculation in the same file asserts the penalty values.

A concrete cost: a simple transfer (21,000 gas used) sent with a 1,000,000 gas limit at an effective gas price of 100 gwei pays a penalty of 610,993 gas. At that price the charge is 6.10993 x 10^16 wei, which is 0.0610993 TEL with the native token’s standard 18 decimal places. The same transfer sent with a gas limit of 210,000 or less pays no penalty.

What the sender pays

The sender prepays gas_limit * effective_gas_price. After execution the node refunds:

refund_wei = (gas_limit - gasUsed - penalty_gas) * effective_gas_price

gasUsed is the post-refund value that appears in the receipt. The net fee is therefore:

net_fee_wei = (gasUsed + penalty_gas) * effective_gas_price

The penalty portion is credited to the chain’s base-fee address for governance processing. It is not burned.

What the receipt shows

Nothing. A penalized transaction and an unpenalized one produce identical receipts. gasUsed does not include the penalty. No EVM log or event marks the charge. Node operators can see tracing debug lines from crates/tn-reth/src/evm/utils.rs and crates/tn-reth/src/evm/handler.rs. System transactions produce no such lines. RPC clients cannot see them.

How to detect the penalty from standard RPC

Wallets and indexers can recover the charge without new RPC methods:

  1. Exact, from balances. For a transaction that only sends value from the sender: net_fee_wei = balance_before - balance_after - value. Then penalty_wei = net_fee_wei - gasUsed * effectiveGasPrice, with both fields taken from the receipt. Adjust balance_after for any internal transfers the sender receives in the same transaction.
  2. Exact or upper bound, from the formula. Recompute the formula with the transaction’s gas field as gas_limit and the receipt’s gasUsed as gas_spent. If the transaction produced no EVM refunds, gasUsed equals the pre-refund gas and the result is exact. If it produced refunds, gasUsed is smaller than the pre-refund gas and the result is an upper bound on the true penalty.

How to avoid the penalty

  • Use eth_estimateGas and set the gas limit near the estimate.
  • Keep the gas limit below ten times the expected gas usage.
  • For small transactions, a gas limit of 210,000 or less is always exempt.

Source of truth

BehaviorCode
Formula and thresholdscrates/tn-reth/src/evm/utils.rs (calculate_gas_penalty)
Refund split and fund destinationcrates/tn-reth/src/evm/handler.rs (TNEvmHandler::reimburse_caller)

This page mirrors those files. Update this page when those files change.

EVM Compatibility

Overview

Telcoin Network runs a standard Ethereum Virtual Machine. There are no custom opcodes, no modified gas costs, and no disabled instructions. Contracts written in Solidity or Vyper deploy and execute identically to Ethereum. Standard tooling, including Hardhat, Foundry, ethers.js, viem, etc., work without modification.

TN supports all standard transaction types:

  • Legacy transactions
  • EIP-2930 (access list)
  • EIP-1559 (type 2)

All Ethereum hardfork rules through Prague/Pectra are active.

The sections below cover the areas where TN diverges from mainnet Ethereum behavior.

Custom Precompiles

TN registers two additional precompiles beyond the standard Ethereum set: a TEL token-issuance precompile at 0x7e1 and a BLS12-381 signature verifier at 0xb151. The standard precompiles (0x01-0x0a etc.) are unchanged.

Both custom addresses hold a single 0xfe (INVALID) byte of code in genesis, so eth_getCode returns 0xfe for them, not 0x. Tooling that infers “not a contract” from empty code will be wrong about these two addresses. The nonzero code is there for two reasons: it keeps the accounts non-empty, which exempts them from EIP-158 state clearing (an account holding only storage counts as empty and would be deleted, wiping the TEL supply counter), and it makes any call that bypasses precompile dispatch fail on the invalid instruction instead of succeeding against an EOA.

TEL Precompile: Token Issuance

The TEL precompile at address 0x00000000000000000000000000000000000007e1 owns the native token’s supply lifecycle: minting, claiming, and burning, plus a totalSupply() view.

Warning

The precompile is not an ERC-20 contract. An earlier design exposed transfer, approve, transferFrom, permit, balanceOf, and the other ERC-20/EIP-2612 selectors at 0x7e1; that surface has been removed. Calling any of them now reverts with Unknown function selector. Do not configure 0x7e1 as a token contract in wallets, bridges, or indexers.

What This Means

TEL is the chain’s native asset. Balances are native account balances, and moving TEL between accounts is an ordinary value transfer (CALL with value) — exactly like moving ETH on Ethereum, and equivalent to what an ERC-20 transfer would have done. Allowances, permits, and other ERC-20 semantics are user-space concerns: protocols that need an ERC-20 representation of TEL wrap it, just as ETH is wrapped into WETH. A canonical wrapper, WTEL, is deployed at 0x239c9fa0a4bfb9b71304e20c094738debfd7e2b0.

Only issuance authority and the supply counter require protocol-level state, so those are all the precompile exposes.

Interface

FunctionAccessGasBehavior
mint(uint256 amount)Governance only41,000Creates a pending mint under a 7-day timelock. A second mint overwrites the pending amount (minting 0 cancels)
claim(address recipient)Governance only25,000After the timelock expires: credits recipient’s native balance, increments totalSupply, clears the pending slots
burn(uint256 amount)Governance only8,000Destroys amount from the precompile’s own balance and decrements totalSupply. The only payable selector: attached value funds the pool it draws from
totalSupply()Any caller2,100Returns the current circulating supply. Callable from STATICCALL

burn is the chain’s only payable inlet at 0x7e1, and like every other issuance selector it is governance-gated. The EVM credits msg.value to the precompile account before the handler runs, so attaching value tops up the pool the burn destroys from. Calling burn(amount) with msg.value == amount funds and burns in a single transaction. Any excess stays in the precompile’s balance for a later burn: it is not refunded, and no selector ever pays balance back out. The genesis account starts at zero balance, a plain value send with empty calldata is refused, and every other selector rejects nonzero value, so a governance burn is the only way an ordinary call can add TEL to the pool.

On testnet builds compiled with the faucet feature, mint becomes an instant mint(address recipient, uint256 amount) with no timelock, and governance can delegate minting via grantMintRole(address) / revokeMintRole(address) / hasMintRole(address). Mainnet binaries are never built with this feature.

Governance is the on-chain governance safe at 0x00000000000000000000000000000000000007a0.

Notable Behaviors

  • Direct calls only: DELEGATECALL and CALLCODE into 0x7e1 still reach the precompile, because revm looks a precompile up by the frame’s code address alone. The dispatcher refuses them with telcoin precompile: only a direct call to 0x7e1 is permitted. Under DELEGATECALL the caller identity the access gates would check is inherited from the parent frame, while the handlers still read and write the canonical 0x7e1 ledger, so authorizing such a frame would mean trusting a spoofable address. The check runs before the calldata length check and before selector dispatch, so read-only selectors are refused through those opcodes too. Only a plain CALL or STATICCALL reaches a handler.
  • STATICCALL write protection: inside a static frame only the read-only selectors are served (totalSupply, plus hasMintRole on faucet builds); every state-mutating selector is refused with static call: state mutation not permitted.
  • Non-payable by default: every selector except burn rejects nonzero call value with call value: selector is not payable, so stray TEL cannot be stranded at the precompile.
  • Rejections consume all gas: every refusal above, plus every unauthorized, decode, or arithmetic failure inside a handler, is a halt, not a revert. revm returns unspent gas only for calls that succeed or revert, and a precompile error is neither. A rejected sub-call therefore loses every unit of gas it was forwarded. Under EIP-150’s default forwarding that is 63/64 of the caller’s remaining gas, leaving the calling frame only the 1/64 it withheld. A rejected top-level transaction is charged its full gas_limit. The zero such a call pushes on the stack (false to a Solidity caller) is not a result to branch on: the gas needed to act on it has already been spent. Probing 0x7e1 with a value-carrying call to see what it does costs the whole gas budget. Satisfy the direct-call, access, static, and payability rules up front rather than planning to recover from a refusal. Neither form surfaces the reason: the call returns no data, and the halt carries no message, so a caller cannot tell from the receipt which rule it broke.
  • Events: Mint(recipient, amount, unlockTimestamp), Claim(recipient, amount), Burn(amount), plus ERC-20-style Transfer events: from address(0) on claim and faucet-mint, to address(0) on burn, and, when a burn is funded with call value, an inbound Transfer(caller, 0x7e1, msg.value) that mirrors the value transfer the EVM performs silently. That inbound log is the one Transfer where neither leg is address(0), and its data field carries the attached value, not the burned amount; the two need not match. A value-funded burn emits three logs in this order: Transfer(caller -> 0x7e1, msg.value), Burn(amount), Transfer(0x7e1 -> address(0), amount). Ordinary native TEL transfers do not emit Transfer events.
  • Supply accounting: totalSupply changes only via claim and burn. It does not track native balance movement such as gas fees or validator rewards.

Implications for Integrations

  • Wallets and explorers should treat TEL as the native asset (like ETH), never as a token at 0x7e1.
  • Bridges and DeFi protocols that need ERC-20 semantics should integrate WTEL, exactly as they would WETH on Ethereum.
  • Indexers tracking supply should watch the precompile’s Mint/Claim/Burn/Transfer events; ordinary TEL movement is visible as native value transfers in transaction traces, not as log events.
  • Indexers that reconstruct TEL as an ERC-20 balance sheet from those Transfer logs must credit the inbound Transfer(caller, 0x7e1, msg.value) on a value-funded burn. It exists for exactly this audience: skip it and the precompile’s balance drifts negative, because tokens leave a pool they were never seen entering.
  • Even with that log credited, the Transfer stream is a complete account of the precompile’s balance only across paths that run precompile code. SELFDESTRUCT naming 0x7e1 as its beneficiary still transfers the balance under EIP-6780 — only the account deletion was removed — and does so with no log and no precompile frame, so the precompile cannot mirror it. Reconcile against 0x7e1’s native balance rather than treating the log stream as closed.

BLS Precompile: Signature Verification

The BLS precompile at address 0x000000000000000000000000000000000000b151 verifies BLS12-381 signatures using the same blst (min_sig) implementation the consensus layer uses to produce them, so on-chain verification can never diverge from what validators sign. Its ABI matches IBlsG1.sol in tn-contracts.

Interface

blsVerify(bytes signature, bytes pubkey, bytes message) → bool
  • signature — 48-byte compressed G1 point.
  • pubkey — 96-byte compressed G2 point.
  • message — raw bytes, at most 4,096 bytes; hash-to-curve with the protocol’s domain-separation tag happens inside the verifier.

These are the protocol’s own compressed encodings — the identical bytes validators submit as proof-of-possession signatures to stake/delegateStake. Only exact 48/96-byte compressed encodings are accepted; uncompressed points are rejected.

Behavior

  • Returns ABI-encoded true/false. Malformed points, wrong lengths, and failed verifications all return false — the precompile never reverts on bad cryptographic input, matching BlsG1.blsVerify’s boolean contract.
  • Callable from STATICCALL (it is a pure view): ConsensusRegistry reaches it that way to verify validator proof-of-possession on staking.
  • Gas: 150,000 base plus 12 gas per 32-byte word of the ABI-encoded arguments (signature, pubkey, and message together), charged before the arguments are decoded.

Fee Distribution

TN modifies where transaction fees are sent compared to Ethereum.

Fee ComponentEthereumTelcoin Network
Priority fee (tip)Block proposerBatch producer (validator)
Base feeBurned (EIP-1559)Credited to the base-fee address (for governance processing)
Gas limit penaltyN/ACredited to the base-fee address (for governance processing)

The base fee is not removed from circulation on TN. It is credited to the chain’s base-fee address (the governance safe by default) for protocol use. See basefees for details on how the base fee adjusts per epoch, and penalties for the quadratic gas limit penalty mechanism.

Chain IDs

NetworkChain ID
Mainnet487
Testnet2017

Blob Transactions (EIP-4844)

TN does not support blob transactions yet. While the EIP-4844 transaction type is recognized, blob gas pricing is effectively disabled. Applications should use standard EIP-1559 transactions.

Block Header Differences

TN repurposes several Ethereum block header fields to carry consensus-layer metadata. These differences do not affect contract execution but are relevant for indexing tools, block explorers, or services that read block headers directly.

Repurposed Fields

FieldEthereum MeaningTN Meaning
noncePoW mining nonceEpoch and consensus round, packed as (epoch << 32) | round
difficultyNetwork difficultyWorker ID and batch index, packed as (batch_index << 16) | worker_id
mix_hashPoW mix digestFork-gated (see Deriving the mix_hash field): on adiri before epoch 574, consensus output digest XOR’d with batch digest (just the output digest if no batches); from adiri epoch 574, and on mainnet from genesis, keccak256("TN_PREVRANDAO_V1" || epoch seed chain value as of this commit || consensus block number || batch index), the latter two little-endian u64
ommers_hashUncle block hashDigest of the consensus Batch executed to produce this block. B256::ZERO if no batches
parent_beacon_block_rootBeacon chain parent rootDigest of the ConsensusHeader that committed the transactions
extra_dataArbitrary miner dataCommittee-shuffle seed (the epoch seed chain value as of the closing commit) at epoch boundaries, empty bytes otherwise
base_fee_per_gasAdjusts per block (EIP-1559)Fixed for the entire epoch, adjusts at epoch boundaries. See basefees
withdrawalsBeacon chain withdrawalsValidator reward records at epoch boundaries, empty otherwise

The mix_hash derivation changed at the PREVRANDAO fork: adiri epoch 574, mainnet from genesis. On adiri, blocks from epochs before 574 use the legacy output_digest ^ batch_digest form, and blocks from epoch 574 onward use the seed-chain derivation. Mainnet builds use the seed-chain derivation at every epoch. Indexers that reproduce adiri mix_hash values from consensus data must branch on the block’s epoch (the upper 32 bits of nonce).

The seed chain value in that hash is the block’s own commit-level value, and the chain advances at every commit. It matches the extra_data of the epoch’s closing block only for that closing block itself; mid-epoch values appear on no executed header. To reproduce mix_hash for any other block, fold the epoch’s seed chain forward from consensus data — the epoch root, then each commit’s leader round and seed signature — up to the commit that produced the block.

Fixed / Unused Fields

FieldValueNotes
requests_hashEMPTY_REQUESTS_HASHEIP-7685 deposit requests not used
excess_blob_gas0Blob transactions not used

Decoding the difficulty Field

The difficulty field packs two values:

difficulty = (batch_index << 16) | worker_id
  • Bits 0-15 (lower 16 bits): worker_id
  • Bits 16+ (upper bits): batch_index

To extract them:

worker_id  = difficulty & 0xFFFF
batch_index = difficulty >> 16

Decoding the nonce Field

The nonce field packs epoch and round:

nonce = (epoch << 32) | round
  • Upper 32 bits: epoch number
  • Lower 32 bits: consensus round number

Deriving the mix_hash Field

mix_hash (exposed to contracts as PREVRANDAO, EIP-4399) is fork-gated on the epoch of the committing leader, which is recoverable from the upper 32 bits of the block’s nonce. The PREVRANDAO fork is adiri epoch 574 (PREVRANDAO_FORK_EPOCH in tn-types forks.rs, compiled only into adiri builds); mainnet builds use the post-fork derivation from genesis.

  • Before the fork (adiri epochs below 574): the consensus output digest XOR’d with the batch digest; just the output digest when the output carried no batches (the epoch-closing block). Both inputs are on the block header, so this equals parent_beacon_block_root ^ ommers_hash.

  • From the fork on (adiri epoch 574 onward, every mainnet epoch):

    mix_hash = keccak256("TN_PREVRANDAO_V1" || seed || consensus_block_number || batch_index)
    

    where seed is the 32-byte epoch seed chain value as of the committing consensus output. The chain folds one seed signature per commit, so the value advances every commit; the closing commit’s value is the one published as extra_data on the epoch’s closing block. The two integers are little-endian u64s. The gate also requires the seed-signature fork (adiri epoch 383), so it never hashes the legacy leader-aggregate seed that pre-383 epochs carry; on adiri that fork is already active by epoch 574.

On adiri, indexers reproducing mix_hash from consensus data must branch on the block’s epoch, not on node version or wall-clock time.

Note on bias: the seeded derivation removes payload grinding (every input is pinned by the committed order, so the leader cannot generate candidate values by re-cutting its proposal), but the committing leader can still compute every PREVRANDAO its commit will produce before broadcasting and withhold the proposal (one propose-or-withhold choice per commit). Contracts that need unbiasable randomness must not rely on PREVRANDAO alone.

Summary

PropertyEthereumTelcoin Network
EVM opcodesStandardStandard (identical)
Gas costsStandardStandard (identical)
Transaction typesLegacy, EIP-2930, EIP-1559, EIP-4844Legacy, EIP-2930, EIP-1559
Native asset ERC-20Requires WETH wrapperRequires WTEL wrapper
Custom precompilesNoneTEL issuance at 0x7e1, BLS verify at 0xb151 (both report 0xfe code)
Base fee destinationBurnedBase-fee address (governance)
Blob transactionsSupportedNot used
Contract languagesSolidity, Vyper, etc.Same
ToolingHardhat, Foundry, ethers.js, viemSame
Chain IDs1 (mainnet)487 (mainnet), 2017 (testnet)

Epoch Boundaries

Overview

Telcoin Network organizes consensus into fixed-duration epochs. Within an epoch, a stable committee of validators produces and orders transaction batches. At the boundary between epochs, the protocol performs three critical operations:

  1. Distribute validator rewards for work done during the epoch
  2. Select and shuffle the next validator committee using verifiable randomness
  3. Adjust the base fee based on the epoch’s aggregate gas usage

These operations execute as system calls within the last block of the epoch. They are deterministic and they do not consume block gas.

Epoch Boundary Detection

Each epoch has a predefined time boundary. The consensus layer monitors the committed timestamp of each sub-DAG output. When an output’s committed timestamp exceeds the epoch boundary, the system sets a close_epoch flag on that output.

The execution engine checks this flag during block finalization. When present, it triggers the epoch-closing system calls before completing the block. If the epoch’s final consensus output contains no transaction batches, a single empty block is created specifically to carry these system calls.

Step 1: Distribute Validator Rewards

How Rewards Are Tracked

During the epoch, the protocol tracks how many consensus headers each validator committed as leader. Every time a validator’s leader certificate triggers a commit, their count increments by one. This happens regardless of whether the output contains transactions. Leaders for empty rounds still receive rewards for advancing the canonical consensus chain.

The tracking is purely in-memory. If a node restarts mid-epoch, it rebuilds the counts by walking already-executed blocks and replaying any consensus output that was committed but not yet processed.

How Rewards Are Applied

At the epoch boundary, the accumulated leader counts are converted into reward records:

FieldValue
validatorAddressThe validator’s execution layer address
consensusHeaderCountNumber of consensus headers they committed as leader

These records are passed to the ConsensusRegistry contract via the applyIncentives system call. The contract distributes the epoch’s issuance among validators, weighted by each validator’s stake and consensus header count; accrued rewards are paid out through the Issuance contract when claimed.

Reward records are also stored in the block body’s withdrawals field (repurposing the Ethereum withdrawal mechanism). This makes reward distribution visible to block explorers and indexers without requiring them to trace system call execution. Outside of epoch boundaries, the withdrawals field is empty. See EVM Compatibilty for details on repurposed block header fields.

Step 2: Select and Shuffle the Validator Committee

Why Shuffling Matters

A predictable committee order could enable targeted attacks against specific validators. TN uses verifiable randomness derived from BLS signatures to shuffle the committee, ensuring no party can predict or manipulate the ordering.

The Selection Process

  1. Read eligible validators — the protocol queries the ConsensusRegistry contract for all validators with Active or PendingActivation status
  2. Handle insufficient validators — if the number of active validators is less than the required committee size, validators with PendingExit status are randomly selected to fill the remaining slots
  3. Generate randomness — the epoch seed chain produces a 32-byte seed: each epoch starts from a root derived from the epoch number, and every committed sub-DAG folds the committing leader’s round and its deterministic BLS seed signature into the running value. The seed is deterministic (all nodes compute the same value) and unpredictable (it cannot be computed before the immediately preceding commit is published)
  4. Fisher-Yates shuffle — the validator list is shuffled using the BLS-derived seed as the random number generator input
  5. Truncate to size — the shuffled list is trimmed to the target committee size
  6. Sort by address — the selected committee is sorted in ascending address order for deterministic indexing
  7. Commit on-chain — the new committee is passed to ConsensusRegistry.concludeEpoch(newCommittee)

Validator Status Transitions

The ConsensusRegistry contract manages validator lifecycle:

StatusMeaning
ActiveEligible for committee selection
PendingActivationStaked and activated mid-epoch; eligible for selection, becomes Active at the boundary
PendingExitRequested to leave but may be retained if the committee is short

Validators transition between states based on on-chain governance actions. The epoch boundary is when these transitions take practical effect. Newly activated validators become eligible starting from the next epoch’s committee selection.

Step 3: Adjust the Base Fee

At the epoch boundary, the protocol evaluates each worker’s aggregate gas usage against that worker’s gas target and adjusts its base fee for the next epoch using the EIP-1559 algorithm.

The GasAccumulator tracks three metrics per worker throughout the epoch:

MetricDescription
blocksNumber of blocks the worker produced
gas_usedTotal gas consumed across all blocks
gas_limitTotal gas limit across all blocks

These totals drive the base fee adjustment:

  • Gas used = target — base fee stays the same
  • Gas used > target — base fee increases (bounded by EIP-1559 max change denominator)
  • Gas used < target — base fee decreases (with a protocol-defined minimum floor)

After adjustment, all accumulators are cleared for the next epoch. See Basefees for the full base fee model.

System Calls

The epoch-closing operations execute as system calls, which are special transactions issued by the protocol rather than by users.

PropertyValue
CallerSYSTEM_ADDRESS (0xfffffffffffffffffffffffffffffffffffffffe)
TargetConsensusRegistry contract
Gas limit100,000,000 per call
Gas price0 (no cost to the block)
Nonce checkDisabled
State changesCommitted immediately

System calls are invisible to users. They do not appear in the transaction list, do not consume block gas, and do not affect the gas used counter. Their effects are visible only through state changes (updated balances, committee rotations) and the block header metadata (extra_data and withdrawals fields).

Pre-Execution System Calls

In addition to epoch-closing calls, every block includes pre-execution system calls that run before user transactions:

  • Consensus root storage (EIP-4788 style) — the first batch in each consensus output writes the ConsensusHeader digest to the beacon roots contract, making consensus data accessible to smart contracts
  • Block hash history (EIP-2935) — the parent block hash is stored in the history contract for on-chain lookups

Block Header at Epoch Boundaries

Two block header fields change behavior at epoch boundaries:

FieldAt epoch boundaryNormal blocks
extra_dataCommittee-shuffle seed (the epoch seed chain value)Empty bytes
withdrawalsValidator reward distribution recordsEmpty list

The extra_data value serves as a public indicator that the epoch closed at this block. It also provides the randomness seed used for committee shuffling, allowing anyone to verify the shuffle was performed correctly.

Summary

PropertyValue
TriggerCommitted timestamp exceeds epoch boundary
OperationsRewards, committee shuffle, base fee adjustment (in that order)
Execution methodSystem calls (zero gas cost, no user-visible transactions)
Randomness sourceEpoch seed chain (BLS seed signatures folded across the epoch’s commits)
Shuffle algorithmFisher-Yates with BLS-derived seed
Reward trackingLeader block count per validator across the epoch
Base fee adjustmentEIP-1559 formula applied to epoch aggregate gas usage
Visible in headersextra_data (shuffle seed) and withdrawals (rewards)

Canonical Updates

Overview

Telcoin Network maintains three complementary chains:

  1. Consensus Chain — a hash-linked chain of ConsensusHeaders that records the ordering decisions made by BFT consensus. This chain is the source of truth for transaction ordering.
  2. Execution Chain — a standard Ethereum-compatible chain of blocks. Each block contains executed transactions and the resulting state changes.
  3. Epoch Chain — a hash-linked chain of EpochRecords, one per epoch, that checkpoint committee membership and chain state at each epoch boundary. This chain enables trustless node synchronization without requiring execution state.

The key architectural principle is consensus-first execution: transactions are ordered by consensus before they are executed. The consensus layer determines what transactions run and in what order. The execution layer applies them to EVM state.

This separation means the consensus layer is execution-agnostic. It operates on raw transaction bytes and cryptographic certificates without any knowledge of EVM, gas, or smart contracts. Different execution environments can be supported in the future without modifying the consensus protocol.

The Consensus Chain

ConsensusHeaders

The consensus chain is a sequence of ConsensusHeaders, each linked to its predecessor by a parent_hash:

FieldDescription
parent_hashKeccak-256 hash of the previous ConsensusHeader
sub_dagThe committed sub-DAG from consensus (contains ordered certificates)
numberBlock height in the consensus chain

Each header’s digest is computed from hash(parent_hash || sub_dag.digest() || number), forming a tamper-evident chain.

Committed Sub-DAGs

TN uses a DAG-based BFT consensus protocol (Bullshark). Validators produce certificates that reference prior certificates, forming a directed acyclic graph. When a leader certificate reaches a quorum of support, the sub-DAG it commits is finalized.

A CommittedSubDag contains:

  • Certificates — signed proposals from validators, each referencing batches of transactions
  • Leader certificate — the certificate that triggered the commit
  • Reputation scores — per-validator performance metrics from the round
  • Commit timestamp — when the sub-DAG was committed

BLS Signatures

Every certificate carries an aggregated BLS signature from the quorum of validators that endorsed it. These signatures serve two purposes:

  1. Verification — any node can verify a certificate’s authenticity against the known committee without re-executing transactions
  2. Randomness — at epoch boundaries, committee shuffling is seeded by the epoch seed chain: each committed leader header carries a deterministic BLS seed signature, and every commit folds it into a running hash across the epoch (see Epoch Boundaries)

Certificates can be verified directly (checked against the current committee) or indirectly (verified through a chain of trust from a previously verified certificate).

The Execution Chain

From Consensus Output to EVM Blocks

When a sub-DAG is committed, the consensus layer produces a ConsensusOutput. The output is an enriched view of the committed data that includes the actual transaction bytes. The execution engine processes this output to extend the EVM chain.

The flow:

  1. Consensus commits a sub-DAG, producing a ConsensusOutput
  2. Batches are extracted — each certificate may contain one or more batches of transactions from different workers
  3. One EVM block per batch — the engine creates and executes one block for each batch, in order
  4. State is updated — each block’s state changes are committed to the EVM state trie

Each batch carries its own metadata:

  • Beneficiary — the validator address that produced the batch (receives priority fees)
  • Base fee — the base fee for this epoch
  • Worker ID — which worker produced the batch
  • Transactions — serialized transaction bytes

When Blocks Are Produced

Not every consensus output results in new EVM blocks. The behavior depends on whether the output contains batches and whether the epoch is closing:

ConditionResult
Output contains batchesOne EVM block per batch
No batches, epoch not closingNo block produced — execution is skipped entirely
No batches, epoch is closingOne empty block created to execute epoch-closing system calls

The “no batches, not closing” case is important for efficiency. Consensus continues to run and commit sub-DAGs even when there are no user transactions. In these cases, the execution chain does not advance, avoiding unnecessary empty blocks. The consensus leader’s participation is still tracked for reward purposes. Consistently extending the consensus chain is important to confirm validators are not byzantine and the network is live.

When an epoch is closing but there are no batches, a single empty block is created specifically to trigger the system calls that distribute rewards and rotate the validator committee. See Epoch Boundaries for details.

Block Header Metadata

Each EVM block header carries consensus metadata in repurposed Ethereum header fields. The parent_beacon_block_root field stores the ConsensusHeader digest, linking each execution block back to the consensus chain. Other fields encode the epoch, round, worker ID, and batch index. See EVM Compatibility for the full mapping.

The Epoch Chain

EpochRecords

The epoch chain is a sequence of EpochRecords, one per epoch, each linked to its predecessor by a parent_hash:

FieldDescription
epochThe epoch number (block height in the epoch chain)
committeeBLS public keys of the active committee for this epoch
next_committeeBLS public keys of the committee for the next epoch
parent_hashKeccak-256 hash of the previous EpochRecord
final_stateBlock number and hash of the last execution block in this epoch
final_consensusBlock number and hash of the last ConsensusHeader in this epoch

Each record’s digest is computed from all its fields, forming a tamper-evident chain. The final_state and final_consensus fields serve as signed checkpoints and anchor the epoch chain to both the execution chain and the consensus chain, allowing any node to verify that all three chains agree.

EpochCertificates

Epoch records are signed by the outgoing committee at the start of the next epoch. This happens asynchronously, so the network continues operating while signatures are collected in the background.

The signing process:

  1. Voting — each committee member produces an EpochVote: a BLS signature over the record’s digest, scoped with an EpochBoundary intent to prevent signature reuse across different protocol contexts
  2. Aggregation — votes are collected and aggregated into an EpochCertificate containing a single aggregated BLS signature and a bitmap indicating which committee members signed
  3. Threshold — a certificate is valid when it contains signatures from a super-quorum (2/3 + 1) of the committee

Different nodes may hold certificates with different signer sets. This is expected and correct because all that matters is that each certificate meets the super-quorum threshold. Every node that stores a valid certificate also becomes a source for other syncing nodes.

Why the Epoch Chain Exists

The epoch chain solves a bootstrapping problem: a syncing node needs to know which validators form the committee for each epoch in order to verify consensus output, but the on-chain committee state only becomes available after execution catches up.

The epoch chain breaks this circular dependency by providing committee membership and chain checkpoints through a lightweight, independently verifiable structure. A node with only the genesis committee can:

  • Verify epoch 0’s record using the genesis committee’s signatures
  • Learn epoch 1’s committee from epoch 0’s next_committee field
  • Verify epoch 1’s record using that committee
  • Continue forward through every epoch, acquiring all committee memberships

This requires no execution state and no trust beyond the genesis committee. One epoch record is generated per day, so the total sync burden for the epoch chain is minimal even after years of operation.

Security Properties

The epoch chain derives its security from five reinforcing mechanisms:

  1. BLS aggregate signature verification — each certificate is verified against the known committee’s public keys. Intent-scoped signing prevents signatures from being replayed across different protocol contexts (e.g., a consensus certificate signature cannot be reused as an epoch vote).
  2. Parent hash chaining — each record commits to the previous record’s digest. Records cannot be reordered, omitted, or inserted without breaking the chain. Verification is sequential from genesis, so a valid chain of records implies a valid history.
  3. Committee continuity — each record’s committee is validated against the previous record’s next_committee field. This ensures that committee transitions follow the on-chain governance decisions made during the epoch. The validation tolerates validator dropouts between epochs (requires at least 2/3 overlap and a minimum of 4 committee members).
  4. Super-quorum threshold (2/3 + 1) — the standard BFT safety bound. Up to 1/3 of the committee can be Byzantine (malicious or offline) without compromising the validity of epoch records. A valid certificate proves that at least 2/3 + 1 honest validators agreed on the record’s contents.
  5. Deterministic record content — all nodes produce identical epoch records from the same inputs. There is no ambiguity about what an epoch record should contain for a given epoch. A valid super-quorum certificate therefore proves that the committee reached agreement on a single, deterministic record.

Attack Surfaces and Mitigations

AttackRisk LevelMitigation
Fake epoch records from peersMitigatedThree-part validation at the application layer: parent hash match, committee validity, BLS certificate verification. Invalid records are rejected before storage.
1/3 Byzantine committeeAccepted (BFT bound)The 2/3 + 1 signature threshold is the standard Byzantine fault tolerance bound. Safety holds as long as fewer than 1/3 of committee members are malicious.
Long-range fork (replaying old valid records)MitigatedParent hash chaining and sequential sync from genesis prevent forking. An attacker cannot substitute an alternative history without producing a valid chain from genesis with super-quorum signatures at every epoch.
Eclipse attack during syncNetwork-levelIf an attacker controls all of a syncing node’s peers, they could serve a fabricated chain. Mitigation relies on peer diversity at the network layer and trusted bootstrap peers.
Dropped validator between epochsAcceptedA validator may be removed on-chain between when the epoch record is created and when it is signed. Committee validation tolerates this as long as at least 2/3 overlap is maintained between the expected and actual committee.
Delayed certificate availabilityBy designCertificates are collected asynchronously after the epoch closes. If a peer does not yet have a certificate, the syncing node retries. Sync stalls briefly but cannot accept invalid records.

Execution-Agnostic Consensus

The consensus layer has no dependency on EVM or any specific execution environment:

  • Raw bytes — transactions are stored and ordered as opaque byte arrays (Vec<Vec<u8>>). Consensus does not parse, validate, or interpret transaction contents.
  • No gas awareness — consensus orders transactions before gas costs are known. The gas limit penalty mechanism (see Gas Limit Penalty) exists specifically because consensus cannot know actual gas usage at ordering time.
  • Cryptographic verification only — ConsensusHeaders can be validated using only BLS signature verification and hash checks, without executing any transactions.

This design means the consensus protocol could support alternative execution environments (e.g., a WASM VM or a ZK execution layer) by changing only the execution engine, not the consensus layer.

Trustless Node Synchronization

TN can synchronize any node from genesis with a single trust anchor: knowledge of the genesis committee. The three chains — epoch, consensus, and execution — work together to enable this.

Node Types

  • Active validators participate in consensus directly and execute blocks as they are committed
  • Observer nodes follow the consensus chain by streaming ConsensusHeaders from peers without participating in consensus rounds
  • Rejoining validators stream headers while syncing batches to catch up with the current state before rejoining consensus

Step 1: Sync the Epoch Chain

The syncing node downloads and verifies epoch records sequentially, starting from epoch 0:

  1. Request an epoch record and its certificate from any peer
  2. Verify three properties:
    • The record’s parent_hash matches the previous record’s digest (chain integrity)
    • The record’s committee matches the previous record’s next_committee (committee continuity)
    • The certificate contains a valid super-quorum BLS signature from the expected committee (authenticity)
  3. Store the verified record and certificate to support other syncing nodes
  4. Repeat for the next epoch until no more records are available

For epoch 0, the genesis committee serves as the trust anchor. Each subsequent epoch’s committee is learned from the previous record’s next_committee field, extending the chain of trust forward.

This step produces two critical outputs:

  • Committee membership for every epoch (required to verify consensus output)
  • Checkpoint hashes — final_consensus and final_state from each record provide verified anchor points for the consensus and execution chains

The epoch chain is lightweight and only contains one record per epoch (approximately one per day). Syncing the epoch chain completes quickly even after years of operation. On node restart, sync always begins from epoch 0 to backfill any gaps.

Step 2: Sync Consensus Headers

Using the verified checkpoint hashes from epoch records, the node downloads consensus headers:

  1. Obtain a verified hash — epoch records provide the final_consensus hash (the last ConsensusHeader digest for each epoch). Current committee members also gossip live consensus output with signatures, providing the latest hash.
  2. Walk backwards — request the ConsensusHeader for the verified hash from a peer. Verify the header’s digest matches. Then request its parent header using the parent_hash field. Repeat until all missing headers are retrieved.
  3. Verify each header — every ConsensusHeader contains certificates with aggregated BLS signatures that can be verified against the known committee for that epoch (obtained in Step 1).

Consensus header sync can proceed in parallel with epoch record sync. As soon as a new epoch record provides a checkpoint hash, the node can begin downloading that epoch’s consensus headers.

Verified hash sources, in order of availability:

SourceWhen AvailableNotes
Epoch records (final_consensus)After epoch record syncHistorical checkpoint per epoch
Live committee gossipOnce current committee is knownProvides the latest consensus header hash in real time
Execution blocks (parent_beacon_block_root)After execution syncEach block stores the ConsensusHeader digest that produced it — useful for fork detection but lags behind consensus

Step 3: Re-execute Batches

Once the node has an unbroken chain of consensus headers, it replays them forward to reconstruct EVM state:

  1. Order headers — consensus headers were retrieved backwards (newest to oldest) but must be executed forwards (oldest to newest)
  2. Extract batches — each ConsensusHeader contains a CommittedSubDag with ordered certificates referencing transaction batches
  3. Execute blocks — one EVM block is created and executed per batch, applying each transaction to the state trie
  4. Verify state — epoch records provide final_state checkpoints (block number and hash) that the node can verify against its locally computed execution state at each epoch boundary

This step transforms the cryptographically verified ordering (consensus chain) into the actual account state, balances, and contract storage (execution chain).

Recovery After Restart

When a node restarts mid-epoch, it must rebuild its in-memory state:

  1. Walk already-executed blocks for the current epoch to restore gas statistics and leader counts
  2. Replay any consensus output that was committed but not yet executed before shutdown
  3. Resume live consensus participation

This ensures no consensus rounds are skipped or double-counted.

Summary

PropertyConsensus ChainExecution ChainEpoch Chain
UnitConsensusHeaderEVM BlockEpochRecord
Linkingparent_hash → digest chainparent_hash → block hash chainparent_hash → record digest chain
ContentCertificates, batches (raw bytes)Transactions, receipts, state changesCommittee keys, state checkpoints
VerificationBLS signatures + hashesState root + receipt rootBLS super-quorum (2/3 + 1) certificate
AdvancementEvery committed sub-DAGOnly when batches exist or epoch closesOnce per epoch
PurposeTransaction orderingState executionTrustless committee sync

Economic Incentives and Rewards

Telcoin Network’s economic model creates alignment between validator interests and network success through carefully structured incentives. Block rewards follow a dynamic issuance model based on consensus participation and leadership roles within committees. Transaction fees, particularly priority fees, are awarded to block proposers to incentivize maximum transaction processing. The validator’s stake requirement ensures long-term alignment with network success, while competitive operational expenses maintain attractive returns relative to capital commitment.

Each validator in the committee operates an independent transaction pool to propose unique batches for the next consensus block. The committee is responsible for validating the proposed transactions before deterministically ordering all batches of transactions to produce the next consensus block. Consensus blocks have a leader for the committed round, and the leader receives the block rewards at the end of the committee’s term. The period of time a committee is responsible is called an “epoch”. Block rewards for all leaders are automatically credited by the protocol at the end of each epoch, drawn from an issuance pool that governance funds on-chain.

The consensus block is then executed, which results in the beneficiary’s account balance increasing. An important distinction from other protocols is that each batch within the consensus block is executed as a separate block in the EVM. This is how the protocol can support multiple basefees in parallel, something that ultimately allows MNOs to protocolize “Quality-of-Service” within the same blockchain.

The two greatest costs to committee validators are computation during EVM execution and uploading batches of transactions for consensus. The cost of computation during execution is recovered through basefees and priority tips. The batch producer receives all the priority fees from transactions that make it to the final executed block. 100% of each transaction’s basefee is transferred to the governance safe to fund future block rewards.

In addition to transaction fees, the leader of the consensus block receives the reward for the round. Telcoin Association governance specifies the total amount of TEL issued per epoch by updating the consensus registry on-chain. Each epoch’s issuance is divided between leaders in proportion to the number of consensus blocks each one led, and the amount is adjustable so rewards can adjust based on overall transaction volume.

One current limitation of the protocol allows for the possibility of duplicate transactions within competing batches from different validators. The first executed batch that contains this transaction receives the priority fees if there are any. However, basefees and block rewards remain the same.