|
|
|
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"
|
|
|
|
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, contractCode []byte,
|
|
|
|
) (fmtTx *types.Transaction, rosettaError *types.Error) {
|
|
|
|
var operations []*types.Operation
|
|
|
|
var isCrossShard, isStaking, isContractCreation 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, isContractCreation = false, 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()
|
|
|
|
isContractCreation = tx.To() == nil
|
|
|
|
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 isContractCreation {
|
|
|
|
contractID, rosettaError := newAccountIdentifier(receipt.ContractAddress)
|
|
|
|
if rosettaError != nil {
|
|
|
|
return nil, rosettaError
|
|
|
|
}
|
|
|
|
txMetadata.ContractAccountIdentifier = contractID
|
|
|
|
} else if len(contractCode) > 0 && tx.To() != nil {
|
|
|
|
// Contract code was found, so receiving account must be the contract address
|
|
|
|
contractID, rosettaError := newAccountIdentifier(*tx.To())
|
|
|
|
if rosettaError != nil {
|
|
|
|
return nil, rosettaError
|
|
|
|
}
|
|
|
|
txMetadata.ContractAccountIdentifier = contractID
|
|
|
|
}
|
|
|
|
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, 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"
|
|
|
|
if num != nil && num.Cmp(big.NewInt(0)) != 0 {
|
|
|
|
value = fmt.Sprintf("-%v", new(big.Int).Abs(num))
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
}
|