Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a new RPC endpoint (bor_sendRawTransactionConditional) to support EIP-4337 Bundled Transactions #945

Merged
merged 25 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
884b124
added new api to support conditional transactions (EIP-4337) (#700)
pratikspatil024 Feb 6, 2023
84ed058
Refactored the code and updated the miner to check for the validity o…
pratikspatil024 Apr 18, 2023
874952c
Merge branch 'develop' of https://github.com/maticnetwork/bor into aa…
pratikspatil024 Jul 3, 2023
0f9d7d6
Added filtering of conditional transactions in txpool (#920)
pratikspatil024 Jul 4, 2023
4b086aa
bug fix
pratikspatil024 Jul 25, 2023
af48c13
Merge branch 'develop' of https://github.com/maticnetwork/bor into aa…
pratikspatil024 Jul 28, 2023
10ed2d9
Supporting nil knownAccounts
pratikspatil024 Jul 31, 2023
0d2def5
lints
pratikspatil024 Jul 31, 2023
6826ae7
bundled transactions are not announced/broadcasted to the peers
pratikspatil024 Aug 9, 2023
d91cc31
Merge branch 'develop' of https://github.com/maticnetwork/bor into aa…
pratikspatil024 Aug 9, 2023
99cb890
fixed after upstream merge
pratikspatil024 Aug 9, 2023
7d837dc
few fixes
pratikspatil024 Aug 9, 2023
c4e0a5c
sentry reject conditional transaction
pratikspatil024 Aug 11, 2023
7deba1f
Changed the namespace of conditional transaction API from `eth` to `b…
pratikspatil024 Sep 6, 2023
b737751
Merge branch 'develop' into aa-4337
pratikspatil024 Sep 11, 2023
6b10621
addressed comments
pratikspatil024 Sep 12, 2023
566386e
reverted changes in ValidateKnownAccounts
pratikspatil024 Sep 12, 2023
abe87af
addressed comments and removed unwanted code
pratikspatil024 Sep 12, 2023
de1c323
Merge branch 'v1.0.0-beta-candidate' into aa-4337
pratikspatil024 Sep 12, 2023
bce5615
addressed comments
pratikspatil024 Sep 12, 2023
768308a
bug fix
pratikspatil024 Sep 12, 2023
674997e
lint
pratikspatil024 Sep 12, 2023
507bc56
Merge branch 'develop' of https://github.com/maticnetwork/bor into aa…
pratikspatil024 Sep 12, 2023
b00bc53
removed licence from core/types/transaction_conditional_test.go
pratikspatil024 Sep 12, 2023
36aaae6
Merge branch 'develop' of https://github.com/maticnetwork/bor into aa…
pratikspatil024 Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions accounts/abi/bind/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ type ContractTransactor interface {

// SendTransaction injects the transaction into the pending pool for execution.
SendTransaction(ctx context.Context, tx *types.Transaction) error

// SendTransactionConditional injects the conditional transaction into the pending pool for execution after verification.
SendTransactionConditional(ctx context.Context, tx *types.Transaction, knownAccounts map[string]map[common.Address]interface{}) error
}

// ContractFilterer defines the methods needed to access log events using one-off
Expand Down
5 changes: 5 additions & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
return nil
}

// a replica of above `SendTransaction` function
func (b *SimulatedBackend) SendTransactionConditional(ctx context.Context, tx *types.Transaction, knownAccounts map[string]map[common.Address]interface{}) error {
pratikspatil024 marked this conversation as resolved.
Show resolved Hide resolved
return b.SendTransaction(ctx, tx)
}

// FilterLogs executes a log filter operation, blocking during execution and
// returning all the results in one batch.
//
Expand Down
4 changes: 4 additions & 0 deletions accounts/abi/bind/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transac
return nil
}

func (mt *mockTransactor) SendTransactionConditional(ctx context.Context, tx *types.Transaction, ownAccounts map[string]map[common.Address]interface{}) error {
return nil
}

type mockCaller struct {
codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int
Expand Down
9 changes: 8 additions & 1 deletion common/types.go
Original file line number Diff line number Diff line change
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
72 changes: 72 additions & 0 deletions core/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ package state

import (
"bytes"
"fmt"
"math/big"
"testing"

"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 +275,73 @@ 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)

trieTemp, _ := s.state.StorageTrie(stateobjaddr1)
fmt.Println("\nStorageTrie.Hash()", trieTemp.Hash())
manav2401 marked this conversation as resolved.
Show resolved Hide resolved

if err := s.state.ValidateKnownAccounts(knownAccounts); err != nil {
t.Fatalf(err.Error())
manav2401 marked this conversation as resolved.
Show resolved Hide resolved
}

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
if err := s.state.ValidateKnownAccounts(knownAccounts); err == nil {
t.Fatalf("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
if err := s.state.ValidateKnownAccounts(knownAccounts); err == nil {
t.Fatalf("should have been an error")
}
}
34 changes: 34 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -1640,6 +1640,40 @@ 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)
marcello33 marked this conversation as resolved.
Show resolved Hide resolved
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:
trie, _ := s.StorageTrie(k)
return fmt.Errorf("invalid root hash for: %v root hash: %v actual root hash: %v", k, v.Single, trie.Hash())
manav2401 marked this conversation as resolved.
Show resolved Hide resolved
}
}

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
Original file line number Diff line number Diff line change
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
}

// 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 {
pratikspatil024 marked this conversation as resolved.
Show resolved Hide resolved
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
68 changes: 68 additions & 0 deletions core/txpool/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import (

"github.com/holiman/uint256"

"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 +81,68 @@ 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)

if count := len(drops); count != 0 {
t.Fatalf("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)

if count := len(drops); count != 0 {
t.Fatalf("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)

if count := len(drops); count != 1 {
t.Fatalf("got %d filtered by TxOptions when there should be a single one", count)
manav2401 marked this conversation as resolved.
Show resolved Hide resolved
}

if drops[0] != tx2 {
t.Fatalf("Got %x, expected %x", drops[0].Hash(), tx2.Hash())
}
}
13 changes: 11 additions & 2 deletions core/txpool/txpool.go
Original file line number Diff line number Diff line change
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()
log.Trace("Removed invalid conditional transaction", "hash", hash)
pratikspatil024 marked this conversation as resolved.
Show resolved Hide resolved
pool.all.Remove(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
Original file line number Diff line number Diff line change
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