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) { 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() defer cancel()
var r string 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, "data": data,
"to": to, "to": to,
}, "latest") }, "latest")

View File

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