Skip to content

RPC.md — Networking, Transaction Bytes & the Estimate Strategy

Living reference for internal/rpc. Tier-1 authority remains docs/ARCHITECTURE.md §5.4/§5.7; this file documents the implemented contracts.


Connection discipline (client.go)

  • gRPC only. net/http never appears on the network surface (rpc/tvm/state/ script); HTTPS is sanctioned solely for managed binary downloads (compiler/solcmgr, audit).
  • One grpc.ClientConn per (network, service): rpc.Dial opens two connections — FullNode protocol.Wallet (broadcast + unsolidified reads) and protocol.WalletSolidity (solidified reads) — and never multiplexes them. Both endpoints come from the network's sail.toml entry.
  • Keepalive 30 s/10 s; every unary call gets a default 10 s deadline when the caller supplies none (WithCallTimeout overrides).
  • Retries are transport-only: codes.Unavailable, 3 attempts, 200 ms backoff. Application errors — including node-side validation failures — surface on the first attempt, always.
  • TRON public gRPC endpoints are plaintext by protocol convention (ports 50051/50061), hence insecure transport credentials.

Transaction byte rules (txbuilder.go)

Protocol constants with dedicated tests (CLAUDE.md §3.9):

ref_block_bytes = bytes 6–7 of the 8-byte big-endian block height
ref_block_hash  = bytes 8–15 of the 32-byte block id
expiration      = timestamp + window        (default window: 60 s)
txid            = SHA-256(serialized raw_data)

TestTxID_MatchesRealTransaction re-serializes real mainnet transactions through the generated pb types and reproduces their on-chain txids byte for byte — proving wire compatibility of our serialization, not just the hash rule.

Chain parameters (chainparams.go)

getEnergyFee (sun per energy unit) and getTransactionFee (sun per bandwidth byte) are read via GetChainParameters; the per-contract dynamic-energy factor (10⁴ precision) via GetContractInfo → contract_state.energy_factor. All are cached with a session TTL (default 10 min — well inside TRON's 6-hour maintenance cycles). No numeric network price exists anywhere else in the codebase; a missing key is an error, never a silent default.

sun estimate strategy (estimate.go, ARCHITECTURE §5.4)

  1. Primary — EstimateEnergy. Availability is gated by the node's vm.estimateEnergy setting, so detection is by runtime capability: the node's own refusal (its characteristic "does not support estimate energy" result, or an absent method) triggers the fallback and is memoized for the process. Version strings are never consulted. Semantics (Erratum E4, Nile-confirmed): the node's energy_required is a fee-limit binary-search upper bound with 1-TRX granularity (up to ≈2× the exact figure for cheap calls) — precisely the right number for a fee-limit-safe energy budget, but NOT the exact E_total; the exact breakdown comes from the fallback path or local metering.
  2. Fallback — TriggerConstantContract. energy_used is the penalty-inclusive total; the breakdown is basic = energy_used − energy_penalty. Cross-check: recomposing ⌊basic × (10000 + energy_factor)/10000⌋ with the chain-read factor must reproduce the total; a mismatch sets Reconciled=false with the signed DiscrepancyEnergy — reported, never hidden.
  3. Bandwidth = serialized size of the real transaction (built against the node's current block via txbuilder, with a placeholder 65-byte signature) plus the 64-byte protocol result margin (java-tron's MAX_RESULT_SIZE_IN_TX — a size constant, not a price).
  4. TRX burn (worst case, no staked resources) = Energy × getEnergyFee + Bandwidth × getTransactionFee.

Output is a table (source labeled; fallback shows the basic/penalty breakdown; discrepancies render as a warning) or the full JSON document under --json.

Hermetic testing (rpctest/)

An in-process gRPC server registers both services on one loopback listener (production code still opens its two separate conns against it) with programmable handlers and per-method call counters. Canned profiles: ProfileWithEstimateEnergy and ProfileWithoutEstimateEnergy (which answers with the real node's refusal message). Every rpc test — including the e2e flows driving the real binary — runs against it; nothing in make check or make e2e touches the real network. Real-network coverage lives behind //go:build differential (make differential, requires NILE_GRPC, NILE_SOLIDITY_GRPC and — for the signing test — NILE_PRIVATE_KEY).