The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
woop/rosetta/services/tx_format.go

278 lines
8.4 KiB

Rosetta Implementation Cleanup (Stage 3 of Node API Overhaul) (#3390) * [core] Add FindLogsWithTopic & unit test Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [hmy] Add GetDetailedBlockSignerInfo Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [hmy] Add IsCommitteeSelectionBlock Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [test] Add test transaction creation helpers Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Refactor account.go & add tests * Move TestNewAccountIdentifier & TestGetAddress to account_test.go Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Move Operation & Tx formatting to own files * Move Respective unit tests to own files * Expose GetOperations & GetStakingOperations * Expose FormatTransaction, FormatCrossShardReceiverTransaction, FormatGenesisTransaction, FormatPreStakingRewardTransaction & FormatUndelegationPayoutTransaction Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Move TransactionMetadata to transaction_construction.go Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Update construction to use new helpers & formatters * Make docs consistent for mempool.go Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Move all special tx & blk handling to own file Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Remove all moved fns, methods & tests from block.go Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * Fix lint & imports Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Rename all tx related files for clarity Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Rename DefaultSenderAddress to FormatDefaultSenderAddress Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Rename Currency to NativeCurrency * This is in anticipation of HRC20 token support with rosetta * Rename various native operation functions accordingly * Add documentation to explain what a native token is Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Fix pre-staking block reward calculation * Move getPreStakingRewardTransactionIdentifiers to block_special.go * Add epoch to block metadata * Update unit tests Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * Add IsLastBlockInEpoch method to Block & Header * Refactor all uses of length check `ShardState` * [hmy] Refactor IsCommitteeSelectionBlock to use chain.IsCommitteeSelectionBlock * Address PR comments Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu> * [rosetta] Update var names in preStakingRewardBlockTransaction Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>
4 years ago
package services
import (
"encoding/hex"
"fmt"
"math/big"
"github.com/coinbase/rosetta-sdk-go/types"
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"
stakingNetwork "github.com/harmony-one/harmony/staking/network"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
var (
// FormatDefaultSenderAddress ..
FormatDefaultSenderAddress = ethcommon.HexToAddress("0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE")
)
// FormatTransaction for staking, cross-shard sender, and plain transactions
func FormatTransaction(
tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt,
) (fmtTx *types.Transaction, rosettaError *types.Error) {
var operations []*types.Operation
var isCrossShard, isStaking bool
var toShard uint32
switch tx.(type) {
case *stakingTypes.StakingTransaction:
isStaking = true
stakingTx := tx.(*stakingTypes.StakingTransaction)
operations, rosettaError = GetOperationsFromStakingTransaction(stakingTx, receipt)
if rosettaError != nil {
return nil, rosettaError
}
isCrossShard = false
toShard = stakingTx.ShardID()
case *hmytypes.Transaction:
isStaking = false
plainTx := tx.(*hmytypes.Transaction)
operations, rosettaError = GetNativeOperationsFromTransaction(plainTx, receipt)
if rosettaError != nil {
return nil, rosettaError
}
isCrossShard = plainTx.ShardID() != plainTx.ToShardID()
toShard = plainTx.ToShardID()
default:
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "unknown transaction type",
})
}
fromShard := tx.ShardID()
txID := &types.TransactionIdentifier{Hash: tx.Hash().String()}
// Set all possible metadata
var txMetadata TransactionMetadata
if isCrossShard {
txMetadata.CrossShardIdentifier = txID
txMetadata.ToShardID = &toShard
txMetadata.FromShardID = &fromShard
}
if len(tx.Data()) > 0 && !isStaking {
hexData := hex.EncodeToString(tx.Data())
txMetadata.Data = &hexData
txMetadata.Logs = receipt.Logs
}
metadata, err := types.MarshalMap(txMetadata)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
return &types.Transaction{
TransactionIdentifier: txID,
Operations: operations,
Metadata: metadata,
}, nil
}
// FormatCrossShardReceiverTransaction for cross-shard payouts on destination shard
func FormatCrossShardReceiverTransaction(
cxReceipt *hmytypes.CXReceipt,
) (txs *types.Transaction, rosettaError *types.Error) {
ctxID := &types.TransactionIdentifier{Hash: cxReceipt.TxHash.String()}
senderAccountID, rosettaError := newAccountIdentifier(cxReceipt.From)
if rosettaError != nil {
return nil, rosettaError
}
receiverAccountID, rosettaError := newAccountIdentifier(*cxReceipt.To)
if rosettaError != nil {
return nil, rosettaError
}
metadata, err := types.MarshalMap(TransactionMetadata{
CrossShardIdentifier: ctxID,
ToShardID: &cxReceipt.ToShardID,
FromShardID: &cxReceipt.ShardID,
})
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
opMetadata, err := types.MarshalMap(common.CrossShardTransactionOperationMetadata{
From: senderAccountID,
To: receiverAccountID,
})
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
return &types.Transaction{
TransactionIdentifier: ctxID,
Metadata: metadata,
Operations: []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0, // There is no gas expenditure for cross-shard transaction payout
},
Type: common.CrossShardTransferNativeOperation,
Status: common.SuccessOperationStatus.Status,
Account: receiverAccountID,
Amount: &types.Amount{
Value: cxReceipt.Amount.String(),
Currency: &common.NativeCurrency,
},
Metadata: opMetadata,
},
},
}, 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, blockSigInfo *hmy.DetailedBlockSignerInfo, 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
}
}
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: rewardsForThisBlock.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"
if num != nil && num.Cmp(big.NewInt(0)) != 0 {
value = fmt.Sprintf("-%v", new(big.Int).Abs(num))
}
return value
}