Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add RSK Support #958

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bchain/coins/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"github.com/trezor/blockbook/bchain/coins/qtum"
"github.com/trezor/blockbook/bchain/coins/ravencoin"
"github.com/trezor/blockbook/bchain/coins/ritocoin"
"github.com/trezor/blockbook/bchain/coins/rsk"
"github.com/trezor/blockbook/bchain/coins/snowgem"
"github.com/trezor/blockbook/bchain/coins/trezarcoin"
"github.com/trezor/blockbook/bchain/coins/unobtanium"
Expand Down Expand Up @@ -138,6 +139,7 @@ func init() {
BlockChainFactories["BNB Smart Chain Archive"] = bsc.NewBNBSmartChainRPC
BlockChainFactories["Polygon"] = polygon.NewPolygonRPC
BlockChainFactories["Polygon Archive"] = polygon.NewPolygonRPC
BlockChainFactories["Rsk"] = rsk.NewRskRPC
}

// GetCoinNameFromConfig gets coin name and coin shortcut from config file
Expand Down
57 changes: 29 additions & 28 deletions bchain/coins/eth/ethrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Configuration struct {
CoinName string `json:"coin_name"`
CoinShortcut string `json:"coin_shortcut"`
RPCURL string `json:"rpc_url"`
WSURL string `json:"ws_url"`
RPCTimeout int `json:"rpc_timeout"`
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
AddressAliases bool `json:"address_aliases,omitempty"`
Expand All @@ -62,14 +63,14 @@ type EthereumRPC struct {
PushHandler func(bchain.NotificationType)
OpenRPC func(string) (bchain.EVMRPCClient, bchain.EVMClient, error)
Mempool *bchain.MempoolEthereumType
mempoolInitialized bool
bestHeaderLock sync.Mutex
MempoolInitialized bool
BestHeaderLock sync.Mutex
bestHeader bchain.EVMHeader
bestHeaderTime time.Time
BestHeaderTime time.Time
NewBlock bchain.EVMNewBlockSubscriber
newBlockSubscription bchain.EVMClientSubscription
NewBlockSubscription bchain.EVMClientSubscription
NewTx bchain.EVMNewTxSubscriber
newTxSubscription bchain.EVMClientSubscription
NewTxSubscription bchain.EVMClientSubscription
ChainConfig *Configuration
}

Expand Down Expand Up @@ -186,7 +187,7 @@ func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOu
return err
}

b.mempoolInitialized = true
b.MempoolInitialized = true

return nil
}
Expand All @@ -206,16 +207,16 @@ func (b *EthereumRPC) subscribeEvents() error {
}()

// new block subscription
if err := b.subscribe(func() (bchain.EVMClientSubscription, error) {
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
b.NewBlockSubscription = nil
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
sub, err := b.RPC.EthSubscribe(ctx, b.NewBlock.Channel(), "newHeads")
if err != nil {
return nil, errors.Annotatef(err, "EthSubscribe newHeads")
}
b.newBlockSubscription = sub
b.NewBlockSubscription = sub
glog.Info("Subscribed to newHeads")
return sub, nil
}); err != nil {
Expand All @@ -239,16 +240,16 @@ func (b *EthereumRPC) subscribeEvents() error {
}()

// new mempool transaction subscription
if err := b.subscribe(func() (bchain.EVMClientSubscription, error) {
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
b.NewTxSubscription = nil
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
sub, err := b.RPC.EthSubscribe(ctx, b.NewTx.Channel(), "newPendingTransactions")
if err != nil {
return nil, errors.Annotatef(err, "EthSubscribe newPendingTransactions")
}
b.newTxSubscription = sub
b.NewTxSubscription = sub
glog.Info("Subscribed to newPendingTransactions")
return sub, nil
}); err != nil {
Expand All @@ -258,8 +259,8 @@ func (b *EthereumRPC) subscribeEvents() error {
return nil
}

// subscribe subscribes notification and tries to resubscribe in case of error
func (b *EthereumRPC) subscribe(f func() (bchain.EVMClientSubscription, error)) error {
// Subscribe subscribes notification and tries to resubscribe in case of error
func (b *EthereumRPC) Subscribe(f func() (bchain.EVMClientSubscription, error)) error {
s, err := f()
if err != nil {
return err
Expand Down Expand Up @@ -299,11 +300,11 @@ func (b *EthereumRPC) subscribe(f func() (bchain.EVMClientSubscription, error))
}

func (b *EthereumRPC) closeRPC() {
if b.newBlockSubscription != nil {
b.newBlockSubscription.Unsubscribe()
if b.NewBlockSubscription != nil {
b.NewBlockSubscription.Unsubscribe()
}
if b.newTxSubscription != nil {
b.newTxSubscription.Unsubscribe()
if b.NewTxSubscription != nil {
b.NewTxSubscription.Unsubscribe()
}
if b.RPC != nil {
b.RPC.Close()
Expand Down Expand Up @@ -406,11 +407,11 @@ func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
}

func (b *EthereumRPC) getBestHeader() (bchain.EVMHeader, error) {
b.bestHeaderLock.Lock()
defer b.bestHeaderLock.Unlock()
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
// do it only in case of normal operation, not initial synchronization
if b.bestHeaderTime.Add(15*time.Minute).Before(time.Now()) && !b.bestHeaderTime.IsZero() && b.mempoolInitialized {
if b.BestHeaderTime.Add(15*time.Minute).Before(time.Now()) && !b.BestHeaderTime.IsZero() && b.MempoolInitialized {
err := b.reconnectRPC()
if err != nil {
return nil, err
Expand All @@ -426,18 +427,18 @@ func (b *EthereumRPC) getBestHeader() (bchain.EVMHeader, error) {
b.bestHeader = nil
return nil, err
}
b.bestHeaderTime = time.Now()
b.BestHeaderTime = time.Now()
}
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.BestHeaderLock.Lock()
b.bestHeader = h
b.bestHeaderTime = time.Now()
b.bestHeaderLock.Unlock()
b.BestHeaderTime = time.Now()
b.BestHeaderLock.Unlock()
}

// GetBestBlockHash returns hash of the tip of the best-block-chain
Expand Down Expand Up @@ -762,7 +763,7 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash)
}
btxs[i] = *btx
if b.mempoolInitialized {
if b.MempoolInitialized {
b.Mempool.RemoveTransactionFromMempool(tx.Hash)
}
}
Expand Down Expand Up @@ -816,7 +817,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
if err != nil {
return nil, err
} else if tx == nil {
if b.mempoolInitialized {
if b.MempoolInitialized {
b.Mempool.RemoveTransactionFromMempool(txid)
}
return nil, bchain.ErrTxNotFound
Expand Down Expand Up @@ -862,7 +863,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
return nil, errors.Annotatef(err, "txid %v", txid)
}
// remove tx from mempool if it is there
if b.mempoolInitialized {
if b.MempoolInitialized {
b.Mempool.RemoveTransactionFromMempool(txid)
}
}
Expand Down
129 changes: 129 additions & 0 deletions bchain/coins/rsk/evm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package rsk

import (
"context"
"github.com/ethereum/go-ethereum"
"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/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/trezor/blockbook/bchain"
"math/big"
"strings"
)

// RskClient wraps a client to implement the EVMClient interface
type RskClient struct {
*ethclient.Client
*RskRPCClient
}

// HeaderByNumber returns a block header that implements the EVMHeader interface
func (c *RskClient) HeaderByNumber(ctx context.Context, number *big.Int) (bchain.EVMHeader, error) {
h, err := rskHeaderByNumber(c.RskRPCClient, ctx, number)
if err != nil {
return nil, err
}

return h, nil
}

// EstimateGas returns the current estimated gas cost for executing a transaction
func (c *RskClient) 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 *RskClient) 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 *RskClient) NonceAt(ctx context.Context, addrDesc bchain.AddressDescriptor, blockNumber *big.Int) (uint64, error) {
return c.Client.NonceAt(ctx, common.BytesToAddress(addrDesc), blockNumber)
}

// RskRPCClient wraps a rpc client to implement the EVMRPCClient interface
type RskRPCClient struct {
*rpc.Client
}

// EthSubscribe subscribes to events and returns a client subscription that implements the EVMClientSubscription interface
func (c *RskRPCClient) 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 &RskClientSubscription{ClientSubscription: sub}, nil
}

// RskHeader wraps a block header to implement the EVMHeader interface
type RskHeader struct {
RskHash ethcommon.Hash `json:"hash"`
ParentHash ethcommon.Hash `json:"parentHash"`
UncleHash ethcommon.Hash `json:"sha3Uncles"`
Coinbase ethcommon.Address `json:"miner"`
Root ethcommon.Hash `json:"stateRoot"`
TxHash ethcommon.Hash `json:"transactionsRoot"`
ReceiptHash ethcommon.Hash `json:"receiptsRoot"`
//Bloom []byte `json:"logsBloom"`
RskDifficulty string `json:"difficulty"`
RskNumber string `json:"number"`
GasLimit string `json:"gasLimit"`
GasUsed string `json:"gasUsed"`
Time string `json:"timestamp"`
//Extra []byte `json:"extraData"`
}

// Hash returns the block hash as a hex string
func (h *RskHeader) Hash() string {
return h.RskHash.Hex()
}

// Number returns the block number
func (h *RskHeader) Number() *big.Int {
number, _ := big.NewInt(0).SetString(stripHex(h.RskNumber), 16)
return number
}

// Difficulty returns the block difficulty
func (h *RskHeader) Difficulty() *big.Int {
difficulty, _ := big.NewInt(0).SetString(stripHex(h.RskDifficulty), 16)
return difficulty
}

// RskClientSubscription wraps a client subcription to implement the EVMClientSubscription interface
type RskClientSubscription struct {
*rpc.ClientSubscription
}

// RskHeaderByNumber HeaderByNumber returns a RSK block header from the current canonical chain. If number is
// nil, the latest known header is returned.
func rskHeaderByNumber(b *RskRPCClient, ctx context.Context, number *big.Int) (*RskHeader, error) {
var head *RskHeader
err := b.Client.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false)
if err == nil && head == nil {
err = ethereum.NotFound
}
return head, err
}

func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
}
pending := big.NewInt(-1)
if number.Cmp(pending) == 0 {
return "pending"
}
return hexutil.EncodeBig(number)
}

func stripHex(hexaString string) string {
// replace 0x or 0X with empty String
numberStr := strings.Replace(hexaString, "0x", "", -1)
numberStr = strings.Replace(numberStr, "0X", "", -1)
return numberStr
}
Loading