Simplify interfaces and remove adapters

- Remove AppSender adapter, use p2p.Sender directly
- Remove warpSignerAdapter, use luxWarp.Signer directly
- Rename TestSender methods to clean names (no App prefix)
- Rename RequestClient interface (was AppRequestClient)
- Fix ID type conversions in tests (use ID directly, not [:] slice)
- Update dependencies: consensus v1.22.32, node v1.22.20, vms v1.0.0
- Remove local replace directives
This commit is contained in:
2025-12-19 12:17:19 -08:00
parent 078d06009c
commit 42a0070e73
27 changed files with 1317 additions and 2343 deletions
+415
View File
@@ -537,3 +537,418 @@ The MigrateAPI has been successfully registered with the EVM node and is now ava
3. Test `migrate_getBlocks` with various block ranges
4. Test full export → import workflow via lux-cli
5. Verify imported data on destination chain
---
## Lux Blockchain Deployment Analysis (2025-12-18)
### Executive Summary
This analysis examines the deployment process for Zoo and SPC subnets, identifying why C-Chain import succeeded while subnet imports fail with `ErrPrunedAncestor`.
### Current State
| Chain | RLP File | Size | Import Method | Status |
|-------|----------|------|---------------|--------|
| C-Chain (96369) | lux-mainnet-96369.rlp | 1.28GB | admin.importChain | WORKED |
| Zoo (200200) | zoo-mainnet-200200.rlp | 1.3MB | admin.importChain | ErrPrunedAncestor |
| SPC (36911) | spc-mainnet-36911.rlp | 7.8KB | admin.importChain | Not tested yet |
---
### 1. Root Cause: Zoo RPC 404
**Diagnosis:** The subnet RPC endpoint returns 404 because the SubnetEVM plugin is not properly initialized or the blockchain is not registered with the node.
**Key Findings:**
1. **Plugin Registration**: SubnetEVM must be registered as a plugin with the node. The C-Chain uses `cchainvm` which is built into the node, but Zoo/SPC require the external `evm` plugin.
2. **Blockchain ID Mismatch**: The RPC path uses the blockchain ID (e.g., `ext/bc/<blockchain-id>/rpc`). If the subnet is not tracking the correct blockchain ID, RPC returns 404.
3. **Subnet Validation**: The node must be a validator for the subnet OR have explicit tracking enabled via `--track-subnets`.
**Solution:**
```bash
# Ensure subnet tracking
lux-node --track-subnets=<subnet-id>
# Verify blockchain registration
curl -X POST http://localhost:9650/ext/info -d '{
"jsonrpc":"2.0",
"id":1,
"method":"info.getBlockchainID",
"params":{"alias":"zoo"}
}'
```
---
### 2. Root Cause: ErrPrunedAncestor
**Location:** `/Users/z/work/lux/evm/core/block_validator.go:111-116`
```go
// Ancestor block must be known.
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
return consensus.ErrUnknownAncestor
}
return consensus.ErrPrunedAncestor
}
```
**The Check Flow:**
1. `HasBlockAndState` calls:
- `GetBlock(hash, number)` - checks if block exists
- `HasState(block.Root())` - checks if state trie exists
2. `HasState` implementation (`blockchain_reader.go:241-244`):
```go
func (bc *BlockChain) HasState(hash common.Hash) bool {
_, err := bc.stateCache.OpenTrie(hash)
return err == nil
}
```
**Critical Issue:** `OpenTrie` requires the FULL state trie to exist in the database. When a subnet starts fresh:
- Genesis block is written via `genesis.Commit()`
- Genesis state is committed to the trie database
- BUT the trie might not be accessible via `OpenTrie` if:
- The trie is stored in snapshot form only
- The trie root wasn't properly committed to disk
- The state scheme (HashDB vs PathDB) differs
**Root Cause:** Genesis state is committed, but `stateCache.OpenTrie(genesisRoot)` fails because the trie nodes are not where `OpenTrie` expects them.
---
### 3. Why C-Chain Worked But Zoo Doesn't
**C-Chain (cchainvm):**
1. **Built into node**: The C-Chain VM is compiled directly into the node binary
2. **Shared genesis**: Uses the network's genesis file which includes C-Chain state
3. **Continuous state**: C-Chain has been running since network genesis - full state trie exists
4. **State scheme consistency**: Uses the same state scheme as the node's defaults
**Zoo/SPC (SubnetEVM plugin):**
1. **External plugin**: Loaded dynamically, separate initialization path
2. **Fresh genesis**: Genesis is created from the subnet's genesis.json at deployment time
3. **State initialization gap**: Genesis state may be written but not properly accessible
4. **Import before consensus**: Import happens before the VM is fully bootstrapped
**Key Difference - State Cache Initialization:**
```go
// In blockchain.go - NewBlockChain
bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb)
// The stateCache is created with:
// - bc.db: the ethdb.Database
// - bc.triedb: the trie database
// For C-Chain: triedb already has the state from continuous operation
// For SubnetEVM: triedb only has genesis state from fresh Commit()
```
---
### 4. What Needs to be Fixed
#### Option A: Ensure Genesis State is Properly Accessible (RECOMMENDED)
**Location:** `core/genesis.go:417-451` (Genesis.Commit)
The issue is that `Genesis.Commit` writes the state, but the trie database might not be fully synced to disk or accessible via `OpenTrie`.
**Fix:**
```go
// In genesis.go Commit function, after writing genesis:
func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Block, error) {
block := g.toBlock(db, triedb)
// ... existing code ...
// CRITICAL: Ensure trie is committed and accessible
if err := triedb.Commit(block.Root(), false); err != nil {
return nil, fmt.Errorf("failed to commit genesis trie: %w", err)
}
// Verify the state is accessible
if _, err := triedb.NodeReader(block.Root()); err != nil {
return nil, fmt.Errorf("genesis state not accessible after commit: %w", err)
}
return block, nil
}
```
#### Option B: Initialize State Cache Before Import
**Location:** `plugin/evm/admin.go:108-154` (ImportChain)
Before importing blocks, ensure the genesis state is properly loaded:
```go
func (p *Admin) ImportChain(_ *http.Request, args *ImportChainArgs, reply *ImportChainReply) error {
// ... existing checks ...
chain := p.vm.eth.BlockChain()
genesis := chain.Genesis()
// Verify genesis state is accessible
if !chain.HasState(genesis.Root()) {
log.Warn("Genesis state not accessible, attempting to recover")
// Recommit genesis if needed
if err := p.vm.ensureGenesisState(); err != nil {
return fmt.Errorf("failed to ensure genesis state: %w", err)
}
}
// Now proceed with import
// ...
}
```
#### Option C: Use StateAt Instead of OpenTrie in HasState
**Location:** `core/blockchain_reader.go:241-244`
The current `HasState` uses `OpenTrie` which requires full trie. Consider using `StateAt` which can fall back to snapshots:
```go
// Current (strict):
func (bc *BlockChain) HasState(hash common.Hash) bool {
_, err := bc.stateCache.OpenTrie(hash)
return err == nil
}
// Alternative (more lenient, but maintains integrity):
func (bc *BlockChain) HasState(hash common.Hash) bool {
// First try OpenTrie (preferred - full state)
if _, err := bc.stateCache.OpenTrie(hash); err == nil {
return true
}
// Fall back to StateAt which can use snapshots
// Only for genesis block (number == 0)
if bc.Genesis() != nil && bc.Genesis().Root() == hash {
_, err := bc.StateAt(hash)
return err == nil
}
return false
}
```
**WARNING:** This option requires careful consideration - it's a workaround, not a fix.
---
### 5. Step-by-Step Plan to Get All Chains Operational
#### Phase 1: Verify Genesis State Accessibility
```bash
# 1. Check genesis state in database
cd /Users/z/work/lux/evm
# 2. Create a diagnostic tool to verify genesis state
# Add to plugin/evm/admin.go:
# RPC: admin_verifyGenesisState
# Returns: {genesisHash, genesisRoot, stateAccessible, snapshotAvailable}
```
#### Phase 2: Fix Genesis Commit
1. Update `core/genesis.go:Commit()` to ensure state is fully committed
2. Add verification step after commit
3. Test with fresh database
```bash
# Test genesis commit
go test ./core -run TestGenesisCommit -v
```
#### Phase 3: Deploy Zoo Subnet
```bash
# 1. Create fresh database directory
mkdir -p /tmp/zoo-test/db
# 2. Generate genesis with allocations
cat > /tmp/zoo-genesis.json << 'EOF'
{
"config": {
"chainId": 200200,
"subnetEVMTimestamp": 0,
"durangoTimestamp": 0,
"feeConfig": {
"gasLimit": 12000000,
"targetBlockRate": 2,
"minBaseFee": 25000000000,
"targetGas": 15000000,
"baseFeeChangeDenominator": 36,
"minBlockGasCost": 0,
"maxBlockGasCost": 1000000,
"blockGasCostStep": 200000
}
},
"alloc": {
"9011E888251AB053B7bD1cdB598Db4f9DEd94714": {
"balance": "0x193e5939a08ce9dbd480000000"
}
},
"timestamp": "0x6727e9c3",
"gasLimit": "0xb71b00",
"difficulty": "0x0",
"baseFeePerGas": "0x5d21dba00"
}
EOF
# 3. Deploy with lux CLI
lux l2 deploy zoo \
--genesis /tmp/zoo-genesis.json \
--vm-binary /Users/z/work/lux/evm/build/evm \
--local
# 4. Verify RPC is responding
curl -X POST http://localhost:9650/ext/bc/zoo/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
```
#### Phase 4: Import Historical Blocks
```bash
# 1. Connect to running node's admin API
curl -X POST http://localhost:9650/ext/bc/zoo/admin \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"admin_importChain",
"params":{
"file":"/Users/z/work/lux/state/rlp/zoo-mainnet/zoo-mainnet-200200.rlp"
}
}'
```
#### Phase 5: Verify Import Success
```bash
# Check block height
curl -X POST http://localhost:9650/ext/bc/zoo/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
# Check specific block
curl -X POST http://localhost:9650/ext/bc/zoo/rpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"eth_getBlockByNumber",
"params":["0x64",true]
}'
```
#### Phase 6: Test AMM Commands
```bash
# Add liquidity
lux amm add-liquidity \
--chain zoo \
--token0 0x... \
--token1 0x... \
--amount0 1000 \
--amount1 1000
# Swap
lux amm swap \
--chain zoo \
--tokenIn 0x... \
--tokenOut 0x... \
--amountIn 100
```
---
### Technical Deep Dive: State Trie Architecture
```
Genesis.Commit()
|
v
+----------------+----------------+
| |
v v
statedb.Commit() triedb.Commit()
| |
v v
State Root Hash Trie Nodes to Disk
| |
+---------> rawdb.Write <---------+
|
v
ethdb.Database
|
+------------+------------+
| | |
v v v
HashDB PathDB Snapshots
```
**The Gap:** After `Genesis.Commit()`, the state root is stored and trie nodes are written, BUT:
1. `stateCache.OpenTrie(root)` requires trie nodes to be in a specific location
2. If using HashDB scheme, nodes must be in `triedb.HashDB`
3. If using PathDB scheme (not supported per code), nodes must be in `triedb.PathDB`
4. Snapshots provide account/storage data but NOT trie structure
**Solution:** Ensure `triedb.Commit(root, true)` is called with `report=true` to force disk sync, and verify accessibility via `NodeReader`.
---
### Files to Modify
| File | Change | Priority |
|------|--------|----------|
| `core/genesis.go` | Add state verification after Commit | HIGH |
| `plugin/evm/admin.go` | Add genesis state check before import | HIGH |
| `core/blockchain.go` | Log state accessibility during NewBlockChain | MEDIUM |
| `plugin/evm/vm.go` | Add ensureGenesisState helper | MEDIUM |
---
### Verification Checklist
- [ ] Genesis state accessible via `OpenTrie`
- [ ] Block 0 (genesis) exists in rawdb
- [ ] Canonical hash for block 0 set correctly
- [ ] Chain config stored with genesis hash
- [ ] Snapshot for genesis state available
- [ ] Import chain skips genesis block (number == 0)
- [ ] Parent hash of block 1 matches genesis hash
- [ ] State root of genesis matches expected value
---
### Known Issues and Workarounds
**Issue 1:** `admin.importChain` skips Accept calls
**Location:** `plugin/evm/admin.go:231-233`
**Impact:** Imported blocks may not be finalized
**Workaround:** Manually trigger consensus acceptance after import
**Issue 2:** StateScheme PathDB not supported
**Location:** `plugin/evm/vm.go:623-626`
**Impact:** Must use HashDB scheme
**Workaround:** Ensure `state-scheme: "hash"` in config
**Issue 3:** Snapshot delayed init when state sync enabled
**Location:** `plugin/evm/vm.go:590`
**Impact:** Snapshots not available during import
**Workaround:** Disable state sync for import node
+222
View File
@@ -0,0 +1,222 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// export reads blocks from a SubnetEVM pebbledb and exports to RLP
package main
import (
"encoding/binary"
"encoding/hex"
"flag"
"fmt"
"os"
"github.com/cockroachdb/pebble"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/geth/rlp"
)
// SubnetEVM namespace for Zoo mainnet
var zooNamespace, _ = hex.DecodeString("337fb73f9bcdac8c31a2d5f7b877ab1e8a2b7f2a1e9bf02a0a0e6c6fd164f1d1")
func main() {
dbPath := flag.String("db", "", "Path to pebbledb")
output := flag.String("output", "blocks.rlp", "Output RLP file")
flag.Parse()
if *dbPath == "" {
fmt.Println("Usage: export -db /path/to/pebbledb -output blocks.rlp")
os.Exit(1)
}
// Open database in readonly mode
db, err := pebble.Open(*dbPath, &pebble.Options{ReadOnly: true})
if err != nil {
fmt.Printf("Failed to open database: %v\n", err)
os.Exit(1)
}
defer db.Close()
fmt.Printf("Database opened: %s\n", *dbPath)
// Find the tip height
tipHeight, tipHash, err := findTip(db)
if err != nil {
fmt.Printf("Failed to find tip: %v\n", err)
os.Exit(1)
}
fmt.Printf("Tip block: height=%d, hash=%s\n", tipHeight, hex.EncodeToString(tipHash))
// Create output file
outFile, err := os.Create(*output)
if err != nil {
fmt.Printf("Failed to create output file: %v\n", err)
os.Exit(1)
}
defer outFile.Close()
// Export blocks from 0 to tip
fmt.Printf("Exporting blocks 0 to %d...\n", tipHeight)
exported := 0
for height := uint64(0); height <= tipHeight; height++ {
block, err := getBlockByHeight(db, height)
if err != nil {
fmt.Printf("Warning: failed to get block %d: %v\n", height, err)
continue
}
// Encode block to RLP
blockRLP, err := rlp.EncodeToBytes(block)
if err != nil {
fmt.Printf("Warning: failed to encode block %d: %v\n", height, err)
continue
}
// Write length-prefixed RLP
lengthBuf := make([]byte, 4)
binary.BigEndian.PutUint32(lengthBuf, uint32(len(blockRLP)))
outFile.Write(lengthBuf)
outFile.Write(blockRLP)
exported++
if height%100 == 0 || height == tipHeight {
fmt.Printf("Exported block %d/%d\n", height, tipHeight)
}
}
fmt.Printf("\nExported %d blocks to %s\n", exported, *output)
}
func findTip(db *pebble.DB) (uint64, []byte, error) {
// Look for AcceptorTipKey
tipKey := append(zooNamespace, []byte("AcceptorTipKey")...)
tipHash, closer, err := db.Get(tipKey)
if err != nil {
return 0, nil, fmt.Errorf("AcceptorTipKey not found: %v", err)
}
tipHashCopy := make([]byte, len(tipHash))
copy(tipHashCopy, tipHash)
closer.Close()
// Get height from hash
height, err := getHeightByHash(db, tipHashCopy)
if err != nil {
return 0, nil, fmt.Errorf("failed to get height for tip: %v", err)
}
return height, tipHashCopy, nil
}
func getHeightByHash(db *pebble.DB, hash []byte) (uint64, error) {
// Key: namespace + 'H' + hash
key := append(zooNamespace, 'H')
key = append(key, hash...)
value, closer, err := db.Get(key)
if err != nil {
return 0, err
}
defer closer.Close()
if len(value) != 8 {
return 0, fmt.Errorf("invalid height value length: %d", len(value))
}
return binary.BigEndian.Uint64(value), nil
}
func getHashByHeight(db *pebble.DB, height uint64) ([]byte, error) {
// Iterate to find header with this height
// Key format: namespace + 'h' + be8(height) + hash
prefix := append(zooNamespace, 'h')
heightBytes := make([]byte, 8)
binary.BigEndian.PutUint64(heightBytes, height)
prefix = append(prefix, heightBytes...)
iter, err := db.NewIter(&pebble.IterOptions{
LowerBound: prefix,
UpperBound: append(prefix, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff),
})
if err != nil {
return nil, err
}
defer iter.Close()
if iter.First() {
key := iter.Key()
// Key is: namespace(32) + 'h'(1) + height(8) + hash(32) = 73 bytes
if len(key) >= 73 {
hash := key[41:73]
hashCopy := make([]byte, 32)
copy(hashCopy, hash)
return hashCopy, nil
}
}
return nil, fmt.Errorf("hash not found for height %d", height)
}
func getBlockByHeight(db *pebble.DB, height uint64) (*types.Block, error) {
hash, err := getHashByHeight(db, height)
if err != nil {
return nil, err
}
header, err := getHeader(db, height, hash)
if err != nil {
return nil, fmt.Errorf("failed to get header: %v", err)
}
body, err := getBody(db, height, hash)
if err != nil {
// Body might be empty for genesis
body = &types.Body{}
}
return types.NewBlockWithHeader(header).WithBody(*body), nil
}
func getHeader(db *pebble.DB, height uint64, hash []byte) (*types.Header, error) {
// Key: namespace + 'h' + be8(height) + hash
key := append(zooNamespace, 'h')
heightBytes := make([]byte, 8)
binary.BigEndian.PutUint64(heightBytes, height)
key = append(key, heightBytes...)
key = append(key, hash...)
value, closer, err := db.Get(key)
if err != nil {
return nil, err
}
defer closer.Close()
var header types.Header
if err := rlp.DecodeBytes(value, &header); err != nil {
return nil, fmt.Errorf("failed to decode header: %v", err)
}
return &header, nil
}
func getBody(db *pebble.DB, height uint64, hash []byte) (*types.Body, error) {
// Key: namespace + 'b' + be8(height) + hash
key := append(zooNamespace, 'b')
heightBytes := make([]byte, 8)
binary.BigEndian.PutUint64(heightBytes, height)
key = append(key, heightBytes...)
key = append(key, hash...)
value, closer, err := db.Get(key)
if err != nil {
return nil, err
}
defer closer.Close()
var body types.Body
if err := rlp.DecodeBytes(value, &body); err != nil {
return nil, fmt.Errorf("failed to decode body: %v", err)
}
return &body, nil
}
+22
View File
@@ -1118,6 +1118,28 @@ func (bc *BlockChain) LastAcceptedBlock() *types.Block {
return bc.acceptorTip
}
// SetLastAcceptedBlockDirect sets the last accepted block directly without
// going through the acceptor queue. This is used for importing historical blocks
// where full consensus acceptance is not required.
// WARNING: This bypasses the normal acceptance flow and should only be used
// for historical block imports where the blocks have already been validated.
func (bc *BlockChain) SetLastAcceptedBlockDirect(block *types.Block) error {
bc.chainmu.Lock()
defer bc.chainmu.Unlock()
bc.acceptorTipLock.Lock()
bc.lastAccepted = block
bc.acceptorTip = block
bc.acceptorTipLock.Unlock()
// Update the accepted block indices
if err := bc.writeBlockAcceptedIndices(block); err != nil {
return fmt.Errorf("failed to write accepted indices: %w", err)
}
return nil
}
// Accept sets a minimum height at which no reorg can pass. Additionally,
// this function may trigger a reorg if the block being accepted is not in the
// canonical chain.
+22 -9
View File
@@ -5,6 +5,7 @@ go 1.25.5
require (
github.com/VictoriaMetrics/fastcache v1.13.0
github.com/btcsuite/btcd/btcutil v1.1.6
github.com/cockroachdb/pebble v1.1.5
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/deckarep/golang-set/v2 v2.8.0
github.com/fjl/gencodec v0.1.1
@@ -15,26 +16,27 @@ require (
github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db
github.com/holiman/bloomfilter/v2 v2.0.3
github.com/holiman/uint256 v1.3.2
github.com/luxfi/cache v1.0.0
github.com/luxfi/cache v1.1.0
github.com/luxfi/codec v1.0.4
github.com/luxfi/consensus v1.22.27
github.com/luxfi/consensus v1.22.32
github.com/luxfi/constants v1.2.3
github.com/luxfi/crypto v1.17.23
github.com/luxfi/crypto v1.17.24
github.com/luxfi/database v1.2.12
github.com/luxfi/geth v1.16.55
github.com/luxfi/geth v1.16.56
github.com/luxfi/go-bip39 v1.1.2
github.com/luxfi/ids v1.2.4
github.com/luxfi/log v1.1.26
github.com/luxfi/math v1.0.2
github.com/luxfi/math v1.1.0
github.com/luxfi/metric v1.4.8
github.com/luxfi/node v1.22.14
github.com/luxfi/node v1.22.20
github.com/luxfi/p2p v1.4.7
github.com/luxfi/timer v1.0.1
github.com/luxfi/trace v0.1.4
github.com/luxfi/units v1.0.0
github.com/luxfi/upgrade v1.0.0
github.com/luxfi/utils v1.0.0
github.com/luxfi/vms v0.1.0
github.com/luxfi/utils v1.1.0
github.com/luxfi/vm v1.0.0
github.com/luxfi/vms v1.0.0
github.com/luxfi/warp v1.16.37
github.com/mattn/go-colorable v0.1.14
github.com/mattn/go-isatty v0.0.20
@@ -71,14 +73,15 @@ require (
github.com/cockroachdb/errors v1.12.0 // indirect
github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 // indirect
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect
github.com/cockroachdb/pebble v1.1.5 // indirect
github.com/cockroachdb/redact v1.1.6 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect
github.com/consensys/gnark-crypto v0.19.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
github.com/dchest/siphash v1.2.3 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dgraph-io/badger/v4 v4.8.0 // indirect
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
@@ -100,25 +103,33 @@ require (
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/graph-gophers/graphql-go v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/influxdata/influxdb-client-go/v2 v2.4.0 // indirect
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c // indirect
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/luxfi/mock v0.1.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/pointerstructure v1.2.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opentracing/opentracing-go v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/stun/v2 v2.0.0 // indirect
@@ -127,7 +138,9 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.10.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
+90 -12
View File
@@ -73,11 +73,14 @@ github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ=
github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
@@ -87,10 +90,14 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs=
github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w=
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
@@ -126,8 +133,12 @@ github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgx
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/getsentry/sentry-go v0.35.1 h1:iopow6UVLE2aXu46xKVIs8Z9D/YZkJrHkgozrxa+tOQ=
github.com/getsentry/sentry-go v0.35.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -138,6 +149,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
@@ -147,6 +160,8 @@ github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -161,6 +176,7 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -177,11 +193,14 @@ github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pI
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k=
github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0=
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
@@ -201,6 +220,13 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k=
github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8=
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs=
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 h1:vilfsDSy7TDxedi9gyBkMvAirat/oRcL0lFdJBf6tdM=
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -224,32 +250,38 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
github.com/luxfi/cache v1.0.0 h1:OcqGi/qs9ky+7O8fR6WMZtFsunvA40LBTg3QRQqJurs=
github.com/luxfi/cache v1.0.0/go.mod h1:BEvpffvV3SXs/KSa9nUC1TszxKwUdIHwIaoXpeiYU+Y=
github.com/luxfi/cache v1.1.0 h1:6LUyGGZ+rrMAJBbAU6+UwkcamXj3zsboRUodIof2Ong=
github.com/luxfi/cache v1.1.0/go.mod h1:9GvlEEE9rFPaaWxvVpSPwW8ZMo2+8VMNNcuPa4AwzPg=
github.com/luxfi/codec v1.0.4 h1:VvX0dfXcRznTFAohFj9aGiuipTQ8IfFYIKFDoqSwkkY=
github.com/luxfi/codec v1.0.4/go.mod h1:jeBaoj/RjO543/huHkufiSY+xjzT7LGIlAGMuLJPZQE=
github.com/luxfi/consensus v1.22.32 h1:XunzH3X0oYBF0E6hsBIOf1naU0arQiTcW3DjRcfjrEE=
github.com/luxfi/consensus v1.22.32/go.mod h1:SIxXUq9h+WOguNbBkAuFPPFZyIFzc1fz0h2l7iTrOwk=
github.com/luxfi/constants v1.2.3 h1:ewo3Ch6AJDI3G42J0rlNAdZU1Fv96YEyOmp3E2K/pzE=
github.com/luxfi/constants v1.2.3/go.mod h1:r5WL5Z03I9iHo910pHykO+RuDCwe4jy0rBSueG1l1mo=
github.com/luxfi/crypto v1.17.23 h1:+G9XZVY9jz+wlTgpCDAMvrK/ZsYvMKgjaclRPHru1VU=
github.com/luxfi/crypto v1.17.23/go.mod h1:2+BSbTqb0Skji6kgh695125Z7f4PapsER8ImCQVmVBw=
github.com/luxfi/crypto v1.17.24 h1:644nJjyslF50RwtAwYhuT711IiLdVKsWX6tM9gwOOUE=
github.com/luxfi/crypto v1.17.24/go.mod h1:FDe5QXUY/JpSEfVkSmPBeOwhWRrCuuGeRuJ/UDq2w2I=
github.com/luxfi/database v1.2.12 h1:0TWcVlkotLcn9oFUXeHn5zBT24RxnPJLw9LIgapZIzc=
github.com/luxfi/database v1.2.12/go.mod h1:BlczmDTU4z76wPGlhtbzI4iWbEIiWDyfDOCGoq8Cmc0=
github.com/luxfi/geth v1.16.55 h1:4N9P8vT+NhUTQ/gzJUHk9GfpNxydY1f+NLKTeZP+qi8=
github.com/luxfi/geth v1.16.55/go.mod h1:/Rh2kzHGzK1HLd9wA6hnpqDwBev/vN+w6E756EY7Rac=
github.com/luxfi/geth v1.16.56 h1:R5d+k8lhTvsId0qJ7zAINTraQZH3KA3bk/jj+cRkUgo=
github.com/luxfi/geth v1.16.56/go.mod h1:/Rh2kzHGzK1HLd9wA6hnpqDwBev/vN+w6E756EY7Rac=
github.com/luxfi/go-bip39 v1.1.2 h1:p+wLMPGs6MLQh7q0YIsmy2EhHL7LHiELEGTJko6t/Jg=
github.com/luxfi/go-bip39 v1.1.2/go.mod h1:96de9VkR2kY/ASAnhMtvt3TSh+PZkAFAngNj0GjRGDo=
github.com/luxfi/ids v1.2.4 h1:e0OaeSI6xWjS9JnxxxfvCCs9HHDUrwDc3M9ctIhjcDI=
github.com/luxfi/ids v1.2.4/go.mod h1:HwvRJSNbuZS+u+0JqeHfPX2S13iFyLCnfGxjBCmt244=
github.com/luxfi/log v1.1.26 h1:ECnJ4wV7TF6WZ5gC6CD6ddZ4OhF+zhQij4bgVUhumDg=
github.com/luxfi/log v1.1.26/go.mod h1:ACN1FtMlJ/NRuWROYH2TtiFmJuUbSG+OxINlRzJM/38=
github.com/luxfi/math v1.0.2 h1:bgBGmVC7S/QxLCgAJZJdvjFHMNXygcApcKofIn7bWpU=
github.com/luxfi/math v1.0.2/go.mod h1:2Jk1/s0f0yN2q0Sx/cBExbFJz7IDEbjxlP3VUiaujms=
github.com/luxfi/math v1.1.0 h1:aSoLX7CytnDs5YvhVZ7tWgP0bYvKGU1/vWSuz46dX3M=
github.com/luxfi/math v1.1.0/go.mod h1:2Jk1/s0f0yN2q0Sx/cBExbFJz7IDEbjxlP3VUiaujms=
github.com/luxfi/metric v1.4.8 h1:mMbcJW+E1z6qQxn34+BE109HlvT6ciiDXuc10qEBJRs=
github.com/luxfi/metric v1.4.8/go.mod h1:AUQ7NSNz9WndAcr/SKnOkP7XSFFnBXOa+ihtJYfDaQY=
github.com/luxfi/mock v0.1.0 h1:IwElfNu+T9sXvzFX6tudPDx1vqPuACRSRdxpD5lxW+o=
github.com/luxfi/mock v0.1.0/go.mod h1:izF+9K0gGzFC9zERn6Po37v46eLdPB+EIsDjL3GLk+U=
github.com/luxfi/node v1.22.20 h1:TLqLiaOtUnEJrV8BF9LzAWPY6oqa+lRFe6LMs7dn5+o=
github.com/luxfi/node v1.22.20/go.mod h1:0VCHqIlFsXShMWGze0ilMiJQJ8pAD2kKYZQbLzC4dV0=
github.com/luxfi/p2p v1.4.7 h1:FI0M3sgkuTj4JLwlQsyAnhOin6KsSLKmL8jwSq95ZoI=
github.com/luxfi/p2p v1.4.7/go.mod h1:mx0oYqe0sm53fwatzbQfK0MHG/T3xC/pu7jsR7nCC5A=
github.com/luxfi/timer v1.0.1 h1:3lRf3j0L/fA3Ud0j67cm/BW2In1kz+jxd1HqgKyglYY=
@@ -260,14 +292,30 @@ github.com/luxfi/units v1.0.0 h1:2aNVB+WsP1XeDob71IsO0w3jJqP3FtZdYnFsmORkJZg=
github.com/luxfi/units v1.0.0/go.mod h1:tma28v4ed1tupdS0kpSeyO+u1wWK/g1NqODPbN1YzmA=
github.com/luxfi/upgrade v1.0.0 h1:mM6YXvU8VzoclA7IdtuE5bmnMuFquYxjKUUW84LJz54=
github.com/luxfi/upgrade v1.0.0/go.mod h1:DZF7dO4erhUEKf907B2e65k4/vGkCPkUngpvIEUS3eI=
github.com/luxfi/utils v1.0.0 h1:eibxyw/xkwnB9KRSmYvKLbdLTUElyfJpxLIBeTBKqlU=
github.com/luxfi/utils v1.0.0/go.mod h1:ABqhBdGNig0CaDcnNYldv1byS0BTNi5IKPbJSPF1p98=
github.com/luxfi/utils v1.1.0 h1:ti7HvjNwJd4ILDMERJtOAWE9mF8l+zqDVkgWnF7Agic=
github.com/luxfi/utils v1.1.0/go.mod h1:ABqhBdGNig0CaDcnNYldv1byS0BTNi5IKPbJSPF1p98=
github.com/luxfi/vm v1.0.0 h1:LkpbT7VavGNnu0AVn2Gcur9bWVglPyAFsCBP1QOty9s=
github.com/luxfi/vm v1.0.0/go.mod h1:dgHBuyQMEf1fgG38hsMpk/ZLlxsEAVg9H8EMkXOpWLg=
github.com/luxfi/vms v1.0.0 h1:b5amEPj1S0k4FDECX3nCS4lFS/VsrCLfyrzacKOqAIs=
github.com/luxfi/vms v1.0.0/go.mod h1:fttFuYQBTLXSsI4oyO2yYAO95py/ejH1Nf/tffHX6X0=
github.com/luxfi/warp v1.16.37 h1:2a348vN7x8rirUlMWtBrRmnKBUtRQHNwys0x55w2rtY=
github.com/luxfi/warp v1.16.37/go.mod h1:4Ysnu62H8hXSSTgQ0wafFLrrV4xlge3sbS6ho4An6MI=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@@ -295,11 +343,14 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY=
github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
@@ -313,6 +364,7 @@ github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1A
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -328,10 +380,15 @@ github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4=
github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
@@ -355,6 +412,8 @@ github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -379,6 +438,9 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -420,6 +482,8 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
@@ -440,7 +504,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@@ -463,18 +529,25 @@ golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -490,6 +563,7 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -499,6 +573,7 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
@@ -507,10 +582,13 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-27
View File
@@ -1,27 +0,0 @@
# Peer Package
The peer package handles networking for the VM.
## Network
The `Network` interface implements the networking portion of the required VM interface. The VM utilizes the `Network` interface to:
- Set an App Gossip handler for incoming VM gossip messages
- Set an App Request handler for incoming VM request messages
- Send App Requests to peers in the network and specify a response handler to be called upon receiving a response or failure notification
- Send App Gossip messages to the network
## Client
The client utilizes the `Network` interface to send requests to peers on the network and utilizes the `waitingHandler` to wait until a response or failure is received from the Luxd networking layer.
This allows the user of `Client` to treat it as if it were returning results from the network synchronously.
```go
result, err := client.Request(nodeID, request) // Blocks until receiving a response from the network
if err != nil {
return err
}
foo(result) // do something with the result
```
+23
View File
@@ -0,0 +1,23 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package network
import "errors"
var (
// ErrNoPeers is returned when there are no peers to send a request to
ErrNoPeers = errors.New("no peers available")
// ErrNoSender is returned when no sender is configured
ErrNoSender = errors.New("no sender configured")
// ErrNetworkClosed is returned when the network has been shut down
ErrNetworkClosed = errors.New("network closed")
// ErrRequestCancelled is returned when a request is cancelled
ErrRequestCancelled = errors.New("request cancelled")
// ErrRequestTimeout is returned when a request times out
ErrRequestTimeout = errors.New("request timeout")
)
+173 -573
View File
@@ -5,637 +5,237 @@ package network
import (
"context"
"errors"
"fmt"
"sync"
"time"
"golang.org/x/sync/semaphore"
"github.com/luxfi/geth/log"
luxlog "github.com/luxfi/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/luxfi/codec"
nodeCore "github.com/luxfi/consensus/engine/core"
validators "github.com/luxfi/consensus/validator"
"github.com/luxfi/consensus/version"
"github.com/luxfi/p2p"
"github.com/luxfi/utils"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/math/set"
"github.com/luxfi/metric"
"github.com/luxfi/p2p"
"github.com/luxfi/evm/network/stats"
"github.com/luxfi/evm/plugin/evm/message"
"github.com/luxfi/consensus/version"
)
// Minimum amount of time to handle a request
const (
minRequestHandlingDuration = 100 * time.Millisecond
maxValidatorSetStaleness = time.Minute
)
// No ID conversion needed - v1.16.15 uses external ids package
var (
errAcquiringSemaphore = errors.New("error acquiring semaphore")
errExpiredRequest = errors.New("expired request")
_ Network = (*network)(nil)
_ validators.Connector = (*network)(nil)
_ nodeCore.AppHandler = (*network)(nil)
)
// loggerWrapper wraps geth/log.Logger to implement luxfi/log.Logger interface
type loggerWrapper struct {
log.Logger
}
// EnabledLevel implements luxfi/log.Logger interface
func (l *loggerWrapper) EnabledLevel() interface{} {
return nil
}
// SetLevel implements luxfi/log.Logger interface
func (l *loggerWrapper) SetLevel(lvl interface{}) {
// No-op as geth logger handles this differently
}
// senderAdapter wraps nodeCore.AppSender to implement p2p.Sender interface
type senderAdapter struct {
appSender nodeCore.AppSender
}
// SendRequest implements p2p.Sender
func (s *senderAdapter) SendRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error {
return s.appSender.SendAppRequest(ctx, nodeIDs, requestID, request)
}
// SendResponse implements p2p.Sender
func (s *senderAdapter) SendResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error {
return s.appSender.SendAppResponse(ctx, nodeID, requestID, response)
}
// SendError implements p2p.Sender
func (s *senderAdapter) SendError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
return s.appSender.SendAppError(ctx, nodeID, requestID, errorCode, errorMessage)
}
// SendGossip implements p2p.Sender
func (s *senderAdapter) SendGossip(ctx context.Context, config p2p.SendConfig, msg []byte) error {
return s.appSender.SendAppGossip(ctx, config.NodeIDs, msg)
}
// SyncedNetworkClient defines ability to send request / response through the Network
// SyncedNetworkClient is the interface required by the state sync client
type SyncedNetworkClient interface {
// SendSyncedAppRequestAny synchronously sends request to an arbitrary peer with a
// node version greater than or equal to minVersion.
// Returns response bytes, the ID of the chosen peer, and ErrRequestFailed if
// the request should be retried.
SendSyncedAppRequestAny(ctx context.Context, minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error)
// SendSyncedAppRequest synchronously sends request to the selected nodeID
// Returns response bytes, and ErrRequestFailed if the request should be retried.
SendSyncedAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte) ([]byte, error)
// TrackBandwidth should be called for each valid request with the bandwidth
// (length of response divided by request time), and with 0 if the response is invalid.
Gossip(msg []byte) error
TrackBandwidth(nodeID ids.NodeID, bandwidth float64)
}
type Network interface {
validators.Connector
nodeCore.AppHandler
SyncedNetworkClient
// SendAppRequestAny sends request to an arbitrary peer with a
// node version greater than or equal to minVersion.
// Returns the ID of the chosen peer, and an error if the request could not
// be sent to a peer with the desired [minVersion].
SendAppRequestAny(ctx context.Context, minVersion *version.Application, message []byte, handler message.ResponseHandler) (ids.NodeID, error)
// SendAppRequest sends message to given nodeID, notifying handler when there's a response or timeout
SendAppRequest(ctx context.Context, nodeID ids.NodeID, message []byte, handler message.ResponseHandler) error
// Shutdown stops all peer channel listeners and marks the node to have stopped
// n.Start() can be called again but the peers will have to be reconnected
// by calling OnPeerConnected for each peer
Shutdown()
// SetRequestHandler sets the provided request handler as the request handler
SetRequestHandler(handler message.RequestHandler)
// Size returns the size of the network in number of connected peers
Size() uint32
// NewClient returns a client to send messages with for the given protocol
NewClient(protocol uint64, options ...p2p.ClientOption) *p2p.Client
// AddHandler registers a server handler for an application protocol
AddHandler(protocol uint64, handler p2p.Handler) error
P2PValidators() interface{}
// AppSender is the interface for sending application messages
type AppSender interface {
p2p.Sender
}
// network is an implementation of Network that processes message requests for
// each peer in linear fashion
type network struct {
lock sync.RWMutex // lock for mutating state of this Network struct
self ids.NodeID // NodeID of this node
requestIDGen uint32 // requestID counter used to track outbound requests
outstandingRequestHandlers map[uint32]message.ResponseHandler // maps luxd requestID => message.ResponseHandler
activeAppRequests *semaphore.Weighted // controls maximum number of active outbound requests
sdkNetwork *p2p.Network // SDK network (luxd p2p) for sending messages to peers
appSender nodeCore.AppSender // luxd AppSender for sending messages
codec codec.Manager // Codec used for parsing messages
appRequestHandler message.RequestHandler // maps request type => handler
peers *peerTracker // tracking of peers & bandwidth
appStats stats.RequestHandlerStats // Provide request handler metrics
// Network handles peer-to-peer networking for the EVM
// It embeds p2p.Network to provide the base functionality
type Network struct {
*p2p.Network
// Set to true when Shutdown is called, after which all operations on this
// struct are no-ops.
//
// Invariant: Even though `closed` is an atomic, `lock` is required to be
// held when sending requests to guarantee that the network isn't closed
// during these calls. This is because closing the network cancels all
// outstanding requests, which means we must guarantee never to register a
// request that will never be fulfilled or cancelled.
closed utils.Atomic[bool]
sender AppSender
codec codec.Manager
maxRequests int64
metrics metric.Registerer
log log.Logger
p2pValidators interface{}
// Request tracking for sync
requestsLock sync.Mutex
pendingRequests map[uint32]chan []byte
nextRequestID uint32
// Request handler - stores the message.RequestHandler for sync operations
// We use interface{} here to avoid import cycles with plugin/evm/message
requestHandler interface{}
// Shutdown
closed bool
closeLock sync.Mutex
}
// NewNetwork creates a new Network instance
func NewNetwork(
ctx context.Context,
appSender nodeCore.AppSender,
sender AppSender,
codec codec.Manager,
maxActiveAppRequests int64,
registerer prometheus.Registerer,
) (Network, error) {
// Initialize P2P network with a no-op logger for compatibility
// The SDK network is used for routing SDK handlers (odd requestIDs)
var p2pNetwork *p2p.Network
if appSender != nil {
// Use luxfi/log.NewNoOpLogger() which implements the required Logger interface
// Wrap appSender to implement p2p.Sender interface
sender := &senderAdapter{appSender: appSender}
var err error
p2pNetwork, err = p2p.NewNetwork(luxlog.NewNoOpLogger(), sender, registerer, "evm")
maxOutboundActiveRequests int64,
metrics metric.Registerer,
) (*Network, error) {
logger := log.New()
var p2pNet *p2p.Network
var err error
if sender != nil {
p2pNet, err = p2p.NewNetwork(logger, sender, metrics, "evm")
if err != nil {
return nil, fmt.Errorf("failed to initialize p2p network: %w", err)
return nil, err
}
}
// For now, use empty node ID since GetNodeID doesn't exist in node's consensus package
// This would normally come from the VM's context
nodeID := ids.EmptyNodeID
return &network{
appSender: appSender,
codec: codec,
self: nodeID,
outstandingRequestHandlers: make(map[uint32]message.ResponseHandler),
activeAppRequests: semaphore.NewWeighted(maxActiveAppRequests),
sdkNetwork: p2pNetwork,
appRequestHandler: message.NoopRequestHandler{},
peers: NewPeerTracker(),
appStats: stats.NewRequestHandlerStats(),
p2pValidators: nil, // p2p validators initialization removed due to interface incompatibility
return &Network{
Network: p2pNet,
sender: sender,
codec: codec,
maxRequests: maxOutboundActiveRequests,
metrics: metrics,
log: logger,
pendingRequests: make(map[uint32]chan []byte),
}, nil
}
// SendAppRequestAny synchronously sends request to an arbitrary peer with a
// node version greater than or equal to minVersion. If minVersion is nil,
// the request will be sent to any peer regardless of their version.
// Returns the ID of the chosen peer, and an error if the request could not
// be sent to a peer with the desired [minVersion].
func (n *network) SendAppRequestAny(ctx context.Context, minVersion *version.Application, request []byte, handler message.ResponseHandler) (ids.NodeID, error) {
// If the context was cancelled, we can skip sending this request.
if err := ctx.Err(); err != nil {
return ids.EmptyNodeID, err
}
// Shutdown stops the network
func (n *Network) Shutdown() {
n.closeLock.Lock()
defer n.closeLock.Unlock()
n.closed = true
// Take a slot from total [activeAppRequests] and block until a slot becomes available.
if err := n.activeAppRequests.Acquire(ctx, 1); err != nil {
return ids.EmptyNodeID, errAcquiringSemaphore
// Cancel all pending requests
n.requestsLock.Lock()
for _, ch := range n.pendingRequests {
close(ch)
}
n.lock.Lock()
defer n.lock.Unlock()
// Convert consensus version to node version for peer tracker
var nodeMinVersion *version.Application
if minVersion != nil {
nodeMinVersion = &version.Application{
Name: minVersion.Name,
Major: minVersion.Major,
Minor: minVersion.Minor,
Patch: minVersion.Patch,
}
}
if nodeID, ok := n.peers.GetAnyPeer(nodeMinVersion); ok {
log.Info("SendAppRequestAny: found peer", "nodeID", nodeID, "minVersion", nodeMinVersion)
return nodeID, n.sendAppRequest(ctx, nodeID, request, handler)
}
log.Info("SendAppRequestAny: no peers found", "minVersion", nodeMinVersion, "peerCount", n.peers.Size())
n.activeAppRequests.Release(1)
return ids.EmptyNodeID, fmt.Errorf("no peers found matching version %s out of %d peers", minVersion, n.peers.Size())
n.pendingRequests = make(map[uint32]chan []byte)
n.requestsLock.Unlock()
}
// SendAppRequest sends request message bytes to specified nodeID, notifying the responseHandler on response or failure
func (n *network) SendAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error {
if nodeID == ids.EmptyNodeID {
return fmt.Errorf("cannot send request to empty nodeID, nodeID=%s, requestLen=%d", nodeID, len(request))
}
// If the context was cancelled, we can skip sending this request.
if err := ctx.Err(); err != nil {
return err
}
// Take a slot from total [activeAppRequests] and block until a slot becomes available.
if err := n.activeAppRequests.Acquire(ctx, 1); err != nil {
return errAcquiringSemaphore
}
n.lock.Lock()
defer n.lock.Unlock()
return n.sendAppRequest(ctx, nodeID, request, responseHandler)
}
// sendAppRequest sends request message bytes to specified nodeID and adds [responseHandler] to [outstandingRequestHandlers]
// so that it can be invoked when the network receives either a response or failure message.
// Assumes [nodeID] is never [self] since we guarantee [self] will not be added to the [peers] map.
// Releases active requests semaphore if there was an error in sending the request
// Returns an error if [appSender] is unable to make the request.
// Assumes write lock is held
func (n *network) sendAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error {
if n.closed.Get() {
n.activeAppRequests.Release(1)
return nil
}
// If the context was cancelled, we can skip sending this request.
if err := ctx.Err(); err != nil {
n.activeAppRequests.Release(1)
return err
}
log.Debug("sending request to peer", "nodeID", nodeID, "requestLen", len(request))
n.peers.TrackPeer(nodeID)
requestID := n.nextRequestID()
n.outstandingRequestHandlers[requestID] = responseHandler
// Send directly to single nodeID
// Send app request to [nodeID].
// On failure, release the slot from [activeAppRequests] and delete request
// from [outstandingRequestHandlers]
//
// Cancellation is removed from this context to avoid erroring unexpectedly.
// SendAppRequest should be non-blocking and any error other than context
// cancellation is unexpected.
//
// This guarantees that the network should never receive an unexpected
// AppResponse.
ctxWithoutCancel := context.WithoutCancel(ctx)
// Create set of node IDs for appSender
nodeIDs := set.Of(nodeID)
if err := n.appSender.SendAppRequest(ctxWithoutCancel, nodeIDs, requestID, request); err != nil {
log.Error(
"request to peer failed",
"nodeID", nodeID,
"requestID", requestID,
"requestLen", len(request),
"error", err,
)
n.activeAppRequests.Release(1)
delete(n.outstandingRequestHandlers, requestID)
return err
}
log.Debug("sent request message to peer", "nodeID", nodeID, "requestID", requestID)
// P2PValidators returns the p2p validators if available
func (n *Network) P2PValidators() *p2p.Validators {
// Return nil - validators are set up separately
return nil
}
// AppRequest is called by luxd -> VM when there is an incoming AppRequest from a peer
// error returned by this function is expected to be treated as fatal by the engine
// returns error if the requestHandler returns an error
// sends a response back to the sender if length of response returned by the handler is >0
// expects the deadline to not have been passed
func (n *network) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, deadline time.Time, request []byte) error {
if n.closed.Get() {
log.Info("AppRequest: network is closed, ignoring", "nodeID", nodeID, "requestID", requestID)
return nil
}
log.Info("AppRequest: received from node", "nodeID", nodeID, "requestID", requestID, "requestLen", len(request), "isNetworkRequest", IsNetworkRequest(requestID))
if !IsNetworkRequest(requestID) {
log.Info("AppRequest: forwarding to SDK network", "nodeID", nodeID, "requestID", requestID, "requestLen", len(request))
if n.sdkNetwork != nil {
_, err := n.sdkNetwork.Request(ctx, nodeID, requestID, deadline, request)
if err != nil {
return fmt.Errorf("SDK network request failed: %s", err.Message)
}
return nil
}
return nil
}
bufferedDeadline, err := calculateTimeUntilDeadline(deadline, n.appStats)
if err != nil {
log.Debug("deadline to process AppRequest has expired, skipping", "nodeID", nodeID, "requestID", requestID, "err", err)
return nil
}
var req message.Request
if _, err := n.codec.Unmarshal(request, &req); err != nil {
log.Debug("failed to unmarshal AppRequest", "nodeID", nodeID, "requestID", requestID, "err", err)
return nil
}
log.Debug("processing incoming request", "nodeID", nodeID, "requestID", requestID, "req", req)
// We make a new context here because we don't want to cancel the context
// passed into n.AppSender.SendAppResponse below
handleCtx, cancel := context.WithDeadline(context.Background(), bufferedDeadline)
defer cancel()
responseBytes, err := req.Handle(handleCtx, nodeID, requestID, n.appRequestHandler)
switch {
case err != nil && err != context.DeadlineExceeded:
return err // Return a fatal error
case responseBytes != nil:
return n.appSender.SendAppResponse(ctx, nodeID, requestID, responseBytes) // Propagate fatal error
default:
return nil
}
// SetRequestHandler sets the handler for incoming requests
// Accepts any type to avoid import cycles with plugin/evm/message
func (n *Network) SetRequestHandler(handler interface{}) {
n.requestHandler = handler
}
// AppResponse is invoked when there is a response received from a peer regarding a request
// Error returned by this function is expected to be treated as fatal by the engine
// If [requestID] is not known, this function will emit a log and return a nil error.
// If the response handler returns an error it is propagated as a fatal error.
func (n *network) AppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error {
log.Debug("received AppResponse from peer", "nodeID", nodeID, "requestID", requestID)
handler, exists := n.markRequestFulfilled(requestID)
if !exists {
log.Debug("forwarding AppResponse to SDK network", "nodeID", nodeID, "requestID", requestID, "responseLen", len(response))
if n.sdkNetwork != nil {
return n.sdkNetwork.Response(ctx, nodeID, requestID, response)
}
// When there's no SDK network and request not found, return ErrUnrequestedResponse
return p2p.ErrUnrequestedResponse
// Size returns the number of connected peers
func (n *Network) Size() int {
if n.Network == nil || n.Network.Peers == nil {
return 0
}
// We must release the slot
n.activeAppRequests.Release(1)
return handler.OnResponse(response)
return len(n.Network.Peers.Sample(1000)) // Approximate count
}
// AppRequestFailed can be called by the luxd -> VM in following cases:
// - node is benched
// - failed to send message to [nodeID] due to a network issue
// - request times out before a response is provided
// error returned by this function is expected to be treated as fatal by the engine
// returns error only when the response handler returns an error
func (n *network) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *nodeCore.AppError) error {
log.Debug("received AppRequestFailed from peer", "nodeID", nodeID, "requestID", requestID)
handler, exists := n.markRequestFulfilled(requestID)
if !exists {
log.Debug("forwarding AppRequestFailed to SDK network", "nodeID", nodeID, "requestID", requestID)
// AppError conversion handled for sdkNetwork compatibility
// For now, sdkNetwork is nil so this won't be called
if n.sdkNetwork != nil {
// Would need to convert appErr to node's AppError type here
return nil
}
return nil
// SendSyncedAppRequestAny sends a request to any available peer
func (n *Network) SendSyncedAppRequestAny(ctx context.Context, minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) {
if n.Network == nil || n.Network.Peers == nil {
return nil, ids.EmptyNodeID, ErrNoPeers
}
// We must release the slot
n.activeAppRequests.Release(1)
return handler.OnFailure()
}
// calculateTimeUntilDeadline calculates the time until deadline and drops it if we missed he deadline to response.
// This function updates metrics for app requests.
// This is called by [AppRequest].
func calculateTimeUntilDeadline(deadline time.Time, stats stats.RequestHandlerStats) (time.Time, error) {
// calculate how much time is left until the deadline
timeTillDeadline := time.Until(deadline)
stats.UpdateTimeUntilDeadline(timeTillDeadline)
// bufferedDeadline is half the time till actual deadline so that the message has a reasonable chance
// of completing its processing and sending the response to the peer.
bufferedDeadline := time.Now().Add(timeTillDeadline / 2)
// check if we have enough time to handle this request
if time.Until(bufferedDeadline) < minRequestHandlingDuration {
// Drop the request if we already missed the deadline to respond.
stats.IncDeadlineDroppedRequest()
return time.Time{}, errExpiredRequest
peers := n.Network.Peers.Sample(1)
if len(peers) == 0 {
return nil, ids.EmptyNodeID, ErrNoPeers
}
return bufferedDeadline, nil
}
// markRequestFulfilled fetches the handler for [requestID] and marks the request with [requestID] as having been fulfilled.
// This is called by either [AppResponse] or [AppRequestFailed].
// Assumes that the write lock is not held.
func (n *network) markRequestFulfilled(requestID uint32) (message.ResponseHandler, bool) {
n.lock.Lock()
defer n.lock.Unlock()
handler, exists := n.outstandingRequestHandlers[requestID]
if !exists {
return nil, false
}
// mark message as processed
delete(n.outstandingRequestHandlers, requestID)
return handler, true
}
// AppGossip is called by luxd -> VM when there is an incoming AppGossip
// from a peer. An error returned by this function is treated as fatal by the
// engine.
func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) error {
if n.sdkNetwork != nil {
// Forward to SDK network which will route to the appropriate handler
return n.sdkNetwork.Gossip(ctx, nodeID, gossipBytes)
}
return nil
}
// Connected adds the given nodeID to the peer list so that it can receive messages
func (n *network) Connected(ctx context.Context, nodeID ids.NodeID, nodeVersion *version.Application) error {
log.Info("network.Connected: adding new peer", "nodeID", nodeID, "nodeVersion", nodeVersion, "self", n.self)
n.lock.Lock()
defer n.lock.Unlock()
if n.closed.Get() {
log.Info("network.Connected: network is closed, ignoring", "nodeID", nodeID)
return nil
}
if nodeID != n.self {
// The legacy peer tracker doesn't expect to be connected to itself.
// Convert consensus version to node version
if nodeVersion != nil {
// Create a node version from the consensus version
nv := &version.Application{
Name: nodeVersion.Name,
Major: nodeVersion.Major,
Minor: nodeVersion.Minor,
Patch: nodeVersion.Patch,
}
log.Info("network.Connected: calling peers.Connected", "nodeID", nodeID, "nv", nv)
n.peers.Connected(nodeID, nv)
log.Info("network.Connected: peer added, peerCount", "count", n.peers.Size())
} else {
log.Info("network.Connected: nodeVersion is nil, NOT adding peer", "nodeID", nodeID)
}
} else {
log.Info("network.Connected: nodeID == self, NOT adding peer", "nodeID", nodeID)
}
// Version conversion handled for sdkNetwork compatibility
// For now, sdkNetwork is nil so this won't be called
if n.sdkNetwork != nil {
// Would need to convert nodeVersion to node's version.Application type here
return nil
}
return nil
}
// Disconnected removes given [nodeID] from the peer list
func (n *network) Disconnected(ctx context.Context, nodeID ids.NodeID) error {
log.Debug("disconnecting peer", "nodeID", nodeID)
n.lock.Lock()
defer n.lock.Unlock()
if n.closed.Get() {
return nil
}
if nodeID != n.self {
// The legacy peer tracker doesn't expect to be connected to itself.
n.peers.Disconnected(nodeID)
}
if n.sdkNetwork != nil {
// TODO: Convert nodeID types when sdkNetwork is implemented
return nil
}
return nil
}
// Shutdown disconnects all peers
func (n *network) Shutdown() {
n.lock.Lock()
defer n.lock.Unlock()
// clean up any pending requests
for requestID, handler := range n.outstandingRequestHandlers {
_ = handler.OnFailure() // make sure all waiting threads are unblocked
delete(n.outstandingRequestHandlers, requestID)
}
n.peers = NewPeerTracker() // reset peers
n.closed.Set(true) // mark network as closed
}
func (n *network) SetRequestHandler(handler message.RequestHandler) {
n.lock.Lock()
defer n.lock.Unlock()
n.appRequestHandler = handler
}
func (n *network) Size() uint32 {
n.lock.RLock()
defer n.lock.RUnlock()
return uint32(n.peers.Size())
}
func (n *network) TrackBandwidth(nodeID ids.NodeID, bandwidth float64) {
n.lock.Lock()
defer n.lock.Unlock()
n.peers.TrackBandwidth(nodeID, bandwidth)
}
// SendSyncedAppRequestAny synchronously sends request to an arbitrary peer with a
// node version greater than or equal to minVersion.
// Returns response bytes, the ID of the chosen peer, and ErrRequestFailed if
// the request should be retried.
func (n *network) SendSyncedAppRequestAny(ctx context.Context, minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) {
waitingHandler := newWaitingResponseHandler()
nodeID, err := n.SendAppRequestAny(ctx, minVersion, request, waitingHandler)
if err != nil {
return nil, nodeID, err
}
response, err := waitingHandler.WaitForResult(ctx)
// Try the first available peer
nodeID := peers[0]
response, err := n.SendSyncedAppRequest(ctx, nodeID, request)
return response, nodeID, err
}
// SendSyncedAppRequest synchronously sends request to the specified nodeID
// Returns response bytes and ErrRequestFailed if the request should be retried.
func (n *network) SendSyncedAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte) ([]byte, error) {
waitingHandler := newWaitingResponseHandler()
if err := n.SendAppRequest(ctx, nodeID, request, waitingHandler); err != nil {
// SendSyncedAppRequest sends a request to a specific peer
func (n *Network) SendSyncedAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte) ([]byte, error) {
if n.sender == nil {
return nil, ErrNoSender
}
n.closeLock.Lock()
if n.closed {
n.closeLock.Unlock()
return nil, ErrNetworkClosed
}
n.closeLock.Unlock()
// Create a channel to receive the response
responseChan := make(chan []byte, 1)
requestID := n.allocateRequestID(responseChan)
defer n.freeRequestID(requestID)
// Send the request
nodeIDs := set.NewSet[ids.NodeID](1)
nodeIDs.Add(nodeID)
if err := n.sender.SendRequest(ctx, nodeIDs, requestID, request); err != nil {
return nil, err
}
return waitingHandler.WaitForResult(ctx)
// Wait for response with timeout
select {
case <-ctx.Done():
return nil, ctx.Err()
case response, ok := <-responseChan:
if !ok {
return nil, ErrRequestCancelled
}
return response, nil
}
}
func (n *network) NewClient(protocol uint64, options ...p2p.ClientOption) *p2p.Client {
if n.sdkNetwork != nil {
return n.sdkNetwork.NewClient(protocol, options...)
// Gossip broadcasts a message to all peers
func (n *Network) Gossip(msg []byte) error {
if n.sender == nil {
return ErrNoSender
}
if n.Network == nil || n.Network.Peers == nil {
return nil // No peers to gossip to
}
peers := n.Network.Peers.Sample(100) // Sample up to 100 peers
if len(peers) == 0 {
return nil // No peers to gossip to
}
nodeIDs := set.NewSet[ids.NodeID](len(peers))
for _, peer := range peers {
nodeIDs.Add(peer)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
return n.sender.SendGossip(ctx, p2p.SendConfig{NodeIDs: nodeIDs}, msg)
}
// TrackBandwidth records bandwidth usage for a peer
func (n *Network) TrackBandwidth(nodeID ids.NodeID, bandwidth float64) {
// TODO: Implement bandwidth tracking metrics
}
// AppResponse handles an incoming response
func (n *Network) AppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error {
n.requestsLock.Lock()
ch, ok := n.pendingRequests[requestID]
n.requestsLock.Unlock()
if ok {
select {
case ch <- response:
default:
}
}
// Return nil when sdkNetwork is not initialized
return nil
}
func (n *network) AddHandler(protocol uint64, handler p2p.Handler) error {
if n.sdkNetwork != nil {
return n.sdkNetwork.AddHandler(protocol, handler)
}
// Return nil when sdkNetwork is not initialized
// The handler won't be used since sdkNetwork is nil
return nil
// allocateRequestID allocates a new request ID and registers the response channel
func (n *Network) allocateRequestID(responseChan chan []byte) uint32 {
n.requestsLock.Lock()
defer n.requestsLock.Unlock()
requestID := n.nextRequestID
n.nextRequestID++
n.pendingRequests[requestID] = responseChan
return requestID
}
// P2PValidators returns the p2p validators
func (n *network) P2PValidators() interface{} {
return n.p2pValidators
}
// invariant: peer/network must use explicitly even request ids.
// for this reason, [n.requestID] is initialized as zero and incremented by 2.
// This is for backwards-compatibility while the SDK router exists with the
// legacy coreth handlers to avoid a (very) narrow edge case where request ids
// can overlap, resulting in a dropped timeout.
func (n *network) nextRequestID() uint32 {
next := n.requestIDGen
n.requestIDGen += 2
return next
}
// IsNetworkRequest checks if the given requestID is a request for this network handler (even-numbered requestIDs)
// SDK requests are odd-numbered requestIDs
// (see invariant: https://github.com/luxfi/node/blob/v1.13.0/network/p2p/router.go#L83)
func IsNetworkRequest(requestID uint32) bool {
return requestID%2 == 0
// freeRequestID frees a request ID
func (n *Network) freeRequestID(requestID uint32) {
n.requestsLock.Lock()
defer n.requestsLock.Unlock()
delete(n.pendingRequests, requestID)
}
-825
View File
@@ -1,825 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package network
import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/luxfi/p2p"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
nodeCore "github.com/luxfi/consensus/engine/core"
"github.com/luxfi/evm/network/peertest"
"github.com/luxfi/evm/plugin/evm/message"
"github.com/luxfi/codec"
"github.com/luxfi/codec/linearcodec"
consensusVersion "github.com/luxfi/consensus/version"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
"github.com/luxfi/node/version"
)
var (
// Use consensus version directly to avoid conversion issues
defaultPeerVersion = &consensusVersion.Application{
Major: 1,
Minor: 0,
Patch: 0,
}
// For compatibility, keep defaultConsensusVersion as an alias
defaultConsensusVersion = defaultPeerVersion
_ message.Request = (*HelloRequest)(nil)
_ = (*HelloResponse)(nil)
_ = (*GreetingRequest)(nil)
_ = (*GreetingResponse)(nil)
_ = (*TestMessage)(nil)
_ message.RequestHandler = (*HelloGreetingRequestHandler)(nil)
_ message.RequestHandler = (*testRequestHandler)(nil)
// common.AppSender is not available in current version
// _ common.AppSender = testAppSender{}
_ p2p.Handler = (*testSDKHandler)(nil)
)
func TestNetworkDoesNotConnectToItself(t *testing.T) {
// Test that the network doesn't count itself when its own nodeID connects
ctx := context.Background()
n, err := NewNetwork(ctx, nil, nil, 1, prometheus.NewRegistry())
require.NoError(t, err)
// The network's self is EmptyNodeID (set in NewNetwork)
// When EmptyNodeID connects, it should be ignored (size stays 0)
consVersion := &consensusVersion.Application{
Name: defaultPeerVersion.Name,
Major: defaultPeerVersion.Major,
Minor: defaultPeerVersion.Minor,
Patch: defaultPeerVersion.Patch,
}
assert.NoError(t, n.Connected(context.Background(), ids.EmptyNodeID, consVersion))
assert.EqualValues(t, uint32(0), n.Size())
// But when a different nodeID connects, it should be counted
nodeID := ids.GenerateTestNodeID()
assert.NoError(t, n.Connected(context.Background(), nodeID, consVersion))
assert.EqualValues(t, uint32(1), n.Size())
}
func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) {
// Enabled - testing request routing to any peer with version matching
callNum := uint32(0)
senderWg := &sync.WaitGroup{}
var net Network
sender := testAppSender{
sendAppRequestFn: func(_ context.Context, nodeID ids.NodeID, requestID uint32, requestBytes []byte) error {
// nodeID is already provided as a parameter
senderWg.Add(1)
go func() {
defer senderWg.Done()
if err := net.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(5*time.Second), requestBytes); err != nil {
panic(err)
}
}()
return nil
},
sendAppResponseFn: func(nodeID ids.NodeID, requestID uint32, responseBytes []byte) error {
senderWg.Add(1)
go func() {
defer senderWg.Done()
if err := net.AppResponse(context.Background(), nodeID, requestID, responseBytes); err != nil {
panic(err)
}
atomic.AddUint32(&callNum, 1)
}()
return nil
},
}
codecManager := buildCodec(t, HelloRequest{}, HelloResponse{})
ctx := context.Background()
net, err := NewNetwork(ctx, sender, codecManager, 16, prometheus.NewRegistry())
require.NoError(t, err)
net.SetRequestHandler(&HelloGreetingRequestHandler{codec: codecManager})
nodeID := ids.GenerateTestNodeID()
assert.NoError(t, net.Connected(context.Background(), nodeID, defaultConsensusVersion))
requestMessage := HelloRequest{Message: "this is a request"}
defer net.Shutdown()
assert.NoError(t, net.Connected(context.Background(), nodeID, defaultConsensusVersion))
totalRequests := 5000
numCallsPerRequest := 1 // on sending response
totalCalls := totalRequests * numCallsPerRequest
requestWg := &sync.WaitGroup{}
requestWg.Add(totalCalls)
for i := 0; i < totalCalls; i++ {
go func(wg *sync.WaitGroup) {
defer wg.Done()
requestBytes, err := message.RequestToBytes(codecManager, requestMessage)
assert.NoError(t, err)
responseBytes, _, err := net.SendSyncedAppRequestAny(context.Background(), defaultPeerVersion, requestBytes)
assert.NoError(t, err)
assert.NotNil(t, responseBytes)
var response TestMessage
if _, err = codecManager.Unmarshal(responseBytes, &response); err != nil {
panic(fmt.Errorf("unexpected error during unmarshal: %w", err))
}
assert.Equal(t, "Hi", response.Message)
}(requestWg)
}
requestWg.Wait()
senderWg.Wait()
assert.Equal(t, totalCalls, int(atomic.LoadUint32(&callNum)))
}
func TestAppRequestOnCtxCancellation(t *testing.T) {
// Enabled - testing request behavior when context is cancelled
codecManager := buildCodec(t, HelloRequest{}, HelloResponse{})
sender := testAppSender{
sendAppRequestFn: func(_ context.Context, nodeID ids.NodeID, requestID uint32, requestBytes []byte) error {
return nil
},
sendAppResponseFn: func(nodeID ids.NodeID, requestID uint32, responseBytes []byte) error {
return nil
},
}
consensusCtx := context.Background()
net, err := NewNetwork(consensusCtx, sender, codecManager, 1, prometheus.NewRegistry())
require.NoError(t, err)
handler := &HelloGreetingRequestHandler{codec: codecManager}
net.SetRequestHandler(handler)
requestMessage := HelloRequest{Message: "this is a request"}
requestBytes, err := message.RequestToBytes(codecManager, requestMessage)
assert.NoError(t, err)
nodeID := ids.GenerateTestNodeID()
ctx, cancel := context.WithCancel(context.Background())
// cancel context prior to sending
cancel()
err = net.SendAppRequest(ctx, nodeID, requestBytes, nil)
assert.ErrorIs(t, err, context.Canceled)
}
func TestRequestRequestsRoutingAndResponse(t *testing.T) {
// Enabled - testing request/response routing to specific peers
callNum := uint32(0)
senderWg := &sync.WaitGroup{}
var net Network
var lock sync.Mutex
contactedNodes := make(map[ids.NodeID]struct{})
sender := testAppSender{
sendAppRequestFn: func(_ context.Context, nodeID ids.NodeID, requestID uint32, requestBytes []byte) error {
// nodeID is already provided as a parameter
lock.Lock()
contactedNodes[nodeID] = struct{}{}
lock.Unlock()
senderWg.Add(1)
go func() {
defer senderWg.Done()
if err := net.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(5*time.Second), requestBytes); err != nil {
panic(err)
}
}()
return nil
},
sendAppResponseFn: func(nodeID ids.NodeID, requestID uint32, responseBytes []byte) error {
senderWg.Add(1)
go func() {
defer senderWg.Done()
if err := net.AppResponse(context.Background(), nodeID, requestID, responseBytes); err != nil {
panic(err)
}
atomic.AddUint32(&callNum, 1)
}()
return nil
},
}
codecManager := buildCodec(t, HelloRequest{}, HelloResponse{})
ctx := context.Background()
net, err := NewNetwork(ctx, sender, codecManager, 16, prometheus.NewRegistry())
require.NoError(t, err)
net.SetRequestHandler(&HelloGreetingRequestHandler{codec: codecManager})
nodes := []ids.NodeID{
ids.GenerateTestNodeID(),
ids.GenerateTestNodeID(),
ids.GenerateTestNodeID(),
ids.GenerateTestNodeID(),
ids.GenerateTestNodeID(),
}
for _, nodeID := range nodes {
assert.NoError(t, net.Connected(context.Background(), nodeID, defaultConsensusVersion))
}
requestMessage := HelloRequest{Message: "this is a request"}
defer net.Shutdown()
totalRequests := 5000
numCallsPerRequest := 1 // on sending response
totalCalls := totalRequests * numCallsPerRequest
requestWg := &sync.WaitGroup{}
requestWg.Add(totalCalls)
nodeIdx := 0
for i := 0; i < totalCalls; i++ {
nodeIdx = (nodeIdx + 1) % (len(nodes))
nodeID := nodes[nodeIdx]
go func(wg *sync.WaitGroup, nodeID ids.NodeID) {
defer wg.Done()
requestBytes, err := message.RequestToBytes(codecManager, requestMessage)
assert.NoError(t, err)
responseBytes, err := net.SendSyncedAppRequest(context.Background(), nodeID, requestBytes)
assert.NoError(t, err)
assert.NotNil(t, responseBytes)
var response TestMessage
if _, err = codecManager.Unmarshal(responseBytes, &response); err != nil {
panic(fmt.Errorf("unexpected error during unmarshal: %w", err))
}
assert.Equal(t, "Hi", response.Message)
}(requestWg, nodeID)
}
requestWg.Wait()
senderWg.Wait()
assert.Equal(t, totalCalls, int(atomic.LoadUint32(&callNum)))
for _, nodeID := range nodes {
if _, exists := contactedNodes[nodeID]; !exists {
t.Fatalf("expected nodeID %s to be contacted but was not", nodeID)
}
}
// ensure empty nodeID is not allowed
assert.ErrorContains(t,
net.SendAppRequest(context.Background(), ids.EmptyNodeID, []byte("hello there"), nil),
"cannot send request to empty nodeID",
)
}
func TestAppRequestOnShutdown(t *testing.T) {
// Enabled - testing request handling during network shutdown
var (
net Network
wg sync.WaitGroup
called bool
)
sender := testAppSender{
sendAppRequestFn: func(_ context.Context, nodeID ids.NodeID, requestID uint32, requestBytes []byte) error {
wg.Add(1)
go func() {
called = true
// shutdown the network here to ensure any outstanding requests are handled as failed
net.Shutdown()
wg.Done()
}() // this is on a goroutine to avoid a deadlock since calling Shutdown takes the lock.
return nil
},
}
codecManager := buildCodec(t, HelloRequest{}, HelloResponse{})
ctx := context.Background()
net, err := NewNetwork(ctx, sender, codecManager, 1, prometheus.NewRegistry())
require.NoError(t, err)
nodeID := ids.GenerateTestNodeID()
require.NoError(t, net.Connected(context.Background(), nodeID, defaultConsensusVersion))
requestMessage := HelloRequest{Message: "this is a request"}
require.NoError(t, net.Connected(context.Background(), nodeID, defaultConsensusVersion))
wg.Add(1)
go func() {
defer wg.Done()
requestBytes, err := message.RequestToBytes(codecManager, requestMessage)
require.NoError(t, err)
responseBytes, _, err := net.SendSyncedAppRequestAny(context.Background(), defaultPeerVersion, requestBytes)
require.Error(t, err, errRequestFailed)
require.Nil(t, responseBytes)
}()
wg.Wait()
require.True(t, called)
}
func TestSyncedAppRequestAnyOnCtxCancellation(t *testing.T) {
// Enabled - testing synced request with context cancellation
codecManager := buildCodec(t, HelloRequest{}, HelloResponse{})
type reqInfo struct {
nodeID ids.NodeID
requestID uint32
}
sentAppRequest := make(chan reqInfo, 1)
sender := testAppSender{
sendAppRequestFn: func(ctx context.Context, nodeID ids.NodeID, requestID uint32, requestBytes []byte) error {
if err := ctx.Err(); err != nil {
return err
}
// nodeID is already provided as a parameter
sentAppRequest <- reqInfo{
nodeID: nodeID,
requestID: requestID,
}
return nil
},
sendAppResponseFn: func(nodeID ids.NodeID, requestID uint32, responseBytes []byte) error {
return nil
},
}
consensusCtx := context.Background()
net, err := NewNetwork(consensusCtx, sender, codecManager, 1, prometheus.NewRegistry())
require.NoError(t, err)
net.SetRequestHandler(&HelloGreetingRequestHandler{codec: codecManager})
assert.NoError(t,
net.Connected(
context.Background(),
ids.GenerateTestNodeID(),
defaultConsensusVersion,
),
)
requestMessage := HelloRequest{Message: "this is a request"}
requestBytes, err := message.RequestToBytes(codecManager, requestMessage)
assert.NoError(t, err)
// cancel context prior to sending
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, _, err = net.SendSyncedAppRequestAny(ctx, defaultPeerVersion, requestBytes)
assert.ErrorIs(t, err, context.Canceled)
// Assert we didn't send anything
select {
case <-sentAppRequest:
assert.FailNow(t, "should not have sent request")
default:
}
// Cancel context after sending
assert.Empty(t, net.(*network).outstandingRequestHandlers) // no outstanding requests
ctx, cancel = context.WithCancel(context.Background())
doneChan := make(chan struct{})
go func() {
_, _, err = net.SendSyncedAppRequestAny(ctx, defaultPeerVersion, requestBytes)
assert.ErrorIs(t, err, context.Canceled)
close(doneChan)
}()
// Wait until we've "sent" the app request over the network
// before cancelling context.
sentAppRequestInfo := <-sentAppRequest
assert.Len(t, net.(*network).outstandingRequestHandlers, 1)
cancel()
<-doneChan
// Should still be able to process a response after cancelling.
assert.Len(t, net.(*network).outstandingRequestHandlers, 1) // context cancellation SendAppRequestAny failure doesn't clear
assert.NoError(t, net.AppResponse(context.Background(), sentAppRequestInfo.nodeID, sentAppRequestInfo.requestID, []byte{}))
assert.Empty(t, net.(*network).outstandingRequestHandlers) // Received response
}
func TestRequestMinVersion(t *testing.T) {
// Enabled - testing version matching for peer selection
callNum := uint32(0)
nodeID := ids.GenerateTestNodeID()
codecManager := buildCodec(t, TestMessage{})
var net Network
sender := testAppSender{
sendAppRequestFn: func(_ context.Context, nodeID ids.NodeID, reqID uint32, messageBytes []byte) error {
atomic.AddUint32(&callNum, 1)
// nodeID is already the correct one provided as a parameter
go func() {
time.Sleep(200 * time.Millisecond)
atomic.AddUint32(&callNum, 1)
responseBytes, err := codecManager.Marshal(message.Version, TestMessage{Message: "this is a response"})
if err != nil {
panic(err)
}
assert.NoError(t, net.AppResponse(context.Background(), nodeID, reqID, responseBytes))
}()
return nil
},
}
// passing nil as codec works because the net.AppRequest is never called
ctx := context.Background()
net, err := NewNetwork(ctx, sender, codecManager, 1, prometheus.NewRegistry())
require.NoError(t, err)
requestMessage := TestMessage{Message: "this is a request"}
requestBytes, err := message.RequestToBytes(codecManager, requestMessage)
assert.NoError(t, err)
assert.NoError(t,
net.Connected(
context.Background(),
nodeID,
&consensusVersion.Application{
Name: version.Client,
Major: 1,
Minor: 7,
Patch: 1,
},
),
)
// ensure version does not match
responseBytes, _, err := net.SendSyncedAppRequestAny(
context.Background(),
&consensusVersion.Application{
Name: version.Client,
Major: 2,
Minor: 0,
Patch: 0,
},
requestBytes,
)
assert.Contains(t, err.Error(), "no peers found matching version")
assert.Nil(t, responseBytes)
// ensure version matches and the request goes through
responseBytes, _, err = net.SendSyncedAppRequestAny(context.Background(), defaultPeerVersion, requestBytes)
assert.NoError(t, err)
var response TestMessage
if _, err = codecManager.Unmarshal(responseBytes, &response); err != nil {
t.Fatal("unexpected error during unmarshal", err)
}
assert.Equal(t, "this is a response", response.Message)
}
func TestOnRequestHonoursDeadline(t *testing.T) {
// Enabled - testing request deadline handling
var net Network
responded := false
sender := testAppSender{
sendAppRequestFn: func(_ context.Context, nodeID ids.NodeID, reqID uint32, message []byte) error {
return nil
},
sendAppResponseFn: func(nodeID ids.NodeID, reqID uint32, message []byte) error {
responded = true
return nil
},
}
codecManager := buildCodec(t, TestMessage{})
requestBytes, err := marshalStruct(codecManager, TestMessage{Message: "hello there"})
assert.NoError(t, err)
requestHandler := &testRequestHandler{
processingDuration: 500 * time.Millisecond,
}
ctx := context.Background()
net, err = NewNetwork(ctx, sender, codecManager, 1, prometheus.NewRegistry())
require.NoError(t, err)
net.SetRequestHandler(requestHandler)
nodeID := ids.GenerateTestNodeID()
requestHandler.response, err = marshalStruct(codecManager, TestMessage{Message: "hi there"})
assert.NoError(t, err)
assert.NoError(t, net.AppRequest(context.Background(), nodeID, 0, time.Now().Add(1*time.Millisecond), requestBytes))
// ensure the handler didn't get called (as peer.Network would've dropped the request)
assert.EqualValues(t, requestHandler.calls, 0)
requestHandler.processingDuration = 0
assert.NoError(t, net.AppRequest(context.Background(), nodeID, 2, time.Now().Add(250*time.Millisecond), requestBytes))
assert.True(t, responded)
assert.EqualValues(t, requestHandler.calls, 1)
}
func TestHandleInvalidMessages(t *testing.T) {
// Enabled - testing handling of malformed/invalid messages
codecManager := buildCodec(t, HelloGossip{}, TestMessage{})
nodeID := ids.GenerateTestNodeID()
requestID := peertest.TestSDKRequestID
sender := &testAppSender{
sendAppErrorF: func(context.Context, ids.NodeID, uint32, int32, string) error {
return nil
},
}
ctx := context.Background()
clientNetwork, err := NewNetwork(ctx, sender, codecManager, 1, prometheus.NewRegistry())
require.NoError(t, err)
clientNetwork.SetRequestHandler(&testRequestHandler{})
assert.NoError(t, clientNetwork.Connected(context.Background(), nodeID, defaultConsensusVersion))
defer clientNetwork.Shutdown()
// Ensure a valid gossip message sent as any App specific message type does not trigger a fatal error
marshaller := helloGossipMarshaller{codec: codecManager}
gossipMsg, err := marshaller.MarshalGossip(&HelloGossip{Msg: "hello there!"})
assert.NoError(t, err)
// Ensure a valid request message sent as any App specific message type does not trigger a fatal error
requestMessage, err := marshalStruct(codecManager, TestMessage{Message: "Hello"})
assert.NoError(t, err)
// Ensure a random message sent as any App specific message type does not trigger a fatal error
garbageResponse := make([]byte, 10)
// Ensure a zero-length message sent as any App specific message type does not trigger a fatal error
emptyResponse := make([]byte, 0)
// Ensure a nil byte slice sent as any App specific message type does not trigger a fatal error
var nilResponse []byte
// Check for edge cases
assert.NoError(t, clientNetwork.AppGossip(context.Background(), nodeID, gossipMsg))
assert.NoError(t, clientNetwork.AppGossip(context.Background(), nodeID, requestMessage))
assert.NoError(t, clientNetwork.AppGossip(context.Background(), nodeID, garbageResponse))
assert.NoError(t, clientNetwork.AppGossip(context.Background(), nodeID, emptyResponse))
assert.NoError(t, clientNetwork.AppGossip(context.Background(), nodeID, nilResponse))
assert.NoError(t, clientNetwork.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(time.Second), gossipMsg))
assert.NoError(t, clientNetwork.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(time.Second), requestMessage))
assert.NoError(t, clientNetwork.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(time.Second), garbageResponse))
assert.NoError(t, clientNetwork.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(time.Second), emptyResponse))
assert.NoError(t, clientNetwork.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(time.Second), nilResponse))
assert.ErrorIs(t, clientNetwork.AppResponse(context.Background(), nodeID, requestID, gossipMsg), p2p.ErrUnrequestedResponse)
assert.ErrorIs(t, clientNetwork.AppResponse(context.Background(), nodeID, requestID, requestMessage), p2p.ErrUnrequestedResponse)
assert.ErrorIs(t, clientNetwork.AppResponse(context.Background(), nodeID, requestID, garbageResponse), p2p.ErrUnrequestedResponse)
assert.ErrorIs(t, clientNetwork.AppResponse(context.Background(), nodeID, requestID, emptyResponse), p2p.ErrUnrequestedResponse)
assert.ErrorIs(t, clientNetwork.AppResponse(context.Background(), nodeID, requestID, nilResponse), p2p.ErrUnrequestedResponse)
}
func TestNetworkPropagatesRequestHandlerError(t *testing.T) {
// Enabled - testing error propagation from request handlers
codecManager := buildCodec(t, TestMessage{})
nodeID := ids.GenerateTestNodeID()
requestID := peertest.TestPeerRequestID
sender := testAppSender{}
ctx := context.Background()
clientNetwork, err := NewNetwork(ctx, sender, codecManager, 1, prometheus.NewRegistry())
require.NoError(t, err)
clientNetwork.SetRequestHandler(&testRequestHandler{err: errors.New("fail")}) // Return an error from the request handler
assert.NoError(t, clientNetwork.Connected(context.Background(), nodeID, defaultConsensusVersion))
defer clientNetwork.Shutdown()
// Ensure a valid request message sent as any App specific message type does not trigger a fatal error
requestMessage, err := marshalStruct(codecManager, TestMessage{Message: "Hello"})
assert.NoError(t, err)
// Check that if the request handler returns an error, it is propagated as a fatal error.
assert.Error(t, clientNetwork.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(time.Second), requestMessage))
}
func TestNetworkAppRequestAfterShutdown(t *testing.T) {
// Enabled - testing requests after network shutdown are handled gracefully
require := require.New(t)
ctx := context.Background()
net, err := NewNetwork(ctx, nil, nil, 16, prometheus.NewRegistry())
require.NoError(err)
net.Shutdown()
require.NoError(net.SendAppRequest(context.Background(), ids.GenerateTestNodeID(), nil, nil))
require.NoError(net.SendAppRequest(context.Background(), ids.GenerateTestNodeID(), nil, nil))
}
func TestNetworkRouting(t *testing.T) {
// Enabled - testing routing between SDK handlers and network handlers
require := require.New(t)
sender := &testAppSender{
sendAppRequestFn: func(_ context.Context, nodeID ids.NodeID, u uint32, bytes []byte) error {
return nil
},
sendAppResponseFn: func(id ids.NodeID, u uint32, bytes []byte) error {
return nil
},
}
protocol := 0
requestID := peertest.TestSDKRequestID
handler := &testSDKHandler{}
networkCodec := codec.NewManager(0)
ctx := context.Background()
network, err := NewNetwork(ctx, sender, networkCodec, 1, prometheus.NewRegistry())
require.NoError(err)
require.NoError(network.AddHandler(uint64(protocol), handler))
nodeID := ids.GenerateTestNodeID()
foobar := append([]byte{byte(protocol)}, []byte("foobar")...)
// forward it to the sdk handler
require.NoError(network.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(5*time.Second), foobar))
require.True(handler.appRequested)
err = network.AppResponse(context.Background(), ids.GenerateTestNodeID(), requestID, foobar)
require.ErrorIs(err, p2p.ErrUnrequestedResponse)
// AppRequestFailed is not exposed on the Network interface
// This test was checking internal behavior that is no longer accessible
// err = network.AppRequestFailed(context.Background(), nodeID, requestID, &nodeCore.AppError{Code: -1, Message: context.DeadlineExceeded.Error()})
// require.ErrorIs(err, p2p.ErrUnrequestedResponse)
}
func buildCodec(t *testing.T, types ...interface{}) codec.Manager {
codecManager := codec.NewDefaultManager()
c := linearcodec.NewDefault()
for _, typ := range types {
assert.NoError(t, c.RegisterType(typ))
}
assert.NoError(t, codecManager.RegisterCodec(message.Version, c))
return codecManager
}
// marshalStruct is a helper method used to marshal an object as `interface{}`
// so that the codec is able to include the TypeID in the resulting bytes
func marshalStruct(codec codec.Manager, obj interface{}) ([]byte, error) {
return codec.Marshal(message.Version, &obj)
}
// Minimal testAppSender for tests
type testAppSender struct {
sendAppRequestFn func(context.Context, ids.NodeID, uint32, []byte) error
sendAppResponseFn func(ids.NodeID, uint32, []byte) error
sendAppErrorF func(context.Context, ids.NodeID, uint32, int32, string) error
}
func (t testAppSender) SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, message []byte) error {
// For test compatibility, call the test function for each node in the set
if t.sendAppRequestFn != nil {
for nodeID := range nodeIDs {
if err := t.sendAppRequestFn(ctx, nodeID, requestID, message); err != nil {
return err
}
}
}
return nil
}
func (t testAppSender) SendAppResponse(_ context.Context, nodeID ids.NodeID, requestID uint32, message []byte) error {
if t.sendAppResponseFn != nil {
return t.sendAppResponseFn(nodeID, requestID, message)
}
return nil
}
func (t testAppSender) SendAppError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
if t.sendAppErrorF != nil {
return t.sendAppErrorF(ctx, nodeID, requestID, errorCode, errorMessage)
}
return nil
}
func (t testAppSender) SendAppGossip(_ context.Context, _ set.Set[ids.NodeID], _ []byte) error {
return nil
}
func (t testAppSender) SendAppGossipSpecific(_ context.Context, _ set.Set[ids.NodeID], _ []byte) error {
return nil
}
func (t testAppSender) SendCrossChainAppRequest(_ context.Context, _ ids.ID, _ uint32, _ []byte) error {
return nil
}
func (t testAppSender) SendCrossChainAppResponse(_ context.Context, _ ids.ID, _ uint32, _ []byte) error {
return nil
}
func (t testAppSender) SendCrossChainAppError(_ context.Context, _ ids.ID, _ uint32, _ int32, _ string) error {
return nil
}
type HelloRequest struct {
Message string `serialize:"true"`
}
func (h HelloRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler message.RequestHandler) ([]byte, error) {
// casting is only necessary for test since RequestHandler does not implement anything at the moment
return handler.(TestRequestHandler).HandleHelloRequest(ctx, nodeID, requestID, &h)
}
func (h HelloRequest) String() string {
return fmt.Sprintf("HelloRequest(%s)", h.Message)
}
type GreetingRequest struct {
Greeting string `serialize:"true"`
}
func (g GreetingRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler message.RequestHandler) ([]byte, error) {
// casting is only necessary for test since RequestHandler does not implement anything at the moment
return handler.(TestRequestHandler).HandleGreetingRequest(ctx, nodeID, requestID, &g)
}
func (g GreetingRequest) String() string {
return fmt.Sprintf("GreetingRequest(%s)", g.Greeting)
}
type HelloResponse struct {
Response string `serialize:"true"`
}
type GreetingResponse struct {
Greet string `serialize:"true"`
}
type TestRequestHandler interface {
HandleHelloRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request *HelloRequest) ([]byte, error)
HandleGreetingRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request *GreetingRequest) ([]byte, error)
}
type HelloGreetingRequestHandler struct {
message.RequestHandler
codec codec.Manager
}
func (h *HelloGreetingRequestHandler) HandleHelloRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request *HelloRequest) ([]byte, error) {
return h.codec.Marshal(message.Version, HelloResponse{Response: "Hi"})
}
func (h *HelloGreetingRequestHandler) HandleGreetingRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request *GreetingRequest) ([]byte, error) {
return h.codec.Marshal(message.Version, GreetingResponse{Greet: "Hey there"})
}
type TestMessage struct {
Message string `serialize:"true"`
}
func (t TestMessage) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler message.RequestHandler) ([]byte, error) {
return handler.(*testRequestHandler).handleTestRequest(ctx, nodeID, requestID, &t)
}
func (t TestMessage) String() string {
return fmt.Sprintf("TestMessage(%s)", t.Message)
}
type HelloGossip struct {
Msg string `serialize:"true"`
}
func (tx *HelloGossip) GossipID() ids.ID {
return ids.FromStringOrPanic(tx.Msg)
}
type helloGossipMarshaller struct {
codec codec.Manager
}
func (g helloGossipMarshaller) MarshalGossip(tx *HelloGossip) ([]byte, error) {
return g.codec.Marshal(0, tx)
}
func (g helloGossipMarshaller) UnmarshalGossip(bytes []byte) (*HelloGossip, error) {
h := &HelloGossip{}
_, err := g.codec.Unmarshal(bytes, h)
return h, err
}
type testRequestHandler struct {
message.RequestHandler
calls uint32
processingDuration time.Duration
response []byte
err error
}
func (r *testRequestHandler) handleTestRequest(ctx context.Context, _ ids.NodeID, _ uint32, _ *TestMessage) ([]byte, error) {
r.calls++
select {
case <-time.After(r.processingDuration):
break
case <-ctx.Done():
return nil, ctx.Err()
}
return r.response, r.err
}
type testSDKHandler struct {
appRequested bool
}
func (t *testSDKHandler) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) {
panic("implement me")
}
func (t *testSDKHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, *nodeCore.AppError) {
t.appRequested = true
return nil, nil
}
func (t *testSDKHandler) CrossChainAppRequest(ctx context.Context, chainID ids.ID, deadline time.Time, requestBytes []byte) ([]byte, error) {
panic("implement me")
}
-224
View File
@@ -1,224 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package network
import (
"math"
"math/rand"
"time"
"github.com/luxfi/consensus/version"
"github.com/luxfi/ids"
luxmath "github.com/luxfi/math/math"
"github.com/luxfi/math/set"
"github.com/luxfi/log"
"github.com/luxfi/geth/metrics"
)
const (
bandwidthHalflife = 5 * time.Minute
// controls how eagerly we connect to new peers vs. using
// peers with known good response bandwidth.
desiredMinResponsivePeers = 20
newPeerConnectFactor = 0.1
// controls how often we prefer a random responsive peer over the most
// performant peer.
randomPeerProbability = 0.2
)
// information we track on a given peer
type peerInfo struct {
version *version.Application
bandwidth luxmath.Averager
}
// peerTracker tracks the bandwidth of responses coming from peers,
// preferring to contact peers with known good bandwidth, connecting
// to new peers with an exponentially decaying probability.
// Note: is not thread safe, caller must handle synchronization.
type peerTracker struct {
peers map[ids.NodeID]*peerInfo // all peers we are connected to
numTrackedPeers *metrics.Gauge
trackedPeers set.Set[ids.NodeID] // peers that we have sent a request to
numResponsivePeers *metrics.Gauge
responsivePeers set.Set[ids.NodeID] // peers that responded to the last request they were sent
bandwidthHeap luxmath.AveragerHeap // tracks bandwidth peers are responding with
averageBandwidthMetric *metrics.GaugeFloat64
averageBandwidth luxmath.Averager
}
func NewPeerTracker() *peerTracker {
return &peerTracker{
peers: make(map[ids.NodeID]*peerInfo),
numTrackedPeers: metrics.GetOrRegisterGauge("net_tracked_peers", nil),
trackedPeers: make(set.Set[ids.NodeID]),
numResponsivePeers: metrics.GetOrRegisterGauge("net_responsive_peers", nil),
responsivePeers: make(set.Set[ids.NodeID]),
bandwidthHeap: luxmath.NewMaxAveragerHeap(),
averageBandwidthMetric: metrics.GetOrRegisterGaugeFloat64("net_average_bandwidth", nil),
averageBandwidth: luxmath.NewAverager(0, bandwidthHalflife, time.Now()),
}
}
// shouldTrackNewPeer returns true if we are not connected to enough peers.
// otherwise returns true probabilistically based on the number of tracked peers.
func (p *peerTracker) shouldTrackNewPeer() bool {
numResponsivePeers := p.responsivePeers.Len()
if numResponsivePeers < desiredMinResponsivePeers {
return true
}
if len(p.trackedPeers) >= len(p.peers) {
// already tracking all the peers
return false
}
newPeerProbability := math.Exp(-float64(numResponsivePeers) * newPeerConnectFactor)
return rand.Float64() < newPeerProbability
}
// getResponsivePeer returns a random [ids.NodeID] of a peer that has responded
// to a request.
func (p *peerTracker) getResponsivePeer() (ids.NodeID, luxmath.Averager, bool) {
// Get an arbitrary element from the set (without removing it)
var nodeID ids.NodeID
var ok bool
for id := range p.responsivePeers {
nodeID = id
ok = true
break
}
if !ok {
return ids.NodeID{}, nil, false
}
// Convert our ids.NodeID to node's ids.NodeID for heap operations
// Both are [20]byte arrays so this is safe
var nodeNodeID [20]byte
copy(nodeNodeID[:], nodeID[:])
averager, ok := p.bandwidthHeap.Remove(nodeNodeID)
if ok {
return nodeID, averager, true
}
peer := p.peers[nodeID]
return nodeID, peer.bandwidth, true
}
func (p *peerTracker) GetAnyPeer(minVersion *version.Application) (ids.NodeID, bool) {
if p.shouldTrackNewPeer() {
for nodeID := range p.peers {
// if minVersion is specified and peer's version is less, skip
if minVersion != nil && p.peers[nodeID].version.Compare(minVersion) < 0 {
continue
}
// skip peers already tracked
if p.trackedPeers.Contains(nodeID) {
continue
}
log.Debug("peer tracking: connecting to new peer", "trackedPeers", len(p.trackedPeers), "nodeID", nodeID)
return nodeID, true
}
}
var (
nodeID ids.NodeID
ok bool
random bool
averager luxmath.Averager
)
if rand.Float64() < randomPeerProbability {
random = true
nodeID, averager, ok = p.getResponsivePeer()
} else {
// Pop returns node's ids.NodeID, convert to our ids.NodeID
var nodeNodeID [20]byte
nodeNodeID, averager, ok = p.bandwidthHeap.Pop()
copy(nodeID[:], nodeNodeID[:])
}
if ok {
log.Debug("peer tracking: popping peer", "nodeID", nodeID, "bandwidth", averager.Read(), "random", random)
return nodeID, true
}
// if no nodes found in the bandwidth heap, return a tracked node at random
// Get an arbitrary element from the tracked peers set
for id := range p.trackedPeers {
return id, true
}
return ids.NodeID{}, false
}
func (p *peerTracker) TrackPeer(nodeID ids.NodeID) {
p.trackedPeers.Add(nodeID)
p.numTrackedPeers.Update(int64(p.trackedPeers.Len()))
}
func (p *peerTracker) TrackBandwidth(nodeID ids.NodeID, bandwidth float64) {
peer := p.peers[nodeID]
if peer == nil {
// we're not connected to this peer, nothing to do here
log.Debug("tracking bandwidth for untracked peer", "nodeID", nodeID)
return
}
now := time.Now()
if peer.bandwidth == nil {
peer.bandwidth = luxmath.NewAverager(bandwidth, bandwidthHalflife, now)
} else {
peer.bandwidth.Observe(bandwidth, now)
}
// Convert our ids.NodeID to node's ids.NodeID for heap operations
var nodeNodeID [20]byte
copy(nodeNodeID[:], nodeID[:])
p.bandwidthHeap.Add(nodeNodeID, peer.bandwidth)
if bandwidth == 0 {
p.responsivePeers.Remove(nodeID)
} else {
p.responsivePeers.Add(nodeID)
p.averageBandwidth.Observe(bandwidth, now)
p.averageBandwidthMetric.Update(p.averageBandwidth.Read())
}
p.numResponsivePeers.Update(int64(p.responsivePeers.Len()))
}
// Connected should be called when [nodeID] connects to this node
func (p *peerTracker) Connected(nodeID ids.NodeID, nodeVersion *version.Application) {
if peer := p.peers[nodeID]; peer != nil {
// Peer is already connected, update the version if it has changed.
// Log a warning message since the consensus engine should never call Connected on a peer
// that we have already marked as Connected.
if nodeVersion.Compare(peer.version) != 0 {
p.peers[nodeID] = &peerInfo{
version: nodeVersion,
bandwidth: peer.bandwidth,
}
log.Warn("updating node version of already connected peer", "nodeID", nodeID, "storedVersion", peer.version, "nodeVersion", nodeVersion)
} else {
log.Warn("ignoring peer connected event for already connected peer with identical version", "nodeID", nodeID)
}
return
}
p.peers[nodeID] = &peerInfo{
version: nodeVersion,
}
}
// Disconnected should be called when [nodeID] disconnects from this node
func (p *peerTracker) Disconnected(nodeID ids.NodeID) {
// Convert our ids.NodeID to node's ids.NodeID for heap operations
var nodeNodeID [20]byte
copy(nodeNodeID[:], nodeID[:])
p.bandwidthHeap.Remove(nodeNodeID)
p.trackedPeers.Remove(nodeID)
p.numTrackedPeers.Update(int64(p.trackedPeers.Len()))
p.responsivePeers.Remove(nodeID)
p.numResponsivePeers.Update(int64(p.responsivePeers.Len()))
delete(p.peers, nodeID)
}
// Size returns the number of peers the node is connected to
func (p *peerTracker) Size() int {
return len(p.peers)
}
-88
View File
@@ -1,88 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package network
import (
"testing"
"github.com/luxfi/ids"
"github.com/stretchr/testify/require"
)
func TestPeerTracker(t *testing.T) {
require := require.New(t)
p := NewPeerTracker()
// Connect some peers
numExtraPeers := 10
numPeers := desiredMinResponsivePeers + numExtraPeers
peerIDs := make([]ids.NodeID, numPeers)
for i := range peerIDs {
peerIDs[i] = ids.GenerateTestNodeID()
p.Connected(peerIDs[i], defaultPeerVersion)
}
responsivePeers := make(map[ids.NodeID]bool)
// Expect requests to go to new peers until we have desiredMinResponsivePeers responsive peers.
for i := 0; i < desiredMinResponsivePeers+numExtraPeers/2; i++ {
peer, ok := p.GetAnyPeer(nil)
require.True(ok)
require.NotNil(peer)
_, exists := responsivePeers[peer]
require.Falsef(exists, "expected connecting to a new peer, but got the same peer twice: peer %s iteration %d", peer, i)
responsivePeers[peer] = true
p.TrackPeer(peer) // mark the peer as having a message sent to it
}
// Mark some peers as responsive and others as not responsive
i := 0
for peer := range responsivePeers {
if i < desiredMinResponsivePeers {
p.TrackBandwidth(peer, 10)
} else {
responsivePeers[peer] = false // remember which peers were not responsive
p.TrackBandwidth(peer, 0)
}
i++
}
// Expect requests to go to responsive or new peers, so long as they are available
numRequests := 50
for i := 0; i < numRequests; i++ {
peer, ok := p.GetAnyPeer(nil)
require.True(ok)
require.NotNil(peer)
responsive, ok := responsivePeers[peer]
if ok {
require.Truef(responsive, "expected connecting to a responsive peer, but got a peer that was not responsive: peer %s iteration %d", peer, i)
p.TrackBandwidth(peer, 10)
} else {
responsivePeers[peer] = false // remember that we connected to this peer
p.TrackPeer(peer) // mark the peer as having a message sent to it
p.TrackBandwidth(peer, 0) // mark the peer as non-responsive
}
}
// Disconnect from peers that were previously responsive and ones we didn't connect to yet.
for _, peer := range peerIDs {
responsive, ok := responsivePeers[peer]
if ok && responsive || !ok {
p.Disconnected(peer)
}
}
// Requests should fall back on non-responsive peers when no other choice is left
peer, ok := p.GetAnyPeer(nil)
require.True(ok)
require.NotNil(peer)
responsive, ok := responsivePeers[peer]
require.True(ok)
require.Falsef(responsive, "expected connecting to a non-responsive peer, but got a peer that was responsive: peer %s", peer)
}
-14
View File
@@ -1,14 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package peertest
const (
// TestSDKRequestID is the request ID for the SDK request, which is odd-numbered.
// See peer.IsNetworkRequest for more details.
TestSDKRequestID uint32 = 1
// TestPeerRequestID is the request ID for the peer request, which is even-numbered.
// See peer.IsNetworkRequest for more details.
TestPeerRequestID uint32 = 0
)
-36
View File
@@ -1,36 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package stats
import (
"time"
"github.com/luxfi/geth/metrics"
)
// RequestHandlerStats provides the interface for metrics for app requests.
type RequestHandlerStats interface {
UpdateTimeUntilDeadline(duration time.Duration)
IncDeadlineDroppedRequest()
}
type requestHandlerStats struct {
timeUntilDeadline *metrics.Timer
droppedRequests *metrics.Counter
}
func (h *requestHandlerStats) IncDeadlineDroppedRequest() {
h.droppedRequests.Inc(1)
}
func (h *requestHandlerStats) UpdateTimeUntilDeadline(duration time.Duration) {
h.timeUntilDeadline.Update(duration)
}
func NewRequestHandlerStats() RequestHandlerStats {
return &requestHandlerStats{
timeUntilDeadline: metrics.GetOrRegisterTimer("net_req_time_until_deadline", nil),
droppedRequests: metrics.GetOrRegisterCounter("net_req_deadline_dropped", nil),
}
}
-62
View File
@@ -1,62 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package network
import (
"context"
"errors"
"github.com/luxfi/evm/plugin/evm/message"
)
var (
_ message.ResponseHandler = (*waitingResponseHandler)(nil)
errRequestFailed = errors.New("request failed")
)
// waitingResponseHandler implements the ResponseHandler interface
// Internally used to wait for response after making a request synchronously
// responseChan may contain response bytes if the original request has not failed
// responseChan is closed in either fail or success scenario
type waitingResponseHandler struct {
responseChan chan []byte // blocking channel with response bytes
failed bool // whether the original request is failed
}
// newWaitingResponseHandler returns new instance of the waitingResponseHandler
func newWaitingResponseHandler() *waitingResponseHandler {
return &waitingResponseHandler{
// Make buffer length 1 so that OnResponse can complete
// even if no goroutine is waiting on the channel (i.e.
// the context of a request is cancelled.)
responseChan: make(chan []byte, 1),
}
}
// OnResponse passes the response bytes to the responseChan and closes the channel
func (w *waitingResponseHandler) OnResponse(response []byte) error {
w.responseChan <- response
close(w.responseChan)
return nil
}
// OnFailure sets the failed flag to true and closes the channel
func (w *waitingResponseHandler) OnFailure() error {
w.failed = true
close(w.responseChan)
return nil
}
func (waitingHandler *waitingResponseHandler) WaitForResult(ctx context.Context) ([]byte, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case response := <-waitingHandler.responseChan:
if waitingHandler.failed {
return nil, errRequestFailed
}
return response, nil
}
}
-130
View File
@@ -8,16 +8,8 @@ import (
"time"
"github.com/luxfi/ids"
// Node interfaces that the VM plugin must implement (from engine/chain/block)
nodeblock "github.com/luxfi/consensus/engine/chain/block"
// Consensus interfaces that our implementation uses (from protocol/chain)
consensusblock "github.com/luxfi/consensus/protocol/chain"
// Network interfaces
"github.com/luxfi/consensus/core/appsender"
"github.com/luxfi/math/set"
)
// BlockAdapter adapts consensus Block to node Block interface
@@ -108,125 +100,3 @@ func (b *BlockAdapter) VerifyWithContext(ctx context.Context, blockCtx *nodebloc
return b.consensus.Verify(ctx)
}
// ContextAdapter adapts between node and consensus context types
type ContextAdapter struct {
nodeCtx *nodeblock.Context
}
// NewContextAdapter creates a context adapter from node to consensus
func NewContextAdapter(nodeCtx *nodeblock.Context) *nodeblock.Context {
// Both use the same Context type from engine/chain/block package
return nodeCtx
}
// NewNodeContextAdapter creates a node context from consensus context
func NewNodeContextAdapter(consensusCtx *nodeblock.Context) *nodeblock.Context {
// Both use the same Context type from engine/chain/block package
return consensusCtx
}
// AppSenderAdapter adapts consensus AppSender to node AppSender interface
type AppSenderAdapter struct {
consensus appsender.AppSender
}
// NewAppSenderAdapter creates an AppSender adapter
func NewAppSenderAdapter(consensusAppSender appsender.AppSender) nodeblock.AppSender {
return &AppSenderAdapter{consensus: consensusAppSender}
}
// SendAppRequest sends an app request
func (a *AppSenderAdapter) SendAppRequest(ctx context.Context, nodeIDs []ids.NodeID, requestID uint32, appRequestBytes []byte) error {
// Convert slice to set for consensus interface
nodeIDSet := set.NewSet[ids.NodeID](len(nodeIDs))
for _, nodeID := range nodeIDs {
nodeIDSet.Add(nodeID)
}
return a.consensus.SendAppRequest(ctx, nodeIDSet, requestID, appRequestBytes)
}
// SendAppResponse sends an app response
func (a *AppSenderAdapter) SendAppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, appResponseBytes []byte) error {
return a.consensus.SendAppResponse(ctx, nodeID, requestID, appResponseBytes)
}
// SendAppError sends an app error
func (a *AppSenderAdapter) SendAppError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
return a.consensus.SendAppError(ctx, nodeID, requestID, errorCode, errorMessage)
}
// SendAppGossip sends app gossip
func (a *AppSenderAdapter) SendAppGossip(ctx context.Context, nodeIDs []ids.NodeID, appGossipBytes []byte) error {
// Convert slice to set for consensus interface
nodeIDSet := set.NewSet[ids.NodeID](len(nodeIDs))
for _, nodeID := range nodeIDs {
nodeIDSet.Add(nodeID)
}
return a.consensus.SendAppGossip(ctx, nodeIDSet, appGossipBytes)
}
// SendAppGossipSpecific sends app gossip to specific nodes
func (a *AppSenderAdapter) SendAppGossipSpecific(ctx context.Context, nodeIDs []ids.NodeID, appGossipBytes []byte) error {
// Convert slice to set for consensus interface
nodeIDSet := set.NewSet[ids.NodeID](len(nodeIDs))
for _, nodeID := range nodeIDs {
nodeIDSet.Add(nodeID)
}
return a.consensus.SendAppGossipSpecific(ctx, nodeIDSet, appGossipBytes)
}
// ReverseAppSenderAdapter adapts node AppSender to consensus AppSender interface
type ReverseAppSenderAdapter struct {
node nodeblock.AppSender
}
// NewReverseAppSenderAdapter creates a reverse AppSender adapter
func NewReverseAppSenderAdapter(nodeAppSender nodeblock.AppSender) appsender.AppSender {
return &ReverseAppSenderAdapter{node: nodeAppSender}
}
// SendAppRequest sends an app request (consensus to node)
func (a *ReverseAppSenderAdapter) SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, appRequestBytes []byte) error {
// Convert set to slice for node interface
nodeIDSlice := make([]ids.NodeID, 0, nodeIDs.Len())
for nodeID := range nodeIDs {
nodeIDSlice = append(nodeIDSlice, nodeID)
}
return a.node.SendAppRequest(ctx, nodeIDSlice, requestID, appRequestBytes)
}
// SendAppResponse sends an app response (consensus to node)
func (a *ReverseAppSenderAdapter) SendAppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, appResponseBytes []byte) error {
return a.node.SendAppResponse(ctx, nodeID, requestID, appResponseBytes)
}
// SendAppError sends an app error (consensus to node)
func (a *ReverseAppSenderAdapter) SendAppError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
return a.node.SendAppError(ctx, nodeID, requestID, errorCode, errorMessage)
}
// SendAppGossip sends app gossip (consensus to node)
func (a *ReverseAppSenderAdapter) SendAppGossip(ctx context.Context, nodeIDs set.Set[ids.NodeID], appGossipBytes []byte) error {
// Convert set to slice for node interface
nodeIDSlice := make([]ids.NodeID, 0, nodeIDs.Len())
for nodeID := range nodeIDs {
nodeIDSlice = append(nodeIDSlice, nodeID)
}
return a.node.SendAppGossip(ctx, nodeIDSlice, appGossipBytes)
}
// SendAppGossipSpecific sends app gossip to specific nodes (consensus to node)
func (a *ReverseAppSenderAdapter) SendAppGossipSpecific(ctx context.Context, nodeIDs set.Set[ids.NodeID], appGossipBytes []byte) error {
// Convert set to slice for node interface
nodeIDSlice := make([]ids.NodeID, 0, nodeIDs.Len())
for nodeID := range nodeIDs {
nodeIDSlice = append(nodeIDSlice, nodeID)
}
return a.node.SendAppGossip(ctx, nodeIDSlice, appGossipBytes)
}
+177 -2
View File
@@ -4,12 +4,19 @@
package evm
import (
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/luxfi/evm/plugin/evm/client"
"github.com/luxfi/log"
"github.com/luxfi/evm/api"
"github.com/luxfi/evm/core"
"github.com/luxfi/evm/plugin/evm/client"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/geth/rlp"
"github.com/luxfi/log"
"github.com/luxfi/utils/profiler"
)
@@ -82,3 +89,171 @@ func (p *Admin) GetVMConfig(_ *http.Request, _ *struct{}, reply *client.ConfigRe
reply.Config = &p.vm.config
return nil
}
// ImportChainArgs represents the arguments for ImportChain
type ImportChainArgs struct {
File string `json:"file"`
}
// ImportChainReply represents the response from ImportChain
type ImportChainReply struct {
Success bool `json:"success"`
BlocksImported int `json:"blocksImported,omitempty"`
HeightBefore uint64 `json:"heightBefore,omitempty"`
HeightAfter uint64 `json:"heightAfter,omitempty"`
Message string `json:"message,omitempty"`
}
// ImportChain imports a blockchain from a local file
func (p *Admin) ImportChain(_ *http.Request, args *ImportChainArgs, reply *ImportChainReply) error {
log.Info("ImportChain called", "file", args.File)
p.vm.vmLock.Lock()
defer p.vm.vmLock.Unlock()
if p.vm.eth == nil {
return fmt.Errorf("ethereum backend not initialized")
}
chain := p.vm.eth.BlockChain()
if chain == nil {
return fmt.Errorf("blockchain not initialized")
}
// Get chain state before import
currentBlock := chain.CurrentBlock()
genesisHash := chain.Genesis().Hash().Hex()
beforeNum := currentBlock.Number.Uint64()
// Import the chain from file
totalImported, err := importBlocksFromFileWithCount(chain, args.File)
if err != nil {
return fmt.Errorf("import failed after %d blocks: %w", totalImported, err)
}
// Get chain state after import
afterBlock := chain.CurrentBlock()
afterNum := afterBlock.Number.Uint64()
// Check if any blocks were actually imported
if totalImported == 0 {
return fmt.Errorf("no blocks imported (before=%d, after=%d, genesis=%s)", beforeNum, afterNum, genesisHash)
}
if afterNum == beforeNum {
return fmt.Errorf("block height unchanged after import: %d blocks parsed but height still %d", totalImported, afterNum)
}
reply.Success = true
reply.BlocksImported = totalImported
reply.HeightBefore = beforeNum
reply.HeightAfter = afterNum
reply.Message = fmt.Sprintf("imported %d blocks, height %d -> %d", totalImported, beforeNum, afterNum)
log.Info("ImportChain: completed", "imported", totalImported, "height", afterNum)
return nil
}
// importBlocksFromFileWithCount imports blocks from an RLP-encoded file and returns the count
func importBlocksFromFileWithCount(chain *core.BlockChain, file string) (int, error) {
in, err := os.Open(file)
if err != nil {
return 0, fmt.Errorf("failed to open file: %w", err)
}
defer in.Close()
var reader io.Reader = in
if strings.HasSuffix(file, ".gz") {
if reader, err = gzip.NewReader(reader); err != nil {
return 0, fmt.Errorf("failed to create gzip reader: %w", err)
}
}
stream := rlp.NewStream(reader, 0)
blocks := make([]*types.Block, 0, 2500)
totalParsed := 0
totalImported := 0
skippedGenesis := false
for batch := 0; ; batch++ {
// Load a batch of blocks from the input file
for len(blocks) < cap(blocks) {
block := new(types.Block)
if err := stream.Decode(block); err == io.EOF {
break
} else if err != nil {
return totalImported, fmt.Errorf("block %d: failed to parse: %w", totalParsed, err)
}
// ignore the genesis block when importing blocks
if block.NumberU64() == 0 {
skippedGenesis = true
continue
}
blocks = append(blocks, block)
totalParsed++
}
if len(blocks) == 0 {
if totalParsed == 0 && !skippedGenesis {
return 0, fmt.Errorf("no blocks found in file (possibly wrong format or empty)")
}
break
}
// Get first block info for error messages
firstBlock := blocks[0]
firstNum := firstBlock.NumberU64()
parentHash := firstBlock.ParentHash().Hex()
// Skip check for existing blocks - let InsertChain handle it
// This ensures we update canonical chain even if blocks exist
// Check parent exists (for first block in batch)
if firstNum > 0 && !chain.HasBlock(firstBlock.ParentHash(), firstNum-1) {
// Check if parent is genesis
genesisHash := chain.Genesis().Hash()
if firstNum == 1 && firstBlock.ParentHash() != genesisHash {
return totalImported, fmt.Errorf("batch %d: block 1 parent mismatch - expected genesis %s, got %s",
batch, genesisHash.Hex(), parentHash)
}
return totalImported, fmt.Errorf("batch %d: parent block missing - firstBlock=%d, parentHash=%s, hasGenesis=%v, skippedGenesis=%v, totalParsed=%d",
batch, firstNum, parentHash, chain.HasBlock(genesisHash, 0), skippedGenesis, totalParsed)
}
// Import the batch
log.Info("ImportChain: inserting batch", "batch", batch, "blocks", len(blocks), "firstNum", firstNum)
n, err := chain.InsertChain(blocks)
if err != nil {
return totalImported, fmt.Errorf("batch %d: insert failed after %d of %d blocks - firstBlock=%d, parentHash=%s, error=%w",
batch, n, len(blocks), firstNum, parentHash, err)
}
log.Info("ImportChain: InsertChain completed", "batch", batch, "inserted", n)
// Set the last accepted block directly to make blocks queryable via RPC.
// This bypasses the acceptor queue which can cause issues with imported blocks.
// The lastAccepted is used as the finalization boundary for RPC queries.
lastInsertedBlock := blocks[n-1]
if err := chain.SetLastAcceptedBlockDirect(lastInsertedBlock); err != nil {
return totalImported, fmt.Errorf("batch %d: failed to set last accepted block: %w", batch, err)
}
log.Info("ImportChain: blocks finalized", "batch", batch, "count", n, "lastAccepted", lastInsertedBlock.NumberU64())
totalImported += n
blocks = blocks[:0]
}
if totalImported == 0 {
return 0, fmt.Errorf("no blocks imported (parsed=%d)", totalParsed)
}
return totalImported, nil
}
// hasAllBlocks checks if all blocks in the list are already in the chain
func hasAllBlocks(chain *core.BlockChain, blocks []*types.Block) bool {
for _, block := range blocks {
if !chain.HasBlock(block.Hash(), block.NumberU64()) {
return false
}
}
return true
}
+1
View File
@@ -20,6 +20,7 @@ const (
func NewDefaultConfig() Config {
return Config{
AdminAPIEnabled: true, // Enable admin API by default for importChain
AllowUnprotectedTxHashes: []common.Hash{
common.HexToHash("0xfefb2da535e927b85fe68eb81cb2e4a5827c905f78381a01ef2322aa9b0aee8e"), // EIP-1820: https://eips.ethereum.org/EIPS/eip-1820
},
+9 -7
View File
@@ -5,22 +5,24 @@ package evm
import (
"io"
"os"
)
// loggerWriter wraps a consensus.Logger to implement io.Writer
type loggerWriter struct {
logger interface{}
writer io.Writer
}
// Write implements io.Writer
// Write implements io.Writer - writes to stderr for plugin subprocess logging
func (w *loggerWriter) Write(p []byte) (n int, err error) {
// Since the logger is an interface{} from GetLogger, we can't call methods on it
// This is a placeholder that just returns success
// In practice, the logger will be set differently during initialization
return len(p), nil
return w.writer.Write(p)
}
// newLoggerWriter creates a new loggerWriter
// newLoggerWriter creates a new loggerWriter that writes to stderr
func newLoggerWriter(logger interface{}) io.Writer {
return &loggerWriter{logger: logger}
return &loggerWriter{
logger: logger,
writer: os.Stderr,
}
}
+98 -114
View File
@@ -12,128 +12,112 @@ import (
"github.com/luxfi/p2p"
)
// TestSender is a test implementation of the Sender interface
// TestSender is a test implementation of the p2p.Sender interface
type TestSender struct {
T *testing.T
CantSendAppGossip bool
CantSendAppGossipSpecific bool
CantSendAppRequest bool
CantSendAppResponse bool
CantSendAppError bool
CantSendGossip bool
CantSendGossipSpecific bool
CantSendRequest bool
CantSendResponse bool
CantSendError bool
SendAppGossipF func(context.Context, set.Set[ids.NodeID], []byte) error
SendAppGossipSpecificF func(context.Context, set.Set[ids.NodeID], []byte) error
SendAppRequestF func(context.Context, set.Set[ids.NodeID], uint32, []byte) error
SendAppResponseF func(context.Context, ids.NodeID, uint32, []byte) error
SendAppErrorF func(context.Context, ids.NodeID, uint32, int32, string) error
SendCrossChainAppRequestF func(context.Context, ids.ID, uint32, []byte) error
SendCrossChainAppResponseF func(context.Context, ids.ID, uint32, []byte) error
SendCrossChainAppErrorF func(context.Context, ids.ID, uint32, int32, string) error
SendGossipF func(context.Context, p2p.SendConfig, []byte) error
SendGossipSpecificF func(context.Context, set.Set[ids.NodeID], []byte) error
SendRequestF func(context.Context, set.Set[ids.NodeID], uint32, []byte) error
SendResponseF func(context.Context, ids.NodeID, uint32, []byte) error
SendErrorF func(context.Context, ids.NodeID, uint32, int32, string) error
SendCrossChainRequestF func(context.Context, ids.ID, uint32, []byte) error
SendCrossChainResponseF func(context.Context, ids.ID, uint32, []byte) error
SendCrossChainErrorF func(context.Context, ids.ID, uint32, int32, string) error
// Channel for capturing sent gossip messages
SentAppGossip chan []byte
}
// SendAppGossip implements the consensus AppSender interface
func (s *TestSender) SendAppGossip(ctx context.Context, nodeIDs set.Set[ids.NodeID], msg []byte) error {
if s.SendAppGossipF != nil {
return s.SendAppGossipF(ctx, nodeIDs, msg)
}
if s.SentAppGossip != nil {
// Send to channel if available
s.SentAppGossip <- msg
}
if s.CantSendAppGossip && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendAppGossip")
}
return nil
}
func (s *TestSender) SendAppGossipSpecific(ctx context.Context, nodeIDs set.Set[ids.NodeID], msg []byte) error {
if s.SendAppGossipSpecificF != nil {
return s.SendAppGossipSpecificF(ctx, nodeIDs, msg)
}
if s.CantSendAppGossipSpecific && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendAppGossipSpecific")
}
return nil
}
func (s *TestSender) SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error {
if s.SendAppRequestF != nil {
return s.SendAppRequestF(ctx, nodeIDs, requestID, request)
}
if s.CantSendAppRequest && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendAppRequest")
}
return nil
}
func (s *TestSender) SendAppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error {
if s.SendAppResponseF != nil {
return s.SendAppResponseF(ctx, nodeID, requestID, response)
}
if s.CantSendAppResponse && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendAppResponse")
}
return nil
}
func (s *TestSender) SendAppError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
if s.SendAppErrorF != nil {
return s.SendAppErrorF(ctx, nodeID, requestID, errorCode, errorMessage)
}
if s.CantSendAppError && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendAppError")
}
return nil
}
func (s *TestSender) SendCrossChainAppRequest(ctx context.Context, chainID ids.ID, requestID uint32, appRequestBytes []byte) error {
if s.SendCrossChainAppRequestF != nil {
return s.SendCrossChainAppRequestF(ctx, chainID, requestID, appRequestBytes)
}
return nil
}
func (s *TestSender) SendCrossChainAppResponse(ctx context.Context, chainID ids.ID, requestID uint32, appResponseBytes []byte) error {
if s.SendCrossChainAppResponseF != nil {
return s.SendCrossChainAppResponseF(ctx, chainID, requestID, appResponseBytes)
}
return nil
}
func (s *TestSender) SendCrossChainAppError(ctx context.Context, chainID ids.ID, requestID uint32, errorCode int32, errorMessage string) error {
if s.SendCrossChainAppErrorF != nil {
return s.SendCrossChainAppErrorF(ctx, chainID, requestID, errorCode, errorMessage)
}
return nil
}
// p2p.Sender interface methods
// SendRequest implements p2p.Sender
func (s *TestSender) SendRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error {
return s.SendAppRequest(ctx, nodeIDs, requestID, request)
}
// SendResponse implements p2p.Sender
func (s *TestSender) SendResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error {
return s.SendAppResponse(ctx, nodeID, requestID, response)
}
// SendError implements p2p.Sender
func (s *TestSender) SendError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
return s.SendAppError(ctx, nodeID, requestID, errorCode, errorMessage)
SentGossip chan []byte
}
// SendGossip implements p2p.Sender
func (s *TestSender) SendGossip(ctx context.Context, config p2p.SendConfig, msg []byte) error {
return s.SendAppGossip(ctx, config.NodeIDs, msg)
if s.SendGossipF != nil {
return s.SendGossipF(ctx, config, msg)
}
if s.SentGossip != nil {
s.SentGossip <- msg
}
if s.CantSendGossip && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendGossip")
}
return nil
}
// SendGossipSpecific sends gossip to specific nodes
func (s *TestSender) SendGossipSpecific(ctx context.Context, nodeIDs set.Set[ids.NodeID], msg []byte) error {
if s.SendGossipSpecificF != nil {
return s.SendGossipSpecificF(ctx, nodeIDs, msg)
}
if s.CantSendGossipSpecific && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendGossipSpecific")
}
return nil
}
// SendRequest implements p2p.Sender
func (s *TestSender) SendRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error {
if s.SendRequestF != nil {
return s.SendRequestF(ctx, nodeIDs, requestID, request)
}
if s.CantSendRequest && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendRequest")
}
return nil
}
// SendResponse implements p2p.Sender
func (s *TestSender) SendResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error {
if s.SendResponseF != nil {
return s.SendResponseF(ctx, nodeID, requestID, response)
}
if s.CantSendResponse && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendResponse")
}
return nil
}
// SendError implements p2p.Sender
func (s *TestSender) SendError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
if s.SendErrorF != nil {
return s.SendErrorF(ctx, nodeID, requestID, errorCode, errorMessage)
}
if s.CantSendError && s.T != nil {
s.T.Helper()
s.T.Fatal("unexpectedly called SendError")
}
return nil
}
// SendCrossChainRequest sends a cross-chain request
func (s *TestSender) SendCrossChainRequest(ctx context.Context, chainID ids.ID, requestID uint32, requestBytes []byte) error {
if s.SendCrossChainRequestF != nil {
return s.SendCrossChainRequestF(ctx, chainID, requestID, requestBytes)
}
return nil
}
// SendCrossChainResponse sends a cross-chain response
func (s *TestSender) SendCrossChainResponse(ctx context.Context, chainID ids.ID, requestID uint32, responseBytes []byte) error {
if s.SendCrossChainResponseF != nil {
return s.SendCrossChainResponseF(ctx, chainID, requestID, responseBytes)
}
return nil
}
// SendCrossChainError sends a cross-chain error
func (s *TestSender) SendCrossChainError(ctx context.Context, chainID ids.ID, requestID uint32, errorCode int32, errorMessage string) error {
if s.SendCrossChainErrorF != nil {
return s.SendCrossChainErrorF(ctx, chainID, requestID, errorCode, errorMessage)
}
return nil
}
+38 -167
View File
@@ -58,7 +58,6 @@ import (
"github.com/luxfi/evm/utils"
"github.com/luxfi/evm/warp"
luxWarp "github.com/luxfi/warp"
"github.com/luxfi/warp/signer"
// Force-load tracer engine to trigger registration
//
@@ -87,7 +86,6 @@ import (
nodechain "github.com/luxfi/consensus/protocol/chain"
consensusmockable "github.com/luxfi/consensus/utils/timer/mockable"
consensusversion "github.com/luxfi/consensus/version"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/database/versiondb"
"github.com/luxfi/ids"
nodemockable "github.com/luxfi/timer/mockable"
@@ -98,9 +96,7 @@ import (
nodeChain "github.com/luxfi/vms/components/chain"
commonEng "github.com/luxfi/consensus/core"
"github.com/luxfi/consensus/core/appsender"
nodeCommonEng "github.com/luxfi/consensus/engine/core"
"github.com/luxfi/math/set"
luxVM "github.com/luxfi/vm"
"github.com/luxfi/database"
luxUtils "github.com/luxfi/utils"
@@ -185,106 +181,9 @@ var legacyApiNames = map[string]string{
"private-debug": "debug",
}
// VM implements the chain.ChainVM interface
// warpSignerAdapter adapts a signer.Signer to warp.WarpSigner
type warpSignerAdapter struct {
signer signer.Signer
nodeID ids.NodeID
}
func (w *warpSignerAdapter) Sign(msg []byte) ([]byte, error) {
// Parse the message as an unsigned warp message
unsignedMsg, err := luxWarp.ParseUnsignedMessage(msg)
if err != nil {
return nil, err
}
// Sign using the signer
sig, err := w.signer.Sign(unsignedMsg)
if err != nil {
return nil, err
}
// Return the signature bytes
return bls.SignatureToBytes(sig), nil
}
func (w *warpSignerAdapter) PublicKey() []byte {
pk := w.signer.GetPublicKey()
if pk == nil {
return nil
}
return bls.PublicKeyToCompressedBytes(pk)
}
func (w *warpSignerAdapter) NodeID() ids.NodeID {
return w.nodeID
}
// lp118SignerAdapter adapts a signer.Signer for lp118 handlers
// Uses luxWarp.UnsignedMessage - the ONE canonical warp package
type lp118SignerAdapter struct {
signer signer.Signer
}
// Sign implements the Signer interface for lp118
func (a *lp118SignerAdapter) Sign(msg *luxWarp.UnsignedMessage) ([]byte, error) {
sig, err := a.signer.Sign(msg)
if err != nil {
return nil, err
}
return bls.SignatureToBytes(sig), nil
}
// appSenderWrapper wraps a consensus appsender.AppSender to implement nodeCommonEng.AppSender
type appSenderWrapper struct {
appSender appsender.AppSender
}
// SendAppRequest implements nodeCommonEng.AppSender
func (w *appSenderWrapper) SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, appRequestBytes []byte) error {
return w.appSender.SendAppRequest(ctx, nodeIDs, requestID, appRequestBytes)
}
// SendAppResponse implements nodeCommonEng.AppSender
func (w *appSenderWrapper) SendAppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, appResponseBytes []byte) error {
return w.appSender.SendAppResponse(ctx, nodeID, requestID, appResponseBytes)
}
// SendAppError implements nodeCommonEng.AppSender
func (w *appSenderWrapper) SendAppError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
return w.appSender.SendAppError(ctx, nodeID, requestID, errorCode, errorMessage)
}
// SendAppGossip implements nodeCommonEng.AppSender
func (w *appSenderWrapper) SendAppGossip(ctx context.Context, nodeIDs set.Set[ids.NodeID], appGossipBytes []byte) error {
return w.appSender.SendAppGossip(ctx, nodeIDs, appGossipBytes)
}
// SendAppGossipSpecific implements nodeCommonEng.AppSender
func (w *appSenderWrapper) SendAppGossipSpecific(ctx context.Context, nodeIDs set.Set[ids.NodeID], appGossipBytes []byte) error {
return w.appSender.SendAppGossipSpecific(ctx, nodeIDs, appGossipBytes)
}
// SendRequest implements nodeCommonEng.AppSender (p2p.Sender compatible)
func (w *appSenderWrapper) SendRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error {
return w.appSender.SendAppRequest(ctx, nodeIDs, requestID, request)
}
// SendResponse implements nodeCommonEng.AppSender (p2p.Sender compatible)
func (w *appSenderWrapper) SendResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error {
return w.appSender.SendAppResponse(ctx, nodeID, requestID, response)
}
// SendError implements nodeCommonEng.AppSender (p2p.Sender compatible)
func (w *appSenderWrapper) SendError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error {
return w.appSender.SendAppError(ctx, nodeID, requestID, errorCode, errorMessage)
}
// SendGossip implements p2p.Sender
func (w *appSenderWrapper) SendGossip(ctx context.Context, config p2p.SendConfig, msg []byte) error {
return w.appSender.SendAppGossip(ctx, config.NodeIDs, msg)
}
// Note: AppSender is now an alias for p2p.Sender, so no wrapper is needed.
// The node passes a p2p.Sender directly via RPC.
// warp.Signer is used directly from luxfi/warp - no adapters needed.
type VM struct {
ctx context.Context
@@ -349,7 +248,7 @@ type VM struct {
// Continuous Profiler
profiler profiler.ContinuousProfiler
network.Network
Network *network.Network
networkCodec codec.Manager
// Metrics
@@ -402,7 +301,7 @@ func (vm *VM) Initialize(
configBytes []byte,
msgChan interface{},
fxs []interface{},
appSender interface{},
sender interface{},
) error {
// Write debug info to stderr for troubleshooting
fmt.Fprintf(os.Stderr, "[EVM] Initialize called with chainCtx type: %T\n", chainCtx)
@@ -425,20 +324,20 @@ func (vm *VM) Initialize(
if !ok {
return fmt.Errorf("dbManager is not database.Database")
}
// Handle nil appSender (used in some tests)
var typedAppSender nodeCommonEng.AppSender
if appSender != nil {
// Handle nil sender (used in some tests)
var typedSender p2p.Sender
if sender != nil {
var ok bool
typedAppSender, ok = appSender.(nodeCommonEng.AppSender)
typedSender, ok = sender.(p2p.Sender)
if !ok {
return fmt.Errorf("appSender is not nodeCommonEng.AppSender")
return fmt.Errorf("sender is not p2p.Sender")
}
}
// Convert fxs from []interface{} to []*nodeCommonEng.Fx
// Convert fxs from []interface{} to []*luxVM.Fx
typedFxs := make([]*commonEng.Fx, len(fxs))
for i, fx := range fxs {
if typedFx, ok := fx.(*nodeCommonEng.Fx); ok {
if typedFx, ok := fx.(*luxVM.Fx); ok {
typedFxs[i] = &commonEng.Fx{
ID: typedFx.ID,
Fx: typedFx.Fx,
@@ -446,8 +345,7 @@ func (vm *VM) Initialize(
}
}
// No adapter needed - nodeCommonEng.AppSender is an alias for commonEng.AppSender
return vm.initializeInternal(ctx, typedChainCtx, typedDBManager, genesisBytes, upgradeBytes, configBytes, typedFxs, typedAppSender)
return vm.initializeInternal(ctx, typedChainCtx, typedDBManager, genesisBytes, upgradeBytes, configBytes, typedFxs, typedSender)
}
// initializeInternal contains the actual initialization logic with strongly typed parameters
@@ -459,7 +357,7 @@ func (vm *VM) initializeInternal(
upgradeBytes []byte,
configBytes []byte,
fxs []*commonEng.Fx,
appSender nodeCommonEng.AppSender,
sender p2p.Sender,
) error {
vm.stateSyncDone = make(chan struct{})
vm.config.SetDefaults(defaultTxPoolConfig)
@@ -658,27 +556,15 @@ func (vm *VM) initializeInternal(
)
vm.networkCodec = message.Codec
// Convert block.AppSender to appsender.AppSender - they should be compatible
// Handle nil appSender (used in some tests)
if appSender != nil {
coreAppSender, ok := appSender.(appsender.AppSender)
if !ok {
return fmt.Errorf("appSender does not implement appsender.AppSender")
}
// Wrap the consensus AppSender to match node's AppSender interface
wrappedAppSender := &appSenderWrapper{appSender: coreAppSender}
vm.Network, err = network.NewNetwork(context.Background(), wrappedAppSender, vm.networkCodec, vm.config.MaxOutboundActiveRequests, vm.sdkMetrics)
} else {
// Use nil network for tests without appSender
vm.Network, err = network.NewNetwork(context.Background(), nil, vm.networkCodec, vm.config.MaxOutboundActiveRequests, vm.sdkMetrics)
}
// p2p.Sender is passed directly to network
vm.Network, err = network.NewNetwork(context.Background(), sender, vm.networkCodec, vm.config.MaxOutboundActiveRequests, vm.sdkMetrics)
if err != nil {
return fmt.Errorf("failed to create network: %w", err)
}
// P2PValidators might be nil in test environments
p2pValidatorsInterface := vm.P2PValidators()
p2pValidatorsInterface := vm.Network.P2PValidators()
if p2pValidatorsInterface != nil {
vm.p2pValidators = p2pValidatorsInterface.(*p2p.Validators)
vm.p2pValidators = p2pValidatorsInterface
}
vm.validatorsManager, err = validators.NewManager(vm.ctx, vm.validatorsDB, &vm.clock)
@@ -706,26 +592,22 @@ func (vm *VM) initializeInternal(
// VM implements warp.BlockClient directly
// Get warp signer from context
var warpAdapter *warpSignerAdapter
// Get warp signer from context - use luxWarp.Signer directly, no adapters
var warpSigner luxWarp.Signer
if chainCtx.WarpSigner != nil {
warpSigner, ok := chainCtx.WarpSigner.(signer.Signer)
var ok bool
warpSigner, ok = chainCtx.WarpSigner.(luxWarp.Signer)
if !ok {
return fmt.Errorf("invalid warp signer type: %T", chainCtx.WarpSigner)
}
// Create a wrapper that implements WarpSigner
warpAdapter = &warpSignerAdapter{
signer: warpSigner,
nodeID: chainCtx.NodeID,
}
}
// Only create warp backend if we have a signer
if warpAdapter != nil {
if warpSigner != nil {
vm.warpBackend, err = warp.NewBackend(
chainCtx.NetworkID, // Use NetworkID from consensus Context
chainCtx.ChainID,
warpAdapter,
warpSigner,
&warpBlockClient{vm: vm}, // Wrapper that implements warp.BlockClient
validators.NewLockedValidatorReader(vm.validatorsManager, &vm.vmLock),
vm.warpDB,
@@ -754,15 +636,11 @@ func (vm *VM) initializeInternal(
// Add p2p warp message warpHandler
// Create adapter to convert our warp backend to lp118.Verifier
warpVerifier := &warpVerifierAdapter{backend: vm.warpBackend}
// Create lp118 signer adapter if we have a warp signer
var lp118Signer *lp118SignerAdapter
if warpAdapter != nil {
lp118Signer = &lp118SignerAdapter{signer: warpAdapter.signer}
}
warpHandler := lp118.NewCachedHandler(meteredCache, warpVerifier, lp118Signer)
// Use warp signer directly - luxWarp.Signer is compatible with lp118.Signer
warpHandler := lp118.NewCachedHandler(meteredCache, warpVerifier, warpSigner)
// Use built-in adapter to convert lp118.Handler to p2p.Handler
p2pHandler := lp118.NewHandlerAdapter(warpHandler)
vm.AddHandler(lp118.HandlerID, p2pHandler)
vm.Network.AddHandler(lp118.HandlerID, p2pHandler)
vm.setAppRequestHandlers()
@@ -1226,19 +1104,13 @@ func (vm *VM) onNormalOperationsStarted() error {
ethTxGossipMarshaller := GossipEthTxMarshaller{}
// P2PValidators might be nil in test environments
var p2pValidators *p2p.Validators
p2pValidatorsInterface := vm.P2PValidators()
p2pValidators := vm.Network.P2PValidators()
var ethTxGossipClient *p2p.Client
if p2pValidatorsInterface != nil {
var ok bool
p2pValidators, ok = p2pValidatorsInterface.(*p2p.Validators)
if !ok {
return fmt.Errorf("failed to get P2P validators")
}
ethTxGossipClient = vm.NewClient(TxGossipHandlerID, p2p.WithValidatorSampling(p2pValidators))
if p2pValidators != nil {
ethTxGossipClient = vm.Network.NewClient(TxGossipHandlerID, p2p.WithValidatorSampling(p2pValidators))
} else {
// In test mode, use a client without validator sampling
ethTxGossipClient = vm.NewClient(TxGossipHandlerID)
ethTxGossipClient = vm.Network.NewClient(TxGossipHandlerID)
}
ethTxGossipMetrics, err := gossip.NewMetrics(vm.sdkMetrics, ethTxGossipNamespace)
if err != nil {
@@ -1265,9 +1137,8 @@ func (vm *VM) onNormalOperationsStarted() error {
}
ethTxPushGossiper := vm.ethTxPushGossiper.Get()
if ethTxPushGossiper == nil && p2pValidatorsInterface != nil {
if ethTxPushGossiper == nil && p2pValidators != nil {
// Only create push gossiper if we have P2P validators
p2pValidators, _ := p2pValidatorsInterface.(*p2p.Validators)
ethTxPushGossiper, err = gossip.NewPushGossiper[*GossipEthTx](
ethTxGossipMarshaller,
ethTxPool,
@@ -1313,7 +1184,7 @@ func (vm *VM) onNormalOperationsStarted() error {
vm.ethTxGossipHandler = handler
}
if err := vm.AddHandler(TxGossipHandlerID, vm.ethTxGossipHandler); err != nil {
if err := vm.Network.AddHandler(TxGossipHandlerID, vm.ethTxGossipHandler); err != nil {
return fmt.Errorf("failed to add eth tx gossip handler: %w", err)
}
@@ -1378,7 +1249,7 @@ func (vm *VM) setAppRequestHandlers() {
)
networkHandler := newNetworkHandler(vm.blockChain, vm.chaindb, evmTrieDB, vm.warpBackend, vm.networkCodec)
vm.SetRequestHandler(networkHandler)
vm.Network.SetRequestHandler(networkHandler)
}
func (vm *VM) WaitForEvent(ctx context.Context) (interface{}, error) {
@@ -1690,7 +1561,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) {
}
if vm.config.WarpAPIEnabled {
warpSDKClient := vm.NewClient(lp118.HandlerID)
warpSDKClient := vm.Network.NewClient(lp118.HandlerID)
// lp118.NewSignatureAggregator expects a node/utils/logging.Logger
// For now, pass nil as the logger is optional
signatureAggregator := lp118.NewSignatureAggregator(nil, warpSDKClient)
@@ -1903,7 +1774,7 @@ func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error {
}
// AppRequestFailed implements the VM interface
func (vm *VM) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *nodeCommonEng.AppError) error {
func (vm *VM) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *commonEng.AppError) error {
// The Network interface doesn't expose AppRequestFailed directly
// We need to handle this at the VM level by logging the error
log.Debug("AppRequestFailed", "nodeID", nodeID, "requestID", requestID, "error", appErr)
@@ -1912,7 +1783,7 @@ func (vm *VM) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID
}
// CrossChainAppRequestFailed implements the VM interface
func (vm *VM) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *nodeCommonEng.AppError) error {
func (vm *VM) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *commonEng.AppError) error {
// Cross-chain app requests are not currently supported
// Just log and return nil to satisfy the interface
log.Debug("CrossChainAppRequestFailed called", "chainID", chainID, "requestID", requestID, "error", appErr.Message)
+6 -6
View File
@@ -100,7 +100,7 @@ func TestSendWarpMessage(t *testing.T) {
require.NoError(t, err)
unsignedWarpMessage, err := luxWarp.NewUnsignedMessage(
context.GetNetworkID(defaultConsensusCtx),
blockchainID[:],
blockchainID,
sendWarpMessageAddressedPayload.Bytes(),
)
require.NoError(t, err)
@@ -192,7 +192,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) {
packagedPayloadBytes,
)
require.NoError(t, err)
unsignedWarpMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID[:], addressedPayload.Bytes())
unsignedWarpMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID, addressedPayload.Bytes())
require.NoError(t, err)
warpMessage, err := luxWarp.NewMessage(unsignedWarpMsg, &luxWarp.BitSetSignature{}) // Create message with empty signature for testing
require.NoError(t, err)
@@ -398,7 +398,7 @@ func TestGetVerifiedWarpMessage(t *testing.T) {
Caller: callerAddr,
InputFn: func(t testing.TB) []byte { return getVerifiedWarpMsg },
Predicates: func() [][]byte {
unsignedMessage, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID[:], []byte{1, 2, 3}) // Invalid addressed payload
unsignedMessage, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID, []byte{1, 2, 3}) // Invalid addressed payload
require.NoError(t, err)
warpMessage, err := luxWarp.NewMessage(unsignedMessage, &luxWarp.BitSetSignature{})
require.NoError(t, err)
@@ -456,7 +456,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) {
blockHash := ids.GenerateTestID()
blockHashPayload, err := payload.NewHash(blockHash[:])
require.NoError(t, err)
unsignedWarpMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID[:], blockHashPayload.Bytes())
unsignedWarpMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID, blockHashPayload.Bytes())
require.NoError(t, err)
warpMessage, err := luxWarp.NewMessage(unsignedWarpMsg, &luxWarp.BitSetSignature{}) // Create message with empty signature for testing
require.NoError(t, err)
@@ -659,7 +659,7 @@ func TestGetVerifiedWarpBlockHash(t *testing.T) {
Caller: callerAddr,
InputFn: func(t testing.TB) []byte { return getVerifiedWarpBlockHash },
Predicates: func() [][]byte {
unsignedMessage, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID[:], []byte{1, 2, 3}) // Invalid block hash payload
unsignedMessage, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID, []byte{1, 2, 3}) // Invalid block hash payload
require.NoError(t, err)
warpMessage, err := luxWarp.NewMessage(unsignedMessage, &luxWarp.BitSetSignature{})
require.NoError(t, err)
@@ -724,7 +724,7 @@ func TestPackEvents(t *testing.T) {
unsignedWarpMessage, err := luxWarp.NewUnsignedMessage(
networkID,
sourceChainID[:],
sourceChainID,
addressedPayload.Bytes(),
)
require.NoError(t, err)
+5 -5
View File
@@ -86,7 +86,7 @@ func init() {
panic(err)
}
addressedPayloadBytes = addressedPayload.Bytes()
unsignedMsg, err = luxWarp.NewUnsignedMessage(constants.UnitTestID, sourceChainID[:], addressedPayload.Bytes())
unsignedMsg, err = luxWarp.NewUnsignedMessage(constants.UnitTestID, sourceChainID, addressedPayload.Bytes())
if err != nil {
panic(err)
}
@@ -136,7 +136,7 @@ func newTestValidator() *testValidator {
PublicKey: cryptoPK, // warp now uses crypto/bls directly
PublicKeyBytes: cryptoPKBytes,
Weight: 3,
NodeID: nodeID[:],
NodeID: nodeID,
},
}
}
@@ -305,7 +305,7 @@ func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigner
cChainID := ids.GenerateTestID()
addressedCall, err := payload.NewAddressedCall(agoUtils.RandomBytes(20), agoUtils.RandomBytes(100))
require.NoError(err)
unsignedMsg, err := luxWarp.NewUnsignedMessage(constants.UnitTestID, cChainID[:], addressedCall.Bytes())
unsignedMsg, err := luxWarp.NewUnsignedMessage(constants.UnitTestID, cChainID, addressedCall.Bytes())
require.NoError(err)
getValidatorsOutput := make(map[ids.NodeID]*validators.GetValidatorOutput)
@@ -471,7 +471,7 @@ func TestInvalidAddressedPayload(t *testing.T) {
}
copy(warpSignature.Signature[:], bls.SignatureToBytes(aggregateSignature))
// Create an unsigned message with an invalid addressed payload
unsignedMsg, err := luxWarp.NewUnsignedMessage(constants.UnitTestID, sourceChainID[:], []byte{1, 2, 3})
unsignedMsg, err := luxWarp.NewUnsignedMessage(constants.UnitTestID, sourceChainID, []byte{1, 2, 3})
require.NoError(t, err)
warpMsg := &luxWarp.Message{
UnsignedMessage: unsignedMsg,
@@ -501,7 +501,7 @@ func TestInvalidBitSet(t *testing.T) {
require.NoError(t, err)
unsignedMsg, err := luxWarp.NewUnsignedMessage(
constants.UnitTestID,
sourceChainID[:],
sourceChainID,
addressedCall.Bytes(),
)
require.NoError(t, err)
+1 -1
View File
@@ -269,7 +269,7 @@ func TestGetBlocks(t *testing.T) {
return responseBytes
},
expectedErr: "failed to unmarshal response: rlp: expected input list",
expectedErr: "rlp: expected input list",
},
"incorrect starting point": {
request: message.BlockRequest{
+7 -7
View File
@@ -194,17 +194,17 @@ func (g *LocalSignatureGetter) GetSignature(ctx context.Context, nodeID ids.Node
// NetworkSignatureGetter implements SignatureGetter by fetching from network peers
type NetworkSignatureGetter struct {
client AppRequestClient
client RequestClient
}
// AppRequestClient sends app requests to peers
type AppRequestClient interface {
// SendAppRequest sends a request to a peer and waits for response
SendAppRequest(ctx context.Context, nodeID ids.NodeID, request []byte) ([]byte, error)
// RequestClient sends requests to peers
type RequestClient interface {
// SendRequest sends a request to a peer and waits for response
SendRequest(ctx context.Context, nodeID ids.NodeID, request []byte) ([]byte, error)
}
// NewNetworkSignatureGetter creates a signature getter that fetches from network
func NewNetworkSignatureGetter(client AppRequestClient) *NetworkSignatureGetter {
func NewNetworkSignatureGetter(client RequestClient) *NetworkSignatureGetter {
return &NetworkSignatureGetter{client: client}
}
@@ -214,7 +214,7 @@ func (g *NetworkSignatureGetter) GetSignature(ctx context.Context, nodeID ids.No
request := unsignedMessage.Bytes()
// Send request to peer
response, err := g.client.SendAppRequest(ctx, nodeID, request)
response, err := g.client.SendRequest(ctx, nodeID, request)
if err != nil {
return nil, fmt.Errorf("failed to get signature from %s: %w", nodeID, err)
}
+5 -5
View File
@@ -88,7 +88,7 @@ func TestSignatureAggregator_AggregateSignatures(t *testing.T) {
sourceChainID := ids.GenerateTestID()
payload := []byte("test payload")
unsignedMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID[:], payload)
unsignedMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID, payload)
require.NoError(err)
// Test successful aggregation with 67% quorum
@@ -149,7 +149,7 @@ func TestSignatureAggregator_InsufficientQuorum(t *testing.T) {
sourceChainID := ids.GenerateTestID()
payload := []byte("test payload")
unsignedMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID[:], payload)
unsignedMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID, payload)
require.NoError(err)
// Try to aggregate with 67% quorum - should fail (only 1/3 validators available)
@@ -174,7 +174,7 @@ func TestSignatureAggregator_NoValidators(t *testing.T) {
sourceChainID := ids.GenerateTestID()
payload := []byte("test payload")
unsignedMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID[:], payload)
unsignedMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID, payload)
require.NoError(err)
// Empty validator set
@@ -218,7 +218,7 @@ func TestSignatureAggregator_AllValidatorsFail(t *testing.T) {
sourceChainID := ids.GenerateTestID()
payload := []byte("test payload")
unsignedMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID[:], payload)
unsignedMsg, err := luxWarp.NewUnsignedMessage(networkID, sourceChainID, payload)
require.NoError(err)
_, err = aggregator.AggregateSignatures(
@@ -240,7 +240,7 @@ func toWarpValidators(infos []*ValidatorInfo) []*luxWarp.Validator {
PublicKey: info.PublicKey,
PublicKeyBytes: bls.PublicKeyToCompressedBytes(info.PublicKey),
Weight: info.Weight,
NodeID: info.NodeID[:],
NodeID: info.NodeID,
}
}
return result
+3 -3
View File
@@ -58,7 +58,7 @@ type backend struct {
networkID uint32
sourceChainID ids.ID
db database.Database
warpSigner WarpSigner
warpSigner luxWarp.Signer
blockClient BlockClient
validatorReader interfaces.ValidatorReader
signatureCache cache.Cacher[ids.ID, []byte]
@@ -71,7 +71,7 @@ type backend struct {
func NewBackend(
networkID uint32,
sourceChainID ids.ID,
warpSigner WarpSigner,
warpSigner luxWarp.Signer,
blockClient BlockClient,
validatorReader interfaces.ValidatorReader,
db database.Database,
@@ -212,7 +212,7 @@ func (b *backend) signMessage(unsignedMessage *luxWarp.UnsignedMessage) ([]byte,
return nil, errors.New("warp signer not configured")
}
sig, err := b.warpSigner.Sign(unsignedMessage.Bytes())
sig, err := b.warpSigner.Sign(unsignedMessage)
if err != nil {
return nil, fmt.Errorf("failed to sign message: %w", err)
}
-26
View File
@@ -1,26 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package warp
import (
"github.com/luxfi/ids"
luxWarp "github.com/luxfi/warp"
)
// WarpSigner defines the interface for signing warp messages
type WarpSigner interface {
// Sign signs a message and returns the signature bytes
Sign(msg []byte) ([]byte, error)
// PublicKey returns the public key bytes
PublicKey() []byte
// NodeID returns the node ID
NodeID() ids.NodeID
}
// SignMessage signs a warp unsigned message
func SignMessage(signer WarpSigner, msg *luxWarp.UnsignedMessage) ([]byte, error) {
return signer.Sign(msg.Bytes())
}