mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
Reference in New Issue
Block a user