Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rosetta Implementation Cleanup FIX (Stage 3 of Node API Overhaul) #3402

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 88 additions & 24 deletions hmy/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/crypto/bls"
Expand All @@ -19,6 +20,8 @@ import (
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/availability"
stakingNetwork "github.com/harmony-one/harmony/staking/network"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -75,47 +78,108 @@ func (hmy *Harmony) GetBlockSigners(

// DetailedBlockSignerInfo contains all of the block singing information
type DetailedBlockSignerInfo 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[common.Address][]bls.SerializedPublicKey
// Signers are all the signers for the block
Signers shard.SlotList
// Committee when the block was signed.
Committee shard.SlotList
// 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 common.Hash
}

// GetDetailedBlockSignerInfo fetches the block signer information for any non-genesis block
func (hmy *Harmony) GetDetailedBlockSignerInfo(
ctx context.Context, blk *types.Block,
) (*DetailedBlockSignerInfo, error) {
slotList, mask, err := hmy.GetBlockSigners(
ctx, rpc.BlockNumber(blk.Number().Uint64()),
parentBlk, err := hmy.BlockByNumber(ctx, rpc.BlockNumber(blk.NumberU64()-1))
if err != nil {
return nil, err
}
parentShardState, err := hmy.BlockChain.ReadShardState(parentBlk.Epoch())
if err != nil {
return nil, err
}
committee, signers, _, err := availability.BallotResult(
parentBlk.Header(), blk.Header(), parentShardState, blk.ShardID(),
)
return &DetailedBlockSignerInfo{
Signers: signers,
Committee: committee,
BlockHash: blk.Hash(),
}, nil
}

// PreStakingBlockRewards are the rewards for a block in the pre-staking era (epoch < staking epoch).
type PreStakingBlockRewards map[common.Address]*big.Int

// GetPreStakingBlockRewards for the given block number.
// Calculated rewards are done exactly like chain.AccumulateRewardsAndCountSigs.
func (hmy *Harmony) GetPreStakingBlockRewards(
ctx context.Context, blk *types.Block,
) (PreStakingBlockRewards, error) {
if hmy.IsStakingEpoch(blk.Epoch()) {
return nil, fmt.Errorf("block %v is in staking era", blk.Number())
}

if cachedReward, ok := hmy.preStakingBlockRewardsCache.Get(blk.Hash()); ok {
return cachedReward.(PreStakingBlockRewards), nil
}
rewards := PreStakingBlockRewards{}

sigInfo, err := hmy.GetDetailedBlockSignerInfo(ctx, blk)
if err != nil {
return nil, err
}
last := big.NewInt(0)
count := big.NewInt(int64(len(sigInfo.Signers)))
for i, slot := range sigInfo.Signers {
rewardsForThisAddr, ok := rewards[slot.EcdsaAddress]
if !ok {
rewardsForThisAddr = big.NewInt(0)
}
cur := big.NewInt(0)
cur.Mul(stakingNetwork.BlockReward, big.NewInt(int64(i+1))).Div(cur, count)
reward := big.NewInt(0).Sub(cur, last)
rewards[slot.EcdsaAddress] = new(big.Int).Add(reward, rewardsForThisAddr)
last = cur
}

totalSigners := uint(0)
sigInfos := map[common.Address][]bls.SerializedPublicKey{}
for _, slot := range slotList {
if _, ok := sigInfos[slot.EcdsaAddress]; !ok {
sigInfos[slot.EcdsaAddress] = []bls.SerializedPublicKey{}
// Report tx fees of the coinbase (== leader)
receipts, err := hmy.GetReceipts(ctx, blk.Hash())
if err != nil {
return nil, err
}
txFees := big.NewInt(0)
for _, tx := range blk.Transactions() {
dbTx, _, _, receiptIndex := rawdb.ReadTransaction(hmy.ChainDb(), tx.Hash())
if dbTx == nil {
return nil, fmt.Errorf("could not find receipt for tx: %v", tx.Hash().String())
}
if ok, err := mask.KeyEnabled(slot.BLSPublicKey); ok && err == nil {
sigInfos[slot.EcdsaAddress] = append(sigInfos[slot.EcdsaAddress], slot.BLSPublicKey)
totalSigners++
if len(receipts) <= int(receiptIndex) {
return nil, fmt.Errorf("invalid receipt indext %v (>= num receipts: %v) for tx: %v",
receiptIndex, len(receipts), tx.Hash().String())
}
txFee := new(big.Int).Mul(tx.GasPrice(), big.NewInt(int64(receipts[receiptIndex].GasUsed)))
txFees = new(big.Int).Add(txFee, txFees)
}
return &DetailedBlockSignerInfo{
Signers: sigInfos,
Committee: slotList,
TotalKeysSigned: totalSigners,
Mask: mask,
BlockHash: blk.Hash(),
}, nil
for _, stx := range blk.StakingTransactions() {
dbsTx, _, _, receiptIndex := rawdb.ReadStakingTransaction(hmy.ChainDb(), stx.Hash())
if dbsTx == nil {
return nil, fmt.Errorf("could not find receipt for tx: %v", stx.Hash().String())
}
if len(receipts) <= int(receiptIndex) {
return nil, fmt.Errorf("invalid receipt indext %v (>= num receipts: %v) for tx: %v",
receiptIndex, len(receipts), stx.Hash().String())
}
txFee := new(big.Int).Mul(stx.GasPrice(), big.NewInt(int64(receipts[receiptIndex].GasUsed)))
txFees = new(big.Int).Add(txFee, txFees)
}
if amt, ok := rewards[blk.Header().Coinbase()]; ok {
rewards[blk.Header().Coinbase()] = new(big.Int).Add(amt, txFees)
} else {
rewards[blk.Header().Coinbase()] = txFees
}

hmy.preStakingBlockRewardsCache.Add(blk.Hash(), rewards)
return rewards, nil
}

// GetLatestChainHeaders ..
Expand Down
43 changes: 24 additions & 19 deletions hmy/hmy.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ import (
const (
// BloomBitsBlocks is the number of blocks a single bloom bit section vector
// contains on the server side.
BloomBitsBlocks uint64 = 4096
leaderCacheSize = 250 // Approx number of BLS keys in committee
undelegationPayoutsCacheSize = 500 // max number of epochs to store in cache
totalStakeCacheDuration = 20 // number of blocks where the returned total stake will remain the same
BloomBitsBlocks uint64 = 4096
leaderCacheSize = 250 // Approx number of BLS keys in committee
undelegationPayoutsCacheSize = 500 // max number of epochs to store in cache
preStakingBlockRewardsCacheSize = 1024 // max number of block rewards to store in cache
totalStakeCacheDuration = 20 // number of blocks where the returned total stake will remain the same
)

var (
Expand Down Expand Up @@ -67,6 +68,8 @@ type Harmony struct {
leaderCache *lru.Cache
// undelegationPayoutsCache to save on recomputation every epoch
undelegationPayoutsCache *lru.Cache
// preStakingBlockRewardsCache to save on recomputation for commonly checked blocks in epoch < staking epoch
preStakingBlockRewardsCache *lru.Cache
// totalStakeCache to save on recomputation for `totalStakeCacheDuration` blocks.
totalStakeCache *totalStakeCache
}
Expand Down Expand Up @@ -111,25 +114,27 @@ func New(
chainDb := nodeAPI.Blockchain().ChainDB()
leaderCache, _ := lru.New(leaderCacheSize)
undelegationPayoutsCache, _ := lru.New(undelegationPayoutsCacheSize)
preStakingBlockRewardsCache, _ := lru.New(preStakingBlockRewardsCacheSize)
totalStakeCache := newTotalStakeCache(totalStakeCacheDuration)
bloomIndexer := NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms)
bloomIndexer.Start(nodeAPI.Blockchain())
return &Harmony{
ShutdownChan: make(chan bool),
BloomRequests: make(chan chan *bloombits.Retrieval),
BloomIndexer: bloomIndexer,
BlockChain: nodeAPI.Blockchain(),
BeaconChain: nodeAPI.Beaconchain(),
TxPool: txPool,
CxPool: cxPool,
eventMux: new(event.TypeMux),
chainDb: chainDb,
NodeAPI: nodeAPI,
ChainID: nodeAPI.Blockchain().Config().ChainID.Uint64(),
ShardID: shardID,
leaderCache: leaderCache,
totalStakeCache: totalStakeCache,
undelegationPayoutsCache: undelegationPayoutsCache,
ShutdownChan: make(chan bool),
BloomRequests: make(chan chan *bloombits.Retrieval),
BloomIndexer: bloomIndexer,
BlockChain: nodeAPI.Blockchain(),
BeaconChain: nodeAPI.Beaconchain(),
TxPool: txPool,
CxPool: cxPool,
eventMux: new(event.TypeMux),
chainDb: chainDb,
NodeAPI: nodeAPI,
ChainID: nodeAPI.Blockchain().Config().ChainID.Uint64(),
ShardID: shardID,
leaderCache: leaderCache,
totalStakeCache: totalStakeCache,
undelegationPayoutsCache: undelegationPayoutsCache,
preStakingBlockRewardsCache: preStakingBlockRewardsCache,
}
}

Expand Down
29 changes: 7 additions & 22 deletions rosetta/services/block_special.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,31 +155,23 @@ func (s *BlockAPI) specialGenesisBlockTransaction(
}

// getPreStakingRewardTransactionIdentifiers is only used for the /block endpoint
// rewards for signing block n is paid out on block n+1
func (s *BlockAPI) getPreStakingRewardTransactionIdentifiers(
ctx context.Context, currBlock *hmytypes.Block,
) ([]*types.TransactionIdentifier, *types.Error) {
if currBlock.Number().Cmp(big.NewInt(1)) != 1 {
return nil, nil
}
blockNumToBeRewarded := currBlock.Number().Uint64() - 1
rewardedBlock, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNumToBeRewarded).EthBlockNumber())
if err != nil {
return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{
"message": err.Error(),
})
}
blockSigInfo, err := s.hmy.GetDetailedBlockSignerInfo(ctx, rewardedBlock)
rewards, err := s.hmy.GetPreStakingBlockRewards(ctx, currBlock)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
txIDs := []*types.TransactionIdentifier{}
for acc, signedBlsKeys := range blockSigInfo.Signers {
if len(signedBlsKeys) > 0 {
txIDs = append(txIDs, getSpecialCaseTransactionIdentifier(currBlock.Hash(), acc, SpecialPreStakingRewardTxID))
}
for addr := range rewards {
txIDs = append(txIDs, getSpecialCaseTransactionIdentifier(
currBlock.Hash(), addr, SpecialPreStakingRewardTxID,
))
}
return txIDs, nil
}
Expand Down Expand Up @@ -221,27 +213,20 @@ func (s *BlockAPI) preStakingRewardBlockTransaction(
if rosettaError != nil {
return nil, rosettaError
}
blockNumOfSigsForReward := blk.Number().Uint64() - 1
signedBlock, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNumOfSigsForReward).EthBlockNumber())
if err != nil {
return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{
"message": err.Error(),
})
}
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, err := s.hmy.GetDetailedBlockSignerInfo(ctx, signedBlock)
rewards, err := s.hmy.GetPreStakingBlockRewards(ctx, blk)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
transactions, rosettaError := FormatPreStakingRewardTransaction(txID, blockSignerInfo, address)
transactions, rosettaError := FormatPreStakingRewardTransaction(txID, rewards, address)
if rosettaError != nil {
return nil, rosettaError
}
Expand Down
43 changes: 8 additions & 35 deletions rosetta/services/tx_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/harmony-one/harmony/hmy"
internalCommon "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/rosetta/common"
stakingNetwork "github.com/harmony-one/harmony/staking/network"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)

Expand Down Expand Up @@ -176,44 +175,18 @@ func FormatGenesisTransaction(

// FormatPreStakingRewardTransaction for block rewards in pre-staking era for a given Bech-32 address.
func FormatPreStakingRewardTransaction(
txID *types.TransactionIdentifier, blockSigInfo *hmy.DetailedBlockSignerInfo, address ethcommon.Address,
txID *types.TransactionIdentifier, rewards hmy.PreStakingBlockRewards, address ethcommon.Address,
) (*types.Transaction, *types.Error) {
signatures, ok := blockSigInfo.Signers[address]
if !ok || len(signatures) == 0 {
return nil, &common.TransactionNotFoundError
}
accID, rosettaError := newAccountIdentifier(address)
if rosettaError != nil {
return nil, rosettaError
}

// Calculate rewards exactly like `AccumulateRewardsAndCountSigs` but short circuit when possible.
// WARNING: must do calculation in the order of the committee to get accurate values.
i := 0
last := big.NewInt(0)
rewardsForThisBlock := big.NewInt(0)
count := big.NewInt(int64(blockSigInfo.TotalKeysSigned))
for _, slot := range blockSigInfo.Committee {
rewardsForThisAddr := big.NewInt(0)
if keys, ok := blockSigInfo.Signers[slot.EcdsaAddress]; ok {
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 slot.EcdsaAddress == address {
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 era for block signer",
})
}
break
}
value, ok := rewards[address]
if !ok {
return nil, common.NewError(common.TransactionNotFoundError, map[string]interface{}{
"message": fmt.Sprintf("%v does not have any rewards for block",
internalCommon.MustAddressToBech32(address)),
})
}

return &types.Transaction{
Expand All @@ -227,7 +200,7 @@ func FormatPreStakingRewardTransaction(
Status: common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
Value: rewardsForThisBlock.String(),
Value: value.String(),
Currency: &common.NativeCurrency,
},
},
Expand Down
Loading