Merge branch 'main' into goTracer

pull/3686/head
peekpi 4 years ago
commit 5c811774ed
  1. 22
      .github/ISSUE_TEMPLATE/stake-heist-submission.md
  2. 2
      .travis.yml
  3. 2
      Dockerfile
  4. 5
      README.md
  5. 55
      consensus/consensus_service.go
  6. 144
      consensus/consensus_v2.go
  7. 3
      consensus/engine/consensus_engine.go
  8. 4
      consensus/view_change.go
  9. 1
      core/blockchain.go
  10. 6
      core/evm.go
  11. 3
      core/rawdb/accessors_offchain.go
  12. 32
      core/vm/contracts.go
  13. 13
      core/vm/evm.go
  14. 1
      core/vm/interpreter.go
  15. 1
      core/vm/runtime/env.go
  16. 1
      core/vm/runtime/runtime.go
  17. 3
      crypto/vrf/bls/bls_vrf.go
  18. 22
      go.mod
  19. 3
      hmy/downloader/adapter_test.go
  20. 103
      internal/chain/engine.go
  21. 6
      internal/configs/sharding/fixedschedule.go
  22. 8
      internal/configs/sharding/localnet.go
  23. 9
      internal/configs/sharding/mainnet.go
  24. 6
      internal/configs/sharding/pangaea.go
  25. 6
      internal/configs/sharding/partner.go
  26. 4
      internal/configs/sharding/shardingconfig.go
  27. 6
      internal/configs/sharding/stress.go
  28. 6
      internal/configs/sharding/testnet.go
  29. 23
      internal/params/config.go
  30. 14
      node/node_handler.go
  31. 73
      node/node_handler_test.go
  32. 8
      node/node_newblock.go
  33. 11
      node/node_syncing.go
  34. 2
      p2p/stream/common/requestmanager/requestmanager.go
  35. 7
      rosetta/common/errors.go
  36. 105
      rosetta/common/operations.go
  37. 149
      rosetta/common/operations_test.go
  38. 2
      rosetta/infra/Dockerfile
  39. 2
      rosetta/services/block.go
  40. 25
      rosetta/services/construction_check.go
  41. 193
      rosetta/services/construction_create.go
  42. 493
      rosetta/services/construction_create_test.go
  43. 24
      rosetta/services/construction_parse.go
  44. 33
      rosetta/services/construction_parse_test.go
  45. 44
      rosetta/services/construction_submit.go
  46. 2
      rosetta/services/mempool.go
  47. 263
      rosetta/services/tx_construction.go
  48. 9
      rosetta/services/tx_format.go
  49. 6
      rosetta/services/tx_format_test.go
  50. 71
      rosetta/services/tx_operation.go
  51. 217
      rosetta/services/tx_operation_components.go
  52. 587
      rosetta/services/tx_operation_components_test.go
  53. 15
      rosetta/services/tx_operation_test.go
  54. 2
      rpc/pool.go
  55. 2
      rpc/transaction.go
  56. 52
      rpc/v2/types.go
  57. 2
      scripts/travis_go_checker.sh
  58. 4
      staking/types/transaction.go
  59. 73
      test/chain/vrf/main.go
  60. 2
      test/helpers/transaction.go

@ -1,22 +0,0 @@
---
name: stake heist submission
about: Submit work for Stake Heist Hackathon
title: "[submission]"
labels: stake-heist
assignees: gizemcakil
---
# Linked bounty (please link the related bounty posting for your work)
# Description of the bug, vulnerability, security threat OR the product/tool
# Steps to reproduce the reported vulnerability OR steps taken to build the product/tool
# Proof of exploitability OR proof of use-case (e.g. screenshot, video, github code source)
# Perceived impact to another user or the organization
# List of URLs and affected parameters (if applicable)
# Browser, OS and/or app version used during testing (if applicable)

@ -2,7 +2,7 @@ os: linux
dist: bionic
language: go
go:
- 1.14
- 1.16
go_import_path: github.com/harmony-one/harmony
cache:
directories:

@ -13,7 +13,7 @@ ENV BLS_DIR=${HMY_PATH}/bls
ENV CGO_CFLAGS="-I${BLS_DIR}/include -I${MCL_DIR}/include"
ENV CGO_LDFLAGS="-L${BLS_DIR}/lib"
ENV LD_LIBRARY_PATH=${BLS_DIR}/lib:${MCL_DIR}/lib
ENV GIMME_GO_VERSION="1.14.1"
ENV GIMME_GO_VERSION="1.16.3"
ENV PATH="/root/bin:${PATH}"
RUN apt-get update -y

@ -3,7 +3,7 @@
[![Build Status](https://travis-ci.com/harmony-one/harmony.svg?branch=main)](https://travis-ci.com/harmony-one/harmony)
![gopherbadger-tag-do-not-edit](https://img.shields.io/badge/Go%20Coverage-45%25-brightgreen.svg?longCache=true&style=flat)
![Discord](https://img.shields.io/discord/532383335348043777.svg)
[![Coverage Status](https://coveralls.io/repos/github/harmony-one/harmony/badge.svg?branch=main)](https://coveralls.io/github/harmony-one/harmony?branch=master)
[![Github Action](https://github.com/harmony-one/harmony/actions/workflows/ci.yaml/badge.svg?event=push)](https://github.com/harmony-one/harmony/actions/workflows/ci.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/harmony-one/harmony)](https://goreportcard.com/report/github.com/harmony-one/harmony)
## General Documentation
@ -16,7 +16,7 @@ http://api.hmny.io/
## Requirements
### **Go 1.14.7**
### **Go 1.16.3**
### **GMP and OpenSSL**
On macOS:
@ -233,3 +233,4 @@ See [`CONTRIBUTING`](CONTRIBUTING.md) for details.
- Integration with WASM
- Fast state synchronization
- Auditable privacy asset using ZK proof

@ -12,7 +12,6 @@ import (
protobuf "github.com/golang/protobuf/proto"
bls_core "github.com/harmony-one/bls/ffi/go/bls"
msg_pb "github.com/harmony-one/harmony/api/proto/message"
"github.com/harmony-one/harmony/block"
consensus_engine "github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/consensus/quorum"
"github.com/harmony-one/harmony/consensus/signature"
@ -242,49 +241,6 @@ func (consensus *Consensus) ReadSignatureBitmapPayload(
)
}
// retrieve corresponding blsPublicKey from Coinbase Address
func (consensus *Consensus) getLeaderPubKeyFromCoinbase(
header *block.Header,
) (*bls.PublicKeyWrapper, error) {
shardState, err := consensus.Blockchain.ReadShardState(header.Epoch())
if err != nil {
return nil, errors.Wrapf(err, "cannot read shard state %v %s",
header.Epoch(),
header.Coinbase().Hash().Hex(),
)
}
committee, err := shardState.FindCommitteeByID(header.ShardID())
if err != nil {
return nil, err
}
committerKey := new(bls_core.PublicKey)
isStaking := consensus.Blockchain.Config().IsStaking(header.Epoch())
for _, member := range committee.Slots {
if isStaking {
// After staking the coinbase address will be the address of bls public key
if utils.GetAddressFromBLSPubKeyBytes(member.BLSPublicKey[:]) == header.Coinbase() {
if committerKey, err = bls.BytesToBLSPublicKey(member.BLSPublicKey[:]); err != nil {
return nil, err
}
return &bls.PublicKeyWrapper{Object: committerKey, Bytes: member.BLSPublicKey}, nil
}
} else {
if member.EcdsaAddress == header.Coinbase() {
if committerKey, err = bls.BytesToBLSPublicKey(member.BLSPublicKey[:]); err != nil {
return nil, err
}
return &bls.PublicKeyWrapper{Object: committerKey, Bytes: member.BLSPublicKey}, nil
}
}
}
return nil, errors.Errorf(
"cannot find corresponding BLS Public Key coinbase %s",
header.Coinbase().Hex(),
)
}
// UpdateConsensusInformation will update shard information (epoch, publicKeys, blockNum, viewID)
// based on the local blockchain. It is called in two cases for now:
// 1. consensus object initialization. because of current dependency where chainreader is only available
@ -424,7 +380,7 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode {
// a solution to take care of this case because the coinbase of the latest block doesn't really represent the
// the real current leader in case of M1 view change.
if !curHeader.IsLastBlockInEpoch() && curHeader.Number().Uint64() != 0 {
leaderPubKey, err := consensus.getLeaderPubKeyFromCoinbase(curHeader)
leaderPubKey, err := chain.GetLeaderPubKeyFromCoinbase(consensus.Blockchain, curHeader)
if err != nil || leaderPubKey == nil {
consensus.getLogger().Error().Err(err).
Msg("[UpdateConsensusInformation] Unable to get leaderPubKey from coinbase")
@ -481,15 +437,6 @@ func (consensus *Consensus) IsLeader() bool {
return false
}
// NeedsRandomNumberGeneration returns true if the current epoch needs random number generation
func (consensus *Consensus) NeedsRandomNumberGeneration(epoch *big.Int) bool {
if consensus.ShardID == 0 && epoch.Uint64() >= shard.Schedule.RandomnessStartingEpoch() {
return true
}
return false
}
// SetViewIDs set both current view ID and view changing ID to the height
// of the blockchain. It is used during client startup to recover the state
func (consensus *Consensus) SetViewIDs(height uint64) {

@ -304,7 +304,6 @@ func (consensus *Consensus) Start(
consensus.consensusTimeout[timeoutBootstrap].Start()
consensus.getLogger().Info().Msg("[ConsensusMainLoop] Start bootstrap timeout (only once)")
vdfInProgress := false
// Set up next block due time.
consensus.NextBlockDue = time.Now().Add(consensus.BlockPeriod)
start := false
@ -402,79 +401,6 @@ func (consensus *Consensus) Start(
// Update time due for next block
consensus.NextBlockDue = time.Now().Add(consensus.BlockPeriod)
//VRF/VDF is only generated in the beacon chain
if consensus.NeedsRandomNumberGeneration(newBlock.Header().Epoch()) {
// generate VRF if the current block has a new leader
if !consensus.Blockchain.IsSameLeaderAsPreviousBlock(newBlock) {
vrfBlockNumbers, err := consensus.Blockchain.ReadEpochVrfBlockNums(newBlock.Header().Epoch())
if err != nil {
consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] no VRF block number from local db")
}
//check if VRF is already generated for the current block
vrfAlreadyGenerated := false
for _, v := range vrfBlockNumbers {
if v == newBlock.NumberU64() {
consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] VRF is already generated for this block")
vrfAlreadyGenerated = true
break
}
}
if !vrfAlreadyGenerated {
//generate a new VRF for the current block
vrfBlockNumbers := consensus.GenerateVrfAndProof(newBlock, vrfBlockNumbers)
//generate a new VDF for the current epoch if there are enough VRFs in the current epoch
//note that >= instead of == is used, because it is possible the current leader
//can commit this block, go offline without finishing VDF
if (!vdfInProgress) && len(vrfBlockNumbers) >= consensus.VdfSeedSize() {
//check local database to see if there's a VDF generated for this epoch
//generate a VDF if no blocknum is available
_, err := consensus.Blockchain.ReadEpochVdfBlockNum(newBlock.Header().Epoch())
if err != nil {
consensus.GenerateVdfAndProof(newBlock, vrfBlockNumbers)
vdfInProgress = true
}
}
}
}
vdfOutput, seed, err := consensus.GetNextRnd()
if err == nil {
vdfInProgress = false
// Verify the randomness
vdfObject := vdf_go.New(shard.Schedule.VdfDifficulty(), seed)
if !vdfObject.Verify(vdfOutput) {
consensus.getLogger().Warn().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] failed to verify the VDF output")
} else {
//write the VDF only if VDF has not been generated
_, err := consensus.Blockchain.ReadEpochVdfBlockNum(newBlock.Header().Epoch())
if err == nil {
consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] VDF has already been generated previously")
} else {
consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Msg("[ConsensusMainLoop] Generated a new VDF")
newBlock.AddVdf(vdfOutput[:])
}
}
}
}
startTime = time.Now()
consensus.msgSender.Reset(newBlock.NumberU64())
@ -774,78 +700,34 @@ func (consensus *Consensus) postCatchup(initBN uint64) {
}
// GenerateVrfAndProof generates new VRF/Proof from hash of previous block
func (consensus *Consensus) GenerateVrfAndProof(newBlock *types.Block, vrfBlockNumbers []uint64) []uint64 {
func (consensus *Consensus) GenerateVrfAndProof(newHeader *block.Header) error {
key, err := consensus.GetConsensusLeaderPrivateKey()
if err != nil {
consensus.getLogger().Error().
Err(err).
Msg("[GenerateVrfAndProof] VRF generation error")
return vrfBlockNumbers
return errors.New("[GenerateVrfAndProof] no leader private key provided")
}
sk := vrf_bls.NewVRFSigner(key.Pri)
blockHash := [32]byte{}
previousHeader := consensus.Blockchain.GetHeaderByNumber(
newBlock.NumberU64() - 1,
)
if previousHeader == nil {
return vrfBlockNumbers
}
previousHash := previousHeader.Hash()
copy(blockHash[:], previousHash[:])
vrf, proof := sk.Evaluate(blockHash[:])
newBlock.AddVrf(append(vrf[:], proof...))
consensus.getLogger().Info().
Uint64("MsgBlockNum", newBlock.NumberU64()).
Uint64("Epoch", newBlock.Header().Epoch().Uint64()).
Int("Num of VRF", len(vrfBlockNumbers)).
Msg("[ConsensusMainLoop] Leader generated a VRF")
return vrfBlockNumbers
}
// ValidateVrfAndProof validates a VRF/Proof from hash of previous block
func (consensus *Consensus) ValidateVrfAndProof(headerObj *block.Header) bool {
vrfPk := vrf_bls.NewVRFVerifier(consensus.LeaderPubKey.Object)
var blockHash [32]byte
previousHeader := consensus.Blockchain.GetHeaderByNumber(
headerObj.Number().Uint64() - 1,
newHeader.Number().Uint64() - 1,
)
if previousHeader == nil {
return false
return errors.New("[GenerateVrfAndProof] no parent header found")
}
previousHash := previousHeader.Hash()
copy(blockHash[:], previousHash[:])
vrfProof := [96]byte{}
copy(vrfProof[:], headerObj.Vrf()[32:])
hash, err := vrfPk.ProofToHash(blockHash[:], vrfProof[:])
if err != nil {
consensus.getLogger().Warn().
Err(err).
Str("MsgBlockNum", headerObj.Number().String()).
Msg("[OnAnnounce] VRF verification error")
return false
vrf, proof := sk.Evaluate(previousHash[:])
if proof == nil {
return errors.New("[GenerateVrfAndProof] failed to generate vrf")
}
if !bytes.Equal(hash[:], headerObj.Vrf()[:32]) {
consensus.getLogger().Warn().
Str("MsgBlockNum", headerObj.Number().String()).
Msg("[OnAnnounce] VRF proof is not valid")
return false
}
newHeader.SetVrf(append(vrf[:], proof...))
vrfBlockNumbers, _ := consensus.Blockchain.ReadEpochVrfBlockNums(
headerObj.Epoch(),
)
consensus.getLogger().Info().
Str("MsgBlockNum", headerObj.Number().String()).
Int("Number of VRF", len(vrfBlockNumbers)).
Msg("[OnAnnounce] validated a new VRF")
Uint64("BlockNum", newHeader.Number().Uint64()).
Uint64("Epoch", newHeader.Epoch().Uint64()).
Hex("VRF+Proof", newHeader.Vrf()).
Msg("[GenerateVrfAndProof] Leader generated a VRF")
return true
return nil
}
// GenerateVdfAndProof generates new VDF/Proof from VRFs in the current epoch

@ -105,6 +105,9 @@ type Engine interface {
// VerifyShardState verifies the shard state during epoch transition is valid
VerifyShardState(chain ChainReader, beacon ChainReader, header *block.Header) error
// VerifyVRF verifies the vrf of the block
VerifyVRF(chain ChainReader, header *block.Header) error
// Beaconchain provides the handle for Beaconchain
Beaconchain() ChainReader

@ -5,6 +5,8 @@ import (
"sync"
"time"
"github.com/harmony-one/harmony/internal/chain"
"github.com/harmony-one/harmony/crypto/bls"
"github.com/ethereum/go-ethereum/common"
@ -174,7 +176,7 @@ func (consensus *Consensus) getNextLeaderKey(viewID uint64) *bls.PublicKeyWrappe
stuckBlockViewID := curHeader.ViewID().Uint64() + 1
gap = int(viewID - stuckBlockViewID)
// this is the truth of the leader based on blockchain blocks
lastLeaderPubKey, err = consensus.getLeaderPubKeyFromCoinbase(curHeader)
lastLeaderPubKey, err = chain.GetLeaderPubKeyFromCoinbase(consensus.Blockchain, curHeader)
if err != nil || lastLeaderPubKey == nil {
consensus.getLogger().Error().Err(err).
Msg("[getNextLeaderKey] Unable to get leaderPubKey from coinbase. Set it to consensus.LeaderPubKey")

@ -2890,6 +2890,7 @@ func (bc *BlockChain) DelegatorsInformation(addr common.Address) []*staking.Dele
}
// GetECDSAFromCoinbase retrieve corresponding ecdsa address from Coinbase Address
// TODO: optimize this func by adding cache etc.
func (bc *BlockChain) GetECDSAFromCoinbase(header *block.Header) (common.Address, error) {
// backward compatibility: before isStaking epoch, coinbase address is the ecdsa address
coinbase := header.Coinbase()

@ -55,6 +55,11 @@ func NewEVMContext(msg Message, header *block.Header, chain ChainContext, author
} else {
beneficiary = *author
}
vrf := common.Hash{}
if len(header.Vrf()) >= 32 {
vrfAndProof := header.Vrf()
copy(vrf[:], vrfAndProof[:32])
}
return vm.Context{
CanTransfer: CanTransfer,
Transfer: Transfer,
@ -64,6 +69,7 @@ func NewEVMContext(msg Message, header *block.Header, chain ChainContext, author
Coinbase: beneficiary,
BlockNumber: header.Number(),
EpochNumber: header.Epoch(),
VRF: vrf,
Time: header.Time(),
GasLimit: header.GasLimit(),
GasPrice: new(big.Int).Set(msg.GasPrice()),

@ -1,6 +1,7 @@
package rawdb
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
@ -301,7 +302,7 @@ func ReadBlockCommitSig(db DatabaseReader, blockNum uint64) ([]byte, error) {
// this is only needed for the compatibility in the migration moment.
data, err = db.Get(lastCommitsKey)
if err != nil {
return nil, errors.New("cannot read commit sig for block " + string(blockNum))
return nil, errors.New(fmt.Sprintf("cannot read commit sig for block: %d ", blockNum))
}
}
return data, nil

@ -76,6 +76,21 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{9}): &blake2F{},
}
// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
// contracts used in the Istanbul release.
var PrecompiledContractsVRF = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{255}): &vrf{},
}
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.RequiredGas(input)
@ -501,3 +516,20 @@ func (c *blake2F) Run(input []byte) ([]byte, error) {
}
return output, nil
}
// VRF implemented as a native contract.
type vrf struct{}
// RequiredGas returns the gas required to execute the pre-compiled contract.
//
// This method does not require any overflow checking as the input size gas costs
// required for anything significant is so high it's impossible to pay for.
func (c *vrf) RequiredGas(input []byte) uint64 {
return GasQuickStep
}
func (c *vrf) Run(input []byte) ([]byte, error) {
// Note the input was overwritten with the vrf of the block.
// So here we simply return it
return append([]byte{}, input...), nil
}

@ -54,7 +54,14 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul
}
if evm.chainRules.IsVRF {
precompiles = PrecompiledContractsVRF
}
if p := precompiles[*contract.CodeAddr]; p != nil {
if _, ok := p.(*vrf); ok {
// Override the input with vrf data of the current block so it can be returned to the contract program.
input = evm.Context.VRF.Bytes()
}
return RunPrecompiledContract(p, input, contract)
}
}
@ -69,6 +76,7 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
evm.interpreter = interpreter
}
return interpreter.Run(contract, input, readOnly)
}
}
return nil, ErrNoCompatibleInterpreter
@ -99,6 +107,7 @@ type Context struct {
BlockNumber *big.Int // Provides information for NUMBER
EpochNumber *big.Int // Provides information for EPOCH
Time *big.Int // Provides information for TIME
VRF common.Hash // Provides information for VRF
TxType types.TransactionType
}
@ -225,6 +234,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul
}
if evm.chainRules.IsVRF {
precompiles = PrecompiledContractsVRF
}
if precompiles[addr] == nil && evm.ChainConfig().IsS3(evm.EpochNumber) && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 {

@ -271,6 +271,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// execute the operation
res, err = operation.execute(&pc, in, contract, mem, stack)
// verifyPool is a build flag. Pool verification makes sure the integrity
// of the integer pool by comparing values to a default value.
if verifyPool {

@ -34,6 +34,7 @@ func NewEnv(cfg *Config) *vm.EVM {
Coinbase: cfg.Coinbase,
BlockNumber: cfg.BlockNumber,
EpochNumber: cfg.EpochNumber,
VRF: cfg.VRF,
Time: cfg.Time,
GasLimit: cfg.GasLimit,
GasPrice: cfg.GasPrice,

@ -38,6 +38,7 @@ type Config struct {
Coinbase common.Address
BlockNumber *big.Int
EpochNumber *big.Int
VRF common.Hash
Time *big.Int
GasLimit uint64
GasPrice *big.Int

@ -65,6 +65,9 @@ func (k *PrivateKey) Evaluate(alpha []byte) ([32]byte, []byte) {
//pi = VRF_prove(SK, alpha)
msgHash := sha256.Sum256(alpha)
pi := k.SignHash(msgHash[:])
if pi == nil {
return [32]byte{}, nil
}
//hash the signature and output as VRF beta
//beta = VRF_proof2hash(pi)

@ -1,6 +1,6 @@
module github.com/harmony-one/harmony
go 1.14
go 1.16
require (
github.com/VictoriaMetrics/fastcache v1.5.7 // indirect
@ -17,7 +17,7 @@ require (
github.com/ethereum/go-ethereum v1.9.23
github.com/fjl/memsize v0.0.0-20180929194037-2a09253e352a // indirect
github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c // indirect
github.com/golang/mock v1.4.0
github.com/golang/mock v1.4.4
github.com/golang/protobuf v1.4.3
github.com/golangci/golangci-lint v1.22.2
github.com/gorilla/mux v1.8.0
@ -29,14 +29,14 @@ require (
github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/golang-lru v0.5.4
github.com/ipfs/go-ds-badger v0.2.4
github.com/libp2p/go-libp2p v0.13.0
github.com/libp2p/go-libp2p-core v0.8.0
github.com/libp2p/go-libp2p v0.14.0
github.com/libp2p/go-libp2p-core v0.8.5
github.com/libp2p/go-libp2p-crypto v0.1.0
github.com/libp2p/go-libp2p-discovery v0.5.0
github.com/libp2p/go-libp2p-kad-dht v0.11.1
github.com/libp2p/go-libp2p-pubsub v0.4.0
github.com/multiformats/go-multiaddr v0.3.1
github.com/multiformats/go-multiaddr-dns v0.2.0
github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/pborman/uuid v1.2.0
github.com/pelletier/go-toml v1.2.0
@ -49,16 +49,16 @@ require (
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.6.1
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca
go.uber.org/ratelimit v0.1.0
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/lint v0.0.0-20200302205851-738671d3881b
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/tools v0.0.0-20200904185747-39188db58858
google.golang.org/grpc v1.31.1
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/tools v0.0.0-20210106214847-113979e3529a
google.golang.org/grpc v1.33.2
google.golang.org/protobuf v1.25.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6
gopkg.in/yaml.v2 v2.3.0

@ -134,6 +134,9 @@ func (e *dummyEngine) VerifyHeaderSignature(engine.ChainReader, *block.Header, b
func (e *dummyEngine) VerifyCrossLink(engine.ChainReader, types.CrossLink) error {
return nil
}
func (e *dummyEngine) VerifyVRF(chain engine.ChainReader, header *block.Header) error {
return nil
}
func (e *dummyEngine) VerifyHeaders(engine.ChainReader, []*block.Header, []bool) (chan<- struct{}, <-chan error) {
return nil, nil
}

@ -5,6 +5,9 @@ import (
"math/big"
"sort"
bls2 "github.com/harmony-one/bls/ffi/go/bls"
blsvrf "github.com/harmony-one/harmony/crypto/vrf/bls"
"github.com/ethereum/go-ethereum/common"
lru "github.com/hashicorp/golang-lru"
"github.com/pkg/errors"
@ -29,6 +32,8 @@ import (
const (
verifiedSigCache = 100
epochCtxCache = 20
vrfBeta = 32 // 32 bytes randomness
vrfProof = 96 // 96 bytes proof (bls sig)
)
type engineImpl struct {
@ -135,6 +140,104 @@ func (e *engineImpl) VerifyShardState(
return nil
}
// VerifyVRF implements Engine, checking the vrf is valid
func (e *engineImpl) VerifyVRF(
bc engine.ChainReader, header *block.Header,
) error {
if bc.ShardID() != header.ShardID() {
return errors.Errorf(
"[VerifyVRF] shardID not match %d %d", bc.ShardID(), header.ShardID(),
)
}
if bc.Config().IsVRF(header.Epoch()) && len(header.Vrf()) != vrfBeta+vrfProof {
return errors.Errorf(
"[VerifyVRF] invalid vrf data format or no vrf proposed %x", header.Vrf(),
)
}
if !bc.Config().IsVRF(header.Epoch()) {
if len(header.Vrf()) != 0 {
return errors.Errorf(
"[VerifyVRF] vrf data present in pre-vrf epoch %x", header.Vrf(),
)
}
return nil
}
leaderPubKey, err := GetLeaderPubKeyFromCoinbase(bc, header)
if leaderPubKey == nil || err != nil {
return err
}
vrfPk := blsvrf.NewVRFVerifier(leaderPubKey.Object)
previousHeader := bc.GetHeaderByNumber(
header.Number().Uint64() - 1,
)
if previousHeader == nil {
return errors.New("[VerifyVRF] no parent header found")
}
previousHash := previousHeader.Hash()
vrfProof := [vrfProof]byte{}
copy(vrfProof[:], header.Vrf()[vrfBeta:])
hash, err := vrfPk.ProofToHash(previousHash[:], vrfProof[:])
if err != nil {
return errors.New("[VerifyVRF] Failed VRF verification")
}
if !bytes.Equal(hash[:], header.Vrf()[:vrfBeta]) {
return errors.New("[VerifyVRF] VRF proof is not valid")
}
return nil
}
// retrieve corresponding blsPublicKey from Coinbase Address
func GetLeaderPubKeyFromCoinbase(
blockchain engine.ChainReader, h *block.Header,
) (*bls.PublicKeyWrapper, error) {
shardState, err := blockchain.ReadShardState(h.Epoch())
if err != nil {
return nil, errors.Wrapf(err, "cannot read shard state %v %s",
h.Epoch(),
h.Coinbase().Hash().Hex(),
)
}
committee, err := shardState.FindCommitteeByID(h.ShardID())
if err != nil {
return nil, err
}
committerKey := new(bls2.PublicKey)
isStaking := blockchain.Config().IsStaking(h.Epoch())
for _, member := range committee.Slots {
if isStaking {
// After staking the coinbase address will be the address of bls public key
if utils.GetAddressFromBLSPubKeyBytes(member.BLSPublicKey[:]) == h.Coinbase() {
if committerKey, err = bls.BytesToBLSPublicKey(member.BLSPublicKey[:]); err != nil {
return nil, err
}
return &bls.PublicKeyWrapper{Object: committerKey, Bytes: member.BLSPublicKey}, nil
}
} else {
if member.EcdsaAddress == h.Coinbase() {
if committerKey, err = bls.BytesToBLSPublicKey(member.BLSPublicKey[:]); err != nil {
return nil, err
}
return &bls.PublicKeyWrapper{Object: committerKey, Bytes: member.BLSPublicKey}, nil
}
}
}
return nil, errors.Errorf(
"cannot find corresponding BLS Public Key coinbase %s",
h.Coinbase().Hex(),
)
}
// VerifySeal implements Engine, checking whether the given block's parent block satisfies
// the PoS difficulty requirements, i.e. >= 2f+1 valid signatures from the committee
// Note that each block header contains the bls signature of the parent block

@ -41,12 +41,6 @@ func (s fixedSchedule) VdfDifficulty() int {
return mainnetVdfDifficulty
}
// TODO: remove it after randomness feature turned on mainnet
//RandonnessStartingEpoch returns starting epoch of randonness generation
func (s fixedSchedule) RandomnessStartingEpoch() uint64 {
return mainnetRandomnessStartingEpoch
}
func (s fixedSchedule) GetNetworkID() NetworkID {
return DevNet
}

@ -24,8 +24,6 @@ const (
localnetBlocksPerEpochV2 = 10
localnetVdfDifficulty = 5000 // This takes about 10s to finish the vdf
localnetRandomnessStartingEpoch = 0
)
func (ls localnetSchedule) InstanceForEpoch(epoch *big.Int) Instance {
@ -116,12 +114,6 @@ func (ls localnetSchedule) VdfDifficulty() int {
return localnetVdfDifficulty
}
// TODO: remove it after randomness feature turned on mainnet
//RandonnessStartingEpoch returns starting epoch of randonness generation
func (ls localnetSchedule) RandomnessStartingEpoch() uint64 {
return localnetRandomnessStartingEpoch
}
func (ls localnetSchedule) GetNetworkID() NetworkID {
return LocalNet
}

@ -17,9 +17,6 @@ const (
mainnetVdfDifficulty = 50000 // This takes about 100s to finish the vdf
// TODO: remove it after randomness feature turned on mainnet
mainnetRandomnessStartingEpoch = 100000
mainnetV0_1Epoch = 1
mainnetV0_2Epoch = 5
mainnetV0_3Epoch = 8
@ -176,12 +173,6 @@ func (ms mainnetSchedule) VdfDifficulty() int {
return mainnetVdfDifficulty
}
// TODO: remove it after randomness feature turned on mainnet
//RandonnessStartingEpoch returns starting epoch of randonness generation
func (ms mainnetSchedule) RandomnessStartingEpoch() uint64 {
return mainnetRandomnessStartingEpoch
}
func (ms mainnetSchedule) GetNetworkID() NetworkID {
return MainNet
}

@ -56,12 +56,6 @@ func (ps pangaeaSchedule) VdfDifficulty() int {
return pangaeaVdfDifficulty
}
// TODO: remove it after randomness feature turned on mainnet
//RandonnessStartingEpoch returns starting epoch of randonness generation
func (ps pangaeaSchedule) RandomnessStartingEpoch() uint64 {
return mainnetRandomnessStartingEpoch
}
func (ps pangaeaSchedule) GetNetworkID() NetworkID {
return Pangaea
}

@ -57,12 +57,6 @@ func (ps partnerSchedule) VdfDifficulty() int {
return partnerVdfDifficulty
}
// TODO: remove it after randomness feature turned on mainnet
//RandonnessStartingEpoch returns starting epoch of randonness generation
func (ps partnerSchedule) RandomnessStartingEpoch() uint64 {
return mainnetRandomnessStartingEpoch
}
func (ps partnerSchedule) GetNetworkID() NetworkID {
return Partner
}

@ -30,10 +30,6 @@ type Schedule interface {
// VDFDifficulty returns number of iterations for VDF calculation
VdfDifficulty() int
// TODO: remove it after randomness feature turned on mainnet
//RandomnessStartingEpoch returns starting epoch of randonness generation
RandomnessStartingEpoch() uint64
// GetNetworkID() return networkID type.
GetNetworkID() NetworkID

@ -59,12 +59,6 @@ func (ss stressnetSchedule) VdfDifficulty() int {
return stressnetVdfDifficulty
}
// TODO: remove it after randomness feature turned on mainnet
//RandonnessStartingEpoch returns starting epoch of randonness generation
func (ss stressnetSchedule) RandomnessStartingEpoch() uint64 {
return mainnetRandomnessStartingEpoch
}
func (ss stressnetSchedule) GetNetworkID() NetworkID {
return StressNet
}

@ -94,12 +94,6 @@ func (ts testnetSchedule) VdfDifficulty() int {
return testnetVdfDifficulty
}
// TODO: remove it after randomness feature turned on mainnet
//RandonnessStartingEpoch returns starting epoch of randonness generation
func (ts testnetSchedule) RandomnessStartingEpoch() uint64 {
return mainnetRandomnessStartingEpoch
}
func (ts testnetSchedule) GetNetworkID() NetworkID {
return TestNet
}

@ -50,6 +50,7 @@ var (
SixtyPercentEpoch: big.NewInt(530), // Around Monday Apr 12th 2021, 22:30 UTC
RedelegationEpoch: big.NewInt(290),
NoEarlyUnlockEpoch: big.NewInt(530), // Around Monday Apr 12th 2021, 22:30 UTC
VRFEpoch: EpochTBD,
EIP155Epoch: big.NewInt(28),
S3Epoch: big.NewInt(28),
IstanbulEpoch: big.NewInt(314),
@ -72,6 +73,7 @@ var (
SixtyPercentEpoch: big.NewInt(73282),
RedelegationEpoch: big.NewInt(36500),
NoEarlyUnlockEpoch: big.NewInt(73580),
VRFEpoch: EpochTBD,
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
IstanbulEpoch: big.NewInt(43800),
@ -95,6 +97,7 @@ var (
SixtyPercentEpoch: big.NewInt(0),
RedelegationEpoch: big.NewInt(0),
NoEarlyUnlockEpoch: big.NewInt(0),
VRFEpoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
IstanbulEpoch: big.NewInt(0),
@ -118,6 +121,7 @@ var (
SixtyPercentEpoch: big.NewInt(0),
RedelegationEpoch: big.NewInt(0),
NoEarlyUnlockEpoch: big.NewInt(0),
VRFEpoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
IstanbulEpoch: big.NewInt(0),
@ -141,6 +145,7 @@ var (
SixtyPercentEpoch: big.NewInt(10),
RedelegationEpoch: big.NewInt(0),
NoEarlyUnlockEpoch: big.NewInt(0),
VRFEpoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
IstanbulEpoch: big.NewInt(0),
@ -163,6 +168,7 @@ var (
SixtyPercentEpoch: EpochTBD, // Never enable it for localnet as localnet has no external validator setup
RedelegationEpoch: big.NewInt(0),
NoEarlyUnlockEpoch: big.NewInt(0),
VRFEpoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
IstanbulEpoch: big.NewInt(0),
@ -187,6 +193,7 @@ var (
big.NewInt(0), // SixtyPercentEpoch
big.NewInt(0), // RedelegationEpoch
big.NewInt(0), // NoEarlyUnlockEpoch
big.NewInt(0), // VRFEpoch
big.NewInt(0), // EIP155Epoch
big.NewInt(0), // S3Epoch
big.NewInt(0), // IstanbulEpoch
@ -211,6 +218,7 @@ var (
big.NewInt(0), // SixtyPercentEpoch
big.NewInt(0), // RedelegationEpoch
big.NewInt(0), // NoEarlyUnlockEpoch
big.NewInt(0), // VRFEpoch
big.NewInt(0), // EIP155Epoch
big.NewInt(0), // S3Epoch
big.NewInt(0), // IstanbulEpoch
@ -288,6 +296,9 @@ type ChainConfig struct {
// more than 7 epochs is disabled
NoEarlyUnlockEpoch *big.Int `json:"no-early-unlock-epoch,omitempty"`
// VRFEpoch is the epoch when VRF randomness is enabled
VRFEpoch *big.Int `json:"vrf-epoch,omitempty"`
// EIP155 hard fork epoch (include EIP158 too)
EIP155Epoch *big.Int `json:"eip155-epoch,omitempty"`
@ -374,6 +385,11 @@ func (c *ChainConfig) IsNoEarlyUnlock(epoch *big.Int) bool {
return isForked(c.NoEarlyUnlockEpoch, epoch)
}
// IsVRF determines whether it is the epoch to enable vrf
func (c *ChainConfig) IsVRF(epoch *big.Int) bool {
return isForked(c.VRFEpoch, epoch)
}
// IsPreStaking determines whether staking transactions are allowed
func (c *ChainConfig) IsPreStaking(epoch *big.Int) bool {
return isForked(c.PreStakingEpoch, epoch)
@ -452,9 +468,9 @@ func isForked(s, epoch *big.Int) bool {
// Rules is a one time interface meaning that it shouldn't be used in between transition
// phases.
type Rules struct {
ChainID *big.Int
EthChainID *big.Int
IsCrossLink, IsEIP155, IsS3, IsReceiptLog, IsIstanbul bool
ChainID *big.Int
EthChainID *big.Int
IsCrossLink, IsEIP155, IsS3, IsReceiptLog, IsIstanbul, IsVRF bool
}
// Rules ensures c's ChainID is not nil.
@ -475,5 +491,6 @@ func (c *ChainConfig) Rules(epoch *big.Int) Rules {
IsS3: c.IsS3(epoch),
IsReceiptLog: c.IsReceiptLog(epoch),
IsIstanbul: c.IsIstanbul(epoch),
IsVRF: c.IsVRF(epoch),
}
}

@ -277,6 +277,18 @@ func (node *Node) VerifyNewBlock(newBlock *types.Block) error {
return err
}
if err := node.Blockchain().Engine().VerifyVRF(
node.Blockchain(), newBlock.Header(),
); err != nil {
utils.Logger().Error().
Str("blockHash", newBlock.Hash().Hex()).
Err(err).
Msg("[VerifyNewBlock] Cannot verify vrf for the new block")
return errors.Wrap(err,
"[VerifyNewBlock] Cannot verify vrf for the new block",
)
}
if err := node.Blockchain().Engine().VerifyShardState(
node.Blockchain(), node.Beaconchain(), newBlock.Header(),
); err != nil {
@ -284,7 +296,7 @@ func (node *Node) VerifyNewBlock(newBlock *types.Block) error {
Str("blockHash", newBlock.Hash().Hex()).
Err(err).
Msg("[VerifyNewBlock] Cannot verify shard state for the new block")
return errors.New(
return errors.Wrap(err,
"[VerifyNewBlock] Cannot verify shard state for the new block",
)
}

@ -1,6 +1,7 @@
package node
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
@ -102,7 +103,79 @@ func TestVerifyNewBlock(t *testing.T) {
commitSigs, func() uint64 { return 0 }, common.Address{}, nil, nil,
)
// work around vrf verification as it's tested in another test.
node.Blockchain().Config().VRFEpoch = big.NewInt(2)
if err := node.VerifyNewBlock(block); err != nil {
t.Error("New block is not verified successfully:", err)
}
}
func TestVerifyVRF(t *testing.T) {
blsKey := bls.RandPrivateKey()
pubKey := blsKey.GetPublicKey()
leader := p2p.Peer{IP: "127.0.0.1", Port: "8882", ConsensusPubKey: pubKey}
priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902")
host, err := p2p.NewHost(p2p.HostConfig{
Self: &leader,
BLSKey: priKey,
})
if err != nil {
t.Fatalf("newhost failure: %v", err)
}
decider := quorum.NewDecider(
quorum.SuperMajorityVote, shard.BeaconChainShardID,
)
consensus, err := consensus.New(
host, shard.BeaconChainShardID, leader, multibls.GetPrivateKeys(blsKey), decider,
)
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
archiveMode := make(map[uint32]bool)
archiveMode[0] = true
archiveMode[1] = false
node := New(host, consensus, testDBFactory, nil, archiveMode)
consensus.Blockchain = node.Blockchain()
txs := make(map[common.Address]types.Transactions)
stks := staking.StakingTransactions{}
node.Worker.CommitTransactions(
txs, stks, common.Address{},
)
commitSigs := make(chan []byte)
go func() {
commitSigs <- []byte{}
}()
ecdsaAddr := pubKey.GetAddress()
shardState := &shard.State{}
com := shard.Committee{ShardID: uint32(0)}
spKey := bls.SerializedPublicKey{}
spKey.FromLibBLSPublicKey(pubKey)
curNodeID := shard.Slot{
ecdsaAddr,
spKey,
nil,
}
com.Slots = append(com.Slots, curNodeID)
shardState.Epoch = big.NewInt(1)
shardState.Shards = append(shardState.Shards, com)
node.Consensus.LeaderPubKey = &bls.PublicKeyWrapper{spKey, pubKey}
node.Worker.GetCurrentHeader().SetEpoch(big.NewInt(1))
node.Consensus.GenerateVrfAndProof(node.Worker.GetCurrentHeader())
block, _ := node.Worker.FinalizeNewBlock(
commitSigs, func() uint64 { return 0 }, ecdsaAddr, nil, shardState,
)
// Write shard state for the new epoch
node.Blockchain().WriteShardStateBytes(node.Blockchain().ChainDb(), big.NewInt(1), node.Worker.GetCurrentHeader().ShardState())
node.Blockchain().Config().VRFEpoch = big.NewInt(0)
if err := node.Blockchain().Engine().VerifyVRF(
node.Blockchain(), block.Header(),
); err != nil {
t.Error("New vrf is not verified successfully:", err)
}
}

@ -164,6 +164,14 @@ func (node *Node) ProposeNewBlock(commitSigs chan []byte) (*types.Block, error)
return nil, err
}
// Add VRF
if node.Blockchain().Config().IsVRF(header.Epoch()) {
//generate a new VRF for the current block
if err := node.Consensus.GenerateVrfAndProof(header); err != nil {
return nil, err
}
}
// Prepare normal and staking transactions retrieved from transaction pool
utils.AnalysisStart("proposeNewBlockChooseFromTxnPool")

@ -87,8 +87,15 @@ func (node *Node) createStateSync() *legacysync.StateSync {
// Thus for compatibility, we are doing the arithmetics here, and not to change the
// protocol itself. This is just the temporary hack and will not be a concern after
// state sync.
syncPort := node.downloaderServer.Port
mutatedPort := strconv.Itoa(syncPort + legacysync.SyncingPortDifference)
var mySyncPort int
if node.downloaderServer != nil {
mySyncPort = node.downloaderServer.Port
} else {
// If local sync server is not started, the port field in protocol is actually not
// functional, simply set it to default value.
mySyncPort = nodeconfig.DefaultDNSPort
}
mutatedPort := strconv.Itoa(mySyncPort + legacysync.SyncingPortDifference)
return legacysync.CreateStateSync(node.SelfPeer.IP, mutatedPort,
node.GetSyncID(), node.NodeConfig.Role() == nodeconfig.ExplorerNode)
}

@ -92,7 +92,7 @@ func (rm *requestManager) DoRequest(ctx context.Context, raw sttypes.Request, op
func (rm *requestManager) doRequestAsync(ctx context.Context, raw sttypes.Request, options ...RequestOption) <-chan responseData {
req := &request{
Request: raw,
respC: make(chan responseData),
respC: make(chan responseData, 1),
doneC: make(chan struct{}),
}
for _, opt := range options {

@ -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")
}
}

@ -1,5 +1,5 @@
# Build
FROM golang:1.14 AS build
FROM golang:1.16 AS build
RUN apt update -y && \
apt install libgmp-dev libssl-dev git -y && \

@ -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
}
// 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)
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())
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,26 +129,42 @@ func GetNativeOperationsFromStakingTransaction(
}
}
operations := append(gasOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: gasOperations[0].OperationIdentifier.Index + 1,
},
Type: tx.StakingType().String(),
Status: GetTransactionStatus(tx, receipt),
Account: accountID,
Amount: amount,
Metadata: metadata,
})
// expose delegated balance
if tx.StakingType() == stakingTypes.DirectiveDelegate {
op2 := getDelegateOperationForSubAccount(tx, operations[1])
return append(operations, op2), nil
if len(operations) > 0 {
operations = append(operations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: operations[0].OperationIdentifier.Index + 1,
},
Type: tx.StakingType().String(),
Status: GetTransactionStatus(tx, receipt),
Account: accountID,
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 tx.StakingType() == stakingTypes.DirectiveUndelegate {
op2 := getUndelegateOperationForSubAccount(tx, operations[1], receipt)
return append(operations, op2), nil
if signed {
// expose delegated balance
if tx.StakingType() == stakingTypes.DirectiveDelegate {
op2 := getDelegateOperationForSubAccount(tx, operations[1])
return append(operations, op2), nil
}
if tx.StakingType() == stakingTypes.DirectiveUndelegate {
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
}
fromAddr, err := internal_common.AddressToBech32(from)
if err != nil {
return nil, err
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
}
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)
}

@ -80,6 +80,8 @@ else
fi
echo "Running go test..."
# Fix https://github.com/golang/go/issues/44129#issuecomment-788351567
go get -t ./...
if go test -v -count=1 ./...
then
echo "go test succeeded."

@ -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

@ -0,0 +1,73 @@
package main
import (
"bytes"
"fmt"
"time"
"github.com/harmony-one/harmony/crypto/bls"
bls_core "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/crypto/hash"
vrf_bls "github.com/harmony-one/harmony/crypto/vrf/bls"
)
func init() {
bls_core.Init(bls_core.BLS12_381)
}
func main() {
blsPriKey := bls.RandPrivateKey()
pubKeyWrapper := bls.PublicKeyWrapper{Object: blsPriKey.GetPublicKey()}
pubKeyWrapper.Bytes.FromLibBLSPublicKey(pubKeyWrapper.Object)
blockHash := hash.Keccak256([]byte{1, 2, 3, 4, 5})
sig := &bls_core.Sign{}
startTime := time.Now()
for i := 0; i < 1000; i++ {
sig = blsPriKey.SignHash(blockHash[:])
}
endTime := time.Now()
fmt.Printf("Time required to sign 1000 times: %f seconds\n", endTime.Sub(startTime).Seconds())
startTime = time.Now()
for i := 0; i < 1000; i++ {
sig.VerifyHash(pubKeyWrapper.Object, blockHash[:])
}
endTime = time.Now()
fmt.Printf("Time required to verify sig 1000 times: %f seconds\n", endTime.Sub(startTime).Seconds())
sk := vrf_bls.NewVRFSigner(blsPriKey)
vrf := [32]byte{}
proof := []byte{}
startTime = time.Now()
for i := 0; i < 1000; i++ {
vrf, proof = sk.Evaluate(blockHash[:])
}
endTime = time.Now()
fmt.Printf("Time required to generate vrf 1000 times: %f seconds\n", endTime.Sub(startTime).Seconds())
pk := vrf_bls.NewVRFVerifier(blsPriKey.GetPublicKey())
resultVrf := [32]byte{}
startTime = time.Now()
for i := 0; i < 1000; i++ {
resultVrf, _ = pk.ProofToHash(blockHash, proof)
}
endTime = time.Now()
fmt.Printf("Time required to verify vrf 1000 times: %f seconds\n", endTime.Sub(startTime).Seconds())
if bytes.Compare(vrf[:], resultVrf[:]) != 0 {
fmt.Printf("Failed to verify VRF")
}
// A example result of a single run:
//Time required to sign 1000 times: 0.542673 seconds
//Time required to verify sig 1000 times: 1.499797 seconds
//Time required to generate vrf 1000 times: 0.525362 seconds
//Time required to verify vrf 1000 times: 2.076890 seconds
}

@ -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