mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
The evm plugin turns cgo on for blst. Doing so also pulled in two optional native backends keyed on bare cgo, neither of which a build host carries by default, so `go build ./evm` needed a luxcpp and a luxgpu install or it did not compile: # github.com/luxfi/chains/evm/cevm # [pkg-config --cflags -- lux-cevm lux-crypto-secp256k1] Package lux-cevm was not found in the pkg-config search path. # github.com/luxfi/accel/ops/code code_cpu.go:102:10: fatal error: lux/gpu/hqc.h: No such file or directory Both are the same conflation: "a C compiler exists" is being written where "this C library is installed" is meant. evm/cevm gets the second predicate it was missing, lux_cevm_native, and accel v1.3.2 restores lux_hqc_native on its side. cevm_nocgo.go and cevm_secp256k1_nocgo.go already mirrored the full API for exactly this case, so the default build takes them and reports CPUSequential; -tags=lux_cevm_native links libevm/libevm-gpu unchanged. The tests move with the predicate they actually need. TestABIVersion locks the linked library's ABI constant, so it belongs beside TestLibraryABIVersion in the native file rather than in the untagged one, where it asserted 6 against the stub's documented 0 and failed every CGO_ENABLED=0 run. Verified: all twelve VMs build at their images.json cgo setting; go build ./... and go test ./evm/cevm/... green at CGO=0 and CGO=1; -tags=lux_cevm_native still selects cevm_cgo.go + cevm_secp256k1.go.
217 lines
6.3 KiB
Go
217 lines
6.3 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
//go:build cgo && lux_cevm_native
|
|
|
|
package cevm
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"math/big"
|
|
"testing"
|
|
|
|
luxcrypto "github.com/luxfi/crypto"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/core/types"
|
|
)
|
|
|
|
// TestBatchRecoverSenders_ParityWithSequential is the consensus-critical
|
|
// gate: for any batch of signed transactions, BatchRecoverSenders MUST
|
|
// produce byte-identical results to per-tx types.Sender. A divergence
|
|
// here is a kernel bug that would corrupt sender derivation in the
|
|
// parallel BlockExecutor.
|
|
//
|
|
// Coverage:
|
|
// - LegacyTxType (EIP-155 protected, chain id 96369)
|
|
// - DynamicFeeTxType (EIP-1559)
|
|
// - AccessListTxType (EIP-2930)
|
|
// - 64 signers across 64 txs to exercise the batch path proper.
|
|
func TestBatchRecoverSenders_ParityWithSequential(t *testing.T) {
|
|
const N = 64
|
|
chainID := big.NewInt(96369) // Lux mainnet C-Chain
|
|
signer := types.NewCancunSigner(chainID)
|
|
|
|
// Generate N independent keys + sign one tx per key, alternating tx types.
|
|
keys := make([]*ecdsa.PrivateKey, N)
|
|
txs := make(types.Transactions, N)
|
|
want := make([]common.Address, N)
|
|
for i := 0; i < N; i++ {
|
|
k, err := luxcrypto.GenerateKey()
|
|
if err != nil {
|
|
t.Fatalf("GenerateKey[%d]: %v", i, err)
|
|
}
|
|
keys[i] = k
|
|
// luxcrypto.PubkeyToAddress returns its own common.Address type; the
|
|
// 20-byte payload matches geth's. Convert by raw bytes.
|
|
la := luxcrypto.PubkeyToAddress(k.PublicKey)
|
|
want[i] = common.BytesToAddress(la[:])
|
|
|
|
var inner types.TxData
|
|
switch i % 3 {
|
|
case 0:
|
|
// LegacyTxType (EIP-155 protected after signing).
|
|
inner = &types.LegacyTx{
|
|
Nonce: uint64(i),
|
|
GasPrice: big.NewInt(1_000_000_000),
|
|
Gas: 21_000,
|
|
To: &common.Address{0xde, 0xad},
|
|
Value: big.NewInt(int64(i + 1)),
|
|
Data: nil,
|
|
}
|
|
case 1:
|
|
// DynamicFeeTxType.
|
|
inner = &types.DynamicFeeTx{
|
|
ChainID: chainID,
|
|
Nonce: uint64(i),
|
|
GasTipCap: big.NewInt(1_000_000_000),
|
|
GasFeeCap: big.NewInt(2_000_000_000),
|
|
Gas: 21_000,
|
|
To: &common.Address{0xbe, 0xef},
|
|
Value: big.NewInt(int64(i + 1)),
|
|
Data: nil,
|
|
}
|
|
case 2:
|
|
// AccessListTxType.
|
|
inner = &types.AccessListTx{
|
|
ChainID: chainID,
|
|
Nonce: uint64(i),
|
|
GasPrice: big.NewInt(1_000_000_000),
|
|
Gas: 21_000,
|
|
To: &common.Address{0xca, 0xfe},
|
|
Value: big.NewInt(int64(i + 1)),
|
|
Data: nil,
|
|
}
|
|
}
|
|
tx, err := types.SignNewTx(k, signer, inner)
|
|
if err != nil {
|
|
t.Fatalf("SignNewTx[%d]: %v", i, err)
|
|
}
|
|
// Drop the cached sender so the parity test exercises real recovery
|
|
// on the sequential reference path.
|
|
txs[i] = tx
|
|
}
|
|
|
|
// Reference: per-tx types.Sender. We freshly recover here (no cache yet).
|
|
got := make([]common.Address, N)
|
|
for i, tx := range txs {
|
|
// Re-decode the signed tx so the from-cache is empty.
|
|
raw, err := tx.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatalf("MarshalBinary[%d]: %v", i, err)
|
|
}
|
|
var fresh types.Transaction
|
|
if err := fresh.UnmarshalBinary(raw); err != nil {
|
|
t.Fatalf("UnmarshalBinary[%d]: %v", i, err)
|
|
}
|
|
txs[i] = &fresh
|
|
addr, err := types.Sender(signer, &fresh)
|
|
if err != nil {
|
|
t.Fatalf("types.Sender[%d]: %v", i, err)
|
|
}
|
|
got[i] = addr
|
|
if addr != want[i] {
|
|
t.Fatalf("types.Sender[%d] disagrees with PubkeyToAddress: got=%x want=%x",
|
|
i, addr, want[i])
|
|
}
|
|
}
|
|
|
|
// Re-marshal once more so the cache is fresh again before the batch call.
|
|
for i, tx := range txs {
|
|
raw, err := tx.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatalf("re-marshal[%d]: %v", i, err)
|
|
}
|
|
var fresh types.Transaction
|
|
if err := fresh.UnmarshalBinary(raw); err != nil {
|
|
t.Fatalf("re-unmarshal[%d]: %v", i, err)
|
|
}
|
|
txs[i] = &fresh
|
|
}
|
|
|
|
// Subject: BatchRecoverSenders. Must match the reference position-for-
|
|
// position with no errors.
|
|
batch, err := BatchRecoverSenders(txs, signer)
|
|
if err != nil {
|
|
t.Fatalf("BatchRecoverSenders: %v", err)
|
|
}
|
|
if len(batch) != N {
|
|
t.Fatalf("len(batch)=%d, want %d", len(batch), N)
|
|
}
|
|
for i := 0; i < N; i++ {
|
|
if batch[i] != got[i] {
|
|
t.Errorf("tx[%d]: batch=%x sequential=%x (mismatch)", i, batch[i], got[i])
|
|
}
|
|
}
|
|
|
|
// Cache check: a subsequent types.Sender call on a tx the batch
|
|
// recovered should hit the cache (same address, no error). The cache
|
|
// is the exact reason we wire this up: downstream code can keep
|
|
// calling types.Sender freely.
|
|
for i, tx := range txs {
|
|
addr, err := types.Sender(signer, tx)
|
|
if err != nil {
|
|
t.Errorf("post-batch Sender[%d]: %v", i, err)
|
|
}
|
|
if addr != batch[i] {
|
|
t.Errorf("cache disagrees post-batch on tx[%d]: cached=%x batch=%x",
|
|
i, addr, batch[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBatchRecoverSenders_Empty checks the empty fast-path: zero txs in,
|
|
// zero addresses out, no error, no cgo work.
|
|
func TestBatchRecoverSenders_Empty(t *testing.T) {
|
|
signer := types.NewCancunSigner(big.NewInt(96369))
|
|
out, err := BatchRecoverSenders(nil, signer)
|
|
if err != nil {
|
|
t.Fatalf("BatchRecoverSenders(nil): %v", err)
|
|
}
|
|
if len(out) != 0 {
|
|
t.Errorf("len(out)=%d, want 0", len(out))
|
|
}
|
|
}
|
|
|
|
// TestBatchRecoverSenders_SingleTx covers n=1 — the smallest non-empty
|
|
// batch. The ecrecover pipeline has been observed to take an n==0 vs n>=1
|
|
// branch; n=1 is the boundary case that hits the actual kernel.
|
|
func TestBatchRecoverSenders_SingleTx(t *testing.T) {
|
|
chainID := big.NewInt(96369)
|
|
signer := types.NewCancunSigner(chainID)
|
|
k, err := luxcrypto.GenerateKey()
|
|
if err != nil {
|
|
t.Fatalf("GenerateKey: %v", err)
|
|
}
|
|
la := luxcrypto.PubkeyToAddress(k.PublicKey)
|
|
want := common.BytesToAddress(la[:])
|
|
tx, err := types.SignNewTx(k, signer, &types.DynamicFeeTx{
|
|
ChainID: chainID,
|
|
Nonce: 0,
|
|
GasTipCap: big.NewInt(1),
|
|
GasFeeCap: big.NewInt(2),
|
|
Gas: 21_000,
|
|
To: &common.Address{0x42},
|
|
Value: big.NewInt(1),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SignNewTx: %v", err)
|
|
}
|
|
// Decode-roundtrip to clear the cache.
|
|
raw, _ := tx.MarshalBinary()
|
|
var fresh types.Transaction
|
|
if err := fresh.UnmarshalBinary(raw); err != nil {
|
|
t.Fatalf("UnmarshalBinary: %v", err)
|
|
}
|
|
|
|
out, err := BatchRecoverSenders(types.Transactions{&fresh}, signer)
|
|
if err != nil {
|
|
t.Fatalf("BatchRecoverSenders: %v", err)
|
|
}
|
|
if len(out) != 1 {
|
|
t.Fatalf("len(out)=%d want 1", len(out))
|
|
}
|
|
if out[0] != want {
|
|
t.Errorf("batch=%x want=%x", out[0], want)
|
|
}
|
|
}
|