forked from cerc-io/plugeth
This change adds support for blob-transaction in certain API-endpoints, e.g. eth_fillTransaction. A follow-up PR will add support for signing such transactions.
2056 lines
66 KiB
Go
2056 lines
66 KiB
Go
// Copyright 2023 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package ethapi
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum"
|
|
"github.com/ethereum/go-ethereum/accounts"
|
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/consensus"
|
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
"github.com/ethereum/go-ethereum/core"
|
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"github.com/ethereum/go-ethereum/core/state"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/core/vm"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/ethereum/go-ethereum/event"
|
|
"github.com/ethereum/go-ethereum/internal/blocktest"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
"github.com/holiman/uint256"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
func testTransactionMarshal(t *testing.T, tests []txData, config *params.ChainConfig) {
|
|
t.Parallel()
|
|
var (
|
|
signer = types.LatestSigner(config)
|
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
)
|
|
|
|
for i, tt := range tests {
|
|
var tx2 types.Transaction
|
|
tx, err := types.SignNewTx(key, signer, tt.Tx)
|
|
if err != nil {
|
|
t.Fatalf("test %d: signing failed: %v", i, err)
|
|
}
|
|
// Regular transaction
|
|
if data, err := json.Marshal(tx); err != nil {
|
|
t.Fatalf("test %d: marshalling failed; %v", i, err)
|
|
} else if err = tx2.UnmarshalJSON(data); err != nil {
|
|
t.Fatalf("test %d: sunmarshal failed: %v", i, err)
|
|
} else if want, have := tx.Hash(), tx2.Hash(); want != have {
|
|
t.Fatalf("test %d: stx changed, want %x have %x", i, want, have)
|
|
}
|
|
|
|
// rpcTransaction
|
|
rpcTx := newRPCTransaction(tx, common.Hash{}, 0, 0, 0, nil, config)
|
|
if data, err := json.Marshal(rpcTx); err != nil {
|
|
t.Fatalf("test %d: marshalling failed; %v", i, err)
|
|
} else if err = tx2.UnmarshalJSON(data); err != nil {
|
|
t.Fatalf("test %d: unmarshal failed: %v", i, err)
|
|
} else if want, have := tx.Hash(), tx2.Hash(); want != have {
|
|
t.Fatalf("test %d: tx changed, want %x have %x", i, want, have)
|
|
} else {
|
|
want, have := tt.Want, string(data)
|
|
require.JSONEqf(t, want, have, "test %d: rpc json not match, want %s have %s", i, want, have)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTransaction_RoundTripRpcJSON(t *testing.T) {
|
|
var (
|
|
config = params.AllEthashProtocolChanges
|
|
tests = allTransactionTypes(common.Address{0xde, 0xad}, config)
|
|
)
|
|
testTransactionMarshal(t, tests, config)
|
|
}
|
|
|
|
func TestTransactionBlobTx(t *testing.T) {
|
|
config := *params.TestChainConfig
|
|
config.ShanghaiTime = new(uint64)
|
|
config.CancunTime = new(uint64)
|
|
tests := allBlobTxs(common.Address{0xde, 0xad}, &config)
|
|
|
|
testTransactionMarshal(t, tests, &config)
|
|
}
|
|
|
|
type txData struct {
|
|
Tx types.TxData
|
|
Want string
|
|
}
|
|
|
|
func allTransactionTypes(addr common.Address, config *params.ChainConfig) []txData {
|
|
return []txData{
|
|
{
|
|
Tx: &types.LegacyTx{
|
|
Nonce: 5,
|
|
GasPrice: big.NewInt(6),
|
|
Gas: 7,
|
|
To: &addr,
|
|
Value: big.NewInt(8),
|
|
Data: []byte{0, 1, 2, 3, 4},
|
|
V: big.NewInt(9),
|
|
R: big.NewInt(10),
|
|
S: big.NewInt(11),
|
|
},
|
|
Want: `{
|
|
"blockHash": null,
|
|
"blockNumber": null,
|
|
"from": "0x71562b71999873db5b286df957af199ec94617f7",
|
|
"gas": "0x7",
|
|
"gasPrice": "0x6",
|
|
"hash": "0x5f3240454cd09a5d8b1c5d651eefae7a339262875bcd2d0e6676f3d989967008",
|
|
"input": "0x0001020304",
|
|
"nonce": "0x5",
|
|
"to": "0xdead000000000000000000000000000000000000",
|
|
"transactionIndex": null,
|
|
"value": "0x8",
|
|
"type": "0x0",
|
|
"chainId": "0x539",
|
|
"v": "0xa96",
|
|
"r": "0xbc85e96592b95f7160825d837abb407f009df9ebe8f1b9158a4b8dd093377f75",
|
|
"s": "0x1b55ea3af5574c536967b039ba6999ef6c89cf22fc04bcb296e0e8b0b9b576f5"
|
|
}`,
|
|
}, {
|
|
Tx: &types.LegacyTx{
|
|
Nonce: 5,
|
|
GasPrice: big.NewInt(6),
|
|
Gas: 7,
|
|
To: nil,
|
|
Value: big.NewInt(8),
|
|
Data: []byte{0, 1, 2, 3, 4},
|
|
V: big.NewInt(32),
|
|
R: big.NewInt(10),
|
|
S: big.NewInt(11),
|
|
},
|
|
Want: `{
|
|
"blockHash": null,
|
|
"blockNumber": null,
|
|
"from": "0x71562b71999873db5b286df957af199ec94617f7",
|
|
"gas": "0x7",
|
|
"gasPrice": "0x6",
|
|
"hash": "0x806e97f9d712b6cb7e781122001380a2837531b0fc1e5f5d78174ad4cb699873",
|
|
"input": "0x0001020304",
|
|
"nonce": "0x5",
|
|
"to": null,
|
|
"transactionIndex": null,
|
|
"value": "0x8",
|
|
"type": "0x0",
|
|
"chainId": "0x539",
|
|
"v": "0xa96",
|
|
"r": "0x9dc28b267b6ad4e4af6fe9289668f9305c2eb7a3241567860699e478af06835a",
|
|
"s": "0xa0b51a071aa9bed2cd70aedea859779dff039e3630ea38497d95202e9b1fec7"
|
|
}`,
|
|
},
|
|
{
|
|
Tx: &types.AccessListTx{
|
|
ChainID: config.ChainID,
|
|
Nonce: 5,
|
|
GasPrice: big.NewInt(6),
|
|
Gas: 7,
|
|
To: &addr,
|
|
Value: big.NewInt(8),
|
|
Data: []byte{0, 1, 2, 3, 4},
|
|
AccessList: types.AccessList{
|
|
types.AccessTuple{
|
|
Address: common.Address{0x2},
|
|
StorageKeys: []common.Hash{types.EmptyRootHash},
|
|
},
|
|
},
|
|
V: big.NewInt(32),
|
|
R: big.NewInt(10),
|
|
S: big.NewInt(11),
|
|
},
|
|
Want: `{
|
|
"blockHash": null,
|
|
"blockNumber": null,
|
|
"from": "0x71562b71999873db5b286df957af199ec94617f7",
|
|
"gas": "0x7",
|
|
"gasPrice": "0x6",
|
|
"hash": "0x121347468ee5fe0a29f02b49b4ffd1c8342bc4255146bb686cd07117f79e7129",
|
|
"input": "0x0001020304",
|
|
"nonce": "0x5",
|
|
"to": "0xdead000000000000000000000000000000000000",
|
|
"transactionIndex": null,
|
|
"value": "0x8",
|
|
"type": "0x1",
|
|
"accessList": [
|
|
{
|
|
"address": "0x0200000000000000000000000000000000000000",
|
|
"storageKeys": [
|
|
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
|
|
]
|
|
}
|
|
],
|
|
"chainId": "0x539",
|
|
"v": "0x0",
|
|
"r": "0xf372ad499239ae11d91d34c559ffc5dab4daffc0069e03afcabdcdf231a0c16b",
|
|
"s": "0x28573161d1f9472fa0fd4752533609e72f06414f7ab5588699a7141f65d2abf",
|
|
"yParity": "0x0"
|
|
}`,
|
|
}, {
|
|
Tx: &types.AccessListTx{
|
|
ChainID: config.ChainID,
|
|
Nonce: 5,
|
|
GasPrice: big.NewInt(6),
|
|
Gas: 7,
|
|
To: nil,
|
|
Value: big.NewInt(8),
|
|
Data: []byte{0, 1, 2, 3, 4},
|
|
AccessList: types.AccessList{
|
|
types.AccessTuple{
|
|
Address: common.Address{0x2},
|
|
StorageKeys: []common.Hash{types.EmptyRootHash},
|
|
},
|
|
},
|
|
V: big.NewInt(32),
|
|
R: big.NewInt(10),
|
|
S: big.NewInt(11),
|
|
},
|
|
Want: `{
|
|
"blockHash": null,
|
|
"blockNumber": null,
|
|
"from": "0x71562b71999873db5b286df957af199ec94617f7",
|
|
"gas": "0x7",
|
|
"gasPrice": "0x6",
|
|
"hash": "0x067c3baebede8027b0f828a9d933be545f7caaec623b00684ac0659726e2055b",
|
|
"input": "0x0001020304",
|
|
"nonce": "0x5",
|
|
"to": null,
|
|
"transactionIndex": null,
|
|
"value": "0x8",
|
|
"type": "0x1",
|
|
"accessList": [
|
|
{
|
|
"address": "0x0200000000000000000000000000000000000000",
|
|
"storageKeys": [
|
|
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
|
|
]
|
|
}
|
|
],
|
|
"chainId": "0x539",
|
|
"v": "0x1",
|
|
"r": "0x542981b5130d4613897fbab144796cb36d3cb3d7807d47d9c7f89ca7745b085c",
|
|
"s": "0x7425b9dd6c5deaa42e4ede35d0c4570c4624f68c28d812c10d806ffdf86ce63",
|
|
"yParity": "0x1"
|
|
}`,
|
|
}, {
|
|
Tx: &types.DynamicFeeTx{
|
|
ChainID: config.ChainID,
|
|
Nonce: 5,
|
|
GasTipCap: big.NewInt(6),
|
|
GasFeeCap: big.NewInt(9),
|
|
Gas: 7,
|
|
To: &addr,
|
|
Value: big.NewInt(8),
|
|
Data: []byte{0, 1, 2, 3, 4},
|
|
AccessList: types.AccessList{
|
|
types.AccessTuple{
|
|
Address: common.Address{0x2},
|
|
StorageKeys: []common.Hash{types.EmptyRootHash},
|
|
},
|
|
},
|
|
V: big.NewInt(32),
|
|
R: big.NewInt(10),
|
|
S: big.NewInt(11),
|
|
},
|
|
Want: `{
|
|
"blockHash": null,
|
|
"blockNumber": null,
|
|
"from": "0x71562b71999873db5b286df957af199ec94617f7",
|
|
"gas": "0x7",
|
|
"gasPrice": "0x9",
|
|
"maxFeePerGas": "0x9",
|
|
"maxPriorityFeePerGas": "0x6",
|
|
"hash": "0xb63e0b146b34c3e9cb7fbabb5b3c081254a7ded6f1b65324b5898cc0545d79ff",
|
|
"input": "0x0001020304",
|
|
"nonce": "0x5",
|
|
"to": "0xdead000000000000000000000000000000000000",
|
|
"transactionIndex": null,
|
|
"value": "0x8",
|
|
"type": "0x2",
|
|
"accessList": [
|
|
{
|
|
"address": "0x0200000000000000000000000000000000000000",
|
|
"storageKeys": [
|
|
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
|
|
]
|
|
}
|
|
],
|
|
"chainId": "0x539",
|
|
"v": "0x1",
|
|
"r": "0x3b167e05418a8932cd53d7578711fe1a76b9b96c48642402bb94978b7a107e80",
|
|
"s": "0x22f98a332d15ea2cc80386c1ebaa31b0afebfa79ebc7d039a1e0074418301fef",
|
|
"yParity": "0x1"
|
|
}`,
|
|
}, {
|
|
Tx: &types.DynamicFeeTx{
|
|
ChainID: config.ChainID,
|
|
Nonce: 5,
|
|
GasTipCap: big.NewInt(6),
|
|
GasFeeCap: big.NewInt(9),
|
|
Gas: 7,
|
|
To: nil,
|
|
Value: big.NewInt(8),
|
|
Data: []byte{0, 1, 2, 3, 4},
|
|
AccessList: types.AccessList{},
|
|
V: big.NewInt(32),
|
|
R: big.NewInt(10),
|
|
S: big.NewInt(11),
|
|
},
|
|
Want: `{
|
|
"blockHash": null,
|
|
"blockNumber": null,
|
|
"from": "0x71562b71999873db5b286df957af199ec94617f7",
|
|
"gas": "0x7",
|
|
"gasPrice": "0x9",
|
|
"maxFeePerGas": "0x9",
|
|
"maxPriorityFeePerGas": "0x6",
|
|
"hash": "0xcbab17ee031a9d5b5a09dff909f0a28aedb9b295ac0635d8710d11c7b806ec68",
|
|
"input": "0x0001020304",
|
|
"nonce": "0x5",
|
|
"to": null,
|
|
"transactionIndex": null,
|
|
"value": "0x8",
|
|
"type": "0x2",
|
|
"accessList": [],
|
|
"chainId": "0x539",
|
|
"v": "0x0",
|
|
"r": "0x6446b8a682db7e619fc6b4f6d1f708f6a17351a41c7fbd63665f469bc78b41b9",
|
|
"s": "0x7626abc15834f391a117c63450047309dbf84c5ce3e8e609b607062641e2de43",
|
|
"yParity": "0x0"
|
|
}`,
|
|
},
|
|
}
|
|
}
|
|
|
|
func allBlobTxs(addr common.Address, config *params.ChainConfig) []txData {
|
|
return []txData{
|
|
{
|
|
Tx: &types.BlobTx{
|
|
Nonce: 6,
|
|
GasTipCap: uint256.NewInt(1),
|
|
GasFeeCap: uint256.NewInt(5),
|
|
Gas: 6,
|
|
To: addr,
|
|
BlobFeeCap: uint256.NewInt(1),
|
|
BlobHashes: []common.Hash{{1}},
|
|
Value: new(uint256.Int),
|
|
V: uint256.NewInt(32),
|
|
R: uint256.NewInt(10),
|
|
S: uint256.NewInt(11),
|
|
},
|
|
Want: `{
|
|
"blockHash": null,
|
|
"blockNumber": null,
|
|
"from": "0x71562b71999873db5b286df957af199ec94617f7",
|
|
"gas": "0x6",
|
|
"gasPrice": "0x5",
|
|
"maxFeePerGas": "0x5",
|
|
"maxPriorityFeePerGas": "0x1",
|
|
"maxFeePerBlobGas": "0x1",
|
|
"hash": "0x1f2b59a20e61efc615ad0cbe936379d6bbea6f938aafaf35eb1da05d8e7f46a3",
|
|
"input": "0x",
|
|
"nonce": "0x6",
|
|
"to": "0xdead000000000000000000000000000000000000",
|
|
"transactionIndex": null,
|
|
"value": "0x0",
|
|
"type": "0x3",
|
|
"accessList": [],
|
|
"chainId": "0x1",
|
|
"blobVersionedHashes": [
|
|
"0x0100000000000000000000000000000000000000000000000000000000000000"
|
|
],
|
|
"v": "0x0",
|
|
"r": "0x618be8908e0e5320f8f3b48042a079fe5a335ebd4ed1422a7d2207cd45d872bc",
|
|
"s": "0x27b2bc6c80e849a8e8b764d4549d8c2efac3441e73cf37054eb0a9b9f8e89b27",
|
|
"yParity": "0x0"
|
|
}`,
|
|
},
|
|
}
|
|
}
|
|
|
|
func newTestAccountManager(t *testing.T) (*accounts.Manager, accounts.Account) {
|
|
var (
|
|
dir = t.TempDir()
|
|
am = accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: true})
|
|
b = keystore.NewKeyStore(dir, 2, 1)
|
|
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
)
|
|
acc, err := b.ImportECDSA(testKey, "")
|
|
if err != nil {
|
|
t.Fatalf("failed to create test account: %v", err)
|
|
}
|
|
if err := b.Unlock(acc, ""); err != nil {
|
|
t.Fatalf("failed to unlock account: %v\n", err)
|
|
}
|
|
am.AddBackend(b)
|
|
return am, acc
|
|
}
|
|
|
|
type testBackend struct {
|
|
db ethdb.Database
|
|
chain *core.BlockChain
|
|
pending *types.Block
|
|
accman *accounts.Manager
|
|
acc accounts.Account
|
|
}
|
|
|
|
func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend {
|
|
var (
|
|
cacheConfig = &core.CacheConfig{
|
|
TrieCleanLimit: 256,
|
|
TrieDirtyLimit: 256,
|
|
TrieTimeLimit: 5 * time.Minute,
|
|
SnapshotLimit: 0,
|
|
TrieDirtyDisabled: true, // Archive mode
|
|
}
|
|
)
|
|
accman, acc := newTestAccountManager(t)
|
|
gspec.Alloc[acc.Address] = core.GenesisAccount{Balance: big.NewInt(params.Ether)}
|
|
// Generate blocks for testing
|
|
db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator)
|
|
txlookupLimit := uint64(0)
|
|
chain, err := core.NewBlockChain(db, cacheConfig, gspec, nil, engine, vm.Config{}, nil, &txlookupLimit)
|
|
if err != nil {
|
|
t.Fatalf("failed to create tester chain: %v", err)
|
|
}
|
|
if n, err := chain.InsertChain(blocks); err != nil {
|
|
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
|
}
|
|
|
|
backend := &testBackend{db: db, chain: chain, accman: accman, acc: acc}
|
|
return backend
|
|
}
|
|
|
|
func (b *testBackend) setPendingBlock(block *types.Block) {
|
|
b.pending = block
|
|
}
|
|
|
|
func (b testBackend) SyncProgress() ethereum.SyncProgress { return ethereum.SyncProgress{} }
|
|
func (b testBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
|
|
return big.NewInt(0), nil
|
|
}
|
|
func (b testBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) {
|
|
return nil, nil, nil, nil, nil
|
|
}
|
|
func (b testBackend) ChainDb() ethdb.Database { return b.db }
|
|
func (b testBackend) AccountManager() *accounts.Manager { return b.accman }
|
|
func (b testBackend) ExtRPCEnabled() bool { return false }
|
|
func (b testBackend) RPCGasCap() uint64 { return 10000000 }
|
|
func (b testBackend) RPCEVMTimeout() time.Duration { return time.Second }
|
|
func (b testBackend) RPCTxFeeCap() float64 { return 0 }
|
|
func (b testBackend) UnprotectedAllowed() bool { return false }
|
|
func (b testBackend) SetHead(number uint64) {}
|
|
func (b testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
|
if number == rpc.LatestBlockNumber {
|
|
return b.chain.CurrentBlock(), nil
|
|
}
|
|
if number == rpc.PendingBlockNumber && b.pending != nil {
|
|
return b.pending.Header(), nil
|
|
}
|
|
return b.chain.GetHeaderByNumber(uint64(number)), nil
|
|
}
|
|
func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
|
return b.chain.GetHeaderByHash(hash), nil
|
|
}
|
|
func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
|
|
if blockNr, ok := blockNrOrHash.Number(); ok {
|
|
return b.HeaderByNumber(ctx, blockNr)
|
|
}
|
|
if blockHash, ok := blockNrOrHash.Hash(); ok {
|
|
return b.HeaderByHash(ctx, blockHash)
|
|
}
|
|
panic("unknown type rpc.BlockNumberOrHash")
|
|
}
|
|
func (b testBackend) CurrentHeader() *types.Header { return b.chain.CurrentBlock() }
|
|
func (b testBackend) CurrentBlock() *types.Header { return b.chain.CurrentBlock() }
|
|
func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
|
|
if number == rpc.LatestBlockNumber {
|
|
head := b.chain.CurrentBlock()
|
|
return b.chain.GetBlock(head.Hash(), head.Number.Uint64()), nil
|
|
}
|
|
if number == rpc.PendingBlockNumber {
|
|
return b.pending, nil
|
|
}
|
|
return b.chain.GetBlockByNumber(uint64(number)), nil
|
|
}
|
|
func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
|
return b.chain.GetBlockByHash(hash), nil
|
|
}
|
|
func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
|
|
if blockNr, ok := blockNrOrHash.Number(); ok {
|
|
return b.BlockByNumber(ctx, blockNr)
|
|
}
|
|
if blockHash, ok := blockNrOrHash.Hash(); ok {
|
|
return b.BlockByHash(ctx, blockHash)
|
|
}
|
|
panic("unknown type rpc.BlockNumberOrHash")
|
|
}
|
|
func (b testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
|
|
return b.chain.GetBlock(hash, uint64(number.Int64())).Body(), nil
|
|
}
|
|
func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
|
|
if number == rpc.PendingBlockNumber {
|
|
panic("pending state not implemented")
|
|
}
|
|
header, err := b.HeaderByNumber(ctx, number)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if header == nil {
|
|
return nil, nil, errors.New("header not found")
|
|
}
|
|
stateDb, err := b.chain.StateAt(header.Root)
|
|
return stateDb, header, err
|
|
}
|
|
func (b testBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
|
|
if blockNr, ok := blockNrOrHash.Number(); ok {
|
|
return b.StateAndHeaderByNumber(ctx, blockNr)
|
|
}
|
|
panic("only implemented for number")
|
|
}
|
|
func (b testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { panic("implement me") }
|
|
func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
|
header, err := b.HeaderByHash(ctx, hash)
|
|
if header == nil || err != nil {
|
|
return nil, err
|
|
}
|
|
receipts := rawdb.ReadReceipts(b.db, hash, header.Number.Uint64(), header.Time, b.chain.Config())
|
|
return receipts, nil
|
|
}
|
|
func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
|
|
if b.pending != nil && hash == b.pending.Hash() {
|
|
return nil
|
|
}
|
|
return big.NewInt(1)
|
|
}
|
|
func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) *vm.EVM {
|
|
if vmConfig == nil {
|
|
vmConfig = b.chain.GetVMConfig()
|
|
}
|
|
txContext := core.NewEVMTxContext(msg)
|
|
context := core.NewEVMBlockContext(header, b.chain, nil)
|
|
if blockContext != nil {
|
|
context = *blockContext
|
|
}
|
|
return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig)
|
|
}
|
|
func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
|
|
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash)
|
|
return true, tx, blockHash, blockNumber, index, nil
|
|
}
|
|
func (b testBackend) GetPoolTransactions() (types.Transactions, error) { panic("implement me") }
|
|
func (b testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { panic("implement me") }
|
|
func (b testBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
|
|
return 0, nil
|
|
}
|
|
func (b testBackend) Stats() (pending int, queued int) { panic("implement me") }
|
|
func (b testBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) SubscribeNewTxsEvent(events chan<- core.NewTxsEvent) event.Subscription {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() }
|
|
func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() }
|
|
func (b testBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
panic("implement me")
|
|
}
|
|
func (b testBackend) BloomStatus() (uint64, uint64) { panic("implement me") }
|
|
func (b testBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
|
|
panic("implement me")
|
|
}
|
|
|
|
func TestEstimateGas(t *testing.T) {
|
|
t.Parallel()
|
|
// Initialize test accounts
|
|
var (
|
|
accounts = newAccounts(2)
|
|
genesis = &core.Genesis{
|
|
Config: params.MergedTestChainConfig,
|
|
Alloc: core.GenesisAlloc{
|
|
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
|
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
|
},
|
|
}
|
|
genBlocks = 10
|
|
signer = types.HomesteadSigner{}
|
|
randomAccounts = newAccounts(2)
|
|
)
|
|
api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
|
// Transfer from account[0] to account[1]
|
|
// value: 1000 wei
|
|
// fee: 0 wei
|
|
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &accounts[1].addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, accounts[0].key)
|
|
b.AddTx(tx)
|
|
b.SetPoS()
|
|
}))
|
|
var testSuite = []struct {
|
|
blockNumber rpc.BlockNumber
|
|
call TransactionArgs
|
|
overrides StateOverride
|
|
expectErr error
|
|
want uint64
|
|
}{
|
|
// simple transfer on latest block
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &accounts[0].addr,
|
|
To: &accounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
|
},
|
|
expectErr: nil,
|
|
want: 21000,
|
|
},
|
|
// simple transfer with insufficient funds on latest block
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &randomAccounts[0].addr,
|
|
To: &accounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
|
},
|
|
expectErr: core.ErrInsufficientFunds,
|
|
want: 21000,
|
|
},
|
|
// empty create
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{},
|
|
expectErr: nil,
|
|
want: 53000,
|
|
},
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{},
|
|
overrides: StateOverride{
|
|
randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
|
|
},
|
|
expectErr: nil,
|
|
want: 53000,
|
|
},
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &randomAccounts[0].addr,
|
|
To: &randomAccounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
|
},
|
|
overrides: StateOverride{
|
|
randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(0))},
|
|
},
|
|
expectErr: core.ErrInsufficientFunds,
|
|
},
|
|
// Test for a bug where the gas price was set to zero but the basefee non-zero
|
|
//
|
|
// contract BasefeeChecker {
|
|
// constructor() {
|
|
// require(tx.gasprice >= block.basefee);
|
|
// if (tx.gasprice > 0) {
|
|
// require(block.basefee > 0);
|
|
// }
|
|
// }
|
|
//}
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &accounts[0].addr,
|
|
Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"),
|
|
GasPrice: (*hexutil.Big)(big.NewInt(1_000_000_000)), // Legacy as pricing
|
|
},
|
|
expectErr: nil,
|
|
want: 67617,
|
|
},
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &accounts[0].addr,
|
|
Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"),
|
|
MaxFeePerGas: (*hexutil.Big)(big.NewInt(1_000_000_000)), // 1559 gas pricing
|
|
},
|
|
expectErr: nil,
|
|
want: 67617,
|
|
},
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &accounts[0].addr,
|
|
Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"),
|
|
GasPrice: nil, // No legacy gas pricing
|
|
MaxFeePerGas: nil, // No 1559 gas pricing
|
|
},
|
|
expectErr: nil,
|
|
want: 67595,
|
|
},
|
|
// Blobs should have no effect on gas estimate
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &accounts[0].addr,
|
|
To: &accounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
BlobHashes: []common.Hash{common.Hash{0x01, 0x22}},
|
|
BlobFeeCap: (*hexutil.Big)(big.NewInt(1)),
|
|
},
|
|
want: 21000,
|
|
},
|
|
}
|
|
for i, tc := range testSuite {
|
|
result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides)
|
|
if tc.expectErr != nil {
|
|
if err == nil {
|
|
t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr)
|
|
continue
|
|
}
|
|
if !errors.Is(err, tc.expectErr) {
|
|
t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err)
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("test %d: want no error, have %v", i, err)
|
|
continue
|
|
}
|
|
if float64(result) > float64(tc.want)*(1+estimateGasErrorRatio) {
|
|
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCall(t *testing.T) {
|
|
t.Parallel()
|
|
// Initialize test accounts
|
|
var (
|
|
accounts = newAccounts(3)
|
|
genesis = &core.Genesis{
|
|
Config: params.MergedTestChainConfig,
|
|
Alloc: core.GenesisAlloc{
|
|
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
|
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
|
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
|
|
},
|
|
}
|
|
genBlocks = 10
|
|
signer = types.HomesteadSigner{}
|
|
)
|
|
api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
|
// Transfer from account[0] to account[1]
|
|
// value: 1000 wei
|
|
// fee: 0 wei
|
|
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &accounts[1].addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, accounts[0].key)
|
|
b.AddTx(tx)
|
|
b.SetPoS()
|
|
}))
|
|
randomAccounts := newAccounts(3)
|
|
var testSuite = []struct {
|
|
blockNumber rpc.BlockNumber
|
|
overrides StateOverride
|
|
call TransactionArgs
|
|
blockOverrides BlockOverrides
|
|
expectErr error
|
|
want string
|
|
}{
|
|
// transfer on genesis
|
|
{
|
|
blockNumber: rpc.BlockNumber(0),
|
|
call: TransactionArgs{
|
|
From: &accounts[0].addr,
|
|
To: &accounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
|
},
|
|
expectErr: nil,
|
|
want: "0x",
|
|
},
|
|
// transfer on the head
|
|
{
|
|
blockNumber: rpc.BlockNumber(genBlocks),
|
|
call: TransactionArgs{
|
|
From: &accounts[0].addr,
|
|
To: &accounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
|
},
|
|
expectErr: nil,
|
|
want: "0x",
|
|
},
|
|
// transfer on a non-existent block, error expects
|
|
{
|
|
blockNumber: rpc.BlockNumber(genBlocks + 1),
|
|
call: TransactionArgs{
|
|
From: &accounts[0].addr,
|
|
To: &accounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
|
},
|
|
expectErr: errors.New("header not found"),
|
|
},
|
|
// transfer on the latest block
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &accounts[0].addr,
|
|
To: &accounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
|
},
|
|
expectErr: nil,
|
|
want: "0x",
|
|
},
|
|
// Call which can only succeed if state is state overridden
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &randomAccounts[0].addr,
|
|
To: &randomAccounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
|
},
|
|
overrides: StateOverride{
|
|
randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
|
|
},
|
|
want: "0x",
|
|
},
|
|
// Invalid call without state overriding
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &randomAccounts[0].addr,
|
|
To: &randomAccounts[1].addr,
|
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
|
},
|
|
expectErr: core.ErrInsufficientFunds,
|
|
},
|
|
// Successful simple contract call
|
|
//
|
|
// // SPDX-License-Identifier: GPL-3.0
|
|
//
|
|
// pragma solidity >=0.7.0 <0.8.0;
|
|
//
|
|
// /**
|
|
// * @title Storage
|
|
// * @dev Store & retrieve value in a variable
|
|
// */
|
|
// contract Storage {
|
|
// uint256 public number;
|
|
// constructor() {
|
|
// number = block.number;
|
|
// }
|
|
// }
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &randomAccounts[0].addr,
|
|
To: &randomAccounts[2].addr,
|
|
Data: hex2Bytes("8381f58a"), // call number()
|
|
},
|
|
overrides: StateOverride{
|
|
randomAccounts[2].addr: OverrideAccount{
|
|
Code: hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033"),
|
|
StateDiff: &map[common.Hash]common.Hash{{}: common.BigToHash(big.NewInt(123))},
|
|
},
|
|
},
|
|
want: "0x000000000000000000000000000000000000000000000000000000000000007b",
|
|
},
|
|
// Block overrides should work
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &accounts[1].addr,
|
|
Input: &hexutil.Bytes{
|
|
0x43, // NUMBER
|
|
0x60, 0x00, 0x52, // MSTORE offset 0
|
|
0x60, 0x20, 0x60, 0x00, 0xf3,
|
|
},
|
|
},
|
|
blockOverrides: BlockOverrides{Number: (*hexutil.Big)(big.NewInt(11))},
|
|
want: "0x000000000000000000000000000000000000000000000000000000000000000b",
|
|
},
|
|
// Invalid blob tx
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &accounts[1].addr,
|
|
Input: &hexutil.Bytes{0x00},
|
|
BlobHashes: []common.Hash{},
|
|
},
|
|
expectErr: core.ErrBlobTxCreate,
|
|
},
|
|
// BLOBHASH opcode
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
call: TransactionArgs{
|
|
From: &accounts[1].addr,
|
|
To: &randomAccounts[2].addr,
|
|
BlobHashes: []common.Hash{common.Hash{0x01, 0x22}},
|
|
BlobFeeCap: (*hexutil.Big)(big.NewInt(1)),
|
|
},
|
|
overrides: StateOverride{
|
|
randomAccounts[2].addr: {
|
|
Code: hex2Bytes("60004960005260206000f3"),
|
|
},
|
|
},
|
|
want: "0x0122000000000000000000000000000000000000000000000000000000000000",
|
|
},
|
|
}
|
|
for i, tc := range testSuite {
|
|
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)
|
|
if tc.expectErr != nil {
|
|
if err == nil {
|
|
t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr)
|
|
continue
|
|
}
|
|
if !errors.Is(err, tc.expectErr) {
|
|
// Second try
|
|
if !reflect.DeepEqual(err, tc.expectErr) {
|
|
t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("test %d: want no error, have %v", i, err)
|
|
continue
|
|
}
|
|
if !reflect.DeepEqual(result.String(), tc.want) {
|
|
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, result.String(), tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSignTransaction(t *testing.T) {
|
|
t.Parallel()
|
|
// Initialize test accounts
|
|
var (
|
|
key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
|
to = crypto.PubkeyToAddress(key.PublicKey)
|
|
genesis = &core.Genesis{
|
|
Config: params.MergedTestChainConfig,
|
|
Alloc: core.GenesisAlloc{},
|
|
}
|
|
)
|
|
b := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
|
b.SetPoS()
|
|
})
|
|
api := NewTransactionAPI(b, nil)
|
|
res, err := api.FillTransaction(context.Background(), TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to fill tx defaults: %v\n", err)
|
|
}
|
|
|
|
res, err = api.SignTransaction(context.Background(), argsFromTransaction(res.Tx, b.acc.Address))
|
|
if err != nil {
|
|
t.Fatalf("failed to sign tx: %v\n", err)
|
|
}
|
|
tx, err := json.Marshal(res.Tx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expect := `{"type":"0x2","chainId":"0x1","nonce":"0x0","to":"0x703c4b2bd70c169f5717101caee543299fc946c7","gas":"0x5208","gasPrice":null,"maxPriorityFeePerGas":"0x0","maxFeePerGas":"0x684ee180","value":"0x1","input":"0x","accessList":[],"v":"0x0","r":"0x8fabeb142d585dd9247f459f7e6fe77e2520c88d50ba5d220da1533cea8b34e1","s":"0x582dd68b21aef36ba23f34e49607329c20d981d30404daf749077f5606785ce7","yParity":"0x0","hash":"0x93927839207cfbec395da84b8a2bc38b7b65d2cb2819e9fef1f091f5b1d4cc8f"}`
|
|
if !bytes.Equal(tx, []byte(expect)) {
|
|
t.Errorf("result mismatch. Have:\n%s\nWant:\n%s\n", tx, expect)
|
|
}
|
|
}
|
|
|
|
func TestSignBlobTransaction(t *testing.T) {
|
|
t.Parallel()
|
|
// Initialize test accounts
|
|
var (
|
|
key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
|
to = crypto.PubkeyToAddress(key.PublicKey)
|
|
genesis = &core.Genesis{
|
|
Config: params.MergedTestChainConfig,
|
|
Alloc: core.GenesisAlloc{},
|
|
}
|
|
)
|
|
b := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
|
b.SetPoS()
|
|
})
|
|
api := NewTransactionAPI(b, nil)
|
|
res, err := api.FillTransaction(context.Background(), TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
BlobHashes: []common.Hash{{0x01, 0x22}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to fill tx defaults: %v\n", err)
|
|
}
|
|
|
|
_, err = api.SignTransaction(context.Background(), argsFromTransaction(res.Tx, b.acc.Address))
|
|
if err == nil {
|
|
t.Fatalf("should fail on blob transaction")
|
|
}
|
|
if !errors.Is(err, errBlobTxNotSupported) {
|
|
t.Errorf("error mismatch. Have: %v, want: %v", err, errBlobTxNotSupported)
|
|
}
|
|
}
|
|
|
|
func TestSendBlobTransaction(t *testing.T) {
|
|
t.Parallel()
|
|
// Initialize test accounts
|
|
var (
|
|
key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
|
to = crypto.PubkeyToAddress(key.PublicKey)
|
|
genesis = &core.Genesis{
|
|
Config: params.MergedTestChainConfig,
|
|
Alloc: core.GenesisAlloc{},
|
|
}
|
|
)
|
|
b := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
|
b.SetPoS()
|
|
})
|
|
api := NewTransactionAPI(b, nil)
|
|
res, err := api.FillTransaction(context.Background(), TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
BlobHashes: []common.Hash{common.Hash{0x01, 0x22}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to fill tx defaults: %v\n", err)
|
|
}
|
|
|
|
_, err = api.SendTransaction(context.Background(), argsFromTransaction(res.Tx, b.acc.Address))
|
|
if err == nil {
|
|
t.Errorf("sending tx should have failed")
|
|
} else if !errors.Is(err, errBlobTxNotSupported) {
|
|
t.Errorf("unexpected error. Have %v, want %v\n", err, errBlobTxNotSupported)
|
|
}
|
|
}
|
|
|
|
func TestFillBlobTransaction(t *testing.T) {
|
|
t.Parallel()
|
|
// Initialize test accounts
|
|
var (
|
|
key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
|
to = crypto.PubkeyToAddress(key.PublicKey)
|
|
genesis = &core.Genesis{
|
|
Config: params.MergedTestChainConfig,
|
|
Alloc: core.GenesisAlloc{},
|
|
}
|
|
emptyBlob = kzg4844.Blob{}
|
|
emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob)
|
|
emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit)
|
|
emptyBlobHash common.Hash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
|
|
)
|
|
b := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
|
b.SetPoS()
|
|
})
|
|
api := NewTransactionAPI(b, nil)
|
|
type result struct {
|
|
Hashes []common.Hash
|
|
Sidecar *types.BlobTxSidecar
|
|
}
|
|
suite := []struct {
|
|
name string
|
|
args TransactionArgs
|
|
err string
|
|
want *result
|
|
}{
|
|
{
|
|
name: "TestInvalidParamsCombination1",
|
|
args: TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
Blobs: []kzg4844.Blob{{}},
|
|
Proofs: []kzg4844.Proof{{}},
|
|
},
|
|
err: `blob proofs provided while commitments were not`,
|
|
},
|
|
{
|
|
name: "TestInvalidParamsCombination2",
|
|
args: TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
Blobs: []kzg4844.Blob{{}},
|
|
Commitments: []kzg4844.Commitment{{}},
|
|
},
|
|
err: `blob commitments provided while proofs were not`,
|
|
},
|
|
{
|
|
name: "TestInvalidParamsCount1",
|
|
args: TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
Blobs: []kzg4844.Blob{{}},
|
|
Commitments: []kzg4844.Commitment{{}, {}},
|
|
Proofs: []kzg4844.Proof{{}, {}},
|
|
},
|
|
err: `number of blobs and commitments mismatch (have=2, want=1)`,
|
|
},
|
|
{
|
|
name: "TestInvalidParamsCount2",
|
|
args: TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
Blobs: []kzg4844.Blob{{}, {}},
|
|
Commitments: []kzg4844.Commitment{{}, {}},
|
|
Proofs: []kzg4844.Proof{{}},
|
|
},
|
|
err: `number of blobs and proofs mismatch (have=1, want=2)`,
|
|
},
|
|
{
|
|
name: "TestInvalidProofVerification",
|
|
args: TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
Blobs: []kzg4844.Blob{{}, {}},
|
|
Commitments: []kzg4844.Commitment{{}, {}},
|
|
Proofs: []kzg4844.Proof{{}, {}},
|
|
},
|
|
err: `failed to verify blob proof: short buffer`,
|
|
},
|
|
{
|
|
name: "TestGenerateBlobHashes",
|
|
args: TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
Blobs: []kzg4844.Blob{emptyBlob},
|
|
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
|
Proofs: []kzg4844.Proof{emptyBlobProof},
|
|
},
|
|
want: &result{
|
|
Hashes: []common.Hash{emptyBlobHash},
|
|
Sidecar: &types.BlobTxSidecar{
|
|
Blobs: []kzg4844.Blob{emptyBlob},
|
|
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
|
Proofs: []kzg4844.Proof{emptyBlobProof},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "TestValidBlobHashes",
|
|
args: TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
BlobHashes: []common.Hash{emptyBlobHash},
|
|
Blobs: []kzg4844.Blob{emptyBlob},
|
|
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
|
Proofs: []kzg4844.Proof{emptyBlobProof},
|
|
},
|
|
want: &result{
|
|
Hashes: []common.Hash{emptyBlobHash},
|
|
Sidecar: &types.BlobTxSidecar{
|
|
Blobs: []kzg4844.Blob{emptyBlob},
|
|
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
|
Proofs: []kzg4844.Proof{emptyBlobProof},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "TestInvalidBlobHashes",
|
|
args: TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
BlobHashes: []common.Hash{{0x01, 0x22}},
|
|
Blobs: []kzg4844.Blob{emptyBlob},
|
|
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
|
Proofs: []kzg4844.Proof{emptyBlobProof},
|
|
},
|
|
err: fmt.Sprintf("blob hash verification failed (have=%s, want=%s)", common.Hash{0x01, 0x22}, emptyBlobHash),
|
|
},
|
|
{
|
|
name: "TestGenerateBlobProofs",
|
|
args: TransactionArgs{
|
|
From: &b.acc.Address,
|
|
To: &to,
|
|
Value: (*hexutil.Big)(big.NewInt(1)),
|
|
Blobs: []kzg4844.Blob{emptyBlob},
|
|
},
|
|
want: &result{
|
|
Hashes: []common.Hash{emptyBlobHash},
|
|
Sidecar: &types.BlobTxSidecar{
|
|
Blobs: []kzg4844.Blob{emptyBlob},
|
|
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
|
Proofs: []kzg4844.Proof{emptyBlobProof},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range suite {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
res, err := api.FillTransaction(context.Background(), tc.args)
|
|
if len(tc.err) > 0 {
|
|
if err == nil {
|
|
t.Fatalf("missing error. want: %s", tc.err)
|
|
} else if err != nil && err.Error() != tc.err {
|
|
t.Fatalf("error mismatch. want: %s, have: %s", tc.err, err.Error())
|
|
}
|
|
return
|
|
}
|
|
if err != nil && len(tc.err) == 0 {
|
|
t.Fatalf("expected no error. have: %s", err)
|
|
}
|
|
if res == nil {
|
|
t.Fatal("result missing")
|
|
}
|
|
want, err := json.Marshal(tc.want)
|
|
if err != nil {
|
|
t.Fatalf("failed to encode expected: %v", err)
|
|
}
|
|
have, err := json.Marshal(result{Hashes: res.Tx.BlobHashes(), Sidecar: res.Tx.BlobTxSidecar()})
|
|
if err != nil {
|
|
t.Fatalf("failed to encode computed sidecar: %v", err)
|
|
}
|
|
if !bytes.Equal(have, want) {
|
|
t.Errorf("blob sidecar mismatch. Have: %s, want: %s", have, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func argsFromTransaction(tx *types.Transaction, from common.Address) TransactionArgs {
|
|
var (
|
|
gas = tx.Gas()
|
|
nonce = tx.Nonce()
|
|
input = tx.Data()
|
|
)
|
|
return TransactionArgs{
|
|
From: &from,
|
|
To: tx.To(),
|
|
Gas: (*hexutil.Uint64)(&gas),
|
|
MaxFeePerGas: (*hexutil.Big)(tx.GasFeeCap()),
|
|
MaxPriorityFeePerGas: (*hexutil.Big)(tx.GasTipCap()),
|
|
Value: (*hexutil.Big)(tx.Value()),
|
|
Nonce: (*hexutil.Uint64)(&nonce),
|
|
Input: (*hexutil.Bytes)(&input),
|
|
ChainID: (*hexutil.Big)(tx.ChainId()),
|
|
// TODO: impl accessList conversion
|
|
//AccessList: tx.AccessList(),
|
|
BlobFeeCap: (*hexutil.Big)(tx.BlobGasFeeCap()),
|
|
BlobHashes: tx.BlobHashes(),
|
|
}
|
|
}
|
|
|
|
type account struct {
|
|
key *ecdsa.PrivateKey
|
|
addr common.Address
|
|
}
|
|
|
|
func newAccounts(n int) (accounts []account) {
|
|
for i := 0; i < n; i++ {
|
|
key, _ := crypto.GenerateKey()
|
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
|
accounts = append(accounts, account{key: key, addr: addr})
|
|
}
|
|
slices.SortFunc(accounts, func(a, b account) int { return a.addr.Cmp(b.addr) })
|
|
return accounts
|
|
}
|
|
|
|
func newRPCBalance(balance *big.Int) **hexutil.Big {
|
|
rpcBalance := (*hexutil.Big)(balance)
|
|
return &rpcBalance
|
|
}
|
|
|
|
func hex2Bytes(str string) *hexutil.Bytes {
|
|
rpcBytes := hexutil.Bytes(common.Hex2Bytes(str))
|
|
return &rpcBytes
|
|
}
|
|
|
|
func TestRPCMarshalBlock(t *testing.T) {
|
|
t.Parallel()
|
|
var (
|
|
txs []*types.Transaction
|
|
to = common.BytesToAddress([]byte{0x11})
|
|
)
|
|
for i := uint64(1); i <= 4; i++ {
|
|
var tx *types.Transaction
|
|
if i%2 == 0 {
|
|
tx = types.NewTx(&types.LegacyTx{
|
|
Nonce: i,
|
|
GasPrice: big.NewInt(11111),
|
|
Gas: 1111,
|
|
To: &to,
|
|
Value: big.NewInt(111),
|
|
Data: []byte{0x11, 0x11, 0x11},
|
|
})
|
|
} else {
|
|
tx = types.NewTx(&types.AccessListTx{
|
|
ChainID: big.NewInt(1337),
|
|
Nonce: i,
|
|
GasPrice: big.NewInt(11111),
|
|
Gas: 1111,
|
|
To: &to,
|
|
Value: big.NewInt(111),
|
|
Data: []byte{0x11, 0x11, 0x11},
|
|
})
|
|
}
|
|
txs = append(txs, tx)
|
|
}
|
|
block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, txs, nil, nil, blocktest.NewHasher())
|
|
|
|
var testSuite = []struct {
|
|
inclTx bool
|
|
fullTx bool
|
|
want string
|
|
}{
|
|
// without txs
|
|
{
|
|
inclTx: false,
|
|
fullTx: false,
|
|
want: `{
|
|
"difficulty": "0x0",
|
|
"extraData": "0x",
|
|
"gasLimit": "0x0",
|
|
"gasUsed": "0x0",
|
|
"hash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee",
|
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
"miner": "0x0000000000000000000000000000000000000000",
|
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"nonce": "0x0000000000000000",
|
|
"number": "0x64",
|
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
|
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
|
"size": "0x296",
|
|
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"timestamp": "0x0",
|
|
"transactionsRoot": "0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e",
|
|
"uncles": []
|
|
}`,
|
|
},
|
|
// only tx hashes
|
|
{
|
|
inclTx: true,
|
|
fullTx: false,
|
|
want: `{
|
|
"difficulty": "0x0",
|
|
"extraData": "0x",
|
|
"gasLimit": "0x0",
|
|
"gasUsed": "0x0",
|
|
"hash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee",
|
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
"miner": "0x0000000000000000000000000000000000000000",
|
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"nonce": "0x0000000000000000",
|
|
"number": "0x64",
|
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
|
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
|
"size": "0x296",
|
|
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"timestamp": "0x0",
|
|
"transactions": [
|
|
"0x7d39df979e34172322c64983a9ad48302c2b889e55bda35324afecf043a77605",
|
|
"0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4",
|
|
"0x98909ea1ff040da6be56bc4231d484de1414b3c1dac372d69293a4beb9032cb5",
|
|
"0x12e1f81207b40c3bdcc13c0ee18f5f86af6d31754d57a0ea1b0d4cfef21abef1"
|
|
],
|
|
"transactionsRoot": "0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e",
|
|
"uncles": []
|
|
}`,
|
|
},
|
|
// full tx details
|
|
{
|
|
inclTx: true,
|
|
fullTx: true,
|
|
want: `{
|
|
"difficulty": "0x0",
|
|
"extraData": "0x",
|
|
"gasLimit": "0x0",
|
|
"gasUsed": "0x0",
|
|
"hash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee",
|
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
"miner": "0x0000000000000000000000000000000000000000",
|
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"nonce": "0x0000000000000000",
|
|
"number": "0x64",
|
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
|
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
|
"size": "0x296",
|
|
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"timestamp": "0x0",
|
|
"transactions": [
|
|
{
|
|
"blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee",
|
|
"blockNumber": "0x64",
|
|
"from": "0x0000000000000000000000000000000000000000",
|
|
"gas": "0x457",
|
|
"gasPrice": "0x2b67",
|
|
"hash": "0x7d39df979e34172322c64983a9ad48302c2b889e55bda35324afecf043a77605",
|
|
"input": "0x111111",
|
|
"nonce": "0x1",
|
|
"to": "0x0000000000000000000000000000000000000011",
|
|
"transactionIndex": "0x0",
|
|
"value": "0x6f",
|
|
"type": "0x1",
|
|
"accessList": [],
|
|
"chainId": "0x539",
|
|
"v": "0x0",
|
|
"r": "0x0",
|
|
"s": "0x0",
|
|
"yParity": "0x0"
|
|
},
|
|
{
|
|
"blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee",
|
|
"blockNumber": "0x64",
|
|
"from": "0x0000000000000000000000000000000000000000",
|
|
"gas": "0x457",
|
|
"gasPrice": "0x2b67",
|
|
"hash": "0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4",
|
|
"input": "0x111111",
|
|
"nonce": "0x2",
|
|
"to": "0x0000000000000000000000000000000000000011",
|
|
"transactionIndex": "0x1",
|
|
"value": "0x6f",
|
|
"type": "0x0",
|
|
"chainId": "0x7fffffffffffffee",
|
|
"v": "0x0",
|
|
"r": "0x0",
|
|
"s": "0x0"
|
|
},
|
|
{
|
|
"blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee",
|
|
"blockNumber": "0x64",
|
|
"from": "0x0000000000000000000000000000000000000000",
|
|
"gas": "0x457",
|
|
"gasPrice": "0x2b67",
|
|
"hash": "0x98909ea1ff040da6be56bc4231d484de1414b3c1dac372d69293a4beb9032cb5",
|
|
"input": "0x111111",
|
|
"nonce": "0x3",
|
|
"to": "0x0000000000000000000000000000000000000011",
|
|
"transactionIndex": "0x2",
|
|
"value": "0x6f",
|
|
"type": "0x1",
|
|
"accessList": [],
|
|
"chainId": "0x539",
|
|
"v": "0x0",
|
|
"r": "0x0",
|
|
"s": "0x0",
|
|
"yParity": "0x0"
|
|
},
|
|
{
|
|
"blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee",
|
|
"blockNumber": "0x64",
|
|
"from": "0x0000000000000000000000000000000000000000",
|
|
"gas": "0x457",
|
|
"gasPrice": "0x2b67",
|
|
"hash": "0x12e1f81207b40c3bdcc13c0ee18f5f86af6d31754d57a0ea1b0d4cfef21abef1",
|
|
"input": "0x111111",
|
|
"nonce": "0x4",
|
|
"to": "0x0000000000000000000000000000000000000011",
|
|
"transactionIndex": "0x3",
|
|
"value": "0x6f",
|
|
"type": "0x0",
|
|
"chainId": "0x7fffffffffffffee",
|
|
"v": "0x0",
|
|
"r": "0x0",
|
|
"s": "0x0"
|
|
}
|
|
],
|
|
"transactionsRoot": "0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e",
|
|
"uncles": []
|
|
}`,
|
|
},
|
|
}
|
|
|
|
for i, tc := range testSuite {
|
|
resp := RPCMarshalBlock(block, tc.inclTx, tc.fullTx, params.MainnetChainConfig)
|
|
out, err := json.Marshal(resp)
|
|
if err != nil {
|
|
t.Errorf("test %d: json marshal error: %v", i, err)
|
|
continue
|
|
}
|
|
require.JSONEqf(t, tc.want, string(out), "test %d", i)
|
|
}
|
|
}
|
|
|
|
func TestRPCGetBlockOrHeader(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Initialize test accounts
|
|
var (
|
|
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
|
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
|
acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey)
|
|
acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey)
|
|
genesis = &core.Genesis{
|
|
Config: params.TestChainConfig,
|
|
Alloc: core.GenesisAlloc{
|
|
acc1Addr: {Balance: big.NewInt(params.Ether)},
|
|
acc2Addr: {Balance: big.NewInt(params.Ether)},
|
|
},
|
|
}
|
|
genBlocks = 10
|
|
signer = types.HomesteadSigner{}
|
|
tx = types.NewTx(&types.LegacyTx{
|
|
Nonce: 11,
|
|
GasPrice: big.NewInt(11111),
|
|
Gas: 1111,
|
|
To: &acc2Addr,
|
|
Value: big.NewInt(111),
|
|
Data: []byte{0x11, 0x11, 0x11},
|
|
})
|
|
withdrawal = &types.Withdrawal{
|
|
Index: 0,
|
|
Validator: 1,
|
|
Address: common.Address{0x12, 0x34},
|
|
Amount: 10,
|
|
}
|
|
pending = types.NewBlockWithWithdrawals(&types.Header{Number: big.NewInt(11), Time: 42}, []*types.Transaction{tx}, nil, nil, []*types.Withdrawal{withdrawal}, blocktest.NewHasher())
|
|
)
|
|
backend := newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) {
|
|
// Transfer from account[0] to account[1]
|
|
// value: 1000 wei
|
|
// fee: 0 wei
|
|
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &acc2Addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, acc1Key)
|
|
b.AddTx(tx)
|
|
})
|
|
backend.setPendingBlock(pending)
|
|
api := NewBlockChainAPI(backend)
|
|
blockHashes := make([]common.Hash, genBlocks+1)
|
|
ctx := context.Background()
|
|
for i := 0; i <= genBlocks; i++ {
|
|
header, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(i))
|
|
if err != nil {
|
|
t.Errorf("failed to get block: %d err: %v", i, err)
|
|
}
|
|
blockHashes[i] = header.Hash()
|
|
}
|
|
pendingHash := pending.Hash()
|
|
|
|
var testSuite = []struct {
|
|
blockNumber rpc.BlockNumber
|
|
blockHash *common.Hash
|
|
fullTx bool
|
|
reqHeader bool
|
|
file string
|
|
expectErr error
|
|
}{
|
|
// 0. latest header
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
reqHeader: true,
|
|
file: "tag-latest",
|
|
},
|
|
// 1. genesis header
|
|
{
|
|
blockNumber: rpc.BlockNumber(0),
|
|
reqHeader: true,
|
|
file: "number-0",
|
|
},
|
|
// 2. #1 header
|
|
{
|
|
blockNumber: rpc.BlockNumber(1),
|
|
reqHeader: true,
|
|
file: "number-1",
|
|
},
|
|
// 3. latest-1 header
|
|
{
|
|
blockNumber: rpc.BlockNumber(9),
|
|
reqHeader: true,
|
|
file: "number-latest-1",
|
|
},
|
|
// 4. latest+1 header
|
|
{
|
|
blockNumber: rpc.BlockNumber(11),
|
|
reqHeader: true,
|
|
file: "number-latest+1",
|
|
},
|
|
// 5. pending header
|
|
{
|
|
blockNumber: rpc.PendingBlockNumber,
|
|
reqHeader: true,
|
|
file: "tag-pending",
|
|
},
|
|
// 6. latest block
|
|
{
|
|
blockNumber: rpc.LatestBlockNumber,
|
|
file: "tag-latest",
|
|
},
|
|
// 7. genesis block
|
|
{
|
|
blockNumber: rpc.BlockNumber(0),
|
|
file: "number-0",
|
|
},
|
|
// 8. #1 block
|
|
{
|
|
blockNumber: rpc.BlockNumber(1),
|
|
file: "number-1",
|
|
},
|
|
// 9. latest-1 block
|
|
{
|
|
blockNumber: rpc.BlockNumber(9),
|
|
fullTx: true,
|
|
file: "number-latest-1",
|
|
},
|
|
// 10. latest+1 block
|
|
{
|
|
blockNumber: rpc.BlockNumber(11),
|
|
fullTx: true,
|
|
file: "number-latest+1",
|
|
},
|
|
// 11. pending block
|
|
{
|
|
blockNumber: rpc.PendingBlockNumber,
|
|
file: "tag-pending",
|
|
},
|
|
// 12. pending block + fullTx
|
|
{
|
|
blockNumber: rpc.PendingBlockNumber,
|
|
fullTx: true,
|
|
file: "tag-pending-fullTx",
|
|
},
|
|
// 13. latest header by hash
|
|
{
|
|
blockHash: &blockHashes[len(blockHashes)-1],
|
|
reqHeader: true,
|
|
file: "hash-latest",
|
|
},
|
|
// 14. genesis header by hash
|
|
{
|
|
blockHash: &blockHashes[0],
|
|
reqHeader: true,
|
|
file: "hash-0",
|
|
},
|
|
// 15. #1 header
|
|
{
|
|
blockHash: &blockHashes[1],
|
|
reqHeader: true,
|
|
file: "hash-1",
|
|
},
|
|
// 16. latest-1 header
|
|
{
|
|
blockHash: &blockHashes[len(blockHashes)-2],
|
|
reqHeader: true,
|
|
file: "hash-latest-1",
|
|
},
|
|
// 17. empty hash
|
|
{
|
|
blockHash: &common.Hash{},
|
|
reqHeader: true,
|
|
file: "hash-empty",
|
|
},
|
|
// 18. pending hash
|
|
{
|
|
blockHash: &pendingHash,
|
|
reqHeader: true,
|
|
file: `hash-pending`,
|
|
},
|
|
// 19. latest block
|
|
{
|
|
blockHash: &blockHashes[len(blockHashes)-1],
|
|
file: "hash-latest",
|
|
},
|
|
// 20. genesis block
|
|
{
|
|
blockHash: &blockHashes[0],
|
|
file: "hash-genesis",
|
|
},
|
|
// 21. #1 block
|
|
{
|
|
blockHash: &blockHashes[1],
|
|
file: "hash-1",
|
|
},
|
|
// 22. latest-1 block
|
|
{
|
|
blockHash: &blockHashes[len(blockHashes)-2],
|
|
fullTx: true,
|
|
file: "hash-latest-1-fullTx",
|
|
},
|
|
// 23. empty hash + body
|
|
{
|
|
blockHash: &common.Hash{},
|
|
fullTx: true,
|
|
file: "hash-empty-fullTx",
|
|
},
|
|
// 24. pending block
|
|
{
|
|
blockHash: &pendingHash,
|
|
file: `hash-pending`,
|
|
},
|
|
// 25. pending block + fullTx
|
|
{
|
|
blockHash: &pendingHash,
|
|
fullTx: true,
|
|
file: "hash-pending-fullTx",
|
|
},
|
|
}
|
|
|
|
for i, tt := range testSuite {
|
|
var (
|
|
result map[string]interface{}
|
|
err error
|
|
rpc string
|
|
)
|
|
if tt.blockHash != nil {
|
|
if tt.reqHeader {
|
|
result = api.GetHeaderByHash(context.Background(), *tt.blockHash)
|
|
rpc = "eth_getHeaderByHash"
|
|
} else {
|
|
result, err = api.GetBlockByHash(context.Background(), *tt.blockHash, tt.fullTx)
|
|
rpc = "eth_getBlockByHash"
|
|
}
|
|
} else {
|
|
if tt.reqHeader {
|
|
result, err = api.GetHeaderByNumber(context.Background(), tt.blockNumber)
|
|
rpc = "eth_getHeaderByNumber"
|
|
} else {
|
|
result, err = api.GetBlockByNumber(context.Background(), tt.blockNumber, tt.fullTx)
|
|
rpc = "eth_getBlockByNumber"
|
|
}
|
|
}
|
|
if tt.expectErr != nil {
|
|
if err == nil {
|
|
t.Errorf("test %d: want error %v, have nothing", i, tt.expectErr)
|
|
continue
|
|
}
|
|
if !errors.Is(err, tt.expectErr) {
|
|
t.Errorf("test %d: error mismatch, want %v, have %v", i, tt.expectErr, err)
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("test %d: want no error, have %v", i, err)
|
|
continue
|
|
}
|
|
|
|
testRPCResponseWithFile(t, i, result, rpc, tt.file)
|
|
}
|
|
}
|
|
|
|
func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Hash) {
|
|
config := *params.MergedTestChainConfig
|
|
var (
|
|
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
|
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
|
acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey)
|
|
acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey)
|
|
contract = common.HexToAddress("0000000000000000000000000000000000031ec7")
|
|
genesis = &core.Genesis{
|
|
Config: &config,
|
|
ExcessBlobGas: new(uint64),
|
|
BlobGasUsed: new(uint64),
|
|
Alloc: core.GenesisAlloc{
|
|
acc1Addr: {Balance: big.NewInt(params.Ether)},
|
|
acc2Addr: {Balance: big.NewInt(params.Ether)},
|
|
// // SPDX-License-Identifier: GPL-3.0
|
|
// pragma solidity >=0.7.0 <0.9.0;
|
|
//
|
|
// contract Token {
|
|
// event Transfer(address indexed from, address indexed to, uint256 value);
|
|
// function transfer(address to, uint256 value) public returns (bool) {
|
|
// emit Transfer(msg.sender, to, value);
|
|
// return true;
|
|
// }
|
|
// }
|
|
contract: {Balance: big.NewInt(params.Ether), Code: common.FromHex("0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a9059cbb14610030575b600080fd5b61004a6004803603810190610045919061016a565b610060565b60405161005791906101c5565b60405180910390f35b60008273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516100bf91906101ef565b60405180910390a36001905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610101826100d6565b9050919050565b610111816100f6565b811461011c57600080fd5b50565b60008135905061012e81610108565b92915050565b6000819050919050565b61014781610134565b811461015257600080fd5b50565b6000813590506101648161013e565b92915050565b60008060408385031215610181576101806100d1565b5b600061018f8582860161011f565b92505060206101a085828601610155565b9150509250929050565b60008115159050919050565b6101bf816101aa565b82525050565b60006020820190506101da60008301846101b6565b92915050565b6101e981610134565b82525050565b600060208201905061020460008301846101e0565b9291505056fea2646970667358221220b469033f4b77b9565ee84e0a2f04d496b18160d26034d54f9487e57788fd36d564736f6c63430008120033")},
|
|
},
|
|
}
|
|
signer = types.LatestSignerForChainID(params.TestChainConfig.ChainID)
|
|
txHashes = make([]common.Hash, genBlocks)
|
|
)
|
|
|
|
backend := newTestBackend(t, genBlocks, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
|
var (
|
|
tx *types.Transaction
|
|
err error
|
|
)
|
|
switch i {
|
|
case 0:
|
|
// transfer 1000wei
|
|
tx, err = types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &acc2Addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), types.HomesteadSigner{}, acc1Key)
|
|
case 1:
|
|
// create contract
|
|
tx, err = types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: nil, Gas: 53100, GasPrice: b.BaseFee(), Data: common.FromHex("0x60806040")}), signer, acc1Key)
|
|
case 2:
|
|
// with logs
|
|
// transfer(address to, uint256 value)
|
|
data := fmt.Sprintf("0xa9059cbb%s%s", common.HexToHash(common.BigToAddress(big.NewInt(int64(i + 1))).Hex()).String()[2:], common.BytesToHash([]byte{byte(i + 11)}).String()[2:])
|
|
tx, err = types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &contract, Gas: 60000, GasPrice: b.BaseFee(), Data: common.FromHex(data)}), signer, acc1Key)
|
|
case 3:
|
|
// dynamic fee with logs
|
|
// transfer(address to, uint256 value)
|
|
data := fmt.Sprintf("0xa9059cbb%s%s", common.HexToHash(common.BigToAddress(big.NewInt(int64(i + 1))).Hex()).String()[2:], common.BytesToHash([]byte{byte(i + 11)}).String()[2:])
|
|
fee := big.NewInt(500)
|
|
fee.Add(fee, b.BaseFee())
|
|
tx, err = types.SignTx(types.NewTx(&types.DynamicFeeTx{Nonce: uint64(i), To: &contract, Gas: 60000, Value: big.NewInt(1), GasTipCap: big.NewInt(500), GasFeeCap: fee, Data: common.FromHex(data)}), signer, acc1Key)
|
|
case 4:
|
|
// access list with contract create
|
|
accessList := types.AccessList{{
|
|
Address: contract,
|
|
StorageKeys: []common.Hash{{0}},
|
|
}}
|
|
tx, err = types.SignTx(types.NewTx(&types.AccessListTx{Nonce: uint64(i), To: nil, Gas: 58100, GasPrice: b.BaseFee(), Data: common.FromHex("0x60806040"), AccessList: accessList}), signer, acc1Key)
|
|
case 5:
|
|
// blob tx
|
|
fee := big.NewInt(500)
|
|
fee.Add(fee, b.BaseFee())
|
|
tx, err = types.SignTx(types.NewTx(&types.BlobTx{
|
|
Nonce: uint64(i),
|
|
GasTipCap: uint256.NewInt(1),
|
|
GasFeeCap: uint256.MustFromBig(fee),
|
|
Gas: params.TxGas,
|
|
To: acc2Addr,
|
|
BlobFeeCap: uint256.NewInt(1),
|
|
BlobHashes: []common.Hash{{1}},
|
|
Value: new(uint256.Int),
|
|
}), signer, acc1Key)
|
|
}
|
|
if err != nil {
|
|
t.Errorf("failed to sign tx: %v", err)
|
|
}
|
|
if tx != nil {
|
|
b.AddTx(tx)
|
|
txHashes[i] = tx.Hash()
|
|
}
|
|
b.SetPoS()
|
|
})
|
|
return backend, txHashes
|
|
}
|
|
|
|
func TestRPCGetTransactionReceipt(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
backend, txHashes = setupReceiptBackend(t, 6)
|
|
api = NewTransactionAPI(backend, new(AddrLocker))
|
|
)
|
|
|
|
var testSuite = []struct {
|
|
txHash common.Hash
|
|
file string
|
|
}{
|
|
// 0. normal success
|
|
{
|
|
txHash: txHashes[0],
|
|
file: "normal-transfer-tx",
|
|
},
|
|
// 1. create contract
|
|
{
|
|
txHash: txHashes[1],
|
|
file: "create-contract-tx",
|
|
},
|
|
// 2. with logs success
|
|
{
|
|
txHash: txHashes[2],
|
|
file: "with-logs",
|
|
},
|
|
// 3. dynamic tx with logs success
|
|
{
|
|
txHash: txHashes[3],
|
|
file: `dynamic-tx-with-logs`,
|
|
},
|
|
// 4. access list tx with create contract
|
|
{
|
|
txHash: txHashes[4],
|
|
file: "create-contract-with-access-list",
|
|
},
|
|
// 5. txhash empty
|
|
{
|
|
txHash: common.Hash{},
|
|
file: "txhash-empty",
|
|
},
|
|
// 6. txhash not found
|
|
{
|
|
txHash: common.HexToHash("deadbeef"),
|
|
file: "txhash-notfound",
|
|
},
|
|
// 7. blob tx
|
|
{
|
|
txHash: txHashes[5],
|
|
file: "blob-tx",
|
|
},
|
|
}
|
|
|
|
for i, tt := range testSuite {
|
|
var (
|
|
result interface{}
|
|
err error
|
|
)
|
|
result, err = api.GetTransactionReceipt(context.Background(), tt.txHash)
|
|
if err != nil {
|
|
t.Errorf("test %d: want no error, have %v", i, err)
|
|
continue
|
|
}
|
|
testRPCResponseWithFile(t, i, result, "eth_getTransactionReceipt", tt.file)
|
|
}
|
|
}
|
|
|
|
func TestRPCGetBlockReceipts(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
genBlocks = 6
|
|
backend, _ = setupReceiptBackend(t, genBlocks)
|
|
api = NewBlockChainAPI(backend)
|
|
)
|
|
blockHashes := make([]common.Hash, genBlocks+1)
|
|
ctx := context.Background()
|
|
for i := 0; i <= genBlocks; i++ {
|
|
header, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(i))
|
|
if err != nil {
|
|
t.Errorf("failed to get block: %d err: %v", i, err)
|
|
}
|
|
blockHashes[i] = header.Hash()
|
|
}
|
|
|
|
var testSuite = []struct {
|
|
test rpc.BlockNumberOrHash
|
|
file string
|
|
}{
|
|
// 0. block without any txs(hash)
|
|
{
|
|
test: rpc.BlockNumberOrHashWithHash(blockHashes[0], false),
|
|
file: "number-0",
|
|
},
|
|
// 1. block without any txs(number)
|
|
{
|
|
test: rpc.BlockNumberOrHashWithNumber(0),
|
|
file: "number-1",
|
|
},
|
|
// 2. earliest tag
|
|
{
|
|
test: rpc.BlockNumberOrHashWithNumber(rpc.EarliestBlockNumber),
|
|
file: "tag-earliest",
|
|
},
|
|
// 3. latest tag
|
|
{
|
|
test: rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber),
|
|
file: "tag-latest",
|
|
},
|
|
// 4. block with legacy transfer tx(hash)
|
|
{
|
|
test: rpc.BlockNumberOrHashWithHash(blockHashes[1], false),
|
|
file: "block-with-legacy-transfer-tx",
|
|
},
|
|
// 5. block with contract create tx(number)
|
|
{
|
|
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(2)),
|
|
file: "block-with-contract-create-tx",
|
|
},
|
|
// 6. block with legacy contract call tx(hash)
|
|
{
|
|
test: rpc.BlockNumberOrHashWithHash(blockHashes[3], false),
|
|
file: "block-with-legacy-contract-call-tx",
|
|
},
|
|
// 7. block with dynamic fee tx(number)
|
|
{
|
|
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(4)),
|
|
file: "block-with-dynamic-fee-tx",
|
|
},
|
|
// 8. block is empty
|
|
{
|
|
test: rpc.BlockNumberOrHashWithHash(common.Hash{}, false),
|
|
file: "hash-empty",
|
|
},
|
|
// 9. block is not found
|
|
{
|
|
test: rpc.BlockNumberOrHashWithHash(common.HexToHash("deadbeef"), false),
|
|
file: "hash-notfound",
|
|
},
|
|
// 10. block is not found
|
|
{
|
|
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(genBlocks + 1)),
|
|
file: "block-notfound",
|
|
},
|
|
// 11. block with blob tx
|
|
{
|
|
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(6)),
|
|
file: "block-with-blob-tx",
|
|
},
|
|
}
|
|
|
|
for i, tt := range testSuite {
|
|
var (
|
|
result interface{}
|
|
err error
|
|
)
|
|
result, err = api.GetBlockReceipts(context.Background(), tt.test)
|
|
if err != nil {
|
|
t.Errorf("test %d: want no error, have %v", i, err)
|
|
continue
|
|
}
|
|
testRPCResponseWithFile(t, i, result, "eth_getBlockReceipts", tt.file)
|
|
}
|
|
}
|
|
|
|
func testRPCResponseWithFile(t *testing.T, testid int, result interface{}, rpc string, file string) {
|
|
data, err := json.MarshalIndent(result, "", " ")
|
|
if err != nil {
|
|
t.Errorf("test %d: json marshal error", testid)
|
|
return
|
|
}
|
|
outputFile := filepath.Join("testdata", fmt.Sprintf("%s-%s.json", rpc, file))
|
|
if os.Getenv("WRITE_TEST_FILES") != "" {
|
|
os.WriteFile(outputFile, data, 0644)
|
|
}
|
|
want, err := os.ReadFile(outputFile)
|
|
if err != nil {
|
|
t.Fatalf("error reading expected test file: %s output: %v", outputFile, err)
|
|
}
|
|
require.JSONEqf(t, string(want), string(data), "test %d: json not match, want: %s, have: %s", testid, string(want), string(data))
|
|
}
|