Skip to content

EVM - #154

Open
lordbutterfly-hive wants to merge 16 commits into
developfrom
feature/evm-mapping-bot
Open

EVM#154
lordbutterfly-hive wants to merge 16 commits into
developfrom
feature/evm-mapping-bot

Conversation

@lordbutterfly-hive

Copy link
Copy Markdown
Contributor

Summary

Standalone bot binary for the EVM bridge — scans Ethereum for deposits, constructs MPT proofs, submits to the EVM
mapping contract, and handles the withdrawal broadcast + confirmation pipeline.

What it does

  • Deposit scanning — polls finalized Ethereum blocks via RPC, detects ETH transfers and ERC-20 Transfer events to
    the vault address
  • Proof construction — builds Merkle Patricia Trie proofs from full block receipt/transaction data, submits map
    calls to the contract via L2 transactions
  • Withdrawal pipeline — reads pending withdrawals from contract state, queries TSS signatures via
    getTssRequests, assembles signed EIP-1559 transactions, broadcasts to Ethereum, submits confirmSpend with receipt
    proofs
  • L2 submission — uses TransactionCrafter.SignFinal (same path as the UTXO mapping bot) for all contract
    interactions. Separate DID, separate nonce space, no interference with existing bots.
  • Checkpoint persistence — atomic write (tmp + rename), survives restarts, deposits idempotent via contract dedup

Security

Two security passes (15 attack vectors, 53 test cases):

  • DER signature parsing — 6 bounds checks prevent crash on malformed TSS responses
  • Pending spend validation — negative amounts and empty fields rejected
  • Block scan retry — failed deposits don't advance checkpoint, liveness alert after 10 retries
  • RLP content bounds — decodeRLPListHeader validates content fits data
  • Stale withdrawal cleanup — detects nonce advancement from other instances

Configuration (env vars)

Var Required Default Description
ETH_RPC yes http://localhost:8545 Ethereum RPC (Helios recommended)
VAULT_ADDRESS yes Vault ETH address (0x...)
CONTRACT_ID yes Magi contract ID (vsc1...)
BOT_ETH_PRIVKEY no auto-generated Hex secp256k1 key for L2 signing
GRAPHQL_URLS no https://api.vsc.eco/api/v1/graphql Magi node endpoints (comma-separated)
NET_ID no vsc-mainnet Network ID
RC_LIMIT no 10000 RC limit per L2 transaction
NETWORK no mainnet mainnet or testnet

Files

  • cmd/evm-mapping-bot/main.go — bot binary (1,850 lines)
  • cmd/evm-mapping-bot/security_pass1_test.go — 53 security tests

Dependencies

  • Requires feature/evm-bridge-node PR merged first (crypto host functions + ledger support)
  • Requires EVM mapping contract deployed on target network
  • Bot DID must be funded with HBD for RC

Test plan

  • go build ./cmd/evm-mapping-bot/ compiles
  • go vet ./cmd/evm-mapping-bot/ clean
  • go test ./cmd/evm-mapping-bot/ -run TestSecurity — 53/53 pass
  • go test -race ./cmd/evm-mapping-bot/ -run TestSecurity — zero races
  • Deploy contract to testnet, run bot against it, verify first deposit end to end

Comment thread cmd/evm-mapping-bot/main.go Dismissed
@techcoderx
techcoderx changed the base branch from main to develop April 20, 2026 04:40
@miloridenour
miloridenour force-pushed the develop branch 2 times, most recently from 01985c2 to 81442c5 Compare May 18, 2026 20:09
@miloridenour
miloridenour force-pushed the develop branch 2 times, most recently from ff6561c to df882dc Compare May 19, 2026 18:27
@miloridenour
miloridenour force-pushed the develop branch 3 times, most recently from 2928dea to 75c384f Compare June 2, 2026 00:25
@miloridenour
miloridenour force-pushed the feature/evm-mapping-bot branch from e4f39c0 to 77ffd6f Compare June 8, 2026 20:26
@miloridenour miloridenour changed the title EVM mapping bot EVM Jun 24, 2026
@miloridenour
miloridenour force-pushed the develop branch 2 times, most recently from 24331ec to 7e1d77e Compare June 24, 2026 22:08
miloridenour and others added 10 commits June 30, 2026 20:57
The reindex ChainActiveAt callback queried electionDb before its Mongo
collection was bound (the elections plugin inits after the reindex gate),
nil-dereferencing and crash-looping every node on its first restart; it now
reads the elections collection directly off the connected DbInstance via a new
elections.ChainActiveVersionAt helper. Corrected the consensus-version devnet
test's "0.3" vs "0.3.0" target comparison and made candidate visibility
race-robust by latching on adoption. Added TestConsensusVersionLateAdoptionReindexDevnet,
which takes a node offline through a rollout to cover both the restart/catch-up
path and the seeded version-lag full reindex.
…/F21/F22)

Fix five deterministic, network-wide chain halts. Each panics before the
per-tx recover (executeTxSafely), so it escapes to the block-listener's
coarse recover and permanently stops the block pipeline on every node:

- F4: an unknown offchain op.Type left a nil VSCTransaction that was
  dereferenced in Ingest/ExecuteBatch. Unprivileged (any funded user via
  submitTransactionV1), devnet-confirmed.
- F21: a known op with an undecodable, attacker-controlled CBOR payload
  nil-dereferenced node.MarshalJSON in DecodeTxCbor.
- F9: a dropped bls.AggregatePubkeys error fed a nil/invalid pubkey into
  bls.Verify and panicked.
- F13: an empty witness list divided by zero in GenerateSchedule.
- F22: a zero-op tx indexed Ops[0] out of range in the block signer path.

F4/F21 are handled together: ToTransaction now returns ([]VSCTransaction,
error) and errors on the first unknown type or undecodable payload instead
of dropping ops or relying on zero-value rejection. An invalid tx fails
ATOMICALLY -- no op executes, a fixed 50 RC is charged, and the whole tx is
marked FAILED via the same oplog finalization as every other tx (new
TxPacket.Invalid branch in ExecuteBatch).
…4-3)

An incoming election whose new committee is below MinMembers (e.g. empty)
would persist and then drive consensus.GenerateSchedule into
witnessList[slot % 0] -- an integer divide-by-zero that panic-halts every
validating node. Reject it in TxElectionResult.ExecuteTx before StoreElection
so the prior committee stays in charge and the chain keeps producing.

Gated on the 0.4.0 consensus line (MinMembersGuardActive), resolved from the
version active at the election's submit height
(ActiveConsensusVersion(tx.Self.BlockHeight)) -- deterministic on-chain and
replay-safe (mainnet history has valid pre-raise 7-member elections). Keep the
pure GenerateSchedule empty-list backstop (already on develop as the F13 fix)
as defense-in-depth, and log invalid block-proposal rejections symmetrically
with the skip/stale cases.
The offchain unstake_hbd op built TxStakeHbd (identical to stake_hbd), so it
STAKED funds instead of releasing them -- the wrong direction. Build the
dedicated TxUnstakeHbd (ledgerSession.Unstake) instead. The L1 vsc.unstake_hbd
path was already correct; only the offchain path was wrong.

Correcting the direction changes ledger state on a VALID input, so it is
version-gated on the 0.4.0. chain-active version is threaded into
OffchainTransaction.ToTransaction via ActiveConsensusVersion at the op's
anchored height (same height in Ingest and ExecuteBatch), so a full reindex
reproduces historical ledger state and the fix flips network-wide only at
the 0.4.0 floor.
tibfox and others added 2 commits July 2, 2026 15:55
The contract's deposit-proof reader expects PER-RECEIPT logIndex (position
within the tx's own Logs list, 0..N-1). The bare upstream bot wrote
eth_getLogs's BLOCK-LEVEL index into the deposit payload — that's correct
only by accident when a tx emits exactly one matching log.

A multi-Transfer-log tx (e.g. one deposit that also triggers a token-side
side-effect Transfer to the same vault) maps both logs to block-level
indices that the contract treats as nonsense per-receipt positions ->
deposit either crashes parsing or credits the wrong sender/amount, and on
the surviving path collides on IsObserved -> permanently uncreditable.

Fix lifted from review5 W4-A CRIT #14 + the wetransfer F2-bot hardening:

- New lookupPerReceiptLogIndex(rpc, txHash, tokenAddr, vaultPaddedTopic,
  blockLogIndex) function. Fetches the receipt, finds the SPECIFIC log
  whose block-level logIndex matches the eth_getLogs entry, returns its
  per-receipt array position. Sanity-checks address/topics so a stale
  receipt or RPC mistake surfaces as an error rather than mis-resolving.
- Caller (the scanBlock ERC-20 deposit emit loop) now calls
  lookupPerReceiptLogIndex and writes the resolved position. Skips the
  deposit with a slog.Warn on lookup failure (will be re-detected on the
  next scan of the same height).
- F2 hardening: a non-standard RPC returning a log with no logIndex is
  rejected explicitly (hexToUint64("")==0 would otherwise silently match
  only block index 0, mis-resolving every multi-log tx).

Reuses upstream's existing rpc.getReceipt + TransferEventSig + hexToUint64
(no new helpers introduced). Build clean: go build ./cmd/evm-mapping-bot/.
@miloridenour
miloridenour force-pushed the feature/evm-mapping-bot branch from f7c3faa to 8149583 Compare July 3, 2026 16:30
Lands 20 approved MED fixes (MEDs 43,137,120,4,44,30,46,51,47,49,50,48,
98,96,72,99,91,57,115,116) onto cmd/evm-mapping-bot/main.go. MED-100 is
a sound deferral (separate-repo contract finding, zero uint16 casts in the
bot). Scrutinizer-PASS verdict: bot-all.md. Build + vet clean; zero new
test regressions (one pre-existing failure fixed by MED-30 forward-compat).
Also adds /evm-mapping-bot to .gitignore to prevent committing the compiled
output binary.
account-mapping zkverifier id (M23-F5 fix; bot was mapping zero deposits)

The bot capped its L1 scan at contractHeight = state key 'h' read from the
account-mapping contract, but M23-F5 moved 'h' into the ZK verifier contract.
The bot had no verifier id -> contractHeight always 0 -> scanUpTo=min(finalized,0)=0
-> mapped ZERO deposits. Fix: fetchContractLastHeight reads am['zkverifier'] then
'h' from that verifier (legacy local 'h' honored first; no new config). Devnet-proven
(BotResilience: LastScannedBlock 0 -> 74).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants