[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
Daniel Van Der Maden 4 years ago committed by GitHub
parent 60108cd1b5
commit 4608b69365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      rosetta/common/operations.go
  2. 36
      rosetta/services/block.go
  3. 168
      rosetta/services/block_side_effect.go
  4. 46
      rosetta/services/block_side_effect_test.go
  5. 319
      rosetta/services/block_special.go
  6. 77
      rosetta/services/block_special_test.go
  7. 109
      rosetta/services/tx_format.go
  8. 133
      rosetta/services/tx_format_test.go
  9. 81
      rosetta/services/tx_operation.go
  10. 91
      rosetta/services/tx_operation_test.go

@ -24,15 +24,15 @@ const (
// ContractCreationOperation is an operation that only affects the native currency.
ContractCreationOperation = "ContractCreation"
// GenesisFundsOperation is a special operation for genesis block only.
// GenesisFundsOperation is a side effect operation for genesis block only.
// Note that no transaction can be constructed with this operation.
GenesisFundsOperation = "Genesis"
// PreStakingBlockRewardOperation is a special operation for pre-staking era only.
// PreStakingBlockRewardOperation is a side effect operation for pre-staking era only.
// Note that no transaction can be constructed with this operation.
PreStakingBlockRewardOperation = "PreStakingBlockReward"
// UndelegationPayoutOperation is a special operation for committee election block only.
// UndelegationPayoutOperation is a side effect operation for committee election block only.
// Note that no transaction can be constructed with this operation.
UndelegationPayoutOperation = "UndelegationPayout"
)

@ -48,15 +48,14 @@ func (s *BlockAPI) Block(
return nil, rosettaError
}
// Format genesis block if it is requested.
if blk.Number().Uint64() == 0 {
return s.genesisBlock(ctx, request, blk)
}
currBlockID = &types.BlockIdentifier{
Index: blk.Number().Int64(),
Hash: blk.Hash().String(),
}
if blk.NumberU64() == 0 {
prevBlockID = currBlockID
} else {
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{}{
@ -67,13 +66,17 @@ func (s *BlockAPI) Block(
Index: prevBlock.Number().Int64(),
Hash: prevBlock.Hash().String(),
}
}
// Report undelegation payouts as transactions to fit API.
// Report all transactions here since all undelegation payout amounts are known after fetching payouts.
transactions, rosettaError := s.getAllUndelegationPayoutTransactions(ctx, blk)
// Report any side effect transaction now as it can be computed & cached on block fetch.
transactions := []*types.Transaction{}
if s.containsSideEffectTransaction(ctx, blk) {
tx, rosettaError := s.getSideEffectTransaction(ctx, blk)
if rosettaError != nil {
return nil, rosettaError
}
transactions = append(transactions, tx)
}
metadata, err := types.MarshalMap(BlockMetadata{
Epoch: blk.Epoch(),
@ -110,14 +113,6 @@ func (s *BlockAPI) Block(
})
}
}
// Report pre-staking era block rewards as transactions to fit API.
if !s.hmy.IsStakingEpoch(blk.Epoch()) {
preStakingRewardTxIDs, rosettaError := s.getPreStakingRewardTransactionIdentifiers(ctx, blk)
if rosettaError != nil {
return nil, rosettaError
}
otherTransactions = append(otherTransactions, preStakingRewardTxIDs...)
}
return &types.BlockResponse{
Block: responseBlock,
@ -133,17 +128,12 @@ func (s *BlockAPI) BlockTransaction(
return nil, err
}
// Format genesis block transaction request
if request.BlockIdentifier.Index == 0 {
return s.specialGenesisBlockTransaction(ctx, request)
}
blockHash := ethcommon.HexToHash(request.BlockIdentifier.Hash)
txHash := ethcommon.HexToHash(request.TransactionIdentifier.Hash)
txInfo, rosettaError := s.getTransactionInfo(ctx, blockHash, txHash)
if rosettaError != nil {
// If no transaction info is found, check for special case transactions.
response, rosettaError2 := s.specialBlockTransaction(ctx, request)
// If no transaction info is found, check for side effect case transaction.
response, rosettaError2 := s.sideEffectBlockTransaction(ctx, request)
if rosettaError2 != nil && rosettaError2.Code != common.TransactionNotFoundError.Code {
return nil, common.NewError(common.TransactionNotFoundError, map[string]interface{}{
"from_error": rosettaError2,

@ -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")
}
}

@ -9,8 +9,6 @@ import (
ethcommon "github.com/ethereum/go-ethereum/common"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/hmy"
internalCommon "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/rosetta/common"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
@ -32,7 +30,7 @@ func FormatTransaction(
case *stakingTypes.StakingTransaction:
isStaking = true
stakingTx := tx.(*stakingTypes.StakingTransaction)
operations, rosettaError = GetOperationsFromStakingTransaction(stakingTx, receipt)
operations, rosettaError = GetNativeOperationsFromStakingTransaction(stakingTx, receipt)
if rosettaError != nil {
return nil, rosettaError
}
@ -150,111 +148,6 @@ func FormatCrossShardReceiverTransaction(
}, nil
}
// FormatGenesisTransaction for genesis block's initial balances
func FormatGenesisTransaction(
txID *types.TransactionIdentifier, targetAddr ethcommon.Address, shardID uint32,
) (fmtTx *types.Transaction, rosettaError *types.Error) {
var b32Addr string
targetB32Addr := internalCommon.MustAddressToBech32(targetAddr)
for _, tx := range getPseudoTransactionForGenesis(getGenesisSpec(shardID)) {
if tx.To() == nil {
return nil, common.NewError(common.CatchAllError, nil)
}
b32Addr = internalCommon.MustAddressToBech32(*tx.To())
if targetB32Addr == b32Addr {
accID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
return nil, rosettaError
}
return &types.Transaction{
TransactionIdentifier: txID,
Operations: []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.GenesisFundsOperation,
Status: common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
Value: tx.Value().String(),
Currency: &common.NativeCurrency,
},
},
},
}, nil
}
}
return nil, &common.TransactionNotFoundError
}
// FormatPreStakingRewardTransaction for block rewards in pre-staking era for a given Bech-32 address.
func FormatPreStakingRewardTransaction(
txID *types.TransactionIdentifier, rewards hmy.PreStakingBlockRewards, address ethcommon.Address,
) (*types.Transaction, *types.Error) {
accID, rosettaError := newAccountIdentifier(address)
if rosettaError != nil {
return nil, rosettaError
}
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{
TransactionIdentifier: txID,
Operations: []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.PreStakingBlockRewardOperation,
Status: common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
Value: value.String(),
Currency: &common.NativeCurrency,
},
},
},
}, nil
}
// FormatUndelegationPayoutTransaction for undelegation payouts at committee selection block
func FormatUndelegationPayoutTransaction(
txID *types.TransactionIdentifier, delegatorPayouts hmy.UndelegationPayouts, address ethcommon.Address,
) (*types.Transaction, *types.Error) {
accID, rosettaError := newAccountIdentifier(address)
if rosettaError != nil {
return nil, rosettaError
}
payout, ok := delegatorPayouts[address]
if !ok {
return nil, &common.TransactionNotFoundError
}
return &types.Transaction{
TransactionIdentifier: txID,
Operations: []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.UndelegationPayoutOperation,
Status: common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
Value: payout.String(),
Currency: &common.NativeCurrency,
},
},
},
}, nil
}
// negativeBigValue formats a transaction value as a string
func negativeBigValue(num *big.Int) string {
value := "0"

@ -8,11 +8,9 @@ import (
"testing"
"github.com/coinbase/rosetta-sdk-go/types"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/hmy"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/rosetta/common"
stakingTypes "github.com/harmony-one/harmony/staking/types"
@ -169,137 +167,6 @@ func testFormatPlainTransaction(
}
}
func TestFormatGenesisTransaction(t *testing.T) {
genesisSpec := getGenesisSpec(0)
testBlkHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238")
for acc := range genesisSpec.Alloc {
txID := getSpecialCaseTransactionIdentifier(testBlkHash, acc, SpecialGenesisTxID)
tx, rosettaError := FormatGenesisTransaction(txID, acc, 0)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(txID, tx.TransactionIdentifier) {
t.Error("expected transaction ID of formatted tx to be same as requested")
}
if len(tx.Operations) != 1 {
t.Error("expected exactly 1 operation")
}
if err := assertNativeOperationTypeUniquenessInvariant(tx.Operations); err != nil {
t.Error(err)
}
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 TestFormatPreStakingRewardTransactionSuccess(t *testing.T) {
testKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
testBlkHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238")
testRewards := hmy.PreStakingBlockRewards{
testAddr: big.NewInt(1),
}
refTxID := getSpecialCaseTransactionIdentifier(testBlkHash, testAddr, SpecialPreStakingRewardTxID)
tx, rosettaError := FormatPreStakingRewardTransaction(refTxID, testRewards, testAddr)
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 err := assertNativeOperationTypeUniquenessInvariant(tx.Operations); err != nil {
t.Error(err)
}
if tx.Operations[0].OperationIdentifier.Index != 0 {
t.Error("expected operational ID to be 0")
}
if tx.Operations[0].Type != common.PreStakingBlockRewardOperation {
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")
}
}
func TestFormatPreStakingRewardTransactionFail(t *testing.T) {
testKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
testBlkHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238")
testRewards := hmy.PreStakingBlockRewards{
FormatDefaultSenderAddress: big.NewInt(1),
}
testTxID := getSpecialCaseTransactionIdentifier(testBlkHash, testAddr, SpecialPreStakingRewardTxID)
_, rosettaError := FormatPreStakingRewardTransaction(testTxID, testRewards, testAddr)
if rosettaError == nil {
t.Fatal("expected rosetta error")
}
if common.TransactionNotFoundError.Code != rosettaError.Code {
t.Error("expected transaction not found error")
}
}
func TestFormatUndelegationPayoutTransaction(t *testing.T) {
testKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
testPayout := big.NewInt(1e10)
testDelegatorPayouts := hmy.UndelegationPayouts{
testAddr: testPayout,
}
testBlockHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238")
testTxID := getSpecialCaseTransactionIdentifier(testBlockHash, testAddr, SpecialUndelegationPayoutTxID)
tx, rosettaError := FormatUndelegationPayoutTransaction(testTxID, testDelegatorPayouts, testAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if len(tx.Operations) != 1 {
t.Fatal("expected tx operations to be of length 1")
}
if err := assertNativeOperationTypeUniquenessInvariant(tx.Operations); err != nil {
t.Error(err)
}
if tx.Operations[0].OperationIdentifier.Index != 0 {
t.Error("Expect first operation to be index 0")
}
if tx.Operations[0].Type != common.UndelegationPayoutOperation {
t.Errorf("Expect operation type to be: %v", common.UndelegationPayoutOperation)
}
if tx.Operations[0].Status != common.SuccessOperationStatus.Status {
t.Error("expected successful operation status")
}
if tx.Operations[0].Amount.Value != fmt.Sprintf("%v", testPayout) {
t.Errorf("expect payout to be %v", testPayout)
}
_, rosettaError = FormatUndelegationPayoutTransaction(testTxID, hmy.UndelegationPayouts{}, testAddr)
if rosettaError == nil {
t.Fatal("Expect error for no payouts found")
}
if rosettaError.Code != common.TransactionNotFoundError.Code {
t.Errorf("expect error code %v", common.TransactionNotFoundError.Code)
}
}
func testFormatCrossShardSenderTransaction(
t *testing.T, gasLimit, gasUsed uint64, senderKey, receiverKey *ecdsa.PrivateKey,
) {

@ -7,7 +7,9 @@ import (
"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"
"github.com/harmony-one/harmony/hmy"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/rosetta/common"
rpcV2 "github.com/harmony-one/harmony/rpc/v2"
@ -32,7 +34,7 @@ func GetNativeOperationsFromTransaction(
// All operations excepts for cross-shard tx payout expend gas
gasExpended := new(big.Int).Mul(new(big.Int).SetUint64(receipt.GasUsed), tx.GasPrice())
gasOperations := newNativeOperations(gasExpended, accountID)
gasOperations := newNativeOperationsWithGas(gasExpended, accountID)
// Handle different cases of plain transactions
var txOperations []*types.Operation
@ -56,9 +58,9 @@ func GetNativeOperationsFromTransaction(
return append(gasOperations, txOperations...), nil
}
// GetOperationsFromStakingTransaction for all staking directives
// GetNativeOperationsFromStakingTransaction for all staking directives
// Note that only native operations can come from staking transactions.
func GetOperationsFromStakingTransaction(
func GetNativeOperationsFromStakingTransaction(
tx *stakingTypes.StakingTransaction, receipt *hmytypes.Receipt,
) ([]*types.Operation, *types.Error) {
senderAddress, err := tx.SenderAddress()
@ -72,7 +74,7 @@ func GetOperationsFromStakingTransaction(
// All operations excepts for cross-shard tx payout expend gas
gasExpended := new(big.Int).Mul(new(big.Int).SetUint64(receipt.GasUsed), tx.GasPrice())
gasOperations := newNativeOperations(gasExpended, accountID)
gasOperations := newNativeOperationsWithGas(gasExpended, accountID)
// Format staking message for metadata using decimal numbers (hence usage of rpcV2)
rpcStakingTx, err := rpcV2.NewStakingTransaction(tx, ethcommon.Hash{}, 0, 0, 0)
@ -125,6 +127,73 @@ func GetOperationsFromStakingTransaction(
}), nil
}
// GetSideEffectOperationsFromUndelegationPayouts from the given payouts.
// If the startingOperationIndex is provided, all operations will be indexed starting from the given operation index.
func GetSideEffectOperationsFromUndelegationPayouts(
payouts hmy.UndelegationPayouts, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
return getSideEffectOperationsFromValueMap(
payouts, common.UndelegationPayoutOperation, startingOperationIndex,
)
}
// GetSideEffectOperationsFromPreStakingRewards from the given rewards.
// If the startingOperationIndex is provided, all operations will be indexed starting from the given operation index.
func GetSideEffectOperationsFromPreStakingRewards(
rewards hmy.PreStakingBlockRewards, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
return getSideEffectOperationsFromValueMap(
rewards, common.PreStakingBlockRewardOperation, startingOperationIndex,
)
}
// GetSideEffectOperationsFromGenesisSpec for the given spec.
// If the startingOperationIndex is provided, all operations will be indexed starting from the given operation index.
func GetSideEffectOperationsFromGenesisSpec(
spec *core.Genesis, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
valueMap := map[ethcommon.Address]*big.Int{}
for address, acc := range spec.Alloc {
valueMap[address] = acc.Balance
}
return getSideEffectOperationsFromValueMap(
valueMap, common.GenesisFundsOperation, startingOperationIndex,
)
}
// getSideEffectOperationsFromValueMap is a helper for side effect operation construction from a value map.
func getSideEffectOperationsFromValueMap(
valueMap map[ethcommon.Address]*big.Int, opType string, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
var opIndex int64
operations := []*types.Operation{}
if startingOperationIndex != nil {
opIndex = *startingOperationIndex
} else {
opIndex = 0
}
for address, value := range valueMap {
accID, rosettaError := newAccountIdentifier(address)
if rosettaError != nil {
return nil, rosettaError
}
operations = append(operations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: opIndex,
},
Type: opType,
Status: common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
Value: value.String(),
Currency: &common.NativeCurrency,
},
})
opIndex++
}
return operations, nil
}
func getAmountFromCreateValidatorMessage(data []byte) (*types.Amount, *types.Error) {
msg, err := stakingTypes.RLPDecodeStakeMsg(data, stakingTypes.DirectiveCreateValidator)
if err != nil {
@ -382,9 +451,9 @@ func newContractCreationNativeOperations(
}, nil
}
// newNativeOperations creates a new operation with the gas fee as the first operation.
// newNativeOperationsWithGas creates a new operation with the gas fee as the first operation.
// Note: the gas fee is gasPrice * gasUsed.
func newNativeOperations(
func newNativeOperationsWithGas(
gasFeeInATTO *big.Int, accountID *types.AccountIdentifier,
) []*types.Operation {
return []*types.Operation{

@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
hmytypes "github.com/harmony-one/harmony/core/types"
internalCommon "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/rosetta/common"
"github.com/harmony-one/harmony/staking"
@ -59,7 +60,7 @@ func TestGetStakingOperationsFromCreateValidator(t *testing.T) {
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
@ -74,7 +75,7 @@ func TestGetStakingOperationsFromCreateValidator(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -86,6 +87,74 @@ func TestGetStakingOperationsFromCreateValidator(t *testing.T) {
}
}
func TestGetSideEffectOperationsFromValueMap(t *testing.T) {
testAcc1 := crypto.PubkeyToAddress(internalCommon.MustGeneratePrivateKey().PublicKey)
testAcc2 := crypto.PubkeyToAddress(internalCommon.MustGeneratePrivateKey().PublicKey)
testAmount1 := big.NewInt(12000)
testAmount2 := big.NewInt(10000)
testPayouts := map[ethcommon.Address]*big.Int{
testAcc1: testAmount1,
testAcc2: testAmount2,
}
testType := common.GenesisFundsOperation
ops, rosettaError := getSideEffectOperationsFromValueMap(testPayouts, testType, nil)
if rosettaError != nil {
t.Fatal(rosettaError)
}
for i, op := range ops {
if int64(i) != op.OperationIdentifier.Index {
t.Errorf("expected operation %v to have operation index %v", i, i)
}
address, err := getAddress(op.Account)
if err != nil {
t.Fatal(err)
}
if value, ok := testPayouts[address]; !ok {
t.Errorf("operation %v has address that is not in test map", i)
} else if value.String() != op.Amount.Value {
t.Errorf("operation %v has wrong value (%v != %v)", i, value.String(), op.Amount.Value)
}
if op.Type != testType {
t.Errorf("operation %v has wrong type", i)
}
if len(op.RelatedOperations) != 0 {
t.Errorf("operation %v has related operations", i)
}
if types.Hash(op.Amount.Currency) != common.NativeCurrencyHash {
t.Errorf("operation %v has wrong currency", i)
}
}
testStartingIndex := int64(12)
ops, rosettaError = getSideEffectOperationsFromValueMap(testPayouts, testType, &testStartingIndex)
if rosettaError != nil {
t.Fatal(rosettaError)
}
for i, op := range ops {
if int64(i)+testStartingIndex != op.OperationIdentifier.Index {
t.Errorf("expected operation %v to have operation index %v", i, int64(i)+testStartingIndex)
}
address, err := getAddress(op.Account)
if err != nil {
t.Fatal(err)
}
if value, ok := testPayouts[address]; !ok {
t.Errorf("operation %v has address that is not in test map", i)
} else if value.String() != op.Amount.Value {
t.Errorf("operation %v has wrong value (%v != %v)", i, value.String(), op.Amount.Value)
}
if op.Type != testType {
t.Errorf("operation %v has wrong type", i)
}
if len(op.RelatedOperations) != 0 {
t.Errorf("operation %v has related operations", i)
}
if types.Hash(op.Amount.Currency) != common.NativeCurrencyHash {
t.Errorf("operation %v has wrong currency", i)
}
}
}
func TestGetStakingOperationsFromDelegate(t *testing.T) {
gasLimit := uint64(1e18)
senderKey, err := crypto.GenerateKey()
@ -123,7 +192,7 @@ func TestGetStakingOperationsFromDelegate(t *testing.T) {
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
@ -138,7 +207,7 @@ func TestGetStakingOperationsFromDelegate(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -187,7 +256,7 @@ func TestGetStakingOperationsFromUndelegate(t *testing.T) {
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
@ -202,7 +271,7 @@ func TestGetStakingOperationsFromUndelegate(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -251,7 +320,7 @@ func TestGetStakingOperationsFromCollectRewards(t *testing.T) {
},
},
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
@ -266,7 +335,7 @@ func TestGetStakingOperationsFromCollectRewards(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -308,7 +377,7 @@ func TestGetStakingOperationsFromEditValidator(t *testing.T) {
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
@ -323,7 +392,7 @@ func TestGetStakingOperationsFromEditValidator(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -587,7 +656,7 @@ func TestNewNativeOperations(t *testing.T) {
Currency: &common.NativeCurrency,
}
ops := newNativeOperations(gasFee, accountID)
ops := newNativeOperationsWithGas(gasFee, accountID)
if len(ops) != 1 {
t.Fatalf("Expected new operations to be of length 1")
}

Loading…
Cancel
Save