feat: support staking construction

pull/3700/head
xiaohuo 4 years ago committed by Leo Chen
parent d4f48df8e9
commit c1d54fb541
  1. 7
      rosetta/common/errors.go
  2. 105
      rosetta/common/operations.go
  3. 149
      rosetta/common/operations_test.go
  4. 2
      rosetta/services/block.go
  5. 25
      rosetta/services/construction_check.go
  6. 193
      rosetta/services/construction_create.go
  7. 493
      rosetta/services/construction_create_test.go
  8. 24
      rosetta/services/construction_parse.go
  9. 33
      rosetta/services/construction_parse_test.go
  10. 44
      rosetta/services/construction_submit.go
  11. 2
      rosetta/services/mempool.go
  12. 263
      rosetta/services/tx_construction.go
  13. 9
      rosetta/services/tx_format.go
  14. 6
      rosetta/services/tx_format_test.go
  15. 35
      rosetta/services/tx_operation.go
  16. 217
      rosetta/services/tx_operation_components.go
  17. 587
      rosetta/services/tx_operation_components_test.go
  18. 15
      rosetta/services/tx_operation_test.go
  19. 2
      rpc/pool.go
  20. 2
      rpc/transaction.go
  21. 44
      rpc/v2/types.go
  22. 4
      staking/types/transaction.go
  23. 2
      test/helpers/transaction.go

@ -79,6 +79,13 @@ var (
Message: "invalid transaction construction",
Retriable: false,
}
// InvalidStakingConstructionError ..
InvalidStakingConstructionError = types.Error{
Code: 10,
Message: "invalid staking transaction construction",
Retriable: false,
}
)
// NewError create a new error with a given detail structure

@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"
"github.com/harmony-one/harmony/internal/common"
"github.com/coinbase/rosetta-sdk-go/types"
rpcV2 "github.com/harmony-one/harmony/rpc/v2"
@ -23,6 +25,21 @@ const (
// NativeCrossShardTransferOperation is an operation that only affects the native currency.
NativeCrossShardTransferOperation = "NativeCrossShardTransfer"
// CreateValidatorOperation is an operation that only affects the native currency.
CreateValidatorOperation = "CreateValidator"
// EditValidatorOperation is an operation that only affects the native currency.
EditValidatorOperation = "EditValidator"
// DelegateOperation is an operation that only affects the native currency.
DelegateOperation = "Delegate"
// UndelegateOperation is an operation that only affects the native currency.
UndelegateOperation = "Undelegate"
// CollectRewardsOperation is an operation that only affects the native currency.
CollectRewardsOperation = "CollectRewards"
// GenesisFundsOperation is a side effect operation for genesis block only.
// Note that no transaction can be constructed with this operation.
GenesisFundsOperation = "Genesis"
@ -122,3 +139,91 @@ func (s *CrossShardTransactionOperationMetadata) UnmarshalFromInterface(data int
*s = T
return nil
}
func (s *CreateValidatorOperationMetadata) UnmarshalFromInterface(data interface{}) error {
var T CreateValidatorOperationMetadata
dat, err := json.Marshal(data)
if err != nil {
return err
}
if err := json.Unmarshal(dat, &T); err != nil {
return err
}
*s = T
return nil
}
func (s *EditValidatorOperationMetadata) UnmarshalFromInterface(data interface{}) error {
var T EditValidatorOperationMetadata
dat, err := json.Marshal(data)
if err != nil {
return err
}
if err := json.Unmarshal(dat, &T); err != nil {
return err
}
*s = T
return nil
}
func (s *DelegateOperationMetadata) UnmarshalFromInterface(data interface{}) error {
var T DelegateOperationMetadata
dat, err := json.Marshal(data)
if err != nil {
return err
}
if err := json.Unmarshal(dat, &T); err != nil {
return err
}
if T.Amount == nil || T.ValidatorAddress == "" || T.DelegatorAddress == "" {
return fmt.Errorf("expected validator address & delegator address & amount be present for DelegateOperationMetadata")
}
if !common.IsBech32Address(T.ValidatorAddress) || !common.IsBech32Address(T.DelegatorAddress) {
return fmt.Errorf("expected validator address & delegator address to be bech32 format for DelegateOperationMetadata")
}
*s = T
return nil
}
func (s *UndelegateOperationMetadata) UnmarshalFromInterface(data interface{}) error {
var T UndelegateOperationMetadata
dat, err := json.Marshal(data)
if err != nil {
return err
}
if err := json.Unmarshal(dat, &T); err != nil {
return err
}
if T.Amount == nil || T.ValidatorAddress == "" || T.DelegatorAddress == "" {
return fmt.Errorf("expected validator address & delegator address & amount be present for UndelegateOperationMetadata")
}
if !common.IsBech32Address(T.ValidatorAddress) || !common.IsBech32Address(T.DelegatorAddress) {
return fmt.Errorf("expected validator address & delegator address to be bech32 format for UndelegateOperationMetadata")
}
*s = T
return nil
}
func (s *CollectRewardsMetadata) UnmarshalFromInterface(data interface{}) error {
var T CollectRewardsMetadata
dat, err := json.Marshal(data)
if err != nil {
return err
}
if err := json.Unmarshal(dat, &T); err != nil {
return err
}
if T.DelegatorAddress == "" {
return fmt.Errorf("expected delegator address be present for CollectRewardsMetadata")
}
if !common.IsBech32Address(T.DelegatorAddress) {
return fmt.Errorf("expected delegator address to be bech32 format for CollectRewardsMetadata")
}
*s = T
return nil
}

@ -1,6 +1,7 @@
package common
import (
"math/big"
"reflect"
"sort"
"testing"
@ -81,3 +82,151 @@ func TestStakingOperationTypes(t *testing.T) {
t.Errorf("operation types are invalid")
}
}
func TestCreateValidatorOperationMetadata_UnmarshalFromInterface(t *testing.T) {
data := map[string]interface{}{
"validatorAddress": "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy",
"commissionRate": 100000000000000000,
"maxCommissionRate": 900000000000000000,
"maxChangeRate": 50000000000000000,
"minSelfDelegation": 10,
"maxTotalDelegation": 3000,
"amount": 100,
"name": "Alice",
"website": "alice.harmony.one",
"identity": "alice",
"securityContact": "Bob",
"details": "Don't mess with me!!!",
}
s := CreateValidatorOperationMetadata{}
err := s.UnmarshalFromInterface(data)
if err != nil {
t.Fatal(err)
}
if s.ValidatorAddress != "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy" {
t.Fatal("wrong validator address")
}
if s.CommissionRate.Cmp(new(big.Int).SetInt64(100000000000000000)) != 0 {
t.Fatal("wrong commission rate")
}
if s.MaxCommissionRate.Cmp(new(big.Int).SetInt64(900000000000000000)) != 0 {
t.Fatal("wrong max commission rate")
}
if s.MaxChangeRate.Cmp(new(big.Int).SetInt64(50000000000000000)) != 0 {
t.Fatal("wrong max change rate")
}
if s.MinSelfDelegation.Cmp(new(big.Int).SetInt64(10)) != 0 {
t.Fatal("wrong min self delegation")
}
if s.MaxTotalDelegation.Cmp(new(big.Int).SetInt64(3000)) != 0 {
t.Fatal("wrong max total delegation")
}
if s.Amount.Cmp(new(big.Int).SetInt64(100)) != 0 {
t.Fatal("wrong amount")
}
if s.Name != "Alice" {
t.Fatal("wrong name")
}
if s.Website != "alice.harmony.one" {
t.Fatal("wrong website")
}
if s.Identity != "alice" {
t.Fatal("wrong identity")
}
if s.SecurityContact != "Bob" {
t.Fatal("wrong security contact")
}
if s.Details != "Don't mess with me!!!" {
t.Fatal("wrong detail")
}
}
func TestEditValidatorOperationMetadata_UnmarshalFromInterface(t *testing.T) {
data := map[string]interface{}{
"validatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9",
"commissionRate": 100000000000000000,
"minSelfDelegation": 10,
"maxTotalDelegation": 3000,
"name": "Alice",
"website": "alice.harmony.one",
"identity": "alice",
"securityContact": "Bob",
"details": "Don't mess with me!!!",
}
s := EditValidatorOperationMetadata{}
err := s.UnmarshalFromInterface(data)
if err != nil {
t.Fatal(err)
}
if s.ValidatorAddress != "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" {
t.Fatal("wrong validator address")
}
if s.CommissionRate.Cmp(new(big.Int).SetInt64(100000000000000000)) != 0 {
t.Fatal("wrong commission rate")
}
if s.MinSelfDelegation.Cmp(new(big.Int).SetInt64(10)) != 0 {
t.Fatal("wrong min self delegation")
}
if s.MaxTotalDelegation.Cmp(new(big.Int).SetInt64(3000)) != 0 {
t.Fatal("wrong max total delegation")
}
if s.Name != "Alice" {
t.Fatal("wrong name")
}
if s.Website != "alice.harmony.one" {
t.Fatal("wrong website")
}
if s.Identity != "alice" {
t.Fatal("wrong identity")
}
if s.SecurityContact != "Bob" {
t.Fatal("wrong security contact")
}
if s.Details != "Don't mess with me!!!" {
t.Fatal("wrong detail")
}
}
func TestDelegateOperationMetadata_UnmarshalFromInterface(t *testing.T) {
data := map[string]interface{}{
"validatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9",
"delegatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9",
"amount": 20000,
}
s := DelegateOperationMetadata{}
err := s.UnmarshalFromInterface(data)
if err != nil {
t.Fatal(err)
}
if s.ValidatorAddress != "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" {
t.Fatal("wrong validator address")
}
if s.DelegatorAddress != "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" {
t.Fatal("wrong delegator address")
}
if s.Amount.Cmp(new(big.Int).SetInt64(20000)) != 0 {
t.Fatal("wrong amount")
}
}
func TestUndelegateOperationMetadata_UnmarshalFromInterface(t *testing.T) {
data := map[string]interface{}{
"validatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9",
"delegatorAddress": "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9",
"amount": 20000,
}
s := UndelegateOperationMetadata{}
err := s.UnmarshalFromInterface(data)
if err != nil {
t.Fatal(err)
}
if s.ValidatorAddress != "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" {
t.Fatal("wrong validator address")
}
if s.DelegatorAddress != "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" {
t.Fatal("wrong delegator address")
}
if s.Amount.Cmp(new(big.Int).SetInt64(20000)) != 0 {
t.Fatal("wrong amount")
}
}

@ -183,7 +183,7 @@ func (s *BlockAPI) BlockTransaction(
return nil, rosettaError
}
}
transaction, rosettaError = FormatTransaction(txInfo.tx, txInfo.receipt, contractInfo)
transaction, rosettaError = FormatTransaction(txInfo.tx, txInfo.receipt, contractInfo, true)
if rosettaError != nil {
return nil, rosettaError
}

@ -6,6 +6,8 @@ import (
"fmt"
"math/big"
"github.com/harmony-one/harmony/core"
"github.com/coinbase/rosetta-sdk-go/types"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@ -75,6 +77,22 @@ func (s *ConstructAPI) ConstructionPreprocess(
"message": "sender address is not found for given operations",
})
}
if request.Operations[0].Type == common.CreateValidatorOperation && len(txMetadata.SlotPubKeys) == 0 {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "invalid slot public keys",
})
}
if request.Operations[0].Type == common.CreateValidatorOperation && len(txMetadata.SlotKeySigs) == 0 {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "invalid slot key signatures",
})
}
if request.Operations[0].Type == common.EditValidatorOperation && (txMetadata.SlotPubKeyToAdd == "" ||
txMetadata.SlotPubKeyToRemove == "" || txMetadata.SlotKeyToAddSig == "") {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "slot pub key to add/remove or sig to add error",
})
}
if txMetadata.ToShardID != nil && txMetadata.FromShardID != nil &&
components.Type != common.NativeCrossShardTransferOperation && *txMetadata.ToShardID != *txMetadata.FromShardID {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
@ -222,9 +240,10 @@ func (s *ConstructAPI) ConstructionMetadata(
)
}
} else {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "staking operations are not supported",
})
estGasUsed, err = core.IntrinsicGas(data, false, false,
false, options.OperationType == common.CreateValidatorOperation)
estGasUsed *= 2
}
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{

@ -6,6 +6,10 @@ import (
"encoding/json"
"fmt"
"github.com/harmony-one/harmony/crypto/bls"
common2 "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/numeric"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
@ -32,7 +36,7 @@ type WrappedTransaction struct {
// unpackWrappedTransactionFromString ..
func unpackWrappedTransactionFromString(
str string,
str string, signed bool,
) (*WrappedTransaction, hmyTypes.PoolTransaction, *types.Error) {
wrappedTransaction := &WrappedTransaction{}
if err := json.Unmarshal([]byte(str), wrappedTransaction); err != nil {
@ -54,13 +58,196 @@ func unpackWrappedTransactionFromString(
var tx hmyTypes.PoolTransaction
stream := rlp.NewStream(bytes.NewBuffer(wrappedTransaction.RLPBytes), 0)
if wrappedTransaction.IsStaking {
index := 0
if signed {
index = 1
}
stakingTx := &stakingTypes.StakingTransaction{}
if err := stakingTx.DecodeRLP(stream); err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "rlp encoding error for staking transaction").Error(),
})
}
tx = stakingTx
intendedReceipt := &hmyTypes.Receipt{
GasUsed: stakingTx.GasLimit(),
}
formattedTx, rosettaError := FormatTransaction(
stakingTx, intendedReceipt, &ContractInfo{ContractCode: wrappedTransaction.ContractCode}, signed,
)
if rosettaError != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": rosettaError,
})
}
var stakingTransaction *stakingTypes.StakingTransaction
switch stakingTx.StakingType() {
case stakingTypes.DirectiveCreateValidator:
var createValidatorMsg common.CreateValidatorOperationMetadata
// to solve deserialization error
slotPubKeys := formattedTx.Operations[index].Metadata["slotPubKeys"]
delete(formattedTx.Operations[index].Metadata, "slotPubKeys")
slotKeySigs := formattedTx.Operations[index].Metadata["slotKeySigs"]
delete(formattedTx.Operations[index].Metadata, "slotKeySigs")
err := createValidatorMsg.UnmarshalFromInterface(formattedTx.Operations[index].Metadata)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
validatorAddr, err := common2.Bech32ToAddress(createValidatorMsg.ValidatorAddress)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveCreateValidator, stakingTypes.CreateValidator{
Description: stakingTypes.Description{
Name: createValidatorMsg.Name,
Identity: createValidatorMsg.Identity,
Website: createValidatorMsg.Website,
SecurityContact: createValidatorMsg.SecurityContact,
Details: createValidatorMsg.Details,
},
CommissionRates: stakingTypes.CommissionRates{
Rate: numeric.Dec{createValidatorMsg.CommissionRate},
MaxRate: numeric.Dec{createValidatorMsg.MaxCommissionRate},
MaxChangeRate: numeric.Dec{createValidatorMsg.MaxChangeRate},
},
MinSelfDelegation: createValidatorMsg.MinSelfDelegation,
MaxTotalDelegation: createValidatorMsg.MaxTotalDelegation,
ValidatorAddress: validatorAddr,
Amount: createValidatorMsg.Amount,
SlotPubKeys: slotPubKeys.([]bls.SerializedPublicKey),
SlotKeySigs: slotKeySigs.([]bls.SerializedSignature),
}
}
stakingTransaction, _ = stakingTypes.NewStakingTransaction(stakingTx.Nonce(), stakingTx.GasLimit(), stakingTx.GasPrice(), stakePayloadMaker)
case stakingTypes.DirectiveEditValidator:
var editValidatorMsg common.EditValidatorOperationMetadata
// to solve deserialization error
slotKeyToRemove := formattedTx.Operations[index].Metadata["slotPubKeyToRemove"]
delete(formattedTx.Operations[index].Metadata, "slotPubKeyToRemove")
slotKeyToAdd := formattedTx.Operations[index].Metadata["slotPubKeyToAdd"]
delete(formattedTx.Operations[index].Metadata, "slotPubKeyToAdd")
slotKeySigs := formattedTx.Operations[index].Metadata["slotKeyToAddSig"]
delete(formattedTx.Operations[index].Metadata, "slotKeyToAddSig")
err := editValidatorMsg.UnmarshalFromInterface(formattedTx.Operations[index].Metadata)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
validatorAddr, err := common2.Bech32ToAddress(editValidatorMsg.ValidatorAddress)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveEditValidator, stakingTypes.EditValidator{
ValidatorAddress: validatorAddr,
Description: stakingTypes.Description{
Name: editValidatorMsg.Name,
Identity: editValidatorMsg.Identity,
Website: editValidatorMsg.Website,
SecurityContact: editValidatorMsg.SecurityContact,
Details: editValidatorMsg.Details,
},
CommissionRate: &numeric.Dec{editValidatorMsg.CommissionRate},
MinSelfDelegation: editValidatorMsg.MinSelfDelegation,
MaxTotalDelegation: editValidatorMsg.MaxTotalDelegation,
SlotKeyToAdd: slotKeyToAdd.(*bls.SerializedPublicKey),
SlotKeyToRemove: slotKeyToRemove.(*bls.SerializedPublicKey),
SlotKeyToAddSig: slotKeySigs.(*bls.SerializedSignature),
}
}
stakingTransaction, _ = stakingTypes.NewStakingTransaction(stakingTx.Nonce(), stakingTx.GasLimit(), stakingTx.GasPrice(), stakePayloadMaker)
case stakingTypes.DirectiveDelegate:
var delegateMsg common.DelegateOperationMetadata
err := delegateMsg.UnmarshalFromInterface(formattedTx.Operations[index].Metadata)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
validatorAddr, err := common2.Bech32ToAddress(delegateMsg.ValidatorAddress)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
delegatorAddr, err := common2.Bech32ToAddress(delegateMsg.DelegatorAddress)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveDelegate, stakingTypes.Delegate{
ValidatorAddress: validatorAddr,
DelegatorAddress: delegatorAddr,
Amount: delegateMsg.Amount,
}
}
stakingTransaction, _ = stakingTypes.NewStakingTransaction(stakingTx.Nonce(), stakingTx.GasLimit(), stakingTx.GasPrice(), stakePayloadMaker)
case stakingTypes.DirectiveUndelegate:
var undelegateMsg common.UndelegateOperationMetadata
err := undelegateMsg.UnmarshalFromInterface(formattedTx.Operations[index].Metadata)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
validatorAddr, err := common2.Bech32ToAddress(undelegateMsg.ValidatorAddress)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
delegatorAddr, err := common2.Bech32ToAddress(undelegateMsg.DelegatorAddress)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveUndelegate, stakingTypes.Undelegate{
ValidatorAddress: validatorAddr,
DelegatorAddress: delegatorAddr,
Amount: undelegateMsg.Amount,
}
}
stakingTransaction, _ = stakingTypes.NewStakingTransaction(stakingTx.Nonce(), stakingTx.GasLimit(), stakingTx.GasPrice(), stakePayloadMaker)
case stakingTypes.DirectiveCollectRewards:
var collectRewardsMsg common.CollectRewardsMetadata
err := collectRewardsMsg.UnmarshalFromInterface(formattedTx.Operations[index].Metadata)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
delegatorAddr, err := common2.Bech32ToAddress(collectRewardsMsg.DelegatorAddress)
if err != nil {
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveCollectRewards, stakingTypes.CollectRewards{
DelegatorAddress: delegatorAddr,
}
}
stakingTransaction, _ = stakingTypes.NewStakingTransaction(stakingTx.Nonce(), stakingTx.GasLimit(), stakingTx.GasPrice(), stakePayloadMaker)
default:
return nil, nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "staking type error",
})
}
stakingTransaction.SetRawSignature(stakingTx.RawSignatureValues())
tx = stakingTransaction
} else {
plainTx := &hmyTypes.Transaction{}
if err := plainTx.DecodeRLP(stream); err != nil {
@ -185,7 +372,7 @@ func (s *ConstructAPI) ConstructionCombine(
if err := assertValidNetworkIdentifier(request.NetworkIdentifier, s.hmy.ShardID); err != nil {
return nil, err
}
wrappedTransaction, tx, rosettaError := unpackWrappedTransactionFromString(request.UnsignedTransaction)
wrappedTransaction, tx, rosettaError := unpackWrappedTransactionFromString(request.UnsignedTransaction, false)
if rosettaError != nil {
return nil, rosettaError
}

@ -2,10 +2,19 @@ package services
import (
"bytes"
"crypto/ecdsa"
"encoding/json"
"fmt"
"math/big"
"strings"
"testing"
common2 "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/numeric"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/ethereum/go-ethereum/crypto"
@ -47,7 +56,7 @@ func TestUnpackWrappedTransactionFromString(t *testing.T) {
if err != nil {
t.Fatal(err)
}
testWrappedTx, testTx, rosettaError := unpackWrappedTransactionFromString(string(marshalledBytes))
testWrappedTx, testTx, rosettaError := unpackWrappedTransactionFromString(string(marshalledBytes), true)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -83,7 +92,7 @@ func TestUnpackWrappedTransactionFromString(t *testing.T) {
if err != nil {
t.Fatal(err)
}
testWrappedTx, testStx, rosettaError := unpackWrappedTransactionFromString(string(marshalledBytes))
testWrappedTx, testStx, rosettaError := unpackWrappedTransactionFromString(string(marshalledBytes), true)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -97,7 +106,7 @@ func TestUnpackWrappedTransactionFromString(t *testing.T) {
// Test invalid marshall
marshalledBytesFail := marshalledBytes[:]
marshalledBytesFail[0] = 0x0
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail))
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail), true)
if rosettaError == nil {
t.Fatal("expected error")
}
@ -108,7 +117,7 @@ func TestUnpackWrappedTransactionFromString(t *testing.T) {
if err != nil {
t.Fatal(err)
}
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail))
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail), true)
if rosettaError == nil {
t.Fatal("expected error")
}
@ -119,7 +128,7 @@ func TestUnpackWrappedTransactionFromString(t *testing.T) {
if err != nil {
t.Fatal(err)
}
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail))
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail), true)
if rosettaError == nil {
t.Fatal("expected error")
}
@ -130,7 +139,7 @@ func TestUnpackWrappedTransactionFromString(t *testing.T) {
if err != nil {
t.Fatal(err)
}
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail))
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail), true)
if rosettaError == nil {
t.Fatal("expected error")
}
@ -142,8 +151,478 @@ func TestUnpackWrappedTransactionFromString(t *testing.T) {
if err != nil {
t.Fatal(err)
}
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail))
_, _, rosettaError = unpackWrappedTransactionFromString(string(marshalledBytesFail), true)
if rosettaError == nil {
t.Fatal("expected error")
}
}
func TestRecoverSenderAddressFromCreateValidatorString(t *testing.T) {
key, err := crypto.HexToECDSA("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")
if err != nil {
t.Fatal(err.Error())
}
stakingTransaction, expectedPayload, err := stakingCreateValidatorTransaction(key)
if err != nil {
t.Fatal(err.Error())
}
address, err := stakingTransaction.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
_, tx, rosettaError := unpackWrappedTransactionFromString("{\"rlp_bytes\":\"+QExgPkBIJQLWF+NrvvGijEfvUyyDZF0rRdAFvg4hUFsaWNlhWFsaWNlkWFsaWNlLmhhcm1vbnkub25lg0JvYpVEb24ndCBtZXNzIHdpdGggbWUhISHdyYgBY0V4XYoAAMmIDH1xO0naAADIh7GivC7FAACIiscjBInoAACJoqFdCVGb4AAA8bAwssOLExbakeBorDvYdRwJAe9sAqHVi8cSEEkYMCxu0D1YlGcdDIFtrStNMDMg8gL4YrhgaPgAtq32V7Z0kD4EcIBgkSuJO3x7UAeIgIJHVQqz4YblakTr88pIj47RpC9s7zoEvV0rK361p2eEjTE1s2LmaM5rukLHudVmbY46g75we1cI5yLFiTn+mwfBcPO3BiQUiQVrx14tYxAAAICEdzWUAIOhvkCAgIA=\",\"is_staking\":true,\"contract_code\":\"0x\",\"from\":{\"address\":\"one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9\",\"metadata\":{\"hex_address\":\"0xeBCD16e8c1D8f493bA04E99a56474122D81A9c58\"}}}", false)
if rosettaError != nil {
t.Fatal(rosettaError)
}
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
stakingTx, ok := tx.(*stakingTypes.StakingTransaction)
if !ok {
t.Fatal()
}
sig, err := hexutil.Decode("0x5ee37d4f19016a7ba6eef5dad74f0214ecfb73feda46dd263c3dd0ed54f9d4f60d78033196f148b2a005fe0c1af9afbb0e6f087075f1c9addb3ec8d7d2b6b16600")
if err != nil {
t.Fatal(err)
}
stakingTx, err = stakingTx.WithSignature(signer, sig)
if err != nil {
t.Fatal(err)
}
v, r, s := stakingTransaction.RawSignatureValues()
v1, r1, s1 := stakingTx.RawSignatureValues()
if v.String() != v1.String() || r.String() != r1.String() || s.String() != s1.String() {
t.Log(stakingTransaction.RawSignatureValues())
t.Log(stakingTx.RawSignatureValues())
t.Fatal("signature error")
}
if expectedPayload != signer.Hash(stakingTx) {
t.Error("payload error")
}
address, err = stakingTx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
}
func TestRecoverSenderAddressFromEditValidatorString(t *testing.T) {
key, err := crypto.HexToECDSA("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")
if err != nil {
t.Fatal(err.Error())
}
stakingTransaction, expectedPayload, err := stakingEditValidatorTransaction(key)
if err != nil {
t.Fatal(err.Error())
}
address, err := stakingTransaction.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
// todo to correct rlp bytes
_, tx, rosettaError := unpackWrappedTransactionFromString("{\"rlp_bytes\":\"+QFBAfkBMZTrzRbowdj0k7oE6ZpWR0Ei2BqcWPg4hUFsaWNlhWFsaWNlkWFsaWNlLmhhcm1vbnkub25lg0JvYpVEb24ndCBtZXNzIHdpdGggbWUhISHJiAFjRXhdigAAiIrHIwSJ6AAAiaKhXQlRm+AAALAwssOLExbakeBorDvYdRwJAe9sAqHVi8cSEEkYMCxu0D1YlGcdDIFtrStNMDMg8gKwMLLDixMW2pHgaKw72HUcCQHvbAKh1YvHEhBJGDAsbtA9WJRnHQyBba0rTTAzIPICuGBo+AC2rfZXtnSQPgRwgGCRK4k7fHtQB4iAgkdVCrPhhuVqROvzykiPjtGkL2zvOgS9XSsrfrWnZ4SNMTWzYuZozmu6Qse51WZtjjqDvnB7VwjnIsWJOf6bB8Fw87cGJBSAgIR3NZQAgqQQgICA\",\"is_staking\":true,\"contract_code\":\"0x\",\"from\":{\"address\":\"one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9\",\"metadata\":{\"hex_address\":\"0xeBCD16e8c1D8f493bA04E99a56474122D81A9c58\"}}}", false)
if rosettaError != nil {
t.Fatal(rosettaError)
}
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
stakingTx, ok := tx.(*stakingTypes.StakingTransaction)
if !ok {
t.Fatal()
}
sig, err := hexutil.Decode("0xe7d6edf1fc99806533bb2c5c5ece5873ddbf391c58ab2b839afb0770d86d70df6ccb4727afa97b0f26bec18dbb6bf287ef93867b2129af2d2a9bcced735d79f400")
if err != nil {
t.Fatal(err)
}
stakingTx, err = stakingTx.WithSignature(signer, sig)
if err != nil {
t.Fatal(err)
}
v, r, s := stakingTransaction.RawSignatureValues()
v1, r1, s1 := stakingTx.RawSignatureValues()
if v.String() != v1.String() || r.String() != r1.String() || s.String() != s1.String() {
t.Log(stakingTransaction.RawSignatureValues())
t.Log(stakingTx.RawSignatureValues())
t.Fatal("signature error")
}
if expectedPayload != signer.Hash(stakingTx) {
t.Error("payload error")
}
address, err = stakingTx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
}
func TestRecoverSenderAddressFromDelegateString(t *testing.T) {
key, err := crypto.HexToECDSA("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")
if err != nil {
t.Fatal(err.Error())
}
stakingTransaction, expectedPayload, err := stakingDelegateTransaction(key)
if err != nil {
t.Fatal(err.Error())
}
address, err := stakingTransaction.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
_, tx, rosettaError := unpackWrappedTransactionFromString("{\"rlp_bytes\":\"+EEC85TrzRbowdj0k7oE6ZpWR0Ei2BqcWJTrzRbowdj0k7oE6ZpWR0Ei2BqcWIiKxyMEiegAAICEdzWUAIKkEICAgA==\",\"is_staking\":true,\"contract_code\":\"0x\",\"from\":{\"address\":\"one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9\",\"metadata\":{\"hex_address\":\"0xeBCD16e8c1D8f493bA04E99a56474122D81A9c58\"}}}", false)
if rosettaError != nil {
t.Fatal(rosettaError)
}
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
stakingTx, ok := tx.(*stakingTypes.StakingTransaction)
if !ok {
t.Fatal()
}
sig, err := hexutil.Decode("0xfcfdda6ac52e81c5a4f53628588cd7fb2b0da40fb42f26472329698522230ca759fd3ba51b4078cc2b2370253688f1cc7f16706e897bf9fee0c1e7369f22dbf500")
if err != nil {
t.Fatal(err)
}
stakingTx, err = stakingTx.WithSignature(signer, sig)
if err != nil {
t.Fatal(err)
}
v, r, s := stakingTransaction.RawSignatureValues()
v1, r1, s1 := stakingTx.RawSignatureValues()
if v.String() != v1.String() || r.String() != r1.String() || s.String() != s1.String() {
t.Log(stakingTransaction.RawSignatureValues())
t.Log(stakingTx.RawSignatureValues())
t.Fatal("signature error")
}
if expectedPayload != signer.Hash(stakingTx) {
t.Error("payload error")
}
address, err = stakingTx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
}
func TestRecoverSenderAddressFromUndelegateString(t *testing.T) {
key, err := crypto.HexToECDSA("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")
if err != nil {
t.Fatal(err.Error())
}
stakingTransaction, expectedPayload, err := stakingUndelegateTransaction(key)
if err != nil {
t.Fatal(err.Error())
}
address, err := stakingTransaction.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
_, tx, rosettaError := unpackWrappedTransactionFromString("{\"rlp_bytes\":\"+EED85TrzRbowdj0k7oE6ZpWR0Ei2BqcWJTrzRbowdj0k7oE6ZpWR0Ei2BqcWIiKxyMEiegAAICEdzWUAIJSCICAgA==\",\"is_staking\":true,\"contract_code\":\"0x\",\"from\":{\"address\":\"one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9\",\"metadata\":{\"hex_address\":\"0xeBCD16e8c1D8f493bA04E99a56474122D81A9c58\"}}}", false)
if rosettaError != nil {
t.Fatal(rosettaError)
}
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
stakingTx, ok := tx.(*stakingTypes.StakingTransaction)
if !ok {
t.Fatal()
}
sig, err := hexutil.Decode("0xb24e011d4013f0101a68ee0a6181e311fab6f65b768623f4ef8c6d5e429b507e3ce5dd52f3e85ae969854f7ac36a2ac74fe2d779ac9a9d8f38a64f683565980e00")
if err != nil {
t.Fatal(err)
}
stakingTx, err = stakingTx.WithSignature(signer, sig)
if err != nil {
t.Fatal(err)
}
v, r, s := stakingTransaction.RawSignatureValues()
v1, r1, s1 := stakingTx.RawSignatureValues()
if v.String() != v1.String() || r.String() != r1.String() || s.String() != s1.String() {
t.Log(stakingTransaction.RawSignatureValues())
t.Log(stakingTx.RawSignatureValues())
t.Fatal("signature error")
}
if expectedPayload != signer.Hash(stakingTx) {
t.Error("payload error")
}
address, err = stakingTx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
}
func TestRecoverSenderAddressFromCollectRewardsString(t *testing.T) {
key, err := crypto.HexToECDSA("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")
if err != nil {
t.Fatal(err.Error())
}
stakingTransaction, expectedPayload, err := stakingCollectRewardsTransaction(key)
if err != nil {
t.Fatal(err.Error())
}
address, err := stakingTransaction.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
_, tx, rosettaError := unpackWrappedTransactionFromString("{\"rlp_bytes\":\"4wTVlOvNFujB2PSTugTpmlZHQSLYGpxYgIR3NZQAglIIgICA\",\"is_staking\":true,\"contract_code\":\"0x\",\"from\":{\"address\":\"one13lx3exmpfc446vsguc5d0mtgha2ff7h5uz85pk\",\"metadata\":{\"hex_address\":\"0x8fCD1C9B614E2b5D3208E628d7eD68bF5494faF4\"}}}", false)
if rosettaError != nil {
a, _ := json.Marshal(rosettaError)
fmt.Println(string(a))
t.Fatal(rosettaError)
}
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
stakingTx, ok := tx.(*stakingTypes.StakingTransaction)
if !ok {
t.Fatal()
}
sig, err := hexutil.Decode("0x13f25e40cf5cadf9ae68a318b216de52dc97ec423f581a36defccbdd31870cc26c66872f9f543246d0da7ee6b48d7e11e5034227795e80a5c1e95ac68a2a024500")
if err != nil {
t.Fatal(err)
}
stakingTx, err = stakingTx.WithSignature(signer, sig)
if err != nil {
t.Fatal(err)
}
v, r, s := stakingTransaction.RawSignatureValues()
v1, r1, s1 := stakingTx.RawSignatureValues()
if v.String() != v1.String() || r.String() != r1.String() || s.String() != s1.String() {
t.Log(stakingTransaction.RawSignatureValues())
t.Log(stakingTx.RawSignatureValues())
t.Fatal("signature error")
}
if expectedPayload != signer.Hash(stakingTx) {
t.Error("payload error")
}
address, err = stakingTx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
if strings.ToLower(hexutil.Encode(address[:])) != "0xebcd16e8c1d8f493ba04e99a56474122d81a9c58" {
t.Fatal("address error")
}
}
func stakingCreateValidatorTransaction(key *ecdsa.PrivateKey) (*stakingTypes.StakingTransaction, common2.Hash, error) {
var pub bls.SerializedPublicKey
pubb, err := hexutil.Decode("0x30b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202")
if err != nil {
return nil, common2.Hash{}, err
}
copy(pub[:], pubb)
var sig bls.SerializedSignature
sigg, err := hexutil.Decode("0x68f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b7062414")
if err != nil {
return nil, common2.Hash{}, err
}
copy(sig[:], sigg)
validator, _ := common.Bech32ToAddress("one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy")
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveCreateValidator, stakingTypes.CreateValidator{
Description: stakingTypes.Description{
Name: "Alice",
Identity: "alice",
Website: "alice.harmony.one",
SecurityContact: "Bob",
Details: "Don't mess with me!!!",
},
CommissionRates: stakingTypes.CommissionRates{
Rate: numeric.Dec{new(big.Int).SetUint64(100000000000000000)},
MaxRate: numeric.Dec{new(big.Int).SetUint64(900000000000000000)},
MaxChangeRate: numeric.Dec{new(big.Int).SetUint64(50000000000000000)},
},
MinSelfDelegation: new(big.Int).Mul(new(big.Int).SetInt64(10), big.NewInt(1e18)),
MaxTotalDelegation: new(big.Int).Mul(new(big.Int).SetInt64(3000), big.NewInt(1e18)),
ValidatorAddress: validator,
SlotPubKeys: []bls.SerializedPublicKey{pub},
SlotKeySigs: []bls.SerializedSignature{sig},
Amount: new(big.Int).Mul(new(big.Int).SetInt64(100), big.NewInt(1e18)),
}
}
gasPrice := big.NewInt(2000000000)
tx, _ := stakingTypes.NewStakingTransaction(0, 10600000, gasPrice, stakePayloadMaker)
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
signingPayload := signer.Hash(tx)
stakingTransaction, err := stakingTypes.Sign(tx, signer, key)
if err != nil {
return nil, common2.Hash{}, err
}
return stakingTransaction, signingPayload, nil
}
func stakingEditValidatorTransaction(key *ecdsa.PrivateKey) (*stakingTypes.StakingTransaction, common2.Hash, error) {
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
var slotKeyToRemove bls.SerializedPublicKey
removeBytes, _ := hexutil.Decode("0x30b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202")
copy(slotKeyToRemove[:], removeBytes)
var slotKeyToAdd bls.SerializedPublicKey
addBytes, _ := hexutil.Decode("0x30b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202")
copy(slotKeyToAdd[:], addBytes)
var slotKeyToAddSig bls.SerializedSignature
sigBytes, _ := hexutil.Decode("0x68f800b6adf657b674903e04708060912b893b7c7b500788808247550ab3e186e56a44ebf3ca488f8ed1a42f6cef3a04bd5d2b2b7eb5a767848d3135b362e668ce6bba42c7b9d5666d8e3a83be707b5708e722c58939fe9b07c170f3b7062414")
copy(slotKeyToAddSig[:], sigBytes)
validator, _ := common.Bech32ToAddress("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9")
return stakingTypes.DirectiveEditValidator, stakingTypes.EditValidator{
Description: stakingTypes.Description{
Name: "Alice",
Identity: "alice",
Website: "alice.harmony.one",
SecurityContact: "Bob",
Details: "Don't mess with me!!!",
},
CommissionRate: &numeric.Dec{new(big.Int).SetUint64(100000000000000000)},
MinSelfDelegation: new(big.Int).Mul(new(big.Int).SetInt64(10), big.NewInt(1e18)),
MaxTotalDelegation: new(big.Int).Mul(new(big.Int).SetInt64(3000), big.NewInt(1e18)),
SlotKeyToRemove: &slotKeyToRemove,
SlotKeyToAdd: &slotKeyToAdd,
SlotKeyToAddSig: &slotKeyToAddSig,
ValidatorAddress: validator,
}
}
gasPrice := big.NewInt(2000000000)
tx, _ := stakingTypes.NewStakingTransaction(0, 42000, gasPrice, stakePayloadMaker)
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
signingPayload := signer.Hash(tx)
stakingTransaction, err := stakingTypes.Sign(tx, signer, key)
if err != nil {
return nil, common2.Hash{}, err
}
return stakingTransaction, signingPayload, nil
}
func stakingDelegateTransaction(key *ecdsa.PrivateKey) (*stakingTypes.StakingTransaction, common2.Hash, error) {
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
validator, _ := common.Bech32ToAddress("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9")
delegator, _ := common.Bech32ToAddress("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9")
return stakingTypes.DirectiveDelegate, stakingTypes.Delegate{
ValidatorAddress: validator,
DelegatorAddress: delegator,
Amount: new(big.Int).Mul(new(big.Int).SetInt64(10), big.NewInt(1e18)),
}
}
gasPrice := big.NewInt(2000000000)
tx, _ := stakingTypes.NewStakingTransaction(0, 42000, gasPrice, stakePayloadMaker)
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
signingPayload := signer.Hash(tx)
stakingTransaction, err := stakingTypes.Sign(tx, signer, key)
if err != nil {
return nil, common2.Hash{}, err
}
return stakingTransaction, signingPayload, nil
}
func stakingUndelegateTransaction(key *ecdsa.PrivateKey) (*stakingTypes.StakingTransaction, common2.Hash, error) {
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
validator, _ := common.Bech32ToAddress("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9")
delegator, _ := common.Bech32ToAddress("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9")
return stakingTypes.DirectiveUndelegate, stakingTypes.Undelegate{
ValidatorAddress: validator,
DelegatorAddress: delegator,
Amount: new(big.Int).Mul(new(big.Int).SetInt64(10), big.NewInt(1e18)),
}
}
gasPrice := big.NewInt(2000000000)
tx, _ := stakingTypes.NewStakingTransaction(0, 21000, gasPrice, stakePayloadMaker)
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
signingPayload := signer.Hash(tx)
stakingTransaction, err := stakingTypes.Sign(tx, signer, key)
if err != nil {
return nil, common2.Hash{}, err
}
return stakingTransaction, signingPayload, nil
}
func stakingCollectRewardsTransaction(key *ecdsa.PrivateKey) (*stakingTypes.StakingTransaction, common2.Hash, error) {
stakePayloadMaker := func() (stakingTypes.Directive, interface{}) {
delegator, _ := common.Bech32ToAddress("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9")
return stakingTypes.DirectiveCollectRewards, stakingTypes.CollectRewards{
DelegatorAddress: delegator,
}
}
gasPrice := big.NewInt(2000000000)
tx, _ := stakingTypes.NewStakingTransaction(0, 21000, gasPrice, stakePayloadMaker)
signer := stakingTypes.NewEIP155Signer(new(big.Int).SetUint64(1))
signingPayload := signer.Hash(tx)
stakingTransaction, err := stakingTypes.Sign(tx, signer, key)
if err != nil {
return nil, common2.Hash{}, err
}
return stakingTransaction, signingPayload, nil
}

@ -18,7 +18,7 @@ func (s *ConstructAPI) ConstructionParse(
if err := assertValidNetworkIdentifier(request.NetworkIdentifier, s.hmy.ShardID); err != nil {
return nil, err
}
wrappedTransaction, tx, rosettaError := unpackWrappedTransactionFromString(request.Transaction)
wrappedTransaction, tx, rosettaError := unpackWrappedTransactionFromString(request.Transaction, request.Signed)
if rosettaError != nil {
return nil, rosettaError
}
@ -30,7 +30,23 @@ func (s *ConstructAPI) ConstructionParse(
if request.Signed {
return parseSignedTransaction(ctx, wrappedTransaction, tx)
}
return parseUnsignedTransaction(ctx, wrappedTransaction, tx)
rsp, err := parseUnsignedTransaction(ctx, wrappedTransaction, tx)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err,
})
}
// it is unsigned as it reach to here, makes no sense, just to happy rosetta testing
switch rsp.Operations[0].Type {
case common.CreateValidatorOperation:
delete(rsp.Operations[0].Metadata, "slotPubKeys")
delete(rsp.Operations[0].Metadata, "slotKeySigs")
return rsp, nil
default:
return rsp, nil
}
}
// parseUnsignedTransaction ..
@ -54,7 +70,7 @@ func parseUnsignedTransaction(
GasUsed: tx.GasLimit(),
}
formattedTx, rosettaError := FormatTransaction(
tx, intendedReceipt, &ContractInfo{ContractCode: wrappedTransaction.ContractCode},
tx, intendedReceipt, &ContractInfo{ContractCode: wrappedTransaction.ContractCode}, false,
)
if rosettaError != nil {
return nil, rosettaError
@ -97,7 +113,7 @@ func parseSignedTransaction(
GasUsed: tx.GasLimit(),
}
formattedTx, rosettaError := FormatTransaction(
tx, intendedReceipt, &ContractInfo{ContractCode: wrappedTransaction.ContractCode},
tx, intendedReceipt, &ContractInfo{ContractCode: wrappedTransaction.ContractCode}, true,
)
if rosettaError != nil {
return nil, rosettaError

@ -5,6 +5,8 @@ import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/coinbase/rosetta-sdk-go/parser"
"github.com/ethereum/go-ethereum/crypto"
@ -38,7 +40,7 @@ func TestParseUnsignedTransaction(t *testing.T) {
refTestReceipt := &hmytypes.Receipt{
GasUsed: testTx.GasLimit(),
}
refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, &ContractInfo{})
refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, &ContractInfo{}, false)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -79,7 +81,32 @@ func TestParseUnsignedTransaction(t *testing.T) {
}
func TestParseUnsignedTransactionStaking(t *testing.T) {
// TODO (dm): implement staking test
bytes := "{\"rlp_bytes\":\"+DkC65TrzRbowdj0k7oE6ZpWR0Ei2BqcWJTrzRbowdj0k7oE6ZpWR0Ei2BqcWAqAhHc1lACCUgiAgIA=\",\"is_staking\":true,\"contract_code\":\"0x\",\"from\":{\"address\":\"one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9\",\"metadata\":{\"hex_address\":\"0xeBCD16e8c1D8f493bA04E99a56474122D81A9c58\"}}}"
wrappedTransaction, tx, rosettaError := unpackWrappedTransactionFromString(bytes, false)
if rosettaError != nil {
t.Fatal(rosettaError)
}
_, err := parseUnsignedTransaction(context.Background(), wrappedTransaction, tx)
if err != nil {
t.Fatal(err)
}
bytes = "{\"rlp_bytes\":\"+HgC65TrzRbowdj0k7oE6ZpWR0Ei2BqcWJTrzRbowdj0k7oE6ZpWR0Ei2BqcWAoBhDuaygCCpBAon6JAUrdiS7lD8XyHhos2yj8gAff6d+If7EVYGijoRVOgKnIb+8ecrVX0wO/R1C0AX/FUL5AJ6jyOh1CtJ3kfV/c=\",\"is_staking\":true,\"contract_code\":\"0x\",\"from\":{\"address\":\"one1snm53h0yk5xrqcxrna0n3g3a4um7peqctwrt30\",\"metadata\":{\"hex_address\":\"0x84f748DDE4B50C3060c39f5f38a23dAF37E0e418\"}}}"
wrappedTransaction, tx, rosettaError = unpackWrappedTransactionFromString(bytes, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}
addr, err2 := tx.SenderAddress()
if err2 != nil {
t.Fatal(err2)
}
if hexutil.Encode(addr[:]) != "0x84f748dde4b50c3060c39f5f38a23daf37e0e418" {
t.Fatal("sender addr error")
}
_, err = parseSignedTransaction(context.Background(), wrappedTransaction, tx)
if err != nil {
t.Fatal(err)
}
}
func TestParseSignedTransaction(t *testing.T) {
@ -101,7 +128,7 @@ func TestParseSignedTransaction(t *testing.T) {
refTestReceipt := &hmytypes.Receipt{
GasUsed: testTx.GasLimit(),
}
refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, &ContractInfo{})
refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, &ContractInfo{}, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}

@ -5,11 +5,10 @@ import (
"fmt"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/pkg/errors"
hmyTypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/rosetta/common"
stakingTypes "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors"
)
// ConstructionHash implements the /construction/hash endpoint.
@ -19,7 +18,7 @@ func (s *ConstructAPI) ConstructionHash(
if err := assertValidNetworkIdentifier(request.NetworkIdentifier, s.hmy.ShardID); err != nil {
return nil, err
}
_, tx, rosettaError := unpackWrappedTransactionFromString(request.SignedTransaction)
_, tx, rosettaError := unpackWrappedTransactionFromString(request.SignedTransaction, true)
if rosettaError != nil {
return nil, rosettaError
}
@ -45,7 +44,7 @@ func (s *ConstructAPI) ConstructionSubmit(
if err := assertValidNetworkIdentifier(request.NetworkIdentifier, s.hmy.ShardID); err != nil {
return nil, err
}
wrappedTransaction, tx, rosettaError := unpackWrappedTransactionFromString(request.SignedTransaction)
wrappedTransaction, tx, rosettaError := unpackWrappedTransactionFromString(request.SignedTransaction, true)
if rosettaError != nil {
return nil, rosettaError
}
@ -55,45 +54,54 @@ func (s *ConstructAPI) ConstructionSubmit(
})
}
if tx.ShardID() != s.hmy.ShardID {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
return nil, common.NewError(common.StakingTransactionSubmissionError, map[string]interface{}{
"message": fmt.Sprintf("transaction is for shard %v != shard %v", tx.ShardID(), s.hmy.ShardID),
})
}
wrappedSenderAddress, err := getAddress(wrappedTransaction.From)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
return nil, common.NewError(common.StakingTransactionSubmissionError, map[string]interface{}{
"message": errors.WithMessage(err, "unable to get address from wrapped transaction"),
})
}
txSenderAddress, err := tx.SenderAddress()
var signedTx hmyTypes.PoolTransaction
if stakingTx, ok := tx.(*stakingTypes.StakingTransaction); ok && wrappedTransaction.IsStaking {
signedTx = stakingTx
} else if plainTx, ok := tx.(*hmyTypes.Transaction); ok && !wrappedTransaction.IsStaking {
signedTx = plainTx
} else {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "invalid/inconsistent type or unknown transaction type stored in wrapped transaction",
})
}
txSenderAddress, err := signedTx.SenderAddress()
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
return nil, common.NewError(common.StakingTransactionSubmissionError, map[string]interface{}{
"message": errors.WithMessage(err, "unable to get sender address from transaction").Error(),
})
}
if wrappedSenderAddress != txSenderAddress {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
return nil, common.NewError(common.StakingTransactionSubmissionError, map[string]interface{}{
"message": "transaction sender address does not match wrapped transaction sender address",
})
}
if stakingTx, ok := tx.(*stakingTypes.StakingTransaction); ok && wrappedTransaction.IsStaking {
if err := s.hmy.SendStakingTx(ctx, stakingTx); err != nil {
if wrappedTransaction.IsStaking {
if err := s.hmy.SendStakingTx(ctx, signedTx.(*stakingTypes.StakingTransaction)); err != nil {
return nil, common.NewError(common.StakingTransactionSubmissionError, map[string]interface{}{
"message": err.Error(),
"message": fmt.Sprintf("error is: %s, gas price is: %s, gas limit is: %d", err.Error(), signedTx.GasPrice().String(), signedTx.GasLimit()),
})
}
} else if plainTx, ok := tx.(*hmyTypes.Transaction); ok && !wrappedTransaction.IsStaking {
if err := s.hmy.SendTx(ctx, plainTx); err != nil {
} else {
if err := s.hmy.SendTx(ctx, signedTx.(*hmyTypes.Transaction)); err != nil {
return nil, common.NewError(common.TransactionSubmissionError, map[string]interface{}{
"message": err.Error(),
})
}
} else {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "invalid/inconsistent type or unknown transaction type stored in wrapped transaction",
})
}
return &types.TransactionIdentifierResponse{

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

@ -3,9 +3,14 @@ package services
import (
"encoding/json"
"fmt"
"math/big"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/harmony-one/harmony/crypto/bls"
common2 "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/numeric"
types2 "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors"
hmyTypes "github.com/harmony-one/harmony/core/types"
@ -22,6 +27,12 @@ type TransactionMetadata struct {
ContractAccountIdentifier *types.AccountIdentifier `json:"contract_account_identifier,omitempty"`
Data *string `json:"data,omitempty"`
Logs []*hmyTypes.Log `json:"logs,omitempty"`
// SlotPubKeys SlotPubKeyToAdd SlotPubKeyToRemove are all hex representation of bls public key
SlotPubKeys []string `json:"slot_pub_keys,omitempty"`
SlotKeySigs []string `json:"slot_key_sigs,omitempty"`
SlotKeyToAddSig string `json:"slot_key_to_add_sig,omitempty"`
SlotPubKeyToAdd string `json:"slot_pub_key_to_add,omitempty"`
SlotPubKeyToRemove string `json:"slot_pub_key_to_remove,omitempty"`
}
// UnmarshalFromInterface ..
@ -39,7 +50,6 @@ func (t *TransactionMetadata) UnmarshalFromInterface(metaData interface{}) error
}
// ConstructTransaction object (unsigned).
// TODO (dm): implement staking transaction construction
func ConstructTransaction(
components *OperationComponents, metadata *ConstructMetadata, sourceShardID uint32,
) (response hmyTypes.PoolTransaction, rosettaError *types.Error) {
@ -69,6 +79,26 @@ func ConstructTransaction(
if tx, rosettaError = constructPlainTransaction(components, metadata, sourceShardID); rosettaError != nil {
return nil, rosettaError
}
case common.CreateValidatorOperation:
if tx, rosettaError = constructCreateValidatorTransaction(components, metadata); rosettaError != nil {
return nil, rosettaError
}
case common.EditValidatorOperation:
if tx, rosettaError = constructEditValidatorTransaction(components, metadata); rosettaError != nil {
return nil, rosettaError
}
case common.DelegateOperation:
if tx, rosettaError = constructDelegateTransaction(components, metadata); rosettaError != nil {
return nil, rosettaError
}
case common.UndelegateOperation:
if tx, rosettaError = constructUndelegateTransaction(components, metadata); rosettaError != nil {
return nil, rosettaError
}
case common.CollectRewardsOperation:
if tx, rosettaError = constructCollectRewardsTransaction(components, metadata); rosettaError != nil {
return nil, rosettaError
}
default:
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": fmt.Sprintf("cannot create transaction with component type %v", components.Type),
@ -141,6 +171,237 @@ func constructContractCreationTransaction(
), nil
}
func constructCreateValidatorTransaction(
components *OperationComponents, metadata *ConstructMetadata,
) (hmyTypes.PoolTransaction, *types.Error) {
createValidatorMsg := components.StakingMessage.(common.CreateValidatorOperationMetadata)
validatorAddr, err := common2.Bech32ToAddress(createValidatorMsg.ValidatorAddress)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "convert validator address error").Error(),
})
}
var slotPubKeys []bls.SerializedPublicKey
for _, slotPubKey := range metadata.Transaction.SlotPubKeys {
var pubKey bls.SerializedPublicKey
key, err := hexutil.Decode(slotPubKey)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "decode slot public key error").Error(),
})
}
copy(pubKey[:], key)
slotPubKeys = append(slotPubKeys, pubKey)
}
var slotKeySigs []bls.SerializedSignature
for _, slotKeySig := range metadata.Transaction.SlotKeySigs {
var keySig bls.SerializedSignature
sig, err := hexutil.Decode(slotKeySig)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "decode slot key sig error").Error(),
})
}
copy(keySig[:], sig)
slotKeySigs = append(slotKeySigs, keySig)
}
stakePayloadMaker := func() (types2.Directive, interface{}) {
return types2.DirectiveCreateValidator, types2.CreateValidator{
Description: types2.Description{
Name: createValidatorMsg.Name,
Identity: createValidatorMsg.Identity,
Website: createValidatorMsg.Website,
SecurityContact: createValidatorMsg.SecurityContact,
Details: createValidatorMsg.Details,
},
CommissionRates: types2.CommissionRates{
Rate: numeric.Dec{createValidatorMsg.CommissionRate},
MaxRate: numeric.Dec{createValidatorMsg.MaxCommissionRate},
MaxChangeRate: numeric.Dec{createValidatorMsg.MaxChangeRate},
},
MinSelfDelegation: new(big.Int).Mul(createValidatorMsg.MinSelfDelegation, big.NewInt(1e18)),
MaxTotalDelegation: new(big.Int).Mul(createValidatorMsg.MaxTotalDelegation, big.NewInt(1e18)),
ValidatorAddress: validatorAddr,
SlotPubKeys: slotPubKeys,
SlotKeySigs: slotKeySigs,
Amount: new(big.Int).Mul(createValidatorMsg.Amount, big.NewInt(1e18)),
}
}
stakingTransaction, err := types2.NewStakingTransaction(metadata.Nonce, metadata.GasLimit, metadata.GasPrice, stakePayloadMaker)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "new staking transaction error").Error(),
})
}
return stakingTransaction, nil
}
func constructEditValidatorTransaction(
components *OperationComponents, metadata *ConstructMetadata,
) (hmyTypes.PoolTransaction, *types.Error) {
editValidatorMsg := components.StakingMessage.(common.EditValidatorOperationMetadata)
validatorAddr, err := common2.Bech32ToAddress(editValidatorMsg.ValidatorAddress)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "convert validator address error").Error(),
})
}
var slotKeyToAdd bls.SerializedPublicKey
slotKeyToAddBytes, err := hexutil.Decode(metadata.Transaction.SlotPubKeyToAdd)
copy(slotKeyToAdd[:], slotKeyToAddBytes)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "parse slotKeyToAdd error").Error(),
})
}
var slotKeyToRemove bls.SerializedPublicKey
slotKeyToRemoveBytes, err := hexutil.Decode(metadata.Transaction.SlotPubKeyToRemove)
copy(slotKeyToRemove[:], slotKeyToRemoveBytes)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "parse slotKeyToRemove error").Error(),
})
}
var slotKeyToAddSig bls.SerializedSignature
SlotKeyToAddSigBytes, err := hexutil.Decode(metadata.Transaction.SlotKeyToAddSig)
copy(slotKeyToAddSig[:], SlotKeyToAddSigBytes)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "parse slotKeyToAddSig error").Error(),
})
}
stakePayloadMaker := func() (types2.Directive, interface{}) {
return types2.DirectiveEditValidator, types2.EditValidator{
ValidatorAddress: validatorAddr,
Description: types2.Description{
Name: editValidatorMsg.Name,
Identity: editValidatorMsg.Identity,
Website: editValidatorMsg.Website,
SecurityContact: editValidatorMsg.SecurityContact,
Details: editValidatorMsg.Details,
},
CommissionRate: &numeric.Dec{editValidatorMsg.CommissionRate},
MinSelfDelegation: new(big.Int).Mul(editValidatorMsg.MinSelfDelegation, big.NewInt(1e18)),
MaxTotalDelegation: new(big.Int).Mul(editValidatorMsg.MaxTotalDelegation, big.NewInt(1e18)),
SlotKeyToAdd: &slotKeyToAdd,
SlotKeyToRemove: &slotKeyToRemove,
SlotKeyToAddSig: &slotKeyToAddSig,
}
}
stakingTransaction, err := types2.NewStakingTransaction(metadata.Nonce, metadata.GasLimit, metadata.GasPrice, stakePayloadMaker)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "new staking transaction error").Error(),
})
}
return stakingTransaction, nil
}
func constructDelegateTransaction(
components *OperationComponents, metadata *ConstructMetadata,
) (hmyTypes.PoolTransaction, *types.Error) {
delegaterMsg := components.StakingMessage.(common.DelegateOperationMetadata)
delegatorAddr, err := common2.Bech32ToAddress(delegaterMsg.DelegatorAddress)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "convert delegator address error").Error(),
})
}
validatorAddr, err := common2.Bech32ToAddress(delegaterMsg.ValidatorAddress)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "convert validator address error").Error(),
})
}
stakePayloadMaker := func() (types2.Directive, interface{}) {
return types2.DirectiveDelegate, types2.Delegate{
DelegatorAddress: delegatorAddr,
ValidatorAddress: validatorAddr,
Amount: new(big.Int).Mul(delegaterMsg.Amount, big.NewInt(1e18)),
}
}
stakingTransaction, err := types2.NewStakingTransaction(metadata.Nonce, metadata.GasLimit, metadata.GasPrice, stakePayloadMaker)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "new staking transaction error").Error(),
})
}
return stakingTransaction, nil
}
func constructUndelegateTransaction(
components *OperationComponents, metadata *ConstructMetadata,
) (hmyTypes.PoolTransaction, *types.Error) {
undelegaterMsg := components.StakingMessage.(common.UndelegateOperationMetadata)
delegatorAddr, err := common2.Bech32ToAddress(undelegaterMsg.DelegatorAddress)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "convert delegator address error").Error(),
})
}
validatorAddr, err := common2.Bech32ToAddress(undelegaterMsg.ValidatorAddress)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "convert validator address error").Error(),
})
}
stakePayloadMaker := func() (types2.Directive, interface{}) {
return types2.DirectiveUndelegate, types2.Undelegate{
DelegatorAddress: delegatorAddr,
ValidatorAddress: validatorAddr,
Amount: new(big.Int).Mul(undelegaterMsg.Amount, big.NewInt(1e18)),
}
}
stakingTransaction, err := types2.NewStakingTransaction(metadata.Nonce, metadata.GasLimit, metadata.GasPrice, stakePayloadMaker)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "new staking transaction error").Error(),
})
}
return stakingTransaction, nil
}
func constructCollectRewardsTransaction(
components *OperationComponents, metadata *ConstructMetadata,
) (hmyTypes.PoolTransaction, *types.Error) {
collectRewardsMsg := components.StakingMessage.(common.CollectRewardsMetadata)
delegatorAddr, err := common2.Bech32ToAddress(collectRewardsMsg.DelegatorAddress)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "convert delegator address error").Error(),
})
}
stakePayloadMaker := func() (types2.Directive, interface{}) {
return types2.DirectiveCollectRewards, types2.CollectRewards{
DelegatorAddress: delegatorAddr,
}
}
stakingTransaction, err := types2.NewStakingTransaction(metadata.Nonce, metadata.GasLimit, metadata.GasPrice, stakePayloadMaker)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "new staking transaction error").Error(),
})
}
return stakingTransaction, nil
}
// constructPlainTransaction ..
func constructPlainTransaction(
components *OperationComponents, metadata *ConstructMetadata, sourceShardID uint32,

@ -30,7 +30,7 @@ type ContractInfo struct {
// FormatTransaction for staking, cross-shard sender, and plain transactions
func FormatTransaction(
tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt, contractInfo *ContractInfo,
tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt, contractInfo *ContractInfo, signed bool,
) (fmtTx *types.Transaction, rosettaError *types.Error) {
var operations []*types.Operation
var isCrossShard, isStaking, isContractCreation bool
@ -40,7 +40,7 @@ func FormatTransaction(
case *stakingTypes.StakingTransaction:
isStaking = true
stakingTx := tx.(*stakingTypes.StakingTransaction)
operations, rosettaError = GetNativeOperationsFromStakingTransaction(stakingTx, receipt)
operations, rosettaError = GetNativeOperationsFromStakingTransaction(stakingTx, receipt, signed)
if rosettaError != nil {
return nil, rosettaError
}
@ -166,3 +166,8 @@ func negativeBigValue(num *big.Int) string {
}
return value
}
func positiveStringValue(amount string) string {
bigInt, _ := new(big.Int).SetString(amount, 10)
return new(big.Int).Abs(bigInt).String()
}

@ -78,7 +78,7 @@ func testFormatStakingTransaction(
Status: hmytypes.ReceiptStatusSuccessful,
GasUsed: gasUsed,
}
rosettaTx, rosettaError := FormatTransaction(tx, receipt, &ContractInfo{})
rosettaTx, rosettaError := FormatTransaction(tx, receipt, &ContractInfo{}, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -133,7 +133,7 @@ func testFormatPlainTransaction(
Status: hmytypes.ReceiptStatusSuccessful,
GasUsed: gasUsed,
}
rosettaTx, rosettaError := FormatTransaction(tx, receipt, &ContractInfo{})
rosettaTx, rosettaError := FormatTransaction(tx, receipt, &ContractInfo{}, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -190,7 +190,7 @@ func testFormatCrossShardSenderTransaction(
Status: hmytypes.ReceiptStatusSuccessful,
GasUsed: gasUsed,
}
rosettaTx, rosettaError := FormatTransaction(tx, receipt, &ContractInfo{})
rosettaTx, rosettaError := FormatTransaction(tx, receipt, &ContractInfo{}, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}

@ -74,7 +74,7 @@ func GetNativeOperationsFromTransaction(
// GetNativeOperationsFromStakingTransaction for all staking directives
// Note that only native token operations can come from staking transactions.
func GetNativeOperationsFromStakingTransaction(
tx *stakingTypes.StakingTransaction, receipt *hmytypes.Receipt,
tx *stakingTypes.StakingTransaction, receipt *hmytypes.Receipt, signed bool,
) ([]*types.Operation, *types.Error) {
senderAddress, err := tx.SenderAddress()
if err != nil {
@ -85,12 +85,16 @@ func GetNativeOperationsFromStakingTransaction(
return nil, rosettaError
}
var operations []*types.Operation
if signed {
// 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)
operations = newNativeOperationsWithGas(gasExpended, accountID)
}
// Format staking message for metadata using decimal numbers (hence usage of rpcV2)
rpcStakingTx, err := rpcV2.NewStakingTransaction(tx, ethcommon.Hash{}, 0, 0, 0)
rpcStakingTx, err := rpcV2.NewStakingTransaction(tx, ethcommon.Hash{}, 0, 0, 0, signed)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
@ -125,9 +129,10 @@ func GetNativeOperationsFromStakingTransaction(
}
}
operations := append(gasOperations, &types.Operation{
if len(operations) > 0 {
operations = append(operations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: gasOperations[0].OperationIdentifier.Index + 1,
Index: operations[0].OperationIdentifier.Index + 1,
},
Type: tx.StakingType().String(),
Status: GetTransactionStatus(tx, receipt),
@ -135,6 +140,20 @@ func GetNativeOperationsFromStakingTransaction(
Amount: amount,
Metadata: metadata,
})
} else {
operations = []*types.Operation{{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: tx.StakingType().String(),
Status: GetTransactionStatus(tx, receipt),
Account: accountID,
Amount: amount,
Metadata: metadata,
}}
}
if signed {
// expose delegated balance
if tx.StakingType() == stakingTypes.DirectiveDelegate {
@ -146,6 +165,7 @@ func GetNativeOperationsFromStakingTransaction(
op2 := getUndelegateOperationForSubAccount(tx, operations[1], receipt)
return append(operations, op2), nil
}
}
return operations, nil
@ -635,11 +655,6 @@ func getAmountFromCollectRewards(
break
}
}
if amount == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": fmt.Sprintf("collect rewards amount not found for %v", senderAddress.String()),
})
}
return amount, nil
}

@ -4,6 +4,8 @@ import (
"fmt"
"math/big"
common2 "github.com/harmony-one/harmony/internal/common"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/harmony-one/harmony/rosetta/common"
@ -38,7 +40,6 @@ func (s *OperationComponents) IsStaking() bool {
// Providing a gas expenditure operation is INVALID.
// All staking & cross-shard operations require metadata matching the operation type to be a valid.
// All other operations do not require metadata.
// TODO (dm): implement staking transaction construction
func GetOperationComponents(
operations []*types.Operation,
) (*OperationComponents, *types.Error) {
@ -61,6 +62,16 @@ func GetOperationComponents(
return getCrossShardOperationComponents(operations[0])
case common.ContractCreationOperation:
return getContractCreationOperationComponents(operations[0])
case common.CreateValidatorOperation:
return getCreateValidatorOperationComponents(operations[0])
case common.EditValidatorOperation:
return getEditValidatorOperationComponents(operations[0])
case common.DelegateOperation:
return getDelegateOperationComponents(operations[0])
case common.UndelegateOperation:
return getUndelegateOperationComponents(operations[0])
case common.CollectRewardsOperation:
return getCollectRewardsOperationComponents(operations[0])
default:
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": fmt.Sprintf("%v is unsupported or invalid operation type", operations[0].Type),
@ -233,3 +244,207 @@ func getContractCreationOperationComponents(
}
return components, nil
}
func getCreateValidatorOperationComponents(
operation *types.Operation,
) (*OperationComponents, *types.Error) {
if operation == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "nil operation",
})
}
metadata := common.CreateValidatorOperationMetadata{}
if err := metadata.UnmarshalFromInterface(operation.Metadata); err != nil {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "invalid metadata").Error(),
})
}
if metadata.ValidatorAddress == "" || !common2.IsBech32Address(metadata.ValidatorAddress) {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": "validator address must not be empty or wrong format",
})
}
if metadata.CommissionRate == nil || metadata.MaxCommissionRate == nil || metadata.MaxChangeRate == nil {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": "commission rate & max commission rate & max change rate must not be nil",
})
}
if metadata.MinSelfDelegation == nil || metadata.MaxTotalDelegation == nil {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": "min self delegation & max total delegation much not be nil",
})
}
if metadata.Amount == nil {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": "amount must not be nil",
})
}
if metadata.Name == "" || metadata.Website == "" || metadata.Identity == "" ||
metadata.SecurityContact == "" || metadata.Details == "" {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": "name & website & identity & security contract & details must no be empty",
})
}
// slot public key would be add into
// https://github.com/harmony-one/harmony/blob/3a8125666817149eaf9cea7870735e26cfe49c87/rosetta/services/tx_construction.go#L16
// see https://github.com/harmony-one/harmony/issues/3431
components := &OperationComponents{
Type: operation.Type,
From: operation.Account,
StakingMessage: metadata,
}
if components.From == nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "operation must have account sender/from identifier for creating validator",
})
}
return components, nil
}
func getEditValidatorOperationComponents(
operation *types.Operation,
) (*OperationComponents, *types.Error) {
if operation == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "nil operation",
})
}
metadata := common.EditValidatorOperationMetadata{}
if err := metadata.UnmarshalFromInterface(operation.Metadata); err != nil {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "invalid metadata").Error(),
})
}
if metadata.ValidatorAddress == "" || !common2.IsBech32Address(metadata.ValidatorAddress) {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": "validator address must not be empty or wrong format",
})
}
if metadata.CommissionRate == nil || metadata.MinSelfDelegation == nil || metadata.MaxTotalDelegation == nil {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": "commission rate & max commission rate & max change rate must not be nil",
})
}
if metadata.Name == "" || metadata.Website == "" || metadata.Identity == "" ||
metadata.SecurityContact == "" || metadata.Details == "" {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": "name & website & identity & security contract & details must no be empty",
})
}
components := &OperationComponents{
Type: operation.Type,
From: operation.Account,
StakingMessage: metadata,
}
if components.From == nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "operation must have account sender/from identifier for editing validator",
})
}
return components, nil
}
func getDelegateOperationComponents(
operation *types.Operation,
) (*OperationComponents, *types.Error) {
if operation == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "nil operation",
})
}
metadata := common.DelegateOperationMetadata{}
if err := metadata.UnmarshalFromInterface(operation.Metadata); err != nil {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "invalid metadata").Error(),
})
}
// validator and delegator and amount already got checked inside UnmarshalFromInterface
components := &OperationComponents{
Type: operation.Type,
From: operation.Account,
StakingMessage: metadata,
}
if components.From == nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "operation must have account sender/from identifier for delegating",
})
}
return components, nil
}
func getUndelegateOperationComponents(
operation *types.Operation,
) (*OperationComponents, *types.Error) {
if operation == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "nil operation",
})
}
metadata := common.UndelegateOperationMetadata{}
if err := metadata.UnmarshalFromInterface(operation.Metadata); err != nil {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "invalid metadata").Error(),
})
}
// validator and delegator and amount already got checked inside UnmarshalFromInterface
components := &OperationComponents{
Type: operation.Type,
From: operation.Account,
StakingMessage: metadata,
}
if components.From == nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "operation must have account sender/from identifier for undelegating",
})
}
return components, nil
}
func getCollectRewardsOperationComponents(
operation *types.Operation,
) (*OperationComponents, *types.Error) {
if operation == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "nil operation",
})
}
metadata := common.CollectRewardsMetadata{}
if err := metadata.UnmarshalFromInterface(operation.Metadata); err != nil {
return nil, common.NewError(common.InvalidStakingConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "invalid metadata").Error(),
})
}
//delegator already got checked inside UnmarshalFromInterface
components := &OperationComponents{
Type: operation.Type,
From: operation.Account,
StakingMessage: metadata,
}
if components.From == nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "operation must have account sender/from identifier for collecting rewards",
})
}
return components, nil
}

@ -6,7 +6,7 @@ import (
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/harmony-one/harmony/crypto/bls"
internalCommon "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/rosetta/common"
)
@ -490,6 +490,591 @@ func TestGetTransferOperationComponents(t *testing.T) {
}
}
func TestCreateValidatorOperationComponents(t *testing.T) {
refFromKey := internalCommon.MustGeneratePrivateKey()
refFrom, rosettaError := newAccountIdentifier(crypto.PubkeyToAddress(refFromKey.PublicKey))
if rosettaError != nil {
t.Fatal(rosettaError)
}
validatorKey := internalCommon.MustGeneratePrivateKey()
validatorAddr := crypto.PubkeyToAddress(validatorKey.PublicKey)
validatorBech32Addr, _ := internalCommon.AddressToBech32(validatorAddr)
blsKey := bls.RandPrivateKey()
var serializedPubKey bls.SerializedPublicKey
copy(serializedPubKey[:], blsKey.GetPublicKey().Serialize())
// test valid operations
refOperations := &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.CreateValidatorOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"validatorAddress": validatorBech32Addr,
"commissionRate": new(big.Int).SetInt64(10),
"maxCommissionRate": new(big.Int).SetInt64(90),
"maxChangeRate": new(big.Int).SetInt64(2),
"minSelfDelegation": new(big.Int).SetInt64(10000),
"maxTotalDelegation": new(big.Int).SetInt64(10000000),
"amount": new(big.Int).SetInt64(100000),
"name": "Test validator",
"website": "https://test.website.com",
"identity": "test identity",
"securityContact": "security contact",
"details": "test detail",
},
}
testComponents, rosettaError := getCreateValidatorOperationComponents(refOperations)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if testComponents.Type != refOperations.Type {
t.Error("expected same operation")
}
if testComponents.From == nil || types.Hash(testComponents.From) != types.Hash(refFrom) {
t.Error("expected same sender")
}
if testComponents.Amount != nil {
t.Error("expected nil amount")
}
if testComponents.To != nil {
t.Error("expected nil to")
}
// test invalid operation
// test nil account
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.CreateValidatorOperation,
Account: nil,
Metadata: map[string]interface{}{
"validatorAddress": validatorAddr,
"commissionRate": new(big.Int).SetInt64(10),
"maxCommissionRate": new(big.Int).SetInt64(90),
"maxChangeRate": new(big.Int).SetInt64(2),
"minSelfDelegation": new(big.Int).SetInt64(10000),
"maxTotalDelegation": new(big.Int).SetInt64(10000000),
"amount": new(big.Int).SetInt64(100000),
"name": "Test validator",
"website": "https://test.website.com",
"identity": "test identity",
"securityContact": "security contact",
"details": "test detail",
},
}
_, rosettaError = getCreateValidatorOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
// test nil operation
_, rosettaError = getCreateValidatorOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid validator
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.CreateValidatorOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"commissionRate": new(big.Int).SetInt64(10),
"maxCommissionRate": new(big.Int).SetInt64(90),
"maxChangeRate": new(big.Int).SetInt64(2),
"minSelfDelegation": new(big.Int).SetInt64(10000),
"maxTotalDelegation": new(big.Int).SetInt64(10000000),
"amount": new(big.Int).SetInt64(100000),
"name": "Test validator",
"website": "https://test.website.com",
"identity": "test identity",
"securityContact": "security contact",
"details": "test detail",
},
}
_, rosettaError = getCreateValidatorOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid commission rate
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.CreateValidatorOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"validatorAddress": validatorAddr,
"maxCommissionRate": new(big.Int).SetInt64(90),
"maxChangeRate": new(big.Int).SetInt64(2),
"minSelfDelegation": new(big.Int).SetInt64(10000),
"maxTotalDelegation": new(big.Int).SetInt64(10000000),
"amount": new(big.Int).SetInt64(100000),
"name": "Test validator",
"website": "https://test.website.com",
"identity": "test identity",
"securityContact": "security contact",
"details": "test detail",
},
}
_, rosettaError = getCreateValidatorOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
}
func TestEditValidatorOperationComponents(t *testing.T) {
refFromKey := internalCommon.MustGeneratePrivateKey()
refFrom, rosettaError := newAccountIdentifier(crypto.PubkeyToAddress(refFromKey.PublicKey))
if rosettaError != nil {
t.Fatal(rosettaError)
}
validatorKey := internalCommon.MustGeneratePrivateKey()
validatorAddr := crypto.PubkeyToAddress(validatorKey.PublicKey)
validatorBech32Addr, _ := internalCommon.AddressToBech32(validatorAddr)
// test valid operations
refOperations := &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.EditValidatorOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"validatorAddress": validatorBech32Addr,
"commissionRate": new(big.Int).SetInt64(10),
"minSelfDelegation": new(big.Int).SetInt64(10000),
"maxTotalDelegation": new(big.Int).SetInt64(10000000),
"name": "Test validator",
"website": "https://test.website.com",
"identity": "test identity",
"securityContact": "security contact",
"details": "test detail",
},
}
testComponents, rosettaError := getEditValidatorOperationComponents(refOperations)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if testComponents.Type != refOperations.Type {
t.Error("expected same operation")
}
if testComponents.From == nil || types.Hash(testComponents.From) != types.Hash(refFrom) {
t.Error("expected same sender")
}
if testComponents.Amount != nil {
t.Error("expected nil amount")
}
if testComponents.To != nil {
t.Error("expected nil to")
}
// test invalid operation
// test nil account
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.EditValidatorOperation,
Metadata: map[string]interface{}{
"validatorAddress": validatorAddr,
"commissionRate": new(big.Int).SetInt64(10),
"minSelfDelegation": new(big.Int).SetInt64(10000),
"maxTotalDelegation": new(big.Int).SetInt64(10000000),
"name": "Test validator",
"website": "https://test.website.com",
"identity": "test identity",
"securityContact": "security contact",
"details": "test detail",
},
}
_, rosettaError = getEditValidatorOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
// test nil operation
_, rosettaError = getEditValidatorOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid validator
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.EditValidatorOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"commissionRate": new(big.Int).SetInt64(10),
"minSelfDelegation": new(big.Int).SetInt64(10000),
"maxTotalDelegation": new(big.Int).SetInt64(10000000),
"name": "Test validator",
"website": "https://test.website.com",
"identity": "test identity",
"securityContact": "security contact",
"details": "test detail",
},
}
_, rosettaError = getEditValidatorOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid commission rate
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.EditValidatorOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"validatorAddress": validatorAddr,
"minSelfDelegation": new(big.Int).SetInt64(10000),
"maxTotalDelegation": new(big.Int).SetInt64(10000000),
"name": "Test validator",
"website": "https://test.website.com",
"identity": "test identity",
"securityContact": "security contact",
"details": "test detail",
},
}
_, rosettaError = getEditValidatorOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
if rosettaError == nil {
t.Error("expected error")
}
}
func TestDelegateOperationComponents(t *testing.T) {
refFromKey := internalCommon.MustGeneratePrivateKey()
delegatorAddr := crypto.PubkeyToAddress(refFromKey.PublicKey)
delegatorBech32Addr, _ := internalCommon.AddressToBech32(delegatorAddr)
refFrom, rosettaError := newAccountIdentifier(crypto.PubkeyToAddress(refFromKey.PublicKey))
if rosettaError != nil {
t.Fatal(rosettaError)
}
validatorKey := internalCommon.MustGeneratePrivateKey()
validatorAddr, _ := internalCommon.AddressToBech32(crypto.PubkeyToAddress(validatorKey.PublicKey))
// test valid operations
refOperations := &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.DelegateOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"delegatorAddress": delegatorBech32Addr,
"validatorAddress": validatorAddr,
"amount": new(big.Int).SetInt64(100),
},
}
testComponents, rosettaError := getDelegateOperationComponents(refOperations)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if testComponents.Type != refOperations.Type {
t.Error("expected same operation")
}
if testComponents.From == nil || types.Hash(testComponents.From) != types.Hash(refFrom) {
t.Error("expected same sender")
}
if testComponents.Amount != nil {
t.Error("expected nil amount")
}
if testComponents.To != nil {
t.Error("expected nil to")
}
// test nil account
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.DelegateOperation,
Metadata: map[string]interface{}{
"delegatorAddress": delegatorAddr,
"validatorAddress": validatorAddr,
"amount": new(big.Int).SetInt64(100),
},
}
_, rosettaError = getDelegateOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
// test nil operation
_, rosettaError = getDelegateOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid delegator
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.DelegateOperation,
Metadata: map[string]interface{}{
"validatorAddress": validatorAddr,
"amount": new(big.Int).SetInt64(100),
},
}
_, rosettaError = getDelegateOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid validator
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.DelegateOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"delegatorAddress": delegatorAddr,
"amount": new(big.Int).SetInt64(100),
},
}
_, rosettaError = getDelegateOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid amount
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.DelegateOperation,
Metadata: map[string]interface{}{
"delegatorAddress": delegatorAddr,
"validatorAddress": validatorAddr,
},
}
_, rosettaError = getDelegateOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
}
func TestUndelegateOperationComponents(t *testing.T) {
refFromKey := internalCommon.MustGeneratePrivateKey()
delegatorAddr := crypto.PubkeyToAddress(refFromKey.PublicKey)
delegatorBech32Addr, _ := internalCommon.AddressToBech32(delegatorAddr)
refFrom, rosettaError := newAccountIdentifier(crypto.PubkeyToAddress(refFromKey.PublicKey))
if rosettaError != nil {
t.Fatal(rosettaError)
}
validatorKey := internalCommon.MustGeneratePrivateKey()
validatorAddr, _ := internalCommon.AddressToBech32(crypto.PubkeyToAddress(validatorKey.PublicKey))
// test valid operations
refOperations := &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.UndelegateOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"delegatorAddress": delegatorBech32Addr,
"validatorAddress": validatorAddr,
"amount": new(big.Int).SetInt64(100),
},
}
testComponents, rosettaError := getUndelegateOperationComponents(refOperations)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if testComponents.Type != refOperations.Type {
t.Error("expected same operation")
}
if testComponents.From == nil || types.Hash(testComponents.From) != types.Hash(refFrom) {
t.Error("expected same sender")
}
if testComponents.Amount != nil {
t.Error("expected nil amount")
}
if testComponents.To != nil {
t.Error("expected nil to")
}
// test nil account
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.UndelegateOperation,
Metadata: map[string]interface{}{
"delegatorAddress": delegatorAddr,
"validatorAddress": validatorAddr,
"amount": new(big.Int).SetInt64(100),
},
}
_, rosettaError = getUndelegateOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
// test nil operation
_, rosettaError = getUndelegateOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid delegator
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.UndelegateOperation,
Metadata: map[string]interface{}{
"validatorAddress": validatorAddr,
"amount": new(big.Int).SetInt64(100),
},
}
_, rosettaError = getUndelegateOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid validator
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.UndelegateOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"delegatorAddress": delegatorAddr,
"amount": new(big.Int).SetInt64(100),
},
}
_, rosettaError = getUndelegateOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid amount
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.UndelegateOperation,
Metadata: map[string]interface{}{
"delegatorAddress": delegatorAddr,
"validatorAddress": validatorAddr,
},
}
_, rosettaError = getUndelegateOperationComponents(nil)
if rosettaError == nil {
t.Error("expected error")
}
}
func TestCollectRewardsOperationComponents(t *testing.T) {
refFromKey := internalCommon.MustGeneratePrivateKey()
validatorAddr := crypto.PubkeyToAddress(refFromKey.PublicKey)
refFrom, rosettaError := newAccountIdentifier(validatorAddr)
delegatorBech32Addr, _ := internalCommon.AddressToBech32(validatorAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
// test valid operations
refOperations := &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.EditValidatorOperation,
Account: refFrom,
Metadata: map[string]interface{}{
"delegatorAddress": delegatorBech32Addr,
},
}
testComponents, rosettaError := getCollectRewardsOperationComponents(refOperations)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if testComponents.Type != refOperations.Type {
t.Error("expected same operation")
}
if testComponents.From == nil || types.Hash(testComponents.From) != types.Hash(refFrom) {
t.Error("expected same sender")
}
if testComponents.Amount != nil {
t.Error("expected nil amount")
}
if testComponents.To != nil {
t.Error("expected nil to")
}
// test invalid operation
// test nil operation
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.EditValidatorOperation,
Metadata: map[string]interface{}{
"delegatorAddress": validatorAddr,
},
}
_, rosettaError = getCollectRewardsOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
// test invalid delegator
refOperations = &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: common.EditValidatorOperation,
Account: refFrom,
Metadata: map[string]interface{}{},
}
_, rosettaError = getCollectRewardsOperationComponents(refOperations)
if rosettaError == nil {
t.Error("expected error")
}
}
func TestGetOperationComponents(t *testing.T) {
refFromAmount := &types.Amount{
Value: "-12000",

@ -1,6 +1,7 @@
package services
import (
"encoding/json"
"fmt"
"math/big"
"reflect"
@ -74,12 +75,14 @@ func TestGetStakingOperationsFromCreateValidator(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
operationsRaw, _ := json.Marshal(operations)
refOperationsRaw, _ := json.Marshal(refOperations)
t.Errorf("Expected operations to be:\n %v\n not\n %v", string(operationsRaw), string(refOperationsRaw))
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
@ -231,7 +234,7 @@ func TestGetStakingOperationsFromDelegate(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -365,7 +368,7 @@ func TestGetStakingOperationsFromUndelegate(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -426,7 +429,7 @@ func TestGetStakingOperationsFromCollectRewards(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -480,7 +483,7 @@ func TestGetStakingOperationsFromEditValidator(t *testing.T) {
},
Metadata: metadata,
})
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt)
operations, rosettaError := GetNativeOperationsFromStakingTransaction(tx, receipt, true)
if rosettaError != nil {
t.Fatal(rosettaError)
}

@ -262,7 +262,7 @@ func (s *PublicPoolService) PendingStakingTransactions(
continue // Legacy behavior is to not return error here
}
case V2:
tx, err = v2.NewStakingTransaction(stakingTx, common.Hash{}, 0, 0, 0)
tx, err = v2.NewStakingTransaction(stakingTx, common.Hash{}, 0, 0, 0, true)
if err != nil {
utils.Logger().Debug().
Err(err).

@ -241,7 +241,7 @@ func (s *PublicTransactionService) GetStakingTransactionByHash(
}
return NewStructuredResponse(tx)
case V2:
tx, err := v2.NewStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index)
tx, err := v2.NewStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index, true)
if err != nil {
return nil, err
}

@ -5,6 +5,8 @@ import (
"math/big"
"strings"
"github.com/pkg/errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
@ -126,6 +128,7 @@ type CreateValidatorMsg struct {
SecurityContact string `json:"securityContact"`
Details string `json:"details"`
SlotPubKeys []bls.SerializedPublicKey `json:"slotPubKeys"`
SlotKeySigs []bls.SerializedSignature `json:"slotKeySigs"`
}
// EditValidatorMsg represents a staking transaction's edit validator directive that
@ -142,6 +145,7 @@ type EditValidatorMsg struct {
Details string `json:"details"`
SlotPubKeyToAdd *bls.SerializedPublicKey `json:"slotPubKeyToAdd"`
SlotPubKeyToRemove *bls.SerializedPublicKey `json:"slotPubKeyToRemove"`
SlotKeyToAddSig *bls.SerializedSignature `json:"slotKeyToAddSig"`
}
// CollectRewardsMsg represents a staking transaction's collect rewards directive that
@ -419,12 +423,9 @@ func NewStakingTxReceipt(
// representation, with the given location metadata set (if available).
func NewStakingTransaction(
tx *staking.StakingTransaction, blockHash common.Hash,
blockNumber uint64, timestamp uint64, index uint64,
blockNumber uint64, timestamp uint64, index uint64, signed bool,
) (*StakingTransaction, error) {
from, err := tx.SenderAddress()
if err != nil {
return nil, nil
}
v, r, s := tx.RawSignatureValues()
var rpcMsg interface{}
@ -432,7 +433,7 @@ func NewStakingTransaction(
case staking.DirectiveCreateValidator:
rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCreateValidator)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("RLP decode error: %s", err.Error()))
}
msg, ok := rawMsg.(*staking.CreateValidator)
if !ok {
@ -440,7 +441,7 @@ func NewStakingTransaction(
}
validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("convert validator address error: %s", err.Error()))
}
rpcMsg = &CreateValidatorMsg{
ValidatorAddress: validatorAddress,
@ -456,11 +457,12 @@ func NewStakingTransaction(
SecurityContact: msg.Description.SecurityContact,
Details: msg.Description.Details,
SlotPubKeys: msg.SlotPubKeys,
SlotKeySigs: msg.SlotKeySigs,
}
case staking.DirectiveEditValidator:
rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveEditValidator)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("RLP decode error: %s", err.Error()))
}
msg, ok := rawMsg.(*staking.EditValidator)
if !ok {
@ -468,7 +470,7 @@ func NewStakingTransaction(
}
validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("convert validator address error: %s", err.Error()))
}
// Edit validators txs need not have commission rates to edit
commissionRate := &big.Int{}
@ -487,11 +489,12 @@ func NewStakingTransaction(
Details: msg.Description.Details,
SlotPubKeyToAdd: msg.SlotKeyToAdd,
SlotPubKeyToRemove: msg.SlotKeyToRemove,
SlotKeyToAddSig: msg.SlotKeyToAddSig,
}
case staking.DirectiveCollectRewards:
rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveCollectRewards)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("RLP decode error: %s", err.Error()))
}
msg, ok := rawMsg.(*staking.CollectRewards)
if !ok {
@ -499,13 +502,13 @@ func NewStakingTransaction(
}
delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("convert delegator address error: %s", err.Error()))
}
rpcMsg = &CollectRewardsMsg{DelegatorAddress: delegatorAddress}
case staking.DirectiveDelegate:
rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveDelegate)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("RLP decode error: %s", err.Error()))
}
msg, ok := rawMsg.(*staking.Delegate)
if !ok {
@ -513,11 +516,11 @@ func NewStakingTransaction(
}
delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("convert delegator address error: %s", err.Error()))
}
validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("convert validator address error: %s", err.Error()))
}
rpcMsg = &DelegateMsg{
DelegatorAddress: delegatorAddress,
@ -527,7 +530,7 @@ func NewStakingTransaction(
case staking.DirectiveUndelegate:
rawMsg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveUndelegate)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("RLP decode error: %s", err.Error()))
}
msg, ok := rawMsg.(*staking.Undelegate)
if !ok {
@ -535,7 +538,7 @@ func NewStakingTransaction(
}
delegatorAddress, err := internal_common.AddressToBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("convert delegator address error: %s", err.Error()))
}
validatorAddress, err := internal_common.AddressToBech32(msg.ValidatorAddress)
if err != nil {
@ -566,11 +569,18 @@ func NewStakingTransaction(
result.TransactionIndex = index
}
if signed {
from, err := tx.SenderAddress()
if err != nil {
return nil, errors.New(fmt.Sprintf("get sender address error: %s", err.Error()))
}
fromAddr, err := internal_common.AddressToBech32(from)
if err != nil {
return nil, err
}
result.From = fromAddr
}
return result, nil
}
@ -732,5 +742,5 @@ func NewStakingTransactionFromBlockIndex(b *types.Block, index uint64) (*Staking
"tx index %v greater than or equal to number of transactions on block %v", index, b.Hash().String(),
)
}
return NewStakingTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index)
return NewStakingTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time().Uint64(), index, true)
}

@ -143,6 +143,10 @@ func (tx *StakingTransaction) WithSignature(signer Signer, sig []byte) (*Staking
return cpy, nil
}
func (tx *StakingTransaction) SetRawSignature(v, r, s *big.Int) {
tx.data.R, tx.data.S, tx.data.V = r, s, v
}
// GasLimit returns gas of StakingTransaction.
func (tx *StakingTransaction) GasLimit() uint64 {
return tx.data.GasLimit

@ -34,7 +34,7 @@ func CreateTestStakingTransaction(
// GetMessageFromStakingTx gets the staking message, as seen by the rpc layer
func GetMessageFromStakingTx(tx *stakingTypes.StakingTransaction) (map[string]interface{}, error) {
rpcStakingTx, err := rpcV2.NewStakingTransaction(tx, ethcommon.Hash{}, 0, 0, 0)
rpcStakingTx, err := rpcV2.NewStakingTransaction(tx, ethcommon.Hash{}, 0, 0, 0, true)
if err != nil {
return nil, err
}

Loading…
Cancel
Save