[Rosetta] Make all side effects one transaction (#3458)
* [rosetta] Start refactor of side effect transactions * Rename special transactions to side effect transactions * Rename various variables for consistancy within context of side effect transasctions * Remove all individual transaction for side effects, instead start process of batching all side effects under one transaction. * Finish batching of genesis side effect transaction. Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Add `getSideEffectTransaction` & refactor consumers This will hook in the logic to report all side effects under 1 transactions. Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Remove genesis logic path for special effect txs It is not integrated with the normal special effect logic Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Rename helper fns to use 'side effect' Replacing special case Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix special case operation index Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Consolidate side effect operation logic * Update tests for new side effect logic Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix import Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Update inline doc Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix comment for /block logic Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>pull/3464/head
parent
60108cd1b5
commit
4608b69365
@ -0,0 +1,168 @@ |
||||
package services |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"github.com/coinbase/rosetta-sdk-go/types" |
||||
ethcommon "github.com/ethereum/go-ethereum/common" |
||||
|
||||
"github.com/harmony-one/harmony/core" |
||||
hmytypes "github.com/harmony-one/harmony/core/types" |
||||
nodeconfig "github.com/harmony-one/harmony/internal/configs/node" |
||||
shardingconfig "github.com/harmony-one/harmony/internal/configs/sharding" |
||||
"github.com/harmony-one/harmony/rosetta/common" |
||||
"github.com/harmony-one/harmony/shard" |
||||
) |
||||
|
||||
// containsSideEffectTransaction checks if the block contains any side effect operations to report.
|
||||
func (s *BlockAPI) containsSideEffectTransaction( |
||||
ctx context.Context, blk *hmytypes.Block, |
||||
) bool { |
||||
if blk == nil { |
||||
return false |
||||
} |
||||
return s.hmy.IsCommitteeSelectionBlock(blk.Header()) || !s.hmy.IsStakingEpoch(blk.Epoch()) || blk.NumberU64() == 0 |
||||
} |
||||
|
||||
const ( |
||||
// SideEffectTransactionSuffix is use in the transaction identifier for each block that contains
|
||||
// side-effect operations.
|
||||
SideEffectTransactionSuffix = "side_effect" |
||||
blockHashStrLen = 64 |
||||
) |
||||
|
||||
// getSideEffectTransactionIdentifier fetches 'transaction identifier' for side effect operations
|
||||
// for a given block.
|
||||
// Side effects are genesis funds, pre-staking era block rewards, and undelegation payouts.
|
||||
// Must include block hash to guarantee uniqueness of tx identifiers.
|
||||
func getSideEffectTransactionIdentifier( |
||||
blockHash ethcommon.Hash, |
||||
) *types.TransactionIdentifier { |
||||
return &types.TransactionIdentifier{ |
||||
Hash: fmt.Sprintf("%v_%v", |
||||
blockHash.String(), SideEffectTransactionSuffix, |
||||
), |
||||
} |
||||
} |
||||
|
||||
// unpackSideEffectTransactionIdentifier returns the blockHash if the txID is formatted correctly.
|
||||
func unpackSideEffectTransactionIdentifier( |
||||
txID *types.TransactionIdentifier, |
||||
) (ethcommon.Hash, *types.Error) { |
||||
hash := txID.Hash |
||||
hash = strings.TrimPrefix(hash, "0x") |
||||
hash = strings.TrimPrefix(hash, "0X") |
||||
if len(hash) < blockHashStrLen || string(hash[blockHashStrLen]) != "_" || |
||||
hash[blockHashStrLen+1:] != SideEffectTransactionSuffix { |
||||
return ethcommon.Hash{}, common.NewError(common.CatchAllError, map[string]interface{}{ |
||||
"message": "unknown side effect transaction ID format", |
||||
}) |
||||
} |
||||
blkHash := ethcommon.HexToHash(hash[:blockHashStrLen]) |
||||
return blkHash, nil |
||||
} |
||||
|
||||
// getSideEffectTransaction returns the side effect transaction for a block if said block has one.
|
||||
// Side effects to reports are: genesis funds, undelegation payouts, permissioned-phase block rewards.
|
||||
func (s *BlockAPI) getSideEffectTransaction( |
||||
ctx context.Context, blk *hmytypes.Block, |
||||
) (*types.Transaction, *types.Error) { |
||||
if !s.containsSideEffectTransaction(ctx, blk) { |
||||
return nil, common.NewError(common.TransactionNotFoundError, map[string]interface{}{ |
||||
"message": "no side effect transaction found for given block", |
||||
}) |
||||
} |
||||
|
||||
var startingOpIndex *int64 |
||||
txOperations := []*types.Operation{} |
||||
updateStartingOpIndex := func(newOperations []*types.Operation) { |
||||
if len(newOperations) > 0 { |
||||
index := newOperations[len(newOperations)-1].OperationIdentifier.Index + 1 |
||||
startingOpIndex = &index |
||||
} |
||||
txOperations = append(txOperations, newOperations...) |
||||
} |
||||
|
||||
// Handle genesis funds
|
||||
if blk.NumberU64() == 0 { |
||||
ops, rosettaError := GetSideEffectOperationsFromGenesisSpec(getGenesisSpec(s.hmy.ShardID), startingOpIndex) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
updateStartingOpIndex(ops) |
||||
} |
||||
// Handle block rewards for epoch < staking epoch (permissioned-phase block rewards)
|
||||
// Note that block rewards don't start until the second block.
|
||||
if !s.hmy.IsStakingEpoch(blk.Epoch()) && blk.NumberU64() > 1 { |
||||
rewards, err := s.hmy.GetPreStakingBlockRewards(ctx, blk) |
||||
if err != nil { |
||||
return nil, common.NewError(common.CatchAllError, map[string]interface{}{ |
||||
"message": err.Error(), |
||||
}) |
||||
} |
||||
ops, rosettaError := GetSideEffectOperationsFromPreStakingRewards(rewards, startingOpIndex) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
updateStartingOpIndex(ops) |
||||
} |
||||
// Handle undelegation payout
|
||||
if s.hmy.IsCommitteeSelectionBlock(blk.Header()) && s.hmy.IsPreStakingEpoch(blk.Epoch()) { |
||||
payouts, err := s.hmy.GetUndelegationPayouts(ctx, blk.Epoch()) |
||||
if err != nil { |
||||
return nil, common.NewError(common.CatchAllError, map[string]interface{}{ |
||||
"message": err.Error(), |
||||
}) |
||||
} |
||||
ops, rosettaError := GetSideEffectOperationsFromUndelegationPayouts(payouts, startingOpIndex) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
updateStartingOpIndex(ops) |
||||
} |
||||
|
||||
return &types.Transaction{ |
||||
TransactionIdentifier: getSideEffectTransactionIdentifier(blk.Hash()), |
||||
Operations: txOperations, |
||||
}, nil |
||||
} |
||||
|
||||
// sideEffectBlockTransaction is a formatter for side effect transactions
|
||||
func (s *BlockAPI) sideEffectBlockTransaction( |
||||
ctx context.Context, request *types.BlockTransactionRequest, |
||||
) (*types.BlockTransactionResponse, *types.Error) { |
||||
// If no transaction info is found, check for special case transactions.
|
||||
blk, rosettaError := getBlock(ctx, s.hmy, &types.PartialBlockIdentifier{Index: &request.BlockIdentifier.Index}) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
blkHash, rosettaError := unpackSideEffectTransactionIdentifier( |
||||
request.TransactionIdentifier, |
||||
) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
if blkHash.String() != blk.Hash().String() { |
||||
return nil, common.NewError(common.TransactionNotFoundError, map[string]interface{}{ |
||||
"message": fmt.Sprintf("side effect transaction is not for block: %v", blk.NumberU64()), |
||||
}) |
||||
} |
||||
tx, rosettaError := s.getSideEffectTransaction(ctx, blk) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
return &types.BlockTransactionResponse{Transaction: tx}, nil |
||||
} |
||||
|
||||
// getGenesisSpec ..
|
||||
func getGenesisSpec(shardID uint32) *core.Genesis { |
||||
if shard.Schedule.GetNetworkID() == shardingconfig.MainNet { |
||||
return core.NewGenesisSpec(nodeconfig.Mainnet, shardID) |
||||
} |
||||
if shard.Schedule.GetNetworkID() == shardingconfig.LocalNet { |
||||
return core.NewGenesisSpec(nodeconfig.Localnet, shardID) |
||||
} |
||||
return core.NewGenesisSpec(nodeconfig.Testnet, shardID) |
||||
} |
@ -0,0 +1,46 @@ |
||||
package services |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
"reflect" |
||||
"testing" |
||||
|
||||
"github.com/coinbase/rosetta-sdk-go/types" |
||||
ethcommon "github.com/ethereum/go-ethereum/common" |
||||
|
||||
"github.com/harmony-one/harmony/rosetta/common" |
||||
) |
||||
|
||||
var ( |
||||
oneBig = big.NewInt(1e18) |
||||
tenOnes = new(big.Int).Mul(big.NewInt(10), oneBig) |
||||
twelveOnes = new(big.Int).Mul(big.NewInt(12), oneBig) |
||||
gasPrice = big.NewInt(10000) |
||||
) |
||||
|
||||
func TestSideEffectTransactionIdentifier(t *testing.T) { |
||||
testBlkHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238") |
||||
refTxID := &types.TransactionIdentifier{ |
||||
Hash: fmt.Sprintf("%v_%v", testBlkHash.String(), SideEffectTransactionSuffix), |
||||
} |
||||
specialTxID := getSideEffectTransactionIdentifier(testBlkHash) |
||||
if !reflect.DeepEqual(refTxID, specialTxID) { |
||||
t.Fatal("invalid for mate for special case TxID") |
||||
} |
||||
unpackedBlkHash, rosettaError := unpackSideEffectTransactionIdentifier(specialTxID) |
||||
if rosettaError != nil { |
||||
t.Fatal(rosettaError) |
||||
} |
||||
if unpackedBlkHash.String() != testBlkHash.String() { |
||||
t.Errorf("expected blk hash to be %v not %v", unpackedBlkHash.String(), testBlkHash.String()) |
||||
} |
||||
|
||||
_, rosettaError = unpackSideEffectTransactionIdentifier(&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") |
||||
} |
||||
} |
@ -1,319 +0,0 @@ |
||||
package services |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"math/big" |
||||
"strings" |
||||
|
||||
"github.com/coinbase/rosetta-sdk-go/types" |
||||
ethcommon "github.com/ethereum/go-ethereum/common" |
||||
|
||||
"github.com/harmony-one/harmony/core" |
||||
hmytypes "github.com/harmony-one/harmony/core/types" |
||||
internalCommon "github.com/harmony-one/harmony/internal/common" |
||||
nodeconfig "github.com/harmony-one/harmony/internal/configs/node" |
||||
shardingconfig "github.com/harmony-one/harmony/internal/configs/sharding" |
||||
"github.com/harmony-one/harmony/rosetta/common" |
||||
"github.com/harmony-one/harmony/rpc" |
||||
"github.com/harmony-one/harmony/shard" |
||||
) |
||||
|
||||
// SpecialTransactionSuffix enum for all special transactions
|
||||
type SpecialTransactionSuffix uint |
||||
|
||||
// Special transaction suffixes that are specific to the rosetta package
|
||||
const ( |
||||
SpecialGenesisTxID SpecialTransactionSuffix = iota |
||||
SpecialPreStakingRewardTxID |
||||
SpecialUndelegationPayoutTxID |
||||
) |
||||
|
||||
// Length for special case transaction identifiers
|
||||
const ( |
||||
blockHashStrLen = 64 |
||||
bech32AddrStrLen = 42 |
||||
) |
||||
|
||||
// String ..
|
||||
func (s SpecialTransactionSuffix) String() string { |
||||
return [...]string{"genesis", "reward", "undelegation"}[s] |
||||
} |
||||
|
||||
// getSpecialCaseTransactionIdentifier fetches 'transaction identifiers' for a given block-hash and suffix.
|
||||
// Special cases include genesis transactions, pre-staking era block rewards, and undelegation payouts.
|
||||
// Must include block hash to guarantee uniqueness of tx identifiers.
|
||||
func getSpecialCaseTransactionIdentifier( |
||||
blockHash ethcommon.Hash, address ethcommon.Address, suffix SpecialTransactionSuffix, |
||||
) *types.TransactionIdentifier { |
||||
return &types.TransactionIdentifier{ |
||||
Hash: fmt.Sprintf("%v_%v_%v", |
||||
blockHash.String(), internalCommon.MustAddressToBech32(address), suffix.String(), |
||||
), |
||||
} |
||||
} |
||||
|
||||
// unpackSpecialCaseTransactionIdentifier returns the suffix & blockHash if the txID is formatted correctly.
|
||||
func unpackSpecialCaseTransactionIdentifier( |
||||
txID *types.TransactionIdentifier, expectedSuffix SpecialTransactionSuffix, |
||||
) (ethcommon.Hash, ethcommon.Address, *types.Error) { |
||||
hash := txID.Hash |
||||
hash = strings.TrimPrefix(hash, "0x") |
||||
hash = strings.TrimPrefix(hash, "0X") |
||||
minCharCount := blockHashStrLen + bech32AddrStrLen + 2 |
||||
if len(hash) < minCharCount || string(hash[blockHashStrLen]) != "_" || |
||||
string(hash[minCharCount-1]) != "_" || expectedSuffix.String() != hash[minCharCount:] { |
||||
return ethcommon.Hash{}, ethcommon.Address{}, common.NewError(common.CatchAllError, map[string]interface{}{ |
||||
"message": "unknown special case transaction ID format", |
||||
}) |
||||
} |
||||
blkHash := ethcommon.HexToHash(hash[:blockHashStrLen]) |
||||
addr := internalCommon.MustBech32ToAddress(hash[blockHashStrLen+1 : minCharCount-1]) |
||||
return blkHash, addr, 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) { |
||||
var currBlockID, prevBlockID *types.BlockIdentifier |
||||
currBlockID = &types.BlockIdentifier{ |
||||
Index: blk.Number().Int64(), |
||||
Hash: blk.Hash().String(), |
||||
} |
||||
prevBlockID = currBlockID |
||||
|
||||
metadata, err := types.MarshalMap(BlockMetadata{ |
||||
Epoch: blk.Epoch(), |
||||
}) |
||||
if err != nil { |
||||
return nil, common.NewError(common.CatchAllError, map[string]interface{}{ |
||||
"message": err.Error(), |
||||
}) |
||||
} |
||||
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.
|
||||
Metadata: metadata, |
||||
} |
||||
|
||||
otherTransactions := []*types.TransactionIdentifier{} |
||||
// Report initial genesis funds as transactions to fit API.
|
||||
for _, tx := range getPseudoTransactionForGenesis(getGenesisSpec(blk.ShardID())) { |
||||
if tx.To() == nil { |
||||
return nil, common.NewError(common.CatchAllError, nil) |
||||
} |
||||
otherTransactions = append( |
||||
otherTransactions, getSpecialCaseTransactionIdentifier(blk.Hash(), *tx.To(), SpecialGenesisTxID), |
||||
) |
||||
} |
||||
|
||||
return &types.BlockResponse{ |
||||
Block: responseBlock, |
||||
OtherTransactions: otherTransactions, |
||||
}, nil |
||||
} |
||||
|
||||
// getPseudoTransactionForGenesis to create unsigned transaction that contain genesis funds.
|
||||
// Note that this is for internal usage only. Genesis funds are not transactions.
|
||||
func getPseudoTransactionForGenesis(spec *core.Genesis) []*hmytypes.Transaction { |
||||
txs := []*hmytypes.Transaction{} |
||||
for acc, bal := range spec.Alloc { |
||||
txs = append(txs, hmytypes.NewTransaction( |
||||
0, acc, spec.ShardID, bal.Balance, 0, big.NewInt(0), spec.ExtraData, |
||||
)) |
||||
} |
||||
return txs |
||||
} |
||||
|
||||
// specialGenesisBlockTransaction is a special handler for genesis block transactions
|
||||
func (s *BlockAPI) specialGenesisBlockTransaction( |
||||
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, address, rosettaError := unpackSpecialCaseTransactionIdentifier( |
||||
request.TransactionIdentifier, SpecialGenesisTxID, |
||||
) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
if blkHash.String() != genesisBlock.Hash().String() { |
||||
return nil, &common.TransactionNotFoundError |
||||
} |
||||
txs, rosettaError := FormatGenesisTransaction(request.TransactionIdentifier, address, s.hmy.ShardID) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
return &types.BlockTransactionResponse{Transaction: txs}, nil |
||||
} |
||||
|
||||
// getPreStakingRewardTransactionIdentifiers is only used for the /block endpoint
|
||||
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 |
||||
} |
||||
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 addr := range rewards { |
||||
txIDs = append(txIDs, getSpecialCaseTransactionIdentifier( |
||||
currBlock.Hash(), addr, SpecialPreStakingRewardTxID, |
||||
)) |
||||
} |
||||
return txIDs, nil |
||||
} |
||||
|
||||
// specialBlockTransaction is a formatter for special, non-genesis, transactions
|
||||
func (s *BlockAPI) specialBlockTransaction( |
||||
ctx context.Context, request *types.BlockTransactionRequest, |
||||
) (*types.BlockTransactionResponse, *types.Error) { |
||||
// If no transaction info is found, check for special case transactions.
|
||||
blk, rosettaError := getBlock(ctx, s.hmy, &types.PartialBlockIdentifier{Index: &request.BlockIdentifier.Index}) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
if s.hmy.IsCommitteeSelectionBlock(blk.Header()) { |
||||
// Note that undelegation payout MUST be checked before reporting error in pre-staking & staking era.
|
||||
response, rosettaError := s.undelegationPayoutBlockTransaction(ctx, request.TransactionIdentifier, blk) |
||||
if rosettaError != nil && !s.hmy.IsStakingEpoch(blk.Epoch()) && s.hmy.IsPreStakingEpoch(blk.Epoch()) { |
||||
// Handle edge case special transaction for pre-staking era
|
||||
return s.preStakingRewardBlockTransaction(ctx, request.TransactionIdentifier, blk) |
||||
} |
||||
return response, rosettaError |
||||
} |
||||
if !s.hmy.IsStakingEpoch(blk.Epoch()) { |
||||
return s.preStakingRewardBlockTransaction(ctx, request.TransactionIdentifier, blk) |
||||
} |
||||
return nil, &common.TransactionNotFoundError |
||||
} |
||||
|
||||
// preStakingRewardBlockTransaction is a special handler for pre-staking era
|
||||
func (s *BlockAPI) preStakingRewardBlockTransaction( |
||||
ctx context.Context, txID *types.TransactionIdentifier, blk *hmytypes.Block, |
||||
) (*types.BlockTransactionResponse, *types.Error) { |
||||
if blk.Number().Cmp(big.NewInt(1)) != 1 { |
||||
return nil, common.NewError(common.TransactionNotFoundError, map[string]interface{}{ |
||||
"message": "block does not contain any pre-staking era block rewards", |
||||
}) |
||||
} |
||||
blkHash, address, rosettaError := unpackSpecialCaseTransactionIdentifier(txID, SpecialPreStakingRewardTxID) |
||||
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(), |
||||
), |
||||
}) |
||||
} |
||||
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, rewards, address) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
return &types.BlockTransactionResponse{Transaction: transactions}, nil |
||||
} |
||||
|
||||
// undelegationPayoutBlockTransaction is a special handler for undelegation payout transactions
|
||||
func (s *BlockAPI) undelegationPayoutBlockTransaction( |
||||
ctx context.Context, txID *types.TransactionIdentifier, blk *hmytypes.Block, |
||||
) (*types.BlockTransactionResponse, *types.Error) { |
||||
blkHash, address, rosettaError := unpackSpecialCaseTransactionIdentifier(txID, SpecialUndelegationPayoutTxID) |
||||
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(), |
||||
), |
||||
}) |
||||
} |
||||
|
||||
delegatorPayouts, err := s.hmy.GetUndelegationPayouts(ctx, blk.Epoch()) |
||||
if err != nil { |
||||
return nil, common.NewError(common.CatchAllError, map[string]interface{}{ |
||||
"message": err.Error(), |
||||
}) |
||||
} |
||||
|
||||
transactions, rosettaError := FormatUndelegationPayoutTransaction(txID, delegatorPayouts, address) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
return &types.BlockTransactionResponse{Transaction: transactions}, nil |
||||
} |
||||
|
||||
// getAllUndelegationPayoutTransactions is only used for the /block endpoint
|
||||
func (s *BlockAPI) getAllUndelegationPayoutTransactions( |
||||
ctx context.Context, blk *hmytypes.Block, |
||||
) ([]*types.Transaction, *types.Error) { |
||||
if !s.hmy.IsCommitteeSelectionBlock(blk.Header()) { |
||||
return []*types.Transaction{}, nil |
||||
} |
||||
|
||||
delegatorPayouts, err := s.hmy.GetUndelegationPayouts(ctx, blk.Epoch()) |
||||
if err != nil { |
||||
return nil, common.NewError(common.CatchAllError, map[string]interface{}{ |
||||
"message": err.Error(), |
||||
}) |
||||
} |
||||
|
||||
transactions := []*types.Transaction{} |
||||
for delegator, payout := range delegatorPayouts { |
||||
accID, rosettaError := newAccountIdentifier(delegator) |
||||
if rosettaError != nil { |
||||
return nil, rosettaError |
||||
} |
||||
transactions = append(transactions, &types.Transaction{ |
||||
TransactionIdentifier: getSpecialCaseTransactionIdentifier( |
||||
blk.Hash(), delegator, SpecialUndelegationPayoutTxID, |
||||
), |
||||
Operations: []*types.Operation{ |
||||
{ |
||||
OperationIdentifier: &types.OperationIdentifier{ |
||||
Index: 0, // There is no gas expenditure for undelegation payout
|
||||
}, |
||||
Type: common.UndelegationPayoutOperation, |
||||
Status: common.SuccessOperationStatus.Status, |
||||
Account: accID, |
||||
Amount: &types.Amount{ |
||||
Value: payout.String(), |
||||
Currency: &common.NativeCurrency, |
||||
}, |
||||
}, |
||||
}, |
||||
}) |
||||
} |
||||
return transactions, nil |
||||
} |
||||
|
||||
// getGenesisSpec ..
|
||||
func getGenesisSpec(shardID uint32) *core.Genesis { |
||||
if shard.Schedule.GetNetworkID() == shardingconfig.MainNet { |
||||
return core.NewGenesisSpec(nodeconfig.Mainnet, shardID) |
||||
} |
||||
if shard.Schedule.GetNetworkID() == shardingconfig.LocalNet { |
||||
return core.NewGenesisSpec(nodeconfig.Localnet, shardID) |
||||
} |
||||
return core.NewGenesisSpec(nodeconfig.Testnet, shardID) |
||||
} |
@ -1,77 +0,0 @@ |
||||
package services |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
"reflect" |
||||
"testing" |
||||
|
||||
"github.com/coinbase/rosetta-sdk-go/types" |
||||
ethcommon "github.com/ethereum/go-ethereum/common" |
||||
|
||||
"github.com/harmony-one/harmony/core" |
||||
internalCommon "github.com/harmony-one/harmony/internal/common" |
||||
nodeconfig "github.com/harmony-one/harmony/internal/configs/node" |
||||
"github.com/harmony-one/harmony/rosetta/common" |
||||
) |
||||
|
||||
var ( |
||||
oneBig = big.NewInt(1e18) |
||||
tenOnes = new(big.Int).Mul(big.NewInt(10), oneBig) |
||||
twelveOnes = new(big.Int).Mul(big.NewInt(12), oneBig) |
||||
gasPrice = big.NewInt(10000) |
||||
) |
||||
|
||||
func TestGetPseudoTransactionForGenesis(t *testing.T) { |
||||
genesisSpec := core.NewGenesisSpec(nodeconfig.Testnet, 0) |
||||
txs := getPseudoTransactionForGenesis(genesisSpec) |
||||
for acc := range genesisSpec.Alloc { |
||||
found := false |
||||
for _, tx := range txs { |
||||
if acc == *tx.To() { |
||||
found = true |
||||
break |
||||
} |
||||
} |
||||
if !found { |
||||
t.Error("unable to find genesis account in generated pseudo transactions") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestSpecialCaseTransactionIdentifier(t *testing.T) { |
||||
testBlkHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238") |
||||
testB32Address := "one10g7kfque6ew2jjfxxa6agkdwk4wlyjuncp6gwz" |
||||
testAddress := internalCommon.MustBech32ToAddress(testB32Address) |
||||
refTxID := &types.TransactionIdentifier{ |
||||
Hash: fmt.Sprintf("%v_%v_%v", testBlkHash.String(), testB32Address, SpecialGenesisTxID.String()), |
||||
} |
||||
specialTxID := getSpecialCaseTransactionIdentifier( |
||||
testBlkHash, testAddress, SpecialGenesisTxID, |
||||
) |
||||
if !reflect.DeepEqual(refTxID, specialTxID) { |
||||
t.Fatal("invalid for mate for special case TxID") |
||||
} |
||||
unpackedBlkHash, unpackedAddress, rosettaError := unpackSpecialCaseTransactionIdentifier( |
||||
specialTxID, SpecialGenesisTxID, |
||||
) |
||||
if rosettaError != nil { |
||||
t.Fatal(rosettaError) |
||||
} |
||||
if unpackedAddress != testAddress { |
||||
t.Errorf("expected unpacked address to be %v not %v", testAddress.String(), unpackedAddress.String()) |
||||
} |
||||
if unpackedBlkHash.String() != testBlkHash.String() { |
||||
t.Errorf("expected blk hash to be %v not %v", unpackedBlkHash.String(), testBlkHash.String()) |
||||
} |
||||
|
||||
_, _, rosettaError = unpackSpecialCaseTransactionIdentifier( |
||||
&types.TransactionIdentifier{Hash: ""}, SpecialGenesisTxID, |
||||
) |
||||
if rosettaError == nil { |
||||
t.Fatal("expected rosetta error") |
||||
} |
||||
if rosettaError.Code != common.CatchAllError.Code { |
||||
t.Error("expected error code to be catch call error") |
||||
} |
||||
} |
Loading…
Reference in new issue