forked from cerc-io/plugeth
internal/ethapi: handle blobs in API methods (#28786)
EIP-4844 adds a new transaction type for blobs. Users can submit such transactions via `eth_sendRawTransaction`. In this PR we refrain from adding support to `eth_sendTransaction` and in fact it will fail if the user passes in a blob hash. However since the chain can handle such transactions it makes sense to allow simulating them. E.g. an L2 operator should be able to simulate submitting a rollup blob and updating the L2 state. Most methods that take in a transaction object should recognize blobs. The change boils down to adding `blobVersionedHashes` and `maxFeePerBlobGas` to `TransactionArgs`. In summary: - `eth_sendTransaction`: will fail for blob txes - `eth_signTransaction`: will fail for blob txes The methods that sign txes does not, as of this PR, add support the for new EIP-4844 transaction types. Resuming the summary: - `eth_sendRawTransaction`: can send blob txes - `eth_fillTransaction`: will fill in a blob tx. Note: here we simply fill in normal transaction fields + possibly `maxFeePerBlobGas` when blobs are present. One can imagine a more elaborate set-up where users can submit blobs themselves and we fill in proofs and commitments and such. Left for future PRs if desired. - `eth_call`: can simulate blob messages - `eth_estimateGas`: blobs have no effect here. They have a separate unit of gas which is not tunable in the transaction.
This commit is contained in:
+200
-13
@@ -17,6 +17,7 @@
|
||||
package ethapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
|
||||
"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"
|
||||
@@ -403,10 +405,30 @@ func allBlobTxs(addr common.Address, config *params.ChainConfig) []txData {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -419,6 +441,8 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E
|
||||
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)
|
||||
@@ -430,7 +454,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E
|
||||
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
||||
}
|
||||
|
||||
backend := &testBackend{db: db, chain: chain}
|
||||
backend := &testBackend{db: db, chain: chain, accman: accman, acc: acc}
|
||||
return backend
|
||||
}
|
||||
|
||||
@@ -446,7 +470,7 @@ func (b testBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBloc
|
||||
return nil, nil, nil, nil, nil
|
||||
}
|
||||
func (b testBackend) ChainDb() ethdb.Database { return b.db }
|
||||
func (b testBackend) AccountManager() *accounts.Manager { return nil }
|
||||
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 }
|
||||
@@ -566,7 +590,7 @@ func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*t
|
||||
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) {
|
||||
panic("implement me")
|
||||
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) {
|
||||
@@ -603,7 +627,7 @@ func TestEstimateGas(t *testing.T) {
|
||||
var (
|
||||
accounts = newAccounts(2)
|
||||
genesis = &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Config: params.MergedTestChainConfig,
|
||||
Alloc: core.GenesisAlloc{
|
||||
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
||||
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
||||
@@ -613,12 +637,13 @@ func TestEstimateGas(t *testing.T) {
|
||||
signer = types.HomesteadSigner{}
|
||||
randomAccounts = newAccounts(2)
|
||||
)
|
||||
api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) {
|
||||
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
|
||||
@@ -718,6 +743,18 @@ func TestEstimateGas(t *testing.T) {
|
||||
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)
|
||||
@@ -747,7 +784,7 @@ func TestCall(t *testing.T) {
|
||||
var (
|
||||
accounts = newAccounts(3)
|
||||
genesis = &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Config: params.MergedTestChainConfig,
|
||||
Alloc: core.GenesisAlloc{
|
||||
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
||||
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
||||
@@ -757,12 +794,13 @@ func TestCall(t *testing.T) {
|
||||
genBlocks = 10
|
||||
signer = types.HomesteadSigner{}
|
||||
)
|
||||
api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) {
|
||||
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 {
|
||||
@@ -884,6 +922,32 @@ func TestCall(t *testing.T) {
|
||||
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)
|
||||
@@ -910,6 +974,134 @@ func TestCall(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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 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
|
||||
@@ -1399,9 +1591,7 @@ func TestRPCGetBlockOrHeader(t *testing.T) {
|
||||
}
|
||||
|
||||
func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Hash) {
|
||||
config := *params.TestChainConfig
|
||||
config.ShanghaiTime = new(uint64)
|
||||
config.CancunTime = new(uint64)
|
||||
config := *params.MergedTestChainConfig
|
||||
var (
|
||||
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
@@ -1432,9 +1622,6 @@ func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Ha
|
||||
txHashes = make([]common.Hash, genBlocks)
|
||||
)
|
||||
|
||||
// Set the terminal total difficulty in the config
|
||||
genesis.Config.TerminalTotalDifficulty = big.NewInt(0)
|
||||
genesis.Config.TerminalTotalDifficultyPassed = true
|
||||
backend := newTestBackend(t, genBlocks, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
||||
var (
|
||||
tx *types.Transaction
|
||||
|
||||
Reference in New Issue
Block a user