EVM Abstraction (#848)

This commit is contained in:
kevin 2022-12-17 12:31:19 -07:00 committed by Martin
parent 55f3ad3caa
commit 6a24f4ac30
4 changed files with 337 additions and 140 deletions

View File

@ -274,10 +274,10 @@ func contractGetTransfersFromTx(tx *bchain.RpcTransaction) (bchain.TokenTransfer
}
func (b *EthereumRPC) ethCall(data, to string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
var r string
err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{
err := b.RPC.CallContext(ctx, &r, "eth_call", map[string]interface{}{
"data": data,
"to": to,
}, "latest")

View File

@ -12,10 +12,10 @@ import (
"sync"
"time"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/golang/glog"
@ -24,18 +24,18 @@ import (
"github.com/trezor/blockbook/common"
)
// EthereumNet type specifies the type of ethereum network
type EthereumNet uint32
// Network type specifies the type of ethereum network
type Network uint32
const (
// MainNet is production network
MainNet EthereumNet = 1
MainNet Network = 1
// TestNet is Ropsten test network
TestNet EthereumNet = 3
TestNet Network = 3
// TestNetGoerli is Goerli test network
TestNetGoerli EthereumNet = 5
TestNetGoerli Network = 5
// TestNetSepolia is Sepolia test network
TestNetSepolia EthereumNet = 11155111
TestNetSepolia Network = 11155111
)
// Configuration represents json config file
@ -56,19 +56,22 @@ type Configuration struct {
// EthereumRPC is an interface to JSON-RPC eth service.
type EthereumRPC struct {
*bchain.BaseChain
client *ethclient.Client
rpc *rpc.Client
timeout time.Duration
Client bchain.EVMClient
RPC bchain.EVMRPCClient
MainNetChainID Network
Timeout time.Duration
Parser *EthereumParser
PushHandler func(bchain.NotificationType)
OpenRPC func(string) (bchain.EVMRPCClient, bchain.EVMClient, error)
Mempool *bchain.MempoolEthereumType
mempoolInitialized bool
bestHeaderLock sync.Mutex
bestHeader *ethtypes.Header
bestHeader bchain.EVMHeader
bestHeaderTime time.Time
chanNewBlock chan *ethtypes.Header
newBlockSubscription *rpc.ClientSubscription
chanNewTx chan ethcommon.Hash
newTxSubscription *rpc.ClientSubscription
NewBlock bchain.EVMNewBlockSubscriber
newBlockSubscription bchain.EVMClientSubscription
NewTx bchain.EVMNewTxSubscriber
newTxSubscription bchain.EVMClientSubscription
ChainConfig *Configuration
}
@ -88,15 +91,8 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
c.BlockAddressesToKeep = 100
}
rc, ec, err := openRPC(c.RPCURL)
if err != nil {
return nil, err
}
s := &EthereumRPC{
BaseChain: &bchain.BaseChain{},
client: ec,
rpc: rc,
ChainConfig: &c,
}
@ -104,70 +100,46 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
// always create parser
s.Parser = NewEthereumParser(c.BlockAddressesToKeep, c.AddressAliases)
s.timeout = time.Duration(c.RPCTimeout) * time.Second
// new blocks notifications handling
// the subscription is done in Initialize
s.chanNewBlock = make(chan *ethtypes.Header)
go func() {
for {
h, ok := <-s.chanNewBlock
if !ok {
break
}
glog.V(2).Info("rpc: new block header ", h.Number)
// update best header to the new header
s.bestHeaderLock.Lock()
s.bestHeader = h
s.bestHeaderTime = time.Now()
s.bestHeaderLock.Unlock()
// notify blockbook
pushHandler(bchain.NotificationNewBlock)
}
}()
// new mempool transaction notifications handling
// the subscription is done in Initialize
s.chanNewTx = make(chan ethcommon.Hash)
go func() {
for {
t, ok := <-s.chanNewTx
if !ok {
break
}
hex := t.Hex()
if glog.V(2) {
glog.Info("rpc: new tx ", hex)
}
s.Mempool.AddTransactionToMempool(hex)
pushHandler(bchain.NotificationNewTx)
}
}()
s.Timeout = time.Duration(c.RPCTimeout) * time.Second
s.PushHandler = pushHandler
return s, nil
}
func openRPC(url string) (*rpc.Client, *ethclient.Client, error) {
rc, err := rpc.Dial(url)
if err != nil {
return nil, nil, err
}
ec := ethclient.NewClient(rc)
return rc, ec, nil
}
// Initialize initializes ethereum rpc interface
func (b *EthereumRPC) Initialize() error {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
b.OpenRPC = func(url string) (bchain.EVMRPCClient, bchain.EVMClient, error) {
r, err := rpc.Dial(url)
if err != nil {
return nil, nil, err
}
rc := &EthereumRPCClient{Client: r}
ec := &EthereumClient{Client: ethclient.NewClient(r)}
return rc, ec, nil
}
rc, ec, err := b.OpenRPC(b.ChainConfig.RPCURL)
if err != nil {
return err
}
// set chain specific
b.Client = ec
b.RPC = rc
b.MainNetChainID = MainNet
b.NewBlock = &EthereumNewBlock{channel: make(chan *types.Header)}
b.NewTx = &EthereumNewTx{channel: make(chan ethcommon.Hash)}
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
id, err := b.client.NetworkID(ctx)
id, err := b.Client.NetworkID(ctx)
if err != nil {
return err
}
// parameters for getInfo request
switch EthereumNet(id.Uint64()) {
switch Network(id.Uint64()) {
case MainNet:
b.Testnet = false
b.Network = "livenet"
@ -225,13 +197,26 @@ func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOu
}
func (b *EthereumRPC) subscribeEvents() error {
// subscriptions
if err := b.subscribe(func() (*rpc.ClientSubscription, error) {
// new block notifications handling
go func() {
for {
h, ok := b.NewBlock.Read()
if !ok {
break
}
b.UpdateBestHeader(h)
// notify blockbook
b.PushHandler(bchain.NotificationNewBlock)
}
}()
// new block subscription
if err := b.subscribe(func() (bchain.EVMClientSubscription, error) {
// invalidate the previous subscription - it is either the first one or there was an error
b.newBlockSubscription = nil
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads")
sub, err := b.RPC.EthSubscribe(ctx, b.NewBlock.Channel(), "newHeads")
if err != nil {
return nil, errors.Annotatef(err, "EthSubscribe newHeads")
}
@ -242,12 +227,29 @@ func (b *EthereumRPC) subscribeEvents() error {
return err
}
if err := b.subscribe(func() (*rpc.ClientSubscription, error) {
// new mempool transaction notifications handling
go func() {
for {
t, ok := b.NewTx.Read()
if !ok {
break
}
hex := t.Hex()
if glog.V(2) {
glog.Info("rpc: new tx ", hex)
}
b.Mempool.AddTransactionToMempool(hex)
b.PushHandler(bchain.NotificationNewTx)
}
}()
// new mempool transaction subscription
if err := b.subscribe(func() (bchain.EVMClientSubscription, error) {
// invalidate the previous subscription - it is either the first one or there was an error
b.newTxSubscription = nil
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
sub, err := b.rpc.EthSubscribe(ctx, b.chanNewTx, "newPendingTransactions")
sub, err := b.RPC.EthSubscribe(ctx, b.NewTx.Channel(), "newPendingTransactions")
if err != nil {
return nil, errors.Annotatef(err, "EthSubscribe newPendingTransactions")
}
@ -262,7 +264,7 @@ func (b *EthereumRPC) subscribeEvents() error {
}
// subscribe subscribes notification and tries to resubscribe in case of error
func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error {
func (b *EthereumRPC) subscribe(f func() (bchain.EVMClientSubscription, error)) error {
s, err := f()
if err != nil {
return err
@ -308,27 +310,28 @@ func (b *EthereumRPC) closeRPC() {
if b.newTxSubscription != nil {
b.newTxSubscription.Unsubscribe()
}
if b.rpc != nil {
b.rpc.Close()
if b.RPC != nil {
b.RPC.Close()
}
}
func (b *EthereumRPC) reconnectRPC() error {
glog.Info("Reconnecting RPC")
b.closeRPC()
rc, ec, err := openRPC(b.ChainConfig.RPCURL)
rc, ec, err := b.OpenRPC(b.ChainConfig.RPCURL)
if err != nil {
return err
}
b.rpc = rc
b.client = ec
b.RPC = rc
b.Client = ec
return b.subscribeEvents()
}
// Shutdown cleans up rpc interface to ethereum
func (b *EthereumRPC) Shutdown(ctx context.Context) error {
b.closeRPC()
close(b.chanNewBlock)
b.NewBlock.Close()
b.NewTx.Close()
glog.Info("rpc: shutdown")
return nil
}
@ -380,26 +383,26 @@ func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
id, err := b.client.NetworkID(ctx)
id, err := b.Client.NetworkID(ctx)
if err != nil {
return nil, err
}
var ver string
if err := b.rpc.CallContext(ctx, &ver, "web3_clientVersion"); err != nil {
if err := b.RPC.CallContext(ctx, &ver, "web3_clientVersion"); err != nil {
return nil, err
}
consensusVersion := b.getConsensusVersion()
rv := &bchain.ChainInfo{
Blocks: int(h.Number.Int64()),
Bestblockhash: h.Hash().Hex(),
Difficulty: h.Difficulty.String(),
Blocks: int(h.Number().Int64()),
Bestblockhash: h.Hash(),
Difficulty: h.Difficulty().String(),
Version: ver,
ConsensusVersion: consensusVersion,
}
idi := int(id.Uint64())
if idi == 1 {
if idi == int(b.MainNetChainID) {
rv.Chain = "mainnet"
} else {
rv.Chain = "testnet " + strconv.Itoa(idi)
@ -407,7 +410,7 @@ func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
return rv, nil
}
func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
func (b *EthereumRPC) getBestHeader() (bchain.EVMHeader, error) {
b.bestHeaderLock.Lock()
defer b.bestHeaderLock.Unlock()
// if the best header was not updated for 15 minutes, there could be a subscription problem, reconnect RPC
@ -421,9 +424,9 @@ func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
}
if b.bestHeader == nil {
var err error
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
b.bestHeader, err = b.client.HeaderByNumber(ctx, nil)
b.bestHeader, err = b.Client.HeaderByNumber(ctx, nil)
if err != nil {
b.bestHeader = nil
return nil, err
@ -433,13 +436,22 @@ func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
return b.bestHeader, nil
}
// UpdateBestHeader keeps track of the latest block header confirmed on chain
func (b *EthereumRPC) UpdateBestHeader(h bchain.EVMHeader) {
glog.V(2).Info("rpc: new block header ", h.Number())
b.bestHeaderLock.Lock()
b.bestHeader = h
b.bestHeaderTime = time.Now()
b.bestHeaderLock.Unlock()
}
// GetBestBlockHash returns hash of the tip of the best-block-chain
func (b *EthereumRPC) GetBestBlockHash() (string, error) {
h, err := b.getBestHeader()
if err != nil {
return "", err
}
return h.Hash().Hex(), nil
return h.Hash(), nil
}
// GetBestBlockHeight returns height of the tip of the best-block-chain
@ -448,23 +460,23 @@ func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) {
if err != nil {
return 0, err
}
return uint32(h.Number.Uint64()), nil
return uint32(h.Number().Uint64()), nil
}
// GetBlockHash returns hash of block in best-block-chain at given height
func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) {
var n big.Int
n.SetUint64(uint64(height))
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
h, err := b.client.HeaderByNumber(ctx, &n)
h, err := b.Client.HeaderByNumber(ctx, &n)
if err != nil {
if err == ethereum.NotFound {
return "", bchain.ErrBlockNotFound
}
return "", errors.Annotatef(err, "height %v", height)
}
return h.Hash().Hex(), nil
return h.Hash(), nil
}
func (b *EthereumRPC) ethHeaderToBlockHeader(h *rpcHeader) (*bchain.BlockHeader, error) {
@ -512,24 +524,24 @@ func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) {
if err != nil {
return 0, err
}
bn := bh.Number.Uint64()
bn := bh.Number().Uint64()
// transaction in the best block has 1 confirmation
return uint32(bn - n + 1), nil
}
func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (json.RawMessage, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
var raw json.RawMessage
var err error
if hash != "" {
if hash == "pending" {
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", hash, fullTxs)
err = b.RPC.CallContext(ctx, &raw, "eth_getBlockByNumber", hash, fullTxs)
} else {
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), fullTxs)
err = b.RPC.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), fullTxs)
}
} else {
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), fullTxs)
err = b.RPC.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), fullTxs)
}
if err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
@ -540,11 +552,11 @@ func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (jso
}
func (b *EthereumRPC) processEventsForBlock(blockNumber string) (map[string][]*bchain.RpcLog, []bchain.AddressAliasRecord, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
var logs []rpcLogWithTxHash
var ensRecords []bchain.AddressAliasRecord
err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{
err := b.RPC.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{
"fromBlock": blockNumber,
"toBlock": blockNumber,
})
@ -629,10 +641,10 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, blockHeight uint
data := make([]bchain.EthereumInternalData, len(transactions))
contracts := make([]bchain.ContractInfo, 0)
if ProcessInternalTransactions {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
var trace []rpcTraceResult
err := b.rpc.CallContext(ctx, &trace, "debug_traceBlockByHash", blockHash, map[string]interface{}{"tracer": "callTracer"})
err := b.RPC.CallContext(ctx, &trace, "debug_traceBlockByHash", blockHash, map[string]interface{}{"tracer": "callTracer"})
if err != nil {
glog.Error("debug_traceBlockByHash block ", blockHash, ", error ", err)
return data, contracts, err
@ -778,11 +790,11 @@ func (b *EthereumRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error)
// GetTransaction returns a transaction by the transaction ID.
func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
var tx *bchain.RpcTransaction
hash := ethcommon.HexToHash(txid)
err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", hash)
err := b.RPC.CallContext(ctx, &tx, "eth_getTransactionByHash", hash)
if err != nil {
return nil, err
} else if tx == nil {
@ -815,7 +827,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
return nil, errors.Annotatef(err, "txid %v", txid)
}
var receipt bchain.RpcReceipt
err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash)
err = b.RPC.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
@ -878,17 +890,19 @@ func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) {
// EstimateSmartFee returns fee estimation
func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
var r big.Int
gp, err := b.client.SuggestGasPrice(ctx)
gp, err := b.Client.SuggestGasPrice(ctx)
if err == nil && b != nil {
r = *gp
}
return r, err
}
func getStringFromMap(p string, params map[string]interface{}) (string, bool) {
// GetStringFromMap attempts to return the value for a specific key in a map as a string if valid,
// otherwise returns an empty string with false indicating there was no key found, or the value was not a string
func GetStringFromMap(p string, params map[string]interface{}) (string, bool) {
v, ok := params[p]
if ok {
s, ok := v.(string)
@ -899,43 +913,37 @@ func getStringFromMap(p string, params map[string]interface{}) (string, bool) {
// EthereumTypeEstimateGas returns estimation of gas consumption for given transaction parameters
func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
msg := ethereum.CallMsg{}
s, ok := getStringFromMap("from", params)
if ok && len(s) > 0 {
if s, ok := GetStringFromMap("from", params); ok && len(s) > 0 {
msg.From = ethcommon.HexToAddress(s)
}
s, ok = getStringFromMap("to", params)
if ok && len(s) > 0 {
if s, ok := GetStringFromMap("to", params); ok && len(s) > 0 {
a := ethcommon.HexToAddress(s)
msg.To = &a
}
s, ok = getStringFromMap("data", params)
if ok && len(s) > 0 {
if s, ok := GetStringFromMap("data", params); ok && len(s) > 0 {
msg.Data = ethcommon.FromHex(s)
}
s, ok = getStringFromMap("value", params)
if ok && len(s) > 0 {
if s, ok := GetStringFromMap("value", params); ok && len(s) > 0 {
msg.Value, _ = hexutil.DecodeBig(s)
}
s, ok = getStringFromMap("gas", params)
if ok && len(s) > 0 {
if s, ok := GetStringFromMap("gas", params); ok && len(s) > 0 {
msg.Gas, _ = hexutil.DecodeUint64(s)
}
s, ok = getStringFromMap("gasPrice", params)
if ok && len(s) > 0 {
if s, ok := GetStringFromMap("gasPrice", params); ok && len(s) > 0 {
msg.GasPrice, _ = hexutil.DecodeBig(s)
}
return b.client.EstimateGas(ctx, msg)
return b.Client.EstimateGas(ctx, msg)
}
// SendRawTransaction sends raw transaction
func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
var raw json.RawMessage
err := b.rpc.CallContext(ctx, &raw, "eth_sendRawTransaction", hex)
err := b.RPC.CallContext(ctx, &raw, "eth_sendRawTransaction", hex)
if err != nil {
return "", err
} else if len(raw) == 0 {
@ -953,16 +961,16 @@ func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
// EthereumTypeGetBalance returns current balance of an address
func (b *EthereumRPC) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (*big.Int, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
return b.client.BalanceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil)
return b.Client.BalanceAt(ctx, addrDesc, nil)
}
// EthereumTypeGetNonce returns current balance of an address
func (b *EthereumRPC) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (uint64, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
return b.client.NonceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil)
return b.Client.NonceAt(ctx, addrDesc, nil)
}
// GetChainParser returns ethereum BlockChainParser

130
bchain/coins/eth/evm.go Normal file
View File

@ -0,0 +1,130 @@
package eth
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/trezor/blockbook/bchain"
)
// EthereumClient wraps a client to implement the EVMClient interface
type EthereumClient struct {
*ethclient.Client
}
// HeaderByNumber returns a block header that implements the EVMHeader interface
func (c *EthereumClient) HeaderByNumber(ctx context.Context, number *big.Int) (bchain.EVMHeader, error) {
h, err := c.Client.HeaderByNumber(ctx, number)
if err != nil {
return nil, err
}
return &EthereumHeader{Header: h}, nil
}
// EstimateGas returns the current estimated gas cost for executing a transaction
func (c *EthereumClient) EstimateGas(ctx context.Context, msg interface{}) (uint64, error) {
return c.Client.EstimateGas(ctx, msg.(ethereum.CallMsg))
}
// BalanceAt returns the balance for the given account at a specific block, or latest known block if no block number is provided
func (c *EthereumClient) BalanceAt(ctx context.Context, addrDesc bchain.AddressDescriptor, blockNumber *big.Int) (*big.Int, error) {
return c.Client.BalanceAt(ctx, common.BytesToAddress(addrDesc), blockNumber)
}
// NonceAt returns the nonce for the given account at a specific block, or latest known block if no block number is provided
func (c *EthereumClient) NonceAt(ctx context.Context, addrDesc bchain.AddressDescriptor, blockNumber *big.Int) (uint64, error) {
return c.Client.NonceAt(ctx, common.BytesToAddress(addrDesc), blockNumber)
}
// EthereumRPCClient wraps an rpc client to implement the EVMRPCClient interface
type EthereumRPCClient struct {
*rpc.Client
}
// EthSubscribe subscribes to events and returns a client subscription that implements the EVMClientSubscription interface
func (c *EthereumRPCClient) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (bchain.EVMClientSubscription, error) {
sub, err := c.Client.EthSubscribe(ctx, channel, args...)
if err != nil {
return nil, err
}
return &EthereumClientSubscription{ClientSubscription: sub}, nil
}
// EthereumHeader wraps a block header to implement the EVMHeader interface
type EthereumHeader struct {
*types.Header
}
// Hash returns the block hash as a hex string
func (h *EthereumHeader) Hash() string {
return h.Header.Hash().Hex()
}
// Number returns the block number
func (h *EthereumHeader) Number() *big.Int {
return h.Header.Number
}
// Difficulty returns the block difficulty
func (h *EthereumHeader) Difficulty() *big.Int {
return h.Header.Difficulty
}
// EthereumHash wraps a transaction hash to implement the EVMHash interface
type EthereumHash struct {
common.Hash
}
// EthereumClientSubscription wraps a client subcription to implement the EVMClientSubscription interface
type EthereumClientSubscription struct {
*rpc.ClientSubscription
}
// EthereumNewBlock wraps a block header channel to implement the EVMNewBlockSubscriber interface
type EthereumNewBlock struct {
channel chan *types.Header
}
// Channel returns the underlying channel as an empty interface
func (s *EthereumNewBlock) Channel() interface{} {
return s.channel
}
// Read from the underlying channel and return a block header that implements the EVMHeader interface
func (s *EthereumNewBlock) Read() (bchain.EVMHeader, bool) {
h, ok := <-s.channel
return &EthereumHeader{Header: h}, ok
}
// Close the underlying channel
func (s *EthereumNewBlock) Close() {
close(s.channel)
}
// EthereumNewTx wraps a transaction hash channel to implement the EVMNewTxSubscriber interface
type EthereumNewTx struct {
channel chan common.Hash
}
// Channel returns the underlying channel as an empty interface
func (s *EthereumNewTx) Channel() interface{} {
return s.channel
}
// Read from the underlying channel and return a transaction hash that implements the EVMHash interface
func (s *EthereumNewTx) Read() (bchain.EVMHash, bool) {
h, ok := <-s.channel
return &EthereumHash{Hash: h}, ok
}
// Close the underlying channel
func (s *EthereumNewTx) Close() {
close(s.channel)
}

59
bchain/evm_interface.go Normal file
View File

@ -0,0 +1,59 @@
package bchain
import (
"context"
"math/big"
)
// EVMClient provides the necessary client functionality for evm chain sync
type EVMClient interface {
NetworkID(ctx context.Context) (*big.Int, error)
HeaderByNumber(ctx context.Context, number *big.Int) (EVMHeader, error)
SuggestGasPrice(ctx context.Context) (*big.Int, error)
EstimateGas(ctx context.Context, msg interface{}) (uint64, error)
BalanceAt(ctx context.Context, addrDesc AddressDescriptor, blockNumber *big.Int) (*big.Int, error)
NonceAt(ctx context.Context, addrDesc AddressDescriptor, blockNumber *big.Int) (uint64, error)
}
// EVMRPCClient provides the necessary rpc functionality for evm chain sync
type EVMRPCClient interface {
EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (EVMClientSubscription, error)
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
Close()
}
// EVMHeader provides access to the necessary header data for evm chain sync
type EVMHeader interface {
Hash() string
Number() *big.Int
Difficulty() *big.Int
}
// EVMHash provides access to the necessary hash data for evm chain sync
type EVMHash interface {
Hex() string
}
// EVMClientSubscription provides interaction with an evm client subscription
type EVMClientSubscription interface {
Err() <-chan error
Unsubscribe()
}
// EVMSubscriber provides interaction with a subscription channel
type EVMSubscriber interface {
Channel() interface{}
Close()
}
// EVMNewBlockSubscriber provides interaction with a new block subscription channel
type EVMNewBlockSubscriber interface {
EVMSubscriber
Read() (EVMHeader, bool)
}
// EVMNewBlockSubscriber provides interaction with a new tx subscription channel
type EVMNewTxSubscriber interface {
EVMSubscriber
Read() (EVMHash, bool)
}