RPC for staking txns and txns history queries (#2554)

* staking txn. look up by hash fix on api backend rawdb storage

* node explorer staking txn 'history' RPC layer support

* fix unit test

* add error log when explorer node db instance cannot be fetched

* revert unwanted merge changes during rebase

* use already encoded tx message fields for get staking txn rpc

* update explorer node storage service for staking txns

* use hex string for staking transaction data field

* revert transaction pool apiv1 changes
pull/2685/head
Jong Hyuck Won 5 years ago committed by GitHub
parent 9b07fb71a9
commit b9843abde5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 49
      api/service/explorer/storage.go
  2. 57
      api/service/explorer/structs.go
  3. 2
      block/factory/factory.go
  4. 36
      core/rawdb/accessors_indexes.go
  5. 68
      core/rawdb/accessors_indexes_test.go
  6. 14
      hmy/api_backend.go
  7. 1
      hmy/backend.go
  8. 2
      internal/hmyapi/apiv1/backend.go
  9. 2
      internal/hmyapi/apiv1/transactionpool.go
  10. 8
      internal/hmyapi/apiv1/types.go
  11. 17
      internal/hmyapi/apiv1/util.go
  12. 2
      internal/hmyapi/apiv2/backend.go
  13. 84
      internal/hmyapi/apiv2/transactionpool.go
  14. 6
      internal/hmyapi/apiv2/types.go
  15. 17
      internal/hmyapi/apiv2/util.go
  16. 2
      internal/hmyapi/backend.go
  17. 33
      node/node_explorer.go

@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/internal/utils"
staking "github.com/harmony-one/harmony/staking/types"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/filter" "github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/opt"
@ -83,25 +84,40 @@ func (storage *Storage) Dump(block *types.Block, height uint64) {
// Store txs // Store txs
for _, tx := range block.Transactions() { for _, tx := range block.Transactions() {
explorerTransaction := GetTransaction(tx, block) explorerTransaction := GetTransaction(tx, block)
storage.UpdateAddress(batch, explorerTransaction, tx) storage.UpdateTxAddress(batch, explorerTransaction, tx)
}
// Store staking txns
for _, tx := range block.StakingTransactions() {
explorerTransaction := GetStakingTransaction(tx, block)
storage.UpdateStakingTxAddress(batch, explorerTransaction, tx)
} }
if err := storage.db.Write(batch, nil); err != nil { if err := storage.db.Write(batch, nil); err != nil {
utils.Logger().Warn().Err(err).Msg("cannot write batch") utils.Logger().Warn().Err(err).Msg("cannot write batch")
} }
} }
// UpdateAddress ... // UpdateTxAddress ...
func (storage *Storage) UpdateAddress(batch *leveldb.Batch, explorerTransaction *Transaction, tx *types.Transaction) { func (storage *Storage) UpdateTxAddress(batch *leveldb.Batch, explorerTransaction *Transaction, tx *types.Transaction) {
explorerTransaction.Type = Received
if explorerTransaction.To != "" {
storage.UpdateTxAddressStorage(batch, explorerTransaction.To, explorerTransaction, tx)
}
explorerTransaction.Type = Sent
storage.UpdateTxAddressStorage(batch, explorerTransaction.From, explorerTransaction, tx)
}
// UpdateStakingTxAddress ...
func (storage *Storage) UpdateStakingTxAddress(batch *leveldb.Batch, explorerTransaction *StakingTransaction, tx *staking.StakingTransaction) {
explorerTransaction.Type = Received explorerTransaction.Type = Received
if explorerTransaction.To != "" { if explorerTransaction.To != "" {
storage.UpdateAddressStorage(batch, explorerTransaction.To, explorerTransaction, tx) storage.UpdateStakingTxAddressStorage(batch, explorerTransaction.To, explorerTransaction, tx)
} }
explorerTransaction.Type = Sent explorerTransaction.Type = Sent
storage.UpdateAddressStorage(batch, explorerTransaction.From, explorerTransaction, tx) storage.UpdateStakingTxAddressStorage(batch, explorerTransaction.From, explorerTransaction, tx)
} }
// UpdateAddressStorage updates specific addr Address. // UpdateTxAddressStorage updates specific addr tx Address.
func (storage *Storage) UpdateAddressStorage(batch *leveldb.Batch, addr string, explorerTransaction *Transaction, tx *types.Transaction) { func (storage *Storage) UpdateTxAddressStorage(batch *leveldb.Batch, addr string, explorerTransaction *Transaction, tx *types.Transaction) {
var address Address var address Address
key := GetAddressKey(addr) key := GetAddressKey(addr)
if data, err := storage.db.Get([]byte(key), nil); err == nil { if data, err := storage.db.Get([]byte(key), nil); err == nil {
@ -119,6 +135,25 @@ func (storage *Storage) UpdateAddressStorage(batch *leveldb.Batch, addr string,
} }
} }
// UpdateStakingTxAddressStorage updates specific addr staking tx Address.
func (storage *Storage) UpdateStakingTxAddressStorage(batch *leveldb.Batch, addr string, explorerTransaction *StakingTransaction, tx *staking.StakingTransaction) {
var address Address
key := GetAddressKey(addr)
if data, err := storage.db.Get([]byte(key), nil); err == nil {
if err = rlp.DecodeBytes(data, &address); err != nil {
utils.Logger().Error().Err(err).Msg("Failed due to error")
}
}
address.ID = addr
address.StakingTXs = append(address.StakingTXs, explorerTransaction)
encoded, err := rlp.EncodeToBytes(address)
if err == nil {
batch.Put([]byte(key), encoded)
} else {
utils.Logger().Error().Err(err).Msg("cannot encode address")
}
}
// GetAddresses returns size of addresses from address with prefix. // GetAddresses returns size of addresses from address with prefix.
func (storage *Storage) GetAddresses(size int, prefix string) ([]string, error) { func (storage *Storage) GetAddresses(size int, prefix string) ([]string, error) {
db := storage.GetDB() db := storage.GetDB()

@ -5,9 +5,11 @@ import (
"math/big" "math/big"
"strconv" "strconv"
core2 "github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/common" "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/internal/utils"
staking "github.com/harmony-one/harmony/staking/types"
) )
/* /*
@ -27,9 +29,10 @@ type Data struct {
// Address ... // Address ...
type Address struct { type Address struct {
ID string `json:"id"` ID string `json:"id"`
Balance *big.Int `json:"balance"` Balance *big.Int `json:"balance"`
TXs []*Transaction `json:"txs"` TXs []*Transaction `json:"txs"`
StakingTXs []*StakingTransaction `json:"staking_txs"`
} }
// Transaction ... // Transaction ...
@ -79,3 +82,51 @@ func GetTransaction(tx *types.Transaction, addressBlock *types.Block) *Transacti
Type: "", Type: "",
} }
} }
// StakingTransaction ...
type StakingTransaction struct {
ID string `json:"id"`
Timestamp string `json:"timestamp"`
From string `json:"from"`
To string `json:"to"`
Value *big.Int `json:"value"`
Bytes string `json:"bytes"`
Data string `json:"data"`
GasFee *big.Int `json:"gasFee"`
FromShard uint32 `json:"fromShard"`
ToShard uint32 `json:"toShard"`
Type string `json:"type"`
}
// GetStakingTransaction ...
func GetStakingTransaction(tx *staking.StakingTransaction, addressBlock *types.Block) *StakingTransaction {
msg, err := core2.StakingToMessage(tx, addressBlock.Header().Number())
if err != nil {
utils.Logger().Error().Err(err).Msg("Error when parsing tx into message")
}
gasFee := big.NewInt(0)
gasFee = gasFee.Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
to := ""
if msg.To() != nil {
if to, err = common.AddressToBech32(*msg.To()); err != nil {
return nil
}
}
from := ""
if from, err = common.AddressToBech32(msg.From()); err != nil {
return nil
}
return &StakingTransaction{
ID: tx.Hash().Hex(),
Timestamp: strconv.Itoa(int(addressBlock.Time().Int64() * 1000)),
From: from,
To: to,
Value: msg.Value(),
Bytes: strconv.Itoa(int(tx.Size())),
Data: hex.EncodeToString(msg.Data()),
GasFee: gasFee,
FromShard: tx.ShardID(),
ToShard: 0,
Type: string(msg.Type()),
}
}

@ -30,7 +30,7 @@ func NewFactory(chainConfig *params.ChainConfig) Factory {
func (f *factory) NewHeader(epoch *big.Int) *block.Header { func (f *factory) NewHeader(epoch *big.Int) *block.Header {
var impl blockif.Header var impl blockif.Header
switch { switch {
case f.chainConfig.IsPreStaking(epoch): case f.chainConfig.IsPreStaking(epoch) || f.chainConfig.IsStaking(epoch):
impl = v3.NewHeader() impl = v3.NewHeader()
case f.chainConfig.IsCrossLink(epoch): case f.chainConfig.IsCrossLink(epoch):
impl = v2.NewHeader() impl = v2.NewHeader()

@ -45,7 +45,9 @@ func ReadTxLookupEntry(db DatabaseReader, hash common.Hash) (common.Hash, uint64
// WriteTxLookupEntries stores a positional metadata for every transaction from // WriteTxLookupEntries stores a positional metadata for every transaction from
// a block, enabling hash based transaction and receipt lookups. // a block, enabling hash based transaction and receipt lookups.
func WriteTxLookupEntries(db DatabaseWriter, block *types.Block) { func WriteTxLookupEntries(db DatabaseWriter, block *types.Block) {
for i, tx := range block.Transactions() { // TODO: remove this hack with Tx and StakingTx structure unitification later
f := func(i int, tx *types.Transaction, stx *staking.StakingTransaction) {
isStaking := (stx != nil && tx == nil)
entry := TxLookupEntry{ entry := TxLookupEntry{
BlockHash: block.Hash(), BlockHash: block.Hash(),
BlockIndex: block.NumberU64(), BlockIndex: block.NumberU64(),
@ -53,25 +55,24 @@ func WriteTxLookupEntries(db DatabaseWriter, block *types.Block) {
} }
data, err := rlp.EncodeToBytes(entry) data, err := rlp.EncodeToBytes(entry)
if err != nil { if err != nil {
utils.Logger().Error().Err(err).Msg("Failed to encode transaction lookup entry") utils.Logger().Error().Err(err).Bool("isStaking", isStaking).Msg("Failed to encode transaction lookup entry")
}
var putErr error
if isStaking {
putErr = db.Put(txLookupKey(stx.Hash()), data)
} else {
putErr = db.Put(txLookupKey(tx.Hash()), data)
} }
if err := db.Put(txLookupKey(tx.Hash()), data); err != nil { if putErr != nil {
utils.Logger().Error().Err(err).Msg("Failed to store transaction lookup entry") utils.Logger().Error().Err(err).Bool("isStaking", isStaking).Msg("Failed to store transaction lookup entry")
} }
} }
for i, tx := range block.Transactions() {
f(i, tx, nil)
}
for i, tx := range block.StakingTransactions() { for i, tx := range block.StakingTransactions() {
entry := TxLookupEntry{ f(i, nil, tx)
BlockHash: block.Hash(),
BlockIndex: block.NumberU64(),
Index: uint64(i),
}
data, err := rlp.EncodeToBytes(entry)
if err != nil {
utils.Logger().Error().Err(err).Msg("Failed to encode staking transaction lookup entry")
}
if err := db.Put(txLookupKey(tx.Hash()), data); err != nil {
utils.Logger().Error().Err(err).Msg("Failed to store staking transaction lookup entry")
}
} }
} }
@ -110,6 +111,8 @@ func ReadTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, c
// ReadStakingTransaction retrieves a specific staking transaction from the database, along with // ReadStakingTransaction retrieves a specific staking transaction from the database, along with
// its added positional metadata. // its added positional metadata.
// TODO remove this duplicate function that is inevitable at the moment until the optimization on staking txn with
// unification of txn vs staking txn data structure.
func ReadStakingTransaction(db DatabaseReader, hash common.Hash) (*staking.StakingTransaction, common.Hash, uint64, uint64) { func ReadStakingTransaction(db DatabaseReader, hash common.Hash) (*staking.StakingTransaction, common.Hash, uint64, uint64) {
blockHash, blockNumber, txIndex := ReadTxLookupEntry(db, hash) blockHash, blockNumber, txIndex := ReadTxLookupEntry(db, hash)
if blockHash == (common.Hash{}) { if blockHash == (common.Hash{}) {
@ -143,6 +146,7 @@ func ReadReceipt(db DatabaseReader, hash common.Hash) (*types.Receipt, common.Ha
if blockHash == (common.Hash{}) { if blockHash == (common.Hash{}) {
return nil, common.Hash{}, 0, 0 return nil, common.Hash{}, 0, 0
} }
receipts := ReadReceipts(db, blockHash, blockNumber) receipts := ReadReceipts(db, blockHash, blockNumber)
if len(receipts) <= int(receiptIndex) { if len(receipts) <= int(receiptIndex) {
utils.Logger().Error(). utils.Logger().Error().

@ -47,7 +47,17 @@ func TestLookupStorage(t *testing.T) {
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), 0, big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), 0, big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
txs := []*types.Transaction{tx1, tx2, tx3} txs := []*types.Transaction{tx1, tx2, tx3}
block := types.NewBlock(blockfactory.NewTestHeader().With().Number(big.NewInt(314)).Header(), txs, types.Receipts{&types.Receipt{}, &types.Receipt{}, &types.Receipt{}}, nil, nil, nil) stx := sampleCreateValidatorStakingTxn()
stxs := []*staking.StakingTransaction{stx}
receipts := types.Receipts{
&types.Receipt{},
&types.Receipt{},
&types.Receipt{},
&types.Receipt{},
}
block := types.NewBlock(blockfactory.NewTestHeader().With().Number(big.NewInt(314)).Header(), txs, receipts, nil, nil, stxs)
// Check that no transactions entries are in a pristine database // Check that no transactions entries are in a pristine database
for i, tx := range txs { for i, tx := range txs {
@ -55,6 +65,11 @@ func TestLookupStorage(t *testing.T) {
t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn) t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn)
} }
} }
for i, stx := range stxs {
if stxn, _, _, _ := ReadStakingTransaction(db, stx.Hash()); stxn != nil {
t.Fatalf("stx #%d [%x]: non existent staking transaction returned: %v", i, stxn.Hash(), stxn)
}
}
// Insert all the transactions into the database, and verify contents // Insert all the transactions into the database, and verify contents
WriteBlock(db, block) WriteBlock(db, block)
WriteTxLookupEntries(db, block) WriteTxLookupEntries(db, block)
@ -71,6 +86,18 @@ func TestLookupStorage(t *testing.T) {
} }
} }
} }
for i, stx := range stxs {
if txn, hash, number, index := ReadStakingTransaction(db, stx.Hash()); txn == nil {
t.Fatalf("tx #%d [%x]: staking transaction not found", i, stx.Hash())
} else {
if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) {
t.Fatalf("stx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, stx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i)
}
if stx.Hash() != txn.Hash() {
t.Fatalf("stx #%d [%x]: staking transaction mismatch: have %v, want %v", i, stx.Hash(), txn, stx)
}
}
}
// Delete the transactions and check purge // Delete the transactions and check purge
for i, tx := range txs { for i, tx := range txs {
DeleteTxLookupEntry(db, tx.Hash()) DeleteTxLookupEntry(db, tx.Hash())
@ -78,12 +105,37 @@ func TestLookupStorage(t *testing.T) {
t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn) t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn)
} }
} }
for i, tx := range txs {
DeleteTxLookupEntry(db, tx.Hash())
if stxn, _, _, _ := ReadStakingTransaction(db, tx.Hash()); stxn != nil {
t.Fatalf("stx #%d [%x]: deleted staking transaction returned: %v", i, stx.Hash(), stxn)
}
}
} }
// Test that staking tx hash does not find a plain tx hash (and visa versa) within the same block // Test that staking tx hash does not find a plain tx hash (and visa versa) within the same block
func TestMixedLookupStorage(t *testing.T) { func TestMixedLookupStorage(t *testing.T) {
db := ethdb.NewMemDatabase() db := ethdb.NewMemDatabase()
tx := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), 0, big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) tx := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), 0, big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11})
stx := sampleCreateValidatorStakingTxn()
txs := []*types.Transaction{tx}
stxs := []*staking.StakingTransaction{stx}
header := blockfactory.NewTestHeader().With().Number(big.NewInt(314)).Header()
block := types.NewBlock(header, txs, types.Receipts{&types.Receipt{}, &types.Receipt{}}, nil, nil, stxs)
WriteBlock(db, block)
WriteTxLookupEntries(db, block)
if recTx, _, _, _ := ReadStakingTransaction(db, tx.Hash()); recTx != nil {
t.Fatal("got staking transactions with plain tx hash")
}
if recTx, _, _, _ := ReadTransaction(db, stx.Hash()); recTx != nil {
t.Fatal("got plain transactions with staking tx hash")
}
}
func sampleCreateValidatorStakingTxn() *staking.StakingTransaction {
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
stakePayloadMaker := func() (staking.Directive, interface{}) { stakePayloadMaker := func() (staking.Directive, interface{}) {
p := &bls.PublicKey{} p := &bls.PublicKey{}
@ -123,17 +175,5 @@ func TestMixedLookupStorage(t *testing.T) {
} }
} }
stx, _ := staking.NewStakingTransaction(0, 1e10, big.NewInt(10000), stakePayloadMaker) stx, _ := staking.NewStakingTransaction(0, 1e10, big.NewInt(10000), stakePayloadMaker)
txs := []*types.Transaction{tx} return stx
stxs := []*staking.StakingTransaction{stx}
header := blockfactory.NewTestHeader().With().Number(big.NewInt(314)).Header()
block := types.NewBlock(header, txs, types.Receipts{&types.Receipt{}, &types.Receipt{}}, nil, nil, stxs)
WriteBlock(db, block)
WriteTxLookupEntries(db, block)
if recTx, _, _, _ := ReadStakingTransaction(db, tx.Hash()); recTx != nil {
t.Fatal("got staking transactions with plain tx hash")
}
if recTx, _, _, _ := ReadTransaction(db, stx.Hash()); recTx != nil {
t.Fatal("got plain transactions with staking tx hash")
}
} }

@ -239,6 +239,12 @@ func (b *APIBackend) GetTransactionsHistory(address, txType, order string) ([]co
return hashes, err return hashes, err
} }
// GetStakingTransactionsHistory returns list of staking transactions hashes of address.
func (b *APIBackend) GetStakingTransactionsHistory(address, txType, order string) ([]common.Hash, error) {
hashes, err := b.hmy.nodeAPI.GetStakingTransactionsHistory(address, txType, order)
return hashes, err
}
// NetVersion returns net version // NetVersion returns net version
func (b *APIBackend) NetVersion() uint64 { func (b *APIBackend) NetVersion() uint64 {
return b.hmy.NetVersion() return b.hmy.NetVersion()
@ -281,18 +287,26 @@ func (b *APIBackend) GetValidators(epoch *big.Int) (*shard.Committee, error) {
} }
// ResendCx retrieve blockHash from txID and add blockHash to CxPool for resending // ResendCx retrieve blockHash from txID and add blockHash to CxPool for resending
// Note that cross shard txn is only for regular txns, not for staking txns, so the input txn hash
// is expected to be regular txn hash
func (b *APIBackend) ResendCx(ctx context.Context, txID common.Hash) (uint64, bool) { func (b *APIBackend) ResendCx(ctx context.Context, txID common.Hash) (uint64, bool) {
blockHash, blockNum, index := b.hmy.BlockChain().ReadTxLookupEntry(txID) blockHash, blockNum, index := b.hmy.BlockChain().ReadTxLookupEntry(txID)
if blockHash == (common.Hash{}) {
return 0, false
}
blk := b.hmy.BlockChain().GetBlockByHash(blockHash) blk := b.hmy.BlockChain().GetBlockByHash(blockHash)
if blk == nil { if blk == nil {
return 0, false return 0, false
} }
txs := blk.Transactions() txs := blk.Transactions()
// a valid index is from 0 to len-1 // a valid index is from 0 to len-1
if int(index) > len(txs)-1 { if int(index) > len(txs)-1 {
return 0, false return 0, false
} }
tx := txs[int(index)] tx := txs[int(index)]
// check whether it is a valid cross shard tx // check whether it is a valid cross shard tx
if tx.ShardID() == tx.ToShardID() || blk.Header().ShardID() != tx.ShardID() { if tx.ShardID() == tx.ToShardID() || blk.Header().ShardID() != tx.ShardID() {
return 0, false return 0, false

@ -50,6 +50,7 @@ type NodeAPI interface {
GetBalanceOfAddress(address common.Address) (*big.Int, error) GetBalanceOfAddress(address common.Address) (*big.Int, error)
GetNonceOfAddress(address common.Address) uint64 GetNonceOfAddress(address common.Address) uint64
GetTransactionsHistory(address, txType, order string) ([]common.Hash, error) GetTransactionsHistory(address, txType, order string) ([]common.Hash, error)
GetStakingTransactionsHistory(address, txType, order string) ([]common.Hash, error)
IsCurrentlyLeader() bool IsCurrentlyLeader() bool
ErroredStakingTransactionSink() []staking.RPCTransactionError ErroredStakingTransactionSink() []staking.RPCTransactionError
ErroredTransactionSink() []types.RPCTransactionError ErroredTransactionSink() []types.RPCTransactionError

@ -68,6 +68,8 @@ type Backend interface {
GetShardID() uint32 GetShardID() uint32
// Get transactions history for an address // Get transactions history for an address
GetTransactionsHistory(address, txType, order string) ([]common.Hash, error) GetTransactionsHistory(address, txType, order string) ([]common.Hash, error)
// Get staking transactions history for an address
GetStakingTransactionsHistory(address, txType, order string) ([]common.Hash, error)
// retrieve the blockHash using txID and add blockHash to CxPool for resending // retrieve the blockHash using txID and add blockHash to CxPool for resending
ResendCx(ctx context.Context, txID common.Hash) (uint64, bool) ResendCx(ctx context.Context, txID common.Hash) (uint64, bool)
IsLeader() bool IsLeader() bool

@ -61,7 +61,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionsHistory(ctx context.Context, a
if err != nil { if err != nil {
return nil, err return nil, err
} }
result = ReturnWithPagination(hashes, args) result = ReturnWithPagination(hashes, args.PageIndex, args.PageSize)
if !args.FullTx { if !args.FullTx {
return map[string]interface{}{"transactions": result}, nil return map[string]interface{}{"transactions": result}, nil
} }

@ -203,7 +203,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
return result return result
} }
// newRPCStakingTransaction returns a transaction that will serialize to the RPC // newRPCStakingTransaction returns a staking transaction that will serialize to the RPC
// representation, with the given location metadata set (if available). // representation, with the given location metadata set (if available).
func newRPCStakingTransaction(tx *types2.StakingTransaction, blockHash common.Hash, blockNumber uint64, timestamp uint64, index uint64) *RPCStakingTransaction { func newRPCStakingTransaction(tx *types2.StakingTransaction, blockHash common.Hash, blockNumber uint64, timestamp uint64, index uint64) *RPCStakingTransaction {
from, err := tx.SenderAddress() from, err := tx.SenderAddress()
@ -350,7 +350,7 @@ func newRPCPendingTransaction(tx *types.Transaction) *RPCTransaction {
return newRPCTransaction(tx, common.Hash{}, 0, 0, 0) return newRPCTransaction(tx, common.Hash{}, 0, 0, 0)
} }
// newRPCPendingStakingTransaction returns a pending transaction that will serialize to the RPC representation // newRPCPendingStakingTransaction returns a pending staking transaction that will serialize to the RPC representation
func newRPCPendingStakingTransaction(tx *types2.StakingTransaction) *RPCStakingTransaction { func newRPCPendingStakingTransaction(tx *types2.StakingTransaction) *RPCStakingTransaction {
return newRPCStakingTransaction(tx, common.Hash{}, 0, 0, 0) return newRPCStakingTransaction(tx, common.Hash{}, 0, 0, 0)
} }
@ -474,7 +474,7 @@ func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransacti
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index)
} }
// newRPCStakingTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. // newRPCStakingTransactionFromBlockHash returns a staking transaction that will serialize to the RPC representation.
func newRPCStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCStakingTransaction { func newRPCStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCStakingTransaction {
for idx, tx := range b.StakingTransactions() { for idx, tx := range b.StakingTransactions() {
if tx.Hash() == hash { if tx.Hash() == hash {
@ -484,7 +484,7 @@ func newRPCStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) *RP
return nil return nil
} }
// newRPCStakingTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. // newRPCStakingTransactionFromBlockIndex returns a staking transaction that will serialize to the RPC representation.
func newRPCStakingTransactionFromBlockIndex(b *types.Block, index uint64) *RPCStakingTransaction { func newRPCStakingTransactionFromBlockIndex(b *types.Block, index uint64) *RPCStakingTransaction {
txs := b.StakingTransactions() txs := b.StakingTransactions()
if index >= uint64(len(txs)) { if index >= uint64(len(txs)) {

@ -17,19 +17,18 @@ const (
) )
// ReturnWithPagination returns result with pagination (offset, page in TxHistoryArgs). // ReturnWithPagination returns result with pagination (offset, page in TxHistoryArgs).
func ReturnWithPagination(hashes []common.Hash, args TxHistoryArgs) []common.Hash { func ReturnWithPagination(hashes []common.Hash, pageIndex uint32, pageSize uint32) []common.Hash {
pageSize := defaultPageSize size := defaultPageSize
pageIndex := args.PageIndex if pageSize > 0 {
if args.PageSize > 0 { size = pageSize
pageSize = args.PageSize
} }
if uint64(pageSize)*uint64(pageIndex) >= uint64(len(hashes)) { if uint64(size)*uint64(pageIndex) >= uint64(len(hashes)) {
return make([]common.Hash, 0) return make([]common.Hash, 0)
} }
if uint64(pageSize)*uint64(pageIndex)+uint64(pageSize) > uint64(len(hashes)) { if uint64(size)*uint64(pageIndex)+uint64(size) > uint64(len(hashes)) {
return hashes[pageSize*pageIndex:] return hashes[size*pageIndex:]
} }
return hashes[pageSize*pageIndex : pageSize*pageIndex+pageSize] return hashes[size*pageIndex : size*pageIndex+size]
} }
// SubmitTransaction is a helper function that submits tx to txPool and logs a message. // SubmitTransaction is a helper function that submits tx to txPool and logs a message.

@ -68,6 +68,8 @@ type Backend interface {
GetShardID() uint32 GetShardID() uint32
// Get transactions history for an address // Get transactions history for an address
GetTransactionsHistory(address, txType, order string) ([]common.Hash, error) GetTransactionsHistory(address, txType, order string) ([]common.Hash, error)
// Get staking transactions history for an address
GetStakingTransactionsHistory(address, txType, order string) ([]common.Hash, error)
// retrieve the blockHash using txID and add blockHash to CxPool for resending // retrieve the blockHash using txID and add blockHash to CxPool for resending
ResendCx(ctx context.Context, txID common.Hash) (uint64, bool) ResendCx(ctx context.Context, txID common.Hash) (uint64, bool)
IsLeader() bool IsLeader() bool

@ -61,14 +61,16 @@ func (s *PublicTransactionPoolAPI) GetTransactionsHistory(ctx context.Context, a
if err != nil { if err != nil {
return nil, err return nil, err
} }
result = ReturnWithPagination(hashes, args) result = ReturnWithPagination(hashes, args.PageIndex, args.PageSize)
if !args.FullTx { if !args.FullTx {
return map[string]interface{}{"transactions": result}, nil return map[string]interface{}{"transactions": result}, nil
} }
txs := []*RPCTransaction{} txs := []*RPCTransaction{}
for _, hash := range result { for _, hash := range result {
tx := s.GetTransactionByHash(ctx, hash) tx := s.GetTransactionByHash(ctx, hash)
txs = append(txs, tx) if tx != nil {
txs = append(txs, tx)
}
} }
return map[string]interface{}{"transactions": txs}, nil return map[string]interface{}{"transactions": txs}, nil
} }
@ -105,7 +107,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(ctx context
return nil return nil
} }
// GetTransactionByHash returns the transaction for the given hash // GetTransactionByHash returns the plain transaction for the given hash
func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) *RPCTransaction { func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) *RPCTransaction {
// Try to return an already finalized transaction // Try to return an already finalized transaction
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash) tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash)
@ -120,22 +122,55 @@ func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, has
return nil return nil
} }
// GetStakingTransactionByHash returns the transaction for the given hash // GetStakingTransactionsHistory returns the list of transactions hashes that involve a particular address.
func (s *PublicTransactionPoolAPI) GetStakingTransactionByHash(ctx context.Context, hash common.Hash) *RPCStakingTransaction { func (s *PublicTransactionPoolAPI) GetStakingTransactionsHistory(ctx context.Context, args TxHistoryArgs) (map[string]interface{}, error) {
// Try to return an already finalized transaction address := args.Address
stx, blockHash, blockNumber, index := rawdb.ReadStakingTransaction(s.b.ChainDb(), hash) result := []common.Hash{}
block, _ := s.b.GetBlock(ctx, blockHash) var err error
if block == nil { if strings.HasPrefix(args.Address, "one1") {
return nil address = args.Address
} else {
addr := internal_common.ParseAddr(args.Address)
address, err = internal_common.AddressToBech32(addr)
if err != nil {
return nil, err
}
} }
if stx != nil { hashes, err := s.b.GetStakingTransactionsHistory(address, args.TxType, args.Order)
return newRPCStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index) if err != nil {
return nil, err
} }
// Transaction unknown, return as such result = ReturnWithPagination(hashes, args.PageIndex, args.PageSize)
return nil 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, blockNr uint64) int {
if block, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(blockNr)); block != nil {
return len(block.StakingTransactions())
}
return 0
} }
// GetStakingTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. // 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.b.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, blockNr uint64, index uint64) *RPCStakingTransaction { func (s *PublicTransactionPoolAPI) GetStakingTransactionByBlockNumberAndIndex(ctx context.Context, blockNr uint64, index uint64) *RPCStakingTransaction {
if block, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(blockNr)); block != nil { if block, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(blockNr)); block != nil {
return newRPCStakingTransactionFromBlockIndex(block, index) return newRPCStakingTransactionFromBlockIndex(block, index)
@ -151,7 +186,24 @@ func (s *PublicTransactionPoolAPI) GetStakingTransactionByBlockHashAndIndex(ctx
return nil return nil
} }
// GetTransactionCount returns the number of transactions the given address has sent for the given block number // 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.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)
}
// Transaction unknown, return as such
return nil
}
// GetTransactionCount returns the number of transactions the given address has sent from genesis to the input block number
// NOTE: unlike other txn apis where staking vs. regular txns are separate,
// the transaction count here includes the count of both regular and staking txns
func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr string, blockNr uint64) (uint64, error) { func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr string, blockNr uint64) (uint64, error) {
address := internal_common.ParseAddr(addr) address := internal_common.ParseAddr(addr)
// Ask transaction pool for the nonce which includes pending transactions // Ask transaction pool for the nonce which includes pending transactions

@ -204,7 +204,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
return result return result
} }
// newRPCStakingTransaction returns a transaction that will serialize to the RPC // newRPCStakingTransaction returns a staking transaction that will serialize to the RPC
// representation, with the given location metadata set (if available). // representation, with the given location metadata set (if available).
func newRPCStakingTransaction(tx *types2.StakingTransaction, blockHash common.Hash, blockNumber uint64, timestamp uint64, index uint64) *RPCStakingTransaction { func newRPCStakingTransaction(tx *types2.StakingTransaction, blockHash common.Hash, blockNumber uint64, timestamp uint64, index uint64) *RPCStakingTransaction {
from, err := tx.SenderAddress() from, err := tx.SenderAddress()
@ -475,7 +475,7 @@ func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransacti
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index) return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index)
} }
// newRPCStakingTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. // newRPCStakingTransactionFromBlockHash returns a staking transaction that will serialize to the RPC representation.
func newRPCStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCStakingTransaction { func newRPCStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCStakingTransaction {
for idx, tx := range b.StakingTransactions() { for idx, tx := range b.StakingTransactions() {
if tx.Hash() == hash { if tx.Hash() == hash {
@ -485,7 +485,7 @@ func newRPCStakingTransactionFromBlockHash(b *types.Block, hash common.Hash) *RP
return nil return nil
} }
// newRPCStakingTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. // newRPCStakingTransactionFromBlockIndex returns a staking transaction that will serialize to the RPC representation.
func newRPCStakingTransactionFromBlockIndex(b *types.Block, index uint64) *RPCStakingTransaction { func newRPCStakingTransactionFromBlockIndex(b *types.Block, index uint64) *RPCStakingTransaction {
txs := b.StakingTransactions() txs := b.StakingTransactions()
if index >= uint64(len(txs)) { if index >= uint64(len(txs)) {

@ -17,19 +17,18 @@ const (
) )
// ReturnWithPagination returns result with pagination (offset, page in TxHistoryArgs). // ReturnWithPagination returns result with pagination (offset, page in TxHistoryArgs).
func ReturnWithPagination(hashes []common.Hash, args TxHistoryArgs) []common.Hash { func ReturnWithPagination(hashes []common.Hash, pageIndex uint32, pageSize uint32) []common.Hash {
pageSize := defaultPageSize size := defaultPageSize
pageIndex := args.PageIndex if pageSize > 0 {
if args.PageSize > 0 { size = pageSize
pageSize = args.PageSize
} }
if uint64(pageSize)*uint64(pageIndex) >= uint64(len(hashes)) { if uint64(size)*uint64(pageIndex) >= uint64(len(hashes)) {
return make([]common.Hash, 0) return make([]common.Hash, 0)
} }
if uint64(pageSize)*uint64(pageIndex)+uint64(pageSize) > uint64(len(hashes)) { if uint64(size)*uint64(pageIndex)+uint64(size) > uint64(len(hashes)) {
return hashes[pageSize*pageIndex:] return hashes[size*pageIndex:]
} }
return hashes[pageSize*pageIndex : pageSize*pageIndex+pageSize] return hashes[size*pageIndex : size*pageIndex+size]
} }
// SubmitTransaction is a helper function that submits tx to txPool and logs a message. // SubmitTransaction is a helper function that submits tx to txPool and logs a message.

@ -70,6 +70,8 @@ type Backend interface {
GetShardID() uint32 GetShardID() uint32
// Get transactions history for an address // Get transactions history for an address
GetTransactionsHistory(address, txType, order string) ([]common.Hash, error) GetTransactionsHistory(address, txType, order string) ([]common.Hash, error)
// Get staking transactions history for an address
GetStakingTransactionsHistory(address, txType, order string) ([]common.Hash, error)
// retrieve the blockHash using txID and add blockHash to CxPool for resending // retrieve the blockHash using txID and add blockHash to CxPool for resending
ResendCx(ctx context.Context, txID common.Hash) (uint64, bool) ResendCx(ctx context.Context, txID common.Hash) (uint64, bool)
IsLeader() bool IsLeader() bool

@ -153,6 +153,7 @@ func (node *Node) GetTransactionsHistory(address, txType, order string) ([]commo
key := explorer.GetAddressKey(address) key := explorer.GetAddressKey(address)
bytes, err := explorer.GetStorageInstance(node.SelfPeer.IP, node.SelfPeer.Port, false).GetDB().Get([]byte(key), nil) bytes, err := explorer.GetStorageInstance(node.SelfPeer.IP, node.SelfPeer.Port, false).GetDB().Get([]byte(key), nil)
if err != nil { if err != nil {
utils.Logger().Error().Err(err).Msg("[Explorer] Cannot get storage db instance")
return make([]common.Hash, 0), nil return make([]common.Hash, 0), nil
} }
if err = rlp.DecodeBytes(bytes, &addressData); err != nil { if err = rlp.DecodeBytes(bytes, &addressData); err != nil {
@ -177,3 +178,35 @@ func (node *Node) GetTransactionsHistory(address, txType, order string) ([]commo
} }
return hashes, nil return hashes, nil
} }
// GetStakingTransactionsHistory returns list of staking transactions hashes of address.
func (node *Node) GetStakingTransactionsHistory(address, txType, order string) ([]common.Hash, error) {
addressData := &explorer.Address{}
key := explorer.GetAddressKey(address)
bytes, err := explorer.GetStorageInstance(node.SelfPeer.IP, node.SelfPeer.Port, false).GetDB().Get([]byte(key), nil)
if err != nil {
utils.Logger().Error().Err(err).Msg("[Explorer] Cannot get storage db instance")
return make([]common.Hash, 0), nil
}
if err = rlp.DecodeBytes(bytes, &addressData); err != nil {
utils.Logger().Error().Err(err).Msg("[Explorer] Cannot convert address data from DB")
return nil, err
}
if order == "DESC" {
sort.Slice(addressData.StakingTXs[:], func(i, j int) bool {
return addressData.StakingTXs[i].Timestamp > addressData.StakingTXs[j].Timestamp
})
} else {
sort.Slice(addressData.StakingTXs[:], func(i, j int) bool {
return addressData.StakingTXs[i].Timestamp < addressData.StakingTXs[j].Timestamp
})
}
hashes := make([]common.Hash, 0)
for _, tx := range addressData.StakingTXs {
if txType == "" || txType == "ALL" || txType == tx.Type {
hash := common.HexToHash(tx.ID)
hashes = append(hashes, hash)
}
}
return hashes, nil
}

Loading…
Cancel
Save