Skip to content

Commit

Permalink
contracts: add direct contract backend (#13554)
Browse files Browse the repository at this point in the history
relates to #13383
takes a chunk of changes out of bigger shutter PR
#13553 in which we collect data
from various shutter smart contracts

context on this PR:
- smart contracts objects generated by `abigen` rely on
`bind.ContractBackend` in order to be used
- we currently do not have an implementation of that interface in Erigon
(apart from `SimulatedBackend` which is used in testing - inherited from
Geth)
- in this PR im adding an implementation of `ContractBackend` which uses
the `EthApi` jsonrpc object and directly calls it instead of going via
http rpc and/or websocket (for log update subscriptions) - this is
analogous to how we have `direct` and `remote` grpc clients for our
grpc-based components
- ive also wired the new contract backend to the shutter pool - this
will be used in a subsequent PR (check the aforementioned PR
#13553 for usages)

note that this is needed also for @shohamc1 's work for native AA which
is why it will be good to merge it in main
  • Loading branch information
taratorio authored Jan 27, 2025
1 parent a9ec8a0 commit 2b92946
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 59 deletions.
2 changes: 1 addition & 1 deletion cmd/rpcdaemon/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ func EmbeddedServices(ctx context.Context,
blockReader services.FullBlockReader, ethBackendServer remote.ETHBACKENDServer, txPoolServer txpool.TxpoolServer,
miningServer txpool.MiningServer, stateDiffClient StateChangesClient,
logger log.Logger,
) (eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient, stateCache kvcache.Cache, ff *rpchelper.Filters, err error) {
) (eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient, stateCache kvcache.Cache, ff *rpchelper.Filters) {
if stateCacheCfg.CacheSize > 0 {
// notification about new blocks (state stream) doesn't work now inside erigon - because
// erigon does send this stream to privateAPI (erigon with enabled rpc, still have enabled privateAPI).
Expand Down
196 changes: 196 additions & 0 deletions contracts/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package contracts

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

ethereum "github.com/erigontech/erigon"
libcommon "github.com/erigontech/erigon-lib/common"
"github.com/erigontech/erigon-lib/common/hexutil"
"github.com/erigontech/erigon-lib/common/hexutility"
"github.com/erigontech/erigon/accounts/abi/bind"
"github.com/erigontech/erigon/core/types"
"github.com/erigontech/erigon/eth/filters"
"github.com/erigontech/erigon/event"
"github.com/erigontech/erigon/rpc"
"github.com/erigontech/erigon/turbo/adapter/ethapi"
"github.com/erigontech/erigon/turbo/jsonrpc"
)

var _ bind.ContractBackend = Backend{}

type Backend struct {
api jsonrpc.EthAPI
}

func NewDirectBackend(api jsonrpc.EthAPI) Backend {
return Backend{
api: api,
}
}

func (b Backend) CodeAt(ctx context.Context, contract libcommon.Address, blockNum *big.Int) ([]byte, error) {
return b.api.GetCode(ctx, contract, BlockNumArg(blockNum))
}

func (b Backend) CallContract(ctx context.Context, callMsg ethereum.CallMsg, blockNum *big.Int) ([]byte, error) {
return b.api.Call(ctx, CallArgsFromCallMsg(callMsg), BlockNumArg(blockNum), nil)
}

func (b Backend) PendingCodeAt(ctx context.Context, account libcommon.Address) ([]byte, error) {
return b.api.GetCode(ctx, account, PendingBlockNumArg())
}

func (b Backend) PendingNonceAt(ctx context.Context, account libcommon.Address) (uint64, error) {
count, err := b.api.GetTransactionCount(ctx, account, PendingBlockNumArg())
if err != nil {
return 0, err
}

return uint64(*count), nil
}

func (b Backend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
price, err := b.api.GasPrice(ctx)
if err != nil {
return nil, err
}

return price.ToInt(), nil
}

func (b Backend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
callArgs := CallArgsFromCallMsg(call)
gas, err := b.api.EstimateGas(ctx, &callArgs, nil, nil)
if err != nil {
return 0, err
}

return gas.Uint64(), nil
}

func (b Backend) SendTransaction(ctx context.Context, txn types.Transaction) error {
var buf bytes.Buffer
err := txn.MarshalBinary(&buf)
if err != nil {
return err
}

_, err = b.api.SendRawTransaction(ctx, buf.Bytes())
return err
}

func (b Backend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
logs, err := b.api.GetLogs(ctx, filters.FilterCriteria(query))
if err != nil {
return nil, err
}

res := make([]types.Log, len(logs))
for i, log := range logs {
res[i] = *log
}

return res, nil
}

func (b Backend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
resc, closec := make(chan any), make(chan any)
ctx = rpc.ContextWithNotifier(ctx, rpc.NewLocalNotifier("eth", resc, closec))
_, err := b.api.Logs(ctx, filters.FilterCriteria(query))
if err != nil {
return nil, err
}

sub := event.NewSubscription(func(quit <-chan struct{}) error {
for {
select {
case <-quit:
close(closec)
return nil
case res := <-resc:
log, ok := res.(*types.Log)
if !ok {
return fmt.Errorf("unexpected type %T in SubscribeFilterLogs", res)
}

ch <- *log
}
}
})

return sub, nil
}

func BlockNumArg(blockNum *big.Int) rpc.BlockNumberOrHash {
var blockRef rpc.BlockReference
if blockNum == nil {
blockRef = rpc.LatestBlock
} else {
blockRef = rpc.AsBlockReference(blockNum)
}

return rpc.BlockNumberOrHash(blockRef)
}

func PendingBlockNumArg() rpc.BlockNumberOrHash {
return rpc.BlockNumberOrHash(rpc.PendingBlock)
}

func CallArgsFromCallMsg(callMsg ethereum.CallMsg) ethapi.CallArgs {
var emptyAddress libcommon.Address
var from *libcommon.Address
if callMsg.From != emptyAddress {
from = &callMsg.From
}

var gas *hexutil.Uint64
if callMsg.Gas != 0 {
gas = (*hexutil.Uint64)(&callMsg.Gas)
}

var gasPrice *hexutil.Big
if callMsg.GasPrice != nil {
gasPrice = (*hexutil.Big)(callMsg.GasPrice.ToBig())
}

var feeCap *hexutil.Big
if callMsg.FeeCap != nil {
feeCap = (*hexutil.Big)(callMsg.FeeCap.ToBig())
}

var maxFeePerBlobGas *hexutil.Big
if callMsg.MaxFeePerBlobGas != nil {
maxFeePerBlobGas = (*hexutil.Big)(callMsg.MaxFeePerBlobGas.ToBig())
}

var value *hexutil.Big
if callMsg.Value != nil {
value = (*hexutil.Big)(callMsg.Value.ToBig())
}

var data *hexutility.Bytes
if callMsg.Data != nil {
b := hexutility.Bytes(callMsg.Data)
data = &b
}

var accessList *types.AccessList
if callMsg.AccessList != nil {
accessList = &callMsg.AccessList
}

return ethapi.CallArgs{
From: from,
To: callMsg.To,
Gas: gas,
GasPrice: gasPrice,
MaxFeePerGas: feeCap,
MaxFeePerBlobGas: maxFeePerBlobGas,
Value: value,
Data: data,
AccessList: accessList,
}
}
Loading

0 comments on commit 2b92946

Please sign in to comment.