[Rosetta] Track internal transactions (#3475)

* [rosetta] Refactor operations & prep for internal tx exposure

* Remove gas op relation for tx operations. Gas is for submission & processing
the tx, thus not really related to the amount being transferred
* Make optional starting op a ptr to a uint to keep consistent
* Reorg file for consistency of fn placement
* Rename functions for clarity
* Make getContractCreationNativeOperations consume getBasicTransferOperations for consistency
* Remove invariant doc as it does not apply anymore

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Add framework for parsing traced txs

* Define ContractInfo struct for FormatTransaction
* Add tx trace helper function defs & propagate type defs
* Add a GetTransactionStatus helper fn for Operation formatting/creation
* Add wrapper function, getContractTransferNativeOperations, to get internal operations

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Implement transaction tracer

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [tracer] Add CallerAddress & CodeAddress to tracer logs

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [tracer] Remove ptr to slice & map in StructLogRes

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Implement getContractInternalTransferNativeOperations

* Add ContractAddress to ContractInfo for future usages (i.e: erc20 parsing)
* Only check for contract address if there is tx data in BlockTransaction

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Fix status report for contract related txs

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [tracer] Expose contract address instead of code address

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Add trace cache & update TODO

* Trace any PLAIN transaction instead of only transactions with data.
This is to account for fall back contract fn calls & ignore staking data.

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Make internal tx formatter not return err on nil exec result

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* Fix lint

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Fix tests

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Add internal tx unit tests

* Fix tx data len check for contract related transfers as a transaction with len 0
data can happen for a contract (and fail) due to fall back operations.

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Update invariant comment

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Expose mutually exclusive ops + update docs & tests

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>

* [rosetta] Fix docs and err msgs

Signed-off-by: Daniel Van Der Maden <dvandermaden0@berkeley.edu>
pull/3477/head
Daniel Van Der Maden 4 years ago committed by GitHub
parent 2a722bb024
commit 6b143bb048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 60
      core/vm/gen_structlog.go
  2. 26
      core/vm/logger.go
  3. 20
      core/vm/logger_json.go
  4. 40
      hmy/tracer.go
  5. 13
      rosetta/common/operations.go
  6. 2
      rosetta/rosetta.go
  7. 94
      rosetta/services/block.go
  8. 8
      rosetta/services/construction_parse.go
  9. 4
      rosetta/services/construction_parse_test.go
  10. 2
      rosetta/services/mempool.go
  11. 16
      rosetta/services/tx_format.go
  12. 9
      rosetta/services/tx_format_test.go
  13. 408
      rosetta/services/tx_operation.go
  14. 497
      rosetta/services/tx_operation_test.go

@ -16,23 +16,27 @@ var _ = (*structLogMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (s StructLog) MarshalJSON() ([]byte, error) {
type StructLog struct {
Pc uint64 `json:"pc"`
Op OpCode `json:"op"`
Gas math.HexOrDecimal64 `json:"gas"`
GasCost math.HexOrDecimal64 `json:"gasCost"`
Memory hexutil.Bytes `json:"memory"`
MemorySize int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"`
Err error `json:"-"`
OpName string `json:"opName"`
ErrorString string `json:"error"`
Pc uint64 `json:"pc"`
Op OpCode `json:"op"`
CallerAddress common.Address `json:"callerAddress"`
ContractAddress common.Address `json:"contractAddress"`
Gas math.HexOrDecimal64 `json:"gas"`
GasCost math.HexOrDecimal64 `json:"gasCost"`
Memory hexutil.Bytes `json:"memory"`
MemorySize int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"`
Err error `json:"-"`
OpName string `json:"opName"`
ErrorString string `json:"error"`
}
var enc StructLog
enc.Pc = s.Pc
enc.Op = s.Op
enc.CallerAddress = s.CallerAddress
enc.ContractAddress = s.ContractAddress
enc.Gas = math.HexOrDecimal64(s.Gas)
enc.GasCost = math.HexOrDecimal64(s.GasCost)
enc.Memory = s.Memory
@ -55,17 +59,19 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals from JSON.
func (s *StructLog) UnmarshalJSON(input []byte) error {
type StructLog struct {
Pc *uint64 `json:"pc"`
Op *OpCode `json:"op"`
Gas *math.HexOrDecimal64 `json:"gas"`
GasCost *math.HexOrDecimal64 `json:"gasCost"`
Memory *hexutil.Bytes `json:"memory"`
MemorySize *int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth *int `json:"depth"`
RefundCounter *uint64 `json:"refund"`
Err error `json:"-"`
Pc *uint64 `json:"pc"`
Op *OpCode `json:"op"`
CallerAddress *common.Address `json:"callerAddress"`
ContractAddress *common.Address `json:"contractAddress"`
Gas *math.HexOrDecimal64 `json:"gas"`
GasCost *math.HexOrDecimal64 `json:"gasCost"`
Memory *hexutil.Bytes `json:"memory"`
MemorySize *int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth *int `json:"depth"`
RefundCounter *uint64 `json:"refund"`
Err error `json:"-"`
}
var dec StructLog
if err := json.Unmarshal(input, &dec); err != nil {
@ -77,6 +83,12 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
if dec.Op != nil {
s.Op = *dec.Op
}
if dec.CallerAddress != nil {
s.CallerAddress = *dec.CallerAddress
}
if dec.ContractAddress != nil {
s.ContractAddress = *dec.ContractAddress
}
if dec.Gas != nil {
s.Gas = uint64(*dec.Gas)
}

@ -56,17 +56,19 @@ type LogConfig struct {
// StructLog is emitted to the EVM each cycle and lists information about the current internal state
// prior to the execution of the statement.
type StructLog struct {
Pc uint64 `json:"pc"`
Op OpCode `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Memory []byte `json:"memory"`
MemorySize int `json:"memSize"`
Stack []*big.Int `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"`
Err error `json:"-"`
Pc uint64 `json:"pc"`
Op OpCode `json:"op"`
CallerAddress common.Address `json:"callerAddress"`
ContractAddress common.Address `json:"contractAddress"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Memory []byte `json:"memory"`
MemorySize int `json:"memSize"`
Stack []*big.Int `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"`
Err error `json:"-"`
}
// overrides for gencodec
@ -178,7 +180,7 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
storage = l.changedValues[contract.Address()].Copy()
}
// create a new snapshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, storage, depth, env.StateDB.GetRefund(), err}
log := StructLog{pc, op, contract.CallerAddress, contract.Address(), gas, cost, mem, memory.Len(), stck, storage, depth, env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log)
return nil

@ -50,15 +50,17 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create
// CaptureState outputs state information on the logger.
func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
log := StructLog{
Pc: pc,
Op: op,
Gas: gas,
GasCost: cost,
MemorySize: memory.Len(),
Storage: nil,
Depth: depth,
RefundCounter: env.StateDB.GetRefund(),
Err: err,
Pc: pc,
Op: op,
ContractAddress: contract.Address(),
CallerAddress: contract.CallerAddress,
Gas: gas,
GasCost: cost,
MemorySize: memory.Len(),
Storage: nil,
Depth: depth,
RefundCounter: env.StateDB.GetRefund(),
Err: err,
}
if !l.cfg.DisableMemory {
log.Memory = memory.Data()

@ -719,15 +719,17 @@ type ExecutionResult struct {
// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode
type StructLogRes struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error error `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
Memory *[]string `jsogun:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
Pc uint64 `json:"pc"`
Op string `json:"op"`
CallerAddress common.Address `json:"callerAddress"`
ContractAddress common.Address `json:"contractAddress"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error error `json:"error,omitempty"`
Stack []string `json:"stack,omitempty"`
Memory []string `json:"memory,omitempty"`
Storage map[string]string `json:"storage,omitempty"`
}
// FormatLogs formats EVM returned structured logs for json output
@ -735,33 +737,35 @@ func FormatLogs(logs []vm.StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))
for index, trace := range logs {
formatted[index] = StructLogRes{
Pc: trace.Pc,
Op: trace.Op.String(),
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.Err,
Pc: trace.Pc,
Op: trace.Op.String(),
CallerAddress: trace.CallerAddress,
ContractAddress: trace.ContractAddress,
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.Err,
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32))
}
formatted[index].Stack = &stack
formatted[index].Stack = stack
}
if trace.Memory != nil {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
}
formatted[index].Memory = &memory
formatted[index].Memory = memory
}
if trace.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
formatted[index].Storage = &storage
formatted[index].Storage = storage
}
}
return formatted

@ -10,20 +10,19 @@ import (
staking "github.com/harmony-one/harmony/staking/types"
)
// Invariant: A transaction can only contain 1 type of native operation(s) other than gas expenditure.
const (
// ExpendGasOperation is an operation that only affects the native currency.
ExpendGasOperation = "Gas"
// ContractCreationOperation is an operation that only affects the native currency.
ContractCreationOperation = "ContractCreation"
// NativeTransferOperation is an operation that only affects the native currency.
NativeTransferOperation = "NativeTransfer"
// NativeCrossShardTransferOperation is an operation that only affects the native currency.
NativeCrossShardTransferOperation = "NativeCrossShardTransfer"
// ContractCreationOperation is an operation that only affects the native currency.
ContractCreationOperation = "ContractCreation"
// GenesisFundsOperation is a side effect operation for genesis block only.
// Note that no transaction can be constructed with this operation.
GenesisFundsOperation = "Genesis"
@ -57,6 +56,12 @@ var (
staking.DirectiveUndelegate.String(),
staking.DirectiveCollectRewards.String(),
}
// MutuallyExclusiveOperations for invariant: A transaction can only contain 1 type of 'native' operation.
MutuallyExclusiveOperations = map[string]interface{}{
NativeTransferOperation: struct{}{},
NativeCrossShardTransferOperation: struct{}{},
}
)
var (

@ -23,7 +23,7 @@ import (
var listener net.Listener
// StartServers starts the rosetta http server
// TODO (dm): optimize rosetta to use single flight to avoid re-processing data
// TODO (dm): optimize rosetta to use single flight & use extra caching type DB to avoid re-processing data
func StartServers(hmy *hmy.Harmony, config nodeconfig.RosettaServerConfig) error {
if !config.HTTPEnabled {
utils.Logger().Info().Msg("Rosetta http server disabled...")

@ -4,28 +4,39 @@ import (
"context"
"fmt"
"math/big"
"time"
"github.com/coinbase/rosetta-sdk-go/server"
"github.com/coinbase/rosetta-sdk-go/types"
ethcommon "github.com/ethereum/go-ethereum/common"
lru "github.com/hashicorp/golang-lru"
"github.com/harmony-one/harmony/core/rawdb"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/hmy"
"github.com/harmony-one/harmony/rosetta/common"
"github.com/harmony-one/harmony/rpc"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
const (
// txTraceCacheSize is max number of transaction traces to keep cached
txTraceCacheSize = 1e5
)
// BlockAPI implements the server.BlockAPIServicer interface.
type BlockAPI struct {
hmy *hmy.Harmony
hmy *hmy.Harmony
txTraceCache *lru.Cache
}
// NewBlockAPI creates a new instance of a BlockAPI.
func NewBlockAPI(hmy *hmy.Harmony) server.BlockAPIServicer {
traceCache, _ := lru.New(txTraceCacheSize)
return &BlockAPI{
hmy: hmy,
hmy: hmy,
txTraceCache: traceCache,
}
}
@ -150,11 +161,30 @@ func (s *BlockAPI) BlockTransaction(
var transaction *types.Transaction
if txInfo.tx != nil && txInfo.receipt != nil {
contractCode := []byte{}
if txInfo.tx.To() != nil {
contractCode = state.GetCode(*txInfo.tx.To())
contractInfo := &ContractInfo{}
if _, ok := txInfo.tx.(*hmytypes.Transaction); ok {
// check for contract related operations, if it is a plain transaction.
if txInfo.tx.To() != nil {
// possible call to existing contract so fetch relevant data
contractInfo.ContractCode = state.GetCode(*txInfo.tx.To())
contractInfo.ContractAddress = txInfo.tx.To()
} else {
// contract creation, so address is in receipt
contractInfo.ContractCode = state.GetCode(txInfo.receipt.ContractAddress)
contractInfo.ContractAddress = &txInfo.receipt.ContractAddress
}
blk, rosettaError := getBlock(
ctx, s.hmy, &types.PartialBlockIdentifier{Hash: &request.BlockIdentifier.Hash},
)
if rosettaError != nil {
return nil, rosettaError
}
contractInfo.ExecutionResult, rosettaError = s.getTransactionTrace(ctx, blk, txInfo)
if rosettaError != nil {
return nil, rosettaError
}
}
transaction, rosettaError = FormatTransaction(txInfo.tx, txInfo.receipt, contractCode)
transaction, rosettaError = FormatTransaction(txInfo.tx, txInfo.receipt, contractInfo)
if rosettaError != nil {
return nil, rosettaError
}
@ -223,6 +253,58 @@ func (s *BlockAPI) getTransactionInfo(
}, nil
}
var (
// defaultTraceReExec is the number of blocks the tracer can go back and re-execute to produce historical state.
// Only 1 block is needed to check for internal transactions
defaultTraceReExec = uint64(1)
// defaultTraceTimeout is the amount of time a transaction can execute
defaultTraceTimeout = (10 * time.Second).String()
// defaultTraceLogConfig is the log config of all traces
defaultTraceLogConfig = vm.LogConfig{
DisableMemory: false,
DisableStack: false,
DisableStorage: false,
Debug: false,
Limit: 0,
}
)
// getTransactionTrace for the given txInfo.
func (s *BlockAPI) getTransactionTrace(
ctx context.Context, blk *hmytypes.Block, txInfo *transactionInfo,
) (*hmy.ExecutionResult, *types.Error) {
cacheKey := types.Hash(blk) + types.Hash(txInfo)
if value, ok := s.txTraceCache.Get(cacheKey); ok {
return value.(*hmy.ExecutionResult), nil
}
msg, vmctx, statedb, err := s.hmy.ComputeTxEnv(blk, int(txInfo.txIndex), defaultTraceReExec)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
execResultInterface, err := s.hmy.TraceTx(ctx, msg, vmctx, statedb, &hmy.TraceConfig{
LogConfig: &defaultTraceLogConfig,
Timeout: &defaultTraceTimeout,
Reexec: &defaultTraceReExec,
})
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
execResult, ok := execResultInterface.(*hmy.ExecutionResult)
if !ok {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "unknown tracer exec result type",
})
}
s.txTraceCache.Add(cacheKey, execResult)
return execResult, nil
}
// getBlock ..
func getBlock(
ctx context.Context, hmy *hmy.Harmony, blockID *types.PartialBlockIdentifier,

@ -53,7 +53,9 @@ func parseUnsignedTransaction(
intendedReceipt := &hmyTypes.Receipt{
GasUsed: tx.Gas(),
}
formattedTx, rosettaError := FormatTransaction(tx, intendedReceipt, wrappedTransaction.ContractCode)
formattedTx, rosettaError := FormatTransaction(
tx, intendedReceipt, &ContractInfo{ContractCode: wrappedTransaction.ContractCode},
)
if rosettaError != nil {
return nil, rosettaError
}
@ -94,7 +96,9 @@ func parseSignedTransaction(
intendedReceipt := &hmyTypes.Receipt{
GasUsed: tx.Gas(),
}
formattedTx, rosettaError := FormatTransaction(tx, intendedReceipt, wrappedTransaction.ContractCode)
formattedTx, rosettaError := FormatTransaction(
tx, intendedReceipt, &ContractInfo{ContractCode: wrappedTransaction.ContractCode},
)
if rosettaError != nil {
return nil, rosettaError
}

@ -38,7 +38,7 @@ func TestParseUnsignedTransaction(t *testing.T) {
refTestReceipt := &hmytypes.Receipt{
GasUsed: testTx.Gas(),
}
refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, []byte{})
refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, &ContractInfo{})
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -101,7 +101,7 @@ func TestParseSignedTransaction(t *testing.T) {
refTestReceipt := &hmytypes.Receipt{
GasUsed: testTx.Gas(),
}
refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, []byte{})
refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, &ContractInfo{})
if rosettaError != nil {
t.Fatal(rosettaError)
}

@ -84,7 +84,7 @@ func (s *MempoolAPI) MempoolTransaction(
GasUsed: poolTx.Gas(),
}
respTx, err := FormatTransaction(poolTx, estReceipt, []byte{})
respTx, err := FormatTransaction(poolTx, estReceipt, &ContractInfo{})
if err != nil {
return nil, err
}

@ -9,6 +9,7 @@ import (
ethcommon "github.com/ethereum/go-ethereum/common"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/hmy"
"github.com/harmony-one/harmony/rosetta/common"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
@ -18,9 +19,18 @@ var (
FormatDefaultSenderAddress = ethcommon.HexToAddress("0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE")
)
// ContractInfo contains all relevant data for formatting/inspecting transactions involving contracts
type ContractInfo struct {
// ContractAddress is the address of the primary (or first) contract related to the tx.
ContractAddress *ethcommon.Address `json:"contract_hex_address"`
// ContractCode is the code of the primary (or first) contract related to the tx.
ContractCode []byte `json:"contract_code"`
ExecutionResult *hmy.ExecutionResult `json:"execution_result"`
}
// FormatTransaction for staking, cross-shard sender, and plain transactions
func FormatTransaction(
tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt, contractCode []byte,
tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt, contractInfo *ContractInfo,
) (fmtTx *types.Transaction, rosettaError *types.Error) {
var operations []*types.Operation
var isCrossShard, isStaking, isContractCreation bool
@ -39,7 +49,7 @@ func FormatTransaction(
case *hmytypes.Transaction:
isStaking = false
plainTx := tx.(*hmytypes.Transaction)
operations, rosettaError = GetNativeOperationsFromTransaction(plainTx, receipt)
operations, rosettaError = GetNativeOperationsFromTransaction(plainTx, receipt, contractInfo)
if rosettaError != nil {
return nil, rosettaError
}
@ -62,7 +72,7 @@ func FormatTransaction(
return nil, rosettaError
}
txMetadata.ContractAccountIdentifier = contractID
} else if len(contractCode) > 0 && tx.To() != nil {
} else if contractInfo.ContractAddress != nil && len(contractInfo.ContractCode) > 0 {
// Contract code was found, so receiving account must be the contract address
contractID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {

@ -17,11 +17,10 @@ import (
"github.com/harmony-one/harmony/test/helpers"
)
// Invariant: A transaction can only contain 1 type of native operation(s) other than gas expenditure.
func assertNativeOperationTypeUniquenessInvariant(operations []*types.Operation) error {
foundType := ""
for _, op := range operations {
if op.Type == common.ExpendGasOperation {
if _, ok := common.MutuallyExclusiveOperations[op.Type]; !ok {
continue
}
if foundType == "" {
@ -79,7 +78,7 @@ func testFormatStakingTransaction(
Status: hmytypes.ReceiptStatusSuccessful,
GasUsed: gasUsed,
}
rosettaTx, rosettaError := FormatTransaction(tx, receipt, []byte{})
rosettaTx, rosettaError := FormatTransaction(tx, receipt, &ContractInfo{})
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -134,7 +133,7 @@ func testFormatPlainTransaction(
Status: hmytypes.ReceiptStatusSuccessful,
GasUsed: gasUsed,
}
rosettaTx, rosettaError := FormatTransaction(tx, receipt, []byte{})
rosettaTx, rosettaError := FormatTransaction(tx, receipt, &ContractInfo{})
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -191,7 +190,7 @@ func testFormatCrossShardSenderTransaction(
Status: hmytypes.ReceiptStatusSuccessful,
GasUsed: gasUsed,
}
rosettaTx, rosettaError := FormatTransaction(tx, receipt, []byte{})
rosettaTx, rosettaError := FormatTransaction(tx, receipt, &ContractInfo{})
if rosettaError != nil {
t.Fatal(rosettaError)
}

@ -9,8 +9,9 @@ import (
"github.com/harmony-one/harmony/core"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/hmy"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/rosetta/common"
rpcV2 "github.com/harmony-one/harmony/rpc/v2"
"github.com/harmony-one/harmony/staking"
@ -18,10 +19,10 @@ import (
)
// GetNativeOperationsFromTransaction for one of the following transactions:
// contract creation, cross-shard sender, same-shard transfer.
// contract creation, cross-shard sender, same-shard transfer with and without code execution.
// Native operations only include operations that affect the native currency balance of an account.
func GetNativeOperationsFromTransaction(
tx *hmytypes.Transaction, receipt *hmytypes.Receipt,
tx *hmytypes.Transaction, receipt *hmytypes.Receipt, contractInfo *ContractInfo,
) ([]*types.Operation, *types.Error) {
senderAddress, err := tx.SenderAddress()
if err != nil {
@ -35,20 +36,25 @@ 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 := newNativeOperationsWithGas(gasExpended, accountID)
startingOpIndex := gasOperations[0].OperationIdentifier.Index + 1
// Handle different cases of plain transactions
// Handle based on tx type & available data.
var txOperations []*types.Operation
if tx.To() == nil {
txOperations, rosettaError = newContractCreationNativeOperations(
gasOperations[0].OperationIdentifier, tx, receipt, senderAddress,
txOperations, rosettaError = getContractCreationNativeOperations(
tx, receipt, senderAddress, contractInfo, &startingOpIndex,
)
} else if tx.ShardID() != tx.ToShardID() {
txOperations, rosettaError = newCrossShardSenderTransferNativeOperations(
gasOperations[0].OperationIdentifier, tx, senderAddress,
txOperations, rosettaError = getCrossShardSenderTransferNativeOperations(
tx, senderAddress, &startingOpIndex,
)
} else if contractInfo != nil && contractInfo.ExecutionResult != nil {
txOperations, rosettaError = getContractTransferNativeOperations(
tx, receipt, senderAddress, tx.To(), contractInfo, &startingOpIndex,
)
} else {
txOperations, rosettaError = newTransferNativeOperations(
gasOperations[0].OperationIdentifier, tx, receipt, senderAddress,
txOperations, rosettaError = getBasicTransferNativeOperations(
tx, receipt, senderAddress, tx.To(), &startingOpIndex,
)
}
if rosettaError != nil {
@ -59,7 +65,7 @@ func GetNativeOperationsFromTransaction(
}
// GetNativeOperationsFromStakingTransaction for all staking directives
// Note that only native operations can come from staking transactions.
// Note that only native token operations can come from staking transactions.
func GetNativeOperationsFromStakingTransaction(
tx *stakingTypes.StakingTransaction, receipt *hmytypes.Receipt,
) ([]*types.Operation, *types.Error) {
@ -116,11 +122,8 @@ func GetNativeOperationsFromStakingTransaction(
OperationIdentifier: &types.OperationIdentifier{
Index: gasOperations[0].OperationIdentifier.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
gasOperations[0].OperationIdentifier,
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Status: GetTransactionStatus(tx, receipt),
Account: accountID,
Amount: amount,
Metadata: metadata,
@ -161,7 +164,207 @@ func GetSideEffectOperationsFromGenesisSpec(
)
}
// getSideEffectOperationsFromValueMap is a helper for side effect operation construction from a value map.
// GetTransactionStatus for any valid harmony transaction given its receipt.
func GetTransactionStatus(tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt) string {
if _, ok := tx.(*hmytypes.Transaction); ok {
status := common.SuccessOperationStatus.Status
if receipt.Status == hmytypes.ReceiptStatusFailed {
if len(tx.Data()) == 0 && receipt.CumulativeGasUsed <= params.TxGas {
status = common.FailureOperationStatus.Status
} else {
status = common.ContractFailureOperationStatus.Status
}
}
return status
} else if _, ok := tx.(*stakingTypes.StakingTransaction); ok {
return common.SuccessOperationStatus.Status
}
// Type of tx unknown, so default to failure
return common.FailureOperationStatus.Status
}
// getBasicTransferNativeOperations extracts & formats the basic native operations for non-staking transaction.
// Note that this does NOT include any contract related transfers (i.e: internal transactions).
func getBasicTransferNativeOperations(
tx *hmytypes.Transaction, receipt *hmytypes.Receipt, senderAddress ethcommon.Address, toAddress *ethcommon.Address,
startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
if toAddress == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "tx receiver not found",
})
}
// Common operation elements
status := GetTransactionStatus(tx, receipt)
from, rosettaError := newAccountIdentifier(senderAddress)
if rosettaError != nil {
return nil, rosettaError
}
to, rosettaError := newAccountIdentifier(*toAddress)
if rosettaError != nil {
return nil, rosettaError
}
return newSameShardTransferNativeOperations(from, to, tx.Value(), status, startingOperationIndex), nil
}
// getContractTransferNativeOperations extracts & formats the native operations for any
// transaction involving a contract.
// Note that this will include any native tokens that were transferred from the contract (i.e: internal transactions).
func getContractTransferNativeOperations(
tx *hmytypes.Transaction, receipt *hmytypes.Receipt, senderAddress ethcommon.Address, toAddress *ethcommon.Address,
contractInfo *ContractInfo, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
basicOps, rosettaError := getBasicTransferNativeOperations(
tx, receipt, senderAddress, toAddress, startingOperationIndex,
)
if rosettaError != nil {
return nil, rosettaError
}
status := GetTransactionStatus(tx, receipt)
startingIndex := basicOps[len(basicOps)-1].OperationIdentifier.Index + 1
internalTxOps, rosettaError := getContractInternalTransferNativeOperations(
contractInfo.ExecutionResult, status, &startingIndex,
)
if rosettaError != nil {
return nil, rosettaError
}
return append(basicOps, internalTxOps...), nil
}
// getContractCreationNativeOperations extracts & formats the native operations for a contract creation tx
func getContractCreationNativeOperations(
tx *hmytypes.Transaction, receipt *hmytypes.Receipt, senderAddress ethcommon.Address, contractInfo *ContractInfo,
startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
basicOps, rosettaError := getBasicTransferNativeOperations(
tx, receipt, senderAddress, &receipt.ContractAddress, startingOperationIndex,
)
if rosettaError != nil {
return nil, rosettaError
}
for _, op := range basicOps {
op.Type = common.ContractCreationOperation
}
status := GetTransactionStatus(tx, receipt)
startingIndex := basicOps[len(basicOps)-1].OperationIdentifier.Index + 1
internalTxOps, rosettaError := getContractInternalTransferNativeOperations(
contractInfo.ExecutionResult, status, &startingIndex,
)
if rosettaError != nil {
return nil, rosettaError
}
return append(basicOps, internalTxOps...), nil
}
var (
// internalNativeTransferEvmOps are the EVM operations that can execute a native transfer
// where the sender is a contract address. This is also known as ops for an 'internal' transaction.
// All operations have at least 7 elements on the stack when executed.
internalNativeTransferEvmOps = map[string]interface{}{
vm.CALL.String(): struct{}{},
vm.CALLCODE.String(): struct{}{},
}
)
// getContractInternalTransferNativeOperations extracts & formats the native operations for a contract's internal
// native token transfers (i.e: the sender of a transaction is the contract).
func getContractInternalTransferNativeOperations(
executionResult *hmy.ExecutionResult, status string,
startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
ops := []*types.Operation{}
if executionResult == nil {
// No error since nil execution result implies empty StructLogs, which is not an error.
return ops, nil
}
for _, log := range executionResult.StructLogs {
if _, ok := internalNativeTransferEvmOps[log.Op]; ok {
fromAccID, rosettaError := newAccountIdentifier(log.ContractAddress)
if rosettaError != nil {
return nil, rosettaError
}
topIndex := len(log.Stack) - 1
toAccID, rosettaError := newAccountIdentifier(ethcommon.HexToAddress(log.Stack[topIndex-1]))
if rosettaError != nil {
return nil, rosettaError
}
value, ok := new(big.Int).SetString(log.Stack[topIndex-2], 16)
if !ok {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": fmt.Sprintf("unable to set value amount, raw: %v", log.Stack[topIndex-2]),
})
}
ops = append(
ops, newSameShardTransferNativeOperations(fromAccID, toAccID, value, status, startingOperationIndex)...,
)
nextOpIndex := ops[len(ops)-1].OperationIdentifier.Index + 1
startingOperationIndex = &nextOpIndex
}
}
return ops, nil
}
// getCrossShardSenderTransferNativeOperations extracts & formats the native operation(s)
// for cross-shard-tx on the sender's shard.
func getCrossShardSenderTransferNativeOperations(
tx *hmytypes.Transaction, senderAddress ethcommon.Address,
startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
if tx.To() == nil {
return nil, common.NewError(common.CatchAllError, nil)
}
senderAccountID, rosettaError := newAccountIdentifier(senderAddress)
if rosettaError != nil {
return nil, rosettaError
}
receiverAccountID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
return nil, rosettaError
}
metadata, err := types.MarshalMap(common.CrossShardTransactionOperationMetadata{
From: senderAccountID,
To: receiverAccountID,
})
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
var opIndex int64
if startingOperationIndex != nil {
opIndex = *startingOperationIndex
} else {
opIndex = 0
}
return []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: opIndex,
},
Type: common.NativeCrossShardTransferOperation,
Status: common.SuccessOperationStatus.Status,
Account: senderAccountID,
Amount: &types.Amount{
Value: negativeBigValue(tx.Value()),
Currency: &common.NativeCurrency,
},
Metadata: metadata,
},
}, nil
}
// getSideEffectOperationsFromValueMap is a helper for side effect operation construction from a address to value map.
func getSideEffectOperationsFromValueMap(
valueMap map[ethcommon.Address]*big.Int, opType string, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
@ -263,159 +466,23 @@ func getAmountFromCollectRewards(
return amount, nil
}
// newTransferNativeOperations extracts & formats the native operation(s) for plain transaction,
// including contract transactions.
func newTransferNativeOperations(
startingOperationID *types.OperationIdentifier,
tx *hmytypes.Transaction, receipt *hmytypes.Receipt, senderAddress ethcommon.Address,
) ([]*types.Operation, *types.Error) {
if tx.To() == nil {
return nil, common.NewError(common.CatchAllError, nil)
}
receiverAddress := *tx.To()
// Common elements
opType := common.NativeTransferOperation
opStatus := common.SuccessOperationStatus.Status
if receipt.Status == hmytypes.ReceiptStatusFailed {
if len(tx.Data()) > 0 {
opStatus = common.ContractFailureOperationStatus.Status
} else {
// Should never see a failed non-contract related transaction on chain
opStatus = common.FailureOperationStatus.Status
utils.Logger().Warn().Msgf("Failed transaction on chain: %v", tx.Hash().String())
}
}
// newSameShardTransferNativeOperations creates a new slice of operations for a native transfer on the same shard.
func newSameShardTransferNativeOperations(
from, to *types.AccountIdentifier, amount *big.Int, status string,
startingOperationIndex *int64,
) []*types.Operation {
// Subtraction operation elements
subOperationID := &types.OperationIdentifier{
Index: startingOperationID.Index + 1,
}
subRelatedID := []*types.OperationIdentifier{
startingOperationID,
}
subAccountID, rosettaError := newAccountIdentifier(senderAddress)
if rosettaError != nil {
return nil, rosettaError
}
subAmount := &types.Amount{
Value: negativeBigValue(tx.Value()),
Currency: &common.NativeCurrency,
}
// Addition operation elements
addOperationID := &types.OperationIdentifier{
Index: subOperationID.Index + 1,
}
addRelatedID := []*types.OperationIdentifier{
subOperationID,
}
addAccountID, rosettaError := newAccountIdentifier(receiverAddress)
if rosettaError != nil {
return nil, rosettaError
}
addAmount := &types.Amount{
Value: tx.Value().String(),
Currency: &common.NativeCurrency,
}
return []*types.Operation{
{
OperationIdentifier: subOperationID,
RelatedOperations: subRelatedID,
Type: opType,
Status: opStatus,
Account: subAccountID,
Amount: subAmount,
},
{
OperationIdentifier: addOperationID,
RelatedOperations: addRelatedID,
Type: opType,
Status: opStatus,
Account: addAccountID,
Amount: addAmount,
},
}, nil
}
// newCrossShardSenderTransferNativeOperations extracts & formats the native operation(s)
// for cross-shard-tx on the sender's shard.
func newCrossShardSenderTransferNativeOperations(
startingOperationID *types.OperationIdentifier,
tx *hmytypes.Transaction, senderAddress ethcommon.Address,
) ([]*types.Operation, *types.Error) {
if tx.To() == nil {
return nil, common.NewError(common.CatchAllError, nil)
}
senderAccountID, rosettaError := newAccountIdentifier(senderAddress)
if rosettaError != nil {
return nil, rosettaError
}
receiverAccountID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
return nil, rosettaError
}
metadata, 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.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOperationID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
startingOperationID,
},
Type: common.NativeCrossShardTransferOperation,
Status: common.SuccessOperationStatus.Status,
Account: senderAccountID,
Amount: &types.Amount{
Value: negativeBigValue(tx.Value()),
Currency: &common.NativeCurrency,
},
Metadata: metadata,
},
}, nil
}
// newContractCreationNativeOperations extracts & formats the native operation(s) for a contract creation tx
func newContractCreationNativeOperations(
startingOperationID *types.OperationIdentifier,
tx *hmytypes.Transaction, txReceipt *hmytypes.Receipt, senderAddress ethcommon.Address,
) ([]*types.Operation, *types.Error) {
// TODO: correct the contract creation transaction...
// Set execution status as necessary
status := common.SuccessOperationStatus.Status
if txReceipt.Status == hmytypes.ReceiptStatusFailed {
status = common.ContractFailureOperationStatus.Status
}
contractAddressID, rosettaError := newAccountIdentifier(txReceipt.ContractAddress)
if rosettaError != nil {
return nil, rosettaError
var opIndex int64
if startingOperationIndex != nil {
opIndex = *startingOperationIndex
} else {
opIndex = 0
}
// Subtraction operation elements
subOperationID := &types.OperationIdentifier{
Index: startingOperationID.Index + 1,
}
subRelatedID := []*types.OperationIdentifier{
startingOperationID,
}
subAccountID, rosettaError := newAccountIdentifier(senderAddress)
if rosettaError != nil {
return nil, rosettaError
Index: opIndex,
}
subAmount := &types.Amount{
Value: negativeBigValue(tx.Value()),
Value: negativeBigValue(amount),
Currency: &common.NativeCurrency,
}
@ -427,28 +494,27 @@ func newContractCreationNativeOperations(
subOperationID,
}
addAmount := &types.Amount{
Value: tx.Value().String(),
Value: amount.String(),
Currency: &common.NativeCurrency,
}
return []*types.Operation{
{
OperationIdentifier: subOperationID,
RelatedOperations: subRelatedID,
Type: common.ContractCreationOperation,
Type: common.NativeTransferOperation,
Status: status,
Account: subAccountID,
Account: from,
Amount: subAmount,
},
{
OperationIdentifier: addOperationID,
RelatedOperations: addRelatedID,
Type: common.ContractCreationOperation,
Type: common.NativeTransferOperation,
Status: status,
Account: contractAddressID,
Account: to,
Amount: addAmount,
},
}, nil
}
}
// newNativeOperationsWithGas creates a new operation with the gas fee as the first operation.

@ -11,6 +11,8 @@ import (
"github.com/ethereum/go-ethereum/crypto"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/hmy"
internalCommon "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/rosetta/common"
@ -63,12 +65,9 @@ func TestGetStakingOperationsFromCreateValidator(t *testing.T) {
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: negativeBigValue(tenOnes),
Currency: &common.NativeCurrency,
@ -124,6 +123,9 @@ func TestGetSideEffectOperationsFromValueMap(t *testing.T) {
t.Errorf("operation %v has wrong currency", i)
}
}
if err := assertNativeOperationTypeUniquenessInvariant(ops); err != nil {
t.Error(err)
}
testStartingIndex := int64(12)
ops, rosettaError = getSideEffectOperationsFromValueMap(testPayouts, testType, &testStartingIndex)
@ -153,6 +155,9 @@ func TestGetSideEffectOperationsFromValueMap(t *testing.T) {
t.Errorf("operation %v has wrong currency", i)
}
}
if err := assertNativeOperationTypeUniquenessInvariant(ops); err != nil {
t.Error(err)
}
}
func TestGetStakingOperationsFromDelegate(t *testing.T) {
@ -195,12 +200,9 @@ func TestGetStakingOperationsFromDelegate(t *testing.T) {
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: negativeBigValue(tenOnes),
Currency: &common.NativeCurrency,
@ -259,12 +261,9 @@ func TestGetStakingOperationsFromUndelegate(t *testing.T) {
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: fmt.Sprintf("0"),
Currency: &common.NativeCurrency,
@ -323,12 +322,9 @@ func TestGetStakingOperationsFromCollectRewards(t *testing.T) {
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: fmt.Sprintf("%v", tenOnes.Uint64()),
Currency: &common.NativeCurrency,
@ -380,12 +376,9 @@ func TestGetStakingOperationsFromEditValidator(t *testing.T) {
refOperations := newNativeOperationsWithGas(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: fmt.Sprintf("0"),
Currency: &common.NativeCurrency,
@ -404,7 +397,7 @@ func TestGetStakingOperationsFromEditValidator(t *testing.T) {
}
}
func TestNewTransferNativeOperations(t *testing.T) {
func TestGetBasicTransferOperations(t *testing.T) {
signer := hmytypes.NewEIP155Signer(params.TestChainConfig.ChainID)
tx, err := helpers.CreateTestTransaction(
signer, 0, 0, 0, 1e18, gasPrice, big.NewInt(1), []byte("test"),
@ -432,11 +425,6 @@ func TestNewTransferNativeOperations(t *testing.T) {
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
{
Index: startingOpID.Index,
},
},
Type: common.NativeTransferOperation,
Status: common.ContractFailureOperationStatus.Status,
Account: senderAccID,
@ -466,7 +454,8 @@ func TestNewTransferNativeOperations(t *testing.T) {
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusFailed,
}
operations, rosettaError := newTransferNativeOperations(startingOpID, tx, receipt, senderAddr)
opIndex := startingOpID.Index + 1
operations, rosettaError := getBasicTransferNativeOperations(tx, receipt, senderAddr, tx.To(), &opIndex)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -481,7 +470,7 @@ func TestNewTransferNativeOperations(t *testing.T) {
refOperations[0].Status = common.SuccessOperationStatus.Status
refOperations[1].Status = common.SuccessOperationStatus.Status
receipt.Status = hmytypes.ReceiptStatusSuccessful
operations, rosettaError = newTransferNativeOperations(startingOpID, tx, receipt, senderAddr)
operations, rosettaError = getBasicTransferNativeOperations(tx, receipt, senderAddr, tx.To(), &opIndex)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -493,7 +482,7 @@ func TestNewTransferNativeOperations(t *testing.T) {
}
}
func TestNewCrossShardSenderTransferNativeOperations(t *testing.T) {
func TestGetCrossShardSenderTransferNativeOperations(t *testing.T) {
signer := hmytypes.NewEIP155Signer(params.TestChainConfig.ChainID)
tx, err := helpers.CreateTestTransaction(
signer, 0, 1, 0, 1e18, gasPrice, big.NewInt(1), []byte("data-does-nothing"),
@ -527,9 +516,6 @@ func TestNewCrossShardSenderTransferNativeOperations(t *testing.T) {
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
startingOpID,
},
Type: common.NativeCrossShardTransferOperation,
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
@ -540,7 +526,8 @@ func TestNewCrossShardSenderTransferNativeOperations(t *testing.T) {
Metadata: metadata,
},
}
operations, rosettaError := newCrossShardSenderTransferNativeOperations(startingOpID, tx, senderAddr)
opIndex := startingOpID.Index + 1
operations, rosettaError := getCrossShardSenderTransferNativeOperations(tx, senderAddr, &opIndex)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -552,7 +539,365 @@ func TestNewCrossShardSenderTransferNativeOperations(t *testing.T) {
}
}
func TestNewContractCreationNativeOperations(t *testing.T) {
var (
testExecResultForInternalTx = &hmy.ExecutionResult{
StructLogs: []hmy.StructLogRes{
{
Pc: 1316,
Op: "DUP9",
CallerAddress: ethcommon.HexToAddress("0x2a44f609f860d4ff8835f9ec1d9b1acdae1fd9cb"),
ContractAddress: ethcommon.HexToAddress("0x4c4fde977fbbe722cddf5719d7edd488510be16a"),
Gas: 6677398,
GasCost: 3,
Depth: 1,
Stack: []string{
"0000000000000000000000000000000000000000000000000000000023024408",
"000000000000000000000000000000000000000000000000000000000000019a",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000050",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000000",
"00000000000000000000000000000000000000000000021e19e0c9bab2400000",
"0000000000000000000000000000000000000000000000000000000000000080",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000080",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000080",
"00000000000000000000000000000000000000000000021e19e0c9bab2400000",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
},
Memory: []string{
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000003",
"0000000000000000000000000000000000000000000000000000000000000080",
},
Storage: map[string]string{
"43a43725d4b041c11b63ea10be0771546465a0c0654fd13c2712f2a8ce3f8b85": "0000000000000000000000000000000000000000000000000000000000000050",
},
},
{
Pc: 1317,
Op: vm.CALL.String(),
CallerAddress: ethcommon.HexToAddress("0x2a44f609f860d4ff8835f9ec1d9b1acdae1fd9cb"),
ContractAddress: ethcommon.HexToAddress("0x4c4fde977fbbe722cddf5719d7edd488510be16a"),
Gas: 6677395,
GasCost: 9700,
Depth: 1,
Stack: []string{
"0000000000000000000000000000000000000000000000000000000023024408",
"000000000000000000000000000000000000000000000000000000000000019a",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000050",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000000",
"00000000000000000000000000000000000000000000021e19e0c9bab2400000",
"0000000000000000000000000000000000000000000000000000000000000080",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000080",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000080",
"0000000000000000000000000000000000000000000000000000000000002710",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000000",
},
Memory: []string{
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000003",
"0000000000000000000000000000000000000000000000000000000000000080",
},
Storage: map[string]string{
"43a43725d4b041c11b63ea10be0771546465a0c0654fd13c2712f2a8ce3f8b85": "0000000000000000000000000000000000000000000000000000000000000050",
},
},
{
Pc: 1318,
Op: "SWAP4",
CallerAddress: ethcommon.HexToAddress("0x2a44f609f860d4ff8835f9ec1d9b1acdae1fd9cb"),
ContractAddress: ethcommon.HexToAddress("0x4c4fde977fbbe722cddf5719d7edd488510be16a"),
Gas: 6669995,
GasCost: 3,
Depth: 1,
Stack: []string{
"0000000000000000000000000000000000000000000000000000000023024408",
"000000000000000000000000000000000000000000000000000000000000019a",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000050",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000000",
"00000000000000000000000000000000000000000000021e19e0c9bab2400000",
"0000000000000000000000000000000000000000000000000000000000000080",
"0000000000000000000000000000000000000000000000000000000000000001",
},
Memory: []string{
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000003",
"0000000000000000000000000000000000000000000000000000000000000080",
},
Storage: map[string]string{
"43a43725d4b041c11b63ea10be0771546465a0c0654fd13c2712f2a8ce3f8b85": "0000000000000000000000000000000000000000000000000000000000000050",
},
},
{
Pc: 1319,
Op: vm.CALLCODE.String(),
CallerAddress: ethcommon.HexToAddress("0x2a44f609f860d4ff8835f9ec1d9b1acdae1fd9cb"),
ContractAddress: ethcommon.HexToAddress("0x4c4fde977fbbe722cddf5719d7edd488510be16a"),
Gas: 6677395,
GasCost: 9700,
Depth: 1,
Stack: []string{
"0000000000000000000000000000000000000000000000000000000023024408",
"000000000000000000000000000000000000000000000000000000000000019a",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000050",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000000",
"00000000000000000000000000000000000000000000021e19e0c9bab2400000",
"0000000000000000000000000000000000000000000000000000000000000080",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000080",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000080",
"0000000000000000000000000000000000000000000000000000000000002710",
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000000",
},
Memory: []string{
"0000000000000000000000007c41e0668b551f4f902cfaec05b5bdca68b124ce",
"0000000000000000000000000000000000000000000000000000000000000003",
"0000000000000000000000000000000000000000000000000000000000000080",
},
Storage: map[string]string{
"43a43725d4b041c11b63ea10be0771546465a0c0654fd13c2712f2a8ce3f8b85": "0000000000000000000000000000000000000000000000000000000000000050",
},
},
},
}
testExecResultForInternalTxValueSum = uint64(20000)
)
func TestGetContractInternalTransferNativeOperations(t *testing.T) {
refStatus := common.SuccessOperationStatus.Status
refStartingIndex := int64(23)
baseValidation := func(ops []*types.Operation, expectedValueSum uint64) {
prevIndex := int64(-1)
valueSum := int64(0)
absValueSum := uint64(0)
for i, op := range ops {
if op.OperationIdentifier.Index <= prevIndex {
t.Errorf("expect prev index (%v) < curr index (%v) for op %v",
prevIndex, op.OperationIdentifier.Index, i,
)
}
prevIndex = op.OperationIdentifier.Index
if op.Status != refStatus {
t.Errorf("wrong status for op %v", i)
}
if op.Type != common.NativeTransferOperation {
t.Errorf("wrong operation type for op %v", i)
}
if types.Hash(op.Amount.Currency) != common.NativeCurrencyHash {
t.Errorf("wrong currency for op %v", i)
}
val, err := types.AmountValue(op.Amount)
if err != nil {
t.Error(err)
}
valueSum += val.Int64()
absValueSum += val.Abs(val).Uint64()
}
if valueSum != 0 {
t.Errorf("expected sum of all non-gas values to be 0")
}
if expectedValueSum*2 != absValueSum {
t.Errorf("sum of all positive values of operations do not match execpted sum of values")
}
}
testOps, rosettaError := getContractInternalTransferNativeOperations(
testExecResultForInternalTx, refStatus, &refStartingIndex,
)
if rosettaError != nil {
t.Error(rosettaError)
}
baseValidation(testOps, testExecResultForInternalTxValueSum)
if len(testOps) == 0 {
t.Errorf("expect atleast 1 operation")
}
if testOps[0].OperationIdentifier.Index != refStartingIndex {
t.Errorf("expected starting index to be %v", refStartingIndex)
}
if err := assertNativeOperationTypeUniquenessInvariant(testOps); err != nil {
t.Error(err)
}
testOps, rosettaError = getContractInternalTransferNativeOperations(
testExecResultForInternalTx, refStatus, nil,
)
if rosettaError != nil {
t.Error(rosettaError)
}
baseValidation(testOps, testExecResultForInternalTxValueSum)
if len(testOps) == 0 {
t.Errorf("expect atleast 1 operation")
}
if testOps[0].OperationIdentifier.Index != 0 {
t.Errorf("expected starting index to be 0")
}
if err := assertNativeOperationTypeUniquenessInvariant(testOps); err != nil {
t.Error(err)
}
testOps, rosettaError = getContractInternalTransferNativeOperations(
nil, refStatus, nil,
)
if rosettaError != nil {
t.Error(rosettaError)
}
if len(testOps) != 0 {
t.Errorf("expected len 0 test operations for nil execution result")
}
if err := assertNativeOperationTypeUniquenessInvariant(testOps); err != nil {
t.Error(err)
}
testOps, rosettaError = getContractInternalTransferNativeOperations(
&hmy.ExecutionResult{}, refStatus, nil,
)
if rosettaError != nil {
t.Error(rosettaError)
}
if len(testOps) != 0 {
t.Errorf("expected len 0 test operations for nil struct logs")
}
if err := assertNativeOperationTypeUniquenessInvariant(testOps); err != nil {
t.Error(err)
}
}
func TestGetContractTransferNativeOperations(t *testing.T) {
signer := hmytypes.NewEIP155Signer(params.TestChainConfig.ChainID)
refTxValue := big.NewInt(1)
refTx, err := helpers.CreateTestTransaction(
signer, 0, 0, 0, 1e18, gasPrice, refTxValue, []byte("blah-blah-blah"),
)
if err != nil {
t.Fatal(err.Error())
}
refSenderAddr, err := refTx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
refStatus := common.SuccessOperationStatus.Status
refStartingIndex := int64(23)
refReceipt := &hmytypes.Receipt{
PostState: nil,
Status: 1,
GasUsed: params.TxGas * 3, // somme arb number > TxGas
}
baseValidation := func(ops []*types.Operation, expectedValueSum uint64) {
prevIndex := int64(-1)
valueSum := int64(0)
absValueSum := uint64(0)
for i, op := range ops {
if op.OperationIdentifier.Index <= prevIndex {
t.Errorf("expect prev index (%v) < curr index (%v) for op %v",
prevIndex, op.OperationIdentifier.Index, i,
)
}
prevIndex = op.OperationIdentifier.Index
if op.Status != refStatus {
t.Errorf("wrong status for op %v", i)
}
if types.Hash(op.Amount.Currency) != common.NativeCurrencyHash {
t.Errorf("wrong currency for op %v", i)
}
if op.Type == common.ExpendGasOperation {
continue
}
if op.Type != common.NativeTransferOperation {
t.Errorf("wrong operation type for op %v", i)
}
val, err := types.AmountValue(op.Amount)
if err != nil {
t.Error(err)
}
valueSum += val.Int64()
absValueSum += val.Abs(val).Uint64()
}
if valueSum != 0 {
t.Errorf("expected sum of all non-gas values to be 0")
}
if expectedValueSum*2 != absValueSum {
t.Errorf("sum of all positive values of operations do not match execpted sum of values")
}
}
testOps, rosettaError := getContractTransferNativeOperations(
refTx, refReceipt, refSenderAddr, refTx.To(),
&ContractInfo{ExecutionResult: testExecResultForInternalTx}, &refStartingIndex,
)
if rosettaError != nil {
t.Error(rosettaError)
}
baseValidation(testOps, testExecResultForInternalTxValueSum+refTxValue.Uint64())
if len(testOps) == 0 {
t.Errorf("expect atleast 1 operation")
}
if testOps[0].OperationIdentifier.Index != refStartingIndex {
t.Errorf("expected starting index to be %v", refStartingIndex)
}
if err := assertNativeOperationTypeUniquenessInvariant(testOps); err != nil {
t.Error(err)
}
testOps, rosettaError = getContractTransferNativeOperations(
refTx, refReceipt, refSenderAddr, refTx.To(),
&ContractInfo{ExecutionResult: testExecResultForInternalTx}, nil,
)
if rosettaError != nil {
t.Error(rosettaError)
}
baseValidation(testOps, testExecResultForInternalTxValueSum+refTxValue.Uint64())
if len(testOps) == 0 {
t.Errorf("expect atleast 1 operation")
}
if testOps[0].OperationIdentifier.Index != 0 {
t.Errorf("expected starting index to be 0")
}
if err := assertNativeOperationTypeUniquenessInvariant(testOps); err != nil {
t.Error(err)
}
testOps, rosettaError = getContractTransferNativeOperations(
refTx, refReceipt, refSenderAddr, refTx.To(),
&ContractInfo{}, nil,
)
if rosettaError != nil {
t.Error(rosettaError)
}
baseValidation(testOps, refTxValue.Uint64())
if len(testOps) == 0 {
t.Errorf("expect atleast 1 operation")
}
if testOps[0].OperationIdentifier.Index != 0 {
t.Errorf("expected starting index to be 0")
}
if len(testOps) > 3 {
t.Errorf("expect at most 3 operations for nil ExecutionResult")
}
if err := assertNativeOperationTypeUniquenessInvariant(testOps); err != nil {
t.Error(err)
}
}
func TestGetContractCreationNativeOperations(t *testing.T) {
dummyContractKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
@ -586,9 +931,6 @@ func TestNewContractCreationNativeOperations(t *testing.T) {
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
startingOpID,
},
Type: common.ContractCreationOperation,
Status: common.ContractFailureOperationStatus.Status,
Account: senderAccID,
@ -619,7 +961,8 @@ func TestNewContractCreationNativeOperations(t *testing.T) {
Status: hmytypes.ReceiptStatusFailed,
ContractAddress: contractAddr,
}
operations, rosettaError := newContractCreationNativeOperations(startingOpID, tx, receipt, senderAddr)
opIndex := startingOpID.Index + 1
operations, rosettaError := getContractCreationNativeOperations(tx, receipt, senderAddr, &ContractInfo{}, &opIndex)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -634,7 +977,7 @@ func TestNewContractCreationNativeOperations(t *testing.T) {
refOperations[0].Status = common.SuccessOperationStatus.Status
refOperations[1].Status = common.SuccessOperationStatus.Status
receipt.Status = hmytypes.ReceiptStatusSuccessful // Indicate successful tx
operations, rosettaError = newContractCreationNativeOperations(startingOpID, tx, receipt, senderAddr)
operations, rosettaError = getContractCreationNativeOperations(tx, receipt, senderAddr, &ContractInfo{}, &opIndex)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -644,6 +987,62 @@ func TestNewContractCreationNativeOperations(t *testing.T) {
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
traceValidation := func(ops []*types.Operation, expectedValueSum uint64) {
prevIndex := int64(-1)
valueSum := int64(0)
absValueSum := uint64(0)
for i, op := range ops {
if op.OperationIdentifier.Index <= prevIndex {
t.Errorf("expect prev index (%v) < curr index (%v) for op %v",
prevIndex, op.OperationIdentifier.Index, i,
)
}
prevIndex = op.OperationIdentifier.Index
if op.Status != refOperations[0].Status {
t.Errorf("wrong status for op %v", i)
}
if types.Hash(op.Amount.Currency) != common.NativeCurrencyHash {
t.Errorf("wrong currency for op %v", i)
}
if op.Type == common.ExpendGasOperation || op.Type == common.ContractCreationOperation {
continue
}
if op.Type != common.NativeTransferOperation {
t.Errorf("wrong operation type for op %v", i)
}
val, err := types.AmountValue(op.Amount)
if err != nil {
t.Error(err)
}
valueSum += val.Int64()
absValueSum += val.Abs(val).Uint64()
}
if valueSum != 0 {
t.Errorf("expected sum of all non-gas values to be 0")
}
if expectedValueSum*2 != absValueSum {
t.Errorf("sum of all positive values of operations do not match execpted sum of values")
}
}
operations, rosettaError = getContractCreationNativeOperations(
tx, receipt, senderAddr, &ContractInfo{ExecutionResult: testExecResultForInternalTx}, &opIndex,
)
if rosettaError != nil {
t.Fatal(rosettaError)
}
traceValidation(operations, testExecResultForInternalTxValueSum)
if len(operations) == 0 {
t.Errorf("expect atleast 1 operation")
}
if operations[0].OperationIdentifier.Index != opIndex {
t.Errorf("expect first operation to be %v", opIndex)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestNewNativeOperations(t *testing.T) {

Loading…
Cancel
Save