diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 900dff593c..69461c6dbf 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -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" @@ -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 diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index a41f8e57bf..f9f9cde0e1 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -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"` @@ -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 } @@ -186,7 +187,7 @@ func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOu return err } - b.mempoolInitialized = true + b.MempoolInitialized = true return nil } @@ -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 { @@ -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 { @@ -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 @@ -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() @@ -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 @@ -426,7 +427,7 @@ 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 } @@ -434,10 +435,10 @@ func (b *EthereumRPC) getBestHeader() (bchain.EVMHeader, error) { // 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 @@ -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) } } @@ -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 @@ -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) } } diff --git a/bchain/coins/rsk/evm.go b/bchain/coins/rsk/evm.go new file mode 100644 index 0000000000..e885feeb33 --- /dev/null +++ b/bchain/coins/rsk/evm.go @@ -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 +} diff --git a/bchain/coins/rsk/rskrpc.go b/bchain/coins/rsk/rskrpc.go new file mode 100644 index 0000000000..7a18eafcff --- /dev/null +++ b/bchain/coins/rsk/rskrpc.go @@ -0,0 +1,226 @@ +package rsk + +import ( + "context" + "encoding/json" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/golang/glog" + "github.com/juju/errors" + "github.com/trezor/blockbook/bchain" + "github.com/trezor/blockbook/bchain/coins/eth" +) + +const ( + // MainNet is production network + MainNet eth.Network = 30 + // TestNet is test network + TestNet eth.Network = 31 +) + +// RskRPC is an interface to JSON-RPC rsk service. +type RskRPC struct { + *eth.EthereumRPC + WSClient bchain.EVMClient + WSRPC bchain.EVMRPCClient +} + +// NewRskRPC returns new RskRPC instance. +func NewRskRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { + c, err := eth.NewEthereumRPC(config, pushHandler) + if err != nil { + return nil, err + } + + s := &RskRPC{ + EthereumRPC: c.(*eth.EthereumRPC), + } + + return s, nil +} + +// Initialize rsk rpc interface +func (b *RskRPC) Initialize() error { + b.OpenRPC = func(url string) (bchain.EVMRPCClient, bchain.EVMClient, error) { + r, err := rpc.Dial(url) + if err != nil { + return nil, nil, err + } + rc := &RskRPCClient{Client: r} + ec := &RskClient{Client: ethclient.NewClient(r), RskRPCClient: rc} + return rc, ec, nil + } + + rc, ec, err := b.OpenRPC(b.ChainConfig.RPCURL) + if err != nil { + return err + } + + wsrc, wsec, wserr := b.OpenRPC(b.ChainConfig.WSURL) + if wserr != nil { + return wserr + } + + // set chain specific + b.Client = ec + b.RPC = rc + b.MainNetChainID = MainNet + b.NewBlock = eth.NewEthereumNewBlock() + b.NewTx = eth.NewEthereumNewTx() + b.WSRPC = wsrc + b.WSClient = wsec + + ctx, cancel := context.WithTimeout(context.Background(), b.Timeout) + defer cancel() + + id, err := b.Client.NetworkID(ctx) + if err != nil { + return err + } + + // parameters for getInfo request + switch eth.Network(id.Uint64()) { + case MainNet: + b.Testnet = false + b.Network = "livenet" + case TestNet: + b.Testnet = true + b.Network = "testnet" + default: + return errors.Errorf("Unknown network id %v", id) + } + + return nil +} + +// InitializeMempool creates subscriptions to newHeads and newPendingTransactions +func (b *RskRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error { + if b.Mempool == nil { + return errors.New("Mempool not created") + } + + // get initial mempool transactions + txs, err := b.GetMempoolTransactions() + if err != nil { + return err + } + for _, txid := range txs { + b.Mempool.AddTransactionToMempool(txid) + } + + b.Mempool.OnNewTxAddr = onNewTxAddr + b.Mempool.OnNewTx = onNewTx + + if err = b.subscribeEvents(); err != nil { + return err + } + + b.MempoolInitialized = true + + return nil +} + +func (b *RskRPC) subscribeEvents() 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) + defer cancel() + sub, err := b.WSRPC.EthSubscribe(ctx, b.NewBlock.Channel(), "newHeads") + if err != nil { + return nil, errors.Annotatef(err, "RskSubscribe newHeads") + } + b.NewBlockSubscription = sub + glog.Info("Subscribed to newHeads") + return sub, nil + }); err != nil { + return err + } + + // 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) + defer cancel() + sub, err := b.WSRPC.EthSubscribe(ctx, b.NewTx.Channel(), "newPendingTransactions") + if err != nil { + return nil, errors.Annotatef(err, "RskSubscribe newPendingTransactions") + } + b.NewTxSubscription = sub + glog.Info("Subscribed to newPendingTransactions") + return sub, nil + }); err != nil { + return err + } + + return nil +} + +func (b *RskRPC) closeRPC() { + b.closeRPC() + if b.WSRPC != nil { + b.WSRPC.Close() + } +} + +func (b *RskRPC) reconnectRPC() error { + b.OpenRPC = func(url string) (bchain.EVMRPCClient, bchain.EVMClient, error) { + r, err := rpc.Dial(url) + if err != nil { + return nil, nil, err + } + rc := &RskRPCClient{Client: r} + ec := &RskClient{Client: ethclient.NewClient(r), RskRPCClient: rc} + return rc, ec, nil + } + + glog.Info("Reconnecting RPC") + b.closeRPC() + rc, ec, err := b.OpenRPC(b.ChainConfig.RPCURL) + if err != nil { + return err + } + b.RPC = rc + b.Client = ec + + wsrc, wsec, wserr := b.OpenRPC(b.ChainConfig.WSURL) + if wserr != nil { + return wserr + } + + b.WSRPC = wsrc + b.WSClient = wsec + + return b.subscribeEvents() +} diff --git a/build/templates/blockbook/blockchaincfg.json b/build/templates/blockbook/blockchaincfg.json index 525937c5be..43bd210263 100644 --- a/build/templates/blockbook/blockchaincfg.json +++ b/build/templates/blockbook/blockchaincfg.json @@ -9,6 +9,7 @@ "coin_shortcut": "{{.Coin.Shortcut}}", "coin_label": "{{.Coin.Label}}", "rpc_url": "{{template "IPC.RPCURLTemplate" .}}", + "ws_url": "{{template "IPC.WSURLTemplate" .}}", "rpc_user": "{{.IPC.RPCUser}}", "rpc_pass": "{{.IPC.RPCPass}}", "rpc_timeout": {{.IPC.RPCTimeout}}, diff --git a/build/tools/templates.go b/build/tools/templates.go index f414e5e154..681441acb1 100644 --- a/build/tools/templates.go +++ b/build/tools/templates.go @@ -49,6 +49,7 @@ type Config struct { } `json:"coin"` Ports struct { BackendRPC int `json:"backend_rpc"` + BackendWS int `json:"backend_ws"` BackendMessageQueue int `json:"backend_message_queue"` BackendP2P int `json:"backend_p2p"` BackendHttp int `json:"backend_http"` @@ -58,6 +59,7 @@ type Config struct { } `json:"ports"` IPC struct { RPCURLTemplate string `json:"rpc_url_template"` + WSURLTemplate string `json:"ws_url_template"` RPCUser string `json:"rpc_user"` RPCPass string `json:"rpc_pass"` RPCTimeout int `json:"rpc_timeout"` @@ -124,6 +126,7 @@ func generateRPCAuth(user, pass string) (string, error) { func (c *Config) ParseTemplate() *template.Template { templates := map[string]string{ "IPC.RPCURLTemplate": c.IPC.RPCURLTemplate, + "IPC.WSURLTemplate": c.IPC.WSURLTemplate, "IPC.MessageQueueBindingTemplate": c.IPC.MessageQueueBindingTemplate, "Backend.ExecCommandTemplate": c.Backend.ExecCommandTemplate, "Backend.LogrotateFilesTemplate": c.Backend.LogrotateFilesTemplate, diff --git a/configs/coins/rsk.json b/configs/coins/rsk.json new file mode 100644 index 0000000000..b2a240b37d --- /dev/null +++ b/configs/coins/rsk.json @@ -0,0 +1,66 @@ +{ + "coin": { + "name": "Rsk", + "shortcut": "RBTC", + "label": "Rsk", + "alias": "rsk" + }, + "ports": { + "backend_rpc": 4444, + "backend_ws": 4445, + "backend_message_queue": 0, + "backend_p2p": 5050, + "backend_http": 4444, + "blockbook_internal": 9100, + "blockbook_public": 9200 + }, + "ipc": { + "rpc_url_template": "http://127.0.0.1:{{.Ports.BackendRPC}}", + "ws_url_template": "ws://127.0.0.1:{{.Ports.BackendWS}}/websocket", + "rpc_timeout": 60 + }, + "backend": { + "package_name": "backend-rsk", + "package_revision": "satoshilabs-1", + "system_user": "rsk", + "version": "5.2.0", + "binary_url": "https://github.com/rsksmart/rskj/releases/download/FINGERROOT-5.2.0/rskj-core-5.2.0-FINGERROOT-all.jar", + "verification_type": "sha256", + "verification_source": "70ae5209720ad6477c1c32d8a8d94e29ebb0db25d57e9903546519d614eddf9f", + "extract_command": "sh -c 'cp \"$$1\" backend/' fake-extract", + "exclude_files": [], + "exec_command_template": "/bin/sh -c 'java -Xmx4G -Xms1024m -Ddatabase.dir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -Drpc.providers.web.http.port={{.Ports.BackendRPC}} -Drpc.providers.web.ws.enabled=true -Drpc.providers.web.ws.port=4445 -Drpc.providers.web.cors=\"*\" -Dlogback.configurationFile=~/backend/logback.xml -cp {{.Env.BackendInstallPath}}/{{.Coin.Alias}}/rskj-core-5.2.0-FINGERROOT-all.jar co.rsk.Start 2>>{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'", + "logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log", + "postinst_script_template": "", + "service_type": "simple", + "service_additional_params_template": "", + "protect_memory": true, + "mainnet": true, + "server_config_file": "", + "client_config_file": "" + }, + "blockbook": { + "package_name": "blockbook-rsk", + "system_user": "blockbook-rsk", + "internal_binding_template": ":{{.Ports.BlockbookInternal}}", + "public_binding_template": ":{{.Ports.BlockbookPublic}}", + "explorer_url": "", + "additional_params": "", + "block_chain": { + "parse": true, + "mempool_workers": 8, + "mempool_sub_workers": 2, + "block_addresses_to_keep": 300, + "additional_params": { + "mempoolTxTimeoutHours": 48, + "queryBackendOnMempoolResync": false, + "fiat_rates": "coingecko", + "fiat_rates_params": "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"rootstock\", \"periodSeconds\": 60}" + } + } + }, + "meta": { + "package_maintainer": "IT", + "package_maintainer_email": "it@satoshilabs.com" + } +} diff --git a/docs/ports.md b/docs/ports.md index 2b93de4d83..40af83e38e 100644 --- a/docs/ports.md +++ b/docs/ports.md @@ -53,6 +53,7 @@ | eCash | 9197 | 9097 | 8097 | 38397 | | Avalanche | 9198 | 9098 | 8098 | 38398 p2p | | Avalanche Archive | 9199 | 9099 | 8099 | 38399 p2p | +| Rsk | 9200 | 9100 | 4444 | 4444 http, 4445 ws, 5050 p2p | | Ethereum Testnet Goerli Archive | 19106 | 19006 | 18006 | 18106 http, 18506 authrpc, 48306 p2p | | Bitcoin Signet | 19120 | 19020 | 18020 | 48320 | | Bitcoin Regtest | 19121 | 19021 | 18021 | 48321 | diff --git a/tests/rpc/testdata/rsk.json b/tests/rpc/testdata/rsk.json new file mode 100644 index 0000000000..7cfd921b7f --- /dev/null +++ b/tests/rpc/testdata/rsk.json @@ -0,0 +1,31 @@ +{ + "blockHeight": 4539328, + "blockHash": "0x057c7b08f065e25d828964084fbd6b642193fea0363ddcd1aeaca1e4be544163", + "blockTime": 1660059126, + "blockSize": 2344, + "blockTxs": [ + "0x9d55ab123053c57b2883d4ae70b9084384cfb25df4a5f44d16d86ed8060c0edb", + "0xeca563a033697f9aebf3092e819c7df5f40632367f1389fb6bc43c2cddc29fcb", + "0xf8775db567c8f66e15acb4179bf4707706f9df81a8282802d980f5a134275c79" + ], + "txDetails": { + "0x9d55ab123053c57b2883d4ae70b9084384cfb25df4a5f44d16d86ed8060c0edb": { + "txid": "0x9d55ab123053c57b2883d4ae70b9084384cfb25df4a5f44d16d86ed8060c0edb", + "blocktime": 1660059126, + "time": 1660059126, + "vin": [ + { + "addresses": ["0x2ce79A1c4b60e61d00ac88afD19d86e6082F4e6b"] + } + ], + "vout": [ + { + "value": 0.00021716, + "scriptPubKey": { + "addresses": ["0xBB3958F529ea5ddAC006dF6a8Af94E8D006efca4"] + } + } + ] + } + } +} \ No newline at end of file diff --git a/tests/tests.json b/tests/tests.json index 9bea77fb76..dcc815ea80 100644 --- a/tests/tests.json +++ b/tests/tests.json @@ -155,6 +155,10 @@ "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "MempoolSync", "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] }, + "rsk": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", + "GetBlockHeader"] + }, "vertcoin": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"],