Skip to content

Commit

Permalink
added new api to support conditional transactions (EIP-4337)
Browse files Browse the repository at this point in the history
  • Loading branch information
pratikspatil024 committed Jan 31, 2023
1 parent e06376c commit ca177db
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 15 deletions.
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
27 changes: 27 additions & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"math/big"
"reflect"
"sync"
"time"

Expand Down Expand Up @@ -675,6 +676,32 @@ 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 {
// check knownAccounts
currentState, _ := b.blockchain.State()

for k, v := range knownAccounts["knownAccounts"] {
// check if the value is hex string or an object
if object, ok := v.(string); ok {
actualRootHash := currentState.StorageTrie(k).Hash()
if common.HexToHash(object) != actualRootHash {
return fmt.Errorf("invalid root hash for: %v root hash: %v actual root hash: %v", k, common.HexToHash(object), actualRootHash)
}
} else if object, ok := v.(map[string]interface{}); ok {
for slot, value := range object {
actualValue := currentState.GetState(k, common.HexToHash(slot))
if common.HexToHash(value.(string)) != actualValue {
return fmt.Errorf("invalid slot value at address: %v slot: %v value: %v actual value: %v", k, slot, value, actualValue)
}
}
} else {
return fmt.Errorf("invalid type in knownAccounts %v", reflect.TypeOf(v))
}
}
return b.SendTransaction(ctx, tx)
}

// FilterLogs executes a log filter operation, blocking during execution and
// returning all the results in one batch.
//
Expand Down
33 changes: 18 additions & 15 deletions accounts/abi/bind/backends/simulated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,17 @@ func TestSimulatedBackend(t *testing.T) {

var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")

// the following is based on this contract:
// contract T {
// event received(address sender, uint amount, bytes memo);
// event receivedAddr(address sender);
// the following is based on this contract:
// contract T {
// event received(address sender, uint amount, bytes memo);
// event receivedAddr(address sender);
//
// function receive(bytes calldata memo) external payable returns (string memory res) {
// emit received(msg.sender, msg.value, memo);
// emit receivedAddr(msg.sender);
// return "hello world";
// }
// }
// function receive(bytes calldata memo) external payable returns (string memory res) {
// emit received(msg.sender, msg.value, memo);
// emit receivedAddr(msg.sender);
// return "hello world";
// }
// }
const abiJSON = `[ { "constant": false, "inputs": [ { "name": "memo", "type": "bytes" } ], "name": "receive", "outputs": [ { "name": "res", "type": "string" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" }, { "indexed": false, "name": "amount", "type": "uint256" }, { "indexed": false, "name": "memo", "type": "bytes" } ], "name": "received", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" } ], "name": "receivedAddr", "type": "event" } ]`
const abiBin = `0x608060405234801561001057600080fd5b506102a0806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029`
const deployedCode = `60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029`
Expand Down Expand Up @@ -1000,7 +1000,8 @@ func TestCodeAt(t *testing.T) {
}

// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
//
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
func TestPendingAndCallContract(t *testing.T) {
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
sim := simTestBackend(testAddr)
Expand Down Expand Up @@ -1211,10 +1212,11 @@ func TestFork(t *testing.T) {
Example contract to test event emission:
pragma solidity >=0.7.0 <0.9.0;
contract Callable {
event Called();
function Call() public { emit Called(); }
}
contract Callable {
event Called();
function Call() public { emit Called(); }
}
*/
const callableAbi = "[{\"anonymous\":false,\"inputs\":[],\"name\":\"Called\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"Call\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"

Expand All @@ -1232,6 +1234,7 @@ const callableBin = "6080604052348015600f57600080fd5b5060998061001e6000396000f3f
// 7. Mine two blocks to trigger a reorg.
// 8. Check that the event was removed.
// 9. Re-send the transaction and mine a block.
//
// 10. Check that the event was reborn.
func TestForkLogsReborn(t *testing.T) {
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
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 @@ -74,6 +74,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: 9 additions & 0 deletions ethclient/ethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,15 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
}

func (ec *Client) SendTransactionConditional(ctx context.Context, tx *types.Transaction, knownAccounts map[string]map[common.Address]interface{}) error {
data, err := tx.MarshalBinary()
if err != nil {
return err
}

return ec.c.CallContext(ctx, nil, "eth_sendRawTransactionConditional", hexutil.Encode(data), knownAccounts)
}

func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
Expand Down
36 changes: 36 additions & 0 deletions ethclient/ethclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,10 @@ func testCallContract(t *testing.T, client *rpc.Client) {
func testAtFunctions(t *testing.T, client *rpc.Client) {
ec := NewClient(client)

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

// send a transaction for some interesting pending status
sendTransaction(ec)
time.Sleep(100 * time.Millisecond)
Expand Down Expand Up @@ -694,3 +698,35 @@ func sendTransaction(ec *Client) error {
}
return ec.SendTransaction(context.Background(), tx)
}

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
}

return ec.SendTransactionConditional(context.Background(), tx, map[string]map[common.Address]interface{}{
"knownAccounts": {
common.HexToAddress("0xadd1add1add1add1add1add1add1add1add1add1"): "0x313AaDcA1750CaadC7BCB26FF08175c95DCf8E38",
common.HexToAddress("0xadd2add2add2add2add2add2add2add2add2add2"): "0x413AaDcA1750CaadC7BCB26FF08175c95DCf8E38",
},
})
}
35 changes: 35 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"math/big"
"reflect"
"runtime"
"strings"
"time"
Expand Down Expand Up @@ -1928,6 +1929,40 @@ 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, knownAccounts map[string]map[common.Address]interface{}) (common.Hash, error) {
tx := new(types.Transaction)
if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err
}

// check knownAccounts
currentBlock := s.b.CurrentBlock()
currentState, _, _ := s.b.StateAndHeaderByNumber(ctx, rpc.BlockNumber(currentBlock.Number().Int64()))

for k, v := range knownAccounts["knownAccounts"] {
// check if the value is hex string or an object
if object, ok := v.(string); ok {
actualRootHash := currentState.StorageTrie(k).Hash()
if common.HexToHash(object) != actualRootHash {
return common.Hash{}, fmt.Errorf("invalid root hash for: %v root hash: %v actual root hash: %v", k, common.HexToHash(object), actualRootHash)
}
} else if object, ok := v.(map[string]interface{}); ok {
for slot, value := range object {
actualValue := currentState.GetState(k, common.HexToHash(slot))
if common.HexToHash(value.(string)) != actualValue {
return common.Hash{}, fmt.Errorf("invalid slot value at address: %v slot: %v value: %v actual value: %v", k, slot, value, actualValue)
}
}
} else {
return common.Hash{}, fmt.Errorf("invalid type in knownAccounts %v", reflect.TypeOf(v))
}
}

return SubmitTransaction(ctx, s.b, tx)
}

// Sign calculates an ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message).
//
Expand Down
8 changes: 8 additions & 0 deletions internal/jsre/deps/web3.js
Original file line number Diff line number Diff line change
Expand Up @@ -5383,6 +5383,13 @@ var methods = function () {
inputFormatter: [null]
});

var sendRawTransactionConditional = new Method({
name: 'sendRawTransactionConditional',
call: 'eth_sendRawTransactionConditional',
params: 2,
inputFormatter: [null]
});

var sendTransaction = new Method({
name: 'sendTransaction',
call: 'eth_sendTransaction',
Expand Down Expand Up @@ -5471,6 +5478,7 @@ var methods = function () {
call,
estimateGas,
sendRawTransaction,
sendRawTransactionConditional,
signTransaction,
sendTransaction,
sign,
Expand Down
5 changes: 5 additions & 0 deletions mobile/ethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package geth
import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
Expand Down Expand Up @@ -314,3 +315,7 @@ func (ec *EthereumClient) EstimateGas(ctx *Context, msg *CallMsg) (gas int64, _
func (ec *EthereumClient) SendTransaction(ctx *Context, tx *Transaction) error {
return ec.client.SendTransaction(ctx.context, tx.tx)
}

func (ec *EthereumClient) SendTransactionConditional(ctx *Context, tx *Transaction, knownAccounts map[string]map[common.Address]interface{}) error {
return ec.client.SendTransactionConditional(ctx.context, tx.tx, knownAccounts)
}

0 comments on commit ca177db

Please sign in to comment.