Rosetta Implementation - pt2 FIX (Stage 3.2 of Node API Overhaul) (#3321)

* [rosetta] Fix decimal precision on staking transactions

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Add GenesisFundsOperation & PreStakingEraBlockRewardsOperation

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Add special case transaction ID & refactor to use

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [hmy] Fix block signer mask creation

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [staking] Update docs for block reward

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Rename PreStakingEraBlockRewardOperation

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Add pre-staking block reward txs & refactor special cases

* Add getBlockSigner info
* Rename vars named block to blk for pkg imports
* Add unit tests for block reward formatting

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Fix lint

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Add new line to rosetta start msg

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [go.mod] Correct go mod

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Make pre-staking block rewards calc same as AccumulateRewardsAndCountSigs

* Change committee to signers for blockSignerInfo

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Add sanity check for pre-staking block reward amount

* Correct network chain for sync status

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Improve unpackSpecialCaseTransactionIdentifier unit test

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>
pull/3325/head
Daniel Van Der Maden 4 years ago committed by GitHub
parent 2272237738
commit 67e4c975b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      go.mod
  2. 10
      hmy/blockchain.go
  3. 8
      rosetta/common/operations.go
  4. 2
      rosetta/common/operations_test.go
  5. 2
      rosetta/rosetta.go
  6. 374
      rosetta/services/block.go
  7. 153
      rosetta/services/block_test.go
  8. 2
      rosetta/services/network.go
  9. 2
      staking/network/reward.go

@ -21,18 +21,16 @@ require (
github.com/golang/mock v1.4.0
github.com/golang/protobuf v1.4.0
github.com/golangci/golangci-lint v1.22.2
github.com/gorilla/handlers v1.4.0 // indirect
github.com/gorilla/mux v1.7.4
github.com/gorilla/websocket v1.4.2
github.com/gorilla/websocket v1.4.2 // indirect
github.com/harmony-ek/gencodec v0.0.0-20190215044613-e6740dbdd846
github.com/harmony-one/abool v1.0.1
github.com/harmony-one/bls v0.0.6
github.com/harmony-one/taggedrlp v0.1.4
github.com/harmony-one/vdf v0.0.0-20190924175951-620379da8849
github.com/hashicorp/golang-lru v0.5.4
github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365
github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365 // indirect
github.com/ipfs/go-ds-badger v0.2.4
github.com/jackpal/gateway v1.0.6 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/karalabe/hid v1.0.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
@ -41,16 +39,15 @@ require (
github.com/libp2p/go-libp2p-core v0.6.1
github.com/libp2p/go-libp2p-crypto v0.1.0
github.com/libp2p/go-libp2p-discovery v0.5.0
github.com/libp2p/go-libp2p-host v0.1.0
github.com/libp2p/go-libp2p-host v0.1.0 // indirect
github.com/libp2p/go-libp2p-kad-dht v0.8.3
github.com/libp2p/go-libp2p-net v0.1.0
github.com/libp2p/go-libp2p-peer v0.2.0
github.com/libp2p/go-libp2p-peerstore v0.2.6
github.com/libp2p/go-libp2p-net v0.1.0 // indirect
github.com/libp2p/go-libp2p-peer v0.2.0 // indirect
github.com/libp2p/go-libp2p-peerstore v0.2.6 // indirect
github.com/libp2p/go-libp2p-pubsub v0.3.3
github.com/multiformats/go-multiaddr v0.2.2
github.com/multiformats/go-multiaddr-net v0.1.5
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/otiai10/copy v1.2.0
github.com/pborman/uuid v1.2.0
github.com/pelletier/go-toml v1.2.0
github.com/pkg/errors v0.9.1
@ -65,8 +62,6 @@ require (
github.com/spf13/viper v1.6.1
github.com/stretchr/testify v1.6.1
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
github.com/uber/jaeger-client-go v2.20.1+incompatible // indirect
github.com/uber/jaeger-lib v2.2.0+incompatible // indirect
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
golang.org/x/lint v0.0.0-20200302205851-738671d3881b
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a

@ -48,11 +48,15 @@ func (hmy *Harmony) GetBlockSigners(
return nil, nil, err
}
pubKeys := make([]internal_bls.PublicKeyWrapper, len(committee.Slots))
for _, validator := range committee.Slots {
wrapper := internal_bls.PublicKeyWrapper{Bytes: validator.BLSPublicKey}
if wrapper.Object, err = bls.BytesToBLSPublicKey(wrapper.Bytes[:]); err != nil {
for i, validator := range committee.Slots {
key, err := bls.BytesToBLSPublicKey(validator.BLSPublicKey[:])
if err != nil {
return nil, nil, err
}
pubKeys[i] = internal_bls.PublicKeyWrapper{
Bytes: validator.BLSPublicKey,
Object: key,
}
}
mask, err := internal_bls.NewMask(pubKeys, nil)
if err != nil {

@ -17,6 +17,12 @@ const (
// ContractCreationOperation ..
ContractCreationOperation = "ContractCreation"
// GenesisFundsOperation ..
GenesisFundsOperation = "Genesis"
// PreStakingEraBlockRewardOperation ..
PreStakingEraBlockRewardOperation = "PreStakingBlockReward"
)
var (
@ -26,6 +32,8 @@ var (
TransferOperation,
CrossShardTransferOperation,
ContractCreationOperation,
GenesisFundsOperation,
PreStakingEraBlockRewardOperation,
}
// StakingOperationTypes ..

@ -53,6 +53,8 @@ func TestPlainOperationTypes(t *testing.T) {
TransferOperation,
CrossShardTransferOperation,
ContractCreationOperation,
GenesisFundsOperation,
PreStakingEraBlockRewardOperation,
}
sort.Strings(referenceOperationTypes)
sort.Strings(plainOperationTypes)

@ -53,7 +53,7 @@ func StartServers(hmy *hmy.Harmony, config nodeconfig.RosettaServerConfig) error
return err
}
go newHTTPServer(router).Serve(listener)
fmt.Printf("Started Rosetta server at: %v", endpoint)
fmt.Printf("Started Rosetta server at: %v\n", endpoint)
return nil
}

@ -5,6 +5,7 @@ import (
"encoding/hex"
"fmt"
"math/big"
"strings"
"github.com/coinbase/rosetta-sdk-go/server"
"github.com/coinbase/rosetta-sdk-go/types"
@ -13,6 +14,7 @@ import (
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/rawdb"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/hmy"
internalCommon "github.com/harmony-one/harmony/internal/common"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
@ -23,9 +25,14 @@ import (
rpcV2 "github.com/harmony-one/harmony/rpc/v2"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking"
stakingNetwork "github.com/harmony-one/harmony/staking/network"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
const (
blockHashLen = 64
)
// BlockAPI implements the server.BlockAPIServicer interface.
type BlockAPI struct {
hmy *hmy.Harmony
@ -46,62 +53,118 @@ func (s *BlockAPI) Block(
return nil, err
}
var block *hmytypes.Block
var blk *hmytypes.Block
var currBlockID, prevBlockID *types.BlockIdentifier
if block, rosettaError = s.getBlock(ctx, request); rosettaError != nil {
if blk, rosettaError = s.getBlock(ctx, request.BlockIdentifier); rosettaError != nil {
return nil, rosettaError
}
currBlockID = &types.BlockIdentifier{
Index: block.Number().Int64(),
Hash: block.Hash().String(),
// Format genesis block if it is requested.
if blk.Number().Uint64() == 0 {
return s.genesisBlock(ctx, request, blk)
}
otherTransactions := []*types.TransactionIdentifier{}
if block.Number().Int64() > 0 {
prevBlock, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(block.Number().Int64()-1).EthBlockNumber())
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
prevBlockID = &types.BlockIdentifier{
Index: prevBlock.Number().Int64(),
Hash: prevBlock.Hash().String(),
}
} else {
prevBlockID = currBlockID
genesisSpec := getGenesisSpec(block.ShardID())
for _, tx := range getPseudoTransactionForGenesis(genesisSpec) {
otherTransactions = append(otherTransactions, &types.TransactionIdentifier{
Hash: tx.To().String(), // use hex address as tx identifiers for genesis block only
})
}
currBlockID = &types.BlockIdentifier{
Index: blk.Number().Int64(),
Hash: blk.Hash().String(),
}
prevBlock, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blk.Number().Int64()-1).EthBlockNumber())
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
prevBlockID = &types.BlockIdentifier{
Index: prevBlock.Number().Int64(),
Hash: prevBlock.Hash().String(),
}
responseBlock := &types.Block{
BlockIdentifier: currBlockID,
ParentBlockIdentifier: prevBlockID,
Timestamp: block.Time().Int64() * 1e3, // Timestamp must be in ms.
Transactions: []*types.Transaction{}, // Do not return tx details as it is optional.
Timestamp: blk.Time().Int64() * 1e3, // Timestamp must be in ms.
Transactions: []*types.Transaction{}, // Do not return tx details as it is optional.
}
for _, tx := range block.Transactions() {
otherTransactions := []*types.TransactionIdentifier{}
for _, tx := range blk.Transactions() {
otherTransactions = append(otherTransactions, &types.TransactionIdentifier{
Hash: tx.Hash().String(),
})
}
for _, tx := range block.StakingTransactions() {
for _, tx := range blk.StakingTransactions() {
otherTransactions = append(otherTransactions, &types.TransactionIdentifier{
Hash: tx.Hash().String(),
})
}
for _, cxReceipts := range block.IncomingReceipts() {
// Report cross-shard transaction payouts.
for _, cxReceipts := range blk.IncomingReceipts() {
for _, cxReceipt := range cxReceipts.Receipts {
otherTransactions = append(otherTransactions, &types.TransactionIdentifier{
Hash: cxReceipt.TxHash.String(),
})
}
}
// Report pre-staking era block rewards as transactions to fit API.
if !s.hmy.IsStakingEpoch(blk.Epoch()) {
blockSigInfo, rosettaError := getBlockSignerInfo(ctx, s.hmy, blk)
if rosettaError != nil {
return nil, rosettaError
}
for acc, signedBlsKeys := range blockSigInfo.signers {
if len(signedBlsKeys) > 0 {
b32Addr, err := internalCommon.AddressToBech32(acc)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
otherTransactions = append(otherTransactions, getSpecialCaseTransactionIdentifier(blk.Hash(), b32Addr))
}
}
}
return &types.BlockResponse{
Block: responseBlock,
OtherTransactions: otherTransactions,
}, nil
}
// genesisBlock is a special handler for the genesis block.
func (s *BlockAPI) genesisBlock(
ctx context.Context, request *types.BlockRequest, blk *hmytypes.Block,
) (response *types.BlockResponse, rosettaError *types.Error) {
if blk.Number().Uint64() != 0 {
return nil, common.NewError(common.SanityCheckError, map[string]interface{}{
"message": "tried to format response as genesis block on non-genesis block",
})
}
var currBlockID, prevBlockID *types.BlockIdentifier
currBlockID = &types.BlockIdentifier{
Index: blk.Number().Int64(),
Hash: blk.Hash().String(),
}
prevBlockID = currBlockID
responseBlock := &types.Block{
BlockIdentifier: currBlockID,
ParentBlockIdentifier: prevBlockID,
Timestamp: blk.Time().Int64() * 1e3, // Timestamp must be in ms.
Transactions: []*types.Transaction{}, // Do not return tx details as it is optional.
}
otherTransactions := []*types.TransactionIdentifier{}
// Report initial genesis funds as transactions to fit API.
for _, tx := range getPseudoTransactionForGenesis(getGenesisSpec(blk.ShardID())) {
b32Addr, err := internalCommon.AddressToBech32(*tx.To())
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
otherTransactions = append(otherTransactions, getSpecialCaseTransactionIdentifier(blk.Hash(), b32Addr))
}
return &types.BlockResponse{
Block: responseBlock,
@ -117,20 +180,26 @@ func (s *BlockAPI) BlockTransaction(
return nil, err
}
// Special case for genesis block
// Format genesis block transaction request
if request.BlockIdentifier.Index == 0 {
txs, rosettaError := formatGenesisTransaction(request.TransactionIdentifier, s.hmy.ShardID)
if rosettaError != nil {
return nil, rosettaError
}
return &types.BlockTransactionResponse{Transaction: txs}, nil
return s.genesisBlockTransaction(ctx, request)
}
blockHash := ethcommon.HexToHash(request.BlockIdentifier.Hash)
txHash := ethcommon.HexToHash(request.TransactionIdentifier.Hash)
txInfo, rosettaError := s.getTransactionInfo(ctx, blockHash, txHash)
if rosettaError != nil {
return nil, rosettaError
blk, rosettaError2 := s.getBlock(ctx, &types.PartialBlockIdentifier{Index: &request.BlockIdentifier.Index})
if rosettaError2 != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"error": rosettaError2,
"base_error": rosettaError,
})
}
if s.hmy.IsStakingEpoch(blk.Epoch()) {
return nil, rosettaError
}
return s.preStakingEraBlockRewardTransaction(ctx, request.TransactionIdentifier, blk)
}
var transaction *types.Transaction
@ -150,15 +219,66 @@ func (s *BlockAPI) BlockTransaction(
return &types.BlockTransactionResponse{Transaction: transaction}, nil
}
// genesisBlockTransaction is a special handler for genesis block transactions
func (s *BlockAPI) genesisBlockTransaction(
ctx context.Context, request *types.BlockTransactionRequest,
) (response *types.BlockTransactionResponse, rosettaError *types.Error) {
genesisBlock, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(0).EthBlockNumber())
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
blkHash, b32Addr, rosettaError := unpackSpecialCaseTransactionIdentifier(request.TransactionIdentifier)
if rosettaError != nil {
return nil, rosettaError
}
if blkHash.String() != genesisBlock.Hash().String() {
return nil, &common.TransactionNotFoundError
}
txs, rosettaError := formatGenesisTransaction(request.TransactionIdentifier, b32Addr, s.hmy.ShardID)
if rosettaError != nil {
return nil, rosettaError
}
return &types.BlockTransactionResponse{Transaction: txs}, nil
}
// preStakingEraBlockRewardTransaction is a special handler for pre-staking era block reward transactions
func (s *BlockAPI) preStakingEraBlockRewardTransaction(
ctx context.Context, txID *types.TransactionIdentifier, blk *hmytypes.Block,
) (*types.BlockTransactionResponse, *types.Error) {
blkHash, b32Address, rosettaError := unpackSpecialCaseTransactionIdentifier(txID)
if rosettaError != nil {
return nil, rosettaError
}
if blkHash.String() != blk.Hash().String() {
return nil, common.NewError(common.SanityCheckError, map[string]interface{}{
"message": fmt.Sprintf(
"block hash %v != requested block hash %v in tx ID", blkHash.String(), blk.Hash().String(),
),
})
}
blockSignerInfo, rosettaError := getBlockSignerInfo(ctx, s.hmy, blk)
if rosettaError != nil {
return nil, rosettaError
}
transactions, rosettaError := formatPreStakingBlockRewardsTransaction(b32Address, blockSignerInfo)
if rosettaError != nil {
return nil, rosettaError
}
return &types.BlockTransactionResponse{Transaction: transactions}, nil
}
// getBlock ..
func (s *BlockAPI) getBlock(
ctx context.Context, request *types.BlockRequest,
) (block *hmytypes.Block, rosettaError *types.Error) {
ctx context.Context, request *types.PartialBlockIdentifier,
) (blk *hmytypes.Block, rosettaError *types.Error) {
var err error
if request.BlockIdentifier.Hash != nil {
requestBlockHash := ethcommon.HexToHash(*request.BlockIdentifier.Hash)
block, err = s.hmy.GetBlock(ctx, requestBlockHash)
} else if request.BlockIdentifier.Index != nil {
block, err = s.hmy.BlockByNumber(ctx, rpc.BlockNumber(*request.BlockIdentifier.Index).EthBlockNumber())
if request.Hash != nil {
requestBlockHash := ethcommon.HexToHash(*request.Hash)
blk, err = s.hmy.GetBlock(ctx, requestBlockHash)
} else if request.Index != nil {
blk, err = s.hmy.BlockByNumber(ctx, rpc.BlockNumber(*request.Index).EthBlockNumber())
} else {
return nil, &common.BlockNotFoundError
}
@ -167,7 +287,7 @@ func (s *BlockAPI) getBlock(
"message": err.Error(),
})
}
return block, nil
return blk, nil
}
// transactionInfo stores all related information for any transaction on the Harmony chain
@ -252,6 +372,76 @@ func getPseudoTransactionForGenesis(spec *core.Genesis) []*hmytypes.Transaction
return txs
}
// getSpecialCaseTransactionIdentifier fetches 'transaction identifiers' for a given block-hash and suffix.
// Special cases include genesis transactions & pre-staking era block rewards.
// Must include block hash to guarantee uniqueness of tx identifiers.
func getSpecialCaseTransactionIdentifier(
blockHash ethcommon.Hash, suffix string,
) *types.TransactionIdentifier {
return &types.TransactionIdentifier{
Hash: fmt.Sprintf("%v_%v", blockHash.String(), suffix),
}
}
// unpackSpecialCaseTransactionIdentifier returns the suffix & blockHash if the txID is formatted correctly.
func unpackSpecialCaseTransactionIdentifier(
txID *types.TransactionIdentifier,
) (ethcommon.Hash, string, *types.Error) {
hash := txID.Hash
hash = strings.TrimPrefix(hash, "0x")
hash = strings.TrimPrefix(hash, "0X")
if len(hash) < blockHashLen+1 || string(hash[blockHashLen]) != "_" {
return ethcommon.Hash{}, "", common.NewError(common.CatchAllError, map[string]interface{}{
"message": "unknown special case transaction ID format",
})
}
return ethcommon.HexToHash(hash[:blockHashLen]), hash[blockHashLen+1:], nil
}
// blockSignerInfo contains all of the block singing information
type blockSignerInfo struct {
// signers is a map of addresses in the signers for the block to
// all of the serialized BLS keys that signed said block.
signers map[ethcommon.Address][]bls.SerializedPublicKey
// totalKeysSigned is the total number of bls keys that signed the block.
totalKeysSigned uint
// mask is the bitmap mask for the block.
mask *bls.Mask
blockHash ethcommon.Hash
}
// getBlockSignerInfo fetches the block signer information for any non-genesis block
func getBlockSignerInfo(
ctx context.Context, hmy *hmy.Harmony, blk *hmytypes.Block,
) (*blockSignerInfo, *types.Error) {
slotList, mask, err := hmy.GetBlockSigners(
ctx, rpc.BlockNumber(blk.Number().Uint64()).EthBlockNumber(),
)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
totalSigners := uint(0)
sigInfos := map[ethcommon.Address][]bls.SerializedPublicKey{}
for _, slot := range slotList {
if _, ok := sigInfos[slot.EcdsaAddress]; !ok {
sigInfos[slot.EcdsaAddress] = []bls.SerializedPublicKey{}
}
if ok, err := mask.KeyEnabled(slot.BLSPublicKey); ok && err == nil {
sigInfos[slot.EcdsaAddress] = append(sigInfos[slot.EcdsaAddress], slot.BLSPublicKey)
totalSigners++
}
}
return &blockSignerInfo{
signers: sigInfos,
totalKeysSigned: totalSigners,
mask: mask,
blockHash: blk.Hash(),
}, nil
}
// TransactionMetadata ..
type TransactionMetadata struct {
CrossShardIdentifier *types.TransactionIdentifier `json:"cross_shard_transaction_identifier,omitempty"`
@ -296,7 +486,7 @@ func formatCrossShardReceiverTransaction(
Status: common.SuccessOperationStatus.Status,
Account: receiverAccountID,
Amount: &types.Amount{
Value: fmt.Sprintf("%v", cxReceipt.Amount.Uint64()),
Value: fmt.Sprintf("%v", cxReceipt.Amount),
Currency: &common.Currency,
},
Metadata: map[string]interface{}{"from_account": senderAccountID},
@ -307,11 +497,18 @@ func formatCrossShardReceiverTransaction(
// formatGenesisTransaction for genesis block's initial balances
func formatGenesisTransaction(
txID *types.TransactionIdentifier, shardID uint32,
txID *types.TransactionIdentifier, targetB32Addr string, shardID uint32,
) (fmtTx *types.Transaction, rosettaError *types.Error) {
var b32Addr string
var err error
genesisSpec := getGenesisSpec(shardID)
for _, tx := range getPseudoTransactionForGenesis(genesisSpec) {
if tx.To().String() == txID.Hash {
if b32Addr, err = internalCommon.AddressToBech32(*tx.To()); err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
if targetB32Addr == b32Addr {
accID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
return nil, rosettaError
@ -323,7 +520,7 @@ func formatGenesisTransaction(
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.TransferOperation,
Type: common.GenesisFundsOperation,
Status: common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
@ -341,6 +538,71 @@ func formatGenesisTransaction(
return nil, &common.TransactionNotFoundError
}
// formatPreStakingBlockRewardsTransaction for block rewards in pre-staking era for a given Bech-32 address
func formatPreStakingBlockRewardsTransaction(
b32Address string, blockSigInfo *blockSignerInfo,
) (*types.Transaction, *types.Error) {
addr, err := internalCommon.Bech32ToAddress(b32Address)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
signatures, ok := blockSigInfo.signers[addr]
if !ok || len(signatures) == 0 {
return nil, &common.TransactionNotFoundError
}
accID, rosettaError := newAccountIdentifier(addr)
if rosettaError != nil {
return nil, rosettaError
}
// Calculate rewards exactly like `AccumulateRewardsAndCountSigs` but short circuit when possible.
var rewardsForThisBlock *big.Int
i := 0
last := big.NewInt(0)
count := big.NewInt(int64(blockSigInfo.totalKeysSigned))
for sigAddr, keys := range blockSigInfo.signers {
rewardsForThisAddr := big.NewInt(0)
for range keys {
cur := big.NewInt(0)
cur.Mul(stakingNetwork.BlockReward, big.NewInt(int64(i+1))).Div(cur, count)
reward := big.NewInt(0).Sub(cur, last)
rewardsForThisAddr = new(big.Int).Add(reward, rewardsForThisAddr)
last = cur
i++
}
if sigAddr == addr {
rewardsForThisBlock = rewardsForThisAddr
if !(rewardsForThisAddr.Cmp(big.NewInt(0)) > 0) {
return nil, common.NewError(common.SanityCheckError, map[string]interface{}{
"message": "expected non-zero block reward in pre-staking ear for block signer",
})
}
break
}
}
return &types.Transaction{
TransactionIdentifier: getSpecialCaseTransactionIdentifier(blockSigInfo.blockHash, b32Address),
Operations: []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.PreStakingEraBlockRewardOperation,
Status: common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
Value: fmt.Sprintf("%v", rewardsForThisBlock),
Currency: &common.Currency,
},
},
},
}, nil
}
// formatTransaction for staking, cross-shard sender, and plain transactions
func formatTransaction(
tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt,
@ -416,7 +678,7 @@ func getOperations(
}
// All operations excepts for cross-shard tx payout expend gas
gasExpended := receipt.GasUsed * tx.GasPrice().Uint64()
gasExpended := new(big.Int).Mul(new(big.Int).SetUint64(receipt.GasUsed), tx.GasPrice())
gasOperations := newOperations(gasExpended, accountID)
// Handle different cases of plain transactions
@ -453,7 +715,7 @@ func getStakingOperations(
}
// All operations excepts for cross-shard tx payout expend gas
gasExpended := receipt.GasUsed * tx.GasPrice().Uint64()
gasExpended := new(big.Int).Mul(new(big.Int).SetUint64(receipt.GasUsed), tx.GasPrice())
gasOperations := newOperations(gasExpended, accountID)
// Format staking message for metadata
@ -525,7 +787,7 @@ func getAmountFromCreateValidatorMessage(data []byte) (*types.Amount, *types.Err
})
}
return &types.Amount{
Value: fmt.Sprintf("-%v", stkMsg.Amount.Uint64()),
Value: fmt.Sprintf("-%v", stkMsg.Amount),
Currency: &common.Currency,
}, nil
}
@ -544,7 +806,7 @@ func getAmountFromDelegateMessage(data []byte) (*types.Amount, *types.Error) {
})
}
return &types.Amount{
Value: fmt.Sprintf("-%v", stkMsg.Amount.Uint64()),
Value: fmt.Sprintf("-%v", stkMsg.Amount),
Currency: &common.Currency,
}, nil
}
@ -563,7 +825,7 @@ func getAmountFromUndelegateMessage(data []byte) (*types.Amount, *types.Error) {
})
}
return &types.Amount{
Value: fmt.Sprintf("%v", stkMsg.Amount.Uint64()),
Value: fmt.Sprintf("%v", stkMsg.Amount),
Currency: &common.Currency,
}, nil
}
@ -576,7 +838,7 @@ func getAmountFromCollectRewards(
for _, log := range logs {
if log.Address == senderAddress {
amount = &types.Amount{
Value: fmt.Sprintf("%v", big.NewInt(0).SetBytes(log.Data).Uint64()),
Value: fmt.Sprintf("%v", big.NewInt(0).SetBytes(log.Data)),
Currency: &common.Currency,
}
break
@ -778,7 +1040,7 @@ func newAccountIdentifier(
// newOperations creates a new operation with the gas fee as the first operation.
// Note: the gas fee is gasPrice * gasUsed.
func newOperations(
gasFeeInATTO uint64, accountID *types.AccountIdentifier,
gasFeeInATTO *big.Int, accountID *types.AccountIdentifier,
) []*types.Operation {
return []*types.Operation{
{

@ -13,6 +13,7 @@ import (
"github.com/harmony-one/harmony/core"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/crypto/bls"
internalCommon "github.com/harmony-one/harmony/internal/common"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/internal/params"
@ -20,6 +21,7 @@ import (
"github.com/harmony-one/harmony/rpc"
rpcV2 "github.com/harmony-one/harmony/rpc/v2"
"github.com/harmony-one/harmony/staking"
stakingNetwork "github.com/harmony-one/harmony/staking/network"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
@ -220,9 +222,14 @@ func testFormatPlainTransaction(
func TestFormatGenesisTransaction(t *testing.T) {
genesisSpec := getGenesisSpec(0)
testBlkHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238")
for acc := range genesisSpec.Alloc {
txID := &types.TransactionIdentifier{Hash: acc.String()}
tx, rosettaError := formatGenesisTransaction(txID, 0)
b32Addr, err := internalCommon.AddressToBech32(acc)
if err != nil {
t.Fatal(err)
}
txID := getSpecialCaseTransactionIdentifier(testBlkHash, b32Addr)
tx, rosettaError := formatGenesisTransaction(txID, b32Addr, 0)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -232,6 +239,104 @@ func TestFormatGenesisTransaction(t *testing.T) {
if len(tx.Operations) != 1 {
t.Error("expected exactly 1 operation")
}
if tx.Operations[0].OperationIdentifier.Index != 0 {
t.Error("expected operational ID to be 0")
}
if tx.Operations[0].Type != common.GenesisFundsOperation {
t.Error("expected operation to be genesis funds operations")
}
if tx.Operations[0].Status != common.SuccessOperationStatus.Status {
t.Error("expected successful operation status")
}
}
}
func TestFormatPreStakingBlockRewardsTransactionSuccess(t *testing.T) {
testKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
testB32Addr, err := internalCommon.AddressToBech32(testAddr)
if err != nil {
t.Fatal(err)
}
testBlockSigInfo := &blockSignerInfo{
signers: map[ethcommon.Address][]bls.SerializedPublicKey{
testAddr: { // Only care about length for this test
bls.SerializedPublicKey{},
bls.SerializedPublicKey{},
},
},
totalKeysSigned: 150,
blockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
}
refTxID := getSpecialCaseTransactionIdentifier(testBlockSigInfo.blockHash, testB32Addr)
tx, rosettaError := formatPreStakingBlockRewardsTransaction(testB32Addr, testBlockSigInfo)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(tx.TransactionIdentifier, refTxID) {
t.Errorf("Expected TxID %v got %v", refTxID, tx.TransactionIdentifier)
}
if len(tx.Operations) != 1 {
t.Fatal("Expected exactly 1 operation")
}
if tx.Operations[0].OperationIdentifier.Index != 0 {
t.Error("expected operational ID to be 0")
}
if tx.Operations[0].Type != common.PreStakingEraBlockRewardOperation {
t.Error("expected operation type to be pre staking era block rewards")
}
if tx.Operations[0].Status != common.SuccessOperationStatus.Status {
t.Error("expected successful operation status")
}
// Expect: myNumberOfSigForBlock * (totalAmountOfRewardsPerBlock / numOfSigsForBlock) to be my block reward amount
refAmount := new(big.Int).Mul(new(big.Int).Quo(stakingNetwork.BlockReward, big.NewInt(150)), big.NewInt(2))
fmtRefAmount := fmt.Sprintf("%v", refAmount)
if tx.Operations[0].Amount.Value != fmtRefAmount {
t.Errorf("expected operation amount to be %v not %v", fmtRefAmount, tx.Operations[0].Amount.Value)
}
}
func TestFormatPreStakingBlockRewardsTransactionFail(t *testing.T) {
testKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
testB32Addr, err := internalCommon.AddressToBech32(testAddr)
if err != nil {
t.Fatal(err)
}
testBlockSigInfo := &blockSignerInfo{
signers: map[ethcommon.Address][]bls.SerializedPublicKey{
testAddr: {},
},
totalKeysSigned: 150,
blockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
}
_, rosettaError := formatPreStakingBlockRewardsTransaction(testB32Addr, testBlockSigInfo)
if rosettaError == nil {
t.Fatal("expected rosetta error")
}
if !reflect.DeepEqual(&common.TransactionNotFoundError, rosettaError) {
t.Error("expected transaction not found error")
}
testBlockSigInfo = &blockSignerInfo{
signers: map[ethcommon.Address][]bls.SerializedPublicKey{},
totalKeysSigned: 150,
blockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
}
_, rosettaError = formatPreStakingBlockRewardsTransaction(testB32Addr, testBlockSigInfo)
if rosettaError == nil {
t.Fatal("expected rosetta error")
}
if !reflect.DeepEqual(&common.TransactionNotFoundError, rosettaError) {
t.Error("expected transaction not found error")
}
}
@ -322,7 +427,7 @@ func TestGetStakingOperationsFromCreateValidator(t *testing.T) {
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed))).Uint64()
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
@ -383,7 +488,7 @@ func TestGetStakingOperationsFromDelegate(t *testing.T) {
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed))).Uint64()
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
@ -444,7 +549,7 @@ func TestGetStakingOperationsFromUndelegate(t *testing.T) {
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed))).Uint64()
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
@ -498,7 +603,7 @@ func TestGetStakingOperationsFromCollectRewards(t *testing.T) {
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed))).Uint64()
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
@ -559,7 +664,7 @@ func TestGetStakingOperationsFromEditValidator(t *testing.T) {
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed))).Uint64()
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
@ -891,7 +996,7 @@ func TestNewOperations(t *testing.T) {
accountID := &types.AccountIdentifier{
Address: "test-address",
}
gasFee := uint64(1e18)
gasFee := big.NewInt(int64(1e18))
amount := &types.Amount{
Value: fmt.Sprintf("-%v", gasFee),
Currency: &common.Currency,
@ -1037,3 +1142,35 @@ func TestGetPseudoTransactionForGenesis(t *testing.T) {
}
}
}
func TestSpecialCaseTransactionIdentifier(t *testing.T) {
testBlkHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238")
testB32Address := "one10g7kfque6ew2jjfxxa6agkdwk4wlyjuncp6gwz"
refTxID := &types.TransactionIdentifier{
Hash: fmt.Sprintf("%v_%v", testBlkHash.String(), testB32Address),
}
specialTxID := getSpecialCaseTransactionIdentifier(testBlkHash, testB32Address)
if !reflect.DeepEqual(refTxID, specialTxID) {
t.Fatal("invalid for mate for special case TxID")
}
unpackedBlkHash, unpackedB32Address, rosettaError := unpackSpecialCaseTransactionIdentifier(specialTxID)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if unpackedB32Address != testB32Address {
t.Errorf("expected unpacked address to be %v not %v", testB32Address, unpackedB32Address)
}
if unpackedBlkHash.String() != testBlkHash.String() {
t.Errorf("expected blk hash to be %v not %v", unpackedBlkHash.String(), testBlkHash.String())
}
_, _, rosettaError = unpackSpecialCaseTransactionIdentifier(
&types.TransactionIdentifier{Hash: ""},
)
if rosettaError == nil {
t.Fatal("expected rosetta error")
}
if rosettaError.Code != common.CatchAllError.Code {
t.Error("expected error code to be catch call error")
}
}

@ -73,7 +73,7 @@ func (s *NetworkAPI) NetworkStatus(
}
targetHeight := int64(s.hmy.NodeAPI.GetMaxPeerHeight())
syncStatus := common.SyncingFinish
if s.hmy.NodeAPI.IsOutOfSync(s.hmy.BeaconChain) {
if s.hmy.NodeAPI.IsOutOfSync(s.hmy.BlockChain) {
syncStatus = common.SyncingNewBlock
} else if targetHeight == 0 {
syncStatus = common.SyncingStartup

@ -13,7 +13,7 @@ import (
)
var (
// BlockReward is the block reward, to be split evenly among block signers.
// BlockReward is the block reward, to be split evenly among block signers in pre-staking era.
BlockReward = new(big.Int).Mul(big.NewInt(24), big.NewInt(denominations.One))
// BaseStakedReward is the flat-rate block reward for epos staking launch.
// 28 ONE per block

Loading…
Cancel
Save