From 579250a071b4eb36108029e41bdd37a393b69335 Mon Sep 17 00:00:00 2001 From: Daniel Van Der Maden Date: Wed, 29 Jul 2020 18:50:43 -0700 Subject: [PATCH] Node API Refactor - pt2 (Stage 2.2 of Node API Overhaul) (#3259) * [rpc] Refactor - expose RPC functions in rpc package Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Undo v1 & v2 code duplication pt1 * Move apiv1 into rpc package as baseline * Create version enum * Refactor API creation to use version enum Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Rejoin GetBlockByNumber & GetBlockByHash * Both RPC versions are supported in 1 function * Created types directory for v1 & v2 response types * Added framework to make rpc layer more clear and legible Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Undo v1 & v2 code duplication pt2 * Rejoin blockchain.go functions * Create legacy service * Improve inline documentation Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Undo v1 & v2 code duplication pt3 * Use StructuredResponse when returning an exposed type from RPC * Remove addrlock.go * Add RPCBlock creation with exposed struct for easy utility from consumers * Update blockchain.go to reflect changes of RPCBlock Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Undo v1 & v2 code duplication pt3 * Create types for RPCs that are returned. * Reorganize types to appropriate v1 & v2 package. Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Undo v1 & v2 code duplication pt4 * Add context to all RPCs * Add response switches based on different versions of the API * Add debug logs for RPC that don't return errors Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Undo v1 & v2 code duplication pt5 * Add versions switches to all transaction related RPCs * Integrate response with newly defined structures * Add debug messages for RPCs that don't return errors * Add inline documentation for all transaction related RPCs Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Undo v1 & v2 code duplication pt6 * Remove apiv2 * Move StructuredResponse & TxHistoryArgs to main rpc types.go Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Undo v1 & v2 code duplication pt7 * Create contract, pool, staking & transaction services * Move apis into correct services * Remove util.go (functions are now baked into RPCs in pool.go) * Remove transaction.go (functions/methods are distributed among created services) * Update changes for StructuredResponse Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Add rpc server start/stop to pkg Signed-off-by: Daniel Van Der Maden * [node] Refactor - use new RPC start / stop for servers Signed-off-by: Daniel Van Der Maden * [hmy] Refactor - rename network.go to net.go to match rpc * Change names for consistency with rpc pkg Signed-off-by: Daniel Van Der Maden * fix go lint & go imports Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Fix GetTransactionReceipt for staking txs Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - Rename v1 & v2 struct to not have leading 'RPC' * Update comments Signed-off-by: Daniel Van Der Maden * [rpc] Refactor - make defaultBlocksPeriod be the num blocks per epoch Signed-off-by: Daniel Van Der Maden --- hmy/blockchain.go | 6 +- hmy/hmy.go | 2 +- hmy/{network.go => net.go} | 2 +- hmy/staking.go | 2 +- internal/hmyapi/README.md | 98 -- internal/hmyapi/apiv1/addrlock.go | 54 - internal/hmyapi/apiv1/blockchain.go | 1033 ----------------- internal/hmyapi/apiv1/debug.go | 32 - internal/hmyapi/apiv1/error.go | 18 - internal/hmyapi/apiv1/harmony.go | 54 - internal/hmyapi/apiv1/net.go | 29 - internal/hmyapi/apiv1/transactionpool.go | 410 ------- internal/hmyapi/apiv1/types.go | 538 --------- internal/hmyapi/apiv1/util.go | 73 -- internal/hmyapi/apiv2/addrlock.go | 54 - internal/hmyapi/apiv2/blockchain.go | 987 ---------------- internal/hmyapi/apiv2/debug.go | 32 - internal/hmyapi/apiv2/harmony.go | 69 -- internal/hmyapi/apiv2/net.go | 28 - internal/hmyapi/apiv2/transactionpool.go | 432 ------- internal/hmyapi/apiv2/types.go | 539 --------- internal/hmyapi/apiv2/util.go | 74 -- internal/hmyapi/backend.go | 64 - node/rpc.go | 136 +-- rpc/blockchain.go | 668 +++++++++++ {internal/hmyapi => rpc}/common/hacks.go | 0 {internal/hmyapi => rpc}/common/types.go | 25 + rpc/contract.go | 196 ++++ rpc/debug.go | 40 + {internal/hmyapi/apiv2 => rpc}/error.go | 6 +- {internal/hmyapi => rpc}/filters/api.go | 0 {internal/hmyapi => rpc}/filters/filter.go | 0 .../hmyapi => rpc}/filters/filter_criteria.go | 0 .../hmyapi => rpc}/filters/filter_system.go | 0 rpc/harmony.go | 88 ++ rpc/net.go | 59 + rpc/pool.go | 295 +++++ rpc/rpc.go | 167 +++ rpc/staking.go | 621 ++++++++++ rpc/transaction.go | 747 ++++++++++++ rpc/types.go | 200 ++++ rpc/v1/legacy.go | 40 + rpc/v1/types.go | 735 ++++++++++++ rpc/v2/legacy.go | 39 + rpc/v2/types.go | 734 ++++++++++++ 45 files changed, 4681 insertions(+), 4745 deletions(-) rename hmy/{network.go => net.go} (93%) delete mode 100644 internal/hmyapi/README.md delete mode 100644 internal/hmyapi/apiv1/addrlock.go delete mode 100644 internal/hmyapi/apiv1/blockchain.go delete mode 100644 internal/hmyapi/apiv1/debug.go delete mode 100644 internal/hmyapi/apiv1/error.go delete mode 100644 internal/hmyapi/apiv1/harmony.go delete mode 100644 internal/hmyapi/apiv1/net.go delete mode 100644 internal/hmyapi/apiv1/transactionpool.go delete mode 100644 internal/hmyapi/apiv1/types.go delete mode 100644 internal/hmyapi/apiv1/util.go delete mode 100644 internal/hmyapi/apiv2/addrlock.go delete mode 100644 internal/hmyapi/apiv2/blockchain.go delete mode 100644 internal/hmyapi/apiv2/debug.go delete mode 100644 internal/hmyapi/apiv2/harmony.go delete mode 100644 internal/hmyapi/apiv2/net.go delete mode 100644 internal/hmyapi/apiv2/transactionpool.go delete mode 100644 internal/hmyapi/apiv2/types.go delete mode 100644 internal/hmyapi/apiv2/util.go delete mode 100644 internal/hmyapi/backend.go create mode 100644 rpc/blockchain.go rename {internal/hmyapi => rpc}/common/hacks.go (100%) rename {internal/hmyapi => rpc}/common/types.go (71%) create mode 100644 rpc/contract.go create mode 100644 rpc/debug.go rename {internal/hmyapi/apiv2 => rpc}/error.go (69%) rename {internal/hmyapi => rpc}/filters/api.go (100%) rename {internal/hmyapi => rpc}/filters/filter.go (100%) rename {internal/hmyapi => rpc}/filters/filter_criteria.go (100%) rename {internal/hmyapi => rpc}/filters/filter_system.go (100%) create mode 100644 rpc/harmony.go create mode 100644 rpc/net.go create mode 100644 rpc/pool.go create mode 100644 rpc/rpc.go create mode 100644 rpc/staking.go create mode 100644 rpc/transaction.go create mode 100644 rpc/types.go create mode 100644 rpc/v1/legacy.go create mode 100644 rpc/v1/types.go create mode 100644 rpc/v2/legacy.go create mode 100644 rpc/v2/types.go diff --git a/hmy/blockchain.go b/hmy/blockchain.go index f6546bcfe..eb9a827a5 100644 --- a/hmy/blockchain.go +++ b/hmy/blockchain.go @@ -33,13 +33,13 @@ func (hmy *Harmony) GetShardState() (*shard.State, error) { // GetBlockSigners .. func (hmy *Harmony) GetBlockSigners( - ctx context.Context, blockNr rpc.BlockNumber, + ctx context.Context, blockNum rpc.BlockNumber, ) (shard.SlotList, *internal_bls.Mask, error) { - blk, err := hmy.BlockByNumber(ctx, blockNr) + blk, err := hmy.BlockByNumber(ctx, blockNum) if err != nil { return nil, nil, err } - blockWithSigners, err := hmy.BlockByNumber(ctx, blockNr+1) + blockWithSigners, err := hmy.BlockByNumber(ctx, blockNum+1) if err != nil { return nil, nil, err } diff --git a/hmy/hmy.go b/hmy/hmy.go index 00ef8d3d3..3f9ed6f65 100644 --- a/hmy/hmy.go +++ b/hmy/hmy.go @@ -17,7 +17,7 @@ import ( "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/vm" nodeconfig "github.com/harmony-one/harmony/internal/configs/node" - commonRPC "github.com/harmony-one/harmony/internal/hmyapi/common" + commonRPC "github.com/harmony-one/harmony/rpc/common" "github.com/harmony-one/harmony/shard" staking "github.com/harmony-one/harmony/staking/types" lru "github.com/hashicorp/golang-lru" diff --git a/hmy/network.go b/hmy/net.go similarity index 93% rename from hmy/network.go rename to hmy/net.go index 1e8a65089..a1a7c685b 100644 --- a/hmy/network.go +++ b/hmy/net.go @@ -2,7 +2,7 @@ package hmy import ( nodeconfig "github.com/harmony-one/harmony/internal/configs/node" - commonRPC "github.com/harmony-one/harmony/internal/hmyapi/common" + commonRPC "github.com/harmony-one/harmony/rpc/common" "github.com/harmony-one/harmony/staking/network" "github.com/libp2p/go-libp2p-core/peer" ) diff --git a/hmy/staking.go b/hmy/staking.go index de62a1b45..aefaf101c 100644 --- a/hmy/staking.go +++ b/hmy/staking.go @@ -11,8 +11,8 @@ import ( "github.com/harmony-one/harmony/core/rawdb" "github.com/harmony-one/harmony/core/types" internal_common "github.com/harmony-one/harmony/internal/common" - commonRPC "github.com/harmony-one/harmony/internal/hmyapi/common" "github.com/harmony-one/harmony/numeric" + commonRPC "github.com/harmony-one/harmony/rpc/common" "github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard/committee" "github.com/harmony-one/harmony/staking/availability" diff --git a/internal/hmyapi/README.md b/internal/hmyapi/README.md deleted file mode 100644 index b9d4d19fe..000000000 --- a/internal/hmyapi/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# JSON RPC - -## JSSDK -[FireStack-Lab/Harmony-sdk-core](https://github.com/FireStack-Lab/Harmony-sdk-core) - -## JSON-RPC methods - -### Network info related -* [ ] net_listening - check if network is connected -* [x] hmy_protocolVersion - check protocol version -* [ ] net_version - get network id -* [ ] net_peerCount - peer count -* [x] hmy_getNodeMetadata - get node's version, bls key - -### BlockChain info related -* [ ] hmy_gasPrice - return min-gas-price -* [ ] hmy_estimateGas - calculating estimate gas using signed bytes -* [x] hmy_blockNumber - get latest block number -* [x] hmy_getBlockByHash - get block by block hash -* [x] hmy_getBlockByNumber -* [ ] hmy_getUncleByBlockHashAndIndex - get uncle by block hash and index number -* [ ] hmy_getUncleByBlockNumberAndIndex - get uncle by block number and index number -* [ ] hmy_getUncleCountByBlockHash - get uncle count by block hash -* [ ] hmy_getUncleCountByBlockNumber - get uncle count by block number -* [ ] hmy_syncing - Returns an object with data about the sync status -* [ ] hmy_coinbase - return coinbase address -* [ ] hmy_mining - return if mining client is mining -* [ ] hmy_hashrate - return current hash rate for blockchain - - -### Account related -* [x] hmy_getBalance - get balance for account address -* [x] hmy_getAccountNonce - get nonce for account address -* [ ] hmy_accounts - return accounts that lives in node - -### Transactions related -* [ ] hmy_getTransactionReceipt - get transaction receipt by given transaction hash -* [ ] hmy_sendRawTransaction - send transaction bytes(signed) to blockchain -* [ ] hmy_sendTransaction - send transaction object(with signature) to blockchain -* [x] hmy_getBlockTransactionCountByHash - get transaction count of block by block hash -* [x] hmy_getBlockTransactionCountByNumber - get transaction count of block by block number -* [x] hmy_getTransactionByHash - get transaction object of block by block hash -* [x] hmy_getTransactionByBlockHashAndIndex - get transaction object of block by block hash and index number -* [x] hmy_getTransactionByBlockNumberAndIndex - get transaction object of block by block number and index number -* [ ] hmy_sign - sign message using node specific sign method. -* [ ] hmy_pendingTransactions - returns the pending transactions list. -* [ ] hmy_getTransactionsCount - returns the number of confirmed (not pending) transactions count for the account -* [ ] hmy_getStakingTransactionsCount - returns the number of confirmed (not pending) staking transactions count for the account - - -### Contract related -* [ ] hmy_call - call contract method -* [x] hmy_getCode - get deployed contract's byte code -* [x] hmy_getStorageAt - get storage position at a given address -* ~~[ ] hmy_getCompilers~~ - DEPRECATED -* ~~[ ] hmy_compileLLL~~ - DEPRECATED -* ~~[ ] hmy_compileSolidity~~ - DEPRECATED -* ~~[ ] hmy_compileSerpent~~ - DEPRECATED - -### Subscribes -* [ ] hmy_getLogs - log subscriber -* [x] hmy_newFilter - creates a filter object, based on filter options -* [x] hmy_newBlockFilter - creates a filter in the node, to notify when a new block arrives -* [x] hmy_newPendingTransactionFilter - creates a filter in the node, to notify when new pending transactions arrive -* [ ] hmy_getFilterChanges - polling method for a filter -* [ ] hmy_getFilterLogs - returns an array of all logs matching filter with given id. -* [x] hmy_uninstallFilter - uninstalls a filter with given id - - -### Others, not very important for current stage of work -* [ ] web3_clientVersion -* [ ] web3_sha3 -* [ ] hmy_getWork -* [ ] hmy_submitWork -* [ ] hmy_submitHashrate -* [ ] hmy_getProof -* [ ] db_putString -* [ ] db_getString -* [ ] db_putHex -* [ ] db_getHex - -### SHH Whisper Protocol -The ``shh`` is for the whisper protocol to communicate p2p and broadcast - -* [ ] shh_post -* [ ] shh_version -* [ ] shh_newIdentity -* [ ] shh_hasIdentity -* [ ] shh_newGroup -* [ ] shh_addToGroup -* [ ] shh_newFilter -* [ ] shh_uninstallFilter -* [ ] shh_getFilterChanges -* [ ] shh_getMessages - -### API Versions -* For API V1 you specify 1.0 version in curl -* For API V2 you specify 2.0 version in curl, V2 has output numbers were changed to decimals and also fixed few errors diff --git a/internal/hmyapi/apiv1/addrlock.go b/internal/hmyapi/apiv1/addrlock.go deleted file mode 100644 index bda2e8471..000000000 --- a/internal/hmyapi/apiv1/addrlock.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package apiv1 - -import ( - "sync" - - "github.com/ethereum/go-ethereum/common" -) - -// AddrLocker ... -type AddrLocker struct { - mu sync.Mutex - locks map[common.Address]*sync.Mutex -} - -// lock returns the lock of the given address. -func (l *AddrLocker) lock(address common.Address) *sync.Mutex { - l.mu.Lock() - defer l.mu.Unlock() - if l.locks == nil { - l.locks = make(map[common.Address]*sync.Mutex) - } - if _, ok := l.locks[address]; !ok { - l.locks[address] = new(sync.Mutex) - } - return l.locks[address] -} - -// LockAddr locks an account's mutex. This is used to prevent another tx getting the -// same nonce until the lock is released. The mutex prevents the (an identical nonce) from -// being read again during the time that the first transaction is being signed. -func (l *AddrLocker) LockAddr(address common.Address) { - l.lock(address).Lock() -} - -// UnlockAddr unlocks the mutex of the given account. -func (l *AddrLocker) UnlockAddr(address common.Address) { - l.lock(address).Unlock() -} diff --git a/internal/hmyapi/apiv1/blockchain.go b/internal/hmyapi/apiv1/blockchain.go deleted file mode 100644 index 0ffb9358f..000000000 --- a/internal/hmyapi/apiv1/blockchain.go +++ /dev/null @@ -1,1033 +0,0 @@ -package apiv1 - -import ( - "context" - "fmt" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/rpc" - "github.com/harmony-one/harmony/block" - "github.com/harmony-one/harmony/common/denominations" - "github.com/harmony-one/harmony/consensus/quorum" - "github.com/harmony-one/harmony/consensus/reward" - "github.com/harmony-one/harmony/core" - "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/core/vm" - "github.com/harmony-one/harmony/hmy" - internal_common "github.com/harmony-one/harmony/internal/common" - "github.com/harmony-one/harmony/internal/params" - "github.com/harmony-one/harmony/internal/utils" - "github.com/harmony-one/harmony/numeric" - "github.com/harmony-one/harmony/shard" - "github.com/harmony-one/harmony/shard/committee" - "github.com/harmony-one/harmony/staking/network" - staking "github.com/harmony-one/harmony/staking/types" - "github.com/pkg/errors" -) - -const ( - defaultGasPrice = denominations.Nano - defaultFromAddress = "0x0000000000000000000000000000000000000000" - defaultBlocksPeriod = 15000 - validatorsPageSize = 100 - initSupply = int64(12600000000) -) - -// PublicBlockChainAPI provides an API to access the Harmony blockchain. -// It offers only methods that operate on public data that is freely available to anyone. -type PublicBlockChainAPI struct { - hmy *hmy.Harmony -} - -// NewPublicBlockChainAPI creates a new Harmony blockchain API. -func NewPublicBlockChainAPI(hmy *hmy.Harmony) *PublicBlockChainAPI { - return &PublicBlockChainAPI{hmy} -} - -// BlockArgs is struct to include optional block formatting params. -type BlockArgs struct { - WithSigners bool `json:"withSigners"` - InclTx bool `json:"inclTx"` - FullTx bool `json:"fullTx"` - Signers []string `json:"signers"` - InclStaking bool `json:"inclStaking"` -} - -func (s *PublicBlockChainAPI) isBeaconShard() error { - if s.hmy.ShardID != shard.BeaconChainShardID { - return ErrNotBeaconShard - } - return nil -} - -func (s *PublicBlockChainAPI) isBlockGreaterThanLatest(blockNum rpc.BlockNumber) error { - // rpc.BlockNumber is int64 (latest = -1. pending = -2) and currentBlockNum is uint64. - // Most straightfoward to make sure to return nil error for latest and pending block num - // since they are never greater than latest - if blockNum != rpc.PendingBlockNumber && - blockNum != rpc.LatestBlockNumber && - uint64(blockNum) > s.hmy.CurrentBlock().NumberU64() { - return ErrRequestedBlockTooHigh - } - return nil -} - -// GetBlockByNumber returns the requested block. When blockNum is -1 the chain head is returned. When fullTx is true all -// transactions in the block are returned in full detail, otherwise only the transaction hash is returned. -func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - blk, err := s.hmy.BlockByNumber(ctx, blockNum) - if blk != nil { - blockArgs := BlockArgs{WithSigners: false, InclTx: true, FullTx: fullTx, InclStaking: true} - leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) - response, err := RPCMarshalBlock(blk, blockArgs, leader) - if err == nil && blockNum == rpc.PendingBlockNumber { - // Pending blocks need to nil out a few fields - for _, field := range []string{"hash", "nonce", "miner"} { - response[field] = nil - } - } - return response, err - } - return nil, err -} - -// GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full -// detail, otherwise only the transaction hash is returned. -func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) { - blk, err := s.hmy.GetBlock(ctx, blockHash) - if blk != nil { - blockArgs := BlockArgs{WithSigners: false, InclTx: true, FullTx: fullTx, InclStaking: true} - leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) - return RPCMarshalBlock(blk, blockArgs, leader) - } - return nil, err -} - -// GetBlockByNumberNew returns the requested block. When blockNum is -1 the chain head is returned. When fullTx in blockArgs is true all -// transactions in the block are returned in full detail, otherwise only the transaction hash is returned. When withSigners in BlocksArgs is true -// it shows block signers for this block in list of one addresses. -func (s *PublicBlockChainAPI) GetBlockByNumberNew(ctx context.Context, blockNum rpc.BlockNumber, blockArgs BlockArgs) (map[string]interface{}, error) { - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - blk, err := s.hmy.BlockByNumber(ctx, blockNum) - blockArgs.InclTx = true - if blockArgs.WithSigners { - blockArgs.Signers, err = s.GetBlockSigners(ctx, blockNum) - if err != nil { - return nil, err - } - } - if blk != nil { - leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) - response, err := RPCMarshalBlock(blk, blockArgs, leader) - if err == nil && blockNum == rpc.PendingBlockNumber { - // Pending blocks need to nil out a few fields - for _, field := range []string{"hash", "nonce", "miner"} { - response[field] = nil - } - } - return response, err - } - return nil, err -} - -// GetBlockByHashNew returns the requested block. When fullTx in blockArgs is true all transactions in the block are returned in full -// detail, otherwise only the transaction hash is returned. When withSigners in BlocksArgs is true -// it shows block signers for this block in list of one addresses. -func (s *PublicBlockChainAPI) GetBlockByHashNew(ctx context.Context, blockHash common.Hash, blockArgs BlockArgs) (map[string]interface{}, error) { - blk, err := s.hmy.GetBlock(ctx, blockHash) - if err != nil { - return nil, err - } - blockArgs.InclTx = true - if blockArgs.WithSigners { - blockArgs.Signers, err = s.GetBlockSigners(ctx, rpc.BlockNumber(blk.NumberU64())) - if err != nil { - return nil, err - } - } - leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) - return RPCMarshalBlock(blk, blockArgs, leader) -} - -// GetBlocks method returns blocks in range blockStart, blockEnd just like GetBlockByNumber but all at once. -func (s *PublicBlockChainAPI) GetBlocks(ctx context.Context, blockStart rpc.BlockNumber, blockEnd rpc.BlockNumber, blockArgs BlockArgs) ([]map[string]interface{}, error) { - result := make([]map[string]interface{}, 0) - for i := blockStart; i <= blockEnd; i++ { - blk, err := s.hmy.BlockByNumber(ctx, i) - blockArgs.InclTx = true - if blockArgs.WithSigners { - blockArgs.Signers, err = s.GetBlockSigners(ctx, rpc.BlockNumber(i)) - if err != nil { - return nil, err - } - } - if blk != nil { - leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) - rpcBlock, err := RPCMarshalBlock(blk, blockArgs, leader) - if err == nil && i == rpc.PendingBlockNumber { - // Pending blocks need to nil out a few fields - for _, field := range []string{"hash", "nonce", "miner"} { - rpcBlock[field] = nil - } - } - result = append(result, rpcBlock) - } - } - return result, nil -} - -// GetValidators returns validators list for a particular epoch. -func (s *PublicBlockChainAPI) GetValidators(ctx context.Context, epoch int64) (map[string]interface{}, error) { - cmt, err := s.hmy.GetValidators(big.NewInt(epoch)) - if err != nil { - return nil, err - } - balanceQueryBlock := shard.Schedule.EpochLastBlock(uint64(epoch)) - if balanceQueryBlock > s.hmy.CurrentBlock().NumberU64() { - balanceQueryBlock = s.hmy.CurrentBlock().NumberU64() - } - validators := make([]map[string]interface{}, 0) - for _, validator := range cmt.Slots { - oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress) - if err != nil { - return nil, err - } - validatorBalance, err := s.getBalanceByBlockNumber(ctx, oneAddress, rpc.BlockNumber(balanceQueryBlock)) - if err != nil { - return nil, err - } - validatorsFields := map[string]interface{}{ - "address": oneAddress, - "balance": (*hexutil.Big)(validatorBalance), - } - validators = append(validators, validatorsFields) - } - result := map[string]interface{}{ - "shardID": cmt.ShardID, - "validators": validators, - } - return result, nil -} - -// GetValidatorKeys returns list of bls public keys in the committee for a particular epoch. -func (s *PublicBlockChainAPI) GetValidatorKeys(ctx context.Context, epoch int64) ([]string, error) { - cmt, err := s.hmy.GetValidators(big.NewInt(epoch)) - if err != nil { - return nil, err - } - - validators := make([]string, len(cmt.Slots)) - for i, v := range cmt.Slots { - validators[i] = v.BLSPublicKey.Hex() - } - return validators, nil -} - -// IsLastBlock checks if block is last epoch block. -func (s *PublicBlockChainAPI) IsLastBlock(blockNum uint64) (bool, error) { - if err := s.isBeaconShard(); err != nil { - return false, err - } - return shard.Schedule.IsLastBlock(blockNum), nil -} - -// EpochLastBlock returns epoch last block. -func (s *PublicBlockChainAPI) EpochLastBlock(epoch uint64) (uint64, error) { - if err := s.isBeaconShard(); err != nil { - return 0, err - } - return shard.Schedule.EpochLastBlock(epoch), nil -} - -// GetBlockSigners returns signers for a particular block. -func (s *PublicBlockChainAPI) GetBlockSigners(ctx context.Context, blockNum rpc.BlockNumber) ([]string, error) { - if uint64(blockNum) == 0 { - return []string{}, nil - } - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - slots, mask, err := s.hmy.GetBlockSigners(ctx, blockNum) - if err != nil { - return nil, err - } - signers := []string{} - for _, validator := range slots { - oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress) - if err != nil { - return nil, err - } - if ok, err := mask.KeyEnabled(validator.BLSPublicKey); err == nil && ok { - signers = append(signers, oneAddress) - } - } - return signers, nil -} - -// GetBlockSignerKeys returns bls public keys that signed the block. -func (s *PublicBlockChainAPI) GetBlockSignerKeys(ctx context.Context, blockNum rpc.BlockNumber) ([]string, error) { - if uint64(blockNum) == 0 { - return []string{}, nil - } - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - slots, mask, err := s.hmy.GetBlockSigners(ctx, blockNum) - if err != nil { - return nil, err - } - signers := []string{} - for _, validator := range slots { - if ok, err := mask.KeyEnabled(validator.BLSPublicKey); err == nil && ok { - signers = append(signers, validator.BLSPublicKey.Hex()) - } - } - return signers, nil -} - -// IsBlockSigner returns true if validator with address signed blockNum block. -func (s *PublicBlockChainAPI) IsBlockSigner(ctx context.Context, blockNum rpc.BlockNumber, address string) (bool, error) { - if uint64(blockNum) == 0 { - return false, nil - } - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return false, err - } - slots, mask, err := s.hmy.GetBlockSigners(ctx, blockNum) - if err != nil { - return false, err - } - for _, validator := range slots { - oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress) - if err != nil { - return false, err - } - if oneAddress != address { - continue - } - if ok, err := mask.KeyEnabled(validator.BLSPublicKey); err == nil && ok { - return true, nil - } - } - return false, nil -} - -// GetSignedBlocks returns how many blocks a particular validator signed for last defaultBlocksPeriod (3 hours ~ 1500 blocks). -func (s *PublicBlockChainAPI) GetSignedBlocks(ctx context.Context, address string) hexutil.Uint64 { - totalSigned := uint64(0) - lastBlock := uint64(0) - blockHeight := uint64(s.BlockNumber()) - if blockHeight >= defaultBlocksPeriod { - lastBlock = blockHeight - defaultBlocksPeriod + 1 - } - for i := lastBlock; i <= blockHeight; i++ { - signed, err := s.IsBlockSigner(ctx, rpc.BlockNumber(i), address) - if err == nil && signed { - totalSigned++ - } - } - return hexutil.Uint64(totalSigned) -} - -// GetEpoch returns current epoch. -func (s *PublicBlockChainAPI) GetEpoch(ctx context.Context) hexutil.Uint64 { - return hexutil.Uint64(s.LatestHeader(ctx).Epoch) -} - -// GetLeader returns current shard leader. -func (s *PublicBlockChainAPI) GetLeader(ctx context.Context) string { - return s.LatestHeader(ctx).Leader -} - -// GetValidatorSelfDelegation returns validator stake. -func (s *PublicBlockChainAPI) GetValidatorSelfDelegation(ctx context.Context, address string) (hexutil.Uint64, error) { - if err := s.isBeaconShard(); err != nil { - return hexutil.Uint64(0), err - } - return hexutil.Uint64(s.hmy.GetValidatorSelfDelegation(internal_common.ParseAddr(address)).Uint64()), nil -} - -// GetValidatorTotalDelegation returns total balace stacking for validator with delegation. -func (s *PublicBlockChainAPI) GetValidatorTotalDelegation(ctx context.Context, address string) (hexutil.Uint64, error) { - if err := s.isBeaconShard(); err != nil { - return hexutil.Uint64(0), err - } - delegations := s.hmy.GetDelegationsByValidator(internal_common.ParseAddr(address)) - totalStake := big.NewInt(0) - for _, delegation := range delegations { - totalStake.Add(totalStake, delegation.Amount) - } - // TODO: return more than uint64 - return hexutil.Uint64(totalStake.Uint64()), nil -} - -// GetShardingStructure returns an array of sharding structures. -func (s *PublicBlockChainAPI) GetShardingStructure(ctx context.Context) ([]map[string]interface{}, error) { - // Get header and number of shards. - epoch := s.GetEpoch(ctx) - numShard := shard.Schedule.InstanceForEpoch(big.NewInt(int64(epoch))).NumShards() - - // Return shareding structure for each case. - return shard.Schedule.GetShardingStructure(int(numShard), int(s.hmy.ShardID)), nil -} - -// GetShardID returns shard ID of the requested node. -func (s *PublicBlockChainAPI) GetShardID(ctx context.Context) (int, error) { - return int(s.hmy.ShardID), nil -} - -// GetCode returns the code stored at the given address in the state for the given block number. -func (s *PublicBlockChainAPI) GetCode(ctx context.Context, addr string, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { - address := internal_common.ParseAddr(addr) - state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNr) - if state == nil || err != nil { - return nil, err - } - code := state.GetCode(address) - return code, state.Error() -} - -// GetStorageAt returns the storage from the state at the given address, key and -// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block -// numbers are also allowed. -func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, addr string, key string, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { - address := internal_common.ParseAddr(addr) - state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNr) - if state == nil || err != nil { - return nil, err - } - res := state.GetState(address, common.HexToHash(key)) - return res[:], state.Error() -} - -func (s *PublicBlockChainAPI) getBalanceByBlockNumber(ctx context.Context, address string, blockNr rpc.BlockNumber) (*hexutil.Big, error) { - addr := internal_common.ParseAddr(address) - balance, err := s.hmy.GetBalance(ctx, addr, blockNr) - if balance == nil { - return nil, err - } - return (*hexutil.Big)(balance), err -} - -// GetBalanceByBlockNumber returns balance by block number. -func (s *PublicBlockChainAPI) GetBalanceByBlockNumber(ctx context.Context, address string, blockNr rpc.BlockNumber) (*hexutil.Big, error) { - if err := s.isBlockGreaterThanLatest(blockNr); err != nil { - return nil, err - } - return s.getBalanceByBlockNumber(ctx, address, blockNr) -} - -// GetAccountNonce returns the nonce value of the given address for the given block number -func (s *PublicBlockChainAPI) GetAccountNonce(ctx context.Context, address string, blockNr rpc.BlockNumber) (uint64, error) { - addr := internal_common.ParseAddr(address) - return s.hmy.GetAccountNonce(ctx, addr, rpc.BlockNumber(blockNr)) -} - -// GetBalance returns the amount of Atto for the given address in the state of the -// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta -// block numbers are also allowed. -func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address string, blockNr rpc.BlockNumber) (*hexutil.Big, error) { - return s.getBalanceByBlockNumber(ctx, address, rpc.BlockNumber(blockNr)) -} - -// BlockNumber returns the block number of the chain head. -func (s *PublicBlockChainAPI) BlockNumber() hexutil.Uint64 { - header, _ := s.hmy.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available - if header == nil { - return 0 - } - return hexutil.Uint64(header.Number().Uint64()) -} - -// ResendCx requests that the egress receipt for the given cross-shard -// transaction be sent to the destination shard for credit. This is used for -// unblocking a half-complete cross-shard transaction whose fund has been -// withdrawn already from the source shard but not credited yet in the -// destination account due to transient failures. -func (s *PublicBlockChainAPI) ResendCx(ctx context.Context, txID common.Hash) (bool, error) { - _, success := s.hmy.ResendCx(ctx, txID) - return success, nil -} - -// Call executes the given transaction on the state for the given block number. -// It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. -func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { - result, _, _, err := doCall(ctx, s.hmy, args, blockNr, vm.Config{}, 5*time.Second, s.hmy.RPCGasCap) - return (hexutil.Bytes)(result), err -} - -// LatestHeader returns the latest header information -func (s *PublicBlockChainAPI) LatestHeader(ctx context.Context) *HeaderInformation { - header, _ := s.hmy.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available - leader := s.hmy.GetLeaderAddress(header.Coinbase(), header.Epoch()) - return newHeaderInformation(header, leader) -} - -// GetHeaderByNumber returns block header at given number -func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, blockNum rpc.BlockNumber) (*HeaderInformation, error) { - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - header, err := s.hmy.HeaderByNumber(context.Background(), blockNum) - if err != nil { - return nil, err - } - leader := s.hmy.GetLeaderAddress(header.Coinbase(), header.Epoch()) - return newHeaderInformation(header, leader), nil -} - -// GetTotalStaking returns total staking by validators, only meant to be called on beaconchain -// explorer node -func (s *PublicBlockChainAPI) GetTotalStaking() (*big.Int, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetTotalStakingSnapshot(), nil -} - -// GetMedianRawStakeSnapshot returns the raw median stake, only meant to be called on beaconchain -// explorer node -func (s *PublicBlockChainAPI) GetMedianRawStakeSnapshot() ( - *committee.CompletedEPoSRound, error, -) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetMedianRawStakeSnapshot() -} - -// GetLatestChainHeaders .. -func (s *PublicBlockChainAPI) GetLatestChainHeaders() *block.HeaderPair { - return s.hmy.GetLatestChainHeaders() -} - -// GetAllValidatorAddresses returns all validator addresses. -func (s *PublicBlockChainAPI) GetAllValidatorAddresses() ([]string, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - validatorAddresses := s.hmy.GetAllValidatorAddresses() - addresses := make([]string, len(validatorAddresses)) - for i, addr := range validatorAddresses { - oneAddr, _ := internal_common.AddressToBech32(addr) - addresses[i] = oneAddr - } - return addresses, nil -} - -// GetElectedValidatorAddresses returns elected validator addresses. -func (s *PublicBlockChainAPI) GetElectedValidatorAddresses() ([]string, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - electedAddresses := s.hmy.GetElectedValidatorAddresses() - addresses := make([]string, len(electedAddresses)) - for i, addr := range electedAddresses { - oneAddr, _ := internal_common.AddressToBech32(addr) - addresses[i] = oneAddr - } - return addresses, nil -} - -// GetValidatorInformation returns information about a validator. -func (s *PublicBlockChainAPI) GetValidatorInformation( - ctx context.Context, address string, -) (*staking.ValidatorRPCEnhanced, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(rpc.LatestBlockNumber)) - if err != nil { - return nil, errors.Wrapf(err, "could not retrieve the latest blk information") - } - return s.hmy.GetValidatorInformation( - internal_common.ParseAddr(address), blk, - ) -} - -// GetValidatorInformationByBlockNumber returns information about a validator. -func (s *PublicBlockChainAPI) GetValidatorInformationByBlockNumber( - ctx context.Context, address string, blockNr rpc.BlockNumber, -) (*staking.ValidatorRPCEnhanced, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - if err := s.isBlockGreaterThanLatest(blockNr); err != nil { - return nil, err - } - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNr)) - if err != nil { - return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNr) - } - return s.hmy.GetValidatorInformation( - internal_common.ParseAddr(address), blk, - ) -} - -func (s *PublicBlockChainAPI) getAllValidatorInformation( - ctx context.Context, page int, blockNr rpc.BlockNumber, -) ([]*staking.ValidatorRPCEnhanced, error) { - if page < -1 { - return nil, errors.Errorf("page given %d cannot be less than -1", page) - } - addresses := s.hmy.GetAllValidatorAddresses() - if page != -1 && len(addresses) <= page*validatorsPageSize { - return make([]*staking.ValidatorRPCEnhanced, 0), nil - } - validatorsNum := len(addresses) - start := 0 - if page != -1 { - validatorsNum = validatorsPageSize - start = page * validatorsPageSize - if len(addresses)-start < validatorsPageSize { - validatorsNum = len(addresses) - start - } - } - validators := []*staking.ValidatorRPCEnhanced{} - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNr)) - if err != nil { - return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNr) - } - for i := start; i < start+validatorsNum; i++ { - information, err := s.hmy.GetValidatorInformation(addresses[i], blk) - if err == nil { - validators = append(validators, information) - } - } - return validators, nil -} - -// GetAllValidatorInformation returns information about all validators. -// If page is -1, return all instead of `validatorsPageSize` elements. -func (s *PublicBlockChainAPI) GetAllValidatorInformation( - ctx context.Context, page int, -) ([]*staking.ValidatorRPCEnhanced, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - blockNr := s.hmy.CurrentBlock().NumberU64() - - // delete cache for previous block - prevKey := fmt.Sprintf("all-info-%d", blockNr-1) - s.hmy.SingleFlightForgetKey(prevKey) - - key := fmt.Sprintf("all-info-%d", blockNr) - res, err := s.hmy.SingleFlightRequest( - key, - func() (interface{}, error) { - return s.getAllValidatorInformation(ctx, page, rpc.LatestBlockNumber) - }, - ) - if err != nil { - return nil, err - } - return res.([]*staking.ValidatorRPCEnhanced), nil -} - -// GetAllValidatorInformationByBlockNumber returns information about all validators. -// If page is -1, return all instead of `validatorsPageSize` elements. -func (s *PublicBlockChainAPI) GetAllValidatorInformationByBlockNumber( - ctx context.Context, page int, blockNr rpc.BlockNumber, -) ([]*staking.ValidatorRPCEnhanced, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - if err := s.isBlockGreaterThanLatest(blockNr); err != nil { - return nil, err - } - return s.getAllValidatorInformation(ctx, page, blockNr) -} - -// GetAllDelegationInformation returns delegation information about `validatorsPageSize` validators, -// starting at `page*validatorsPageSize`. -// If page is -1, return all instead of `validatorsPageSize` elements. -func (s *PublicBlockChainAPI) GetAllDelegationInformation(ctx context.Context, page int) ([][]*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - if page < -1 { - return make([][]*RPCDelegation, 0), nil - } - addresses := s.hmy.GetAllValidatorAddresses() - if page != -1 && len(addresses) <= page*validatorsPageSize { - return make([][]*RPCDelegation, 0), nil - } - validatorsNum := len(addresses) - start := 0 - if page != -1 { - validatorsNum = validatorsPageSize - start = page * validatorsPageSize - if len(addresses)-start < validatorsPageSize { - validatorsNum = len(addresses) - start - } - } - validators := make([][]*RPCDelegation, validatorsNum) - var err error - for i := start; i < start+validatorsNum; i++ { - validators[i-start], err = s.GetDelegationsByValidator(ctx, addresses[i].String()) - if err != nil { - return nil, err - } - } - return validators, nil -} - -// GetDelegationsByDelegator returns list of delegations for a delegator address. -func (s *PublicBlockChainAPI) GetDelegationsByDelegator(ctx context.Context, address string) ([]*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - delegatorAddress := internal_common.ParseAddr(address) - validators, delegations := s.hmy.GetDelegationsByDelegator(delegatorAddress) - result := []*RPCDelegation{} - for i := range delegations { - delegation := delegations[i] - - undelegations := make([]RPCUndelegation, len(delegation.Undelegations)) - - for j := range delegation.Undelegations { - undelegations = append(undelegations, RPCUndelegation{ - delegation.Undelegations[j].Amount, - delegation.Undelegations[j].Epoch, - }) - } - valAddr, _ := internal_common.AddressToBech32(validators[i]) - delAddr, _ := internal_common.AddressToBech32(delegatorAddress) - result = append(result, &RPCDelegation{ - valAddr, - delAddr, - delegation.Amount, - delegation.Reward, - undelegations, - }) - } - return result, nil -} - -// GetDelegationsByDelegatorByBlockNumber returns list of delegations for a delegator address at given block number -func (s *PublicBlockChainAPI) GetDelegationsByDelegatorByBlockNumber( - ctx context.Context, address string, blockNum rpc.BlockNumber, -) ([]*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - delegatorAddress := internal_common.ParseAddr(address) - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) - if err != nil { - return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNum) - } - validators, delegations := s.hmy.GetDelegationsByDelegatorByBlock(delegatorAddress, blk) - result := make([]*RPCDelegation, len(delegations)) - for i := range delegations { - delegation := delegations[i] - - undelegations := make([]RPCUndelegation, len(delegation.Undelegations)) - - for j := range delegation.Undelegations { - undelegations[j] = RPCUndelegation{ - delegation.Undelegations[j].Amount, - delegation.Undelegations[j].Epoch, - } - } - valAddr, _ := internal_common.AddressToBech32(validators[i]) - delAddr, _ := internal_common.AddressToBech32(delegatorAddress) - result[i] = &RPCDelegation{ - valAddr, - delAddr, - delegation.Amount, - delegation.Reward, - undelegations, - } - } - return result, nil -} - -// GetDelegationsByValidator returns list of delegations for a validator address. -func (s *PublicBlockChainAPI) GetDelegationsByValidator(ctx context.Context, address string) ([]*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - validatorAddress := internal_common.ParseAddr(address) - delegations := s.hmy.GetDelegationsByValidator(validatorAddress) - result := make([]*RPCDelegation, 0) - for _, delegation := range delegations { - - undelegations := []RPCUndelegation{} - - for j := range delegation.Undelegations { - undelegations = append(undelegations, RPCUndelegation{ - delegation.Undelegations[j].Amount, - delegation.Undelegations[j].Epoch, - }) - } - valAddr, _ := internal_common.AddressToBech32(validatorAddress) - delAddr, _ := internal_common.AddressToBech32(delegation.DelegatorAddress) - result = append(result, &RPCDelegation{ - valAddr, - delAddr, - delegation.Amount, - delegation.Reward, - undelegations, - }) - } - return result, nil -} - -// GetDelegationByDelegatorAndValidator returns a delegation for delegator and validator. -func (s *PublicBlockChainAPI) GetDelegationByDelegatorAndValidator(ctx context.Context, address string, validator string) (*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - delegatorAddress := internal_common.ParseAddr(address) - validatorAddress := internal_common.ParseAddr(validator) - validators, delegations := s.hmy.GetDelegationsByDelegator(delegatorAddress) - for i := range delegations { - if validators[i] != validatorAddress { - continue - } - delegation := delegations[i] - - undelegations := []RPCUndelegation{} - - for j := range delegation.Undelegations { - undelegations = append(undelegations, RPCUndelegation{ - delegation.Undelegations[j].Amount, - delegation.Undelegations[j].Epoch, - }) - } - valAddr, _ := internal_common.AddressToBech32(validatorAddress) - delAddr, _ := internal_common.AddressToBech32(delegatorAddress) - return &RPCDelegation{ - valAddr, - delAddr, - delegation.Amount, - delegation.Reward, - undelegations, - }, nil - } - return nil, nil -} - -// EstimateGas returns an estimate of the amount of gas needed to execute the -// given transaction against the current pending block. -func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { - return doEstimateGas(ctx, s.hmy, args, nil) -} - -// GetCurrentUtilityMetrics .. -func (s *PublicBlockChainAPI) GetCurrentUtilityMetrics() (*network.UtilityMetric, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetCurrentUtilityMetrics() -} - -// GetSuperCommittees .. -func (s *PublicBlockChainAPI) GetSuperCommittees() (*quorum.Transition, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetSuperCommittees() -} - -// GetCurrentBadBlocks .. -func (s *PublicBlockChainAPI) GetCurrentBadBlocks() []core.BadBlock { - return s.hmy.GetCurrentBadBlocks() -} - -// GetTotalSupply .. -func (s *PublicBlockChainAPI) GetTotalSupply() (numeric.Dec, error) { - return numeric.NewDec(initSupply), nil -} - -// GetCirculatingSupply .. -func (s *PublicBlockChainAPI) GetCirculatingSupply() (numeric.Dec, error) { - timestamp := time.Now() - return numeric.NewDec(initSupply).Mul(reward.PercentageForTimeStamp(timestamp.Unix())), nil -} - -// GetStakingNetworkInfo .. -func (s *PublicBlockChainAPI) GetStakingNetworkInfo( - ctx context.Context, -) (*StakingNetworkInfo, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - totalStaking, _ := s.GetTotalStaking() - round, _ := s.GetMedianRawStakeSnapshot() - epoch := s.LatestHeader(ctx).Epoch - epochLastBlock, _ := s.EpochLastBlock(epoch) - totalSupply, _ := s.GetTotalSupply() - circulatingSupply, _ := s.GetCirculatingSupply() - return &StakingNetworkInfo{ - TotalSupply: totalSupply, - CirculatingSupply: circulatingSupply, - EpochLastBlock: epochLastBlock, - TotalStaking: totalStaking, - MedianRawStake: round.MedianStake, - }, nil -} - -// GetLastCrossLinks .. -func (s *PublicBlockChainAPI) GetLastCrossLinks() ([]*types.CrossLink, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetLastCrossLinks() -} - -// docall executes an EVM call -func doCall(ctx context.Context, hmy *hmy.Harmony, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) { - defer func(start time.Time) { - utils.Logger().Debug(). - Dur("runtime", time.Since(start)). - Msg("Executing EVM call finished") - }(time.Now()) - - state, header, err := hmy.StateAndHeaderByNumber(ctx, blockNr) - if state == nil || err != nil { - return nil, 0, false, err - } - // Set sender address or use a default if none specified - var addr common.Address - if args.From == nil { - // Any address does not affect the logic of this call. - addr = common.HexToAddress(defaultFromAddress) - } else { - addr = *args.From - } - // Set default gas & gas price if none were set - gas := uint64(math.MaxUint64 / 2) - if args.Gas != nil { - gas = uint64(*args.Gas) - } - if globalGasCap != nil && globalGasCap.Uint64() < gas { - utils.Logger().Warn(). - Uint64("requested", gas). - Uint64("cap", globalGasCap.Uint64()). - Msg("Caller gas above allowance, capping") - gas = globalGasCap.Uint64() - } - gasPrice := new(big.Int).SetUint64(defaultGasPrice) - if args.GasPrice != nil { - gasPrice = args.GasPrice.ToInt() - } - - value := new(big.Int) - if args.Value != nil { - value = args.Value.ToInt() - } - - var data []byte - if args.Data != nil { - data = []byte(*args.Data) - } - - // Create new call message - msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) - - // Setup context so it may be cancelled the call has completed - // or, in case of unmetered gas, setup a context with a timeout. - var cancel context.CancelFunc - if timeout > 0 { - ctx, cancel = context.WithTimeout(ctx, timeout) - } else { - ctx, cancel = context.WithCancel(ctx) - } - // Make sure the context is cancelled when the call has completed - // this makes sure resources are cleaned up. - defer cancel() - - // Get a new instance of the EVM. - evm, vmError, err := hmy.GetEVM(ctx, msg, state, header) - if err != nil { - return nil, 0, false, err - } - // Wait for the context to be done and cancel the evm. Even if the - // EVM has finished, cancelling may be done (repeatedly) - go func() { - <-ctx.Done() - evm.Cancel() - }() - - // Setup the gas pool (also for unmetered requests) - // and apply the message. - gp := new(core.GasPool).AddGas(math.MaxUint64) - res, gas, failed, err := core.ApplyMessage(evm, msg, gp) - if err := vmError(); err != nil { - return nil, 0, false, err - } - // If the timer caused an abort, return an appropriate error message - if evm.Cancelled() { - return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout) - } - return res, gas, failed, err -} - -// doEstimateGas .. -func doEstimateGas(ctx context.Context, hmy *hmy.Harmony, args CallArgs, gasCap *big.Int) (hexutil.Uint64, error) { - // Binary search the gas requirement, as it may be higher than the amount used - var ( - lo = params.TxGas - 1 - hi uint64 - max uint64 - ) - blockNum := rpc.LatestBlockNumber - if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { - hi = uint64(*args.Gas) - } else { - // Retrieve the blk to act as the gas ceiling - blk, err := hmy.BlockByNumber(ctx, blockNum) - if err != nil { - return 0, err - } - hi = blk.GasLimit() - } - if gasCap != nil && hi > gasCap.Uint64() { - hi = gasCap.Uint64() - } - max = hi - - // Use zero-address if none other is available - if args.From == nil { - args.From = &common.Address{} - } - // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) bool { - args.Gas = (*hexutil.Uint64)(&gas) - - _, _, failed, err := doCall(ctx, hmy, args, blockNum, vm.Config{}, 0, big.NewInt(int64(max))) - if err != nil || failed { - return false - } - return true - } - // Execute the binary search and hone in on an executable gas limit - for lo+1 < hi { - mid := (hi + lo) / 2 - if !executable(mid) { - lo = mid - } else { - hi = mid - } - } - // Reject the transaction as invalid if it still fails at the highest allowance - if hi == max { - if !executable(hi) { - return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", max) - } - } - return hexutil.Uint64(hi), nil -} diff --git a/internal/hmyapi/apiv1/debug.go b/internal/hmyapi/apiv1/debug.go deleted file mode 100644 index c08ef06f7..000000000 --- a/internal/hmyapi/apiv1/debug.go +++ /dev/null @@ -1,32 +0,0 @@ -package apiv1 - -import ( - "context" - - "github.com/ethereum/go-ethereum/log" - "github.com/harmony-one/harmony/hmy" - "github.com/harmony-one/harmony/internal/utils" -) - -// DebugAPI Internal JSON RPC for debugging purpose -type DebugAPI struct { - hmy *hmy.Harmony -} - -// NewDebugAPI Creates a new DebugAPI instance -func NewDebugAPI(hmy *hmy.Harmony) *DebugAPI { - return &DebugAPI{hmy} -} - -// SetLogVerbosity Sets log verbosity on runtime -// Example usage: -// curl -H "Content-Type: application/json" -d '{"method":"debug_setLogVerbosity","params":[0],"id":1}' http://localhost:9123 -func (*DebugAPI) SetLogVerbosity(ctx context.Context, level int) (map[string]interface{}, error) { - if level < int(log.LvlCrit) || level > int(log.LvlTrace) { - return nil, ErrInvalidLogLevel - } - - verbosity := log.Lvl(level) - utils.SetLogVerbosity(verbosity) - return map[string]interface{}{"verbosity": verbosity.String()}, nil -} diff --git a/internal/hmyapi/apiv1/error.go b/internal/hmyapi/apiv1/error.go deleted file mode 100644 index b0d0b7a27..000000000 --- a/internal/hmyapi/apiv1/error.go +++ /dev/null @@ -1,18 +0,0 @@ -package apiv1 - -import ( - "errors" -) - -var ( - // ErrInvalidLogLevel when invalid log level is provided - ErrInvalidLogLevel = errors.New("invalid log level") - // ErrIncorrectChainID when ChainID does not match running node - ErrIncorrectChainID = errors.New("incorrect chain id") - // ErrInvalidChainID when ChainID of signer does not match that of running node - ErrInvalidChainID = errors.New("invalid chain id for signer") - // ErrNotBeaconShard when rpc is called on not beacon chain node - ErrNotBeaconShard = errors.New("cannot call this rpc on non beaconchain node") - // ErrRequestedBlockTooHigh when given block is greater than latest block number - ErrRequestedBlockTooHigh = errors.New("requested block number greater than current block number") -) diff --git a/internal/hmyapi/apiv1/harmony.go b/internal/hmyapi/apiv1/harmony.go deleted file mode 100644 index a51a2ef95..000000000 --- a/internal/hmyapi/apiv1/harmony.go +++ /dev/null @@ -1,54 +0,0 @@ -package apiv1 - -import ( - "context" - "math/big" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/harmony-one/harmony/hmy" - commonRPC "github.com/harmony-one/harmony/internal/hmyapi/common" -) - -// PublicHarmonyAPI provides an API to access Harmony related information. -// It offers only methods that operate on public data that is freely available to anyone. -type PublicHarmonyAPI struct { - hmy *hmy.Harmony -} - -// NewPublicHarmonyAPI ... -func NewPublicHarmonyAPI(hmy *hmy.Harmony) *PublicHarmonyAPI { - return &PublicHarmonyAPI{hmy} -} - -// ProtocolVersion returns the current Harmony protocol version this node supports -func (s *PublicHarmonyAPI) ProtocolVersion() hexutil.Uint { - return hexutil.Uint(s.hmy.ProtocolVersion()) -} - -// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not -// yet received the latest block headers from its pears. In case it is synchronizing: -// - startingBlock: block number this node started to synchronise from -// - currentBlock: block number this node is currently importing -// - highestBlock: block number of the highest block header this node has received from peers -// - pulledStates: number of state entries processed until now -// - knownStates: number of known state entries that still need to be pulled -func (s *PublicHarmonyAPI) Syncing() (interface{}, error) { - // TODO(dm): find our Downloader module for syncing blocks - return false, nil -} - -// GasPrice returns a suggestion for a gas price. -func (s *PublicHarmonyAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) { - // TODO(ricl): add SuggestPrice API - return (*hexutil.Big)(big.NewInt(1)), nil -} - -// GetNodeMetadata produces a NodeMetadata record, data is from the answering RPC node -func (s *PublicHarmonyAPI) GetNodeMetadata() commonRPC.NodeMetadata { - return s.hmy.GetNodeMetadata() -} - -// GetPeerInfo produces a NodePeerInfo record -func (s *PublicHarmonyAPI) GetPeerInfo() commonRPC.NodePeerInfo { - return s.hmy.GetPeerInfo() -} diff --git a/internal/hmyapi/apiv1/net.go b/internal/hmyapi/apiv1/net.go deleted file mode 100644 index c50098ef1..000000000 --- a/internal/hmyapi/apiv1/net.go +++ /dev/null @@ -1,29 +0,0 @@ -package apiv1 - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/harmony-one/harmony/p2p" -) - -// PublicNetAPI offers network related RPC methods -type PublicNetAPI struct { - net p2p.Host - chainID uint64 -} - -// NewPublicNetAPI creates a new net API instance. -func NewPublicNetAPI(net p2p.Host, chainID uint64) *PublicNetAPI { - return &PublicNetAPI{net, chainID} -} - -// PeerCount returns the number of connected peers -func (s *PublicNetAPI) PeerCount() hexutil.Uint { - return hexutil.Uint(s.net.GetPeerCount()) -} - -// Version returns the network version, i.e. ChainID identifying which network we are using -func (s *PublicNetAPI) Version() string { - return fmt.Sprintf("%d", s.chainID) -} diff --git a/internal/hmyapi/apiv1/transactionpool.go b/internal/hmyapi/apiv1/transactionpool.go deleted file mode 100644 index 9e5b6d6fb..000000000 --- a/internal/hmyapi/apiv1/transactionpool.go +++ /dev/null @@ -1,410 +0,0 @@ -package apiv1 - -import ( - "context" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/harmony-one/harmony/core" - "github.com/harmony-one/harmony/core/rawdb" - "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/hmy" - internal_common "github.com/harmony-one/harmony/internal/common" - staking "github.com/harmony-one/harmony/staking/types" - "github.com/pkg/errors" -) - -// TxHistoryArgs is struct to make GetTransactionsHistory request -type TxHistoryArgs struct { - Address string `json:"address"` - PageIndex uint32 `json:"pageIndex"` - PageSize uint32 `json:"pageSize"` - FullTx bool `json:"fullTx"` - TxType string `json:"txType"` - Order string `json:"order"` -} - -// PublicTransactionPoolAPI exposes methods for the RPC interface -type PublicTransactionPoolAPI struct { - hmy *hmy.Harmony - nonceLock *AddrLocker -} - -// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. -func NewPublicTransactionPoolAPI(hmy *hmy.Harmony, nonceLock *AddrLocker) *PublicTransactionPoolAPI { - return &PublicTransactionPoolAPI{hmy, nonceLock} -} - -// GetTransactionsHistory returns the list of transactions hashes that involve a particular address. -func (s *PublicTransactionPoolAPI) GetTransactionsHistory(ctx context.Context, args TxHistoryArgs) (map[string]interface{}, error) { - var address string - var result []common.Hash - var err error - if strings.HasPrefix(args.Address, "one1") { - address = args.Address - } else { - addr := internal_common.ParseAddr(args.Address) - address, err = internal_common.AddressToBech32(addr) - if err != nil { - return nil, err - } - } - hashes, err := s.hmy.GetTransactionsHistory(address, args.TxType, args.Order) - if err != nil { - return nil, err - } - result = ReturnWithPagination(hashes, args.PageIndex, args.PageSize) - if !args.FullTx { - return map[string]interface{}{"transactions": result}, nil - } - txs := []*RPCTransaction{} - for _, hash := range result { - tx := s.GetTransactionByHash(ctx, hash) - txs = append(txs, tx) - } - return map[string]interface{}{"transactions": txs}, nil -} - -// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. -func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNum rpc.BlockNumber) *hexutil.Uint { - if block, _ := s.hmy.BlockByNumber(ctx, blockNum); block != nil { - n := hexutil.Uint(len(block.Transactions())) - return &n - } - return nil -} - -// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash. -func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint { - if block, _ := s.hmy.GetBlock(ctx, blockHash); block != nil { - n := hexutil.Uint(len(block.Transactions())) - return &n - } - return nil -} - -// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. -func (s *PublicTransactionPoolAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNum rpc.BlockNumber, index hexutil.Uint) *RPCTransaction { - if block, _ := s.hmy.BlockByNumber(ctx, blockNum); block != nil { - return newRPCTransactionFromBlockIndex(block, uint64(index)) - } - return nil -} - -// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. -func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction { - if block, _ := s.hmy.GetBlock(ctx, blockHash); block != nil { - return newRPCTransactionFromBlockIndex(block, uint64(index)) - } - return nil -} - -// GetTransactionByHash returns the plain transaction for the given hash -func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) *RPCTransaction { - // Try to return an already finalized transaction - tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.hmy.ChainDb(), hash) - block, _ := s.hmy.GetBlock(ctx, blockHash) - if block == nil { - return nil - } - if tx != nil { - return newRPCTransaction(tx, blockHash, blockNumber, block.Time().Uint64(), index) - } - // Transaction unknown, return as such - return nil -} - -// GetStakingTransactionByHash returns the staking transaction for the given hash -func (s *PublicTransactionPoolAPI) GetStakingTransactionByHash(ctx context.Context, hash common.Hash) *RPCStakingTransaction { - // Try to return an already finalized transaction - stx, blockHash, blockNumber, index := rawdb.ReadStakingTransaction(s.hmy.ChainDb(), hash) - block, _ := s.hmy.GetBlock(ctx, blockHash) - if block == nil { - return nil - } - if stx != nil { - return newRPCStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index) - } - // Transaction unknown, return as such - return nil -} - -// GetStakingTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. -func (s *PublicTransactionPoolAPI) GetStakingTransactionByBlockNumberAndIndex(ctx context.Context, blockNum rpc.BlockNumber, index hexutil.Uint) *RPCStakingTransaction { - if block, _ := s.hmy.BlockByNumber(ctx, blockNum); block != nil { - return newRPCStakingTransactionFromBlockIndex(block, uint64(index)) - } - return nil -} - -// GetStakingTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. -func (s *PublicTransactionPoolAPI) GetStakingTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCStakingTransaction { - if block, _ := s.hmy.GetBlock(ctx, blockHash); block != nil { - return newRPCStakingTransactionFromBlockIndex(block, uint64(index)) - } - return nil -} - -// GetTransactionCount returns the number of transactions the given address has sent for the given block number. -// Legacy for apiv1. For apiv2, please use getAccountNonce/getPoolNonce/getTransactionsCount/getStakingTransactionsCount apis for -// more granular transaction counts queries -func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr string, blockNum rpc.BlockNumber) (*hexutil.Uint64, error) { - address := internal_common.ParseAddr(addr) - // Ask transaction pool for the nonce which includes pending transactions - if blockNum == rpc.PendingBlockNumber { - nonce, err := s.hmy.GetPoolNonce(ctx, address) - if err != nil { - return nil, err - } - return (*hexutil.Uint64)(&nonce), nil - } - // Resolve block number and use its state to ask for the nonce - state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNum) - if state == nil || err != nil { - return nil, err - } - nonce := state.GetNonce(address) - return (*hexutil.Uint64)(&nonce), state.Error() -} - -// GetTransactionsCount returns the number of regular transactions from genesis of input type ("SENT", "RECEIVED", "ALL") -func (s *PublicTransactionPoolAPI) GetTransactionsCount(ctx context.Context, address, txType string) (uint64, error) { - var err error - if !strings.HasPrefix(address, "one1") { - addr := internal_common.ParseAddr(address) - address, err = internal_common.AddressToBech32(addr) - if err != nil { - return 0, err - } - } - return s.hmy.GetTransactionsCount(address, txType) -} - -// GetStakingTransactionsCount returns the number of staking transactions from genesis of input type ("SENT", "RECEIVED", "ALL") -func (s *PublicTransactionPoolAPI) GetStakingTransactionsCount(ctx context.Context, address, txType string) (uint64, error) { - var err error - if !strings.HasPrefix(address, "one1") { - addr := internal_common.ParseAddr(address) - address, err = internal_common.AddressToBech32(addr) - if err != nil { - return 0, err - } - } - return s.hmy.GetStakingTransactionsCount(address, txType) -} - -// SendRawStakingTransaction will add the signed transaction to the transaction pool. -// The sender is responsible for signing the transaction and using the correct nonce. -func (s *PublicTransactionPoolAPI) SendRawStakingTransaction( - ctx context.Context, encodedTx hexutil.Bytes, -) (common.Hash, error) { - if len(encodedTx) >= types.MaxEncodedPoolTransactionSize { - err := errors.Wrapf(core.ErrOversizedData, "encoded tx size: %d", len(encodedTx)) - return common.Hash{}, err - } - tx := new(staking.StakingTransaction) - if err := rlp.DecodeBytes(encodedTx, tx); err != nil { - return common.Hash{}, err - } - c := s.hmy.ChainConfig().ChainID - if id := tx.ChainID(); id.Cmp(c) != 0 { - return common.Hash{}, errors.Wrapf( - ErrInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(), - ) - } - return SubmitStakingTransaction(ctx, s.hmy, tx) -} - -// SendRawTransaction will add the signed transaction to the transaction pool. -// The sender is responsible for signing the transaction and using the correct nonce. -func (s *PublicTransactionPoolAPI) SendRawTransaction( - ctx context.Context, encodedTx hexutil.Bytes, -) (common.Hash, error) { - if len(encodedTx) >= types.MaxEncodedPoolTransactionSize { - err := errors.Wrapf(core.ErrOversizedData, "encoded tx size: %d", len(encodedTx)) - return common.Hash{}, err - } - tx := new(types.Transaction) - if err := rlp.DecodeBytes(encodedTx, tx); err != nil { - return common.Hash{}, err - } - c := s.hmy.ChainConfig().ChainID - if id := tx.ChainID(); id.Cmp(c) != 0 { - return common.Hash{}, errors.Wrapf( - ErrInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(), - ) - } - return SubmitTransaction(ctx, s.hmy, tx) -} - -func (s *PublicTransactionPoolAPI) fillTransactionFields(tx *types.Transaction, fields map[string]interface{}) error { - var err error - fields["shardID"] = tx.ShardID() - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainID()) - } - from, _ := types.Sender(signer, tx) - fields["from"] = from - fields["to"] = "" - if tx.To() != nil { - fields["to"], err = internal_common.AddressToBech32(*tx.To()) - if err != nil { - return err - } - fields["from"], err = internal_common.AddressToBech32(from) - if err != nil { - return err - } - } - return nil -} - -func (s *PublicTransactionPoolAPI) fillStakingTransactionFields(stx *staking.StakingTransaction, fields map[string]interface{}) error { - from, err := stx.SenderAddress() - if err != nil { - return err - } - fields["sender"], err = internal_common.AddressToBech32(from) - if err != nil { - return err - } - fields["type"] = stx.StakingType() - return nil -} - -// GetTransactionReceipt returns the transaction receipt for the given transaction hash. -func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { - var tx *types.Transaction - var stx *staking.StakingTransaction - var blockHash common.Hash - var blockNumber, index uint64 - tx, blockHash, blockNumber, index = rawdb.ReadTransaction(s.hmy.ChainDb(), hash) - if tx == nil { - stx, blockHash, blockNumber, index = rawdb.ReadStakingTransaction(s.hmy.ChainDb(), hash) - if stx == nil { - return nil, nil - } - } - receipts, err := s.hmy.GetReceipts(ctx, blockHash) - if err != nil { - return nil, err - } - if len(receipts) <= int(index) { - return nil, nil - } - receipt := receipts[index] - fields := map[string]interface{}{ - "blockHash": blockHash, - "blockNumber": hexutil.Uint64(blockNumber), - "transactionHash": hash, - "transactionIndex": hexutil.Uint64(index), - "gasUsed": hexutil.Uint64(receipt.GasUsed), - "cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed), - "contractAddress": nil, - "logs": receipt.Logs, - "logsBloom": receipt.Bloom, - } - if tx != nil { - if err = s.fillTransactionFields(tx, fields); err != nil { - return nil, err - } - } else { // stx not nil - if err = s.fillStakingTransactionFields(stx, fields); err != nil { - return nil, err - } - } - // Assign receipt status or post state. - if len(receipt.PostState) > 0 { - fields["root"] = hexutil.Bytes(receipt.PostState) - } else { - fields["status"] = hexutil.Uint(receipt.Status) - } - if receipt.Logs == nil { - fields["logs"] = [][]*types.Log{} - } - // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation - if receipt.ContractAddress != (common.Address{}) { - fields["contractAddress"] = receipt.ContractAddress - } - return fields, nil -} - -// GetPoolStats returns stats for the tx-pool -func (s *PublicTransactionPoolAPI) GetPoolStats() map[string]interface{} { - pendingCount, queuedCount := s.hmy.GetPoolStats() - return map[string]interface{}{ - "executable-count": pendingCount, - "non-executable-count": queuedCount, - } -} - -// PendingTransactions returns the plain transactions that are in the transaction pool -func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) { - pending, err := s.hmy.GetPoolTransactions() - if err != nil { - return nil, err - } - transactions := make([]*RPCTransaction, 0) - for i := range pending { - if plainTx, ok := pending[i].(*types.Transaction); ok { - if tx := newRPCTransaction(plainTx, common.Hash{}, 0, 0, 0); tx != nil { - transactions = append(transactions, tx) - } - } else if _, ok := pending[i].(*staking.StakingTransaction); ok { - continue // Do not return staking transactions here. - } else { - return nil, types.ErrUnknownPoolTxType - } - } - return transactions, nil -} - -// PendingStakingTransactions returns the staking transactions that are in the transaction pool -func (s *PublicTransactionPoolAPI) PendingStakingTransactions() ([]*RPCStakingTransaction, error) { - pending, err := s.hmy.GetPoolTransactions() - if err != nil { - return nil, err - } - transactions := make([]*RPCStakingTransaction, 0) - for i := range pending { - if _, ok := pending[i].(*types.Transaction); ok { - continue // Do not return plain transactions here - } else if stakingTx, ok := pending[i].(*staking.StakingTransaction); ok { - if tx := newRPCStakingTransaction(stakingTx, common.Hash{}, 0, 0, 0); tx != nil { - transactions = append(transactions, tx) - } - } else { - return nil, types.ErrUnknownPoolTxType - } - } - return transactions, nil -} - -// GetCurrentTransactionErrorSink .. -func (s *PublicTransactionPoolAPI) GetCurrentTransactionErrorSink() types.TransactionErrorReports { - return s.hmy.GetCurrentTransactionErrorSink() -} - -// GetCurrentStakingErrorSink .. -func (s *PublicTransactionPoolAPI) GetCurrentStakingErrorSink() types.TransactionErrorReports { - return s.hmy.GetCurrentStakingErrorSink() -} - -// GetCXReceiptByHash returns the transaction for the given hash -func (s *PublicTransactionPoolAPI) GetCXReceiptByHash( - ctx context.Context, hash common.Hash, -) *RPCCXReceipt { - if cx, blockHash, blockNumber, _ := rawdb.ReadCXReceipt(s.hmy.ChainDb(), hash); cx != nil { - return newRPCCXReceipt(cx, blockHash, blockNumber) - } - return nil -} - -// GetPendingCXReceipts .. -func (s *PublicTransactionPoolAPI) GetPendingCXReceipts(ctx context.Context) []*types.CXReceiptsProof { - return s.hmy.GetPendingCXReceipts() -} diff --git a/internal/hmyapi/apiv1/types.go b/internal/hmyapi/apiv1/types.go deleted file mode 100644 index f16adc7c9..000000000 --- a/internal/hmyapi/apiv1/types.go +++ /dev/null @@ -1,538 +0,0 @@ -package apiv1 - -import ( - "encoding/hex" - "math/big" - "strings" - "time" - - "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/rlp" - "github.com/harmony-one/harmony/block" - "github.com/harmony-one/harmony/core/types" - internal_common "github.com/harmony-one/harmony/internal/common" - "github.com/harmony-one/harmony/numeric" - "github.com/harmony-one/harmony/shard" - staking "github.com/harmony-one/harmony/staking/types" -) - -// RPCBlock represents a block that will serialize to the RPC representation of a block -type RPCBlock struct { - Number *hexutil.Big `json:"number"` - ViewID *hexutil.Big `json:"viewID"` - Epoch *hexutil.Big `json:"epoch"` - Hash common.Hash `json:"hash"` - ParentHash common.Hash `json:"parentHash"` - Nonce types.BlockNonce `json:"nonce"` - MixHash common.Hash `json:"mixHash"` - LogsBloom ethtypes.Bloom `json:"logsBloom"` - StateRoot common.Hash `json:"stateRoot"` - Miner common.Address `json:"miner"` - Difficulty *hexutil.Big `json:"difficulty"` - ExtraData []byte `json:"extraData"` - Size hexutil.Uint64 `json:"size"` - GasLimit hexutil.Uint64 `json:"gasLimit"` - GasUsed hexutil.Uint64 `json:"gasUsed"` - Timestamp *big.Int `json:"timestamp"` - TransactionsRoot common.Hash `json:"transactionsRoot"` - ReceiptsRoot common.Hash `json:"receiptsRoot"` - Transactions []interface{} `json:"transactions"` - StakingTxs []interface{} `json:"stakingTxs"` - Uncles []common.Hash `json:"uncles"` - TotalDifficulty *big.Int `json:"totalDifficulty"` - Signers []string `json:"signers"` -} - -// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction -type RPCTransaction struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From string `json:"from"` - Timestamp hexutil.Uint64 `json:"timestamp"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To string `json:"to"` - TransactionIndex hexutil.Uint `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - ShardID uint32 `json:"shardID"` - ToShardID uint32 `json:"toShardID"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` -} - -// RPCStakingTransaction represents a transaction that will serialize to the RPC representation of a staking transaction -type RPCStakingTransaction struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From string `json:"from"` - Timestamp hexutil.Uint64 `json:"timestamp"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Nonce hexutil.Uint64 `json:"nonce"` - TransactionIndex hexutil.Uint `json:"transactionIndex"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` - Type string `json:"type"` - Msg map[string]interface{} `json:"msg"` -} - -// RPCCXReceipt represents a CXReceipt that will serialize to the RPC representation of a CXReceipt -type RPCCXReceipt struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - TxHash common.Hash `json:"hash"` - From string `json:"from"` - To string `json:"to"` - ShardID uint32 `json:"shardID"` - ToShardID uint32 `json:"toShardID"` - Amount *hexutil.Big `json:"value"` -} - -// HeaderInformation represents the latest consensus information -type HeaderInformation struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber uint64 `json:"blockNumber"` - ShardID uint32 `json:"shardID"` - Leader string `json:"leader"` - ViewID uint64 `json:"viewID"` - Epoch uint64 `json:"epoch"` - Timestamp string `json:"timestamp"` - UnixTime uint64 `json:"unixtime"` - LastCommitSig string `json:"lastCommitSig"` - LastCommitBitmap string `json:"lastCommitBitmap"` - CrossLinks *types.CrossLinks `json:"crossLinks,omitempty"` -} - -// RPCDelegation represents a particular delegation to a validator -type RPCDelegation struct { - ValidatorAddress string `json:"validator_address"` - DelegatorAddress string `json:"delegator_address"` - Amount *big.Int `json:"amount"` - Reward *big.Int `json:"reward"` - Undelegations []RPCUndelegation `json:"Undelegations"` -} - -// RPCUndelegation represents one undelegation entry -type RPCUndelegation struct { - Amount *big.Int - Epoch *big.Int -} - -// CallArgs represents the arguments for a call. -type CallArgs struct { - From *common.Address `json:"from"` - To *common.Address `json:"to"` - Gas *hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Value *hexutil.Big `json:"value"` - Data *hexutil.Bytes `json:"data"` -} - -// StakingNetworkInfo returns global staking info. -type StakingNetworkInfo struct { - TotalSupply numeric.Dec `json:"total-supply"` - CirculatingSupply numeric.Dec `json:"circulating-supply"` - EpochLastBlock uint64 `json:"epoch-last-block"` - TotalStaking *big.Int `json:"total-staking"` - MedianRawStake numeric.Dec `json:"median-raw-stake"` -} - -func newHeaderInformation(header *block.Header, leader string) *HeaderInformation { - if header == nil { - return nil - } - - result := &HeaderInformation{ - BlockHash: header.Hash(), - BlockNumber: header.Number().Uint64(), - ShardID: header.ShardID(), - Leader: leader, - ViewID: header.ViewID().Uint64(), - Epoch: header.Epoch().Uint64(), - UnixTime: header.Time().Uint64(), - Timestamp: time.Unix(header.Time().Int64(), 0).UTC().String(), - LastCommitBitmap: hex.EncodeToString(header.LastCommitBitmap()), - } - - sig := header.LastCommitSignature() - result.LastCommitSig = hex.EncodeToString(sig[:]) - - if header.ShardID() == shard.BeaconChainShardID { - decodedCrossLinks := &types.CrossLinks{} - err := rlp.DecodeBytes(header.CrossLinks(), decodedCrossLinks) - if err != nil { - result.CrossLinks = &types.CrossLinks{} - } else { - result.CrossLinks = decodedCrossLinks - } - } - - return result -} - -// newRPCCXReceipt returns a CXReceipt that will serialize to the RPC representation -func newRPCCXReceipt(cx *types.CXReceipt, blockHash common.Hash, blockNumber uint64) *RPCCXReceipt { - result := &RPCCXReceipt{ - BlockHash: blockHash, - TxHash: cx.TxHash, - Amount: (*hexutil.Big)(cx.Amount), - ShardID: cx.ShardID, - ToShardID: cx.ToShardID, - } - if blockHash != (common.Hash{}) { - result.BlockHash = blockHash - result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) - } - - fromAddr, err := internal_common.AddressToBech32(cx.From) - if err != nil { - return nil - } - toAddr := "" - if cx.To != nil { - if toAddr, err = internal_common.AddressToBech32(*cx.To); err != nil { - return nil - } - } - result.From = fromAddr - result.To = toAddr - - return result -} - -// newRPCTransaction returns a transaction that will serialize to the RPC -// representation, with the given location metadata set (if available). -// Note that all txs on Harmony are replay protected (post EIP155 epoch). -func newRPCTransaction( - tx *types.Transaction, blockHash common.Hash, - blockNumber uint64, timestamp uint64, index uint64, -) *RPCTransaction { - from, err := tx.SenderAddress() - if err != nil { - return nil - } - v, r, s := tx.RawSignatureValues() - - result := &RPCTransaction{ - Gas: hexutil.Uint64(tx.Gas()), - GasPrice: (*hexutil.Big)(tx.GasPrice()), - Hash: tx.Hash(), - Input: hexutil.Bytes(tx.Data()), - Nonce: hexutil.Uint64(tx.Nonce()), - Value: (*hexutil.Big)(tx.Value()), - ShardID: tx.ShardID(), - ToShardID: tx.ToShardID(), - Timestamp: hexutil.Uint64(timestamp), - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), - } - if blockHash != (common.Hash{}) { - result.BlockHash = blockHash - result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) - result.TransactionIndex = hexutil.Uint(index) - } - - fromAddr, err := internal_common.AddressToBech32(from) - if err != nil { - return nil - } - toAddr := "" - - if tx.To() != nil { - if toAddr, err = internal_common.AddressToBech32(*tx.To()); err != nil { - return nil - } - result.From = fromAddr - } else { - result.From = strings.ToLower(from.Hex()) - } - result.To = toAddr - - return result -} - -// newRPCStakingTransaction returns a staking transaction that will serialize to the RPC -// representation, with the given location metadata set (if available). -func newRPCStakingTransaction(tx *staking.StakingTransaction, blockHash common.Hash, - blockNumber uint64, timestamp uint64, index uint64, -) *RPCStakingTransaction { - from, err := tx.SenderAddress() - if err != nil { - return nil - } - v, r, s := tx.RawSignatureValues() - - fields := map[string]interface{}{} - - switch tx.StakingType() { - case staking.DirectiveCreateValidator: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCreateValidator) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.CreateValidator) - if !ok { - return nil - } - validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) - if err != nil { - return nil - } - fields = map[string]interface{}{ - "validatorAddress": validatorAddress, - "commissionRate": (*hexutil.Big)(msg.CommissionRates.Rate.Int), - "maxCommissionRate": (*hexutil.Big)(msg.CommissionRates.MaxRate.Int), - "maxChangeRate": (*hexutil.Big)(msg.CommissionRates.MaxChangeRate.Int), - "minSelfDelegation": (*hexutil.Big)(msg.MinSelfDelegation), - "maxTotalDelegation": (*hexutil.Big)(msg.MaxTotalDelegation), - "amount": (*hexutil.Big)(msg.Amount), - "name": msg.Description.Name, - "website": msg.Description.Website, - "identity": msg.Description.Identity, - "securityContact": msg.Description.SecurityContact, - "details": msg.Description.Details, - "slotPubKeys": msg.SlotPubKeys, - } - case staking.DirectiveEditValidator: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveEditValidator) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.EditValidator) - if !ok { - return nil - } - validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) - if err != nil { - return nil - } - // Edit validators txs need not have commission rates to edit - commissionRate := &hexutil.Big{} - if msg.CommissionRate != nil { - commissionRate = (*hexutil.Big)(msg.CommissionRate.Int) - } - fields = map[string]interface{}{ - "validatorAddress": validatorAddress, - "commisionRate": commissionRate, - "minSelfDelegation": (*hexutil.Big)(msg.MinSelfDelegation), - "maxTotalDelegation": (*hexutil.Big)(msg.MaxTotalDelegation), - "name": msg.Description.Name, - "website": msg.Description.Website, - "identity": msg.Description.Identity, - "securityContact": msg.Description.SecurityContact, - "details": msg.Description.Details, - "slotPubKeyToAdd": msg.SlotKeyToAdd, - "slotPubKeyToRemove": msg.SlotKeyToRemove, - } - case staking.DirectiveCollectRewards: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCollectRewards) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.CollectRewards) - if !ok { - return nil - } - delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) - if err != nil { - return nil - } - fields = map[string]interface{}{ - "delegatorAddress": delegatorAddress, - } - case staking.DirectiveDelegate: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveDelegate) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.Delegate) - if !ok { - return nil - } - delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) - if err != nil { - return nil - } - validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) - if err != nil { - return nil - } - fields = map[string]interface{}{ - "delegatorAddress": delegatorAddress, - "validatorAddress": validatorAddress, - "amount": (*hexutil.Big)(msg.Amount), - } - case staking.DirectiveUndelegate: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveUndelegate) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.Undelegate) - if !ok { - return nil - } - delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) - if err != nil { - return nil - } - validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) - if err != nil { - return nil - } - fields = map[string]interface{}{ - "delegatorAddress": delegatorAddress, - "validatorAddress": validatorAddress, - "amount": (*hexutil.Big)(msg.Amount), - } - } - - result := &RPCStakingTransaction{ - Gas: hexutil.Uint64(tx.Gas()), - GasPrice: (*hexutil.Big)(tx.GasPrice()), - Hash: tx.Hash(), - Nonce: hexutil.Uint64(tx.Nonce()), - Timestamp: hexutil.Uint64(timestamp), - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), - Type: tx.StakingType().String(), - Msg: fields, - } - if blockHash != (common.Hash{}) { - result.BlockHash = blockHash - result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) - result.TransactionIndex = hexutil.Uint(index) - } - - fromAddr, err := internal_common.AddressToBech32(from) - if err != nil { - return nil - } - result.From = fromAddr - - return result -} - -// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are -// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain -// transaction hashes. -func RPCMarshalBlock(b *types.Block, blockArgs BlockArgs, leader string) (map[string]interface{}, error) { - head := b.Header() // copies the header once - fields := map[string]interface{}{ - "number": (*hexutil.Big)(head.Number()), - "viewID": (*hexutil.Big)(head.ViewID()), - "epoch": (*hexutil.Big)(head.Epoch()), - "hash": b.Hash(), - "parentHash": head.ParentHash(), - "nonce": 0, // Remove this because we don't have it in our header - "mixHash": head.MixDigest(), - "logsBloom": head.Bloom(), - "stateRoot": head.Root(), - "miner": leader, - "difficulty": 0, // Remove this because we don't have it in our header - "extraData": hexutil.Bytes(head.Extra()), - "size": hexutil.Uint64(b.Size()), - "gasLimit": hexutil.Uint64(head.GasLimit()), - "gasUsed": hexutil.Uint64(head.GasUsed()), - "timestamp": hexutil.Uint64(head.Time().Uint64()), - "transactionsRoot": head.TxHash(), - "receiptsRoot": head.ReceiptHash(), - } - - if blockArgs.InclTx { - formatTx := func(tx *types.Transaction) (interface{}, error) { - return tx.Hash(), nil - } - if blockArgs.FullTx { - formatTx = func(tx *types.Transaction) (interface{}, error) { - return newRPCTransactionFromBlockHash(b, tx.Hash()), nil - } - } - txs := b.Transactions() - transactions := make([]interface{}, len(txs)) - var err error - for i, tx := range txs { - if transactions[i], err = formatTx(tx); err != nil { - return nil, err - } - } - fields["transactions"] = transactions - - if blockArgs.InclStaking { - formatStakingTx := func(tx *staking.StakingTransaction) (interface{}, error) { - return tx.Hash(), nil - } - if blockArgs.FullTx { - formatStakingTx = func(tx *staking.StakingTransaction) (interface{}, error) { - return newRPCStakingTransactionFromBlockHash(b, tx.Hash()), nil - } - } - stakingTxs := b.StakingTransactions() - stakingTransactions := make([]interface{}, len(stakingTxs)) - for i, tx := range stakingTxs { - if stakingTransactions[i], err = formatStakingTx(tx); err != nil { - return nil, err - } - } - fields["stakingTransactions"] = stakingTransactions - } - } - - uncles := b.Uncles() - uncleHashes := make([]common.Hash, len(uncles)) - for i, uncle := range uncles { - uncleHashes[i] = uncle.Hash() - } - fields["uncles"] = uncleHashes - if blockArgs.WithSigners { - fields["signers"] = blockArgs.Signers - } - return fields, nil -} - -// newRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction { - for idx, tx := range b.Transactions() { - if tx.Hash() == hash { - return newRPCTransactionFromBlockIndex(b, uint64(idx)) - } - } - return nil -} - -// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction { - txs := b.Transactions() - if index >= uint64(len(txs)) { - return nil - } - return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) -} - -// newRPCStakingTransactionFromBlockHash returns a staking transaction that will serialize to the RPC representation. -func newRPCStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCStakingTransaction { - for idx, tx := range b.StakingTransactions() { - if tx.Hash() == hash { - return newRPCStakingTransactionFromBlockIndex(b, uint64(idx)) - } - } - return nil -} - -// newRPCStakingTransactionFromBlockIndex returns a staking transaction that will serialize to the RPC representation. -func newRPCStakingTransactionFromBlockIndex(b *types.Block, index uint64) *RPCStakingTransaction { - txs := b.StakingTransactions() - if index >= uint64(len(txs)) { - return nil - } - return newRPCStakingTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) -} diff --git a/internal/hmyapi/apiv1/util.go b/internal/hmyapi/apiv1/util.go deleted file mode 100644 index 4e545f6e6..000000000 --- a/internal/hmyapi/apiv1/util.go +++ /dev/null @@ -1,73 +0,0 @@ -package apiv1 - -import ( - "context" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/hmy" - common2 "github.com/harmony-one/harmony/internal/common" - "github.com/harmony-one/harmony/internal/utils" - staking "github.com/harmony-one/harmony/staking/types" -) - -// defaultPageSize is to have default pagination. -const ( - defaultPageSize = uint32(1000) -) - -// ReturnWithPagination returns result with pagination (offset, page in TxHistoryArgs). -func ReturnWithPagination(hashes []common.Hash, pageIndex uint32, pageSize uint32) []common.Hash { - size := defaultPageSize - if pageSize > 0 { - size = pageSize - } - if uint64(size)*uint64(pageIndex) >= uint64(len(hashes)) { - return make([]common.Hash, 0) - } - if uint64(size)*uint64(pageIndex)+uint64(size) > uint64(len(hashes)) { - return hashes[size*pageIndex:] - } - return hashes[size*pageIndex : size*pageIndex+size] -} - -// SubmitTransaction is a helper function that submits tx to txPool and logs a message. -func SubmitTransaction( - ctx context.Context, hmy *hmy.Harmony, tx *types.Transaction, -) (common.Hash, error) { - if err := hmy.SendTx(ctx, tx); err != nil { - utils.Logger().Warn().Err(err).Msg("Could not submit transaction") - return tx.Hash(), err - } - if tx.To() == nil { - signer := types.MakeSigner(hmy.ChainConfig(), hmy.CurrentBlock().Epoch()) - from, err := types.Sender(signer, tx) - if err != nil { - return common.Hash{}, err - } - addr := crypto.CreateAddress(from, tx.Nonce()) - utils.Logger().Info(). - Str("fullhash", tx.Hash().Hex()). - Str("contract", common2.MustAddressToBech32(addr)). - Msg("Submitted contract creation") - } else { - utils.Logger().Info(). - Str("fullhash", tx.Hash().Hex()). - Str("recipient", tx.To().Hex()). - Msg("Submitted transaction") - } - return tx.Hash(), nil -} - -// SubmitStakingTransaction is a helper function that submits tx to txPool and logs a message. -func SubmitStakingTransaction( - ctx context.Context, hmy *hmy.Harmony, tx *staking.StakingTransaction, -) (common.Hash, error) { - if err := hmy.SendStakingTx(ctx, tx); err != nil { - utils.Logger().Warn().Err(err).Msg("Could not submit staking transaction") - return tx.Hash(), err - } - utils.Logger().Info().Str("fullhash", tx.Hash().Hex()).Msg("Submitted Staking transaction") - return tx.Hash(), nil -} diff --git a/internal/hmyapi/apiv2/addrlock.go b/internal/hmyapi/apiv2/addrlock.go deleted file mode 100644 index 468333b87..000000000 --- a/internal/hmyapi/apiv2/addrlock.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package apiv2 - -import ( - "sync" - - "github.com/ethereum/go-ethereum/common" -) - -// AddrLocker ... -type AddrLocker struct { - mu sync.Mutex - locks map[common.Address]*sync.Mutex -} - -// lock returns the lock of the given address. -func (l *AddrLocker) lock(address common.Address) *sync.Mutex { - l.mu.Lock() - defer l.mu.Unlock() - if l.locks == nil { - l.locks = make(map[common.Address]*sync.Mutex) - } - if _, ok := l.locks[address]; !ok { - l.locks[address] = new(sync.Mutex) - } - return l.locks[address] -} - -// LockAddr locks an account's mutex. This is used to prevent another tx getting the -// same nonce until the lock is released. The mutex prevents the (an identical nonce) from -// being read again during the time that the first transaction is being signed. -func (l *AddrLocker) LockAddr(address common.Address) { - l.lock(address).Lock() -} - -// UnlockAddr unlocks the mutex of the given account. -func (l *AddrLocker) UnlockAddr(address common.Address) { - l.lock(address).Unlock() -} diff --git a/internal/hmyapi/apiv2/blockchain.go b/internal/hmyapi/apiv2/blockchain.go deleted file mode 100644 index e579a9ad3..000000000 --- a/internal/hmyapi/apiv2/blockchain.go +++ /dev/null @@ -1,987 +0,0 @@ -package apiv2 - -import ( - "context" - "fmt" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/rpc" - "github.com/harmony-one/harmony/block" - "github.com/harmony-one/harmony/common/denominations" - "github.com/harmony-one/harmony/consensus/quorum" - "github.com/harmony-one/harmony/consensus/reward" - "github.com/harmony-one/harmony/core" - "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/core/vm" - "github.com/harmony-one/harmony/hmy" - internal_common "github.com/harmony-one/harmony/internal/common" - "github.com/harmony-one/harmony/internal/params" - "github.com/harmony-one/harmony/internal/utils" - "github.com/harmony-one/harmony/numeric" - "github.com/harmony-one/harmony/shard" - "github.com/harmony-one/harmony/shard/committee" - "github.com/harmony-one/harmony/staking/network" - staking "github.com/harmony-one/harmony/staking/types" - "github.com/pkg/errors" -) - -const ( - defaultGasPrice = denominations.Nano - defaultFromAddress = "0x0000000000000000000000000000000000000000" - defaultBlocksPeriod = 15000 - validatorsPageSize = 100 - initSupply = int64(12600000000) -) - -// PublicBlockChainAPI provides an API to access the Harmony blockchain. -// It offers only methods that operate on public data that is freely available to anyone. -type PublicBlockChainAPI struct { - hmy *hmy.Harmony -} - -// NewPublicBlockChainAPI creates a new Harmony blockchain API. -func NewPublicBlockChainAPI(hmy *hmy.Harmony) *PublicBlockChainAPI { - return &PublicBlockChainAPI{hmy} -} - -// BlockArgs is struct to include optional block formatting params. -type BlockArgs struct { - WithSigners bool `json:"withSigners"` - InclTx bool `json:"inclTx"` - FullTx bool `json:"fullTx"` - Signers []string `json:"signers"` - InclStaking bool `json:"inclStaking"` -} - -func (s *PublicBlockChainAPI) isBeaconShard() error { - if s.hmy.ShardID != shard.BeaconChainShardID { - return ErrNotBeaconShard - } - return nil -} - -func (s *PublicBlockChainAPI) isBlockGreaterThanLatest(blockNum uint64) error { - if blockNum > s.hmy.CurrentBlock().NumberU64() { - return ErrRequestedBlockTooHigh - } - return nil -} - -// GetBlockByNumber returns the requested block. When fullTx in blockArgs is true all transactions in the block are returned in full detail, -// otherwise only the transaction hash is returned. When withSigners in BlocksArgs is true it shows block signers for this block in list of one addresses. -func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, blockNum uint64, blockArgs BlockArgs) (map[string]interface{}, error) { - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) - blockArgs.InclTx = true - if blk != nil { - if blockArgs.WithSigners { - blockArgs.Signers, err = s.GetBlockSigners(ctx, blockNum) - if err != nil { - return nil, err - } - } - leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) - response, err := RPCMarshalBlock(blk, blockArgs, leader) - if err == nil && rpc.BlockNumber(blockNum) == rpc.PendingBlockNumber { - // Pending blocks need to nil out a few fields - for _, field := range []string{"hash", "nonce", "miner"} { - response[field] = nil - } - } - return response, err - } - return nil, err -} - -// GetBlockByHash returns the requested block. When fullTx in blockArgs is true all transactions in the block are returned in full -// detail, otherwise only the transaction hash is returned. When withSigners in BlocksArgs is true -// it shows block signers for this block in list of one addresses. -func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, blockArgs BlockArgs) (map[string]interface{}, error) { - blk, err := s.hmy.GetBlock(ctx, blockHash) - blockArgs.InclTx = true - if blk != nil { - if blockArgs.WithSigners { - blockArgs.Signers, err = s.GetBlockSigners(ctx, blk.NumberU64()) - if err != nil { - return nil, err - } - } - leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) - return RPCMarshalBlock(blk, blockArgs, leader) - } - return nil, err -} - -// GetBlocks method returns blocks in range blockStart, blockEnd just like GetBlockByNumber but all at once. -func (s *PublicBlockChainAPI) GetBlocks(ctx context.Context, blockStart, blockEnd uint64, blockArgs BlockArgs) ([]map[string]interface{}, error) { - result := make([]map[string]interface{}, 0) - for i := blockStart; i <= blockEnd; i++ { - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(i)) - blockArgs.InclTx = true - if blockArgs.WithSigners { - blockArgs.Signers, err = s.GetBlockSigners(ctx, i) - if err != nil { - return nil, err - } - } - if blk != nil { - leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) - rpcBlock, err := RPCMarshalBlock(blk, blockArgs, leader) - if err == nil && rpc.BlockNumber(i) == rpc.PendingBlockNumber { - // Pending blocks need to nil out a few fields - for _, field := range []string{"hash", "nonce", "miner"} { - rpcBlock[field] = nil - } - } - result = append(result, rpcBlock) - } - } - return result, nil -} - -// GetValidators returns validators list for a particular epoch. -func (s *PublicBlockChainAPI) GetValidators(ctx context.Context, epoch int64) (map[string]interface{}, error) { - cmt, err := s.hmy.GetValidators(big.NewInt(epoch)) - if err != nil { - return nil, err - } - balanceQueryBlock := shard.Schedule.EpochLastBlock(uint64(epoch)) - if balanceQueryBlock > s.hmy.CurrentBlock().NumberU64() { - balanceQueryBlock = s.hmy.CurrentBlock().NumberU64() - } - validators := make([]map[string]interface{}, 0) - for _, validator := range cmt.Slots { - oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress) - if err != nil { - return nil, err - } - addr := internal_common.ParseAddr(oneAddress) - validatorBalance, err := s.hmy.GetBalance(ctx, addr, rpc.BlockNumber(balanceQueryBlock)) - if err != nil { - return nil, err - } - validatorsFields := map[string]interface{}{ - "address": oneAddress, - "balance": validatorBalance, - } - validators = append(validators, validatorsFields) - } - result := map[string]interface{}{ - "shardID": cmt.ShardID, - "validators": validators, - } - return result, nil -} - -// GetValidatorKeys returns list of bls public keys in the committee for a particular epoch. -func (s *PublicBlockChainAPI) GetValidatorKeys(ctx context.Context, epoch int64) ([]string, error) { - cmt, err := s.hmy.GetValidators(big.NewInt(epoch)) - if err != nil { - return nil, err - } - - validators := make([]string, len(cmt.Slots)) - for i, v := range cmt.Slots { - validators[i] = v.BLSPublicKey.Hex() - } - return validators, nil -} - -// IsLastBlock checks if block is last epoch block. -func (s *PublicBlockChainAPI) IsLastBlock(blockNum uint64) (bool, error) { - if err := s.isBeaconShard(); err != nil { - return false, err - } - return shard.Schedule.IsLastBlock(blockNum), nil -} - -// EpochLastBlock returns epoch last block. -func (s *PublicBlockChainAPI) EpochLastBlock(epoch uint64) (uint64, error) { - if err := s.isBeaconShard(); err != nil { - return 0, err - } - return shard.Schedule.EpochLastBlock(epoch), nil -} - -// GetBlockSigners returns signers for a particular block. -func (s *PublicBlockChainAPI) GetBlockSigners(ctx context.Context, blockNum uint64) ([]string, error) { - if blockNum == 0 || blockNum >= uint64(s.BlockNumber()) { - return []string{}, nil - } - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - slots, mask, err := s.hmy.GetBlockSigners(ctx, rpc.BlockNumber(blockNum)) - if err != nil { - return nil, err - } - signers := []string{} - for _, validator := range slots { - oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress) - if err != nil { - return nil, err - } - if ok, err := mask.KeyEnabled(validator.BLSPublicKey); err == nil && ok { - signers = append(signers, oneAddress) - } - } - return signers, nil -} - -// GetBlockSignerKeys returns bls public keys that signed the block. -func (s *PublicBlockChainAPI) GetBlockSignerKeys(ctx context.Context, blockNum uint64) ([]string, error) { - if blockNum == 0 || blockNum >= uint64(s.BlockNumber()) { - return []string{}, nil - } - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - slots, mask, err := s.hmy.GetBlockSigners(ctx, rpc.BlockNumber(blockNum)) - if err != nil { - return nil, err - } - signers := []string{} - for _, validator := range slots { - if ok, err := mask.KeyEnabled(validator.BLSPublicKey); err == nil && ok { - signers = append(signers, validator.BLSPublicKey.Hex()) - } - } - return signers, nil -} - -// IsBlockSigner returns true if validator with address signed blockNum block. -func (s *PublicBlockChainAPI) IsBlockSigner(ctx context.Context, blockNum uint64, address string) (bool, error) { - if blockNum == 0 { - return false, nil - } - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return false, err - } - slots, mask, err := s.hmy.GetBlockSigners(ctx, rpc.BlockNumber(blockNum)) - if err != nil { - return false, err - } - for _, validator := range slots { - oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress) - if err != nil { - return false, err - } - if oneAddress != address { - continue - } - if ok, err := mask.KeyEnabled(validator.BLSPublicKey); err == nil && ok { - return true, nil - } - } - return false, nil -} - -// GetSignedBlocks returns how many blocks a particular validator signed for last defaultBlocksPeriod (3 hours ~ 1500 blocks). -func (s *PublicBlockChainAPI) GetSignedBlocks(ctx context.Context, address string) uint64 { - totalSigned := uint64(0) - lastBlock := uint64(0) - blockHeight := uint64(s.BlockNumber()) - if blockHeight >= defaultBlocksPeriod { - lastBlock = blockHeight - defaultBlocksPeriod + 1 - } - for i := lastBlock; i <= blockHeight; i++ { - signed, err := s.IsBlockSigner(ctx, i, address) - if err == nil && signed { - totalSigned++ - } - } - return totalSigned -} - -// GetEpoch returns current epoch. -func (s *PublicBlockChainAPI) GetEpoch(ctx context.Context) uint64 { - return s.LatestHeader(ctx).Epoch -} - -// GetLeader returns current shard leader. -func (s *PublicBlockChainAPI) GetLeader(ctx context.Context) string { - return s.LatestHeader(ctx).Leader -} - -// GetValidatorSelfDelegation returns validator stake. -func (s *PublicBlockChainAPI) GetValidatorSelfDelegation(ctx context.Context, address string) (*big.Int, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetValidatorSelfDelegation(internal_common.ParseAddr(address)), nil -} - -// GetValidatorTotalDelegation returns total balace stacking for validator with delegation. -func (s *PublicBlockChainAPI) GetValidatorTotalDelegation(ctx context.Context, address string) (*big.Int, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - delegations := s.hmy.GetDelegationsByValidator(internal_common.ParseAddr(address)) - totalStake := big.NewInt(0) - for _, delegation := range delegations { - totalStake.Add(totalStake, delegation.Amount) - } - return totalStake, nil -} - -// GetShardingStructure returns an array of sharding structures. -func (s *PublicBlockChainAPI) GetShardingStructure(ctx context.Context) ([]map[string]interface{}, error) { - // Get header and number of shards. - epoch := s.GetEpoch(ctx) - numShard := shard.Schedule.InstanceForEpoch(big.NewInt(int64(epoch))).NumShards() - - // Return shareding structure for each case. - return shard.Schedule.GetShardingStructure(int(numShard), int(s.hmy.ShardID)), nil -} - -// GetShardID returns shard ID of the requested node. -func (s *PublicBlockChainAPI) GetShardID(ctx context.Context) (int, error) { - return int(s.hmy.ShardID), nil -} - -// GetCode returns the code stored at the given address in the state for the given block number. -func (s *PublicBlockChainAPI) GetCode(ctx context.Context, addr string, blockNum uint64) (hexutil.Bytes, error) { - address := internal_common.ParseAddr(addr) - state, _, err := s.hmy.StateAndHeaderByNumber(ctx, rpc.BlockNumber(blockNum)) - if state == nil || err != nil { - return nil, err - } - code := state.GetCode(address) - return code, state.Error() -} - -// GetStorageAt returns the storage from the state at the given address, key and -// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block -// numbers are also allowed. -func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, addr string, key string, blockNum uint64) (hexutil.Bytes, error) { - address := internal_common.ParseAddr(addr) - state, _, err := s.hmy.StateAndHeaderByNumber(ctx, rpc.BlockNumber(blockNum)) - if state == nil || err != nil { - return nil, err - } - res := state.GetState(address, common.HexToHash(key)) - return res[:], state.Error() -} - -// GetBalanceByBlockNumber returns balance by block number. -func (s *PublicBlockChainAPI) GetBalanceByBlockNumber(ctx context.Context, address string, blockNum uint64) (*big.Int, error) { - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - addr := internal_common.ParseAddr(address) - return s.hmy.GetBalance(ctx, addr, rpc.BlockNumber(blockNum)) -} - -// GetAccountNonce returns the nonce value of the given address for the given block number -func (s *PublicBlockChainAPI) GetAccountNonce(ctx context.Context, address string, blockNr rpc.BlockNumber) (uint64, error) { - addr := internal_common.ParseAddr(address) - return s.hmy.GetAccountNonce(ctx, addr, blockNr) -} - -// GetBalance returns the amount of Atto for the given address in the state of the -// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta -// block numbers are also allowed. -func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address string) (*big.Int, error) { - addr := internal_common.ParseAddr(address) - return s.hmy.GetBalance(ctx, addr, rpc.BlockNumber(-1)) -} - -// BlockNumber returns the block number of the chain head. -func (s *PublicBlockChainAPI) BlockNumber() uint64 { - header, _ := s.hmy.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available - return header.Number().Uint64() -} - -// ResendCx requests that the egress receipt for the given cross-shard -// transaction be sent to the destination shard for credit. This is used for -// unblocking a half-complete cross-shard transaction whose fund has been -// withdrawn already from the source shard but not credited yet in the -// destination account due to transient failures. -func (s *PublicBlockChainAPI) ResendCx(ctx context.Context, txID common.Hash) (bool, error) { - _, success := s.hmy.ResendCx(ctx, txID) - return success, nil -} - -// Call executes the given transaction on the state for the given block number. -// It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. -func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr uint64) (hexutil.Bytes, error) { - result, _, _, err := doCall(ctx, s.hmy, args, rpc.BlockNumber(blockNr), vm.Config{}, 5*time.Second, s.hmy.RPCGasCap) - return (hexutil.Bytes)(result), err -} - -// LatestHeader returns the latest header information -func (s *PublicBlockChainAPI) LatestHeader(ctx context.Context) *HeaderInformation { - header, _ := s.hmy.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available - leader := s.hmy.GetLeaderAddress(header.Coinbase(), header.Epoch()) - return newHeaderInformation(header, leader) -} - -// GetHeaderByNumber returns block header at given number -func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, blockNum uint64) (*HeaderInformation, error) { - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - header, err := s.hmy.HeaderByNumber(context.Background(), rpc.BlockNumber(blockNum)) - if err != nil { - return nil, err - } - leader := s.hmy.GetLeaderAddress(header.Coinbase(), header.Epoch()) - return newHeaderInformation(header, leader), nil -} - -// GetTotalStaking returns total staking by validators, only meant to be called on beaconchain -// explorer node -func (s *PublicBlockChainAPI) GetTotalStaking() (*big.Int, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetTotalStakingSnapshot(), nil -} - -// GetMedianRawStakeSnapshot returns the raw median stake, only meant to be called on beaconchain -// explorer node -func (s *PublicBlockChainAPI) GetMedianRawStakeSnapshot() ( - *committee.CompletedEPoSRound, error, -) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetMedianRawStakeSnapshot() -} - -// GetAllValidatorAddresses returns all validator addresses. -func (s *PublicBlockChainAPI) GetAllValidatorAddresses() ([]string, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - validatorAddresses := s.hmy.GetAllValidatorAddresses() - addresses := make([]string, len(validatorAddresses)) - for i, addr := range validatorAddresses { - oneAddr, _ := internal_common.AddressToBech32(addr) - addresses[i] = oneAddr - } - return addresses, nil -} - -// GetElectedValidatorAddresses returns elected validator addresses. -func (s *PublicBlockChainAPI) GetElectedValidatorAddresses() ([]string, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - electedAddresses := s.hmy.GetElectedValidatorAddresses() - addresses := make([]string, len(electedAddresses)) - for i, addr := range electedAddresses { - oneAddr, _ := internal_common.AddressToBech32(addr) - addresses[i] = oneAddr - } - return addresses, nil -} - -// GetValidatorInformation .. -func (s *PublicBlockChainAPI) GetValidatorInformation( - ctx context.Context, address string, -) (*staking.ValidatorRPCEnhanced, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(rpc.LatestBlockNumber)) - if err != nil { - return nil, errors.Wrapf(err, "could not retrieve the latest blk information") - } - return s.hmy.GetValidatorInformation( - internal_common.ParseAddr(address), blk, - ) -} - -// GetValidatorInformationByBlockNumber .. -func (s *PublicBlockChainAPI) GetValidatorInformationByBlockNumber( - ctx context.Context, address string, blockNr uint64, -) (*staking.ValidatorRPCEnhanced, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - if err := s.isBlockGreaterThanLatest(blockNr); err != nil { - return nil, err - } - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNr)) - if err != nil { - return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNr) - } - return s.hmy.GetValidatorInformation( - internal_common.ParseAddr(address), blk, - ) -} - -func (s *PublicBlockChainAPI) getAllValidatorInformation( - ctx context.Context, page int, blockNr rpc.BlockNumber, -) ([]*staking.ValidatorRPCEnhanced, error) { - if page < -1 { - return nil, errors.Errorf("page given %d cannot be less than -1", page) - } - addresses := s.hmy.GetAllValidatorAddresses() - if page != -1 && len(addresses) <= page*validatorsPageSize { - return make([]*staking.ValidatorRPCEnhanced, 0), nil - } - validatorsNum := len(addresses) - start := 0 - if page != -1 { - validatorsNum = validatorsPageSize - start = page * validatorsPageSize - if len(addresses)-start < validatorsPageSize { - validatorsNum = len(addresses) - start - } - } - validators := []*staking.ValidatorRPCEnhanced{} - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNr)) - if err != nil { - return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNr) - } - for i := start; i < start+validatorsNum; i++ { - information, err := s.hmy.GetValidatorInformation(addresses[i], blk) - if err == nil { - validators = append(validators, information) - } - } - return validators, nil -} - -// GetAllValidatorInformation returns information about all validators. -// If page is -1, return all else return the pagination. -func (s *PublicBlockChainAPI) GetAllValidatorInformation( - ctx context.Context, page int, -) ([]*staking.ValidatorRPCEnhanced, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - - blockNr := s.hmy.CurrentBlock().NumberU64() - - // delete cache for previous block - prevKey := fmt.Sprintf("all-info-%d", blockNr-1) - s.hmy.SingleFlightForgetKey(prevKey) - - key := fmt.Sprintf("all-info-%d", blockNr) - res, err := s.hmy.SingleFlightRequest( - key, - func() (interface{}, error) { - return s.getAllValidatorInformation(ctx, page, rpc.LatestBlockNumber) - }, - ) - if err != nil { - return nil, err - } - return res.([]*staking.ValidatorRPCEnhanced), nil - -} - -// GetAllValidatorInformationByBlockNumber returns information about all validators. -// If page is -1, return all else return the pagination. -func (s *PublicBlockChainAPI) GetAllValidatorInformationByBlockNumber( - ctx context.Context, page int, blockNr uint64, -) ([]*staking.ValidatorRPCEnhanced, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - if err := s.isBlockGreaterThanLatest(blockNr); err != nil { - return nil, err - } - return s.getAllValidatorInformation(ctx, page, rpc.BlockNumber(blockNr)) -} - -// GetAllDelegationInformation returns delegation information about `validatorsPageSize` validators, -// starting at `page*validatorsPageSize`. -// If page is -1, return all instead of `validatorsPageSize` elements. -func (s *PublicBlockChainAPI) GetAllDelegationInformation(ctx context.Context, page int) ([][]*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - - if page < -1 { - return make([][]*RPCDelegation, 0), nil - } - addresses := s.hmy.GetAllValidatorAddresses() - if page != -1 && len(addresses) <= page*validatorsPageSize { - return make([][]*RPCDelegation, 0), nil - } - validatorsNum := len(addresses) - start := 0 - if page != -1 { - validatorsNum = validatorsPageSize - start = page * validatorsPageSize - if len(addresses)-start < validatorsPageSize { - validatorsNum = len(addresses) - start - } - } - validators := make([][]*RPCDelegation, validatorsNum) - var err error - for i := start; i < start+validatorsNum; i++ { - validators[i-start], err = s.GetDelegationsByValidator(ctx, addresses[i].String()) - if err != nil { - return nil, err - } - } - return validators, nil -} - -// GetDelegationsByDelegator returns list of delegations for a delegator address. -func (s *PublicBlockChainAPI) GetDelegationsByDelegator(ctx context.Context, address string) ([]*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - delegatorAddress := internal_common.ParseAddr(address) - validators, delegations := s.hmy.GetDelegationsByDelegator(delegatorAddress) - result := []*RPCDelegation{} - for i := range delegations { - delegation := delegations[i] - - undelegations := make([]RPCUndelegation, len(delegation.Undelegations)) - - for j := range delegation.Undelegations { - undelegations = append(undelegations, RPCUndelegation{ - delegation.Undelegations[j].Amount, - delegation.Undelegations[j].Epoch, - }) - } - valAddr, _ := internal_common.AddressToBech32(validators[i]) - delAddr, _ := internal_common.AddressToBech32(delegatorAddress) - result = append(result, &RPCDelegation{ - valAddr, - delAddr, - delegation.Amount, - delegation.Reward, - undelegations, - }) - } - return result, nil -} - -// GetDelegationsByDelegatorByBlockNumber returns list of delegations for a delegator address at given block number -func (s *PublicBlockChainAPI) GetDelegationsByDelegatorByBlockNumber( - ctx context.Context, address string, blockNum uint64, -) ([]*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - if err := s.isBlockGreaterThanLatest(blockNum); err != nil { - return nil, err - } - delegatorAddress := internal_common.ParseAddr(address) - blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) - if err != nil { - return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNum) - } - validators, delegations := s.hmy.GetDelegationsByDelegatorByBlock(delegatorAddress, blk) - result := make([]*RPCDelegation, len(delegations)) - for i := range delegations { - delegation := delegations[i] - - undelegations := make([]RPCUndelegation, len(delegation.Undelegations)) - - for j := range delegation.Undelegations { - undelegations[j] = RPCUndelegation{ - delegation.Undelegations[j].Amount, - delegation.Undelegations[j].Epoch, - } - } - valAddr, _ := internal_common.AddressToBech32(validators[i]) - delAddr, _ := internal_common.AddressToBech32(delegatorAddress) - result[i] = &RPCDelegation{ - valAddr, - delAddr, - delegation.Amount, - delegation.Reward, - undelegations, - } - } - return result, nil -} - -// GetDelegationsByValidator returns list of delegations for a validator address. -func (s *PublicBlockChainAPI) GetDelegationsByValidator(ctx context.Context, address string) ([]*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - validatorAddress := internal_common.ParseAddr(address) - delegations := s.hmy.GetDelegationsByValidator(validatorAddress) - result := make([]*RPCDelegation, 0) - for _, delegation := range delegations { - - undelegations := []RPCUndelegation{} - - for j := range delegation.Undelegations { - undelegations = append(undelegations, RPCUndelegation{ - delegation.Undelegations[j].Amount, - delegation.Undelegations[j].Epoch, - }) - } - valAddr, _ := internal_common.AddressToBech32(validatorAddress) - delAddr, _ := internal_common.AddressToBech32(delegation.DelegatorAddress) - result = append(result, &RPCDelegation{ - valAddr, - delAddr, - delegation.Amount, - delegation.Reward, - undelegations, - }) - } - return result, nil -} - -// GetDelegationByDelegatorAndValidator returns a delegation for delegator and validator. -func (s *PublicBlockChainAPI) GetDelegationByDelegatorAndValidator(ctx context.Context, address string, validator string) (*RPCDelegation, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - delegatorAddress := internal_common.ParseAddr(address) - validatorAddress := internal_common.ParseAddr(validator) - validators, delegations := s.hmy.GetDelegationsByDelegator(delegatorAddress) - for i := range delegations { - if validators[i] != validatorAddress { - continue - } - delegation := delegations[i] - - undelegations := []RPCUndelegation{} - - for j := range delegation.Undelegations { - undelegations = append(undelegations, RPCUndelegation{ - delegation.Undelegations[j].Amount, - delegation.Undelegations[j].Epoch, - }) - } - valAddr, _ := internal_common.AddressToBech32(validatorAddress) - delAddr, _ := internal_common.AddressToBech32(delegatorAddress) - return &RPCDelegation{ - valAddr, - delAddr, - delegation.Amount, - delegation.Reward, - undelegations, - }, nil - } - return nil, nil -} - -// EstimateGas returns an estimate of the amount of gas needed to execute the -// given transaction against the current pending block. -func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { - return doEstimateGas(ctx, s.hmy, args, nil) -} - -// GetCurrentUtilityMetrics .. -func (s *PublicBlockChainAPI) GetCurrentUtilityMetrics() (*network.UtilityMetric, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetCurrentUtilityMetrics() -} - -// GetSuperCommittees .. -func (s *PublicBlockChainAPI) GetSuperCommittees() (*quorum.Transition, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetSuperCommittees() -} - -// GetCurrentBadBlocks .. -func (s *PublicBlockChainAPI) GetCurrentBadBlocks() []core.BadBlock { - return s.hmy.GetCurrentBadBlocks() -} - -// GetTotalSupply .. -func (s *PublicBlockChainAPI) GetTotalSupply() (numeric.Dec, error) { - return numeric.NewDec(initSupply), nil -} - -// GetCirculatingSupply .. -func (s *PublicBlockChainAPI) GetCirculatingSupply() (numeric.Dec, error) { - timestamp := time.Now() - return numeric.NewDec(initSupply).Mul(reward.PercentageForTimeStamp(timestamp.Unix())), nil -} - -// GetStakingNetworkInfo .. -func (s *PublicBlockChainAPI) GetStakingNetworkInfo( - ctx context.Context, -) (*StakingNetworkInfo, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - totalStaking, _ := s.GetTotalStaking() - round, _ := s.GetMedianRawStakeSnapshot() - epoch := s.GetEpoch(ctx) - epochLastBlock, _ := s.EpochLastBlock(epoch) - totalSupply, _ := s.GetTotalSupply() - circulatingSupply, _ := s.GetCirculatingSupply() - return &StakingNetworkInfo{ - TotalSupply: totalSupply, - CirculatingSupply: circulatingSupply, - EpochLastBlock: epochLastBlock, - TotalStaking: totalStaking, - MedianRawStake: round.MedianStake, - }, nil -} - -// GetLastCrossLinks .. -func (s *PublicBlockChainAPI) GetLastCrossLinks() ([]*types.CrossLink, error) { - if err := s.isBeaconShard(); err != nil { - return nil, err - } - return s.hmy.GetLastCrossLinks() -} - -// GetLatestChainHeaders .. -func (s *PublicBlockChainAPI) GetLatestChainHeaders() *block.HeaderPair { - return s.hmy.GetLatestChainHeaders() -} - -// docall executes an EVM call -func doCall(ctx context.Context, hmy *hmy.Harmony, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) { - defer func(start time.Time) { - utils.Logger().Debug(). - Dur("runtime", time.Since(start)). - Msg("Executing EVM call finished") - }(time.Now()) - - state, header, err := hmy.StateAndHeaderByNumber(ctx, blockNr) - if state == nil || err != nil { - return nil, 0, false, err - } - // Set sender address or use a default if none specified - var addr common.Address - if args.From == nil { - // Any address does not affect the logic of this call. - addr = common.HexToAddress(defaultFromAddress) - } else { - addr = *args.From - } - // Set default gas & gas price if none were set - gas := uint64(math.MaxUint64 / 2) - if args.Gas != nil { - gas = uint64(*args.Gas) - } - if globalGasCap != nil && globalGasCap.Uint64() < gas { - utils.Logger().Warn(). - Uint64("requested", gas). - Uint64("cap", globalGasCap.Uint64()). - Msg("Caller gas above allowance, capping") - gas = globalGasCap.Uint64() - } - gasPrice := new(big.Int).SetUint64(defaultGasPrice) - if args.GasPrice != nil { - gasPrice = args.GasPrice.ToInt() - } - - value := new(big.Int) - if args.Value != nil { - value = args.Value.ToInt() - } - - var data []byte - if args.Data != nil { - data = []byte(*args.Data) - } - - // Create new call message - msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) - - // Setup context so it may be cancelled the call has completed - // or, in case of unmetered gas, setup a context with a timeout. - var cancel context.CancelFunc - if timeout > 0 { - ctx, cancel = context.WithTimeout(ctx, timeout) - } else { - ctx, cancel = context.WithCancel(ctx) - } - // Make sure the context is cancelled when the call has completed - // this makes sure resources are cleaned up. - defer cancel() - - // Get a new instance of the EVM. - evm, vmError, err := hmy.GetEVM(ctx, msg, state, header) - if err != nil { - return nil, 0, false, err - } - // Wait for the context to be done and cancel the evm. Even if the - // EVM has finished, cancelling may be done (repeatedly) - go func() { - <-ctx.Done() - evm.Cancel() - }() - - // Setup the gas pool (also for unmetered requests) - // and apply the message. - gp := new(core.GasPool).AddGas(math.MaxUint64) - res, gas, failed, err := core.ApplyMessage(evm, msg, gp) - if err := vmError(); err != nil { - return nil, 0, false, err - } - // If the timer caused an abort, return an appropriate error message - if evm.Cancelled() { - return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout) - } - return res, gas, failed, err -} - -// doEstimateGas .. -func doEstimateGas(ctx context.Context, hmy *hmy.Harmony, args CallArgs, gasCap *big.Int) (hexutil.Uint64, error) { - // Binary search the gas requirement, as it may be higher than the amount used - var ( - lo = params.TxGas - 1 - hi uint64 - max uint64 - ) - blockNum := rpc.LatestBlockNumber - if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { - hi = uint64(*args.Gas) - } else { - // Retrieve the blk to act as the gas ceiling - blk, err := hmy.BlockByNumber(ctx, blockNum) - if err != nil { - return 0, err - } - hi = blk.GasLimit() - } - if gasCap != nil && hi > gasCap.Uint64() { - // log.Warn("Caller gas above allowance, capping", "requested", hi, "max", gasCap) - hi = gasCap.Uint64() - } - max = hi - - // Use zero-address if none other is available - if args.From == nil { - args.From = &common.Address{} - } - // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) bool { - args.Gas = (*hexutil.Uint64)(&gas) - - _, _, failed, err := doCall(ctx, hmy, args, blockNum, vm.Config{}, 0, big.NewInt(int64(max))) - if err != nil || failed { - return false - } - return true - } - // Execute the binary search and hone in on an executable gas limit - for lo+1 < hi { - mid := (hi + lo) / 2 - if !executable(mid) { - lo = mid - } else { - hi = mid - } - } - // Reject the transaction as invalid if it still fails at the highest allowance - if hi == max { - if !executable(hi) { - return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", max) - } - } - return hexutil.Uint64(hi), nil -} diff --git a/internal/hmyapi/apiv2/debug.go b/internal/hmyapi/apiv2/debug.go deleted file mode 100644 index 5eb37effc..000000000 --- a/internal/hmyapi/apiv2/debug.go +++ /dev/null @@ -1,32 +0,0 @@ -package apiv2 - -import ( - "context" - - "github.com/ethereum/go-ethereum/log" - "github.com/harmony-one/harmony/hmy" - "github.com/harmony-one/harmony/internal/utils" -) - -// DebugAPI Internal JSON RPC for debugging purpose -type DebugAPI struct { - hmy *hmy.Harmony -} - -// NewDebugAPI Creates a new DebugAPI instance -func NewDebugAPI(hmy *hmy.Harmony) *DebugAPI { - return &DebugAPI{hmy} -} - -// SetLogVerbosity Sets log verbosity on runtime -// Example usage: -// curl -H "Content-Type: application/json" -d '{"method":"debug_setLogVerbosity","params":[0],"id":1}' http://localhost:9123 -func (*DebugAPI) SetLogVerbosity(ctx context.Context, level int) (map[string]interface{}, error) { - if level < int(log.LvlCrit) || level > int(log.LvlTrace) { - return nil, ErrInvalidLogLevel - } - - verbosity := log.Lvl(level) - utils.SetLogVerbosity(verbosity) - return map[string]interface{}{"verbosity": verbosity.String()}, nil -} diff --git a/internal/hmyapi/apiv2/harmony.go b/internal/hmyapi/apiv2/harmony.go deleted file mode 100644 index 276f3121a..000000000 --- a/internal/hmyapi/apiv2/harmony.go +++ /dev/null @@ -1,69 +0,0 @@ -package apiv2 - -import ( - "context" - "math/big" - - "github.com/harmony-one/harmony/hmy" - commonRPC "github.com/harmony-one/harmony/internal/hmyapi/common" - "github.com/harmony-one/harmony/internal/params" -) - -// PublicHarmonyAPI provides an API to access Harmony related information. -// It offers only methods that operate on public data that is freely available to anyone. -type PublicHarmonyAPI struct { - hmy *hmy.Harmony -} - -// NewPublicHarmonyAPI ... -func NewPublicHarmonyAPI(hmy *hmy.Harmony) *PublicHarmonyAPI { - return &PublicHarmonyAPI{hmy} -} - -// ProtocolVersion returns the current Harmony protocol version this node supports -func (s *PublicHarmonyAPI) ProtocolVersion() int { - return s.hmy.ProtocolVersion() -} - -// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not -// yet received the latest block headers from its pears. In case it is synchronizing: -// - startingBlock: block number this node started to synchronise from -// - currentBlock: block number this node is currently importing -// - highestBlock: block number of the highest block header this node has received from peers -// - pulledStates: number of state entries processed until now -// - knownStates: number of known state entries that still need to be pulled -func (s *PublicHarmonyAPI) Syncing() (interface{}, error) { - // TODO(dm): find our Downloader module for syncing blocks - return false, nil -} - -// GasPrice returns a suggestion for a gas price. -func (s *PublicHarmonyAPI) GasPrice(ctx context.Context) (*big.Int, error) { - // TODO(ricl): add SuggestPrice API - return big.NewInt(1), nil -} - -// NodeMetadata captures select metadata of the RPC answering node -type NodeMetadata struct { - BLSPublicKey []string `json:"blskey"` - Version string `json:"version"` - NetworkType string `json:"network"` - ChainConfig params.ChainConfig `json:"chain-config"` - IsLeader bool `json:"is-leader"` - ShardID uint32 `json:"shard-id"` - CurrentEpoch uint64 `json:"current-epoch"` - BlocksPerEpoch *uint64 `json:"blocks-per-epoch,omitempty"` - Role string `json:"role"` - DNSZone string `json:"dns-zone"` - Archival bool `json:"is-archival"` -} - -// GetNodeMetadata produces a NodeMetadata record, data is from the answering RPC node -func (s *PublicHarmonyAPI) GetNodeMetadata() commonRPC.NodeMetadata { - return s.hmy.GetNodeMetadata() -} - -// GetPeerInfo produces a NodePeerInfo record, containing peer info of the node -func (s *PublicHarmonyAPI) GetPeerInfo() commonRPC.NodePeerInfo { - return s.hmy.GetPeerInfo() -} diff --git a/internal/hmyapi/apiv2/net.go b/internal/hmyapi/apiv2/net.go deleted file mode 100644 index 8fc979621..000000000 --- a/internal/hmyapi/apiv2/net.go +++ /dev/null @@ -1,28 +0,0 @@ -package apiv2 - -import ( - "fmt" - - "github.com/harmony-one/harmony/p2p" -) - -// PublicNetAPI offers network related RPC methods -type PublicNetAPI struct { - net p2p.Host - chainID uint64 -} - -// NewPublicNetAPI creates a new net API instance. -func NewPublicNetAPI(net p2p.Host, chainID uint64) *PublicNetAPI { - return &PublicNetAPI{net, chainID} -} - -// PeerCount returns the number of connected peers -func (s *PublicNetAPI) PeerCount() int { - return s.net.GetPeerCount() -} - -// Version returns the network version, i.e. ChainID identifying which network we are using -func (s *PublicNetAPI) Version() string { - return fmt.Sprintf("%d", s.chainID) -} diff --git a/internal/hmyapi/apiv2/transactionpool.go b/internal/hmyapi/apiv2/transactionpool.go deleted file mode 100644 index 51347bd80..000000000 --- a/internal/hmyapi/apiv2/transactionpool.go +++ /dev/null @@ -1,432 +0,0 @@ -package apiv2 - -import ( - "context" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/harmony-one/harmony/core" - "github.com/harmony-one/harmony/core/rawdb" - "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/hmy" - internal_common "github.com/harmony-one/harmony/internal/common" - staking "github.com/harmony-one/harmony/staking/types" - "github.com/pkg/errors" -) - -// TxHistoryArgs is struct to make GetTransactionsHistory request -type TxHistoryArgs struct { - Address string `json:"address"` - PageIndex uint32 `json:"pageIndex"` - PageSize uint32 `json:"pageSize"` - FullTx bool `json:"fullTx"` - TxType string `json:"txType"` - Order string `json:"order"` -} - -// PublicTransactionPoolAPI exposes methods for the RPC interface -type PublicTransactionPoolAPI struct { - hmy *hmy.Harmony - nonceLock *AddrLocker -} - -// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. -func NewPublicTransactionPoolAPI(hmy *hmy.Harmony, nonceLock *AddrLocker) *PublicTransactionPoolAPI { - return &PublicTransactionPoolAPI{hmy, nonceLock} -} - -// GetTransactionsHistory returns the list of transactions hashes that involve a particular address. -func (s *PublicTransactionPoolAPI) GetTransactionsHistory(ctx context.Context, args TxHistoryArgs) (map[string]interface{}, error) { - var address string - var result []common.Hash - var err error - if strings.HasPrefix(args.Address, "one1") { - address = args.Address - } else { - addr := internal_common.ParseAddr(args.Address) - address, err = internal_common.AddressToBech32(addr) - if err != nil { - return nil, err - } - } - hashes, err := s.hmy.GetTransactionsHistory(address, args.TxType, args.Order) - if err != nil { - return nil, err - } - result = ReturnWithPagination(hashes, args.PageIndex, args.PageSize) - if !args.FullTx { - return map[string]interface{}{"transactions": result}, nil - } - txs := []*RPCTransaction{} - for _, hash := range result { - tx := s.GetTransactionByHash(ctx, hash) - if tx != nil { - txs = append(txs, tx) - } - } - return map[string]interface{}{"transactions": txs}, nil -} - -// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. -func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNum uint64) int { - if block, _ := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNum)); block != nil { - return len(block.Transactions()) - } - return 0 -} - -// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash. -func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) int { - if block, _ := s.hmy.GetBlock(ctx, blockHash); block != nil { - return len(block.Transactions()) - } - return 0 -} - -// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. -func (s *PublicTransactionPoolAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNum uint64, index uint64) *RPCTransaction { - if block, _ := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNum)); block != nil { - return newRPCTransactionFromBlockIndex(block, index) - } - return nil -} - -// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. -func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index uint64) *RPCTransaction { - if block, _ := s.hmy.GetBlock(ctx, blockHash); block != nil { - return newRPCTransactionFromBlockIndex(block, index) - } - return nil -} - -// GetTransactionByHash returns the plain transaction for the given hash -func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) *RPCTransaction { - // Try to return an already finalized transaction - tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.hmy.ChainDb(), hash) - block, _ := s.hmy.GetBlock(ctx, blockHash) - if block == nil { - return nil - } - if tx != nil { - return newRPCTransaction(tx, blockHash, blockNumber, block.Time().Uint64(), index) - } - // Transaction unknown, return as such - return nil -} - -// GetStakingTransactionsHistory returns the list of transactions hashes that involve a particular address. -func (s *PublicTransactionPoolAPI) GetStakingTransactionsHistory(ctx context.Context, args TxHistoryArgs) (map[string]interface{}, error) { - var address string - var result []common.Hash - var err error - if strings.HasPrefix(args.Address, "one1") { - address = args.Address - } else { - addr := internal_common.ParseAddr(args.Address) - address, err = internal_common.AddressToBech32(addr) - if err != nil { - return nil, err - } - } - hashes, err := s.hmy.GetStakingTransactionsHistory(address, args.TxType, args.Order) - if err != nil { - return nil, err - } - result = ReturnWithPagination(hashes, args.PageIndex, args.PageSize) - if !args.FullTx { - return map[string]interface{}{"staking_transactions": result}, nil - } - txs := []*RPCStakingTransaction{} - for _, hash := range result { - tx := s.GetStakingTransactionByHash(ctx, hash) - if tx != nil { - txs = append(txs, tx) - } - } - return map[string]interface{}{"staking_transactions": txs}, nil -} - -// GetBlockStakingTransactionCountByNumber returns the number of staking transactions in the block with the given block number. -func (s *PublicTransactionPoolAPI) GetBlockStakingTransactionCountByNumber(ctx context.Context, blockNum uint64) int { - if block, _ := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNum)); block != nil { - return len(block.StakingTransactions()) - } - return 0 -} - -// GetBlockStakingTransactionCountByHash returns the number of staking transactions in the block with the given hash. -func (s *PublicTransactionPoolAPI) GetBlockStakingTransactionCountByHash(ctx context.Context, blockHash common.Hash) int { - if block, _ := s.hmy.GetBlock(ctx, blockHash); block != nil { - return len(block.StakingTransactions()) - } - return 0 -} - -// GetStakingTransactionByBlockNumberAndIndex returns the staking transaction for the given block number and index. -func (s *PublicTransactionPoolAPI) GetStakingTransactionByBlockNumberAndIndex(ctx context.Context, blockNum uint64, index uint64) *RPCStakingTransaction { - if block, _ := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNum)); block != nil { - return newRPCStakingTransactionFromBlockIndex(block, index) - } - return nil -} - -// GetStakingTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. -func (s *PublicTransactionPoolAPI) GetStakingTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index uint64) *RPCStakingTransaction { - if block, _ := s.hmy.GetBlock(ctx, blockHash); block != nil { - return newRPCStakingTransactionFromBlockIndex(block, index) - } - return nil -} - -// GetStakingTransactionByHash returns the staking transaction for the given hash -func (s *PublicTransactionPoolAPI) GetStakingTransactionByHash(ctx context.Context, hash common.Hash) *RPCStakingTransaction { - // Try to return an already finalized transaction - stx, blockHash, blockNumber, index := rawdb.ReadStakingTransaction(s.hmy.ChainDb(), hash) - block, _ := s.hmy.GetBlock(ctx, blockHash) - if block == nil { - return nil - } - if stx != nil { - return newRPCStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index) - } - // Transaction unknown, return as such - return nil -} - -// GetTransactionsCount returns the number of regular transactions from genesis of input type ("SENT", "RECEIVED", "ALL") -func (s *PublicTransactionPoolAPI) GetTransactionsCount(ctx context.Context, address, txType string) (uint64, error) { - var err error - if !strings.HasPrefix(address, "one1") { - addr := internal_common.ParseAddr(address) - address, err = internal_common.AddressToBech32(addr) - if err != nil { - return 0, err - } - } - return s.hmy.GetTransactionsCount(address, txType) -} - -// GetStakingTransactionsCount returns the number of staking transactions from genesis of input type ("SENT", "RECEIVED", "ALL") -func (s *PublicTransactionPoolAPI) GetStakingTransactionsCount(ctx context.Context, address, txType string) (uint64, error) { - var err error - if !strings.HasPrefix(address, "one1") { - addr := internal_common.ParseAddr(address) - address, err = internal_common.AddressToBech32(addr) - if err != nil { - return 0, err - } - } - return s.hmy.GetStakingTransactionsCount(address, txType) -} - -// SendRawStakingTransaction will add the signed transaction to the transaction pool. -// The sender is responsible for signing the transaction and using the correct nonce. -func (s *PublicTransactionPoolAPI) SendRawStakingTransaction( - ctx context.Context, encodedTx hexutil.Bytes, -) (common.Hash, error) { - if len(encodedTx) >= types.MaxEncodedPoolTransactionSize { - err := errors.Wrapf(core.ErrOversizedData, "encoded tx size: %d", len(encodedTx)) - return common.Hash{}, err - } - tx := new(staking.StakingTransaction) - if err := rlp.DecodeBytes(encodedTx, tx); err != nil { - return common.Hash{}, err - } - c := s.hmy.ChainConfig().ChainID - if id := tx.ChainID(); id.Cmp(c) != 0 { - return common.Hash{}, errors.Wrapf( - ErrInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(), - ) - } - return SubmitStakingTransaction(ctx, s.hmy, tx) -} - -// SendRawTransaction will add the signed transaction to the transaction pool. -// The sender is responsible for signing the transaction and using the correct nonce. -func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) { - if len(encodedTx) >= types.MaxEncodedPoolTransactionSize { - err := errors.Wrapf(core.ErrOversizedData, "encoded tx size: %d", len(encodedTx)) - return common.Hash{}, err - } - tx := new(types.Transaction) - if err := rlp.DecodeBytes(encodedTx, tx); err != nil { - return common.Hash{}, err - } - c := s.hmy.ChainConfig().ChainID - if id := tx.ChainID(); id.Cmp(c) != 0 { - return common.Hash{}, errors.Wrapf( - ErrInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(), - ) - } - return SubmitTransaction(ctx, s.hmy, tx) -} - -func (s *PublicTransactionPoolAPI) fillTransactionFields(tx *types.Transaction, fields map[string]interface{}) error { - var err error - fields["shardID"] = tx.ShardID() - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainID()) - } - from, _ := types.Sender(signer, tx) - fields["from"] = from - fields["to"] = "" - if tx.To() != nil { - fields["to"], err = internal_common.AddressToBech32(*tx.To()) - if err != nil { - return err - } - fields["from"], err = internal_common.AddressToBech32(from) - if err != nil { - return err - } - } - return nil -} - -func (s *PublicTransactionPoolAPI) fillStakingTransactionFields(stx *staking.StakingTransaction, fields map[string]interface{}) error { - from, err := stx.SenderAddress() - if err != nil { - return err - } - fields["sender"], err = internal_common.AddressToBech32(from) - if err != nil { - return err - } - fields["type"] = stx.StakingType() - return nil -} - -// GetTransactionReceipt returns the transaction receipt for the given transaction hash. -func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { - var tx *types.Transaction - var stx *staking.StakingTransaction - var blockHash common.Hash - var blockNumber, index uint64 - tx, blockHash, blockNumber, index = rawdb.ReadTransaction(s.hmy.ChainDb(), hash) - if tx == nil { - stx, blockHash, blockNumber, index = rawdb.ReadStakingTransaction(s.hmy.ChainDb(), hash) - if stx == nil { - return nil, nil - } - } - receipts, err := s.hmy.GetReceipts(ctx, blockHash) - if err != nil { - return nil, err - } - if len(receipts) <= int(index) { - return nil, nil - } - receipt := receipts[index] - fields := map[string]interface{}{ - "blockHash": blockHash, - "blockNumber": blockNumber, - "transactionHash": hash, - "transactionIndex": index, - "gasUsed": receipt.GasUsed, - "cumulativeGasUsed": receipt.CumulativeGasUsed, - "contractAddress": nil, - "logs": receipt.Logs, - "logsBloom": receipt.Bloom, - } - if tx != nil { - if err = s.fillTransactionFields(tx, fields); err != nil { - return nil, err - } - } else { // stx not nil - if err = s.fillStakingTransactionFields(stx, fields); err != nil { - return nil, err - } - } - // Assign receipt status or post state. - if len(receipt.PostState) > 0 { - fields["root"] = hexutil.Bytes(receipt.PostState) - } else { - fields["status"] = receipt.Status - } - if receipt.Logs == nil { - fields["logs"] = [][]*types.Log{} - } - // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation - if receipt.ContractAddress != (common.Address{}) { - fields["contractAddress"] = receipt.ContractAddress - } - return fields, nil -} - -// GetPoolStats returns stats for the tx-pool -func (s *PublicTransactionPoolAPI) GetPoolStats() map[string]interface{} { - pendingCount, queuedCount := s.hmy.GetPoolStats() - return map[string]interface{}{ - "executable-count": pendingCount, - "non-executable-count": queuedCount, - } -} - -// PendingTransactions returns the plain transactions that are in the transaction pool -func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) { - pending, err := s.hmy.GetPoolTransactions() - if err != nil { - return nil, err - } - transactions := make([]*RPCTransaction, 0) - for i := range pending { - if plainTx, ok := pending[i].(*types.Transaction); ok { - if tx := newRPCTransaction(plainTx, common.Hash{}, 0, 0, 0); tx != nil { - transactions = append(transactions, tx) - } - } else if _, ok := pending[i].(*staking.StakingTransaction); ok { - continue // Do not return staking transactions here. - } else { - return nil, types.ErrUnknownPoolTxType - } - } - return transactions, nil -} - -// PendingStakingTransactions returns the staking transactions that are in the transaction pool -func (s *PublicTransactionPoolAPI) PendingStakingTransactions() ([]*RPCStakingTransaction, error) { - pending, err := s.hmy.GetPoolTransactions() - if err != nil { - return nil, err - } - transactions := make([]*RPCStakingTransaction, 0) - for i := range pending { - if _, ok := pending[i].(*types.Transaction); ok { - continue // Do not return plain transactions here - } else if stakingTx, ok := pending[i].(*staking.StakingTransaction); ok { - if tx := newRPCStakingTransaction(stakingTx, common.Hash{}, 0, 0, 0); tx != nil { - transactions = append(transactions, tx) - } - } else { - return nil, types.ErrUnknownPoolTxType - } - } - return transactions, nil -} - -// GetCurrentTransactionErrorSink .. -func (s *PublicTransactionPoolAPI) GetCurrentTransactionErrorSink() types.TransactionErrorReports { - return s.hmy.GetCurrentTransactionErrorSink() -} - -// GetCurrentStakingErrorSink .. -func (s *PublicTransactionPoolAPI) GetCurrentStakingErrorSink() types.TransactionErrorReports { - return s.hmy.GetCurrentStakingErrorSink() -} - -// GetCXReceiptByHash returns the transaction for the given hash -func (s *PublicTransactionPoolAPI) GetCXReceiptByHash(ctx context.Context, hash common.Hash) *RPCCXReceipt { - if cx, blockHash, blockNumber, _ := rawdb.ReadCXReceipt(s.hmy.ChainDb(), hash); cx != nil { - return newRPCCXReceipt(cx, blockHash, blockNumber) - } - return nil -} - -// GetPendingCXReceipts .. -func (s *PublicTransactionPoolAPI) GetPendingCXReceipts(ctx context.Context) []*types.CXReceiptsProof { - return s.hmy.GetPendingCXReceipts() -} diff --git a/internal/hmyapi/apiv2/types.go b/internal/hmyapi/apiv2/types.go deleted file mode 100644 index a6f67e10f..000000000 --- a/internal/hmyapi/apiv2/types.go +++ /dev/null @@ -1,539 +0,0 @@ -package apiv2 - -import ( - "encoding/hex" - "math/big" - "strings" - "time" - - "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/rlp" - "github.com/harmony-one/harmony/block" - "github.com/harmony-one/harmony/core/types" - internal_common "github.com/harmony-one/harmony/internal/common" - "github.com/harmony-one/harmony/numeric" - "github.com/harmony-one/harmony/shard" - staking "github.com/harmony-one/harmony/staking/types" -) - -// RPCBlock represents a block that will serialize to the RPC representation of a block -type RPCBlock struct { - Number *big.Int `json:"number"` - ViewID *big.Int `json:"viewID"` - Epoch *big.Int `json:"epoch"` - Hash common.Hash `json:"hash"` - ParentHash common.Hash `json:"parentHash"` - Nonce types.BlockNonce `json:"nonce"` - MixHash common.Hash `json:"mixHash"` - LogsBloom ethtypes.Bloom `json:"logsBloom"` - StateRoot common.Hash `json:"stateRoot"` - Miner string `json:"miner"` - Difficulty *big.Int `json:"difficulty"` - ExtraData []byte `json:"extraData"` - Size uint64 `json:"size"` - GasLimit uint64 `json:"gasLimit"` - GasUsed uint64 `json:"gasUsed"` - Timestamp *big.Int `json:"timestamp"` - TransactionsRoot common.Hash `json:"transactionsRoot"` - ReceiptsRoot common.Hash `json:"receiptsRoot"` - Transactions []interface{} `json:"transactions"` - StakingTxs []interface{} `json:"stakingTxs"` - Uncles []common.Hash `json:"uncles"` - TotalDifficulty *big.Int `json:"totalDifficulty"` - Signers []string `json:"signers"` -} - -// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction -type RPCTransaction struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber *big.Int `json:"blockNumber"` - From string `json:"from"` - Timestamp uint64 `json:"timestamp"` - Gas uint64 `json:"gas"` - GasPrice *big.Int `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce uint64 `json:"nonce"` - To string `json:"to"` - TransactionIndex uint64 `json:"transactionIndex"` - Value *big.Int `json:"value"` - ShardID uint32 `json:"shardID"` - ToShardID uint32 `json:"toShardID"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` -} - -// RPCStakingTransaction represents a transaction that will serialize to the RPC representation of a staking transaction -type RPCStakingTransaction struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber *big.Int `json:"blockNumber"` - From string `json:"from"` - Timestamp uint64 `json:"timestamp"` - Gas uint64 `json:"gas"` - GasPrice *big.Int `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Nonce uint64 `json:"nonce"` - TransactionIndex uint64 `json:"transactionIndex"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` - Type string `json:"type"` - Msg map[string]interface{} `json:"msg"` -} - -// RPCCXReceipt represents a CXReceipt that will serialize to the RPC representation of a CXReceipt -type RPCCXReceipt struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber *big.Int `json:"blockNumber"` - TxHash common.Hash `json:"hash"` - From string `json:"from"` - To string `json:"to"` - ShardID uint32 `json:"shardID"` - ToShardID uint32 `json:"toShardID"` - Amount *big.Int `json:"value"` -} - -// HeaderInformation represents the latest consensus information -type HeaderInformation struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber uint64 `json:"blockNumber"` - ShardID uint32 `json:"shardID"` - Leader string `json:"leader"` - ViewID uint64 `json:"viewID"` - Epoch uint64 `json:"epoch"` - Timestamp string `json:"timestamp"` - UnixTime uint64 `json:"unixtime"` - LastCommitSig string `json:"lastCommitSig"` - LastCommitBitmap string `json:"lastCommitBitmap"` - CrossLinks *types.CrossLinks `json:"crossLinks,omitempty"` -} - -// RPCDelegation represents a particular delegation to a validator -type RPCDelegation struct { - ValidatorAddress string `json:"validator_address"` - DelegatorAddress string `json:"delegator_address"` - Amount *big.Int `json:"amount"` - Reward *big.Int `json:"reward"` - Undelegations []RPCUndelegation `json:"Undelegations"` -} - -// RPCUndelegation represents one undelegation entry -type RPCUndelegation struct { - Amount *big.Int - Epoch *big.Int -} - -// CallArgs represents the arguments for a call. -type CallArgs struct { - From *common.Address `json:"from"` - To *common.Address `json:"to"` - Gas *hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Value *hexutil.Big `json:"value"` - Data *hexutil.Bytes `json:"data"` -} - -// StakingNetworkInfo returns global staking info. -type StakingNetworkInfo struct { - TotalSupply numeric.Dec `json:"total-supply"` - CirculatingSupply numeric.Dec `json:"circulating-supply"` - EpochLastBlock uint64 `json:"epoch-last-block"` - TotalStaking *big.Int `json:"total-staking"` - MedianRawStake numeric.Dec `json:"median-raw-stake"` -} - -func newHeaderInformation(header *block.Header, leader string) *HeaderInformation { - if header == nil { - return nil - } - - result := &HeaderInformation{ - BlockHash: header.Hash(), - BlockNumber: header.Number().Uint64(), - ShardID: header.ShardID(), - Leader: leader, - ViewID: header.ViewID().Uint64(), - Epoch: header.Epoch().Uint64(), - UnixTime: header.Time().Uint64(), - Timestamp: time.Unix(header.Time().Int64(), 0).UTC().String(), - LastCommitBitmap: hex.EncodeToString(header.LastCommitBitmap()), - } - - sig := header.LastCommitSignature() - result.LastCommitSig = hex.EncodeToString(sig[:]) - - if header.ShardID() == shard.BeaconChainShardID { - decodedCrossLinks := &types.CrossLinks{} - err := rlp.DecodeBytes(header.CrossLinks(), decodedCrossLinks) - if err != nil { - result.CrossLinks = &types.CrossLinks{} - } else { - result.CrossLinks = decodedCrossLinks - } - } - - return result -} - -// newRPCCXReceipt returns a CXReceipt that will serialize to the RPC representation -func newRPCCXReceipt(cx *types.CXReceipt, blockHash common.Hash, blockNumber uint64) *RPCCXReceipt { - result := &RPCCXReceipt{ - BlockHash: blockHash, - TxHash: cx.TxHash, - Amount: cx.Amount, - ShardID: cx.ShardID, - ToShardID: cx.ToShardID, - } - if blockHash != (common.Hash{}) { - result.BlockHash = blockHash - result.BlockNumber = (*big.Int)(new(big.Int).SetUint64(blockNumber)) - } - - fromAddr, err := internal_common.AddressToBech32(cx.From) - if err != nil { - return nil - } - toAddr := "" - if cx.To != nil { - if toAddr, err = internal_common.AddressToBech32(*cx.To); err != nil { - return nil - } - } - result.From = fromAddr - result.To = toAddr - - return result -} - -// newRPCTransaction returns a transaction that will serialize to the RPC -// representation, with the given location metadata set (if available). -// Note that all txs on Harmony are replay protected (post EIP155 epoch). -func newRPCTransaction( - tx *types.Transaction, blockHash common.Hash, - blockNumber uint64, timestamp uint64, index uint64, -) *RPCTransaction { - from, err := tx.SenderAddress() - if err != nil { - return nil - } - v, r, s := tx.RawSignatureValues() - - result := &RPCTransaction{ - Gas: tx.Gas(), - GasPrice: tx.GasPrice(), - Hash: tx.Hash(), - Input: hexutil.Bytes(tx.Data()), - Nonce: tx.Nonce(), - Value: tx.Value(), - ShardID: tx.ShardID(), - ToShardID: tx.ToShardID(), - Timestamp: timestamp, - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), - } - if blockHash != (common.Hash{}) { - result.BlockHash = blockHash - result.BlockNumber = (*big.Int)(new(big.Int).SetUint64(blockNumber)) - result.TransactionIndex = index - } - - fromAddr, err := internal_common.AddressToBech32(from) - if err != nil { - return nil - } - toAddr := "" - - if tx.To() != nil { - if toAddr, err = internal_common.AddressToBech32(*tx.To()); err != nil { - return nil - } - result.From = fromAddr - } else { - result.From = strings.ToLower(from.Hex()) - } - result.To = toAddr - - return result -} - -// newRPCStakingTransaction returns a staking transaction that will serialize to the RPC -// representation, with the given location metadata set (if available). -func newRPCStakingTransaction( - tx *staking.StakingTransaction, blockHash common.Hash, - blockNumber uint64, timestamp uint64, index uint64, -) *RPCStakingTransaction { - from, err := tx.SenderAddress() - if err != nil { - return nil - } - v, r, s := tx.RawSignatureValues() - - fields := make(map[string]interface{}) - - switch tx.StakingType() { - case staking.DirectiveCreateValidator: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCreateValidator) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.CreateValidator) - if !ok { - return nil - } - validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) - if err != nil { - return nil - } - fields = map[string]interface{}{ - "validatorAddress": validatorAddress, - "name": msg.Description.Name, - "commissionRate": msg.CommissionRates.Rate.Int, - "maxCommissionRate": msg.CommissionRates.MaxRate.Int, - "maxChangeRate": msg.CommissionRates.MaxChangeRate.Int, - "minSelfDelegation": msg.MinSelfDelegation, - "maxTotalDelegation": msg.MaxTotalDelegation, - "amount": msg.Amount, - "website": msg.Description.Website, - "identity": msg.Description.Identity, - "securityContact": msg.Description.SecurityContact, - "details": msg.Description.Details, - "slotPubKeys": msg.SlotPubKeys, - } - case staking.DirectiveEditValidator: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveEditValidator) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.EditValidator) - if !ok { - return nil - } - validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) - if err != nil { - return nil - } - // Edit validators txs need not have commission rates to edit - commissionRate := &big.Int{} - if msg.CommissionRate != nil { - commissionRate = msg.CommissionRate.Int - } - fields = map[string]interface{}{ - "validatorAddress": validatorAddress, - "commisionRate": commissionRate, - "name": msg.Description.Name, - "minSelfDelegation": msg.MinSelfDelegation, - "maxTotalDelegation": msg.MaxTotalDelegation, - "website": msg.Description.Website, - "identity": msg.Description.Identity, - "securityContact": msg.Description.SecurityContact, - "details": msg.Description.Details, - "slotPubKeyToAdd": msg.SlotKeyToAdd, - "slotPubKeyToRemove": msg.SlotKeyToRemove, - } - case staking.DirectiveCollectRewards: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCollectRewards) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.CollectRewards) - if !ok { - return nil - } - delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) - if err != nil { - return nil - } - fields = map[string]interface{}{ - "delegatorAddress": delegatorAddress, - } - case staking.DirectiveDelegate: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveDelegate) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.Delegate) - if !ok { - return nil - } - delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) - if err != nil { - return nil - } - validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) - if err != nil { - return nil - } - fields = map[string]interface{}{ - "delegatorAddress": delegatorAddress, - "validatorAddress": validatorAddress, - "amount": msg.Amount, - } - case staking.DirectiveUndelegate: - rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveUndelegate) - if err != nil { - return nil - } - msg, ok := rawMsg.(*staking.Undelegate) - if !ok { - return nil - } - delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) - if err != nil { - return nil - } - validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) - if err != nil { - return nil - } - fields = map[string]interface{}{ - "delegatorAddress": delegatorAddress, - "validatorAddress": validatorAddress, - "amount": msg.Amount, - } - } - - result := &RPCStakingTransaction{ - Gas: tx.Gas(), - GasPrice: tx.GasPrice(), - Hash: tx.Hash(), - Nonce: tx.Nonce(), - Timestamp: timestamp, - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), - Type: tx.StakingType().String(), - Msg: fields, - } - if blockHash != (common.Hash{}) { - result.BlockHash = blockHash - result.BlockNumber = new(big.Int).SetUint64(blockNumber) - result.TransactionIndex = index - } - - fromAddr, err := internal_common.AddressToBech32(from) - if err != nil { - return nil - } - result.From = fromAddr - - return result -} - -// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are -// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain -// transaction hashes. -func RPCMarshalBlock(b *types.Block, blockArgs BlockArgs, leader string) (map[string]interface{}, error) { - head := b.Header() // copies the header once - fields := map[string]interface{}{ - "number": (*big.Int)(head.Number()), - "viewID": (*big.Int)(head.ViewID()), - "epoch": (*big.Int)(head.Epoch()), - "hash": b.Hash(), - "parentHash": head.ParentHash(), - "nonce": 0, // Remove this because we don't have it in our header - "mixHash": head.MixDigest(), - "logsBloom": head.Bloom(), - "stateRoot": head.Root(), - "miner": leader, - "difficulty": 0, // Remove this because we don't have it in our header - "extraData": hexutil.Bytes(head.Extra()), - "size": uint64(b.Size()), - "gasLimit": head.GasLimit(), - "gasUsed": head.GasUsed(), - "timestamp": head.Time().Uint64(), - "transactionsRoot": head.TxHash(), - "receiptsRoot": head.ReceiptHash(), - } - - if blockArgs.InclTx { - formatTx := func(tx *types.Transaction) (interface{}, error) { - return tx.Hash(), nil - } - if blockArgs.FullTx { - formatTx = func(tx *types.Transaction) (interface{}, error) { - return newRPCTransactionFromBlockHash(b, tx.Hash()), nil - } - } - txs := b.Transactions() - transactions := make([]interface{}, len(txs)) - var err error - for i, tx := range txs { - if transactions[i], err = formatTx(tx); err != nil { - return nil, err - } - } - fields["transactions"] = transactions - - if blockArgs.InclStaking { - formatStakingTx := func(tx *staking.StakingTransaction) (interface{}, error) { - return tx.Hash(), nil - } - if blockArgs.FullTx { - formatStakingTx = func(tx *staking.StakingTransaction) (interface{}, error) { - return newRPCStakingTransactionFromBlockHash(b, tx.Hash()), nil - } - } - stakingTxs := b.StakingTransactions() - stakingTransactions := make([]interface{}, len(stakingTxs)) - for i, tx := range stakingTxs { - if stakingTransactions[i], err = formatStakingTx(tx); err != nil { - return nil, err - } - } - fields["stakingTransactions"] = stakingTransactions - } - } - - uncles := b.Uncles() - uncleHashes := make([]common.Hash, len(uncles)) - for i, uncle := range uncles { - uncleHashes[i] = uncle.Hash() - } - fields["uncles"] = uncleHashes - if blockArgs.WithSigners { - fields["signers"] = blockArgs.Signers - } - return fields, nil -} - -// newRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction { - for idx, tx := range b.Transactions() { - if tx.Hash() == hash { - return newRPCTransactionFromBlockIndex(b, uint64(idx)) - } - } - return nil -} - -// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction { - txs := b.Transactions() - if index >= uint64(len(txs)) { - return nil - } - return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) -} - -// newRPCStakingTransactionFromBlockHash returns a staking transaction that will serialize to the RPC representation. -func newRPCStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCStakingTransaction { - for idx, tx := range b.StakingTransactions() { - if tx.Hash() == hash { - return newRPCStakingTransactionFromBlockIndex(b, uint64(idx)) - } - } - return nil -} - -// newRPCStakingTransactionFromBlockIndex returns a staking transaction that will serialize to the RPC representation. -func newRPCStakingTransactionFromBlockIndex(b *types.Block, index uint64) *RPCStakingTransaction { - txs := b.StakingTransactions() - if index >= uint64(len(txs)) { - return nil - } - return newRPCStakingTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) -} diff --git a/internal/hmyapi/apiv2/util.go b/internal/hmyapi/apiv2/util.go deleted file mode 100644 index 60418dec7..000000000 --- a/internal/hmyapi/apiv2/util.go +++ /dev/null @@ -1,74 +0,0 @@ -package apiv2 - -import ( - "context" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/hmy" - common2 "github.com/harmony-one/harmony/internal/common" - "github.com/harmony-one/harmony/internal/utils" - staking "github.com/harmony-one/harmony/staking/types" -) - -// defaultPageSize is to have default pagination. -const ( - defaultPageSize = uint32(1000) -) - -// ReturnWithPagination returns result with pagination (offset, page in TxHistoryArgs). -func ReturnWithPagination(hashes []common.Hash, pageIndex uint32, pageSize uint32) []common.Hash { - size := defaultPageSize - if pageSize > 0 { - size = pageSize - } - if uint64(size)*uint64(pageIndex) >= uint64(len(hashes)) { - return make([]common.Hash, 0) - } - if uint64(size)*uint64(pageIndex)+uint64(size) > uint64(len(hashes)) { - return hashes[size*pageIndex:] - } - return hashes[size*pageIndex : size*pageIndex+size] -} - -// SubmitTransaction is a helper function that submits tx to txPool and logs a message. -func SubmitTransaction( - ctx context.Context, hmy *hmy.Harmony, tx *types.Transaction, -) (common.Hash, error) { - if err := hmy.SendTx(ctx, tx); err != nil { - utils.Logger().Warn().Err(err).Msg("Could not submit transaction") - return tx.Hash(), err - } - if tx.To() == nil { - signer := types.MakeSigner(hmy.ChainConfig(), hmy.CurrentBlock().Epoch()) - from, err := types.Sender(signer, tx) - if err != nil { - return common.Hash{}, err - } - addr := crypto.CreateAddress(from, tx.Nonce()) - utils.Logger().Info(). - Str("fullhash", tx.Hash().Hex()). - Str("contract", common2.MustAddressToBech32(addr)). - Msg("Submitted contract creation") - } else { - utils.Logger().Info(). - Str("fullhash", tx.Hash().Hex()). - Str("recipient", tx.To().Hex()). - Msg("Submitted transaction") - } - return tx.Hash(), nil -} - -// SubmitStakingTransaction is a helper function that submits tx to txPool and logs a message. -func SubmitStakingTransaction( - ctx context.Context, hmy *hmy.Harmony, tx *staking.StakingTransaction, -) (common.Hash, error) { - if err := hmy.SendStakingTx(ctx, tx); err != nil { - // legacy behavior is to never return error and always return tx hash - utils.Logger().Warn().Err(err).Msg("Could not submit staking transaction") - return tx.Hash(), nil - } - utils.Logger().Info().Str("fullhash", tx.Hash().Hex()).Msg("Submitted Staking transaction") - return tx.Hash(), nil -} diff --git a/internal/hmyapi/backend.go b/internal/hmyapi/backend.go deleted file mode 100644 index ffb31ea52..000000000 --- a/internal/hmyapi/backend.go +++ /dev/null @@ -1,64 +0,0 @@ -package hmyapi - -import ( - "github.com/ethereum/go-ethereum/rpc" - "github.com/harmony-one/harmony/hmy" - "github.com/harmony-one/harmony/internal/hmyapi/apiv1" - "github.com/harmony-one/harmony/internal/hmyapi/apiv2" -) - -// GetAPIs returns all the APIs. -func GetAPIs(hmy *hmy.Harmony) []rpc.API { - nonceLock := new(apiv1.AddrLocker) - nonceLockV2 := new(apiv2.AddrLocker) - return []rpc.API{ - { - Namespace: "hmy", - Version: "1.0", - Service: apiv1.NewPublicHarmonyAPI(hmy), - Public: true, - }, - { - Namespace: "hmy", - Version: "1.0", - Service: apiv1.NewPublicBlockChainAPI(hmy), - Public: true, - }, - { - Namespace: "hmy", - Version: "1.0", - Service: apiv1.NewPublicTransactionPoolAPI(hmy, nonceLock), - Public: true, - }, - { - Namespace: "hmy", - Version: "1.0", - Service: apiv1.NewDebugAPI(hmy), - Public: false, - }, - { - Namespace: "hmyv2", - Version: "1.0", - Service: apiv2.NewPublicHarmonyAPI(hmy), - Public: true, - }, - { - Namespace: "hmyv2", - Version: "1.0", - Service: apiv2.NewPublicBlockChainAPI(hmy), - Public: true, - }, - { - Namespace: "hmyv2", - Version: "1.0", - Service: apiv2.NewPublicTransactionPoolAPI(hmy, nonceLockV2), - Public: true, - }, - { - Namespace: "hmyv2", - Version: "1.0", - Service: apiv2.NewDebugAPI(hmy), - Public: false, - }, - } -} diff --git a/node/rpc.go b/node/rpc.go index 49697dbed..ff1aa78ad 100644 --- a/node/rpc.go +++ b/node/rpc.go @@ -1,43 +1,16 @@ package node import ( - "fmt" - "net" "strconv" - "strings" "github.com/ethereum/go-ethereum/rpc" "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/hmy" - nodeconfig "github.com/harmony-one/harmony/internal/configs/node" - "github.com/harmony-one/harmony/internal/hmyapi" - "github.com/harmony-one/harmony/internal/hmyapi/apiv1" - "github.com/harmony-one/harmony/internal/hmyapi/apiv2" - "github.com/harmony-one/harmony/internal/hmyapi/filters" - "github.com/harmony-one/harmony/internal/utils" + hmy_rpc "github.com/harmony-one/harmony/rpc" + "github.com/harmony-one/harmony/rpc/filters" "github.com/libp2p/go-libp2p-core/peer" ) -const ( - rpcHTTPPortOffset = 500 - rpcWSPortOffset = 800 -) - -var ( - // HTTP RPC - httpListener net.Listener - httpHandler *rpc.Server - httpEndpoint = "" - wsEndpoint = "" - httpModules = []string{"hmy", "hmyv2", "net", "netv2", "explorer"} - httpVirtualHosts = []string{"*"} - httpTimeouts = rpc.DefaultHTTPTimeouts - httpOrigins = []string{"*"} - wsModules = []string{"hmy", "hmyv2", "net", "netv2", "web3"} - wsOrigins = []string{"*"} - harmony *hmy.Harmony -) - // IsCurrentlyLeader exposes if node is currently the leader node func (node *Node) IsCurrentlyLeader() bool { return node.Consensus.IsLeader() @@ -91,8 +64,10 @@ func (node *Node) ReportPlainErrorSink() types.TransactionErrorReports { // StartRPC start RPC service func (node *Node) StartRPC(nodePort string) error { + harmony := hmy.New(node, node.TxPool, node.CxPool, node.Consensus.ShardID) + // Gather all the possible APIs to surface - apis := node.APIs() + apis := node.APIs(harmony) for _, service := range node.serviceManager.GetServices() { apis = append(apis, service.APIs()...) @@ -100,105 +75,26 @@ func (node *Node) StartRPC(nodePort string) error { port, _ := strconv.Atoi(nodePort) - ip := "" - if !nodeconfig.GetPublicRPC() { - ip = "127.0.0.1" - } - httpEndpoint = fmt.Sprintf("%v:%v", ip, port+rpcHTTPPortOffset) - - if err := node.startHTTP(httpEndpoint, apis, httpModules, httpOrigins, httpVirtualHosts, httpTimeouts); err != nil { - return err - } - wsEndpoint = fmt.Sprintf("%v:%v", ip, port+rpcWSPortOffset) - if err := node.startWS(wsEndpoint, apis, wsModules, wsOrigins, true); err != nil { - node.stopHTTP() - return err - } - - return nil + return hmy_rpc.StartServers(harmony, port, apis) } -// startHTTP initializes and starts the HTTP RPC endpoint. -func (node *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts) error { - // Short circuit if the HTTP endpoint isn't being exposed - if endpoint == "" { - return nil - } - - listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts, timeouts) - if err != nil { - return err - } - - utils.Logger().Info(). - Str("url", fmt.Sprintf("http://%s", endpoint)). - Str("cors", strings.Join(cors, ",")). - Str("vhosts", strings.Join(vhosts, ",")). - Msg("HTTP endpoint opened") - // All listeners booted successfully - httpListener = listener - httpHandler = handler - return nil +// StopRPC stop RPC service +func (node *Node) StopRPC() error { + return hmy_rpc.StopServers() } -// stopHTTP terminates the HTTP RPC endpoint. -func (node *Node) stopHTTP() { - if httpListener != nil { - httpListener.Close() - httpListener = nil - utils.Logger().Info().Str("url", fmt.Sprintf("http://%s", httpEndpoint)).Msg("HTTP endpoint closed") - } - if httpHandler != nil { - httpHandler.Stop() - httpHandler = nil - } -} - -// startWS initializes and starts the websocket RPC endpoint. -func (node *Node) startWS( - endpoint string, apis []rpc.API, modules []string, wsOrigins []string, exposeAll bool, -) error { - // Short circuit if the WS endpoint isn't being exposed - if endpoint == "" { - return nil - } - listener, _, err := rpc.StartWSEndpoint(endpoint, apis, modules, wsOrigins, exposeAll) - if err != nil { - return err - } - utils.Logger().Info(). - Str("url", fmt.Sprintf("ws://%s", listener.Addr())). - Msg("WebSocket endpoint opened") - return nil -} - -// APIs return the collection of RPC services the ethereum package offers. +// APIs return the collection of local RPC services. // NOTE, some of these services probably need to be moved to somewhere else. -func (node *Node) APIs() []rpc.API { - if harmony == nil { - harmony = hmy.New(node, node.TxPool, node.CxPool, node.Consensus.ShardID) - } - // Gather all the possible APIs to surface - apis := hmyapi.GetAPIs(harmony) +func (node *Node) APIs(harmony *hmy.Harmony) []rpc.API { // Append all the local APIs and return - return append(apis, []rpc.API{ + return []rpc.API{ + hmy_rpc.NewPublicNetAPI(node.host, harmony.ChainID, hmy_rpc.V1), + hmy_rpc.NewPublicNetAPI(node.host, harmony.ChainID, hmy_rpc.V2), { Namespace: "hmy", - Version: "1.0", + Version: hmy_rpc.APIVersion, Service: filters.NewPublicFilterAPI(harmony, false), Public: true, }, - { - Namespace: "net", - Version: "1.0", - Service: apiv1.NewPublicNetAPI(node.host, harmony.ChainID), - Public: true, - }, - { - Namespace: "netv2", - Version: "1.0", - Service: apiv2.NewPublicNetAPI(node.host, harmony.ChainID), - Public: true, - }, - }...) + } } diff --git a/rpc/blockchain.go b/rpc/blockchain.go new file mode 100644 index 000000000..660463044 --- /dev/null +++ b/rpc/blockchain.go @@ -0,0 +1,668 @@ +package rpc + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/consensus/reward" + "github.com/harmony-one/harmony/hmy" + internal_common "github.com/harmony-one/harmony/internal/common" + "github.com/harmony-one/harmony/internal/utils" + "github.com/harmony-one/harmony/numeric" + rpc_common "github.com/harmony-one/harmony/rpc/common" + v1 "github.com/harmony-one/harmony/rpc/v1" + v2 "github.com/harmony-one/harmony/rpc/v2" + "github.com/harmony-one/harmony/shard" +) + +const ( + initSupply = int64(12600000000) +) + +var ( + blocksPeriod = shard.Schedule.BlocksPerEpoch() +) + +// PublicBlockchainService provides an API to access the Harmony blockchain. +// It offers only methods that operate on public data that is freely available to anyone. +type PublicBlockchainService struct { + hmy *hmy.Harmony + version Version +} + +// NewPublicBlockchainAPI creates a new API for the RPC interface +func NewPublicBlockchainAPI(hmy *hmy.Harmony, version Version) rpc.API { + return rpc.API{ + Namespace: version.Namespace(), + Version: APIVersion, + Service: &PublicBlockchainService{hmy, version}, + Public: true, + } +} + +// getBlockOptions is a helper to get block args given an interface option from RPC params. +func (s *PublicBlockchainService) getBlockOptions(opts interface{}) (*rpc_common.BlockArgs, error) { + switch s.version { + case V1: + fullTx, ok := opts.(bool) + if !ok { + return nil, fmt.Errorf("invalid type for block arguments") + } + return &rpc_common.BlockArgs{ + WithSigners: false, + FullTx: fullTx, + InclStaking: true, + }, nil + case V2: + parsedBlockArgs := rpc_common.BlockArgs{} + if err := parsedBlockArgs.UnmarshalFromInterface(opts); err != nil { + return nil, err + } + return &parsedBlockArgs, nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// getBalanceByBlockNumber returns balance by block number at given eth blockNum without checks +func (s *PublicBlockchainService) getBalanceByBlockNumber( + ctx context.Context, address string, blockNum rpc.BlockNumber, +) (*big.Int, error) { + addr := internal_common.ParseAddr(address) + balance, err := s.hmy.GetBalance(ctx, addr, blockNum) + if err != nil { + return nil, err + } + return balance, nil +} + +// BlockNumber returns the block number of the chain head. +func (s *PublicBlockchainService) BlockNumber(ctx context.Context) (interface{}, error) { + // Fetch latest header + header, err := s.hmy.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return nil, err + } + + // Format return base on version + switch s.version { + case V1: + return hexutil.Uint64(header.Number().Uint64()), nil + case V2: + return header.Number().Uint64(), nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetBlockByNumber returns the requested block. When blockNum is -1 the chain head is returned. When fullTx is true all +// transactions in the block are returned in full detail, otherwise only the transaction hash is returned. +// When withSigners in BlocksArgs is true it shows block signers for this block in list of one addresses. +func (s *PublicBlockchainService) GetBlockByNumber( + ctx context.Context, blockNumber BlockNumber, opts interface{}, +) (response StructuredResponse, err error) { + // Process arguments based on version + blockNum := blockNumber.EthBlockNumber() + var blockArgs *rpc_common.BlockArgs + blockArgs, ok := opts.(*rpc_common.BlockArgs) + if !ok { + blockArgs, err = s.getBlockOptions(opts) + if err != nil { + return nil, err + } + } + blockArgs.InclTx = true + + // Fetch the block + if isBlockGreaterThanLatest(s.hmy, blockNum) { + return nil, ErrRequestedBlockTooHigh + } + blk, err := s.hmy.BlockByNumber(ctx, blockNum) + if err != nil { + return nil, err + } + if blockArgs.WithSigners { + blockArgs.Signers, err = s.GetBlockSigners(ctx, blockNumber) + if err != nil { + return nil, err + } + } + + // Format the response according to version + leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) + var rpcBlock interface{} + switch s.version { + case V1: + rpcBlock, err = v1.NewBlock(blk, blockArgs, leader) + case V2: + rpcBlock, err = v2.NewBlock(blk, blockArgs, leader) + default: + return nil, ErrUnknownRPCVersion + } + if err != nil { + return nil, err + } + response, err = NewStructuredResponse(rpcBlock) + if err != nil { + return nil, err + } + + // Pending blocks need to nil out a few fields + if blockNum == rpc.PendingBlockNumber { + for _, field := range []string{"hash", "nonce", "miner"} { + response[field] = nil + } + } + return response, err +} + +// GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full +// detail, otherwise only the transaction hash is returned. When withSigners in BlocksArgs is true +// it shows block signers for this block in list of one addresses. +func (s *PublicBlockchainService) GetBlockByHash( + ctx context.Context, blockHash common.Hash, opts interface{}, +) (response StructuredResponse, err error) { + // Process arguments based on version + var blockArgs *rpc_common.BlockArgs + blockArgs, ok := opts.(*rpc_common.BlockArgs) + if !ok { + blockArgs, err = s.getBlockOptions(opts) + if err != nil { + return nil, err + } + } + blockArgs.InclTx = true + + // Fetch the block + blk, err := s.hmy.GetBlock(ctx, blockHash) + if err != nil { + return nil, err + } + if blockArgs.WithSigners { + blockArgs.Signers, err = s.GetBlockSigners(ctx, BlockNumber(blk.NumberU64())) + if err != nil { + return nil, err + } + } + + // Format the response according to version + leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch()) + var rpcBlock interface{} + switch s.version { + case V1: + rpcBlock, err = v1.NewBlock(blk, blockArgs, leader) + case V2: + rpcBlock, err = v2.NewBlock(blk, blockArgs, leader) + default: + return nil, ErrUnknownRPCVersion + } + if err != nil { + return nil, err + } + return NewStructuredResponse(rpcBlock) +} + +// GetBlockByNumberNew is an alias for GetBlockByNumber using rpc_common.BlockArgs +func (s *PublicBlockchainService) GetBlockByNumberNew( + ctx context.Context, blockNum BlockNumber, blockArgs *rpc_common.BlockArgs, +) (StructuredResponse, error) { + return s.GetBlockByNumber(ctx, blockNum, blockArgs) +} + +// GetBlockByHashNew is an alias for GetBlocksByHash using rpc_common.BlockArgs +func (s *PublicBlockchainService) GetBlockByHashNew( + ctx context.Context, blockHash common.Hash, blockArgs *rpc_common.BlockArgs, +) (StructuredResponse, error) { + return s.GetBlockByHash(ctx, blockHash, blockArgs) +} + +// GetBlocks method returns blocks in range blockStart, blockEnd just like GetBlockByNumber but all at once. +func (s *PublicBlockchainService) GetBlocks( + ctx context.Context, blockNumberStart BlockNumber, + blockNumberEnd BlockNumber, blockArgs *rpc_common.BlockArgs, +) ([]StructuredResponse, error) { + blockStart := blockNumberStart.Int64() + blockEnd := blockNumberEnd.Int64() + + // Fetch blocks within given range + result := []StructuredResponse{} + for i := blockStart; i <= blockEnd; i++ { + blockNum := BlockNumber(i) + if blockNum.Int64() > s.hmy.CurrentBlock().Number().Int64() { + break + } + // rpcBlock is already formatted according to version + rpcBlock, err := s.GetBlockByNumber(ctx, blockNum, blockArgs) + if err != nil { + utils.Logger().Warn().Err(err).Msg("RPC Get Blocks Error") + } + if rpcBlock != nil { + result = append(result, rpcBlock) + } + } + return result, nil +} + +// IsLastBlock checks if block is last epoch block. +func (s *PublicBlockchainService) IsLastBlock(ctx context.Context, blockNum uint64) (bool, error) { + if !isBeaconShard(s.hmy) { + return false, ErrNotBeaconShard + } + return shard.Schedule.IsLastBlock(blockNum), nil +} + +// EpochLastBlock returns epoch last block. +func (s *PublicBlockchainService) EpochLastBlock(ctx context.Context, epoch uint64) (uint64, error) { + if !isBeaconShard(s.hmy) { + return 0, ErrNotBeaconShard + } + return shard.Schedule.EpochLastBlock(epoch), nil +} + +// GetBlockSigners returns signers for a particular block. +func (s *PublicBlockchainService) GetBlockSigners( + ctx context.Context, blockNumber BlockNumber, +) ([]string, error) { + // Process arguments based on version + blockNum := blockNumber.EthBlockNumber() + + // Ensure correct block + if blockNum.Int64() == 0 || blockNum.Int64() >= s.hmy.CurrentBlock().Number().Int64() { + return []string{}, nil + } + if isBlockGreaterThanLatest(s.hmy, blockNum) { + return nil, ErrRequestedBlockTooHigh + } + + // Fetch signers + slots, mask, err := s.hmy.GetBlockSigners(ctx, blockNum) + if err != nil { + return nil, err + } + + // Response output is the same for all versions + signers := []string{} + for _, validator := range slots { + oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress) + if err != nil { + return nil, err + } + if ok, err := mask.KeyEnabled(validator.BLSPublicKey); err == nil && ok { + signers = append(signers, oneAddress) + } + } + return signers, nil +} + +// GetBlockSignerKeys returns bls public keys that signed the block. +func (s *PublicBlockchainService) GetBlockSignerKeys( + ctx context.Context, blockNumber BlockNumber, +) ([]string, error) { + // Process arguments based on version + blockNum := blockNumber.EthBlockNumber() + + // Ensure correct block + if blockNum.Int64() == 0 || blockNum.Int64() >= s.hmy.CurrentBlock().Number().Int64() { + return []string{}, nil + } + if isBlockGreaterThanLatest(s.hmy, blockNum) { + return nil, ErrRequestedBlockTooHigh + } + + // Fetch signers + slots, mask, err := s.hmy.GetBlockSigners(ctx, blockNum) + if err != nil { + return nil, err + } + + // Response output is the same for all versions + signers := []string{} + for _, validator := range slots { + if ok, err := mask.KeyEnabled(validator.BLSPublicKey); err == nil && ok { + signers = append(signers, validator.BLSPublicKey.Hex()) + } + } + return signers, nil +} + +// IsBlockSigner returns true if validator with address signed blockNum block. +func (s *PublicBlockchainService) IsBlockSigner( + ctx context.Context, blockNumber BlockNumber, address string, +) (bool, error) { + // Process arguments based on version + blockNum := blockNumber.EthBlockNumber() + + // Ensure correct block + if blockNum.Int64() == 0 { + return false, nil + } + if isBlockGreaterThanLatest(s.hmy, blockNum) { + return false, ErrRequestedBlockTooHigh + } + + // Fetch signers + slots, mask, err := s.hmy.GetBlockSigners(ctx, blockNum) + if err != nil { + return false, err + } + + // Check if given address is in slots (response output is the same for all versions) + for _, validator := range slots { + oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress) + if err != nil { + return false, err + } + if oneAddress != address { + continue + } + if ok, err := mask.KeyEnabled(validator.BLSPublicKey); err == nil && ok { + return true, nil + } + } + return false, nil +} + +// GetSignedBlocks returns how many blocks a particular validator signed for +// last blocksPeriod (1 epoch's worth of blocks). +func (s *PublicBlockchainService) GetSignedBlocks( + ctx context.Context, address string, +) (interface{}, error) { + // Fetch the number of signed blocks within default period + totalSigned := uint64(0) + lastBlock := uint64(0) + blockHeight := s.hmy.CurrentBlock().Number().Uint64() + if blockHeight >= blocksPeriod { + lastBlock = blockHeight - blocksPeriod + 1 + } + for i := lastBlock; i <= blockHeight; i++ { + signed, err := s.IsBlockSigner(ctx, BlockNumber(i), address) + if err == nil && signed { + totalSigned++ + } + } + + // Format the response according to the version + switch s.version { + case V1: + return hexutil.Uint64(totalSigned), nil + case V2: + return totalSigned, nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetEpoch returns current epoch. +func (s *PublicBlockchainService) GetEpoch(ctx context.Context) (interface{}, error) { + // Fetch Header + header, err := s.hmy.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return "", err + } + epoch := header.Epoch().Uint64() + + // Format the response according to the version + switch s.version { + case V1: + return hexutil.Uint64(epoch), nil + case V2: + return epoch, nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetLeader returns current shard leader. +func (s *PublicBlockchainService) GetLeader(ctx context.Context) (string, error) { + // Fetch Header + header, err := s.hmy.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return "", err + } + + // Response output is the same for all versions + leader := s.hmy.GetLeaderAddress(header.Coinbase(), header.Epoch()) + return leader, nil +} + +// GetShardingStructure returns an array of sharding structures. +func (s *PublicBlockchainService) GetShardingStructure( + ctx context.Context, +) ([]StructuredResponse, error) { + // Get header and number of shards. + epoch := s.hmy.CurrentBlock().Epoch() + numShard := shard.Schedule.InstanceForEpoch(epoch).NumShards() + + // Return sharding structure for each case (response output is the same for all versions) + return shard.Schedule.GetShardingStructure(int(numShard), int(s.hmy.ShardID)), nil +} + +// GetShardID returns shard ID of the requested node. +func (s *PublicBlockchainService) GetShardID(ctx context.Context) (int, error) { + // Response output is the same for all versions + return int(s.hmy.ShardID), nil +} + +// GetBalanceByBlockNumber returns balance by block number +func (s *PublicBlockchainService) GetBalanceByBlockNumber( + ctx context.Context, address string, blockNumber BlockNumber, +) (interface{}, error) { + // Process number based on version + blockNum := blockNumber.EthBlockNumber() + + // Fetch balance + if isBlockGreaterThanLatest(s.hmy, blockNum) { + return nil, ErrRequestedBlockTooHigh + } + balance, err := s.getBalanceByBlockNumber(ctx, address, blockNum) + if err != nil { + return nil, err + } + + // Format return base on version + switch s.version { + case V1: + return (*hexutil.Big)(balance), nil + case V2: + return balance, nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// LatestHeader returns the latest header information +func (s *PublicBlockchainService) LatestHeader(ctx context.Context) (StructuredResponse, error) { + // Fetch Header + header, err := s.hmy.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return nil, err + } + + // Response output is the same for all versions + leader := s.hmy.GetLeaderAddress(header.Coinbase(), header.Epoch()) + return NewStructuredResponse(NewHeaderInformation(header, leader)) +} + +// GetLatestChainHeaders .. +func (s *PublicBlockchainService) GetLatestChainHeaders( + ctx context.Context, +) (StructuredResponse, error) { + // Response output is the same for all versions + return NewStructuredResponse(s.hmy.GetLatestChainHeaders()) +} + +// GetLastCrossLinks .. +func (s *PublicBlockchainService) GetLastCrossLinks( + ctx context.Context, +) ([]StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch crosslinks + crossLinks, err := s.hmy.GetLastCrossLinks() + if err != nil { + return nil, err + } + + // Format response, all output is the same for all versions + responseSlice := []StructuredResponse{} + for _, el := range crossLinks { + response, err := NewStructuredResponse(el) + if err != nil { + return nil, err + } + responseSlice = append(responseSlice, response) + } + return responseSlice, nil +} + +// GetHeaderByNumber returns block header at given number +func (s *PublicBlockchainService) GetHeaderByNumber( + ctx context.Context, blockNumber BlockNumber, +) (StructuredResponse, error) { + // Process number based on version + blockNum := blockNumber.EthBlockNumber() + + // Ensure valid block number + if isBlockGreaterThanLatest(s.hmy, blockNum) { + return nil, ErrRequestedBlockTooHigh + } + + // Fetch Header + header, err := s.hmy.HeaderByNumber(ctx, blockNum) + if err != nil { + return nil, err + } + + // Response output is the same for all versions + leader := s.hmy.GetLeaderAddress(header.Coinbase(), header.Epoch()) + return NewStructuredResponse(NewHeaderInformation(header, leader)) +} + +// GetCurrentUtilityMetrics .. +func (s *PublicBlockchainService) GetCurrentUtilityMetrics( + ctx context.Context, +) (StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch metrics + metrics, err := s.hmy.GetCurrentUtilityMetrics() + if err != nil { + return nil, err + } + + // Response output is the same for all versions + return NewStructuredResponse(metrics) +} + +// GetSuperCommittees .. +func (s *PublicBlockchainService) GetSuperCommittees( + ctx context.Context, +) (StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch super committees + cmt, err := s.hmy.GetSuperCommittees() + if err != nil { + return nil, err + } + + // Response output is the same for all versions + return NewStructuredResponse(cmt) +} + +// GetCurrentBadBlocks .. +func (s *PublicBlockchainService) GetCurrentBadBlocks( + ctx context.Context, +) ([]StructuredResponse, error) { + // Fetch bad blocks and format + badBlocks := []StructuredResponse{} + for _, blk := range s.hmy.GetCurrentBadBlocks() { + // Response output is the same for all versions + fmtBadBlock, err := NewStructuredResponse(blk) + if err != nil { + return nil, err + } + badBlocks = append(badBlocks, fmtBadBlock) + } + + return badBlocks, nil +} + +// GetTotalSupply .. +func (s *PublicBlockchainService) GetTotalSupply( + ctx context.Context, +) (numeric.Dec, error) { + // Response output is the same for all versions + return numeric.NewDec(initSupply), nil +} + +// GetCirculatingSupply .. +func (s *PublicBlockchainService) GetCirculatingSupply( + ctx context.Context, +) (numeric.Dec, error) { + timestamp := time.Now() + + // Response output is the same for all versions + return numeric.NewDec(initSupply).Mul(reward.PercentageForTimeStamp(timestamp.Unix())), nil +} + +// GetStakingNetworkInfo .. +func (s *PublicBlockchainService) GetStakingNetworkInfo( + ctx context.Context, +) (StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + totalStaking := s.hmy.GetTotalStakingSnapshot() + header, err := s.hmy.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return nil, err + } + medianSnapshot, err := s.hmy.GetMedianRawStakeSnapshot() + if err != nil { + return nil, err + } + epochLastBlock, err := s.EpochLastBlock(ctx, header.Epoch().Uint64()) + if err != nil { + return nil, err + } + totalSupply, err := s.GetTotalSupply(ctx) + if err != nil { + return nil, err + } + circulatingSupply, err := s.GetCirculatingSupply(ctx) + if err != nil { + return nil, err + } + + // Response output is the same for all versions + return NewStructuredResponse(StakingNetworkInfo{ + TotalSupply: totalSupply, + CirculatingSupply: circulatingSupply, + EpochLastBlock: epochLastBlock, + TotalStaking: totalStaking, + MedianRawStake: medianSnapshot.MedianStake, + }) +} + +func isBlockGreaterThanLatest(hmy *hmy.Harmony, blockNum rpc.BlockNumber) bool { + // rpc.BlockNumber is int64 (latest = -1. pending = -2) and currentBlockNum is uint64. + if blockNum == rpc.PendingBlockNumber { + return true + } + if blockNum == rpc.LatestBlockNumber { + return false + } + return uint64(blockNum) > hmy.CurrentBlock().NumberU64() +} diff --git a/internal/hmyapi/common/hacks.go b/rpc/common/hacks.go similarity index 100% rename from internal/hmyapi/common/hacks.go rename to rpc/common/hacks.go diff --git a/internal/hmyapi/common/types.go b/rpc/common/types.go similarity index 71% rename from internal/hmyapi/common/types.go rename to rpc/common/types.go index 5da9f11a4..9c91aa176 100644 --- a/internal/hmyapi/common/types.go +++ b/rpc/common/types.go @@ -1,10 +1,35 @@ package common import ( + "encoding/json" + "github.com/harmony-one/harmony/internal/params" "github.com/libp2p/go-libp2p-core/peer" ) +// BlockArgs is struct to include optional block formatting params. +type BlockArgs struct { + WithSigners bool `json:"withSigners"` + InclTx bool `json:"inclTx"` + FullTx bool `json:"fullTx"` + Signers []string `json:"signers"` + InclStaking bool `json:"inclStaking"` +} + +// UnmarshalFromInterface .. +func (ba *BlockArgs) UnmarshalFromInterface(blockArgs interface{}) error { + var args BlockArgs + dat, err := json.Marshal(blockArgs) + if err != nil { + return err + } + if err := json.Unmarshal(dat, &args); err != nil { + return err + } + *ba = args + return nil +} + // C .. type C struct { TotalKnownPeers int `json:"total-known-peers"` diff --git a/rpc/contract.go b/rpc/contract.go new file mode 100644 index 000000000..1fea9a121 --- /dev/null +++ b/rpc/contract.go @@ -0,0 +1,196 @@ +package rpc + +import ( + "context" + "fmt" + "math" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/common/denominations" + "github.com/harmony-one/harmony/core" + "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/core/vm" + "github.com/harmony-one/harmony/hmy" + internal_common "github.com/harmony-one/harmony/internal/common" + "github.com/harmony-one/harmony/internal/utils" +) + +const ( + defaultGasPrice = denominations.Nano + defaultFromAddress = "0x0000000000000000000000000000000000000000" +) + +// PublicContractService provides an API to access Harmony's contract services. +// It offers only methods that operate on public data that is freely available to anyone. +type PublicContractService struct { + hmy *hmy.Harmony + version Version +} + +// NewPublicContractAPI creates a new API for the RPC interface +func NewPublicContractAPI(hmy *hmy.Harmony, version Version) rpc.API { + return rpc.API{ + Namespace: version.Namespace(), + Version: APIVersion, + Service: &PublicContractService{hmy, version}, + Public: true, + } +} + +// Call executes the given transaction on the state for the given block number. +// It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. +func (s *PublicContractService) Call( + ctx context.Context, args CallArgs, blockNumber BlockNumber, +) (hexutil.Bytes, error) { + // Process number based on version + blockNum := blockNumber.EthBlockNumber() + + // Execute call + result, _, _, err := doCall(ctx, s.hmy, args, blockNum, vm.Config{}, CallTimeout, s.hmy.RPCGasCap) + + // Response output is the same for all versions + return result, err +} + +// GetCode returns the code stored at the given address in the state for the given block number. +func (s *PublicContractService) GetCode( + ctx context.Context, addr string, blockNumber BlockNumber, +) (hexutil.Bytes, error) { + // Process number based on version + blockNum := blockNumber.EthBlockNumber() + + // Fetch state + address := internal_common.ParseAddr(addr) + state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNum) + if state == nil || err != nil { + return nil, err + } + code := state.GetCode(address) + + // Response output is the same for all versions + return code, state.Error() +} + +// GetStorageAt returns the storage from the state at the given address, key and +// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block +// numbers are also allowed. +func (s *PublicContractService) GetStorageAt( + ctx context.Context, addr string, key string, blockNumber BlockNumber, +) (hexutil.Bytes, error) { + // Process number based on version + blockNum := blockNumber.EthBlockNumber() + + // Fetch state + state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNum) + if state == nil || err != nil { + return nil, err + } + address := internal_common.ParseAddr(addr) + res := state.GetState(address, common.HexToHash(key)) + + // Response output is the same for all versions + return res[:], state.Error() +} + +// docall executes an EVM call +func doCall( + ctx context.Context, hmy *hmy.Harmony, args CallArgs, blockNum rpc.BlockNumber, + vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int, +) ([]byte, uint64, bool, error) { + defer func(start time.Time) { + utils.Logger().Debug(). + Dur("runtime", time.Since(start)). + Msg("Executing EVM call finished") + }(time.Now()) + + // Fetch state + state, header, err := hmy.StateAndHeaderByNumber(ctx, blockNum) + if state == nil || err != nil { + return nil, 0, false, err + } + + // Set sender address or use a default if none specified + var addr common.Address + if args.From == nil { + // Any address does not affect the logic of this call. + addr = common.HexToAddress(defaultFromAddress) + } else { + addr = *args.From + } + + // Set default gas & gas price if none were set + gas := uint64(math.MaxUint64 / 2) + if args.Gas != nil { + gas = uint64(*args.Gas) + } + if globalGasCap != nil && globalGasCap.Uint64() < gas { + utils.Logger().Warn(). + Uint64("requested", gas). + Uint64("cap", globalGasCap.Uint64()). + Msg("Caller gas above allowance, capping") + gas = globalGasCap.Uint64() + } + gasPrice := new(big.Int).SetUint64(defaultGasPrice) + if args.GasPrice != nil { + gasPrice = args.GasPrice.ToInt() + } + + // Set value & data + value := new(big.Int) + if args.Value != nil { + value = args.Value.ToInt() + } + var data []byte + if args.Data != nil { + data = *args.Data + } + + // Create new call message + msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) + + // Setup context so it may be cancelled the call has completed + // or, in case of unmetered gas, setup a context with a timeout. + var cancel context.CancelFunc + if timeout > 0 { + ctx, cancel = context.WithTimeout(ctx, timeout) + } else { + ctx, cancel = context.WithCancel(ctx) + } + + // Make sure the context is cancelled when the call has completed + // this makes sure resources are cleaned up. + defer cancel() + + // Get a new instance of the EVM. + evm, vmError, err := hmy.GetEVM(ctx, msg, state, header) + if err != nil { + return nil, 0, false, err + } + + // Wait for the context to be done and cancel the evm. Even if the + // EVM has finished, cancelling may be done (repeatedly) + go func() { + <-ctx.Done() + evm.Cancel() + }() + + // Setup the gas pool (also for unmetered requests) + // and apply the message. + gp := new(core.GasPool).AddGas(math.MaxUint64) + res, gas, failed, err := core.ApplyMessage(evm, msg, gp) + if err := vmError(); err != nil { + return nil, 0, false, err + } + + // If the timer caused an abort, return an appropriate error message + if evm.Cancelled() { + return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout) + } + + // Response output is the same for all versions + return res, gas, failed, err +} diff --git a/rpc/debug.go b/rpc/debug.go new file mode 100644 index 000000000..090e68f14 --- /dev/null +++ b/rpc/debug.go @@ -0,0 +1,40 @@ +package rpc + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/hmy" + "github.com/harmony-one/harmony/internal/utils" +) + +// PrivateDebugService Internal JSON RPC for debugging purpose +type PrivateDebugService struct { + hmy *hmy.Harmony + version Version +} + +// NewPrivateDebugAPI creates a new API for the RPC interface +// TODO(dm): expose public via config +func NewPrivateDebugAPI(hmy *hmy.Harmony, version Version) rpc.API { + return rpc.API{ + Namespace: version.Namespace(), + Version: APIVersion, + Service: &PrivateDebugService{hmy, version}, + Public: false, + } +} + +// SetLogVerbosity Sets log verbosity on runtime +// Example usage: +// curl -H "Content-Type: application/json" -d '{"method":"debug_setLogVerbosity","params":[0],"id":1}' http://localhost:9123 +func (*PrivateDebugService) SetLogVerbosity(ctx context.Context, level int) (map[string]interface{}, error) { + if level < int(log.LvlCrit) || level > int(log.LvlTrace) { + return nil, ErrInvalidLogLevel + } + + verbosity := log.Lvl(level) + utils.SetLogVerbosity(verbosity) + return map[string]interface{}{"verbosity": verbosity.String()}, nil +} diff --git a/internal/hmyapi/apiv2/error.go b/rpc/error.go similarity index 69% rename from internal/hmyapi/apiv2/error.go rename to rpc/error.go index b80d49da3..cfe653592 100644 --- a/internal/hmyapi/apiv2/error.go +++ b/rpc/error.go @@ -1,4 +1,4 @@ -package apiv2 +package rpc import ( "errors" @@ -15,4 +15,8 @@ var ( ErrNotBeaconShard = errors.New("cannot call this rpc on non beaconchain node") // ErrRequestedBlockTooHigh when given block is greater than latest block number ErrRequestedBlockTooHigh = errors.New("requested block number greater than current block number") + // ErrUnknownRPCVersion when rpc method has an unknown or unhandled version + ErrUnknownRPCVersion = errors.New("API service has an unknown version") + // ErrTransactionNotFound when attempting to get a transaction that does exist or has not been finalized + ErrTransactionNotFound = errors.New("transaction not found") ) diff --git a/internal/hmyapi/filters/api.go b/rpc/filters/api.go similarity index 100% rename from internal/hmyapi/filters/api.go rename to rpc/filters/api.go diff --git a/internal/hmyapi/filters/filter.go b/rpc/filters/filter.go similarity index 100% rename from internal/hmyapi/filters/filter.go rename to rpc/filters/filter.go diff --git a/internal/hmyapi/filters/filter_criteria.go b/rpc/filters/filter_criteria.go similarity index 100% rename from internal/hmyapi/filters/filter_criteria.go rename to rpc/filters/filter_criteria.go diff --git a/internal/hmyapi/filters/filter_system.go b/rpc/filters/filter_system.go similarity index 100% rename from internal/hmyapi/filters/filter_system.go rename to rpc/filters/filter_system.go diff --git a/rpc/harmony.go b/rpc/harmony.go new file mode 100644 index 000000000..278e4ab18 --- /dev/null +++ b/rpc/harmony.go @@ -0,0 +1,88 @@ +package rpc + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/hmy" +) + +// PublicHarmonyService provides an API to access Harmony related information. +// It offers only methods that operate on public data that is freely available to anyone. +type PublicHarmonyService struct { + hmy *hmy.Harmony + version Version +} + +// NewPublicHarmonyAPI creates a new API for the RPC interface +func NewPublicHarmonyAPI(hmy *hmy.Harmony, version Version) rpc.API { + return rpc.API{ + Namespace: version.Namespace(), + Version: APIVersion, + Service: &PublicHarmonyService{hmy, version}, + Public: true, + } +} + +// ProtocolVersion returns the current Harmony protocol version this node supports +// Note that the return type is an interface to account for the different versions +func (s *PublicHarmonyService) ProtocolVersion( + ctx context.Context, +) (interface{}, error) { + // Format response according to version + switch s.version { + case V1: + return hexutil.Uint(s.hmy.ProtocolVersion()), nil + case V2: + return s.hmy.ProtocolVersion(), nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not +// yet received the latest block headers from its pears. In case it is synchronizing: +// - startingBlock: block number this node started to synchronise from +// - currentBlock: block number this node is currently importing +// - highestBlock: block number of the highest block header this node has received from peers +// - pulledStates: number of state entries processed until now +// - knownStates: number of known state entries that still need to be pulled +func (s *PublicHarmonyService) Syncing( + ctx context.Context, +) (interface{}, error) { + // TODO(dm): find our Downloader module for syncing blocks + return false, nil +} + +// GasPrice returns a suggestion for a gas price. +// Note that the return type is an interface to account for the different versions +func (s *PublicHarmonyService) GasPrice(ctx context.Context) (interface{}, error) { + // TODO(dm): add SuggestPrice API + // Format response according to version + switch s.version { + case V1: + return (*hexutil.Big)(big.NewInt(1)), nil + case V2: + return 1, nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetNodeMetadata produces a NodeMetadata record, data is from the answering RPC node +func (s *PublicHarmonyService) GetNodeMetadata( + ctx context.Context, +) (StructuredResponse, error) { + // Response output is the same for all versions + return NewStructuredResponse(s.hmy.GetNodeMetadata()) +} + +// GetPeerInfo produces a NodePeerInfo record +func (s *PublicHarmonyService) GetPeerInfo( + ctx context.Context, +) (StructuredResponse, error) { + // Response output is the same for all versions + return NewStructuredResponse(s.hmy.GetPeerInfo()) +} diff --git a/rpc/net.go b/rpc/net.go new file mode 100644 index 000000000..3a13f5354 --- /dev/null +++ b/rpc/net.go @@ -0,0 +1,59 @@ +package rpc + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/internal/utils" + "github.com/harmony-one/harmony/p2p" +) + +// PublicNetService offers network related RPC methods +type PublicNetService struct { + net p2p.Host + chainID uint64 + version Version +} + +// NewPublicNetAPI creates a new net API instance. +func NewPublicNetAPI(net p2p.Host, chainID uint64, version Version) rpc.API { + // manually set different namespace to preserve legacy behavior + var namespace string + switch version { + case V1: + namespace = netV1Namespace + case V2: + namespace = netV2Namespace + default: + utils.Logger().Error().Msgf("Unknown version %v, ignoring API.", version) + return rpc.API{} + } + + return rpc.API{ + Namespace: namespace, + Version: APIVersion, + Service: &PublicNetService{net, chainID, version}, + Public: true, + } +} + +// PeerCount returns the number of connected peers +// Note that the return type is an interface to account for the different versions +func (s *PublicNetService) PeerCount(ctx context.Context) (interface{}, error) { + // Format response according to version + switch s.version { + case V1: + return hexutil.Uint(s.net.GetPeerCount()), nil + case V2: + return s.net.GetPeerCount(), nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// Version returns the network version, i.e. ChainID identifying which network we are using +func (s *PublicNetService) Version(ctx context.Context) string { + return fmt.Sprintf("%d", s.chainID) +} diff --git a/rpc/pool.go b/rpc/pool.go new file mode 100644 index 000000000..39a99d005 --- /dev/null +++ b/rpc/pool.go @@ -0,0 +1,295 @@ +package rpc + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/core" + "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/hmy" + common2 "github.com/harmony-one/harmony/internal/common" + "github.com/harmony-one/harmony/internal/utils" + v1 "github.com/harmony-one/harmony/rpc/v1" + v2 "github.com/harmony-one/harmony/rpc/v2" + staking "github.com/harmony-one/harmony/staking/types" +) + +// PublicPoolService provides an API to access the Harmony node's transaction pool. +// It offers only methods that operate on public data that is freely available to anyone. +type PublicPoolService struct { + hmy *hmy.Harmony + version Version +} + +// NewPublicPoolAPI creates a new API for the RPC interface +func NewPublicPoolAPI(hmy *hmy.Harmony, version Version) rpc.API { + return rpc.API{ + Namespace: version.Namespace(), + Version: APIVersion, + Service: &PublicPoolService{hmy, version}, + Public: true, + } +} + +// SendRawTransaction will add the signed transaction to the transaction pool. +// The sender is responsible for signing the transaction and using the correct nonce. +func (s *PublicPoolService) SendRawTransaction( + ctx context.Context, encodedTx hexutil.Bytes, +) (common.Hash, error) { + // DOS prevention + if len(encodedTx) >= types.MaxEncodedPoolTransactionSize { + err := errors.Wrapf(core.ErrOversizedData, "encoded tx size: %d", len(encodedTx)) + return common.Hash{}, err + } + + // Verify transaction type & chain + tx := new(types.Transaction) + if err := rlp.DecodeBytes(encodedTx, tx); err != nil { + return common.Hash{}, err + } + c := s.hmy.ChainConfig().ChainID + if id := tx.ChainID(); id.Cmp(c) != 0 { + return common.Hash{}, errors.Wrapf( + ErrInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(), + ) + } + + // Submit transaction + if err := s.hmy.SendTx(ctx, tx); err != nil { + utils.Logger().Warn().Err(err).Msg("Could not submit transaction") + return tx.Hash(), err + } + + // Log submission + if tx.To() == nil { + signer := types.MakeSigner(s.hmy.ChainConfig(), s.hmy.CurrentBlock().Epoch()) + from, err := types.Sender(signer, tx) + if err != nil { + return common.Hash{}, err + } + addr := crypto.CreateAddress(from, tx.Nonce()) + utils.Logger().Info(). + Str("fullhash", tx.Hash().Hex()). + Str("contract", common2.MustAddressToBech32(addr)). + Msg("Submitted contract creation") + } else { + utils.Logger().Info(). + Str("fullhash", tx.Hash().Hex()). + Str("recipient", tx.To().Hex()). + Msg("Submitted transaction") + } + + // Response output is the same for all versions + return tx.Hash(), nil +} + +// SendRawStakingTransaction will add the signed transaction to the transaction pool. +// The sender is responsible for signing the transaction and using the correct nonce. +func (s *PublicPoolService) SendRawStakingTransaction( + ctx context.Context, encodedTx hexutil.Bytes, +) (common.Hash, error) { + // DOS prevention + if len(encodedTx) >= types.MaxEncodedPoolTransactionSize { + err := errors.Wrapf(core.ErrOversizedData, "encoded tx size: %d", len(encodedTx)) + return common.Hash{}, err + } + + // Verify staking transaction type & chain + tx := new(staking.StakingTransaction) + if err := rlp.DecodeBytes(encodedTx, tx); err != nil { + return common.Hash{}, err + } + c := s.hmy.ChainConfig().ChainID + if id := tx.ChainID(); id.Cmp(c) != 0 { + return common.Hash{}, errors.Wrapf( + ErrInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(), + ) + } + + // Submit transaction + if err := s.hmy.SendStakingTx(ctx, tx); err != nil { + utils.Logger().Warn().Err(err).Msg("Could not submit staking transaction") + return tx.Hash(), err + } + + // Log submission + utils.Logger().Info(). + Str("fullhash", tx.Hash().Hex()). + Msg("Submitted Staking transaction") + + // Response output is the same for all versions + return tx.Hash(), nil +} + +// GetPoolStats returns stats for the tx-pool +func (s *PublicPoolService) GetPoolStats( + ctx context.Context, +) (StructuredResponse, error) { + pendingCount, queuedCount := s.hmy.GetPoolStats() + + // Response output is the same for all versions + return StructuredResponse{ + "executable-count": pendingCount, + "non-executable-count": queuedCount, + }, nil +} + +// PendingTransactions returns the plain transactions that are in the transaction pool +func (s *PublicPoolService) PendingTransactions( + ctx context.Context, +) ([]StructuredResponse, error) { + // Fetch all pending transactions (stx & plain tx) + pending, err := s.hmy.GetPoolTransactions() + if err != nil { + return nil, err + } + + // Only format and return plain transactions according to the version + transactions := []StructuredResponse{} + for i := range pending { + if plainTx, ok := pending[i].(*types.Transaction); ok { + var tx interface{} + switch s.version { + case V1: + tx, err = v1.NewTransaction(plainTx, common.Hash{}, 0, 0, 0) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "PendingTransactions") + continue // Legacy behavior is to not return error here + } + case V2: + tx, err = v2.NewTransaction(plainTx, common.Hash{}, 0, 0, 0) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "PendingTransactions") + continue // Legacy behavior is to not return error here + } + default: + return nil, ErrUnknownRPCVersion + } + rpcTx, err := NewStructuredResponse(tx) + if err == nil { + transactions = append(transactions, rpcTx) + } else { + // Legacy behavior is to not return error here + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "PendingTransactions") + } + } else if _, ok := pending[i].(*staking.StakingTransaction); ok { + continue // Do not return staking transactions here. + } else { + return nil, types.ErrUnknownPoolTxType + } + } + return transactions, nil +} + +// PendingStakingTransactions returns the staking transactions that are in the transaction pool +func (s *PublicPoolService) PendingStakingTransactions( + ctx context.Context, +) ([]StructuredResponse, error) { + // Fetch all pending transactions (stx & plain tx) + pending, err := s.hmy.GetPoolTransactions() + if err != nil { + return nil, err + } + + // Only format and return staking transactions according to the version + transactions := []StructuredResponse{} + for i := range pending { + if _, ok := pending[i].(*types.Transaction); ok { + continue // Do not return plain transactions here + } else if stakingTx, ok := pending[i].(*staking.StakingTransaction); ok { + var tx interface{} + switch s.version { + case V1: + tx, err = v1.NewStakingTransaction(stakingTx, common.Hash{}, 0, 0, 0) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "PendingStakingTransactions") + continue // Legacy behavior is to not return error here + } + case V2: + tx, err = v2.NewStakingTransaction(stakingTx, common.Hash{}, 0, 0, 0) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "PendingStakingTransactions") + continue // Legacy behavior is to not return error here + } + default: + return nil, ErrUnknownRPCVersion + } + rpcTx, err := NewStructuredResponse(tx) + if err == nil { + transactions = append(transactions, rpcTx) + } else { + // Legacy behavior is to not return error here + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "PendingStakingTransactions") + } + } else { + return nil, types.ErrUnknownPoolTxType + } + } + return transactions, nil +} + +// GetCurrentTransactionErrorSink .. +func (s *PublicPoolService) GetCurrentTransactionErrorSink( + ctx context.Context, +) ([]StructuredResponse, error) { + // For each transaction error in the error sink, format the response (same format for all versions) + formattedErrors := []StructuredResponse{} + for _, txError := range s.hmy.GetCurrentTransactionErrorSink() { + formattedErr, err := NewStructuredResponse(txError) + if err != nil { + return nil, err + } + formattedErrors = append(formattedErrors, formattedErr) + } + return formattedErrors, nil +} + +// GetCurrentStakingErrorSink .. +func (s *PublicPoolService) GetCurrentStakingErrorSink( + ctx context.Context, +) ([]StructuredResponse, error) { + // For each staking tx error in the error sink, format the response (same format for all versions) + formattedErrors := []StructuredResponse{} + for _, txErr := range s.hmy.GetCurrentStakingErrorSink() { + formattedErr, err := NewStructuredResponse(txErr) + if err != nil { + return nil, err + } + formattedErrors = append(formattedErrors, formattedErr) + } + return formattedErrors, nil +} + +// GetPendingCXReceipts .. +func (s *PublicPoolService) GetPendingCXReceipts( + ctx context.Context, +) ([]StructuredResponse, error) { + // For each cx receipt, format the response (same format for all versions) + formattedReceipts := []StructuredResponse{} + for _, receipts := range s.hmy.GetPendingCXReceipts() { + formattedReceipt, err := NewStructuredResponse(receipts) + if err != nil { + return nil, err + } + formattedReceipts = append(formattedReceipts, formattedReceipt) + } + return formattedReceipts, nil +} diff --git a/rpc/rpc.go b/rpc/rpc.go new file mode 100644 index 000000000..5bb3f7c7f --- /dev/null +++ b/rpc/rpc.go @@ -0,0 +1,167 @@ +package rpc + +import ( + "fmt" + "net" + "strings" + "time" + + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/hmy" + nodeconfig "github.com/harmony-one/harmony/internal/configs/node" + "github.com/harmony-one/harmony/internal/utils" + v1 "github.com/harmony-one/harmony/rpc/v1" + v2 "github.com/harmony-one/harmony/rpc/v2" +) + +// Version enum +const ( + V1 Version = iota + V2 +) + +const ( + // APIVersion used for DApp's, bumped after RPC refactor (7/2020) + APIVersion = "1.1" + // CallTimeout is the timeout given to all contract calls + CallTimeout = 5 * time.Second + // LogTag is the tag found in the log for all RPC logs + LogTag = "[RPC]" + // HTTPPortOffset .. + HTTPPortOffset = 500 + // WSPortOffset .. + WSPortOffset = 800 + + netV1Namespace = "net" + netV2Namespace = "netv2" +) + +var ( + // HTTPModules .. + HTTPModules = []string{"hmy", "hmyv2", netV1Namespace, netV2Namespace, "explorer"} + // WSModules .. + WSModules = []string{"hmy", "hmyv2", netV1Namespace, netV2Namespace, "web3"} + + httpListener net.Listener + httpHandler *rpc.Server + wsListener net.Listener + wsHandler *rpc.Server + httpEndpoint = "" + wsEndpoint = "" + httpVirtualHosts = []string{"*"} + httpTimeouts = rpc.DefaultHTTPTimeouts + httpOrigins = []string{"*"} + wsOrigins = []string{"*"} +) + +// Version of the RPC +type Version int + +// Namespace of the RPC version +func (n Version) Namespace() string { + return HTTPModules[n] +} + +// StartServers starts the http & ws servers +func StartServers(hmy *hmy.Harmony, port int, apis []rpc.API) error { + ip := "" + if !nodeconfig.GetPublicRPC() { + ip = "127.0.0.1" + } + + apis = append(apis, getAPIs(hmy)...) + + httpEndpoint = fmt.Sprintf("%v:%v", ip, port+HTTPPortOffset) + if err := startHTTP(apis); err != nil { + return err + } + wsEndpoint = fmt.Sprintf("%v:%v", ip, port+WSPortOffset) + if err := startWS(apis); err != nil { + return err + } + return nil +} + +// StopServers stops the http & ws servers +func StopServers() error { + if httpListener != nil { + if err := httpListener.Close(); err != nil { + return err + } + httpListener = nil + utils.Logger().Info(). + Str("url", fmt.Sprintf("http://%s", httpEndpoint)). + Msg("HTTP endpoint closed") + } + if httpHandler != nil { + httpHandler.Stop() + httpHandler = nil + } + if wsListener != nil { + if err := wsListener.Close(); err != nil { + return err + } + wsListener = nil + utils.Logger().Info(). + Str("url", fmt.Sprintf("http://%s", wsEndpoint)). + Msg("WS endpoint closed") + } + if wsHandler != nil { + wsHandler.Stop() + wsHandler = nil + } + return nil +} + +// getAPIs returns all the API methods for the RPC interface +func getAPIs(hmy *hmy.Harmony) []rpc.API { + return []rpc.API{ + // Public methods + NewPublicHarmonyAPI(hmy, V1), + NewPublicHarmonyAPI(hmy, V2), + NewPublicBlockchainAPI(hmy, V1), + NewPublicBlockchainAPI(hmy, V2), + NewPublicContractAPI(hmy, V1), + NewPublicContractAPI(hmy, V2), + NewPublicTransactionAPI(hmy, V1), + NewPublicTransactionAPI(hmy, V2), + NewPublicPoolAPI(hmy, V1), + NewPublicPoolAPI(hmy, V2), + NewPublicStakingAPI(hmy, V1), + NewPublicStakingAPI(hmy, V2), + // Private methods + NewPrivateDebugAPI(hmy, V1), + NewPrivateDebugAPI(hmy, V2), + // Legacy methods (subject to removal) + v1.NewPublicLegacyAPI(hmy), + v2.NewPublicLegacyAPI(hmy), + } +} + +func startHTTP(apis []rpc.API) (err error) { + httpListener, httpHandler, err = rpc.StartHTTPEndpoint( + httpEndpoint, apis, HTTPModules, httpOrigins, httpVirtualHosts, httpTimeouts, + ) + if err != nil { + return err + } + + utils.Logger().Info(). + Str("url", fmt.Sprintf("http://%s", httpEndpoint)). + Str("cors", strings.Join(httpOrigins, ",")). + Str("vhosts", strings.Join(httpVirtualHosts, ",")). + Msg("HTTP endpoint opened") + return nil +} + +func startWS(apis []rpc.API) (err error) { + wsListener, wsHandler, err = rpc.StartWSEndpoint(wsEndpoint, apis, WSModules, wsOrigins, true) + if err != nil { + return err + } + + utils.Logger().Info(). + Str("url", fmt.Sprintf("ws://%s", wsListener.Addr())). + Msg("WebSocket endpoint opened") + return nil +} diff --git a/rpc/staking.go b/rpc/staking.go new file mode 100644 index 000000000..c4f172016 --- /dev/null +++ b/rpc/staking.go @@ -0,0 +1,621 @@ +package rpc + +import ( + "context" + "fmt" + "math/big" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/hmy" + internal_common "github.com/harmony-one/harmony/internal/common" + "github.com/harmony-one/harmony/shard" +) + +const ( + validatorsPageSize = 100 +) + +// PublicStakingService provides an API to access Harmony's staking services. +// It offers only methods that operate on public data that is freely available to anyone. +type PublicStakingService struct { + hmy *hmy.Harmony + version Version +} + +// NewPublicStakingAPI creates a new API for the RPC interface +func NewPublicStakingAPI(hmy *hmy.Harmony, version Version) rpc.API { + return rpc.API{ + Namespace: version.Namespace(), + Version: APIVersion, + Service: &PublicStakingService{hmy, version}, + Public: true, + } +} + +// getBalanceByBlockNumber returns balance by block number at given eth blockNum without checks +func (s *PublicStakingService) getBalanceByBlockNumber( + ctx context.Context, address string, blockNum rpc.BlockNumber, +) (*big.Int, error) { + addr := internal_common.ParseAddr(address) + balance, err := s.hmy.GetBalance(ctx, addr, blockNum) + if err != nil { + return nil, err + } + return balance, nil +} + +// getAllValidatorInformation helper function to get all validator information for a given eth block number +func (s *PublicStakingService) getAllValidatorInformation( + ctx context.Context, page int, blockNum rpc.BlockNumber, +) ([]StructuredResponse, error) { + if page < -1 { + return nil, errors.Errorf("page given %d cannot be less than -1", page) + } + + // Get all validators + addresses := s.hmy.GetAllValidatorAddresses() + if page != -1 && len(addresses) <= page*validatorsPageSize { + return []StructuredResponse{}, nil + } + + // Set page start + validatorsNum := len(addresses) + start := 0 + if page != -1 { + validatorsNum = validatorsPageSize + start = page * validatorsPageSize + if len(addresses)-start < validatorsPageSize { + validatorsNum = len(addresses) - start + } + } + + // Fetch block + blk, err := s.hmy.BlockByNumber(ctx, blockNum) + if err != nil { + return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNum) + } + + // Fetch validator information for block + validators := []StructuredResponse{} + for i := start; i < start+validatorsNum; i++ { + validatorInfo, err := s.hmy.GetValidatorInformation(addresses[i], blk) + if err == nil { + // Response output is the same for all versions + information, err := NewStructuredResponse(validatorInfo) + if err != nil { + return nil, err + } + validators = append(validators, information) + } + } + return validators, nil +} + +// GetTotalStaking returns total staking by validators, only meant to be called on beaconchain +// explorer node +func (s *PublicStakingService) GetTotalStaking( + ctx context.Context, +) (*big.Int, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Response output is the same for all versions + return s.hmy.GetTotalStakingSnapshot(), nil +} + +// GetMedianRawStakeSnapshot returns the raw median stake, only meant to be called on beaconchain +// explorer node +func (s *PublicStakingService) GetMedianRawStakeSnapshot( + ctx context.Context, +) (StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch snapshot + snapshot, err := s.hmy.GetMedianRawStakeSnapshot() + if err != nil { + return nil, err + } + + // Response output is the same for all versions + return NewStructuredResponse(snapshot) +} + +// GetElectedValidatorAddresses returns elected validator addresses. +func (s *PublicStakingService) GetElectedValidatorAddresses( + ctx context.Context, +) ([]string, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch elected validators + electedAddresses := s.hmy.GetElectedValidatorAddresses() + addresses := make([]string, len(electedAddresses)) + for i, addr := range electedAddresses { + oneAddr, _ := internal_common.AddressToBech32(addr) + // Response output is the same for all versions + addresses[i] = oneAddr + } + return addresses, nil +} + +// GetValidators returns validators list for a particular epoch. +func (s *PublicStakingService) GetValidators( + ctx context.Context, epoch int64, +) (StructuredResponse, error) { + // Fetch the Committee + cmt, err := s.hmy.GetValidators(big.NewInt(epoch)) + if err != nil { + return nil, err + } + balanceQueryBlock := shard.Schedule.EpochLastBlock(uint64(epoch)) + if balanceQueryBlock > s.hmy.CurrentBlock().NumberU64() { + balanceQueryBlock = s.hmy.CurrentBlock().NumberU64() + } + + validators := []StructuredResponse{} + for _, validator := range cmt.Slots { + // Fetch the balance of the validator + oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress) + if err != nil { + return nil, err + } + validatorBalance, err := s.getBalanceByBlockNumber(ctx, oneAddress, rpc.BlockNumber(balanceQueryBlock)) + if err != nil { + return nil, err + } + + // Format the response according to the version + var validatorsFields StructuredResponse + switch s.version { + case V1: + validatorsFields = StructuredResponse{ + "address": oneAddress, + "balance": (*hexutil.Big)(validatorBalance), + } + case V2: + validatorsFields = StructuredResponse{ + "address": oneAddress, + "balance": validatorBalance, + } + default: + return nil, ErrUnknownRPCVersion + } + validators = append(validators, validatorsFields) + } + result := StructuredResponse{ + "shardID": cmt.ShardID, + "validators": validators, + } + return result, nil +} + +// GetAllValidatorAddresses returns all validator addresses. +func (s *PublicStakingService) GetAllValidatorAddresses( + ctx context.Context, +) ([]string, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch all validator addresses + validatorAddresses := s.hmy.GetAllValidatorAddresses() + addresses := make([]string, len(validatorAddresses)) + for i, addr := range validatorAddresses { + oneAddr, _ := internal_common.AddressToBech32(addr) + // Response output is the same for all versions + addresses[i] = oneAddr + } + return addresses, nil +} + +// GetValidatorKeys returns list of bls public keys in the committee for a particular epoch. +func (s *PublicStakingService) GetValidatorKeys( + ctx context.Context, epoch int64, +) ([]string, error) { + // Fetch the Committee + cmt, err := s.hmy.GetValidators(big.NewInt(epoch)) + if err != nil { + return nil, err + } + + // Response output is the same for all versions + validators := make([]string, len(cmt.Slots)) + for i, v := range cmt.Slots { + validators[i] = v.BLSPublicKey.Hex() + } + return validators, nil +} + +// GetAllValidatorInformation returns information about all validators. +// If page is -1, return all instead of `validatorsPageSize` elements. +func (s *PublicStakingService) GetAllValidatorInformation( + ctx context.Context, page int, +) ([]StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // fetch current block number + blockNum := s.hmy.CurrentBlock().NumberU64() + + // delete cache for previous block + prevKey := fmt.Sprintf("all-info-%d", blockNum-1) + s.hmy.SingleFlightForgetKey(prevKey) + + // Fetch all validator information in a single flight request + key := fmt.Sprintf("all-info-%d", blockNum) + res, err := s.hmy.SingleFlightRequest( + key, + func() (interface{}, error) { + return s.getAllValidatorInformation(ctx, page, rpc.LatestBlockNumber) + }, + ) + if err != nil { + return nil, err + } + + // Response output is the same for all versions + return res.([]StructuredResponse), nil +} + +// GetAllValidatorInformationByBlockNumber returns information about all validators. +// If page is -1, return all instead of `validatorsPageSize` elements. +func (s *PublicStakingService) GetAllValidatorInformationByBlockNumber( + ctx context.Context, page int, blockNumber BlockNumber, +) ([]StructuredResponse, error) { + // Process number based on version + blockNum := blockNumber.EthBlockNumber() + + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + if isBlockGreaterThanLatest(s.hmy, blockNum) { + return nil, ErrRequestedBlockTooHigh + } + + // Response output is the same for all versions + return s.getAllValidatorInformation(ctx, page, blockNum) +} + +// GetValidatorInformation returns information about a validator. +func (s *PublicStakingService) GetValidatorInformation( + ctx context.Context, address string, +) (StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch latest block + blk, err := s.hmy.BlockByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return nil, errors.Wrapf(err, "could not retrieve the latest blk information") + } + + // Fetch validator information + validatorInfo, err := s.hmy.GetValidatorInformation(internal_common.ParseAddr(address), blk) + if err != nil { + return nil, err + } + + // Response output is the same for all versions + return NewStructuredResponse(validatorInfo) +} + +// GetValidatorInformationByBlockNumber returns information about a validator. +func (s *PublicStakingService) GetValidatorInformationByBlockNumber( + ctx context.Context, address string, blockNumber BlockNumber, +) (StructuredResponse, error) { + // Process number based on version + blockNum := blockNumber.EthBlockNumber() + + // Fetch block + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + if isBlockGreaterThanLatest(s.hmy, blockNum) { + return nil, ErrRequestedBlockTooHigh + } + blk, err := s.hmy.BlockByNumber(ctx, blockNum) + if err != nil { + return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNum) + } + + // Fetch validator info + validatorInfo, err := s.hmy.GetValidatorInformation(internal_common.ParseAddr(address), blk) + if err != nil { + return nil, err + } + + // Response output is the same for all versions + return NewStructuredResponse(validatorInfo) +} + +// GetValidatorSelfDelegation returns validator stake. +func (s *PublicStakingService) GetValidatorSelfDelegation( + ctx context.Context, address string, +) (interface{}, error) { + // Ensure node is for beacon shard + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch self delegation + selfDelegation := s.hmy.GetValidatorSelfDelegation(internal_common.ParseAddr(address)).Uint64() + + // Format the response according to the version + switch s.version { + case V1: + return hexutil.Uint64(selfDelegation), nil + case V2: + return selfDelegation, nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetValidatorTotalDelegation returns total balance stacking for validator with delegation. +func (s *PublicStakingService) GetValidatorTotalDelegation( + ctx context.Context, address string, +) (interface{}, error) { + // Ensure node is for beacon shard + if s.hmy.ShardID != shard.BeaconChainShardID { + return nil, ErrNotBeaconShard + } + + // Fetch delegations & sum + delegations := s.hmy.GetDelegationsByValidator(internal_common.ParseAddr(address)) + totalStake := big.NewInt(0) + for _, delegation := range delegations { + totalStake.Add(totalStake, delegation.Amount) + } + + // Format the response according to the version + switch s.version { + case V1: + return hexutil.Uint64(totalStake.Uint64()), nil + case V2: + return totalStake, nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetAllDelegationInformation returns delegation information about `validatorsPageSize` validators, +// starting at `page*validatorsPageSize`. +// If page is -1, return all instead of `validatorsPageSize` elements. +// TODO(dm): optimize with single flight +func (s *PublicStakingService) GetAllDelegationInformation( + ctx context.Context, page int, +) ([][]StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + if page < -1 { + return make([][]StructuredResponse, 0), nil + } + + // Get all validators + addresses := s.hmy.GetAllValidatorAddresses() + + // Return nothing if no delegation on page + if page != -1 && len(addresses) <= page*validatorsPageSize { + return make([][]StructuredResponse, 0), nil + } + + // Set page start + validatorsNum := len(addresses) + start := 0 + if page != -1 { + validatorsNum = validatorsPageSize + start = page * validatorsPageSize + if len(addresses)-start < validatorsPageSize { + validatorsNum = len(addresses) - start + } + } + + // Fetch all delegations + validators := make([][]StructuredResponse, validatorsNum) + var err error + for i := start; i < start+validatorsNum; i++ { + validators[i-start], err = s.GetDelegationsByValidator(ctx, addresses[i].String()) + if err != nil { + return nil, err + } + } + + // Response output is the same for all versions + return validators, nil +} + +// GetDelegationsByDelegator returns list of delegations for a delegator address. +func (s *PublicStakingService) GetDelegationsByDelegator( + ctx context.Context, address string, +) ([]StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch delegation + delegatorAddress := internal_common.ParseAddr(address) + validators, delegations := s.hmy.GetDelegationsByDelegator(delegatorAddress) + + // Format response + result := []StructuredResponse{} + for i := range delegations { + delegation := delegations[i] + undelegations := make([]Undelegation, len(delegation.Undelegations)) + + for j := range delegation.Undelegations { + undelegations = append(undelegations, Undelegation{ + Amount: delegation.Undelegations[j].Amount, + Epoch: delegation.Undelegations[j].Epoch, + }) + } + valAddr, _ := internal_common.AddressToBech32(validators[i]) + delAddr, _ := internal_common.AddressToBech32(delegatorAddress) + + // Response output is the same for all versions + del, err := NewStructuredResponse(Delegation{ + ValidatorAddress: valAddr, + DelegatorAddress: delAddr, + Amount: delegation.Amount, + Reward: delegation.Reward, + Undelegations: undelegations, + }) + if err != nil { + return nil, err + } + result = append(result, del) + } + return result, nil +} + +// GetDelegationsByDelegatorByBlockNumber returns list of delegations for a delegator address at given block number +func (s *PublicStakingService) GetDelegationsByDelegatorByBlockNumber( + ctx context.Context, address string, blockNumber BlockNumber, +) ([]StructuredResponse, error) { + // Process number based on version + blockNum := blockNumber.EthBlockNumber() + + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + if isBlockGreaterThanLatest(s.hmy, blockNum) { + return nil, ErrRequestedBlockTooHigh + } + + // Fetch delegation for block number + delegatorAddress := internal_common.ParseAddr(address) + blk, err := s.hmy.BlockByNumber(ctx, blockNum) + if err != nil { + return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNum) + } + validators, delegations := s.hmy.GetDelegationsByDelegatorByBlock(delegatorAddress, blk) + + // Format response + result := []StructuredResponse{} + for i := range delegations { + delegation := delegations[i] + undelegations := make([]Undelegation, len(delegation.Undelegations)) + + for j := range delegation.Undelegations { + undelegations[j] = Undelegation{ + Amount: delegation.Undelegations[j].Amount, + Epoch: delegation.Undelegations[j].Epoch, + } + } + valAddr, _ := internal_common.AddressToBech32(validators[i]) + delAddr, _ := internal_common.AddressToBech32(delegatorAddress) + + // Response output is the same for all versions + del, err := NewStructuredResponse(Delegation{ + ValidatorAddress: valAddr, + DelegatorAddress: delAddr, + Amount: delegation.Amount, + Reward: delegation.Reward, + Undelegations: undelegations, + }) + if err != nil { + return nil, err + } + result = append(result, del) + } + return result, nil +} + +// GetDelegationsByValidator returns list of delegations for a validator address. +func (s *PublicStakingService) GetDelegationsByValidator( + ctx context.Context, address string, +) ([]StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch delegations + validatorAddress := internal_common.ParseAddr(address) + delegations := s.hmy.GetDelegationsByValidator(validatorAddress) + + // Format response + result := []StructuredResponse{} + for i := range delegations { + delegation := delegations[i] + undelegations := []Undelegation{} + + for j := range delegation.Undelegations { + undelegations = append(undelegations, Undelegation{ + Amount: delegation.Undelegations[j].Amount, + Epoch: delegation.Undelegations[j].Epoch, + }) + } + valAddr, _ := internal_common.AddressToBech32(validatorAddress) + delAddr, _ := internal_common.AddressToBech32(delegation.DelegatorAddress) + + // Response output is the same for all versions + del, err := NewStructuredResponse(Delegation{ + ValidatorAddress: valAddr, + DelegatorAddress: delAddr, + Amount: delegation.Amount, + Reward: delegation.Reward, + Undelegations: undelegations, + }) + if err != nil { + return nil, err + } + result = append(result, del) + } + return result, nil +} + +// GetDelegationByDelegatorAndValidator returns a delegation for delegator and validator. +func (s *PublicStakingService) GetDelegationByDelegatorAndValidator( + ctx context.Context, address string, validator string, +) (StructuredResponse, error) { + if !isBeaconShard(s.hmy) { + return nil, ErrNotBeaconShard + } + + // Fetch delegations + delegatorAddress := internal_common.ParseAddr(address) + validatorAddress := internal_common.ParseAddr(validator) + validators, delegations := s.hmy.GetDelegationsByDelegator(delegatorAddress) + + // Format response + for i := range delegations { + if validators[i] != validatorAddress { + continue + } + delegation := delegations[i] + + undelegations := []Undelegation{} + + for j := range delegation.Undelegations { + undelegations = append(undelegations, Undelegation{ + Amount: delegation.Undelegations[j].Amount, + Epoch: delegation.Undelegations[j].Epoch, + }) + } + valAddr, _ := internal_common.AddressToBech32(validatorAddress) + delAddr, _ := internal_common.AddressToBech32(delegatorAddress) + + // Response output is the same for all versions + return NewStructuredResponse(Delegation{ + ValidatorAddress: valAddr, + DelegatorAddress: delAddr, + Amount: delegation.Amount, + Reward: delegation.Reward, + Undelegations: undelegations, + }) + } + return nil, nil +} + +func isBeaconShard(hmy *hmy.Harmony) bool { + return hmy.ShardID == shard.BeaconChainShardID +} diff --git a/rpc/transaction.go b/rpc/transaction.go new file mode 100644 index 000000000..59cf3234c --- /dev/null +++ b/rpc/transaction.go @@ -0,0 +1,747 @@ +package rpc + +import ( + "context" + "fmt" + "math/big" + "strings" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/core/rawdb" + "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/core/vm" + "github.com/harmony-one/harmony/hmy" + internal_common "github.com/harmony-one/harmony/internal/common" + "github.com/harmony-one/harmony/internal/params" + "github.com/harmony-one/harmony/internal/utils" + v1 "github.com/harmony-one/harmony/rpc/v1" + v2 "github.com/harmony-one/harmony/rpc/v2" + staking "github.com/harmony-one/harmony/staking/types" +) + +const ( + defaultPageSize = uint32(100) +) + +// PublicTransactionService provides an API to access Harmony's transaction service. +// It offers only methods that operate on public data that is freely available to anyone. +type PublicTransactionService struct { + hmy *hmy.Harmony + version Version +} + +// NewPublicTransactionAPI creates a new API for the RPC interface +func NewPublicTransactionAPI(hmy *hmy.Harmony, version Version) rpc.API { + return rpc.API{ + Namespace: version.Namespace(), + Version: APIVersion, + Service: &PublicTransactionService{hmy, version}, + Public: true, + } +} + +// GetAccountNonce returns the nonce value of the given address for the given block number +func (s *PublicTransactionService) GetAccountNonce( + ctx context.Context, address string, blockNumber BlockNumber, +) (uint64, error) { + // Process number based on version + blockNum := blockNumber.EthBlockNumber() + + // Response output is the same for all versions + addr := internal_common.ParseAddr(address) + return s.hmy.GetAccountNonce(ctx, addr, blockNum) +} + +// GetTransactionCount returns the number of transactions the given address has sent for the given block number. +// Legacy for apiv1. For apiv2, please use getAccountNonce/getPoolNonce/getTransactionsCount/getStakingTransactionsCount apis for +// more granular transaction counts queries +// Note that the return type is an interface to account for the different versions +func (s *PublicTransactionService) GetTransactionCount( + ctx context.Context, addr string, blockNumber BlockNumber, +) (response interface{}, err error) { + // Process arguments based on version + blockNum := blockNumber.EthBlockNumber() + address := internal_common.ParseAddr(addr) + + // Fetch transaction count + var nonce uint64 + if blockNum == rpc.PendingBlockNumber { + // Ask transaction pool for the nonce which includes pending transactions + nonce, err = s.hmy.GetPoolNonce(ctx, address) + if err != nil { + return nil, err + } + } else { + // Resolve block number and use its state to ask for the nonce + state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNum) + if err != nil { + return nil, err + } + if state == nil { + return nil, fmt.Errorf("state not found") + } + if state.Error() != nil { + return nil, state.Error() + } + nonce = state.GetNonce(address) + } + + // Format response according to version + switch s.version { + case V1: + return (hexutil.Uint64)(nonce), nil + case V2: + return nonce, nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetTransactionsCount returns the number of regular transactions from genesis of input type ("SENT", "RECEIVED", "ALL") +func (s *PublicTransactionService) GetTransactionsCount( + ctx context.Context, address, txType string, +) (count uint64, err error) { + if !strings.HasPrefix(address, "one1") { + // Handle hex address + addr := internal_common.ParseAddr(address) + address, err = internal_common.AddressToBech32(addr) + if err != nil { + return 0, err + } + } + + // Response output is the same for all versions + return s.hmy.GetTransactionsCount(address, txType) +} + +// GetStakingTransactionsCount returns the number of staking transactions from genesis of input type ("SENT", "RECEIVED", "ALL") +func (s *PublicTransactionService) GetStakingTransactionsCount( + ctx context.Context, address, txType string, +) (count uint64, err error) { + if !strings.HasPrefix(address, "one1") { + // Handle hex address + addr := internal_common.ParseAddr(address) + address, err = internal_common.AddressToBech32(addr) + if err != nil { + return 0, err + } + } + + // Response output is the same for all versions + return s.hmy.GetStakingTransactionsCount(address, txType) +} + +// EstimateGas returns an estimate of the amount of gas needed to execute the +// given transaction against the current pending block. +func (s *PublicTransactionService) EstimateGas( + ctx context.Context, args CallArgs, +) (hexutil.Uint64, error) { + gas, err := doEstimateGas(ctx, s.hmy, args, nil) + if err != nil { + return 0, err + } + + // Response output is the same for all versions + return (hexutil.Uint64)(gas), nil +} + +// GetTransactionByHash returns the plain transaction for the given hash +func (s *PublicTransactionService) GetTransactionByHash( + ctx context.Context, hash common.Hash, +) (StructuredResponse, error) { + // Try to return an already finalized transaction + tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.hmy.ChainDb(), hash) + if tx == nil { + utils.Logger().Debug(). + Err(errors.Wrapf(ErrTransactionNotFound, "hash %v", hash.String())). + Msgf("%v error at %v", LogTag, "GetTransactionByHash") + // Legacy behavior is to not return RPC errors + return nil, nil + } + block, err := s.hmy.GetBlock(ctx, blockHash) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetTransactionByHash") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + // Format the response according to the version + switch s.version { + case V1: + tx, err := v1.NewTransaction(tx, blockHash, blockNumber, block.Time().Uint64(), index) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + case V2: + tx, err := v2.NewTransaction(tx, blockHash, blockNumber, block.Time().Uint64(), index) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetStakingTransactionByHash returns the staking transaction for the given hash +func (s *PublicTransactionService) GetStakingTransactionByHash( + ctx context.Context, hash common.Hash, +) (StructuredResponse, error) { + // Try to return an already finalized transaction + stx, blockHash, blockNumber, index := rawdb.ReadStakingTransaction(s.hmy.ChainDb(), hash) + if stx == nil { + utils.Logger().Debug(). + Err(errors.Wrapf(ErrTransactionNotFound, "hash %v", hash.String())). + Msgf("%v error at %v", LogTag, "GetStakingTransactionByHash") + // Legacy behavior is to not return RPC errors + return nil, nil + } + block, err := s.hmy.GetBlock(ctx, blockHash) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetStakingTransactionByHash") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + switch s.version { + case V1: + tx, err := v1.NewStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + case V2: + tx, err := v2.NewStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetTransactionsHistory returns the list of transactions hashes that involve a particular address. +func (s *PublicTransactionService) GetTransactionsHistory( + ctx context.Context, args TxHistoryArgs, +) (StructuredResponse, error) { + // Fetch transaction history + var address string + var result []common.Hash + var err error + if strings.HasPrefix(args.Address, "one1") { + address = args.Address + } else { + addr := internal_common.ParseAddr(args.Address) + address, err = internal_common.AddressToBech32(addr) + if err != nil { + return nil, err + } + } + hashes, err := s.hmy.GetTransactionsHistory(address, args.TxType, args.Order) + if err != nil { + return nil, err + } + + result = returnHashesWithPagination(hashes, args.PageIndex, args.PageSize) + + // Just hashes have same response format for all versions + if !args.FullTx { + return StructuredResponse{"transactions": result}, nil + } + + // Full transactions have different response format + txs := []StructuredResponse{} + for _, hash := range result { + tx, err := s.GetTransactionByHash(ctx, hash) + if err == nil { + txs = append(txs, tx) + } else { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetTransactionsHistory") + // Legacy behavior is to not return RPC errors + } + } + return StructuredResponse{"transactions": txs}, nil +} + +// GetStakingTransactionsHistory returns the list of transactions hashes that involve a particular address. +func (s *PublicTransactionService) GetStakingTransactionsHistory( + ctx context.Context, args TxHistoryArgs, +) (StructuredResponse, error) { + // Fetch transaction history + var address string + var result []common.Hash + var err error + if strings.HasPrefix(args.Address, "one1") { + address = args.Address + } else { + addr := internal_common.ParseAddr(args.Address) + address, err = internal_common.AddressToBech32(addr) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetStakingTransactionsHistory") + // Legacy behavior is to not return RPC errors + return nil, nil + } + } + hashes, err := s.hmy.GetStakingTransactionsHistory(address, args.TxType, args.Order) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetStakingTransactionsHistory") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + result = returnHashesWithPagination(hashes, args.PageIndex, args.PageSize) + + // Just hashes have same response format for all versions + if !args.FullTx { + return StructuredResponse{"staking_transactions": result}, nil + } + + // Full transactions have different response format + txs := []StructuredResponse{} + for _, hash := range result { + tx, err := s.GetStakingTransactionByHash(ctx, hash) + if err == nil { + txs = append(txs, tx) + } else { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetStakingTransactionsHistory") + // Legacy behavior is to not return RPC errors + } + } + return StructuredResponse{"staking_transactions": txs}, nil +} + +// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. +// Note that the return type is an interface to account for the different versions +func (s *PublicTransactionService) GetBlockTransactionCountByNumber( + ctx context.Context, blockNumber BlockNumber, +) (interface{}, error) { + // Process arguments based on version + blockNum := blockNumber.EthBlockNumber() + + // Fetch block + block, err := s.hmy.BlockByNumber(ctx, blockNum) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetBlockTransactionCountByNumber") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + // Format response according to version + switch s.version { + case V1: + return hexutil.Uint(len(block.Transactions())), nil + case V2: + return len(block.Transactions()), nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash. +// Note that the return type is an interface to account for the different versions +func (s *PublicTransactionService) GetBlockTransactionCountByHash( + ctx context.Context, blockHash common.Hash, +) (interface{}, error) { + // Fetch block + block, err := s.hmy.GetBlock(ctx, blockHash) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetBlockTransactionCountByHash") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + // Format response according to version + switch s.version { + case V1: + return hexutil.Uint(len(block.Transactions())), nil + case V2: + return len(block.Transactions()), nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. +func (s *PublicTransactionService) GetTransactionByBlockNumberAndIndex( + ctx context.Context, blockNumber BlockNumber, index TransactionIndex, +) (StructuredResponse, error) { + // Process arguments based on version + blockNum := blockNumber.EthBlockNumber() + + // Fetch Block + block, err := s.hmy.BlockByNumber(ctx, blockNum) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetTransactionByBlockNumberAndIndex") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + // Format response according to version + switch s.version { + case V1: + tx, err := v1.NewTransactionFromBlockIndex(block, uint64(index)) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + case V2: + tx, err := v2.NewTransactionFromBlockIndex(block, uint64(index)) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. +func (s *PublicTransactionService) GetTransactionByBlockHashAndIndex( + ctx context.Context, blockHash common.Hash, index TransactionIndex, +) (StructuredResponse, error) { + // Fetch Block + block, err := s.hmy.GetBlock(ctx, blockHash) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetTransactionByBlockHashAndIndex") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + // Format response according to version + switch s.version { + case V1: + tx, err := v1.NewTransactionFromBlockIndex(block, uint64(index)) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + case V2: + tx, err := v2.NewTransactionFromBlockIndex(block, uint64(index)) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetBlockStakingTransactionCountByNumber returns the number of staking transactions in the block with the given block number. +// Note that the return type is an interface to account for the different versions +func (s *PublicTransactionService) GetBlockStakingTransactionCountByNumber( + ctx context.Context, blockNumber BlockNumber, +) (interface{}, error) { + // Process arguments based on version + blockNum := blockNumber.EthBlockNumber() + + // Fetch block + block, err := s.hmy.BlockByNumber(ctx, blockNum) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetBlockStakingTransactionCountByNumber") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + // Format response according to version + switch s.version { + case V1: + return hexutil.Uint(len(block.StakingTransactions())), nil + case V2: + return len(block.StakingTransactions()), nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetBlockStakingTransactionCountByHash returns the number of staking transactions in the block with the given hash. +// Note that the return type is an interface to account for the different versions +func (s *PublicTransactionService) GetBlockStakingTransactionCountByHash( + ctx context.Context, blockHash common.Hash, +) (interface{}, error) { + // Fetch block + block, err := s.hmy.GetBlock(ctx, blockHash) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetBlockStakingTransactionCountByHash") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + // Format response according to version + switch s.version { + case V1: + return hexutil.Uint(len(block.StakingTransactions())), nil + case V2: + return len(block.StakingTransactions()), nil + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetStakingTransactionByBlockNumberAndIndex returns the staking transaction for the given block number and index. +func (s *PublicTransactionService) GetStakingTransactionByBlockNumberAndIndex( + ctx context.Context, blockNumber BlockNumber, index TransactionIndex, +) (StructuredResponse, error) { + // Process arguments based on version + blockNum := blockNumber.EthBlockNumber() + + // Fetch Block + block, err := s.hmy.BlockByNumber(ctx, blockNum) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetStakingTransactionByBlockNumberAndIndex") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + // Format response according to version + switch s.version { + case V1: + tx, err := v1.NewStakingTransactionFromBlockIndex(block, uint64(index)) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + case V2: + tx, err := v2.NewStakingTransactionFromBlockIndex(block, uint64(index)) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetStakingTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. +func (s *PublicTransactionService) GetStakingTransactionByBlockHashAndIndex( + ctx context.Context, blockHash common.Hash, index TransactionIndex, +) (StructuredResponse, error) { + // Fetch Block + block, err := s.hmy.GetBlock(ctx, blockHash) + if err != nil { + utils.Logger().Debug(). + Err(err). + Msgf("%v error at %v", LogTag, "GetStakingTransactionByBlockHashAndIndex") + // Legacy behavior is to not return RPC errors + return nil, nil + } + + // Format response according to version + switch s.version { + case V1: + tx, err := v1.NewStakingTransactionFromBlockIndex(block, uint64(index)) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + case V2: + tx, err := v2.NewStakingTransactionFromBlockIndex(block, uint64(index)) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetTransactionReceipt returns the transaction receipt for the given transaction hash. +func (s *PublicTransactionService) GetTransactionReceipt( + ctx context.Context, hash common.Hash, +) (StructuredResponse, error) { + // Fetch receipt for plain & staking transaction + var tx *types.Transaction + var stx *staking.StakingTransaction + var blockHash common.Hash + var blockNumber, index uint64 + tx, blockHash, blockNumber, index = rawdb.ReadTransaction(s.hmy.ChainDb(), hash) + if tx == nil { + stx, blockHash, blockNumber, index = rawdb.ReadStakingTransaction(s.hmy.ChainDb(), hash) + if stx == nil { + return nil, nil + } + } + receipts, err := s.hmy.GetReceipts(ctx, blockHash) + if err != nil { + return nil, err + } + if len(receipts) <= int(index) { + return nil, nil + } + receipt := receipts[index] + + // Format response according to version + var RPCReceipt interface{} + switch s.version { + case V1: + if tx == nil { + RPCReceipt, err = v1.NewReceipt(stx, blockHash, blockNumber, index, receipt) + } else { + RPCReceipt, err = v1.NewReceipt(tx, blockHash, blockNumber, index, receipt) + } + if err != nil { + return nil, err + } + return NewStructuredResponse(RPCReceipt) + case V2: + if tx == nil { + RPCReceipt, err = v2.NewReceipt(stx, blockHash, blockNumber, index, receipt) + } else { + RPCReceipt, err = v2.NewReceipt(tx, blockHash, blockNumber, index, receipt) + } + if err != nil { + return nil, err + } + return NewStructuredResponse(RPCReceipt) + default: + return nil, ErrUnknownRPCVersion + } +} + +// GetCXReceiptByHash returns the transaction for the given hash +func (s *PublicTransactionService) GetCXReceiptByHash( + ctx context.Context, hash common.Hash, +) (StructuredResponse, error) { + if cx, blockHash, blockNumber, _ := rawdb.ReadCXReceipt(s.hmy.ChainDb(), hash); cx != nil { + // Format response according to version + switch s.version { + case V1: + tx, err := v1.NewCxReceipt(cx, blockHash, blockNumber) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + case V2: + tx, err := v2.NewCxReceipt(cx, blockHash, blockNumber) + if err != nil { + return nil, err + } + return NewStructuredResponse(tx) + default: + return nil, ErrUnknownRPCVersion + } + } + utils.Logger().Debug(). + Err(fmt.Errorf("unable to found CX receipt for tx %v", hash.String())). + Msgf("%v error at %v", LogTag, "GetCXReceiptByHash") + return nil, nil // Legacy behavior is to not return an error here +} + +// ResendCx requests that the egress receipt for the given cross-shard +// transaction be sent to the destination shard for credit. This is used for +// unblocking a half-complete cross-shard transaction whose fund has been +// withdrawn already from the source shard but not credited yet in the +// destination account due to transient failures. +func (s *PublicTransactionService) ResendCx(ctx context.Context, txID common.Hash) (bool, error) { + _, success := s.hmy.ResendCx(ctx, txID) + + // Response output is the same for all versions + return success, nil +} + +// returnHashesWithPagination returns result with pagination (offset, page in TxHistoryArgs). +func returnHashesWithPagination(hashes []common.Hash, pageIndex uint32, pageSize uint32) []common.Hash { + size := defaultPageSize + if pageSize > 0 { + size = pageSize + } + if uint64(size)*uint64(pageIndex) >= uint64(len(hashes)) { + return make([]common.Hash, 0) + } + if uint64(size)*uint64(pageIndex)+uint64(size) > uint64(len(hashes)) { + return hashes[size*pageIndex:] + } + return hashes[size*pageIndex : size*pageIndex+size] +} + +// doEstimateGas .. +func doEstimateGas( + ctx context.Context, hmy *hmy.Harmony, args CallArgs, gasCap *big.Int, +) (uint64, error) { + // Binary search the gas requirement, as it may be higher than the amount used + var ( + lo = params.TxGas - 1 + hi uint64 + max uint64 + ) + blockNum := rpc.LatestBlockNumber + if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { + hi = uint64(*args.Gas) + } else { + // Retrieve the blk to act as the gas ceiling + blk, err := hmy.BlockByNumber(ctx, blockNum) + if err != nil { + return 0, err + } + hi = blk.GasLimit() + } + if gasCap != nil && hi > gasCap.Uint64() { + hi = gasCap.Uint64() + } + max = hi + + // Use zero-address if none other is available + if args.From == nil { + args.From = &common.Address{} + } + + // Create a helper to check if a gas allowance results in an executable transaction + executable := func(gas uint64) bool { + args.Gas = (*hexutil.Uint64)(&gas) + + _, _, failed, err := doCall(ctx, hmy, args, blockNum, vm.Config{}, 0, big.NewInt(int64(max))) + if err != nil || failed { + return false + } + return true + } + + // Execute the binary search and hone in on an executable gas limit + for lo+1 < hi { + mid := (hi + lo) / 2 + if !executable(mid) { + lo = mid + } else { + hi = mid + } + } + + // Reject the transaction as invalid if it still fails at the highest allowance + if hi == max { + if !executable(hi) { + return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", max) + } + } + return hi, nil +} diff --git a/rpc/types.go b/rpc/types.go new file mode 100644 index 000000000..f4a75aec9 --- /dev/null +++ b/rpc/types.go @@ -0,0 +1,200 @@ +package rpc + +import ( + "encoding/hex" + "encoding/json" + "math/big" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/block" + "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/numeric" + "github.com/harmony-one/harmony/shard" +) + +// CallArgs represents the arguments for a call. +type CallArgs struct { + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"data"` +} + +// StakingNetworkInfo returns global staking info. +type StakingNetworkInfo struct { + TotalSupply numeric.Dec `json:"total-supply"` + CirculatingSupply numeric.Dec `json:"circulating-supply"` + EpochLastBlock uint64 `json:"epoch-last-block"` + TotalStaking *big.Int `json:"total-staking"` + MedianRawStake numeric.Dec `json:"median-raw-stake"` +} + +// Delegation represents a particular delegation to a validator +type Delegation struct { + ValidatorAddress string `json:"validator_address"` + DelegatorAddress string `json:"delegator_address"` + Amount *big.Int `json:"amount"` + Reward *big.Int `json:"reward"` + Undelegations []Undelegation `json:"Undelegations"` +} + +// Undelegation represents one undelegation entry +type Undelegation struct { + Amount *big.Int + Epoch *big.Int +} + +// StructuredResponse type of RPCs +type StructuredResponse = map[string]interface{} + +// NewStructuredResponse creates a structured response from the given input +func NewStructuredResponse(input interface{}) (StructuredResponse, error) { + var objMap StructuredResponse + dat, err := json.Marshal(input) + if err != nil { + return nil, err + } + if err := json.Unmarshal(dat, &objMap); err != nil { + return nil, err + } + return objMap, nil +} + +// BlockNumber .. +type BlockNumber rpc.BlockNumber + +// UnmarshalJSON converts a hex string or integer to a block number +func (bn *BlockNumber) UnmarshalJSON(data []byte) error { + baseBn := rpc.BlockNumber(0) + baseErr := baseBn.UnmarshalJSON(data) + if baseErr != nil { + input := strings.TrimSpace(string(data)) + if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] + } + input = strings.TrimPrefix(input, "0x") + num, err := strconv.ParseInt(input, 10, 64) + if err != nil { + return err + } + *bn = BlockNumber(num) + return nil + } + *bn = BlockNumber(baseBn) + return nil +} + +// Int64 .. +func (bn BlockNumber) Int64() int64 { + return (int64)(bn) +} + +// EthBlockNumber .. +func (bn BlockNumber) EthBlockNumber() rpc.BlockNumber { + return (rpc.BlockNumber)(bn) +} + +// TransactionIndex .. +type TransactionIndex uint64 + +// UnmarshalJSON converts a hex string or integer to a Transaction index +func (i *TransactionIndex) UnmarshalJSON(data []byte) (err error) { + input := strings.TrimSpace(string(data)) + if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] + } + + var num int64 + if strings.HasPrefix(input, "0x") { + num, err = strconv.ParseInt(strings.TrimPrefix(input, "0x"), 16, 64) + } else { + num, err = strconv.ParseInt(input, 10, 64) + } + if err != nil { + return err + } + + *i = TransactionIndex(num) + return nil +} + +// TxHistoryArgs is struct to include optional transaction formatting params. +type TxHistoryArgs struct { + Address string `json:"address"` + PageIndex uint32 `json:"pageIndex"` + PageSize uint32 `json:"pageSize"` + FullTx bool `json:"fullTx"` + TxType string `json:"txType"` + Order string `json:"order"` +} + +// UnmarshalFromInterface .. +func (ta *TxHistoryArgs) UnmarshalFromInterface(blockArgs interface{}) error { + var args TxHistoryArgs + dat, err := json.Marshal(blockArgs) + if err != nil { + return err + } + if err := json.Unmarshal(dat, &args); err != nil { + return err + } + *ta = args + return nil +} + +// HeaderInformation represents the latest consensus information +type HeaderInformation struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber uint64 `json:"blockNumber"` + ShardID uint32 `json:"shardID"` + Leader string `json:"leader"` + ViewID uint64 `json:"viewID"` + Epoch uint64 `json:"epoch"` + Timestamp string `json:"timestamp"` + UnixTime uint64 `json:"unixtime"` + LastCommitSig string `json:"lastCommitSig"` + LastCommitBitmap string `json:"lastCommitBitmap"` + CrossLinks *types.CrossLinks `json:"crossLinks,omitempty"` +} + +// NewHeaderInformation returns the header information that will serialize to the RPC representation. +func NewHeaderInformation(header *block.Header, leader string) *HeaderInformation { + if header == nil { + return nil + } + + result := &HeaderInformation{ + BlockHash: header.Hash(), + BlockNumber: header.Number().Uint64(), + ShardID: header.ShardID(), + Leader: leader, + ViewID: header.ViewID().Uint64(), + Epoch: header.Epoch().Uint64(), + UnixTime: header.Time().Uint64(), + Timestamp: time.Unix(header.Time().Int64(), 0).UTC().String(), + LastCommitBitmap: hex.EncodeToString(header.LastCommitBitmap()), + } + + sig := header.LastCommitSignature() + result.LastCommitSig = hex.EncodeToString(sig[:]) + + if header.ShardID() == shard.BeaconChainShardID { + decodedCrossLinks := &types.CrossLinks{} + err := rlp.DecodeBytes(header.CrossLinks(), decodedCrossLinks) + if err != nil { + result.CrossLinks = &types.CrossLinks{} + } else { + result.CrossLinks = decodedCrossLinks + } + } + + return result +} diff --git a/rpc/v1/legacy.go b/rpc/v1/legacy.go new file mode 100644 index 000000000..4e0bf413a --- /dev/null +++ b/rpc/v1/legacy.go @@ -0,0 +1,40 @@ +package v1 + +import ( + "context" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/hmy" + internal_common "github.com/harmony-one/harmony/internal/common" +) + +// PublicLegacyService provides an API to access the Harmony blockchain. +// Services here are legacy methods, specific to the V1 RPC that can be deprecated in the future. +type PublicLegacyService struct { + hmy *hmy.Harmony +} + +// NewPublicLegacyAPI creates a new API for the RPC interface +func NewPublicLegacyAPI(hmy *hmy.Harmony) rpc.API { + return rpc.API{ + Namespace: "hmy", + Version: "1.0", + Service: &PublicLegacyService{hmy}, + Public: true, + } +} + +// GetBalance returns the amount of Atto for the given address in the state of the +// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta +// block numbers are also allowed. +func (s *PublicLegacyService) GetBalance( + ctx context.Context, address string, blockNr rpc.BlockNumber, +) (*hexutil.Big, error) { + addr := internal_common.ParseAddr(address) + balance, err := s.hmy.GetBalance(ctx, addr, blockNr) + if err != nil { + return nil, err + } + return (*hexutil.Big)(balance), nil +} diff --git a/rpc/v1/types.go b/rpc/v1/types.go new file mode 100644 index 000000000..f5304c7f4 --- /dev/null +++ b/rpc/v1/types.go @@ -0,0 +1,735 @@ +package v1 + +import ( + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/crypto/bls" + internal_common "github.com/harmony-one/harmony/internal/common" + rpc_common "github.com/harmony-one/harmony/rpc/common" + staking "github.com/harmony-one/harmony/staking/types" +) + +// BlockWithTxHash represents a block that will serialize to the RPC representation of a block +// having ONLY transaction hashes in the Transaction & Staking transaction fields. +type BlockWithTxHash struct { + Number *hexutil.Big `json:"number"` + ViewID *hexutil.Big `json:"viewID"` + Epoch *hexutil.Big `json:"epoch"` + Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash"` + Nonce uint64 `json:"nonce"` + MixHash common.Hash `json:"mixHash"` + LogsBloom ethtypes.Bloom `json:"logsBloom"` + StateRoot common.Hash `json:"stateRoot"` + Miner string `json:"miner"` + Difficulty uint64 `json:"difficulty"` + ExtraData hexutil.Bytes `json:"extraData"` + Size hexutil.Uint64 `json:"size"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + Timestamp hexutil.Uint64 `json:"timestamp"` + TransactionsRoot common.Hash `json:"transactionsRoot"` + ReceiptsRoot common.Hash `json:"receiptsRoot"` + Uncles []common.Hash `json:"uncles"` + Transactions []common.Hash `json:"transactions"` + StakingTxs []common.Hash `json:"stakingTransactions"` + Signers []string `json:"signers,omitempty"` +} + +// BlockWithFullTx represents a block that will serialize to the RPC representation of a block +// having FULL transactions in the Transaction & Staking transaction fields. +type BlockWithFullTx struct { + Number *hexutil.Big `json:"number"` + ViewID *hexutil.Big `json:"viewID"` + Epoch *hexutil.Big `json:"epoch"` + Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash"` + Nonce uint64 `json:"nonce"` + MixHash common.Hash `json:"mixHash"` + LogsBloom ethtypes.Bloom `json:"logsBloom"` + StateRoot common.Hash `json:"stateRoot"` + Miner string `json:"miner"` + Difficulty uint64 `json:"difficulty"` + ExtraData hexutil.Bytes `json:"extraData"` + Size hexutil.Uint64 `json:"size"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + Timestamp hexutil.Uint64 `json:"timestamp"` + TransactionsRoot common.Hash `json:"transactionsRoot"` + ReceiptsRoot common.Hash `json:"receiptsRoot"` + Uncles []common.Hash `json:"uncles"` + Transactions []*Transaction `json:"transactions"` + StakingTxs []*StakingTransaction `json:"stakingTransactions"` + Signers []string `json:"signers,omitempty"` +} + +// Transaction represents a transaction that will serialize to the RPC representation of a transaction +type Transaction struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From string `json:"from"` + Timestamp hexutil.Uint64 `json:"timestamp"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To string `json:"to"` + TransactionIndex hexutil.Uint `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + ShardID uint32 `json:"shardID"` + ToShardID uint32 `json:"toShardID"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` +} + +// StakingTransaction represents a staking transaction that will serialize to the +// RPC representation of a staking transaction +type StakingTransaction struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From string `json:"from"` + Timestamp hexutil.Uint64 `json:"timestamp"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Nonce hexutil.Uint64 `json:"nonce"` + TransactionIndex hexutil.Uint `json:"transactionIndex"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` + Type string `json:"type"` + Msg interface{} `json:"msg"` +} + +// CreateValidatorMsg represents a staking transaction's create validator directive that +// will serialize to the RPC representation +type CreateValidatorMsg struct { + ValidatorAddress string `json:"validatorAddress"` + CommissionRate *hexutil.Big `json:"commissionRate"` + MaxCommissionRate *hexutil.Big `json:"maxCommissionRate"` + MaxChangeRate *hexutil.Big `json:"maxChangeRate"` + MinSelfDelegation *hexutil.Big `json:"minSelfDelegation"` + MaxTotalDelegation *hexutil.Big `json:"maxTotalDelegation"` + Amount *hexutil.Big `json:"amount"` + Name string `json:"name"` + Website string `json:"website"` + Identity string `json:"identity"` + SecurityContact string `json:"securityContact"` + Details string `json:"details"` + SlotPubKeys []bls.SerializedPublicKey `json:"slotPubKeys"` +} + +// EditValidatorMsg represents a staking transaction's edit validator directive that +// will serialize to the RPC representation +type EditValidatorMsg struct { + ValidatorAddress string `json:"validatorAddress"` + CommissionRate *hexutil.Big `json:"commissionRate"` + MinSelfDelegation *hexutil.Big `json:"minSelfDelegation"` + MaxTotalDelegation *hexutil.Big `json:"maxTotalDelegation"` + Name string `json:"name"` + Website string `json:"website"` + Identity string `json:"identity"` + SecurityContact string `json:"securityContact"` + Details string `json:"details"` + SlotPubKeyToAdd *bls.SerializedPublicKey `json:"slotPubKeyToAdd"` + SlotPubKeyToRemove *bls.SerializedPublicKey `json:"slotPubKeyToRemove"` +} + +// CollectRewardsMsg represents a staking transaction's collect rewards directive that +// will serialize to the RPC representation +type CollectRewardsMsg struct { + DelegatorAddress string `json:"delegatorAddress"` +} + +// DelegateMsg represents a staking transaction's delegate directive that +// will serialize to the RPC representation +type DelegateMsg struct { + DelegatorAddress string `json:"delegatorAddress"` + ValidatorAddress string `json:"validatorAddress"` + Amount *hexutil.Big `json:"amount"` +} + +// UndelegateMsg represents a staking transaction's delegate directive that +// will serialize to the RPC representation +type UndelegateMsg struct { + DelegatorAddress string `json:"delegatorAddress"` + ValidatorAddress string `json:"validatorAddress"` + Amount *hexutil.Big `json:"amount"` +} + +// TxReceipt represents a transaction receipt that will serialize to the RPC representation. +type TxReceipt struct { + BlockHash common.Hash `json:"blockHash"` + TransactionHash common.Hash `json:"transactionHash"` + BlockNumber hexutil.Uint64 `json:"blockNumber"` + TransactionIndex hexutil.Uint64 `json:"transactionIndex"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed"` + ContractAddress common.Address `json:"contractAddress"` + Logs []*types.Log `json:"logs"` + LogsBloom ethtypes.Bloom `json:"logsBloom"` + ShardID uint32 `json:"shardID"` + From string `json:"from"` + To string `json:"to"` + Root hexutil.Bytes `json:"root,omitempty"` + Status hexutil.Uint `json:"status,omitempty"` +} + +// StakingTxReceipt represents a staking transaction receipt that will serialize to the RPC representation. +type StakingTxReceipt struct { + BlockHash common.Hash `json:"blockHash"` + TransactionHash common.Hash `json:"transactionHash"` + BlockNumber hexutil.Uint64 `json:"blockNumber"` + TransactionIndex hexutil.Uint64 `json:"transactionIndex"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed"` + ContractAddress common.Address `json:"contractAddress"` + Logs []*types.Log `json:"logs"` + LogsBloom ethtypes.Bloom `json:"logsBloom"` + Sender string `json:"sender"` + Type staking.Directive `json:"type"` + Root hexutil.Bytes `json:"root,omitempty"` + Status hexutil.Uint `json:"status,omitempty"` +} + +// CxReceipt represents a CxReceipt that will serialize to the RPC representation of a CxReceipt +type CxReceipt struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + TxHash common.Hash `json:"hash"` + From string `json:"from"` + To string `json:"to"` + ShardID uint32 `json:"shardID"` + ToShardID uint32 `json:"toShardID"` + Amount *hexutil.Big `json:"value"` +} + +// NewCxReceipt returns a CxReceipt that will serialize to the RPC representation +func NewCxReceipt(cx *types.CXReceipt, blockHash common.Hash, blockNumber uint64) (*CxReceipt, error) { + result := &CxReceipt{ + BlockHash: blockHash, + TxHash: cx.TxHash, + Amount: (*hexutil.Big)(cx.Amount), + ShardID: cx.ShardID, + ToShardID: cx.ToShardID, + } + if blockHash != (common.Hash{}) { + result.BlockHash = blockHash + result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) + } + + fromAddr, err := internal_common.AddressToBech32(cx.From) + if err != nil { + return nil, err + } + toAddr := "" + if cx.To != nil { + if toAddr, err = internal_common.AddressToBech32(*cx.To); err != nil { + return nil, err + } + } + result.From = fromAddr + result.To = toAddr + + return result, nil +} + +// NewTransaction returns a transaction that will serialize to the RPC +// representation, with the given location metadata set (if available). +// Note that all txs on Harmony are replay protected (post EIP155 epoch). +func NewTransaction( + tx *types.Transaction, blockHash common.Hash, + blockNumber uint64, timestamp uint64, index uint64, +) (*Transaction, error) { + from, err := tx.SenderAddress() + if err != nil { + return nil, err + } + v, r, s := tx.RawSignatureValues() + + result := &Transaction{ + Gas: hexutil.Uint64(tx.Gas()), + GasPrice: (*hexutil.Big)(tx.GasPrice()), + Hash: tx.Hash(), + Input: hexutil.Bytes(tx.Data()), + Nonce: hexutil.Uint64(tx.Nonce()), + Value: (*hexutil.Big)(tx.Value()), + ShardID: tx.ShardID(), + ToShardID: tx.ToShardID(), + Timestamp: hexutil.Uint64(timestamp), + V: (*hexutil.Big)(v), + R: (*hexutil.Big)(r), + S: (*hexutil.Big)(s), + } + if blockHash != (common.Hash{}) { + result.BlockHash = blockHash + result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) + result.TransactionIndex = hexutil.Uint(index) + } + + fromAddr, err := internal_common.AddressToBech32(from) + if err != nil { + return nil, err + } + toAddr := "" + + if tx.To() != nil { + if toAddr, err = internal_common.AddressToBech32(*tx.To()); err != nil { + return nil, err + } + result.From = fromAddr + } else { + result.From = strings.ToLower(from.Hex()) + } + result.To = toAddr + + return result, nil +} + +// NewReceipt returns a transaction OR staking transaction that will serialize to the RPC +// representation. +func NewReceipt( + tx interface{}, blockHash common.Hash, blockNumber, blockIndex uint64, receipt *types.Receipt, +) (interface{}, error) { + plainTx, ok := tx.(*types.Transaction) + if ok { + return NewTxReceipt(plainTx, blockHash, blockIndex, blockNumber, receipt) + } + stakingTx, ok := tx.(*staking.StakingTransaction) + if ok { + return NewStakingTxReceipt(stakingTx, blockHash, blockIndex, blockNumber, receipt) + } + return nil, fmt.Errorf("unknown transaction type for RPC receipt") +} + +// NewTxReceipt returns a transaction receipt that will serialize to the RPC representation +func NewTxReceipt( + tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64, receipt *types.Receipt, +) (*TxReceipt, error) { + // Set correct to & from address + senderAddr, err := tx.SenderAddress() + if err != nil { + return nil, err + } + var sender, receiver string + if tx.To() == nil { + // Handle response type for contract receipts + sender = senderAddr.String() + receiver = "" + } else { + // Handle response type for regular transaction + sender, err = internal_common.AddressToBech32(senderAddr) + if err != nil { + return nil, err + } + receiver, err = internal_common.AddressToBech32(*tx.To()) + if err != nil { + return nil, err + } + } + + // Declare receipt + txReceipt := &TxReceipt{ + BlockHash: blockHash, + TransactionHash: tx.Hash(), + BlockNumber: hexutil.Uint64(blockNumber), + TransactionIndex: hexutil.Uint64(blockIndex), + GasUsed: hexutil.Uint64(receipt.GasUsed), + CumulativeGasUsed: hexutil.Uint64(receipt.CumulativeGasUsed), + Logs: receipt.Logs, + LogsBloom: receipt.Bloom, + ShardID: tx.ShardID(), + From: sender, + To: receiver, + } + + // Set optionals + if len(receipt.PostState) > 0 { + txReceipt.Root = receipt.PostState + } else { + txReceipt.Status = hexutil.Uint(receipt.Status) + } + + // Set empty array for empty logs + if receipt.Logs == nil { + txReceipt.Logs = []*types.Log{} + } + + // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation + if receipt.ContractAddress != (common.Address{}) { + txReceipt.ContractAddress = receipt.ContractAddress + } + return txReceipt, nil +} + +// NewStakingTxReceipt returns a staking transaction receipt that will serialize to the RPC representation +func NewStakingTxReceipt( + tx *staking.StakingTransaction, blockHash common.Hash, blockNumber, blockIndex uint64, receipt *types.Receipt, +) (*StakingTxReceipt, error) { + // Set correct sender + senderAddr, err := tx.SenderAddress() + if err != nil { + return nil, err + } + sender, err := internal_common.AddressToBech32(senderAddr) + if err != nil { + return nil, err + } + + // Declare receipt + txReceipt := &StakingTxReceipt{ + BlockHash: blockHash, + TransactionHash: tx.Hash(), + BlockNumber: hexutil.Uint64(blockNumber), + TransactionIndex: hexutil.Uint64(blockIndex), + GasUsed: hexutil.Uint64(receipt.GasUsed), + CumulativeGasUsed: hexutil.Uint64(receipt.CumulativeGasUsed), + Logs: receipt.Logs, + LogsBloom: receipt.Bloom, + Sender: sender, + Type: tx.StakingType(), + } + + // Set optionals + if len(receipt.PostState) > 0 { + txReceipt.Root = receipt.PostState + } else { + txReceipt.Status = hexutil.Uint(receipt.Status) + } + + // Set empty array for empty logs + if receipt.Logs == nil { + txReceipt.Logs = []*types.Log{} + } + + // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation + if receipt.ContractAddress != (common.Address{}) { + txReceipt.ContractAddress = receipt.ContractAddress + } + return txReceipt, nil +} + +// NewStakingTransaction returns a staking transaction that will serialize to the RPC +// representation, with the given location metadata set (if available). +func NewStakingTransaction(tx *staking.StakingTransaction, blockHash common.Hash, + blockNumber uint64, timestamp uint64, index uint64, +) (*StakingTransaction, error) { + from, err := tx.SenderAddress() + if err != nil { + return nil, err + } + v, r, s := tx.RawSignatureValues() + + var rpcMsg interface{} + switch tx.StakingType() { + case staking.DirectiveCreateValidator: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCreateValidator) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.CreateValidator) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) + if err != nil { + return nil, err + } + rpcMsg = &CreateValidatorMsg{ + ValidatorAddress: validatorAddress, + CommissionRate: (*hexutil.Big)(msg.CommissionRates.Rate.Int), + MaxCommissionRate: (*hexutil.Big)(msg.CommissionRates.MaxRate.Int), + MaxChangeRate: (*hexutil.Big)(msg.CommissionRates.MaxChangeRate.Int), + MinSelfDelegation: (*hexutil.Big)(msg.MinSelfDelegation), + MaxTotalDelegation: (*hexutil.Big)(msg.MaxTotalDelegation), + Amount: (*hexutil.Big)(msg.Amount), + Name: msg.Description.Name, + Website: msg.Description.Website, + Identity: msg.Description.Identity, + SecurityContact: msg.Description.SecurityContact, + Details: msg.Description.Details, + SlotPubKeys: msg.SlotPubKeys, + } + case staking.DirectiveEditValidator: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveEditValidator) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.EditValidator) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) + if err != nil { + return nil, err + } + // Edit validators txs need not have commission rates to edit + commissionRate := &hexutil.Big{} + if msg.CommissionRate != nil { + commissionRate = (*hexutil.Big)(msg.CommissionRate.Int) + } + rpcMsg = &EditValidatorMsg{ + ValidatorAddress: validatorAddress, + CommissionRate: commissionRate, + MinSelfDelegation: (*hexutil.Big)(msg.MinSelfDelegation), + MaxTotalDelegation: (*hexutil.Big)(msg.MaxTotalDelegation), + Name: msg.Description.Name, + Website: msg.Description.Website, + Identity: msg.Description.Identity, + SecurityContact: msg.Description.SecurityContact, + Details: msg.Description.Details, + SlotPubKeyToAdd: msg.SlotKeyToAdd, + SlotPubKeyToRemove: msg.SlotKeyToRemove, + } + case staking.DirectiveCollectRewards: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCollectRewards) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.CollectRewards) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) + if err != nil { + return nil, err + } + rpcMsg = &CollectRewardsMsg{DelegatorAddress: delegatorAddress} + case staking.DirectiveDelegate: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveDelegate) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.Delegate) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) + if err != nil { + return nil, err + } + validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) + if err != nil { + return nil, err + } + rpcMsg = &DelegateMsg{ + DelegatorAddress: delegatorAddress, + ValidatorAddress: validatorAddress, + Amount: (*hexutil.Big)(msg.Amount), + } + case staking.DirectiveUndelegate: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveUndelegate) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.Undelegate) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) + if err != nil { + return nil, err + } + validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) + if err != nil { + return nil, err + } + rpcMsg = &UndelegateMsg{ + DelegatorAddress: delegatorAddress, + ValidatorAddress: validatorAddress, + Amount: (*hexutil.Big)(msg.Amount), + } + } + + result := &StakingTransaction{ + Gas: hexutil.Uint64(tx.Gas()), + GasPrice: (*hexutil.Big)(tx.GasPrice()), + Hash: tx.Hash(), + Nonce: hexutil.Uint64(tx.Nonce()), + Timestamp: hexutil.Uint64(timestamp), + V: (*hexutil.Big)(v), + R: (*hexutil.Big)(r), + S: (*hexutil.Big)(s), + Type: tx.StakingType().String(), + Msg: rpcMsg, + } + if blockHash != (common.Hash{}) { + result.BlockHash = blockHash + result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) + result.TransactionIndex = hexutil.Uint(index) + } + + fromAddr, err := internal_common.AddressToBech32(from) + if err != nil { + return nil, err + } + result.From = fromAddr + + return result, nil +} + +// NewBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are +// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain +// transaction hashes. +func NewBlock(b *types.Block, blockArgs *rpc_common.BlockArgs, leader string) (interface{}, error) { + if blockArgs.FullTx { + return NewBlockWithFullTx(b, blockArgs, leader) + } + return NewBlockWithTxHash(b, blockArgs, leader) +} + +// NewBlockWithTxHash .. +func NewBlockWithTxHash( + b *types.Block, blockArgs *rpc_common.BlockArgs, leader string, +) (*BlockWithTxHash, error) { + if blockArgs.FullTx { + return nil, fmt.Errorf("block args specifies full tx, but requested RPC block with only tx hash") + } + + head := b.Header() + blk := &BlockWithTxHash{ + Number: (*hexutil.Big)(head.Number()), + ViewID: (*hexutil.Big)(head.ViewID()), + Epoch: (*hexutil.Big)(head.Epoch()), + Hash: b.Hash(), + ParentHash: head.ParentHash(), + Nonce: 0, // Remove this because we don't have it in our header + MixHash: head.MixDigest(), + LogsBloom: head.Bloom(), + StateRoot: head.Root(), + Miner: leader, + Difficulty: 0, // Remove this because we don't have it in our header + ExtraData: hexutil.Bytes(head.Extra()), + Size: hexutil.Uint64(b.Size()), + GasLimit: hexutil.Uint64(head.GasLimit()), + GasUsed: hexutil.Uint64(head.GasUsed()), + Timestamp: hexutil.Uint64(head.Time().Uint64()), + TransactionsRoot: head.TxHash(), + ReceiptsRoot: head.ReceiptHash(), + Uncles: []common.Hash{}, + Transactions: []common.Hash{}, + StakingTxs: []common.Hash{}, + } + + for _, tx := range b.Transactions() { + blk.Transactions = append(blk.Transactions, tx.Hash()) + } + + if blockArgs.InclStaking { + for _, stx := range b.StakingTransactions() { + blk.StakingTxs = append(blk.StakingTxs, stx.Hash()) + } + } + + if blockArgs.WithSigners { + blk.Signers = blockArgs.Signers + } + return blk, nil +} + +// NewBlockWithFullTx .. +func NewBlockWithFullTx( + b *types.Block, blockArgs *rpc_common.BlockArgs, leader string, +) (*BlockWithFullTx, error) { + if !blockArgs.FullTx { + return nil, fmt.Errorf("block args specifies NO full tx, but requested RPC block with full tx") + } + + head := b.Header() + blk := &BlockWithFullTx{ + Number: (*hexutil.Big)(head.Number()), + ViewID: (*hexutil.Big)(head.ViewID()), + Epoch: (*hexutil.Big)(head.Epoch()), + Hash: b.Hash(), + ParentHash: head.ParentHash(), + Nonce: 0, // Remove this because we don't have it in our header + MixHash: head.MixDigest(), + LogsBloom: head.Bloom(), + StateRoot: head.Root(), + Miner: leader, + Difficulty: 0, // Remove this because we don't have it in our header + ExtraData: hexutil.Bytes(head.Extra()), + Size: hexutil.Uint64(b.Size()), + GasLimit: hexutil.Uint64(head.GasLimit()), + GasUsed: hexutil.Uint64(head.GasUsed()), + Timestamp: hexutil.Uint64(head.Time().Uint64()), + TransactionsRoot: head.TxHash(), + ReceiptsRoot: head.ReceiptHash(), + Uncles: []common.Hash{}, + Transactions: []*Transaction{}, + StakingTxs: []*StakingTransaction{}, + } + + for _, tx := range b.Transactions() { + fmtTx, err := NewTransactionFromBlockHash(b, tx.Hash()) + if err != nil { + return nil, err + } + blk.Transactions = append(blk.Transactions, fmtTx) + } + + if blockArgs.InclStaking { + for _, stx := range b.StakingTransactions() { + fmtStx, err := NewStakingTransactionFromBlockHash(b, stx.Hash()) + if err != nil { + return nil, err + } + blk.StakingTxs = append(blk.StakingTxs, fmtStx) + } + } + + if blockArgs.WithSigners { + blk.Signers = blockArgs.Signers + } + return blk, nil +} + +// NewTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. +func NewTransactionFromBlockHash(b *types.Block, hash common.Hash) (*Transaction, error) { + for idx, tx := range b.Transactions() { + if tx.Hash() == hash { + return NewTransactionFromBlockIndex(b, uint64(idx)) + } + } + return nil, fmt.Errorf("tx %v not found in block %v", hash, b.Hash().String()) +} + +// NewTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. +func NewTransactionFromBlockIndex(b *types.Block, index uint64) (*Transaction, error) { + txs := b.Transactions() + if index >= uint64(len(txs)) { + return nil, fmt.Errorf( + "tx index %v greater than or equal to number of transactions on block %v", index, b.Hash().String(), + ) + } + return NewTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) +} + +// NewStakingTransactionFromBlockHash returns a staking transaction that will serialize to the RPC representation. +func NewStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) (*StakingTransaction, error) { + for idx, tx := range b.StakingTransactions() { + if tx.Hash() == hash { + return NewStakingTransactionFromBlockIndex(b, uint64(idx)) + } + } + return nil, fmt.Errorf("tx %v not found in block %v", hash, b.Hash().String()) +} + +// NewStakingTransactionFromBlockIndex returns a staking transaction that will serialize to the RPC representation. +func NewStakingTransactionFromBlockIndex(b *types.Block, index uint64) (*StakingTransaction, error) { + txs := b.StakingTransactions() + if index >= uint64(len(txs)) { + return nil, fmt.Errorf( + "tx index %v greater than or equal to number of transactions on block %v", index, b.Hash().String(), + ) + } + return NewStakingTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) +} diff --git a/rpc/v2/legacy.go b/rpc/v2/legacy.go new file mode 100644 index 000000000..c68dc9b92 --- /dev/null +++ b/rpc/v2/legacy.go @@ -0,0 +1,39 @@ +package v2 + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/rpc" + "github.com/harmony-one/harmony/hmy" + internal_common "github.com/harmony-one/harmony/internal/common" +) + +// PublicLegacyService provides an API to access the Harmony blockchain. +// Services here are legacy methods, specific to the V1 RPC that can be deprecated in the future. +type PublicLegacyService struct { + hmy *hmy.Harmony +} + +// NewPublicLegacyAPI creates a new API for the RPC interface +func NewPublicLegacyAPI(hmy *hmy.Harmony) rpc.API { + return rpc.API{ + Namespace: "hmyv2", + Version: "1.0", + Service: &PublicLegacyService{hmy}, + Public: true, + } +} + +// GetBalance returns the amount of Atto for the given address in the state of the +// given block number. +func (s *PublicLegacyService) GetBalance( + ctx context.Context, address string, +) (*big.Int, error) { + addr := internal_common.ParseAddr(address) + balance, err := s.hmy.GetBalance(ctx, addr, rpc.BlockNumber(-1)) + if err != nil { + return nil, err + } + return balance, nil +} diff --git a/rpc/v2/types.go b/rpc/v2/types.go new file mode 100644 index 000000000..057bbf5b3 --- /dev/null +++ b/rpc/v2/types.go @@ -0,0 +1,734 @@ +package v2 + +import ( + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/crypto/bls" + internal_common "github.com/harmony-one/harmony/internal/common" + rpc_common "github.com/harmony-one/harmony/rpc/common" + staking "github.com/harmony-one/harmony/staking/types" +) + +// BlockWithTxHash represents a block that will serialize to the RPC representation of a block +// having ONLY transaction hashes in the Transaction & Staking transaction fields. +type BlockWithTxHash struct { + Number *big.Int `json:"number"` + ViewID *big.Int `json:"viewID"` + Epoch *big.Int `json:"epoch"` + Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash"` + Nonce uint64 `json:"nonce"` + MixHash common.Hash `json:"mixHash"` + LogsBloom ethtypes.Bloom `json:"logsBloom"` + StateRoot common.Hash `json:"stateRoot"` + Miner string `json:"miner"` + Difficulty uint64 `json:"difficulty"` + ExtraData hexutil.Bytes `json:"extraData"` + Size uint64 `json:"size"` + GasLimit uint64 `json:"gasLimit"` + GasUsed uint64 `json:"gasUsed"` + Timestamp *big.Int `json:"timestamp"` + TransactionsRoot common.Hash `json:"transactionsRoot"` + ReceiptsRoot common.Hash `json:"receiptsRoot"` + Uncles []common.Hash `json:"uncles"` + Transactions []common.Hash `json:"transactions"` + StakingTxs []common.Hash `json:"stakingTransactions"` + Signers []string `json:"signers,omitempty"` +} + +// BlockWithFullTx represents a block that will serialize to the RPC representation of a block +// having FULL transactions in the Transaction & Staking transaction fields. +type BlockWithFullTx struct { + Number *big.Int `json:"number"` + ViewID *big.Int `json:"viewID"` + Epoch *big.Int `json:"epoch"` + Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash"` + Nonce uint64 `json:"nonce"` + MixHash common.Hash `json:"mixHash"` + LogsBloom ethtypes.Bloom `json:"logsBloom"` + StateRoot common.Hash `json:"stateRoot"` + Miner string `json:"miner"` + Difficulty uint64 `json:"difficulty"` + ExtraData hexutil.Bytes `json:"extraData"` + Size uint64 `json:"size"` + GasLimit uint64 `json:"gasLimit"` + GasUsed uint64 `json:"gasUsed"` + Timestamp *big.Int `json:"timestamp"` + TransactionsRoot common.Hash `json:"transactionsRoot"` + ReceiptsRoot common.Hash `json:"receiptsRoot"` + Uncles []common.Hash `json:"uncles"` + Transactions []*Transaction `json:"transactions"` + StakingTxs []*StakingTransaction `json:"stakingTransactions"` + Signers []string `json:"signers,omitempty"` +} + +// Transaction represents a transaction that will serialize to the RPC representation of a transaction +type Transaction struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber *big.Int `json:"blockNumber"` + From string `json:"from"` + Timestamp uint64 `json:"timestamp"` + Gas uint64 `json:"gas"` + GasPrice *big.Int `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce uint64 `json:"nonce"` + To string `json:"to"` + TransactionIndex uint64 `json:"transactionIndex"` + Value *big.Int `json:"value"` + ShardID uint32 `json:"shardID"` + ToShardID uint32 `json:"toShardID"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` +} + +// StakingTransaction represents a transaction that will serialize to the RPC representation of a staking transaction +type StakingTransaction struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber *big.Int `json:"blockNumber"` + From string `json:"from"` + Timestamp uint64 `json:"timestamp"` + Gas uint64 `json:"gas"` + GasPrice *big.Int `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Nonce uint64 `json:"nonce"` + TransactionIndex uint64 `json:"transactionIndex"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` + Type string `json:"type"` + Msg interface{} `json:"msg"` +} + +// CreateValidatorMsg represents a staking transaction's create validator directive that +// will serialize to the RPC representation +type CreateValidatorMsg struct { + ValidatorAddress string `json:"validatorAddress"` + CommissionRate *big.Int `json:"commissionRate"` + MaxCommissionRate *big.Int `json:"maxCommissionRate"` + MaxChangeRate *big.Int `json:"maxChangeRate"` + MinSelfDelegation *big.Int `json:"minSelfDelegation"` + MaxTotalDelegation *big.Int `json:"maxTotalDelegation"` + Amount *big.Int `json:"amount"` + Name string `json:"name"` + Website string `json:"website"` + Identity string `json:"identity"` + SecurityContact string `json:"securityContact"` + Details string `json:"details"` + SlotPubKeys []bls.SerializedPublicKey `json:"slotPubKeys"` +} + +// EditValidatorMsg represents a staking transaction's edit validator directive that +// will serialize to the RPC representation +type EditValidatorMsg struct { + ValidatorAddress string `json:"validatorAddress"` + CommissionRate *big.Int `json:"commissionRate"` + MinSelfDelegation *big.Int `json:"minSelfDelegation"` + MaxTotalDelegation *big.Int `json:"maxTotalDelegation"` + Name string `json:"name"` + Website string `json:"website"` + Identity string `json:"identity"` + SecurityContact string `json:"securityContact"` + Details string `json:"details"` + SlotPubKeyToAdd *bls.SerializedPublicKey `json:"slotPubKeyToAdd"` + SlotPubKeyToRemove *bls.SerializedPublicKey `json:"slotPubKeyToRemove"` +} + +// CollectRewardsMsg represents a staking transaction's collect rewards directive that +// will serialize to the RPC representation +type CollectRewardsMsg struct { + DelegatorAddress string `json:"delegatorAddress"` +} + +// DelegateMsg represents a staking transaction's delegate directive that +// will serialize to the RPC representation +type DelegateMsg struct { + DelegatorAddress string `json:"delegatorAddress"` + ValidatorAddress string `json:"validatorAddress"` + Amount *big.Int `json:"amount"` +} + +// UndelegateMsg represents a staking transaction's delegate directive that +// will serialize to the RPC representation +type UndelegateMsg struct { + DelegatorAddress string `json:"delegatorAddress"` + ValidatorAddress string `json:"validatorAddress"` + Amount *big.Int `json:"amount"` +} + +// TxReceipt represents a transaction receipt that will serialize to the RPC representation. +type TxReceipt struct { + BlockHash common.Hash `json:"blockHash"` + TransactionHash common.Hash `json:"transactionHash"` + BlockNumber uint64 `json:"blockNumber"` + TransactionIndex uint64 `json:"transactionIndex"` + GasUsed uint64 `json:"gasUsed"` + CumulativeGasUsed uint64 `json:"cumulativeGasUsed"` + ContractAddress common.Address `json:"contractAddress"` + Logs []*types.Log `json:"logs"` + LogsBloom ethtypes.Bloom `json:"logsBloom"` + ShardID uint32 `json:"shardID"` + From string `json:"from"` + To string `json:"to"` + Root hexutil.Bytes `json:"root,omitempty"` + Status uint `json:"status,omitempty"` +} + +// StakingTxReceipt represents a staking transaction receipt that will serialize to the RPC representation. +type StakingTxReceipt struct { + BlockHash common.Hash `json:"blockHash"` + TransactionHash common.Hash `json:"transactionHash"` + BlockNumber uint64 `json:"blockNumber"` + TransactionIndex uint64 `json:"transactionIndex"` + GasUsed uint64 `json:"gasUsed"` + CumulativeGasUsed uint64 `json:"cumulativeGasUsed"` + ContractAddress common.Address `json:"contractAddress"` + Logs []*types.Log `json:"logs"` + LogsBloom ethtypes.Bloom `json:"logsBloom"` + Sender string `json:"sender"` + Type staking.Directive `json:"type"` + Root hexutil.Bytes `json:"root,omitempty"` + Status uint `json:"status,omitempty"` +} + +// CxReceipt represents a CxReceipt that will serialize to the RPC representation of a CxReceipt +type CxReceipt struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber *big.Int `json:"blockNumber"` + TxHash common.Hash `json:"hash"` + From string `json:"from"` + To string `json:"to"` + ShardID uint32 `json:"shardID"` + ToShardID uint32 `json:"toShardID"` + Amount *big.Int `json:"value"` +} + +// NewCxReceipt returns a CxReceipt that will serialize to the RPC representation +func NewCxReceipt(cx *types.CXReceipt, blockHash common.Hash, blockNumber uint64) (*CxReceipt, error) { + result := &CxReceipt{ + BlockHash: blockHash, + TxHash: cx.TxHash, + Amount: cx.Amount, + ShardID: cx.ShardID, + ToShardID: cx.ToShardID, + } + if blockHash != (common.Hash{}) { + result.BlockHash = blockHash + result.BlockNumber = (*big.Int)(new(big.Int).SetUint64(blockNumber)) + } + + fromAddr, err := internal_common.AddressToBech32(cx.From) + if err != nil { + return nil, err + } + toAddr := "" + if cx.To != nil { + if toAddr, err = internal_common.AddressToBech32(*cx.To); err != nil { + return nil, err + } + } + result.From = fromAddr + result.To = toAddr + + return result, nil +} + +// NewTransaction returns a transaction that will serialize to the RPC +// representation, with the given location metadata set (if available). +// Note that all txs on Harmony are replay protected (post EIP155 epoch). +func NewTransaction( + tx *types.Transaction, blockHash common.Hash, + blockNumber uint64, timestamp uint64, index uint64, +) (*Transaction, error) { + from, err := tx.SenderAddress() + if err != nil { + return nil, err + } + v, r, s := tx.RawSignatureValues() + + result := &Transaction{ + Gas: tx.Gas(), + GasPrice: tx.GasPrice(), + Hash: tx.Hash(), + Input: hexutil.Bytes(tx.Data()), + Nonce: tx.Nonce(), + Value: tx.Value(), + ShardID: tx.ShardID(), + ToShardID: tx.ToShardID(), + Timestamp: timestamp, + V: (*hexutil.Big)(v), + R: (*hexutil.Big)(r), + S: (*hexutil.Big)(s), + } + if blockHash != (common.Hash{}) { + result.BlockHash = blockHash + result.BlockNumber = new(big.Int).SetUint64(blockNumber) + result.TransactionIndex = index + } + + fromAddr, err := internal_common.AddressToBech32(from) + if err != nil { + return nil, err + } + toAddr := "" + + if tx.To() != nil { + if toAddr, err = internal_common.AddressToBech32(*tx.To()); err != nil { + return nil, err + } + result.From = fromAddr + } else { + result.From = strings.ToLower(from.Hex()) + } + result.To = toAddr + + return result, nil +} + +// NewReceipt returns a transaction OR staking transaction that will serialize to the RPC representation +func NewReceipt( + tx interface{}, blockHash common.Hash, blockNumber, blockIndex uint64, receipt *types.Receipt, +) (interface{}, error) { + plainTx, ok := tx.(*types.Transaction) + if ok { + return NewTxReceipt(plainTx, blockHash, blockIndex, blockNumber, receipt) + } + stakingTx, ok := tx.(*staking.StakingTransaction) + if ok { + return NewStakingTxReceipt(stakingTx, blockHash, blockIndex, blockNumber, receipt) + } + return nil, fmt.Errorf("unknown transaction type for RPC receipt") +} + +// NewTxReceipt returns a plain transaction receipt that will serialize to the RPC representation +func NewTxReceipt( + tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64, receipt *types.Receipt, +) (*TxReceipt, error) { + // Set correct to & from address + senderAddr, err := tx.SenderAddress() + if err != nil { + return nil, err + } + var sender, receiver string + if tx.To() == nil { + // Handle response type for contract receipts + sender = senderAddr.String() + receiver = "" + } else { + // Handle response type for regular transaction + sender, err = internal_common.AddressToBech32(senderAddr) + if err != nil { + return nil, err + } + receiver, err = internal_common.AddressToBech32(*tx.To()) + if err != nil { + return nil, err + } + } + + // Declare receipt + txReceipt := &TxReceipt{ + BlockHash: blockHash, + TransactionHash: tx.Hash(), + BlockNumber: blockNumber, + TransactionIndex: blockIndex, + GasUsed: receipt.GasUsed, + CumulativeGasUsed: receipt.CumulativeGasUsed, + Logs: receipt.Logs, + LogsBloom: receipt.Bloom, + ShardID: tx.ShardID(), + From: sender, + To: receiver, + } + + // Set optionals + if len(receipt.PostState) > 0 { + txReceipt.Root = receipt.PostState + } else { + txReceipt.Status = uint(receipt.Status) + } + + // Set empty array for empty logs + if receipt.Logs == nil { + txReceipt.Logs = []*types.Log{} + } + + // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation + if receipt.ContractAddress != (common.Address{}) { + txReceipt.ContractAddress = receipt.ContractAddress + } + return txReceipt, nil +} + +// NewStakingTxReceipt returns a staking transaction receipt that will serialize to the RPC representation +func NewStakingTxReceipt( + tx *staking.StakingTransaction, blockHash common.Hash, blockNumber, blockIndex uint64, receipt *types.Receipt, +) (*StakingTxReceipt, error) { + // Set correct sender + senderAddr, err := tx.SenderAddress() + if err != nil { + return nil, err + } + sender, err := internal_common.AddressToBech32(senderAddr) + if err != nil { + return nil, err + } + + // Declare receipt + txReceipt := &StakingTxReceipt{ + BlockHash: blockHash, + TransactionHash: tx.Hash(), + BlockNumber: blockNumber, + TransactionIndex: blockIndex, + GasUsed: receipt.GasUsed, + CumulativeGasUsed: receipt.CumulativeGasUsed, + Logs: receipt.Logs, + LogsBloom: receipt.Bloom, + Sender: sender, + Type: tx.StakingType(), + } + + // Set optionals + if len(receipt.PostState) > 0 { + txReceipt.Root = receipt.PostState + } else { + txReceipt.Status = uint(receipt.Status) + } + + // Set empty array for empty logs + if receipt.Logs == nil { + txReceipt.Logs = []*types.Log{} + } + + // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation + if receipt.ContractAddress != (common.Address{}) { + txReceipt.ContractAddress = receipt.ContractAddress + } + return txReceipt, nil +} + +// NewStakingTransaction returns a staking transaction that will serialize to the RPC +// representation, with the given location metadata set (if available). +func NewStakingTransaction( + tx *staking.StakingTransaction, blockHash common.Hash, + blockNumber uint64, timestamp uint64, index uint64, +) (*StakingTransaction, error) { + from, err := tx.SenderAddress() + if err != nil { + return nil, nil + } + v, r, s := tx.RawSignatureValues() + + var rpcMsg interface{} + switch tx.StakingType() { + case staking.DirectiveCreateValidator: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCreateValidator) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.CreateValidator) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) + if err != nil { + return nil, err + } + rpcMsg = &CreateValidatorMsg{ + ValidatorAddress: validatorAddress, + CommissionRate: msg.CommissionRates.Rate.Int, + MaxCommissionRate: msg.CommissionRates.MaxRate.Int, + MaxChangeRate: msg.CommissionRates.MaxChangeRate.Int, + MinSelfDelegation: msg.MinSelfDelegation, + MaxTotalDelegation: msg.MaxTotalDelegation, + Amount: msg.Amount, + Name: msg.Description.Name, + Website: msg.Description.Website, + Identity: msg.Description.Identity, + SecurityContact: msg.Description.SecurityContact, + Details: msg.Description.Details, + SlotPubKeys: msg.SlotPubKeys, + } + case staking.DirectiveEditValidator: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveEditValidator) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.EditValidator) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) + if err != nil { + return nil, err + } + // Edit validators txs need not have commission rates to edit + commissionRate := &big.Int{} + if msg.CommissionRate != nil { + commissionRate = msg.CommissionRate.Int + } + rpcMsg = &EditValidatorMsg{ + ValidatorAddress: validatorAddress, + CommissionRate: commissionRate, + MinSelfDelegation: msg.MinSelfDelegation, + MaxTotalDelegation: msg.MaxTotalDelegation, + Name: msg.Description.Name, + Website: msg.Description.Website, + Identity: msg.Description.Identity, + SecurityContact: msg.Description.SecurityContact, + Details: msg.Description.Details, + SlotPubKeyToAdd: msg.SlotKeyToAdd, + SlotPubKeyToRemove: msg.SlotKeyToRemove, + } + case staking.DirectiveCollectRewards: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCollectRewards) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.CollectRewards) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) + if err != nil { + return nil, err + } + rpcMsg = &CollectRewardsMsg{DelegatorAddress: delegatorAddress} + case staking.DirectiveDelegate: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveDelegate) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.Delegate) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) + if err != nil { + return nil, err + } + validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) + if err != nil { + return nil, err + } + rpcMsg = &DelegateMsg{ + DelegatorAddress: delegatorAddress, + ValidatorAddress: validatorAddress, + Amount: msg.Amount, + } + case staking.DirectiveUndelegate: + rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveUndelegate) + if err != nil { + return nil, err + } + msg, ok := rawMsg.(*staking.Undelegate) + if !ok { + return nil, fmt.Errorf("could not decode staking message") + } + delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress) + if err != nil { + return nil, err + } + validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress) + if err != nil { + return nil, err + } + rpcMsg = &UndelegateMsg{ + DelegatorAddress: delegatorAddress, + ValidatorAddress: validatorAddress, + Amount: msg.Amount, + } + } + + result := &StakingTransaction{ + Gas: tx.Gas(), + GasPrice: tx.GasPrice(), + Hash: tx.Hash(), + Nonce: tx.Nonce(), + Timestamp: timestamp, + V: (*hexutil.Big)(v), + R: (*hexutil.Big)(r), + S: (*hexutil.Big)(s), + Type: tx.StakingType().String(), + Msg: rpcMsg, + } + if blockHash != (common.Hash{}) { + result.BlockHash = blockHash + result.BlockNumber = new(big.Int).SetUint64(blockNumber) + result.TransactionIndex = index + } + + fromAddr, err := internal_common.AddressToBech32(from) + if err != nil { + return nil, err + } + result.From = fromAddr + + return result, nil +} + +// NewBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are +// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain +// transaction hashes. +func NewBlock(b *types.Block, blockArgs *rpc_common.BlockArgs, leader string) (interface{}, error) { + if blockArgs.FullTx { + return NewBlockWithFullTx(b, blockArgs, leader) + } + return NewBlockWithTxHash(b, blockArgs, leader) +} + +// NewBlockWithTxHash return a block with only the transaction hash that will serialize to the RPC representation +func NewBlockWithTxHash( + b *types.Block, blockArgs *rpc_common.BlockArgs, leader string, +) (*BlockWithTxHash, error) { + if blockArgs.FullTx { + return nil, fmt.Errorf("block args specifies full tx, but requested RPC block with only tx hash") + } + + head := b.Header() + blk := &BlockWithTxHash{ + Number: head.Number(), + ViewID: head.ViewID(), + Epoch: head.Epoch(), + Hash: b.Hash(), + ParentHash: head.ParentHash(), + Nonce: 0, // Remove this because we don't have it in our header + MixHash: head.MixDigest(), + LogsBloom: head.Bloom(), + StateRoot: head.Root(), + Miner: leader, + Difficulty: 0, // Remove this because we don't have it in our header + ExtraData: hexutil.Bytes(head.Extra()), + Size: uint64(b.Size()), + GasLimit: head.GasLimit(), + GasUsed: head.GasUsed(), + Timestamp: head.Time(), + TransactionsRoot: head.TxHash(), + ReceiptsRoot: head.ReceiptHash(), + Uncles: []common.Hash{}, + Transactions: []common.Hash{}, + StakingTxs: []common.Hash{}, + } + + for _, tx := range b.Transactions() { + blk.Transactions = append(blk.Transactions, tx.Hash()) + } + + if blockArgs.InclStaking { + for _, stx := range b.StakingTransactions() { + blk.StakingTxs = append(blk.StakingTxs, stx.Hash()) + } + } + + if blockArgs.WithSigners { + blk.Signers = blockArgs.Signers + } + return blk, nil +} + +// NewBlockWithFullTx return a block with the transaction that will serialize to the RPC representation +func NewBlockWithFullTx( + b *types.Block, blockArgs *rpc_common.BlockArgs, leader string, +) (*BlockWithFullTx, error) { + if !blockArgs.FullTx { + return nil, fmt.Errorf("block args specifies NO full tx, but requested RPC block with full tx") + } + + head := b.Header() + blk := &BlockWithFullTx{ + Number: head.Number(), + ViewID: head.ViewID(), + Epoch: head.Epoch(), + Hash: b.Hash(), + ParentHash: head.ParentHash(), + Nonce: 0, // Remove this because we don't have it in our header + MixHash: head.MixDigest(), + LogsBloom: head.Bloom(), + StateRoot: head.Root(), + Miner: leader, + Difficulty: 0, // Remove this because we don't have it in our header + ExtraData: hexutil.Bytes(head.Extra()), + Size: uint64(b.Size()), + GasLimit: head.GasLimit(), + GasUsed: head.GasUsed(), + Timestamp: head.Time(), + TransactionsRoot: head.TxHash(), + ReceiptsRoot: head.ReceiptHash(), + Uncles: []common.Hash{}, + Transactions: []*Transaction{}, + StakingTxs: []*StakingTransaction{}, + } + + for _, tx := range b.Transactions() { + fmtTx, err := NewTransactionFromBlockHash(b, tx.Hash()) + if err != nil { + return nil, err + } + blk.Transactions = append(blk.Transactions, fmtTx) + } + + if blockArgs.InclStaking { + for _, stx := range b.StakingTransactions() { + fmtStx, err := NewStakingTransactionFromBlockHash(b, stx.Hash()) + if err != nil { + return nil, err + } + blk.StakingTxs = append(blk.StakingTxs, fmtStx) + } + } + + if blockArgs.WithSigners { + blk.Signers = blockArgs.Signers + } + return blk, nil +} + +// NewTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. +func NewTransactionFromBlockHash(b *types.Block, hash common.Hash) (*Transaction, error) { + for idx, tx := range b.Transactions() { + if tx.Hash() == hash { + return NewTransactionFromBlockIndex(b, uint64(idx)) + } + } + return nil, fmt.Errorf("tx %v not found in block %v", hash, b.Hash().String()) +} + +// NewTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. +func NewTransactionFromBlockIndex(b *types.Block, index uint64) (*Transaction, error) { + txs := b.Transactions() + if index >= uint64(len(txs)) { + return nil, fmt.Errorf( + "tx index %v greater than or equal to number of transactions on block %v", index, b.Hash().String(), + ) + } + return NewTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) +} + +// NewStakingTransactionFromBlockHash returns a staking transaction that will serialize to the RPC representation. +func NewStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) (*StakingTransaction, error) { + for idx, tx := range b.StakingTransactions() { + if tx.Hash() == hash { + return NewStakingTransactionFromBlockIndex(b, uint64(idx)) + } + } + return nil, fmt.Errorf("tx %v not found in block %v", hash, b.Hash().String()) +} + +// NewStakingTransactionFromBlockIndex returns a staking transaction that will serialize to the RPC representation. +func NewStakingTransactionFromBlockIndex(b *types.Block, index uint64) (*StakingTransaction, error) { + txs := b.StakingTransactions() + if index >= uint64(len(txs)) { + return nil, fmt.Errorf( + "tx index %v greater than or equal to number of transactions on block %v", index, b.Hash().String(), + ) + } + return NewStakingTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) +}