diff --git a/core/state/intra_block_state.go b/core/state/intra_block_state.go index 9064d22d3d4..db4647172d6 100644 --- a/core/state/intra_block_state.go +++ b/core/state/intra_block_state.go @@ -26,6 +26,7 @@ import ( "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" types2 "github.com/ledgerwatch/erigon-lib/types" + "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/u256" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/types/accounts" @@ -827,3 +828,46 @@ func (sdb *IntraBlockState) AddressInAccessList(addr libcommon.Address) bool { func (sdb *IntraBlockState) SlotInAccessList(addr libcommon.Address, slot libcommon.Hash) (addressPresent bool, slotPresent bool) { return sdb.accessList.Contains(addr, slot) } + +// ValidateKnownAccounts validates the knownAccounts passed in the options parameter in the conditional transaction (EIP-4337) +func (sdb *IntraBlockState) ValidateKnownAccounts(knownAccounts types2.KnownAccountStorageConditions) error { + if knownAccounts == nil { + return nil + } + + for address, condition := range knownAccounts { + tempAccount, err := sdb.stateReader.ReadAccountData(address) + if err != nil { + return fmt.Errorf("error reading account data at: %v", address) + } + + if tempAccount == nil { + return fmt.Errorf("Storage Trie is nil for: %v", address) + } + + // check if the value is hex string or an object + switch { + case condition.IsSingle(): + if *condition.StorageRootHash != tempAccount.Root { + return fmt.Errorf("invalid root hash for: %v root hash: %v actual root hash: %v", address, condition.StorageRootHash, tempAccount.Root) + } + case condition.IsStorage(): + for slot, value := range condition.StorageSlotHashes { + slot := slot + tempByte, err := sdb.stateReader.ReadAccountStorage(address, tempAccount.Incarnation, &slot) + if err != nil { + return fmt.Errorf("error reading account storage at: %v slot: %v", address, slot) + } + + actualValue := libcommon.BytesToHash(common.LeftPadBytes(tempByte, 32)) + if value != actualValue { + return fmt.Errorf("invalid slot value at address: %v slot: %v value: %v actual value: %v", address, slot, value, actualValue) + } + } + default: + return fmt.Errorf("impossible to validate known accounts: %v", address) + } + } + + return nil +} diff --git a/core/state/intra_block_state_test.go b/core/state/intra_block_state_test.go index 08f4b28899f..238be0ee047 100644 --- a/core/state/intra_block_state_test.go +++ b/core/state/intra_block_state_test.go @@ -34,7 +34,9 @@ import ( "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/kv/memdb" + types2 "github.com/ledgerwatch/erigon-lib/types" "github.com/ledgerwatch/erigon/core/types" + "github.com/stretchr/testify/require" ) func TestSnapshotRandom(t *testing.T) { @@ -348,3 +350,75 @@ func TestTransientStorage(t *testing.T) { t.Fatalf("transient storage mismatch: have %x, want %x", got, exp) } } + +func insertKnownAccounts[T libcommon.Hash | map[libcommon.Hash]libcommon.Hash](accounts KnownAccountStorageConditions, k libcommon.Address, v T) { + switch typedV := any(v).(type) { + case libcommon.Hash: + accounts[k] = &KnownAccountStorageCondition{StorageRootHash: &typedV} + case map[libcommon.Hash]libcommon.Hash: + accounts[k] = &KnownAccountStorageCondition{StorageSlotHashes: typedV} + } +} + +func TestValidateKnownAccounts(t *testing.T) { + t.Parallel() + + knownAccounts := make(types2.KnownAccountStorageConditions) + + insertKnownAccounts(knownAccounts, libcommon.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add1"), libcommon.HexToHash("0x2d6f8a898e7dec0bb7a50e8c142be32d7c98c096ff68ed57b9b08280d9aca1ce")) + insertKnownAccounts(knownAccounts, libcommon.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add2"), map[libcommon.Hash]libcommon.Hash{ + libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000aaa"): libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000bbb"), + libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ccc"): libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ddd"), + }) + + stateobjaddr1 := libcommon.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add1") + stateobjaddr2 := libcommon.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add2") + + storageaddr1 := libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000zzz") + storageaddr21 := libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000aaa") + storageaddr22 := libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ccc") + + data1 := libcommon.BytesToHash([]byte{24}) + data21 := libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000bbb") + data22 := libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ddd") + + s := newStateTest() + + // set initial state object value + s.state.SetState(stateobjaddr1, storageaddr1, data1) + s.state.SetState(stateobjaddr2, storageaddr21, data21) + s.state.SetState(stateobjaddr2, storageaddr22, data22) + + require.NoError(t, s.state.ValidateKnownAccounts(knownAccounts)) + + insertKnownAccounts(knownAccounts, libcommon.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add2"), libcommon.HexToHash("0x2d6f8a898e7dec0bb7a50e8c142be32d7c98c096ff68ed57b9b08280d9aca1cf")) + + stateobjaddr3 := libcommon.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add2") + storageaddr3 := libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000yyy") + data3 := libcommon.BytesToHash([]byte{24}) + + s.state.SetState(stateobjaddr3, storageaddr3, data3) + + // expected error + err := s.state.ValidateKnownAccounts(knownAccounts) + require.Error(t, err, "should have been an error") + + // correct the previous mistake "0x2d6f8a898e7dec0bb7a50e8c142be32d7c98c096ff68ed57b9b08280d9aca1cf" -> "0x2d6f8a898e7dec0bb7a50e8c142be32d7c98c096ff68ed57b9b08280d9aca1ce" + insertKnownAccounts(knownAccounts, libcommon.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add2"), libcommon.HexToHash("0x2d6f8a898e7dec0bb7a50e8c142be32d7c98c096ff68ed57b9b08280d9aca1ce")) + insertKnownAccounts(knownAccounts, libcommon.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add3"), map[libcommon.Hash]libcommon.Hash{ + libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000aaa"): libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000bbb"), + libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ccc"): libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ddd"), + }) + + stateobjaddr4 := libcommon.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add3") + storageaddr41 := libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000aaa") + storageaddr42 := libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ccc") + data4 := libcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000bbb") + + s.state.SetState(stateobjaddr4, storageaddr41, data4) + s.state.SetState(stateobjaddr4, storageaddr42, data4) + + // expected error + err = s.state.ValidateKnownAccounts(knownAccounts) + require.Error(t, err, "should have been an error") +} diff --git a/core/types/block_test.go b/core/types/block_test.go index be05c716b26..7fb1efe69c6 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -512,3 +512,167 @@ func TestCopyTxs(t *testing.T) { copies := CopyTxs(txs) assert.Equal(t, txs, copies) } + +func TestValidateBlockNumberOptions4337(t *testing.T) { + t.Parallel() + + testsPass := []struct { + number string + header Header + minBlockNumber *big.Int + maxBlockNumber *big.Int + }{ + { + "1", + Header{Number: big.NewInt(10)}, + big.NewInt(0), + big.NewInt(20), + }, + { + "2", + Header{Number: big.NewInt(10)}, + big.NewInt(10), + big.NewInt(10), + }, + { + "3", + Header{Number: big.NewInt(10)}, + big.NewInt(10), + big.NewInt(11), + }, + { + "4", + Header{Number: big.NewInt(10)}, + big.NewInt(0), + big.NewInt(10), + }, + } + + testsFail := []struct { + number string + header Header + minBlockNumber *big.Int + maxBlockNumber *big.Int + }{ + { + "5", + Header{Number: big.NewInt(10)}, + big.NewInt(0), + big.NewInt(0), + }, + { + "6", + Header{Number: big.NewInt(10)}, + big.NewInt(0), + big.NewInt(9), + }, + { + "7", + Header{Number: big.NewInt(10)}, + big.NewInt(11), + big.NewInt(9), + }, + { + "8", + Header{Number: big.NewInt(10)}, + big.NewInt(11), + big.NewInt(20), + }, + } + + for _, test := range testsPass { + if _, err := types2.BigIntIsWithinRange(test.header.Number, test.minBlockNumber, test.maxBlockNumber); err != nil { + t.Fatalf("test number %v should not have failed. err: %v", test.number, err) + } + } + + for _, test := range testsFail { + if _, err := types2.BigIntIsWithinRange(test.header.Number, test.minBlockNumber, test.maxBlockNumber); err == nil { + t.Fatalf("test number %v should have failed. err is nil", test.number) + } + } +} + +func TestValidateTimestampOptions4337(t *testing.T) { + t.Parallel() + + u64Ptr := func(n uint64) *uint64 { + return &n + } + + testsPass := []struct { + number string + header Header + minTimestamp *uint64 + maxTimestamp *uint64 + }{ + { + "1", + Header{Time: 1600000000}, + u64Ptr(1500000000), + u64Ptr(1700000000), + }, + { + "2", + Header{Time: 1600000000}, + u64Ptr(1600000000), + u64Ptr(1600000000), + }, + { + "3", + Header{Time: 1600000000}, + u64Ptr(1600000000), + u64Ptr(1700000000), + }, + { + "4", + Header{Time: 1600000000}, + u64Ptr(1500000000), + u64Ptr(1600000000), + }, + } + + testsFail := []struct { + number string + header Header + minTimestamp *uint64 + maxTimestamp *uint64 + }{ + { + "5", + Header{Time: 1600000000}, + u64Ptr(1500000000), + u64Ptr(1500000000), + }, + { + "6", + Header{Time: 1600000000}, + u64Ptr(1400000000), + u64Ptr(1500000000), + }, + { + "7", + Header{Time: 1600000000}, + u64Ptr(1700000000), + u64Ptr(1500000000), + }, + { + "8", + Header{Time: 1600000000}, + u64Ptr(1700000000), + u64Ptr(1800000000), + }, + } + + for _, test := range testsPass { + if _, err := types2.Uint64IsWithinRange(&test.header.Time, test.minTimestamp, test.maxTimestamp); err != nil { + t.Fatalf("test number %v should not have failed. err: %v", test.number, err) + } + } + + for _, test := range testsFail { + if _, err := types2.Uint64IsWithinRange(&test.header.Time, test.minTimestamp, test.maxTimestamp); err == nil { + t.Fatalf("test number %v should have failed. err is nil", test.number) + } + } +} diff --git a/erigon-lib/common/hash.go b/erigon-lib/common/hash.go index a2353a4e6b9..90e3316fce0 100644 --- a/erigon-lib/common/hash.go +++ b/erigon-lib/common/hash.go @@ -61,6 +61,12 @@ func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } // If b is larger than len(h), b will be cropped from the left. func HexToHash(s string) Hash { return BytesToHash(hexutility.FromHex(s)) } +func HexToRefHash(s string) *Hash { + v := BytesToHash(FromHex(s)) + + return &v +} + // Bytes gets the byte representation of the underlying hash. func (h Hash) Bytes() []byte { return h[:] } diff --git a/erigon-lib/types/transaction_conditional.go b/erigon-lib/types/transaction_conditional.go new file mode 100644 index 00000000000..ce4dce1cdf7 --- /dev/null +++ b/erigon-lib/types/transaction_conditional.go @@ -0,0 +1,181 @@ +// Copyright 2014 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 . + +package types + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math/big" + + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common/hexutility" +) + +type KnownAccountStorageConditions map[libcommon.Address]*KnownAccountStorageCondition + +type KnownAccountStorageCondition struct { + StorageRootHash *libcommon.Hash + StorageSlotHashes map[libcommon.Hash]libcommon.Hash +} + +func NewKnownAccountStorageConditionWithRootHash(hex string) *KnownAccountStorageCondition { + return &KnownAccountStorageCondition{StorageRootHash: libcommon.HexToRefHash(hex)} +} + +func NewKnownAccountStorageConditionWithSlotHashes(m map[string]string) *KnownAccountStorageCondition { + res := map[libcommon.Hash]libcommon.Hash{} + + for k, v := range m { + res[libcommon.HexToHash(k)] = libcommon.HexToHash(v) + } + + return &KnownAccountStorageCondition{StorageSlotHashes: res} +} + +func (v *KnownAccountStorageCondition) IsSingle() bool { + return v != nil && v.StorageRootHash != nil && !v.IsStorage() +} + +func (v *KnownAccountStorageCondition) IsStorage() bool { + return v != nil && v.StorageSlotHashes != nil +} + +func (v *KnownAccountStorageCondition) MarshalJSON() ([]byte, error) { + if v.IsSingle() { + return json.Marshal(v.StorageRootHash) + } + + if v.IsStorage() { + return json.Marshal(v.StorageSlotHashes) + } + + return []byte("{}"), nil +} + +func (v *KnownAccountStorageCondition) UnmarshalJSON(data []byte) error { + var ErrKnownAccounts = errors.New("an incorrect list of knownAccounts") + + if len(data) == 0 { + return nil + } + + var m map[string]json.RawMessage + + err := json.Unmarshal(data, &m) + if err != nil { + // single Hash value case + v.StorageRootHash = new(libcommon.Hash) + + innerErr := json.Unmarshal(data, v.StorageRootHash) + if innerErr != nil { + return fmt.Errorf("can't unmarshal to single value with error: %v value %q", innerErr, string(data)) + } + + return nil + } + + res := make(map[libcommon.Hash]libcommon.Hash, len(m)) + + for k, v := range m { + // check k if it is a Hex value + var kHash libcommon.Hash + + err = hexutility.UnmarshalFixedText("Hash", []byte(k), kHash[:]) + if err != nil { + return fmt.Errorf("%w by key: %s with key %q and value %q", ErrKnownAccounts, err, k, string(v)) + } + + // check v if it is a Hex value + var vHash libcommon.Hash + + err = hexutility.UnmarshalFixedText("hashTypeName", bytes.Trim(v, "\""), vHash[:]) + if err != nil { + return fmt.Errorf("%w by value: %s with key %q and value %q", ErrKnownAccounts, err, k, string(v)) + } + + res[kHash] = vHash + } + + v.StorageSlotHashes = res + + return nil +} + +type TransactionConditions struct { + KnownAccountStorageConditions KnownAccountStorageConditions `json:"knownAccounts"` + BlockNumberMin *big.Int `json:"blockNumberMin"` + BlockNumberMax *big.Int `json:"blockNumberMax"` + TimestampMin *uint64 `json:"timestampMin"` + TimestampMax *uint64 `json:"timestampMax"` +} + +func (ka KnownAccountStorageConditions) CountStorageEntries() int { + if ka == nil { + return 0 + } + + length := 0 + + for _, v := range ka { + // check if the value is hex string or an object + if v.IsSingle() { + length += 1 + } else { + length += len(v.StorageSlotHashes) + } + } + + return length +} + +func BigIntIsWithinRange(num *big.Int, min *big.Int, max *big.Int) (bool, error) { + if min != nil && num.Cmp(min) == -1 { + return false, fmt.Errorf("provided number %v is less than minimum number: %v", num, min) + } + + if max != nil && num.Cmp(max) == 1 { + return false, fmt.Errorf("provided number %v is greater than maximum number: %v", num, max) + } + + return true, nil +} + +func Uint64IsWithinRange(num *uint64, min *uint64, max *uint64) (bool, error) { + if min != nil && *num < *min { + return false, fmt.Errorf("provided number %v is less than minimum number: %v", num, min) + } + + if max != nil && *num > *max { + return false, fmt.Errorf("provided number %v is greater than maximum number: %v", num, max) + } + + return true, nil +} + +type TransactionConditionsValidationError struct{ Message string } + +func (e *TransactionConditionsValidationError) ErrorCode() int { return -32003 } + +func (e *TransactionConditionsValidationError) Error() string { return e.Message } + +type KnownAccountsLimitExceededError struct{ Message string } + +func (e *KnownAccountsLimitExceededError) ErrorCode() int { return -32005 } + +func (e *KnownAccountsLimitExceededError) Error() string { return e.Message } diff --git a/erigon-lib/types/transaction_conditional_test.go b/erigon-lib/types/transaction_conditional_test.go new file mode 100644 index 00000000000..15627dd5f42 --- /dev/null +++ b/erigon-lib/types/transaction_conditional_test.go @@ -0,0 +1,30 @@ +package types + +import ( + "encoding/json" + "testing" + + "github.com/ledgerwatch/erigon-lib/common" + "github.com/stretchr/testify/require" +) + +func TestKnownAccounts(t *testing.T) { + t.Parallel() + + requestRaw := []byte(`{"0xadd1add1add1add1add1add1add1add1add1add1": "0x000000000000000000000000313aadca1750caadc7bcb26ff08175c95dcf8e38", "0xadd2add2add2add2add2add2add2add2add2add2": {"0x0000000000000000000000000000000000000000000000000000000000000aaa": "0x0000000000000000000000000000000000000000000000000000000000000bbb", "0x0000000000000000000000000000000000000000000000000000000000000ccc": "0x0000000000000000000000000000000000000000000000000000000000000ddd"}}`) + + accs := &KnownAccountStorageConditions{} + + err := json.Unmarshal(requestRaw, accs) + require.NoError(t, err) + + expected := &KnownAccountStorageConditions{ + common.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add1"): NewKnownAccountStorageConditionWithRootHash("0x000000000000000000000000313aadca1750caadc7bcb26ff08175c95dcf8e38"), + common.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add2"): NewKnownAccountStorageConditionWithSlotHashes(map[string]string{ + "0x0000000000000000000000000000000000000000000000000000000000000aaa": "0x0000000000000000000000000000000000000000000000000000000000000bbb", + "0x0000000000000000000000000000000000000000000000000000000000000ccc": "0x0000000000000000000000000000000000000000000000000000000000000ddd", + }), + } + + require.Equal(t, expected, accs) +} diff --git a/turbo/jsonrpc/bor_api.go b/turbo/jsonrpc/bor_api.go index 2ee4a461e86..ccb59682707 100644 --- a/turbo/jsonrpc/bor_api.go +++ b/turbo/jsonrpc/bor_api.go @@ -2,6 +2,7 @@ package jsonrpc import ( "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/gointerfaces/txpool" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon/consensus/bor" @@ -26,15 +27,17 @@ type BorAPI interface { // BorImpl is implementation of the BorAPI interface type BorImpl struct { *BaseAPI - db kv.RoDB // the chain db - bor *bor.Bor + db kv.RoDB // the chain db + bor *bor.Bor + txPool txpool.TxpoolClient } // NewBorAPI returns BorImpl instance -func NewBorAPI(base *BaseAPI, db kv.RoDB, bor *bor.Bor) *BorImpl { +func NewBorAPI(base *BaseAPI, db kv.RoDB, bor *bor.Bor, txpool txpool.TxpoolClient) *BorImpl { return &BorImpl{ BaseAPI: base, db: db, bor: bor, + txPool: txpool, } } diff --git a/turbo/jsonrpc/bor_snapshot.go b/turbo/jsonrpc/bor_snapshot.go index 91bdf642afb..577b63fa87b 100644 --- a/turbo/jsonrpc/bor_snapshot.go +++ b/turbo/jsonrpc/bor_snapshot.go @@ -8,16 +8,22 @@ import ( "github.com/ledgerwatch/erigon-lib/chain" "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common/hexutility" + txPoolProto "github.com/ledgerwatch/erigon-lib/gointerfaces/txpool" "github.com/ledgerwatch/erigon-lib/kv" + types2 "github.com/ledgerwatch/erigon-lib/types" "github.com/ledgerwatch/log/v3" "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/consensus/bor" "github.com/ledgerwatch/erigon/consensus/bor/valset" "github.com/ledgerwatch/erigon/core/rawdb" + "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/eth/borfinality/whitelist" + "github.com/ledgerwatch/erigon/eth/ethconfig" "github.com/ledgerwatch/erigon/rpc" + "github.com/ledgerwatch/erigon/turbo/rpchelper" ) type Snapshot struct { @@ -250,6 +256,106 @@ func (api *BorImpl) GetVoteOnHash(ctx context.Context, starBlockNr uint64, endBl return true, nil } +func ValidateTransactionConditions(tc *types2.TransactionConditions, header *types.Header, ibs *state.IntraBlockState, lengthCheckFlag bool) error { + // check block number range + if _, err := types2.BigIntIsWithinRange(header.Number, tc.BlockNumberMin, tc.BlockNumberMax); err != nil { + return &types2.TransactionConditionsValidationError{Message: "out of block range. err: " + err.Error()} + } + + // check timestamp range + if _, err := types2.Uint64IsWithinRange(&header.Time, tc.TimestampMin, tc.TimestampMax); err != nil { + return &types2.TransactionConditionsValidationError{Message: "out of time range. err: " + err.Error()} + } + + // check knownAccounts + if err := ibs.ValidateKnownAccounts(tc.KnownAccountStorageConditions); err != nil { + return &types2.KnownAccountsLimitExceededError{Message: "limit exceeded. err: number of slots/accounts in KnownAccountStorageConditions exceeds the limit of 1000"} + } + + if lengthCheckFlag { + // check knownAccounts length (number of slots/accounts) should be less than 1000 + if tc.KnownAccountStorageConditions.CountStorageEntries() >= 1000 { + return &types2.KnownAccountsLimitExceededError{Message: "limit exceeded. err: number of slots/accounts in KnownAccountStorageConditions exceeds the limit of 1000"} + } + } + + return nil +} + +// SendRawTransactionConditional will add the signed transaction to the transaction pool. +// The sender/bundler is responsible for signing the transaction +func (api *BorImpl) SendRawTransactionConditional(ctx context.Context, encodedTx hexutility.Bytes, options types2.TransactionConditions) (common.Hash, error) { + txn, err := types.DecodeWrappedTransaction(encodedTx) + if err != nil { + return common.Hash{}, err + } + + // this has been moved to prior to adding of transactions to capture the + // pre state of the db - which is used for logging in the messages below + tx, err := api.db.BeginRo(ctx) + if err != nil { + return common.Hash{}, err + } + defer tx.Rollback() + + currentHeader := rawdb.ReadCurrentHeader(tx) + // Ensure we have an actually valid block + if currentHeader == nil { + return common.Hash{}, errUnknownBlock + } + + tempBlockNumber := rpc.BlockNumber(currentHeader.Number.Int64()) + tempBlockorHash := rpc.BlockNumberOrHash{BlockNumber: &tempBlockNumber} + + readerTemp, err := rpchelper.CreateStateReader(ctx, tx, tempBlockorHash, 0, api.filters, api.stateCache, api.historyV3(tx), "") + if err != nil { + return common.Hash{}, err + } + + currentState := state.New(readerTemp) + + if err := ValidateTransactionConditions(&options, currentHeader, currentState, true); err != nil { + return common.Hash{}, err + } + + // TODO + // put options data in Tx, to use it later while block building + // txn.PutOptions(&options) + + // If the transaction fee cap is already specified, ensure the + // fee of the given transaction is _reasonable_. + if err := checkTxFee(txn.GetPrice().ToBig(), txn.GetGas(), ethconfig.Defaults.RPCTxFeeCap); err != nil { + return common.Hash{}, err + } + if !txn.Protected() { + return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC") + } + + cc, err := api.chainConfig(tx) + if err != nil { + return common.Hash{}, err + } + + txnChainId := txn.GetChainID() + chainId := cc.ChainID + + if chainId.Cmp(txnChainId.ToBig()) != 0 { + return common.Hash{}, fmt.Errorf("invalid chain id, expected: %d got: %d", chainId, *txnChainId) + } + + hash := txn.Hash() + res, err := api.txPool.Add(ctx, &txPoolProto.AddRequest{RlpTxs: [][]byte{encodedTx}}) + if err != nil { + return common.Hash{}, err + } + + if res.Imported[0] != txPoolProto.ImportResult_SUCCESS { + return hash, fmt.Errorf("%s: %s", txPoolProto.ImportResult_name[int32(res.Imported[0])], res.Errors[0]) + } + + return txn.Hash(), nil +} + type BlockSigners struct { Signers []difficultiesKV Diff int diff --git a/turbo/jsonrpc/daemon.go b/turbo/jsonrpc/daemon.go index 9c57c1ed47b..178a6f9e567 100644 --- a/turbo/jsonrpc/daemon.go +++ b/turbo/jsonrpc/daemon.go @@ -36,7 +36,7 @@ func APIList(db kv.RoDB, eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, m var borImpl *BorImpl if bor, ok := engine.(*bor.Bor); ok { - borImpl = NewBorAPI(base, db, bor) // bor (consensus) specific + borImpl = NewBorAPI(base, db, bor, txPool) // bor (consensus) specific } otsImpl := NewOtterscanAPI(base, db, cfg.OtsMaxPageSize)