EVM Abstraction (#848)
This commit is contained in:
parent
55f3ad3caa
commit
6a24f4ac30
@ -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")
|
||||
|
||||
@ -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
130
bchain/coins/eth/evm.go
Normal 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
59
bchain/evm_interface.go
Normal 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)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user