Skip to content

Commit

Permalink
Added a new RPC endpoint (bor_sendRawTransactionConditional) to sup…
Browse files Browse the repository at this point in the history
…port EIP-4337 Bundled Transactions (ethereum#945)

* added new api to support conditional transactions (EIP-4337) (ethereum#700)

* Refactored the code and updated the miner to check for the validity of options (ethereum#793)

* refactored the code and updated the miner to check for the validity of options

* added new errors -32003 and -32005

* added unit tests

* addressed comments

* Aa 4337 update generics (ethereum#799)

* poc

* minor bug fix

* use common.Hash

* updated UnmarshalJSON function (reference - tynes)

* fix

* done

* linters

* with test

* undo some unintentional changes

---------

Co-authored-by: Pratik Patil <pratikspatil024@gmail.com>

* handelling the block range and timestamp range, also made timestamp a pointer

---------

Co-authored-by: Evgeny Danilenko <6655321@bk.ru>

* Added filtering of conditional transactions in txpool (ethereum#920)

* added filtering of conditional transactions in txpool

* minor fix in ValidateKnownAccounts

* bug fix

* Supporting nil knownAccounts

* lints

* bundled transactions are not announced/broadcasted to the peers

* fixed after upstream merge

* few fixes

* sentry reject conditional transaction

* Changed the namespace of conditional transaction API from `eth` to `bor` (ethereum#985)

* added conditional transaction to bor namespace

* test comit

* test comit

* added conditional transaction

* namespapce changed to bor

* cleanup

* cleanup

* addressed comments

* reverted changes in ValidateKnownAccounts

* addressed comments and removed unwanted code

* addressed comments

* bug fix

* lint

* removed licence from core/types/transaction_conditional_test.go

---------

Co-authored-by: Evgeny Danilenko <6655321@bk.ru>
  • Loading branch information
pratikspatil024 and JekaMas committed Sep 13, 2023
1 parent 3442c03 commit 67fc4d8
Show file tree
Hide file tree
Showing 25 changed files with 732 additions and 22 deletions.
9 changes: 8 additions & 1 deletion common/types.go
Expand Up @@ -28,8 +28,9 @@ import (
"reflect"
"strings"

"github.com/ethereum/go-ethereum/common/hexutil"
"golang.org/x/crypto/sha3"

"github.com/ethereum/go-ethereum/common/hexutil"
)

// Lengths of hashes and addresses in bytes.
Expand Down Expand Up @@ -66,6 +67,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(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[:] }

Expand Down
66 changes: 66 additions & 0 deletions core/state/state_test.go
Expand Up @@ -21,8 +21,11 @@ import (
"math/big"
"testing"

"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
Expand Down Expand Up @@ -273,3 +276,66 @@ func compareStateObjects(so0, so1 *stateObject, t *testing.T) {
}
}
}

func TestValidateKnownAccounts(t *testing.T) {
t.Parallel()

knownAccounts := make(types.KnownAccounts)

types.InsertKnownAccounts(knownAccounts, common.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add1"), common.HexToHash("0x2d6f8a898e7dec0bb7a50e8c142be32d7c98c096ff68ed57b9b08280d9aca1ce"))
types.InsertKnownAccounts(knownAccounts, common.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add2"), map[common.Hash]common.Hash{
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000aaa"): common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000bbb"),
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ccc"): common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ddd"),
})

stateobjaddr1 := common.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add1")
stateobjaddr2 := common.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add2")

storageaddr1 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000zzz")
storageaddr21 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000aaa")
storageaddr22 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ccc")

data1 := common.BytesToHash([]byte{24})
data21 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000bbb")
data22 := common.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))

types.InsertKnownAccounts(knownAccounts, common.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add2"), common.HexToHash("0x2d6f8a898e7dec0bb7a50e8c142be32d7c98c096ff68ed57b9b08280d9aca1cf"))

stateobjaddr3 := common.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add2")
storageaddr3 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000yyy")
data3 := common.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"
types.InsertKnownAccounts(knownAccounts, common.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add2"), common.HexToHash("0x2d6f8a898e7dec0bb7a50e8c142be32d7c98c096ff68ed57b9b08280d9aca1ce"))
types.InsertKnownAccounts(knownAccounts, common.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add3"), map[common.Hash]common.Hash{
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000aaa"): common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000bbb"),
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ccc"): common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ddd"),
})

stateobjaddr4 := common.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add3")
storageaddr41 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000aaa")
storageaddr42 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000ccc")
data4 := common.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")
}
33 changes: 33 additions & 0 deletions core/state/statedb.go
Expand Up @@ -1640,6 +1640,39 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre
return s.accessList.Contains(addr, slot)
}

func (s *StateDB) ValidateKnownAccounts(knownAccounts types.KnownAccounts) error {
if knownAccounts == nil {
return nil
}

for k, v := range knownAccounts {
// check if the value is hex string or an object
switch {
case v.IsSingle():
trie, _ := s.StorageTrie(k)
if trie != nil {
actualRootHash := trie.Hash()
if *v.Single != actualRootHash {
return fmt.Errorf("invalid root hash for: %v root hash: %v actual root hash: %v", k, v.Single, actualRootHash)
}
} else {
return fmt.Errorf("Storage Trie is nil for: %v", k)
}
case v.IsStorage():
for slot, value := range v.Storage {
actualValue := s.GetState(k, slot)
if value != actualValue {
return fmt.Errorf("invalid slot value at address: %v slot: %v value: %v actual value: %v", k, slot, value, actualValue)
}
}
default:
return fmt.Errorf("impossible to validate known accounts: %v", k)
}
}

return nil
}

// convertAccountSet converts a provided account set from address keyed to hash keyed.
func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common.Hash]struct{} {
ret := make(map[common.Hash]struct{})
Expand Down
27 changes: 27 additions & 0 deletions core/txpool/list.go
Expand Up @@ -29,6 +29,7 @@ import (

"github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
Expand Down Expand Up @@ -563,6 +564,32 @@ func (l *list) Filter(costLimit *uint256.Int, gasLimit uint64) (types.Transactio
return removed, invalids
}

// FilterTxConditional returns the conditional transactions with invalid KnownAccounts
// TODO - We will also have to check block range and time stamp range!
func (l *list) FilterTxConditional(state *state.StateDB) types.Transactions {
removed := l.txs.filter(func(tx *types.Transaction) bool {
if options := tx.GetOptions(); options != nil {
err := state.ValidateKnownAccounts(options.KnownAccounts)
if err != nil {
log.Error("Error while Filtering Tx Conditional", "err", err)
return true
}

return false
}

return false
})

if len(removed) == 0 {
return nil
}

l.txs.reheap(true)

return removed
}

// Cap places a hard limit on the number of items, returning all transactions
// exceeding that limit.
func (l *list) Cap(threshold int) types.Transactions {
Expand Down
64 changes: 64 additions & 0 deletions core/txpool/list_test.go
Expand Up @@ -22,7 +22,11 @@ import (
"testing"

"github.com/holiman/uint256"
"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/common"
"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/crypto"
)
Expand Down Expand Up @@ -78,3 +82,63 @@ func BenchmarkListAdd(b *testing.B) {
}
}
}

func TestFilterTxConditional(t *testing.T) {
t.Parallel()

// Create an in memory state db to test against.
memDb := rawdb.NewMemoryDatabase()
db := state.NewDatabase(memDb)
state, _ := state.New(common.Hash{}, db, nil)

// Create a private key to sign transactions.
key, _ := crypto.GenerateKey()

// Create a list.
list := newList(true)

// Create a transaction with no defined tx options
// and add to the list.
tx := transaction(0, 1000, key)
list.Add(tx, DefaultConfig.PriceBump)

// There should be no drops at this point.
// No state has been modified.
drops := list.FilterTxConditional(state)

count := len(drops)
require.Equal(t, 0, count, "got %d filtered by TxOptions when there should not be any", count)

// Create another transaction with a known account storage root tx option
// and add to the list.
tx2 := transaction(1, 1000, key)

var options types.OptionsAA4337

options.KnownAccounts = types.KnownAccounts{
common.Address{19: 1}: &types.Value{
Single: common.HexToRefHash("0xe734938daf39aae1fa4ee64dc3155d7c049f28b57a8ada8ad9e86832e0253bef"),
},
}

state.SetState(common.Address{19: 1}, common.Hash{}, common.Hash{30: 1})
tx2.PutOptions(&options)
list.Add(tx2, DefaultConfig.PriceBump)

// There should still be no drops as no state has been modified.
drops = list.FilterTxConditional(state)

count = len(drops)
require.Equal(t, 0, count, "got %d filtered by TxOptions when there should not be any", count)

// Set state that conflicts with tx2's policy
state.SetState(common.Address{19: 1}, common.Hash{}, common.Hash{31: 1})

// tx2 should be the single transaction filtered out
drops = list.FilterTxConditional(state)

count = len(drops)
require.Equal(t, 1, count, "got %d filtered by TxOptions when there should be a single one", count)

require.Equal(t, tx2, drops[0], "Got %x, expected %x", drops[0].Hash(), tx2.Hash())
}
13 changes: 11 additions & 2 deletions core/txpool/txpool.go
Expand Up @@ -2303,10 +2303,19 @@ func (pool *TxPool) demoteUnexecutables() {
pool.enqueueTx(hash, tx, false, false)
}

pendingGauge.Dec(int64(oldsLen + dropsLen + invalidsLen))
// Drop all transactions that no longer have valid TxOptions
txConditionalsRemoved := list.FilterTxConditional(pool.currentState)

for _, tx := range txConditionalsRemoved {
hash := tx.Hash()
pool.all.Remove(hash)
log.Trace("Removed invalid conditional transaction", "hash", hash)
}

pendingGauge.Dec(int64(oldsLen + dropsLen + invalidsLen + len(txConditionalsRemoved)))

if pool.locals.contains(addr) {
localGauge.Dec(int64(oldsLen + dropsLen + invalidsLen))
localGauge.Dec(int64(oldsLen + dropsLen + invalidsLen + len(txConditionalsRemoved)))
}
// If there's a gap in front, alert (should never happen) and postpone all transactions
if list.Len() > 0 && list.txs.Get(nonce) == nil {
Expand Down
4 changes: 4 additions & 0 deletions core/txpool/txpool_test.go
Expand Up @@ -1993,6 +1993,7 @@ func TestUnderpricing(t *testing.T) {
keys[i], _ = crypto.GenerateKey()
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
}

// Generate and queue a batch of transactions, both pending and queued
txs := types.Transactions{}

Expand Down Expand Up @@ -2023,6 +2024,7 @@ func TestUnderpricing(t *testing.T) {
if err := validatePoolInternals(pool); err != nil {
t.Fatalf("pool internal state corrupted: %v", err)
}

// Ensure that adding an underpriced transaction on block limit fails
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, ErrUnderpriced) {
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
Expand Down Expand Up @@ -2064,6 +2066,7 @@ func TestUnderpricing(t *testing.T) {
if err := validatePoolInternals(pool); err != nil {
t.Fatalf("pool internal state corrupted: %v", err)
}

// Ensure that adding local transactions can push out even higher priced ones
ltx = pricedTransaction(1, 100000, big.NewInt(0), keys[2])
if err := pool.AddLocal(ltx); err != nil {
Expand Down Expand Up @@ -2263,6 +2266,7 @@ func TestUnderpricingDynamicFee(t *testing.T) {
if err := validatePoolInternals(pool); err != nil {
t.Fatalf("pool internal state corrupted: %v", err)
}

// Ensure that adding local transactions can push out even higher priced ones
ltx = dynamicFeeTx(1, 100000, big.NewInt(0), big.NewInt(0), keys[2])
if err := pool.AddLocal(ltx); err != nil {
Expand Down
38 changes: 38 additions & 0 deletions core/types/block.go
Expand Up @@ -188,6 +188,44 @@ func (h *Header) EmptyReceipts() bool {
return h.ReceiptHash == EmptyReceiptsHash
}

// ValidateBlockNumberOptions4337 validates the block range passed as in the options parameter in the conditional transaction (EIP-4337)
func (h *Header) ValidateBlockNumberOptions4337(minBlockNumber *big.Int, maxBlockNumber *big.Int) error {
currentBlockNumber := h.Number

if minBlockNumber != nil {
if currentBlockNumber.Cmp(minBlockNumber) == -1 {
return fmt.Errorf("current block number %v is less than minimum block number: %v", currentBlockNumber, minBlockNumber)
}
}

if maxBlockNumber != nil {
if currentBlockNumber.Cmp(maxBlockNumber) == 1 {
return fmt.Errorf("current block number %v is greater than maximum block number: %v", currentBlockNumber, maxBlockNumber)
}
}

return nil
}

// ValidateBlockNumberOptions4337 validates the timestamp range passed as in the options parameter in the conditional transaction (EIP-4337)
func (h *Header) ValidateTimestampOptions4337(minTimestamp *uint64, maxTimestamp *uint64) error {
currentBlockTime := h.Time

if minTimestamp != nil {
if currentBlockTime < *minTimestamp {
return fmt.Errorf("current block time %v is less than minimum timestamp: %v", currentBlockTime, minTimestamp)
}
}

if maxTimestamp != nil {
if currentBlockTime > *maxTimestamp {
return fmt.Errorf("current block time %v is greater than maximum timestamp: %v", currentBlockTime, maxTimestamp)
}
}

return nil
}

// Body is a simple (mutable, non-safe) data container for storing and moving
// a block's data contents (transactions and uncles) together.
type Body struct {
Expand Down

0 comments on commit 67fc4d8

Please sign in to comment.