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/accounts" "github.com/harmony-one/harmony/core/rawdb" "github.com/harmony-one/harmony/core/types" internal_common "github.com/harmony-one/harmony/internal/common" staking "github.com/harmony-one/harmony/staking/types" "github.com/pkg/errors" ) var ( // ErrInvalidChainID when ChainID of signer does not match that of running node errInvalidChainID = errors.New("invalid chain id for signer") ) // 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 { b Backend nonceLock *AddrLocker } // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI { return &PublicTransactionPoolAPI{b, 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) { address := args.Address 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.b.GetTransactionsHistory(address, args.TxType, args.Order) if err != nil { return nil, err } result = ReturnWithPagination(hashes, args) 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, blockNr rpc.BlockNumber) int { if block, _ := s.b.BlockByNumber(ctx, blockNr); 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.b.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, blockNr rpc.BlockNumber, index uint64) *RPCTransaction { if block, _ := s.b.BlockByNumber(ctx, blockNr); 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.b.GetBlock(ctx, blockHash); block != nil { return newRPCTransactionFromBlockIndex(block, index) } return nil } // GetTransactionByHash returns the 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.b.ChainDb(), hash) block, _ := s.b.GetBlock(ctx, blockHash) if block == nil { return nil } if tx != nil { return newRPCTransaction(tx, blockHash, blockNumber, block.Time().Uint64(), index) } // No finalized transaction, try to retrieve it from the pool if tx = s.b.GetPoolTransaction(hash); tx != nil { return newRPCPendingTransaction(tx) } // Transaction unknown, return as such return nil } // GetStakingTransactionByHash returns the 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.b.ChainDb(), hash) block, _ := s.b.GetBlock(ctx, blockHash) if block == nil { return nil } if stx != nil { return newRPCStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index) } return nil } // GetStakingTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. func (s *PublicTransactionPoolAPI) GetStakingTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index uint64) *RPCStakingTransaction { if block, _ := s.b.BlockByNumber(ctx, blockNr); 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.b.GetBlock(ctx, blockHash); block != nil { return newRPCStakingTransactionFromBlockIndex(block, index) } return nil } // GetTransactionCount returns the number of transactions the given address has sent for the given block number func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr string, blockNr rpc.BlockNumber) (uint64, error) { address := internal_common.ParseAddr(addr) // Ask transaction pool for the nonce which includes pending transactions if blockNr == rpc.PendingBlockNumber { nonce, err := s.b.GetPoolNonce(ctx, address) if err != nil { return 0, err } return nonce, nil } // Resolve block number and use its state to ask for the nonce state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr) if state == nil || err != nil { return 0, err } nonce := state.GetNonce(address) return nonce, state.Error() } // SendTransaction creates a transaction for the given argument, sign it and submit it to the // transaction pool. func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { // Look up the wallet containing the requested signer account := accounts.Account{Address: args.From} wallet, err := s.b.AccountManager().Find(account) if err != nil { return common.Hash{}, err } if args.Nonce == nil { // Hold the addresse's mutex around signing to prevent concurrent assignment of // the same nonce to multiple accounts. s.nonceLock.LockAddr(args.From) defer s.nonceLock.UnlockAddr(args.From) } // Set some sanity defaults and terminate on failure if err := args.setDefaults(ctx, s.b); err != nil { return common.Hash{}, err } // Assemble the transaction and sign with the wallet tx := args.toTransaction() signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID) if err != nil { return common.Hash{}, err } return SubmitTransaction(ctx, s.b, signed) } // 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) { tx := new(staking.StakingTransaction) if err := rlp.DecodeBytes(encodedTx, tx); err != nil { return common.Hash{}, err } c := s.b.ChainConfig().ChainID if tx.ChainID().Cmp(c) != 0 { e := errors.Wrapf(errInvalidChainID, "current chain id:%s", c.String()) return common.Hash{}, e } return SubmitStakingTransaction(ctx, s.b, 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) { tx := new(types.Transaction) if err := rlp.DecodeBytes(encodedTx, tx); err != nil { return common.Hash{}, err } c := s.b.ChainConfig().ChainID if tx.ChainID().Cmp(c) != 0 { e := errors.Wrapf(errInvalidChainID, "current chain id:%s", c.String()) return common.Hash{}, e } return SubmitTransaction(ctx, s.b, 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.b.ChainDb(), hash) if tx == nil { stx, blockHash, blockNumber, index = rawdb.ReadStakingTransaction(s.b.ChainDb(), hash) if stx == nil { return nil, nil } } receipts, err := s.b.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 } // PendingTransactions returns the transactions that are in the transaction pool // and have a from address that is one of the accounts this node manages. func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) { pending, err := s.b.GetPoolTransactions() if err != nil { return nil, err } accounts := make(map[common.Address]struct{}) for _, wallet := range s.b.AccountManager().Wallets() { for _, account := range wallet.Accounts() { accounts[account.Address] = struct{}{} } } transactions := make([]*RPCTransaction, 0, len(pending)) for _, tx := range pending { var signer types.Signer = types.HomesteadSigner{} if tx.Protected() { signer = types.NewEIP155Signer(tx.ChainID()) } from, _ := types.Sender(signer, tx) if _, exists := accounts[from]; exists { transactions = append(transactions, newRPCPendingTransaction(tx)) } } return transactions, nil } // 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.b.ChainDb(), hash); cx != nil { return newRPCCXReceipt(cx, blockHash, blockNumber) } return nil } // GetPendingCXReceipts .. func (s *PublicTransactionPoolAPI) GetPendingCXReceipts(ctx context.Context) []*types.CXReceiptsProof { return s.b.GetPendingCXReceipts() }