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

Integration API for EIP-4337 bundler with an L2 validator/sequencer #1357

Merged
merged 5 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions accounts/abi/bind/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
)

var (
Expand Down Expand Up @@ -96,6 +97,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, opts ethapi.TransactionOpts) error
}

// ContractFilterer defines the methods needed to access log events using one-off
Expand Down
13 changes: 13 additions & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
Expand Down Expand Up @@ -677,6 +678,18 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
return nil
}

// SendTransaction updates the pending block to include the given transaction.
func (b *SimulatedBackend) SendTransactionConditional(ctx context.Context, tx *types.Transaction, opts ethapi.TransactionOpts) error {
state, err := b.blockchain.State()
if err != nil {
return err
}
if err := opts.Check(b.pendingBlock.NumberU64(), b.pendingBlock.Time(), state); err != nil {
return err
}
return b.SendTransaction(ctx, tx)
}

// FilterLogs executes a log filter operation, blocking during execution and
// returning all the results in one batch.
//
Expand Down
5 changes: 5 additions & 0 deletions accounts/abi/bind/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -74,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, opts ethapi.TransactionOpts) error {
return nil
}

type mockCaller struct {
codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int
Expand Down
8 changes: 8 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,14 @@ func (s *StateDB) GetCode(addr common.Address) []byte {
return nil
}

func (s *StateDB) GetRoot(addr common.Address) common.Hash {
stateObject := s.getStateObject(addr)
Mister-EA marked this conversation as resolved.
Show resolved Hide resolved
if stateObject != nil {
return stateObject.data.Root
}
return common.Hash{}
}

func (s *StateDB) GetCodeSize(addr common.Address) int {
stateObject := s.getStateObject(addr)
if stateObject != nil {
Expand Down
13 changes: 13 additions & 0 deletions ethclient/ethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
)

Expand Down Expand Up @@ -582,6 +583,18 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
}

// SendTransactionConditional injects a signed transaction into the pending pool for execution.
//
// If the transaction was a contract creation use the TransactionReceipt method to get the
// contract address after the transaction has been mined.
func (ec *Client) SendTransactionConditional(ctx context.Context, tx *types.Transaction, opts ethapi.TransactionOpts) error {
data, err := tx.MarshalBinary()
if err != nil {
return err
}
return ec.c.CallContext(ctx, nil, "eth_sendRawTransactionConditional", hexutil.Encode(data), opts)
}

func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
Expand Down
46 changes: 46 additions & 0 deletions ethclient/ethclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
Expand Down Expand Up @@ -376,6 +377,9 @@ func TestEthClient(t *testing.T) {
"TestDiffAccounts": {
func(t *testing.T) { testDiffAccounts(t, client) },
},
"TestSendTransactionConditional": {
func(t *testing.T) { testSendTransactionConditional(t, client) },
},
// DO not have TestAtFunctions now, because we do not have pending block now
}

Expand Down Expand Up @@ -660,3 +664,45 @@ func testDiffAccounts(t *testing.T, client *rpc.Client) {
}
}
}

func testSendTransactionConditional(t *testing.T, client *rpc.Client) {
ec := NewClient(client)

if err := sendTransactionConditional(ec); err != nil {
t.Fatalf("error: %v", err)
}
}

func sendTransactionConditional(ec *Client) error {
chainID, err := ec.ChainID(context.Background())
if err != nil {
return err
}

nonce, err := ec.PendingNonceAt(context.Background(), testAddr)
if err != nil {
return err
}

signer := types.LatestSignerForChainID(chainID)

tx, err := types.SignNewTx(testKey, signer, &types.LegacyTx{
Nonce: nonce,
To: &common.Address{2},
Value: big.NewInt(1),
Gas: 22000,
GasPrice: big.NewInt(params.InitialBaseFee),
})
if err != nil {
return err
}

root := common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
return ec.SendTransactionConditional(context.Background(), tx, ethapi.TransactionOpts{
KnownAccounts: map[common.Address]ethapi.AccountStorage{
testAddr: ethapi.AccountStorage{
StorageRoot: &root,
},
},
})
}
18 changes: 18 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2137,6 +2137,24 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, input
return SubmitTransaction(ctx, s.b, tx)
}

// SendRawTransactionConditional will add the signed transaction to the transaction pool.
// The sender/bundler is responsible for signing the transaction
func (s *PublicTransactionPoolAPI) SendRawTransactionConditional(ctx context.Context, input hexutil.Bytes, opts TransactionOpts) (common.Hash, error) {
tx := new(types.Transaction)
if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err
}
header := s.b.CurrentHeader()
state, _, err := s.b.StateAndHeaderByNumber(ctx, rpc.BlockNumber(header.Number.Int64()))
if state == nil || err != nil {
return common.Hash{}, err
}
if err := opts.Check(header.Number.Uint64(), header.Time, state); err != nil {
return common.Hash{}, err
}
return SubmitTransaction(ctx, s.b, tx)
}

// Sign calculates an ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message).
//
Expand Down
58 changes: 58 additions & 0 deletions internal/ethapi/gen_tx_opts_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 94 additions & 0 deletions internal/ethapi/transaction_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package ethapi

import (
"bytes"
"encoding/json"
"errors"

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

type AccountStorage struct {
StorageRoot *common.Hash
StorageSlots map[common.Hash]common.Hash
}

func (a *AccountStorage) UnmarshalJSON(data []byte) error {
var hash common.Hash
if err := json.Unmarshal(data, &hash); err == nil {
a.StorageRoot = &hash
return nil
}
return json.Unmarshal(data, &a.StorageSlots)
}

func (a AccountStorage) MarshalJSON() ([]byte, error) {
if a.StorageRoot != nil {
return json.Marshal(*a.StorageRoot)
}
return json.Marshal(a.StorageSlots)
}

type KnownAccounts map[common.Address]AccountStorage

// It is known that marshaling is broken
// https://github.com/golang/go/issues/55890

//go:generate go run github.com/fjl/gencodec -type TransactionOpts -out gen_tx_opts_json.go
type TransactionOpts struct {
KnownAccounts KnownAccounts `json:"knownAccounts"`
BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"`
BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"`
TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"`
TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"`
}

const MaxNumberOfEntries = 1000

func (o *TransactionOpts) Check(blockNumber uint64, timeStamp uint64, statedb *state.StateDB) error {
if o.BlockNumberMin != nil && blockNumber < uint64(*o.BlockNumberMin) {
return errors.New("BlockNumberMin condition not met")
}
if o.BlockNumberMax != nil && blockNumber > uint64(*o.BlockNumberMax) {
return errors.New("BlockNumberMax condition not met")
}
if o.TimestampMin != nil && timeStamp < uint64(*o.TimestampMin) {
return errors.New("TimestampMin condition not met")
}
if o.TimestampMax != nil && timeStamp > uint64(*o.TimestampMax) {
return errors.New("TimestampMax condition not met")
}
counter := 0
for _, account := range o.KnownAccounts {
if account.StorageRoot != nil {
counter += 1
} else if account.StorageSlots != nil {
counter += len(account.StorageSlots)
}
}
if counter > MaxNumberOfEntries {
return errors.New("knownAccounts too large")
}
return o.CheckStorage(statedb)
}

func (o *TransactionOpts) CheckStorage(statedb *state.StateDB) error {
for address, accountStorage := range o.KnownAccounts {
if accountStorage.StorageRoot != nil {
rootHash := statedb.GetRoot(address)
if rootHash != *accountStorage.StorageRoot {
return errors.New("storage root hash condition not met")
}
} else if len(accountStorage.StorageSlots) > 0 {
for slot, value := range accountStorage.StorageSlots {
stored := statedb.GetState(address, slot)
if !bytes.Equal(stored.Bytes(), value.Bytes()) {
return errors.New("storage slot value condition not met")
}
}
}
}
return nil
}
Loading