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/block_test.go

1326 lines
40 KiB

package services
import (
"crypto/ecdsa"
"fmt"
"math/big"
"reflect"
"testing"
"github.com/coinbase/rosetta-sdk-go/types"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/harmony-one/harmony/core"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/hmy"
internalCommon "github.com/harmony-one/harmony/internal/common"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"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"
stakingNetwork "github.com/harmony-one/harmony/staking/network"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
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 createTestStakingTransaction(
payloadMaker func() (stakingTypes.Directive, interface{}), key *ecdsa.PrivateKey, nonce, gasLimit uint64,
) (*stakingTypes.StakingTransaction, error) {
tx, err := stakingTypes.NewStakingTransaction(nonce, gasLimit, gasPrice, payloadMaker)
if err != nil {
return nil, err
}
if key == nil {
key, err = crypto.GenerateKey()
if err != nil {
return nil, err
}
}
// Staking transactions are always post EIP155 epoch
return stakingTypes.Sign(tx, stakingTypes.NewEIP155Signer(tx.ChainID()), key)
}
func getMessageFromStakingTx(tx *stakingTypes.StakingTransaction) (map[string]interface{}, error) {
rpcStakingTx, err := rpcV2.NewStakingTransaction(tx, ethcommon.Hash{}, 0, 0, 0)
if err != nil {
return nil, err
}
return types.MarshalMap(rpcStakingTx.Msg)
}
func createTestTransaction(
signer hmytypes.Signer, fromShard, toShard uint32, nonce, gasLimit uint64, amount *big.Int, data []byte,
) (*hmytypes.Transaction, error) {
fromKey, err := crypto.GenerateKey()
if err != nil {
return nil, err
}
toKey, err := crypto.GenerateKey()
if err != nil {
return nil, err
}
toAddr := crypto.PubkeyToAddress(toKey.PublicKey)
var tx *hmytypes.Transaction
if fromShard != toShard {
tx = hmytypes.NewCrossShardTransaction(
nonce, &toAddr, fromShard, toShard, amount, gasLimit, gasPrice, data,
)
} else {
tx = hmytypes.NewTransaction(
nonce, toAddr, fromShard, amount, gasLimit, gasPrice, data,
)
}
return hmytypes.SignTx(tx, signer, fromKey)
}
func createTestContractCreationTransaction(
signer hmytypes.Signer, shard uint32, nonce, gasLimit uint64, data []byte,
) (*hmytypes.Transaction, error) {
fromKey, err := crypto.GenerateKey()
if err != nil {
return nil, err
}
tx := hmytypes.NewContractCreation(nonce, shard, big.NewInt(0), gasLimit, gasPrice, data)
return hmytypes.SignTx(tx, signer, fromKey)
}
// Invariant: A transaction can only contain 1 type of operation(s) other than gas expenditure.
func assertOperationTypeUniquenessInvariant(operations []*types.Operation) error {
foundType := ""
for _, op := range operations {
if op.Type == common.ExpendGasOperation {
continue
}
if foundType == "" {
foundType = op.Type
}
if op.Type != foundType {
return fmt.Errorf("found more than 1 type in given set of operations")
}
}
return nil
}
// Note that this test only checks the general format of each type transaction on Harmony.
// The detailed operation checks for each type of transaction is done in separate unit tests.
func TestFormatTransactionIntegration(t *testing.T) {
gasLimit := uint64(1e18)
gasUsed := uint64(1e5)
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
receiverKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
testFormatStakingTransaction(t, gasLimit, gasUsed, senderKey, receiverKey)
testFormatPlainTransaction(t, gasLimit, gasUsed, senderKey, receiverKey)
// Note that cross-shard receiver operations/transactions are formatted via
// formatCrossShardReceiverTransaction, thus, it is not tested here -- but tested on its own.
testFormatCrossShardSenderTransaction(t, gasLimit, gasUsed, senderKey, receiverKey)
}
func testFormatStakingTransaction(
t *testing.T, gasLimit, gasUsed uint64, senderKey, receiverKey *ecdsa.PrivateKey,
) {
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
receiverAddr := crypto.PubkeyToAddress(receiverKey.PublicKey)
tx, err := createTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveDelegate, stakingTypes.Delegate{
DelegatorAddress: senderAddr,
ValidatorAddress: receiverAddr,
Amount: tenOnes,
}
}, senderKey, 0, gasLimit)
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful,
GasUsed: gasUsed,
}
rosettaTx, rosettaError := formatTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if len(rosettaTx.Operations) != 2 {
t.Error("Expected 2 operations")
}
if err := assertOperationTypeUniquenessInvariant(rosettaTx.Operations); err != nil {
t.Error(err)
}
if rosettaTx.TransactionIdentifier.Hash != tx.Hash().String() {
t.Error("Invalid transaction")
}
if rosettaTx.Operations[0].Type != common.ExpendGasOperation {
t.Error("Expected 1st operation to be gas type")
}
if rosettaTx.Operations[1].Type != tx.StakingType().String() {
t.Error("Expected 2nd operation to be staking type")
}
if reflect.DeepEqual(rosettaTx.Operations[1].Metadata, map[string]interface{}{}) {
t.Error("Expected staking operation to have some metadata")
}
if !reflect.DeepEqual(rosettaTx.Metadata, map[string]interface{}{}) {
t.Error("Expected transaction to have no metadata")
}
if !reflect.DeepEqual(rosettaTx.Operations[0].Account, senderAccID) {
t.Error("Expected sender to pay gas fee")
}
}
func testFormatPlainTransaction(
t *testing.T, gasLimit, gasUsed uint64, senderKey, receiverKey *ecdsa.PrivateKey,
) {
// Note that post EIP-155 epoch singer is tested in detailed tests.
signer := hmytypes.HomesteadSigner{}
tx, err := createTestTransaction(
signer, 0, 0, 0, 1e18, big.NewInt(1), []byte("test"),
)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful,
GasUsed: gasUsed,
}
rosettaTx, rosettaError := formatTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if len(rosettaTx.Operations) != 3 {
t.Error("Expected 3 operations")
}
if err := assertOperationTypeUniquenessInvariant(rosettaTx.Operations); err != nil {
t.Error(err)
}
if rosettaTx.TransactionIdentifier.Hash != tx.Hash().String() {
t.Error("Invalid transaction")
}
if rosettaTx.Operations[0].Type != common.ExpendGasOperation {
t.Error("Expected 1st operation to be gas")
}
if rosettaTx.Operations[1].Type != common.TransferOperation {
t.Error("Expected 2nd operation to transfer related")
}
if rosettaTx.Operations[1].Metadata != nil {
t.Error("Expected 1st operation to have no metadata")
}
if rosettaTx.Operations[2].Metadata != nil {
t.Error("Expected 2nd operation to have no metadata")
}
if reflect.DeepEqual(rosettaTx.Metadata, map[string]interface{}{}) {
t.Error("Expected transaction to have some metadata")
}
if !reflect.DeepEqual(rosettaTx.Operations[0].Account, senderAccID) {
t.Error("Expected sender to pay gas fee")
}
}
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 := assertOperationTypeUniquenessInvariant(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)
testBlockSigInfo := &blockSignerInfo{
signers: map[ethcommon.Address][]bls.SerializedPublicKey{
testAddr: { // Only care about length for this test
bls.SerializedPublicKey{},
bls.SerializedPublicKey{},
},
},
totalKeysSigned: 150,
blockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
}
refTxID := getSpecialCaseTransactionIdentifier(testBlockSigInfo.blockHash, testAddr, SpecialPreStakingRewardTxID)
tx, rosettaError := formatPreStakingRewardTransaction(refTxID, testBlockSigInfo, 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 := assertOperationTypeUniquenessInvariant(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")
}
// Expect: myNumberOfSigForBlock * (totalAmountOfRewardsPerBlock / numOfSigsForBlock) to be my block reward amount
refAmount := new(big.Int).Mul(new(big.Int).Quo(stakingNetwork.BlockReward, big.NewInt(150)), big.NewInt(2))
fmtRefAmount := fmt.Sprintf("%v", refAmount)
if tx.Operations[0].Amount.Value != fmtRefAmount {
t.Errorf("expected operation amount to be %v not %v", fmtRefAmount, tx.Operations[0].Amount.Value)
}
}
func TestFormatPreStakingRewardTransactionFail(t *testing.T) {
testKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
testBlockSigInfo := &blockSignerInfo{
signers: map[ethcommon.Address][]bls.SerializedPublicKey{
testAddr: {},
},
totalKeysSigned: 150,
blockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
}
testTxID := getSpecialCaseTransactionIdentifier(testBlockSigInfo.blockHash, testAddr, SpecialPreStakingRewardTxID)
_, rosettaError := formatPreStakingRewardTransaction(testTxID, testBlockSigInfo, testAddr)
if rosettaError == nil {
t.Fatal("expected rosetta error")
}
if !reflect.DeepEqual(&common.TransactionNotFoundError, rosettaError) {
t.Error("expected transaction not found error")
}
testBlockSigInfo = &blockSignerInfo{
signers: map[ethcommon.Address][]bls.SerializedPublicKey{},
totalKeysSigned: 150,
blockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
}
_, rosettaError = formatPreStakingRewardTransaction(testTxID, testBlockSigInfo, testAddr)
if rosettaError == nil {
t.Fatal("expected rosetta error")
}
if !reflect.DeepEqual(&common.TransactionNotFoundError, rosettaError) {
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 := assertOperationTypeUniquenessInvariant(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,
) {
// Note that post EIP-155 epoch singer is tested in detailed tests.
signer := hmytypes.HomesteadSigner{}
tx, err := createTestTransaction(
signer, 0, 1, 0, 1e18, big.NewInt(1), []byte("test"),
)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful,
GasUsed: gasUsed,
}
rosettaTx, rosettaError := formatTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if len(rosettaTx.Operations) != 2 {
t.Error("Expected 2 operations")
}
if err := assertOperationTypeUniquenessInvariant(rosettaTx.Operations); err != nil {
t.Error(err)
}
if rosettaTx.TransactionIdentifier.Hash != tx.Hash().String() {
t.Error("Invalid transaction")
}
if rosettaTx.Operations[0].Type != common.ExpendGasOperation {
t.Error("Expected 1st operation to be gas")
}
if rosettaTx.Operations[1].Type != common.CrossShardTransferOperation {
t.Error("Expected 2nd operation to cross-shard transfer related")
}
if reflect.DeepEqual(rosettaTx.Operations[1].Metadata, map[string]interface{}{}) {
t.Error("Expected 1st operation to have metadata")
}
if reflect.DeepEqual(rosettaTx.Metadata, map[string]interface{}{}) {
t.Error("Expected transaction to have some metadata")
}
if !reflect.DeepEqual(rosettaTx.Operations[0].Account, senderAccID) {
t.Error("Expected sender to pay gas fee")
}
}
func TestGetStakingOperationsFromCreateValidator(t *testing.T) {
gasLimit := uint64(1e18)
createValidatorTxDescription := stakingTypes.Description{
Name: "SuperHero",
Identity: "YouWouldNotKnow",
Website: "Secret Website",
SecurityContact: "LicenseToKill",
Details: "blah blah blah",
}
tx, err := createTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
fromKey, _ := crypto.GenerateKey()
return stakingTypes.DirectiveCreateValidator, stakingTypes.CreateValidator{
Description: createValidatorTxDescription,
MinSelfDelegation: tenOnes,
MaxTotalDelegation: twelveOnes,
ValidatorAddress: crypto.PubkeyToAddress(fromKey.PublicKey),
Amount: tenOnes,
}
}, nil, 0, gasLimit)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := getMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newOperations(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,
Amount: &types.Amount{
Value: formatNegativeValue(tenOnes),
Currency: &common.Currency,
},
Metadata: metadata,
})
operations, rosettaError := getStakingOperations(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestGetStakingOperationsFromDelegate(t *testing.T) {
gasLimit := uint64(1e18)
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
validatorKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
validatorAddr := crypto.PubkeyToAddress(validatorKey.PublicKey)
tx, err := createTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveDelegate, stakingTypes.Delegate{
DelegatorAddress: senderAddr,
ValidatorAddress: validatorAddr,
Amount: tenOnes,
}
}, senderKey, 0, gasLimit)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := getMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newOperations(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,
Amount: &types.Amount{
Value: formatNegativeValue(tenOnes),
Currency: &common.Currency,
},
Metadata: metadata,
})
operations, rosettaError := getStakingOperations(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestGetStakingOperationsFromUndelegate(t *testing.T) {
gasLimit := uint64(1e18)
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
validatorKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
validatorAddr := crypto.PubkeyToAddress(validatorKey.PublicKey)
tx, err := createTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveUndelegate, stakingTypes.Undelegate{
DelegatorAddress: senderAddr,
ValidatorAddress: validatorAddr,
Amount: tenOnes,
}
}, senderKey, 0, gasLimit)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := getMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newOperations(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,
Amount: &types.Amount{
Value: fmt.Sprintf("0"),
Currency: &common.Currency,
},
Metadata: metadata,
})
operations, rosettaError := getStakingOperations(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestGetStakingOperationsFromCollectRewards(t *testing.T) {
gasLimit := uint64(1e18)
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
tx, err := createTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveCollectRewards, stakingTypes.CollectRewards{
DelegatorAddress: senderAddr,
}
}, senderKey, 0, gasLimit)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := getMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
Logs: []*hmytypes.Log{
{
Address: senderAddr,
Topics: []ethcommon.Hash{staking.CollectRewardsTopic},
Data: tenOnes.Bytes(),
},
},
}
refOperations := newOperations(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,
Amount: &types.Amount{
Value: fmt.Sprintf("%v", tenOnes.Uint64()),
Currency: &common.Currency,
},
Metadata: metadata,
})
operations, rosettaError := getStakingOperations(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestGetStakingOperationsFromEditValidator(t *testing.T) {
gasLimit := uint64(1e18)
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
tx, err := createTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveEditValidator, stakingTypes.EditValidator{
ValidatorAddress: senderAddr,
}
}, senderKey, 0, gasLimit)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := getMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newOperations(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,
Amount: &types.Amount{
Value: fmt.Sprintf("0"),
Currency: &common.Currency,
},
Metadata: metadata,
})
operations, rosettaError := getStakingOperations(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestNewTransferOperations(t *testing.T) {
signer := hmytypes.NewEIP155Signer(params.TestChainConfig.ChainID)
tx, err := createTestTransaction(
signer, 0, 0, 0, 1e18, big.NewInt(1), []byte("test"),
)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
receiverAccID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
t.Fatal(rosettaError)
}
startingOpID := &types.OperationIdentifier{}
// Test failed 'contract' transaction
refOperations := []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
{
Index: startingOpID.Index,
},
},
Type: common.TransferOperation,
Status: common.ContractFailureOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: formatNegativeValue(tx.Value()),
Currency: &common.Currency,
},
},
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 2,
},
RelatedOperations: []*types.OperationIdentifier{
{
Index: startingOpID.Index + 1,
},
},
Type: common.TransferOperation,
Status: common.ContractFailureOperationStatus.Status,
Account: receiverAccID,
Amount: &types.Amount{
Value: fmt.Sprintf("%v", tx.Value().Uint64()),
Currency: &common.Currency,
},
},
}
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusFailed,
}
operations, rosettaError := newTransferOperations(startingOpID, tx, receipt, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
// Test successful plain / contract transaction
refOperations[0].Status = common.SuccessOperationStatus.Status
refOperations[1].Status = common.SuccessOperationStatus.Status
receipt.Status = hmytypes.ReceiptStatusSuccessful
operations, rosettaError = newTransferOperations(startingOpID, tx, receipt, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestNewCrossShardSenderTransferOperations(t *testing.T) {
signer := hmytypes.NewEIP155Signer(params.TestChainConfig.ChainID)
tx, err := createTestTransaction(
signer, 0, 1, 0, 1e18, big.NewInt(1), []byte("data-does-nothing"),
)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
startingOpID := &types.OperationIdentifier{}
receiverAccID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
t.Error(rosettaError)
}
metadata, err := types.MarshalMap(common.CrossShardTransactionOperationMetadata{
From: senderAccID,
To: receiverAccID,
})
if err != nil {
t.Fatal(err)
}
refOperations := []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
startingOpID,
},
Type: common.CrossShardTransferOperation,
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: formatNegativeValue(tx.Value()),
Currency: &common.Currency,
},
Metadata: metadata,
},
}
operations, rosettaError := newCrossShardSenderTransferOperations(startingOpID, tx, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestFormatCrossShardReceiverTransaction(t *testing.T) {
signer := hmytypes.NewEIP155Signer(params.TestChainConfig.ChainID)
tx, err := createTestTransaction(
signer, 0, 1, 0, 1e18, big.NewInt(1), []byte{},
)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
receiverAccID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
t.Fatal(rosettaError)
}
cxReceipt := &hmytypes.CXReceipt{
TxHash: tx.Hash(),
From: senderAddr,
To: tx.To(),
ShardID: 0,
ToShardID: 1,
Amount: tx.Value(),
}
opMetadata, err := types.MarshalMap(common.CrossShardTransactionOperationMetadata{
From: senderAccID,
To: receiverAccID,
})
if err != nil {
t.Error(err)
}
refCxID := &types.TransactionIdentifier{Hash: tx.Hash().String()}
refOperations := []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0, // There is no gas expenditure for cross-shard payout
},
Type: common.CrossShardTransferOperation,
Status: common.SuccessOperationStatus.Status,
Account: receiverAccID,
Amount: &types.Amount{
Value: fmt.Sprintf("%v", tx.Value().Uint64()),
Currency: &common.Currency,
},
Metadata: opMetadata,
},
}
to := tx.ToShardID()
from := tx.ShardID()
refMetadata, err := types.MarshalMap(TransactionMetadata{
CrossShardIdentifier: refCxID,
ToShardID: &to,
FromShardID: &from,
})
refRosettaTx := &types.Transaction{
TransactionIdentifier: refCxID,
Operations: refOperations,
Metadata: refMetadata,
}
rosettaTx, rosettaError := formatCrossShardReceiverTransaction(cxReceipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(rosettaTx, refRosettaTx) {
t.Errorf("Expected transaction to be %v not %v", refRosettaTx, rosettaTx)
}
if err := assertOperationTypeUniquenessInvariant(rosettaTx.Operations); err != nil {
t.Error(err)
}
}
func TestNewContractCreationOperations(t *testing.T) {
dummyContractKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
chainID := params.TestChainConfig.ChainID
signer := hmytypes.NewEIP155Signer(chainID)
tx, err := createTestContractCreationTransaction(
signer, 0, 0, 1e18, []byte("test"),
)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
startingOpID := &types.OperationIdentifier{}
// Test failed contract creation
contractAddr := crypto.PubkeyToAddress(dummyContractKey.PublicKey)
contractAddressID, rosettaError := newAccountIdentifier(contractAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
refOperations := []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
startingOpID,
},
Type: common.ContractCreationOperation,
Status: common.ContractFailureOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: formatNegativeValue(tx.Value()),
Currency: &common.Currency,
},
Metadata: map[string]interface{}{
"contract_address": contractAddressID,
},
},
}
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusFailed,
ContractAddress: contractAddr,
}
operations, rosettaError := newContractCreationOperations(startingOpID, tx, receipt, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
// Test successful contract creation
refOperations[0].Status = common.SuccessOperationStatus.Status
receipt.Status = hmytypes.ReceiptStatusSuccessful // Indicate successful tx
operations, rosettaError = newContractCreationOperations(startingOpID, tx, receipt, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestNewAccountIdentifier(t *testing.T) {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
addr := crypto.PubkeyToAddress(key.PublicKey)
b32Addr, err := internalCommon.AddressToBech32(addr)
if err != nil {
t.Fatalf(err.Error())
}
metadata, err := types.MarshalMap(AccountMetadata{Address: addr.String()})
if err != nil {
t.Fatalf(err.Error())
}
referenceAccID := &types.AccountIdentifier{
Address: b32Addr,
Metadata: metadata,
}
testAccID, rosettaError := newAccountIdentifier(addr)
if rosettaError != nil {
t.Fatalf("unexpected rosetta error: %v", rosettaError)
}
if !reflect.DeepEqual(referenceAccID, testAccID) {
t.Errorf("reference ID %v != testID %v", referenceAccID, testAccID)
}
}
func TestGetAddress(t *testing.T) {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
addr := crypto.PubkeyToAddress(key.PublicKey)
b32Addr, err := internalCommon.AddressToBech32(addr)
if err != nil {
t.Fatalf(err.Error())
}
testAccID := &types.AccountIdentifier{
Address: b32Addr,
}
testAddr, err := getAddress(testAccID)
if err != nil {
t.Fatal(err)
}
if testAddr != addr {
t.Errorf("expected %v to be %v", testAddr.String(), addr.String())
}
defaultAddr := ethcommon.Address{}
testAddr, err = getAddress(nil)
if err == nil {
t.Error("expected err for nil identifier")
}
if testAddr != defaultAddr {
t.Errorf("expected errored addres to be %v not %v", defaultAddr.String(), testAddr.String())
}
}
func TestNewOperations(t *testing.T) {
accountID := &types.AccountIdentifier{
Address: "test-address",
}
gasFee := big.NewInt(int64(1e18))
amount := &types.Amount{
Value: formatNegativeValue(gasFee),
Currency: &common.Currency,
}
ops := newOperations(gasFee, accountID)
if len(ops) != 1 {
t.Fatalf("Expected new operations to be of length 1")
}
if !reflect.DeepEqual(ops[0].Account, accountID) {
t.Errorf("Expected account ID to be %v not %v", accountID, ops[0].OperationIdentifier)
}
if !reflect.DeepEqual(ops[0].Amount, amount) {
t.Errorf("Expected amount to be %v not %v", amount, ops[0].Amount)
}
if ops[0].Type != common.ExpendGasOperation {
t.Errorf("Expected operation to be %v not %v", common.ExpendGasOperation, ops[0].Type)
}
if ops[0].OperationIdentifier.Index != 0 {
t.Errorf("Expected operational ID to be of index 0")
}
if ops[0].Status != common.SuccessOperationStatus.Status {
t.Errorf("Expected operation status to be %v", common.SuccessOperationStatus.Status)
}
}
func TestFindLogsWithTopic(t *testing.T) {
tests := []struct {
receipt *hmytypes.Receipt
topic ethcommon.Hash
expectedResponse []*hmytypes.Log
}{
// test 0
{
receipt: &hmytypes.Receipt{
Logs: []*hmytypes.Log{
{
Topics: []ethcommon.Hash{
staking.IsValidatorKey,
staking.IsValidator,
},
},
{
Topics: []ethcommon.Hash{
crypto.Keccak256Hash([]byte("test")),
},
},
{
Topics: []ethcommon.Hash{
staking.CollectRewardsTopic,
},
},
},
},
topic: staking.IsValidatorKey,
expectedResponse: []*hmytypes.Log{
{
Topics: []ethcommon.Hash{
staking.IsValidatorKey,
staking.IsValidator,
},
},
},
},
// test 1
{
receipt: &hmytypes.Receipt{
Logs: []*hmytypes.Log{
{
Topics: []ethcommon.Hash{
staking.IsValidatorKey,
staking.IsValidator,
},
},
{
Topics: []ethcommon.Hash{
crypto.Keccak256Hash([]byte("test")),
},
},
{
Topics: []ethcommon.Hash{
staking.CollectRewardsTopic,
},
},
},
},
topic: staking.CollectRewardsTopic,
expectedResponse: []*hmytypes.Log{
{
Topics: []ethcommon.Hash{
staking.CollectRewardsTopic,
},
},
},
},
// test 2
{
receipt: &hmytypes.Receipt{
Logs: []*hmytypes.Log{
{
Topics: []ethcommon.Hash{
staking.IsValidatorKey,
},
},
{
Topics: []ethcommon.Hash{
crypto.Keccak256Hash([]byte("test")),
},
},
{
Topics: []ethcommon.Hash{
staking.CollectRewardsTopic,
},
},
},
},
topic: staking.IsValidator,
expectedResponse: []*hmytypes.Log{},
},
}
for i, test := range tests {
response := findLogsWithTopic(test.receipt, test.topic)
if !reflect.DeepEqual(test.expectedResponse, response) {
t.Errorf("Failed test %v, expected %v, got %v", i, test.expectedResponse, response)
}
}
}
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")
}
}