diff --git a/api/service/syncing/syncing.go b/api/service/syncing/syncing.go index 873da9e90..03a3b46ca 100644 --- a/api/service/syncing/syncing.go +++ b/api/service/syncing/syncing.go @@ -32,7 +32,7 @@ const ( TimesToFail = 5 // downloadBlocks service retry limit RegistrationNumber = 3 SyncingPortDifference = 3000 - inSyncThreshold = 0 // when peerBlockHeight - myBlockHeight <= inSyncThreshold, it's ready to join consensus + inSyncThreshold = 1 // when peerBlockHeight - myBlockHeight <= inSyncThreshold, it's ready to join consensus SyncLoopBatchSize uint32 = 1000 // maximum size for one query of block hashes verifyHeaderBatchSize uint64 = 100 // block chain header verification batch size SyncLoopFrequency = 1 // unit in second diff --git a/cmd/harmony/main.go b/cmd/harmony/main.go index 07971a601..bd078f765 100644 --- a/cmd/harmony/main.go +++ b/cmd/harmony/main.go @@ -315,7 +315,7 @@ func setupNodeAndRun(hc harmonyConfig) { WSPort: hc.WS.Port, DebugEnabled: hc.RPCOpt.DebugEnabled, } - if nodeConfig.ShardID != shard.BeaconChainShardID { + if nodeConfig.ShardID != shard.BeaconChainShardID && hc.General.NodeType != nodeTypeExplorer { utils.Logger().Info(). Uint32("shardID", currentNode.Blockchain().ShardID()). Uint32("shardID", nodeConfig.ShardID).Msg("SupportBeaconSyncing") diff --git a/common/ntp/ntp_test.go b/common/ntp/ntp_test.go index ddd9ff432..2e8b148d7 100644 --- a/common/ntp/ntp_test.go +++ b/common/ntp/ntp_test.go @@ -2,19 +2,23 @@ package ntp import ( "fmt" + "os" "testing" ) func TestCheckLocalTimeAccurate(t *testing.T) { - accurate, err := CheckLocalTimeAccurate("0.pool.ntp.org") - if !accurate { - t.Fatalf("local time is not accurate: %v\n", err) - } - - accurate, err = CheckLocalTimeAccurate("wrong.ip") + accurate, err := CheckLocalTimeAccurate("wrong.ip") if accurate { t.Fatalf("query ntp pool should failed: %v\n", err) } + + accurate, err = CheckLocalTimeAccurate("0.pool.ntp.org") + if !accurate { + if os.IsTimeout(err) { + t.Skip(err) + } + t.Fatal(err) + } } func TestCurrentTime(t *testing.T) { diff --git a/consensus/consensus_service.go b/consensus/consensus_service.go index 7b6fc7e01..ede64dcae 100644 --- a/consensus/consensus_service.go +++ b/consensus/consensus_service.go @@ -421,6 +421,12 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode { Msg("[UpdateConsensusInformation] changing committee") // take care of possible leader change during the epoch + // TODO: in a very rare case, when a M1 view change happened, the block contains coinbase for last leader + // but the new leader is actually recognized by most of the nodes. At this time, if a node sync to this + // exact block and set its leader, it will set with the failed leader as in the coinbase of the block. + // This is a very rare case scenario and not likely to cause any issue in mainnet. But we need to think about + // 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) if err != nil || leaderPubKey == nil { diff --git a/consensus/consensus_v2.go b/consensus/consensus_v2.go index f97cfb663..b2120a0e8 100644 --- a/consensus/consensus_v2.go +++ b/consensus/consensus_v2.go @@ -608,7 +608,7 @@ func (consensus *Consensus) commitBlock(blk *types.Block, committedMsg *FBFTMess func (consensus *Consensus) SetupForNewConsensus(blk *types.Block, committedMsg *FBFTMessage) { atomic.AddUint64(&consensus.blockNum, 1) - consensus.SetCurBlockViewID(committedMsg.ViewID + 1) + consensus.SetViewIDs(committedMsg.ViewID + 1) consensus.LeaderPubKey = committedMsg.SenderPubkeys[0] // Update consensus keys at last so the change of leader status doesn't mess up normal flow if blk.IsLastBlockInEpoch() { diff --git a/consensus/construct_test.go b/consensus/construct_test.go index 24b45c11a..27d1e82f4 100644 --- a/consensus/construct_test.go +++ b/consensus/construct_test.go @@ -71,19 +71,21 @@ func TestConstructPreparedMessage(test *testing.T) { message := "test string" leaderKey := bls.SerializedPublicKey{} leaderKey.FromLibBLSPublicKey(leaderPubKey) + leaderKeyWrapper := bls.PublicKeyWrapper{Object: leaderPubKey, Bytes: leaderKey} validatorKey := bls.SerializedPublicKey{} validatorKey.FromLibBLSPublicKey(validatorPubKey) - consensus.Decider.SubmitVote( + validatorKeyWrapper := bls.PublicKeyWrapper{Object: validatorPubKey, Bytes: validatorKey} + consensus.Decider.AddNewVote( quorum.Prepare, - []bls.SerializedPublicKey{leaderKey}, + []*bls.PublicKeyWrapper{&leaderKeyWrapper}, leaderPriKey.Sign(message), common.BytesToHash(consensus.blockHash[:]), consensus.blockNum, consensus.GetCurBlockViewID(), ) - if _, err := consensus.Decider.SubmitVote( + if _, err := consensus.Decider.AddNewVote( quorum.Prepare, - []bls.SerializedPublicKey{validatorKey}, + []*bls.PublicKeyWrapper{&validatorKeyWrapper}, validatorPriKey.Sign(message), common.BytesToHash(consensus.blockHash[:]), consensus.blockNum, diff --git a/consensus/quorum/one-node-one-vote.go b/consensus/quorum/one-node-one-vote.go index 45db9bcd3..7455d6e72 100644 --- a/consensus/quorum/one-node-one-vote.go +++ b/consensus/quorum/one-node-one-vote.go @@ -36,7 +36,7 @@ func (v *uniformVoteWeight) AddNewVote( for i, pubKey := range pubKeys { pubKeysBytes[i] = pubKey.Bytes } - return v.SubmitVote(p, pubKeysBytes, sig, headerHash, height, viewID) + return v.submitVote(p, pubKeysBytes, sig, headerHash, height, viewID) } // IsQuorumAchieved .. diff --git a/consensus/quorum/one-node-staked-vote.go b/consensus/quorum/one-node-staked-vote.go index dc8dd2e11..0f65257c9 100644 --- a/consensus/quorum/one-node-staked-vote.go +++ b/consensus/quorum/one-node-staked-vote.go @@ -82,7 +82,7 @@ func (v *stakedVoteWeight) AddNewVote( pubKeysBytes[i] = pubKey.Bytes } - ballet, err := v.SubmitVote(p, pubKeysBytes, sig, headerHash, height, viewID) + ballet, err := v.submitVote(p, pubKeysBytes, sig, headerHash, height, viewID) if err != nil { return ballet, err @@ -188,7 +188,6 @@ func (v *stakedVoteWeight) QuorumThreshold() numeric.Dec { // IsAllSigsCollected .. func (v *stakedVoteWeight) IsAllSigsCollected() bool { - utils.Logger().Info().Msgf("ALL SIGS %s", v.voteTally.Commit.tally) return v.voteTally.Commit.tally.Equal(numeric.NewDec(1)) } diff --git a/consensus/quorum/quorom_test.go b/consensus/quorum/quorom_test.go index d6b91a5bf..73767ea25 100644 --- a/consensus/quorum/quorom_test.go +++ b/consensus/quorum/quorom_test.go @@ -88,7 +88,7 @@ func TestSubmitVote(test *testing.T) { decider.UpdateParticipants([]bls.PublicKeyWrapper{pubKeyWrapper1, pubKeyWrapper2}) - if _, err := decider.SubmitVote( + if _, err := decider.submitVote( Prepare, []bls.SerializedPublicKey{pubKeyWrapper1.Bytes}, blsPriKey1.Sign(message), @@ -99,7 +99,7 @@ func TestSubmitVote(test *testing.T) { test.Log(err) } - if _, err := decider.SubmitVote( + if _, err := decider.submitVote( Prepare, []bls.SerializedPublicKey{pubKeyWrapper2.Bytes}, blsPriKey2.Sign(message), @@ -110,7 +110,7 @@ func TestSubmitVote(test *testing.T) { test.Log(err) } if decider.SignersCount(Prepare) != 2 { - test.Fatal("SubmitVote failed") + test.Fatal("submitVote failed") } aggSig := &bls_core.Sign{} @@ -145,7 +145,7 @@ func TestSubmitVoteAggregateSig(test *testing.T) { decider.UpdateParticipants([]bls.PublicKeyWrapper{pubKeyWrapper1, pubKeyWrapper2}) - decider.SubmitVote( + decider.submitVote( Prepare, []bls.SerializedPublicKey{pubKeyWrapper1.Bytes}, blsPriKey1.SignHash(blockHash[:]), @@ -160,7 +160,7 @@ func TestSubmitVoteAggregateSig(test *testing.T) { aggSig.Add(s) } } - if _, err := decider.SubmitVote( + if _, err := decider.submitVote( Prepare, []bls.SerializedPublicKey{pubKeyWrapper2.Bytes, pubKeyWrapper3.Bytes}, aggSig, @@ -172,7 +172,7 @@ func TestSubmitVoteAggregateSig(test *testing.T) { } if decider.SignersCount(Prepare) != 3 { - test.Fatal("SubmitVote failed") + test.Fatal("submitVote failed") } aggSig.Add(blsPriKey1.SignHash(blockHash[:])) @@ -180,7 +180,7 @@ func TestSubmitVoteAggregateSig(test *testing.T) { test.Fatal("AggregateVotes failed") } - if _, err := decider.SubmitVote( + if _, err := decider.submitVote( Prepare, []bls.SerializedPublicKey{pubKeyWrapper2.Bytes}, aggSig, diff --git a/consensus/quorum/quorum.go b/consensus/quorum/quorum.go index f41d47ec1..674ea96c0 100644 --- a/consensus/quorum/quorum.go +++ b/consensus/quorum/quorum.go @@ -81,7 +81,8 @@ type ParticipantTracker interface { // SignatoryTracker .. type SignatoryTracker interface { ParticipantTracker - SubmitVote( + // This func shouldn't be called directly from outside of quorum. Use AddNewVote instead. + submitVote( p Phase, pubkeys []bls.SerializedPublicKey, sig *bls_core.Sign, headerHash common.Hash, height, viewID uint64, @@ -118,6 +119,7 @@ type Decider interface { DependencyInjectionWriter SetVoters(subCommittee *shard.Committee, epoch *big.Int) (*TallyResult, error) Policy() Policy + // Add new vote will add the signature in the memory and increase the cumulative voting power AddNewVote( p Phase, pubkeys []*bls_cosi.PublicKeyWrapper, sig *bls_core.Sign, headerHash common.Hash, @@ -273,7 +275,7 @@ func (s *cIdentities) SignersCount(p Phase) int64 { } } -func (s *cIdentities) SubmitVote( +func (s *cIdentities) submitVote( p Phase, pubkeys []bls.SerializedPublicKey, sig *bls_core.Sign, headerHash common.Hash, height, viewID uint64, diff --git a/consensus/validator.go b/consensus/validator.go index dd805a313..275421ad4 100644 --- a/consensus/validator.go +++ b/consensus/validator.go @@ -114,10 +114,6 @@ func (consensus *Consensus) onPrepared(msg *msg_pb.Message) { Msg("Wrong BlockNum Received, ignoring!") return } - if recvMsg.BlockNum > consensus.blockNum+1 { - consensus.getLogger().Warn().Msgf("[OnPrepared] low consensus block number. Spin sync") - consensus.spinUpStateSync() - } // check validity of prepared signature blockHash := recvMsg.BlockHash @@ -153,10 +149,12 @@ func (consensus *Consensus) onPrepared(msg *msg_pb.Message) { if !consensus.onPreparedSanityChecks(&blockObj, recvMsg) { return } - consensus.getLogger().Info(). - Uint64("MsgBlockNum", recvMsg.BlockNum). - Uint64("MsgViewID", recvMsg.ViewID). - Msg("[OnPrepared] Received OnPrepared message11111111") + + if recvMsg.BlockNum > consensus.blockNum { + consensus.getLogger().Warn().Msgf("[OnPrepared] low consensus block number. Spin sync") + consensus.spinUpStateSync() + } + consensus.mutex.Lock() defer consensus.mutex.Unlock() consensus.getLogger().Info(). @@ -248,7 +246,6 @@ func (consensus *Consensus) onCommitted(msg *msg_pb.Message) { consensus.getLogger().Warn().Msg("[OnCommitted] unable to parse msg") return } - consensus.getLogger().Info(). Uint64("MsgBlockNum", recvMsg.BlockNum). Uint64("MsgViewID", recvMsg.ViewID). @@ -298,10 +295,6 @@ func (consensus *Consensus) onCommitted(msg *msg_pb.Message) { Msg("[OnCommitted] Failed finding a matching block for committed message") return } - consensus.getLogger().Info(). - Uint64("MsgBlockNum", recvMsg.BlockNum). - Uint64("MsgViewID", recvMsg.ViewID). - Msg("[OnCommitted] Received committed message333333") commitPayload := signature.ConstructCommitPayload(consensus.Blockchain, blockObj.Epoch(), blockObj.Hash(), blockObj.NumberU64(), blockObj.Header().ViewID().Uint64()) if !aggSig.VerifyHash(mask.AggregatePublic, commitPayload) { @@ -317,10 +310,10 @@ func (consensus *Consensus) onCommitted(msg *msg_pb.Message) { Msg("[OnCommitted] Received committed message444444") consensus.FBFTLog.AddMessage(recvMsg) - consensus.getLogger().Info(). - Uint64("MsgBlockNum", recvMsg.BlockNum). - Uint64("MsgViewID", recvMsg.ViewID). - Msg("[OnCommitted] Received committed message555555") + if recvMsg.BlockNum > consensus.blockNum { + consensus.getLogger().Info().Msg("[OnCommitted] low consensus block number. Spin up state sync") + consensus.spinUpStateSync() + } consensus.getLogger().Info(). Uint64("MsgBlockNum", recvMsg.BlockNum). diff --git a/consensus/view_change.go b/consensus/view_change.go index 13f21916d..1a94f2892 100644 --- a/consensus/view_change.go +++ b/consensus/view_change.go @@ -131,9 +131,8 @@ func (consensus *Consensus) getNextViewID() (uint64, time.Duration) { if curTimestamp <= blockTimestamp { return consensus.fallbackNextViewID() } - totalNode := consensus.Decider.ParticipantsCount() - // diff is at least 1, and it won't exceed the totalNode - diff := uint64(((curTimestamp - blockTimestamp) / viewChangeTimeout) % int64(totalNode)) + // diff only increases + diff := uint64((curTimestamp - blockTimestamp) / viewChangeTimeout) nextViewID := diff + consensus.GetCurBlockViewID() consensus.getLogger().Info(). @@ -161,7 +160,7 @@ func (consensus *Consensus) getNextLeaderKey(viewID uint64) *bls.PublicKeyWrappe var err error epoch := big.NewInt(0) if consensus.Blockchain == nil { - consensus.getLogger().Error().Msg("[getNextLeaderKey] ChainReader is nil. Use consensus.LeaderPubKey") + consensus.getLogger().Error().Msg("[getNextLeaderKey] Blockchain is nil. Use consensus.LeaderPubKey") lastLeaderPubKey = consensus.LeaderPubKey } else { curHeader := consensus.Blockchain.CurrentHeader() diff --git a/go.mod b/go.mod index 2dd46e09a..5cf37b42d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/beevik/ntp v0.3.0 github.com/btcsuite/btcutil v1.0.2 github.com/cespare/cp v1.1.1 - github.com/coinbase/rosetta-sdk-go v0.4.4 + github.com/coinbase/rosetta-sdk-go v0.4.6 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.7.1 github.com/edsrzf/mmap-go v1.0.0 // indirect diff --git a/node/node.go b/node/node.go index 2574e80d5..2ac7fa1d4 100644 --- a/node/node.go +++ b/node/node.go @@ -204,10 +204,16 @@ func (node *Node) tryBroadcastStaking(stakingTx *staking.StakingTransaction) { // Add new transactions to the pending transaction list. func (node *Node) addPendingTransactions(newTxs types.Transactions) []error { poolTxs := types.PoolTransactions{} + errs := []error{} + acceptCx := node.Blockchain().Config().AcceptsCrossTx(node.Blockchain().CurrentHeader().Epoch()) for _, tx := range newTxs { + if tx.ShardID() != tx.ToShardID() && !acceptCx { + errs = append(errs, errors.WithMessage(errInvalidEpoch, "cross-shard tx not accepted yet")) + continue + } poolTxs = append(poolTxs, tx) } - errs := node.TxPool.AddRemotes(poolTxs) + errs = append(errs, node.TxPool.AddRemotes(poolTxs)...) pendingCount, queueCount := node.TxPool.Stats() utils.Logger().Info(). @@ -221,22 +227,28 @@ func (node *Node) addPendingTransactions(newTxs types.Transactions) []error { // Add new staking transactions to the pending staking transaction list. func (node *Node) addPendingStakingTransactions(newStakingTxs staking.StakingTransactions) []error { - if node.NodeConfig.ShardID == shard.BeaconChainShardID && - node.Blockchain().Config().IsPreStaking(node.Blockchain().CurrentHeader().Epoch()) { - poolTxs := types.PoolTransactions{} - for _, tx := range newStakingTxs { - poolTxs = append(poolTxs, tx) + if node.NodeConfig.ShardID == shard.BeaconChainShardID { + if node.Blockchain().Config().IsPreStaking(node.Blockchain().CurrentHeader().Epoch()) { + poolTxs := types.PoolTransactions{} + for _, tx := range newStakingTxs { + poolTxs = append(poolTxs, tx) + } + errs := node.TxPool.AddRemotes(poolTxs) + pendingCount, queueCount := node.TxPool.Stats() + utils.Logger().Info(). + Int("length of newStakingTxs", len(poolTxs)). + Int("totalPending", pendingCount). + Int("totalQueued", queueCount). + Msg("Got more staking transactions") + return errs } - errs := node.TxPool.AddRemotes(poolTxs) - pendingCount, queueCount := node.TxPool.Stats() - utils.Logger().Info(). - Int("length of newStakingTxs", len(poolTxs)). - Int("totalPending", pendingCount). - Int("totalQueued", queueCount). - Msg("Got more staking transactions") - return errs + return []error{ + errors.WithMessage(errInvalidEpoch, "staking txs not accepted yet"), + } + } + return []error{ + errors.WithMessage(errInvalidShard, fmt.Sprintf("txs only valid on shard %v", shard.BeaconChainShardID)), } - return make([]error, len(newStakingTxs)) } // AddPendingStakingTransaction staking transactions @@ -248,13 +260,17 @@ func (node *Node) AddPendingStakingTransaction( var err error for i := range errs { if errs[i] != nil { - utils.Logger().Info().Err(errs[i]).Msg("[AddPendingStakingTransaction] Failed adding new staking transaction") + utils.Logger().Info(). + Err(errs[i]). + Msg("[AddPendingStakingTransaction] Failed adding new staking transaction") err = errs[i] break } } if err == nil || node.BroadcastInvalidTx { - utils.Logger().Info().Str("Hash", newStakingTx.Hash().Hex()).Msg("Broadcasting Staking Tx") + utils.Logger().Info(). + Str("Hash", newStakingTx.Hash().Hex()). + Msg("Broadcasting Staking Tx") node.tryBroadcastStaking(newStakingTx) } return err @@ -361,6 +377,8 @@ var ( errWrongShardID = errors.New("wrong shard id") errInvalidNodeMsg = errors.New("invalid node message") errIgnoreBeaconMsg = errors.New("ignore beacon sync block") + errInvalidEpoch = errors.New("invalid epoch for transaction") + errInvalidShard = errors.New("invalid shard") ) // validateNodeMessage validate node message diff --git a/rosetta/common/config.go b/rosetta/common/config.go index 30058d574..feb30d67a 100644 --- a/rosetta/common/config.go +++ b/rosetta/common/config.go @@ -12,7 +12,7 @@ import ( const ( // RosettaVersion tied back to the version of the rosetta go-sdk - RosettaVersion = "0.4.4" // TODO (dm): set variable via build flags + RosettaVersion = "1.4.6" // TODO (dm): set variable via build flags // Blockchain .. Blockchain = "Harmony" diff --git a/rosetta/common/operations.go b/rosetta/common/operations.go index 42eff80bf..82fbf5a44 100644 --- a/rosetta/common/operations.go +++ b/rosetta/common/operations.go @@ -15,11 +15,11 @@ const ( // ExpendGasOperation is an operation that only affects the native currency. ExpendGasOperation = "Gas" - // TransferNativeOperation is an operation that only affects the native currency. - TransferNativeOperation = "NativeTransfer" + // NativeTransferOperation is an operation that only affects the native currency. + NativeTransferOperation = "NativeTransfer" - // CrossShardTransferNativeOperation is an operation that only affects the native currency. - CrossShardTransferNativeOperation = "NativeCrossShardTransfer" + // NativeCrossShardTransferOperation is an operation that only affects the native currency. + NativeCrossShardTransferOperation = "NativeCrossShardTransfer" // ContractCreationOperation is an operation that only affects the native currency. ContractCreationOperation = "ContractCreation" @@ -41,8 +41,8 @@ var ( // PlainOperationTypes .. PlainOperationTypes = []string{ ExpendGasOperation, - TransferNativeOperation, - CrossShardTransferNativeOperation, + NativeTransferOperation, + NativeCrossShardTransferOperation, ContractCreationOperation, GenesisFundsOperation, PreStakingBlockRewardOperation, diff --git a/rosetta/common/operations_test.go b/rosetta/common/operations_test.go index ac297c68a..a2e9c08f1 100644 --- a/rosetta/common/operations_test.go +++ b/rosetta/common/operations_test.go @@ -50,8 +50,8 @@ func TestPlainOperationTypes(t *testing.T) { plainOperationTypes := PlainOperationTypes referenceOperationTypes := []string{ ExpendGasOperation, - TransferNativeOperation, - CrossShardTransferNativeOperation, + NativeTransferOperation, + NativeCrossShardTransferOperation, ContractCreationOperation, GenesisFundsOperation, PreStakingBlockRewardOperation, diff --git a/rosetta/services/account.go b/rosetta/services/account.go index f52626195..231e5f2c6 100644 --- a/rosetta/services/account.go +++ b/rosetta/services/account.go @@ -36,39 +36,26 @@ func (s *AccountAPI) AccountBalance( } var block *hmyTypes.Block + var rosettaError *types.Error if request.BlockIdentifier == nil { block = s.hmy.CurrentBlock() } else { - var err error - if request.BlockIdentifier.Hash != nil { - blockHash := ethCommon.HexToHash(*request.BlockIdentifier.Hash) - block, err = s.hmy.GetBlock(ctx, blockHash) - if err != nil { - return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{ - "message": "block hash not found", - }) - } - } else { - blockNum := rpc.BlockNumber(*request.BlockIdentifier.Index) - block, err = s.hmy.BlockByNumber(ctx, blockNum) - if err != nil { - return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{ - "message": "block index not found", - }) - } + block, rosettaError = getBlock(ctx, s.hmy, request.BlockIdentifier) + if rosettaError != nil { + return nil, rosettaError } } addr, err := getAddress(request.AccountIdentifier) if err != nil { - return nil, common.NewError(common.CatchAllError, map[string]interface{}{ + return nil, common.NewError(common.SanityCheckError, map[string]interface{}{ "message": err.Error(), }) } blockNum := rpc.BlockNumber(block.Header().Header.Number().Int64()) balance, err := s.hmy.GetBalance(ctx, addr, blockNum) if err != nil { - return nil, common.NewError(common.CatchAllError, map[string]interface{}{ + return nil, common.NewError(common.SanityCheckError, map[string]interface{}{ "message": "invalid address", }) } @@ -100,7 +87,7 @@ func newAccountIdentifier( ) (*types.AccountIdentifier, *types.Error) { b32Address, err := internalCommon.AddressToBech32(address) if err != nil { - return nil, common.NewError(common.CatchAllError, map[string]interface{}{ + return nil, common.NewError(common.SanityCheckError, map[string]interface{}{ "message": err.Error(), }) } diff --git a/rosetta/services/block.go b/rosetta/services/block.go index a3c0b7785..8dcb15f61 100644 --- a/rosetta/services/block.go +++ b/rosetta/services/block.go @@ -2,6 +2,7 @@ package services import ( "context" + "fmt" "math/big" "github.com/coinbase/rosetta-sdk-go/server" @@ -43,7 +44,7 @@ func (s *BlockAPI) Block( var blk *hmytypes.Block var currBlockID, prevBlockID *types.BlockIdentifier - if blk, rosettaError = s.getBlock(ctx, request.BlockIdentifier); rosettaError != nil { + if blk, rosettaError = getBlock(ctx, s.hmy, request.BlockIdentifier); rosettaError != nil { return nil, rosettaError } @@ -124,30 +125,6 @@ func (s *BlockAPI) Block( }, nil } -// getBlock .. -func (s *BlockAPI) getBlock( - ctx context.Context, request *types.PartialBlockIdentifier, -) (blk *hmytypes.Block, rosettaError *types.Error) { - var err error - if request.Hash != nil { - requestBlockHash := ethcommon.HexToHash(*request.Hash) - blk, err = s.hmy.GetBlock(ctx, requestBlockHash) - } else if request.Index != nil { - blk, err = s.hmy.BlockByNumber(ctx, rpc.BlockNumber(*request.Index).EthBlockNumber()) - } else { - return nil, &common.BlockNotFoundError - } - if err != nil { - return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{ - "message": err.Error(), - }) - } - if blk == nil { - return nil, &common.BlockNotFoundError - } - return blk, nil -} - // BlockTransaction implements the /block/transaction endpoint func (s *BlockAPI) BlockTransaction( ctx context.Context, request *types.BlockTransactionRequest, @@ -174,10 +151,20 @@ func (s *BlockAPI) BlockTransaction( } return response, rosettaError2 } + state, _, err := s.hmy.StateAndHeaderByNumber(ctx, rpc.BlockNumber(request.BlockIdentifier.Index).EthBlockNumber()) + if state == nil || err != nil { + return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{ + "message": fmt.Sprintf("block state not found for block %v", request.BlockIdentifier.Index), + }) + } var transaction *types.Transaction if txInfo.tx != nil && txInfo.receipt != nil { - transaction, rosettaError = FormatTransaction(txInfo.tx, txInfo.receipt) + contractCode := []byte{} + if txInfo.tx.To() != nil { + contractCode = state.GetCode(*txInfo.tx.To()) + } + transaction, rosettaError = FormatTransaction(txInfo.tx, txInfo.receipt, contractCode) if rosettaError != nil { return nil, rosettaError } @@ -245,3 +232,29 @@ func (s *BlockAPI) getTransactionInfo( cxReceipt: cxReceipt, }, nil } + +// getBlock .. +func getBlock( + ctx context.Context, hmy *hmy.Harmony, blockID *types.PartialBlockIdentifier, +) (blk *hmytypes.Block, rosettaError *types.Error) { + var err error + if blockID.Hash != nil { + requestBlockHash := ethcommon.HexToHash(*blockID.Hash) + blk, err = hmy.GetBlock(ctx, requestBlockHash) + } else if blockID.Index != nil { + blk, err = hmy.BlockByNumber(ctx, rpc.BlockNumber(*blockID.Index).EthBlockNumber()) + } else { + return nil, &common.BlockNotFoundError + } + if err != nil { + return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{ + "message": err.Error(), + }) + } + if blk == nil { + return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{ + "message": "block not found for given block identifier", + }) + } + return blk, nil +} diff --git a/rosetta/services/block_special.go b/rosetta/services/block_special.go index 8b6dbe17d..4ac2e0de6 100644 --- a/rosetta/services/block_special.go +++ b/rosetta/services/block_special.go @@ -181,7 +181,7 @@ func (s *BlockAPI) specialBlockTransaction( ctx context.Context, request *types.BlockTransactionRequest, ) (*types.BlockTransactionResponse, *types.Error) { // If no transaction info is found, check for special case transactions. - blk, rosettaError := s.getBlock(ctx, &types.PartialBlockIdentifier{Index: &request.BlockIdentifier.Index}) + blk, rosettaError := getBlock(ctx, s.hmy, &types.PartialBlockIdentifier{Index: &request.BlockIdentifier.Index}) if rosettaError != nil { return nil, rosettaError } diff --git a/rosetta/services/construction_check.go b/rosetta/services/construction_check.go index 7ae31bb8e..53d1b5f2a 100644 --- a/rosetta/services/construction_check.go +++ b/rosetta/services/construction_check.go @@ -9,8 +9,10 @@ import ( "github.com/coinbase/rosetta-sdk-go/types" ethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + ethRpc "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" + "github.com/harmony-one/harmony/core/vm" "github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/rosetta/common" "github.com/harmony-one/harmony/rpc" @@ -74,6 +76,17 @@ func (s *ConstructAPI) ConstructionPreprocess( "message": "sender address is not found for given operations", }) } + if txMetadata.ToShardID != nil && txMetadata.FromShardID != nil && + components.Type != common.NativeCrossShardTransferOperation && *txMetadata.ToShardID != *txMetadata.FromShardID { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": "given from & to shard are different for a native same shard transfer", + }) + } + if request.SuggestedFeeMultiplier != nil && *request.SuggestedFeeMultiplier < 1 { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": "given gas price multiplier must be at least 1", + }) + } options, err := types.MarshalMap(ConstructMetadataOptions{ TransactionMetadata: txMetadata, @@ -85,6 +98,11 @@ func (s *ConstructAPI) ConstructionPreprocess( "message": err.Error(), }) } + if _, err := getAddress(components.From); err != nil { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": err.Error(), + }) + } return &types.ConstructionPreprocessResponse{ Options: options, RequiredPublicKeys: []*types.AccountIdentifier{ @@ -95,10 +113,13 @@ func (s *ConstructAPI) ConstructionPreprocess( // ConstructMetadata with a set of operations will construct a valid transaction type ConstructMetadata struct { - Nonce uint64 `json:"nonce"` - GasLimit uint64 `json:"gas_limit"` - GasPrice *big.Int `json:"gas_price"` - Transaction *TransactionMetadata `json:"transaction_metadata"` + Nonce uint64 `json:"nonce"` + GasLimit uint64 `json:"gas_limit"` + GasPrice *big.Int `json:"gas_price"` + ContractCode hexutil.Bytes `json:"contract_code"` + EvmReturn hexutil.Bytes `json:"evm_return"` + EvmErrorMessage string `json:"evm_error_message"` + Transaction *TransactionMetadata `json:"transaction_metadata"` } // UnmarshalFromInterface .. @@ -151,6 +172,20 @@ func (s *ConstructAPI) ConstructionMetadata( }) } + currBlock, err := s.hmy.BlockByNumber(ctx, ethRpc.LatestBlockNumber) + if err != nil { + return nil, common.NewError(common.CatchAllError, map[string]interface{}{ + "message": err.Error(), + }) + } + + if options.OperationType == common.NativeCrossShardTransferOperation && + !s.hmy.BlockChain.Config().AcceptsCrossTx(currBlock.Epoch()) { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": "cross-shard transaction is not accepted yet", + }) + } + data := hexutil.Bytes{} if options.TransactionMetadata.Data != nil { var err error @@ -161,13 +196,31 @@ func (s *ConstructAPI) ConstructionMetadata( } } + var contractAddress ethCommon.Address + if options.TransactionMetadata.ContractAccountIdentifier != nil { + contractAddress, err = getAddress(options.TransactionMetadata.ContractAccountIdentifier) + if err != nil { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": errors.WithMessage(err, "unable to get provided contract address").Error(), + }) + } + } + state, _, err := s.hmy.StateAndHeaderByNumber(ctx, ethRpc.LatestBlockNumber) + if state == nil || err != nil { + return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{ + "message": "block state not found for latest block", + }) + } + var estGasUsed uint64 if !isStakingOperation(options.OperationType) { if options.OperationType == common.ContractCreationOperation { - estGasUsed, err = rpc.EstimateGas(ctx, s.hmy, rpc.CallArgs{Data: &data}, nil) + estGasUsed, err = rpc.EstimateGas(ctx, s.hmy, rpc.CallArgs{From: senderAddr, Data: &data}, nil) estGasUsed *= 2 // HACK to account for imperfect contract creation estimation } else { - estGasUsed, err = rpc.EstimateGas(ctx, s.hmy, rpc.CallArgs{To: ðCommon.Address{}, Data: &data}, nil) + estGasUsed, err = rpc.EstimateGas( + ctx, s.hmy, rpc.CallArgs{From: senderAddr, To: &contractAddress, Data: &data}, nil, + ) } } else { return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ @@ -185,11 +238,41 @@ func (s *ConstructAPI) ConstructionMetadata( } sugNativeFee, sugNativePrice := getSuggestedNativeFeeAndPrice(gasMul, new(big.Int).SetUint64(estGasUsed)) + evmErrorMsg := "" + evmReturn := hexutil.Bytes{} + if !isStakingOperation(options.OperationType) && + options.OperationType != common.ContractCreationOperation && + len(data) > 0 { + gas := hexutil.Uint64(estGasUsed) + callArgs := rpc.CallArgs{ + From: senderAddr, + To: &contractAddress, + Data: &data, + Gas: &gas, + } + evmExe, err := rpc.DoEVMCall( + ctx, s.hmy, callArgs, ethRpc.LatestBlockNumber, vm.Config{}, rpc.CallTimeout, s.hmy.RPCGasCap, + ) + if err != nil { + return nil, common.NewError(common.CatchAllError, map[string]interface{}{ + "message": errors.WithMessage(err, "unable to execute EVM").Error(), + }) + } + if evmExe.VMErr != nil { + evmErrorMsg = evmExe.VMErr.Error() + } + evmReturn = evmExe.ReturnData + sugNativeFee, sugNativePrice = getSuggestedNativeFeeAndPrice(gasMul, new(big.Int).SetUint64(evmExe.UsedGas)) + } + metadata, err := types.MarshalMap(ConstructMetadata{ - Nonce: nonce, - GasPrice: sugNativePrice, - GasLimit: estGasUsed, - Transaction: options.TransactionMetadata, + Nonce: nonce, + GasPrice: sugNativePrice, + GasLimit: estGasUsed, + Transaction: options.TransactionMetadata, + ContractCode: state.GetCode(contractAddress), + EvmErrorMessage: evmErrorMsg, + EvmReturn: evmReturn, }) if err != nil { return nil, common.NewError(common.CatchAllError, map[string]interface{}{ diff --git a/rosetta/services/construction_check_test.go b/rosetta/services/construction_check_test.go index 548c5531e..47879e935 100644 --- a/rosetta/services/construction_check_test.go +++ b/rosetta/services/construction_check_test.go @@ -28,7 +28,7 @@ func TestConstructMetadataOptions(t *testing.T) { { Metadata: ConstructMetadataOptions{ TransactionMetadata: refTxMedata, - OperationType: common.TransferNativeOperation, + OperationType: common.NativeTransferOperation, GasPriceMultiplier: nil, }, ExpectError: false, @@ -36,7 +36,7 @@ func TestConstructMetadataOptions(t *testing.T) { { Metadata: ConstructMetadataOptions{ TransactionMetadata: refTxMedata, - OperationType: common.TransferNativeOperation, + OperationType: common.NativeTransferOperation, GasPriceMultiplier: &refGasPrice, }, ExpectError: false, @@ -44,7 +44,7 @@ func TestConstructMetadataOptions(t *testing.T) { { Metadata: ConstructMetadataOptions{ TransactionMetadata: nil, - OperationType: common.TransferNativeOperation, + OperationType: common.NativeTransferOperation, GasPriceMultiplier: &refGasPrice, }, ExpectError: true, @@ -52,7 +52,7 @@ func TestConstructMetadataOptions(t *testing.T) { { Metadata: ConstructMetadataOptions{ TransactionMetadata: nil, - OperationType: common.TransferNativeOperation, + OperationType: common.NativeTransferOperation, GasPriceMultiplier: nil, }, ExpectError: true, diff --git a/rosetta/services/construction_create.go b/rosetta/services/construction_create.go index 1151032a5..8c5d01c30 100644 --- a/rosetta/services/construction_create.go +++ b/rosetta/services/construction_create.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/coinbase/rosetta-sdk-go/types" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rlp" "github.com/pkg/errors" @@ -23,9 +24,10 @@ const ( // WrappedTransaction is a wrapper for a transaction that includes all relevant // data to parse a transaction. type WrappedTransaction struct { - RLPBytes []byte `json:"rlp_bytes"` - IsStaking bool `json:"is_staking"` - From *types.AccountIdentifier `json:"from"` + RLPBytes []byte `json:"rlp_bytes"` + IsStaking bool `json:"is_staking"` + ContractCode hexutil.Bytes `json:"contract_code"` + From *types.AccountIdentifier `json:"from"` } // unpackWrappedTransactionFromString .. @@ -111,11 +113,18 @@ func (s *ConstructAPI) ConstructionPayloads( "message": "sender address is not found for given operations", }) } - if types.Hash(senderID) != types.Hash(components.From) { + if senderID.Address != components.From.Address { return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ "message": "sender account identifier from operations does not match account identifier from public key", }) } + if metadata.Transaction.FromShardID != nil && *metadata.Transaction.FromShardID != s.hmy.ShardID { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": fmt.Sprintf("transaction is for shard %v != shard %v", + *metadata.Transaction.FromShardID, s.hmy.ShardID, + ), + }) + } unsignedTx, rosettaError := ConstructTransaction(components, metadata, s.hmy.ShardID) if rosettaError != nil { @@ -132,9 +141,10 @@ func (s *ConstructAPI) ConstructionPayloads( }) } wrappedTxMarshalledBytes, err := json.Marshal(WrappedTransaction{ - RLPBytes: buf.Bytes(), - From: senderID, - IsStaking: components.IsStaking(), + RLPBytes: buf.Bytes(), + From: senderID, + ContractCode: metadata.ContractCode, + IsStaking: components.IsStaking(), }) if err != nil { return nil, common.NewError(common.CatchAllError, map[string]interface{}{ @@ -184,11 +194,16 @@ func (s *ConstructAPI) ConstructionCombine( "message": "require exactly 1 signature", }) } + if tx.ShardID() != s.hmy.ShardID { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": fmt.Sprintf("transaction is for shard %v != shard %v", tx.ShardID(), s.hmy.ShardID), + }) + } sig := request.Signatures[0] if sig.SignatureType != common.SignatureType { return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ - "message": fmt.Sprintf("invalid transaction type, currently only support %v", common.SignatureType), + "message": fmt.Sprintf("invalid signature type, currently only support %v", common.SignatureType), }) } sigAddress, rosettaError := getAddressFromPublicKey(sig.PublicKey) @@ -199,7 +214,7 @@ func (s *ConstructAPI) ConstructionCombine( if rosettaError != nil { return nil, rosettaError } - if wrappedTransaction.From == nil || types.Hash(wrappedTransaction.From) != types.Hash(sigAccountID) { + if wrappedTransaction.From == nil || wrappedTransaction.From.Address != sigAccountID.Address { return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ "message": "signer public key does not match unsigned transaction's sender", }) @@ -239,7 +254,7 @@ func (s *ConstructAPI) ConstructionCombine( senderAddress, err := signedTx.SenderAddress() if err != nil { return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ - "message": errors.WithMessage(err, "unable to get sender address with signed transaction").Error(), + "message": errors.WithMessage(err, "bad signature payload").Error(), }) } if *sigAddress != senderAddress { diff --git a/rosetta/services/construction_parse.go b/rosetta/services/construction_parse.go index 259ff0156..69212530e 100644 --- a/rosetta/services/construction_parse.go +++ b/rosetta/services/construction_parse.go @@ -2,6 +2,7 @@ package services import ( "context" + "fmt" "github.com/coinbase/rosetta-sdk-go/types" "github.com/pkg/errors" @@ -21,6 +22,11 @@ func (s *ConstructAPI) ConstructionParse( if rosettaError != nil { return nil, rosettaError } + if tx.ShardID() != s.hmy.ShardID { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": fmt.Sprintf("transaction is for shard %v != shard %v", tx.ShardID(), s.hmy.ShardID), + }) + } if request.Signed { return parseSignedTransaction(ctx, wrappedTransaction, tx) } @@ -37,11 +43,17 @@ func parseUnsignedTransaction( }) } + if _, err := getAddress(wrappedTransaction.From); err != nil { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": err.Error(), + }) + } + // TODO (dm): implement intended receipt for staking transactions intendedReceipt := &hmyTypes.Receipt{ GasUsed: tx.Gas(), } - formattedTx, rosettaError := FormatTransaction(tx, intendedReceipt) + formattedTx, rosettaError := FormatTransaction(tx, intendedReceipt, wrappedTransaction.ContractCode) if rosettaError != nil { return nil, rosettaError } @@ -52,7 +64,7 @@ func parseUnsignedTransaction( foundSender := false operations := formattedTx.Operations for _, op := range operations { - if types.Hash(op.Account) == types.Hash(tempAccID) { + if op.Account.Address == tempAccID.Address { foundSender = true op.Account = wrappedTransaction.From } @@ -82,21 +94,21 @@ func parseSignedTransaction( intendedReceipt := &hmyTypes.Receipt{ GasUsed: tx.Gas(), } - formattedTx, rosettaError := FormatTransaction(tx, intendedReceipt) + formattedTx, rosettaError := FormatTransaction(tx, intendedReceipt, wrappedTransaction.ContractCode) if rosettaError != nil { return nil, rosettaError } sender, err := tx.SenderAddress() if err != nil { return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ - "message": errors.WithMessage(err, "unable to get sender address, invalid signed transaction"), + "message": errors.WithMessage(err, "unable to get sender address for signed transaction").Error(), }) } senderID, rosettaError := newAccountIdentifier(sender) if rosettaError != nil { return nil, rosettaError } - if types.Hash(senderID) != types.Hash(wrappedTransaction.From) { + if senderID.Address != wrappedTransaction.From.Address { return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ "message": "wrapped transaction sender/from does not match transaction signer", }) diff --git a/rosetta/services/construction_parse_test.go b/rosetta/services/construction_parse_test.go index 1c7ed3c6e..7b4714117 100644 --- a/rosetta/services/construction_parse_test.go +++ b/rosetta/services/construction_parse_test.go @@ -38,7 +38,7 @@ func TestParseUnsignedTransaction(t *testing.T) { refTestReceipt := &hmytypes.Receipt{ GasUsed: testTx.Gas(), } - refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt) + refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, []byte{}) if rosettaError != nil { t.Fatal(rosettaError) } @@ -101,7 +101,7 @@ func TestParseSignedTransaction(t *testing.T) { refTestReceipt := &hmytypes.Receipt{ GasUsed: testTx.Gas(), } - refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt) + refFormattedTx, rosettaError := FormatTransaction(testTx, refTestReceipt, []byte{}) if rosettaError != nil { t.Fatal(rosettaError) } diff --git a/rosetta/services/construction_submit.go b/rosetta/services/construction_submit.go index ec73b5a61..3276ff5b5 100644 --- a/rosetta/services/construction_submit.go +++ b/rosetta/services/construction_submit.go @@ -2,6 +2,7 @@ package services import ( "context" + "fmt" "github.com/coinbase/rosetta-sdk-go/types" "github.com/pkg/errors" @@ -27,6 +28,11 @@ func (s *ConstructAPI) ConstructionHash( "message": "nil transaction", }) } + if tx.ShardID() != s.hmy.ShardID { + return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ + "message": fmt.Sprintf("transaction is for shard %v != shard %v", tx.ShardID(), s.hmy.ShardID), + }) + } return &types.TransactionIdentifierResponse{ TransactionIdentifier: &types.TransactionIdentifier{Hash: tx.Hash().String()}, }, nil @@ -48,6 +54,11 @@ func (s *ConstructAPI) ConstructionSubmit( "message": "nil wrapped transaction or nil unwrapped transaction", }) } + if tx.ShardID() != s.hmy.ShardID { + return nil, common.NewError(common.InvalidTransactionConstructionError, 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 { diff --git a/rosetta/services/mempool.go b/rosetta/services/mempool.go index 54b525280..c46895e3f 100644 --- a/rosetta/services/mempool.go +++ b/rosetta/services/mempool.go @@ -64,7 +64,6 @@ func (s *MempoolAPI) MempoolTransaction( return nil, &common.TransactionNotFoundError } - var nilAddress ethCommon.Address senderAddr, _ := poolTx.SenderAddress() estLog := &hmyTypes.Log{ Address: senderAddr, @@ -73,6 +72,7 @@ func (s *MempoolAPI) MempoolTransaction( BlockNumber: s.hmy.CurrentBlock().NumberU64(), } + // Contract related information for pending transactions is not reported estReceipt := &hmyTypes.Receipt{ PostState: []byte{}, Status: hmyTypes.ReceiptStatusSuccessful, // Assume transaction will succeed @@ -80,11 +80,11 @@ func (s *MempoolAPI) MempoolTransaction( Bloom: [256]byte{}, Logs: []*hmyTypes.Log{estLog}, TxHash: poolTx.Hash(), - ContractAddress: nilAddress, // ContractAddress is only for smart contract creation & can not be determined until transaction is finalized + ContractAddress: ethCommon.Address{}, GasUsed: poolTx.Gas(), } - respTx, err := FormatTransaction(poolTx, estReceipt) + respTx, err := FormatTransaction(poolTx, estReceipt, []byte{}) if err != nil { return nil, err } diff --git a/rosetta/services/tx_construction.go b/rosetta/services/tx_construction.go index 887486754..14177c269 100644 --- a/rosetta/services/tx_construction.go +++ b/rosetta/services/tx_construction.go @@ -18,8 +18,10 @@ type TransactionMetadata struct { CrossShardIdentifier *types.TransactionIdentifier `json:"cross_shard_transaction_identifier,omitempty"` ToShardID *uint32 `json:"to_shard,omitempty"` FromShardID *uint32 `json:"from_shard,omitempty"` - Data *string `json:"data,omitempty"` - Logs []*hmyTypes.Log `json:"logs,omitempty"` + // ContractAccountIdentifier is the 'main' contract account ID associated with a transaction + ContractAccountIdentifier *types.AccountIdentifier `json:"contract_account_identifier,omitempty"` + Data *string `json:"data,omitempty"` + Logs []*hmyTypes.Log `json:"logs,omitempty"` } // UnmarshalFromInterface .. @@ -55,7 +57,7 @@ func ConstructTransaction( var tx hmyTypes.PoolTransaction switch components.Type { - case common.CrossShardTransferNativeOperation: + case common.NativeCrossShardTransferOperation: if tx, rosettaError = constructCrossShardTransaction(components, metadata, sourceShardID); rosettaError != nil { return nil, rosettaError } @@ -63,7 +65,7 @@ func ConstructTransaction( if tx, rosettaError = constructContractCreationTransaction(components, metadata, sourceShardID); rosettaError != nil { return nil, rosettaError } - case common.TransferNativeOperation: + case common.NativeTransferOperation: if tx, rosettaError = constructPlainTransaction(components, metadata, sourceShardID); rosettaError != nil { return nil, rosettaError } diff --git a/rosetta/services/tx_construction_test.go b/rosetta/services/tx_construction_test.go index 966f1e17c..e8e8c9455 100644 --- a/rosetta/services/tx_construction_test.go +++ b/rosetta/services/tx_construction_test.go @@ -27,7 +27,7 @@ func TestConstructPlainTransaction(t *testing.T) { refDataBytes := []byte{0xEE, 0xEE, 0xEE} refData := hexutil.Encode(refDataBytes) refComponents := &OperationComponents{ - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, From: refFrom, To: refTo, Amount: big.NewInt(12000), @@ -116,7 +116,7 @@ func TestConstructPlainTransaction(t *testing.T) { // test invalid receiver _, rosettaError = constructPlainTransaction(&OperationComponents{ - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, From: refFrom, To: nil, Amount: big.NewInt(12000), @@ -126,7 +126,7 @@ func TestConstructPlainTransaction(t *testing.T) { t.Error("expected error") } _, rosettaError = constructPlainTransaction(&OperationComponents{ - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, From: refFrom, To: &types.AccountIdentifier{ Address: "", @@ -140,7 +140,7 @@ func TestConstructPlainTransaction(t *testing.T) { // test valid nil sender _, rosettaError = constructPlainTransaction(&OperationComponents{ - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, From: nil, To: refTo, Amount: big.NewInt(12000), @@ -179,7 +179,7 @@ func TestConstructCrossShardTransaction(t *testing.T) { refDataBytes := []byte{0xEE, 0xEE, 0xEE} refData := hexutil.Encode(refDataBytes) refComponents := &OperationComponents{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, From: refFrom, To: refTo, Amount: big.NewInt(12000), @@ -238,7 +238,7 @@ func TestConstructCrossShardTransaction(t *testing.T) { // test invalid receiver _, rosettaError = constructCrossShardTransaction(&OperationComponents{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, From: refFrom, To: nil, Amount: big.NewInt(12000), @@ -248,7 +248,7 @@ func TestConstructCrossShardTransaction(t *testing.T) { t.Error("expected error") } _, rosettaError = constructCrossShardTransaction(&OperationComponents{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, From: refFrom, To: &types.AccountIdentifier{ Address: "", @@ -262,7 +262,7 @@ func TestConstructCrossShardTransaction(t *testing.T) { // test valid nil sender _, rosettaError = constructCrossShardTransaction(&OperationComponents{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, From: nil, To: refTo, Amount: big.NewInt(12000), @@ -432,7 +432,7 @@ func TestConstructTransaction(t *testing.T) { // test valid cross-shard transfer (negative test cases are in TestConstructCrossShardTransaction) generalTx, rosettaError := ConstructTransaction(&OperationComponents{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, From: refFrom, To: refTo, Amount: big.NewInt(12000), @@ -485,7 +485,7 @@ func TestConstructTransaction(t *testing.T) { // test valid transfer (negative test cases are in TestConstructPlainTransaction) generalTx, rosettaError = ConstructTransaction(&OperationComponents{ - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, From: refFrom, To: refTo, Amount: big.NewInt(12000), @@ -512,7 +512,7 @@ func TestConstructTransaction(t *testing.T) { // test invalid sender shard badShard := refShard + refToShard + 1 _, rosettaError = ConstructTransaction(&OperationComponents{ - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, From: refFrom, To: refTo, Amount: big.NewInt(12000), diff --git a/rosetta/services/tx_format.go b/rosetta/services/tx_format.go index 26b731d33..9995e346e 100644 --- a/rosetta/services/tx_format.go +++ b/rosetta/services/tx_format.go @@ -22,10 +22,10 @@ var ( // FormatTransaction for staking, cross-shard sender, and plain transactions func FormatTransaction( - tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt, + tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt, contractCode []byte, ) (fmtTx *types.Transaction, rosettaError *types.Error) { var operations []*types.Operation - var isCrossShard, isStaking bool + var isCrossShard, isStaking, isContractCreation bool var toShard uint32 switch tx.(type) { @@ -36,7 +36,7 @@ func FormatTransaction( if rosettaError != nil { return nil, rosettaError } - isCrossShard = false + isCrossShard, isContractCreation = false, false toShard = stakingTx.ShardID() case *hmytypes.Transaction: isStaking = false @@ -46,6 +46,7 @@ func FormatTransaction( return nil, rosettaError } isCrossShard = plainTx.ShardID() != plainTx.ToShardID() + isContractCreation = tx.To() == nil toShard = plainTx.ToShardID() default: return nil, common.NewError(common.CatchAllError, map[string]interface{}{ @@ -57,6 +58,20 @@ func FormatTransaction( // Set all possible metadata var txMetadata TransactionMetadata + if isContractCreation { + contractID, rosettaError := newAccountIdentifier(receipt.ContractAddress) + if rosettaError != nil { + return nil, rosettaError + } + txMetadata.ContractAccountIdentifier = contractID + } else if len(contractCode) > 0 && tx.To() != nil { + // Contract code was found, so receiving account must be the contract address + contractID, rosettaError := newAccountIdentifier(*tx.To()) + if rosettaError != nil { + return nil, rosettaError + } + txMetadata.ContractAccountIdentifier = contractID + } if isCrossShard { txMetadata.CrossShardIdentifier = txID txMetadata.ToShardID = &toShard @@ -122,7 +137,7 @@ func FormatCrossShardReceiverTransaction( OperationIdentifier: &types.OperationIdentifier{ Index: 0, // There is no gas expenditure for cross-shard transaction payout }, - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Status: common.SuccessOperationStatus.Status, Account: receiverAccountID, Amount: &types.Amount{ diff --git a/rosetta/services/tx_format_test.go b/rosetta/services/tx_format_test.go index a3944d6f2..37ccaff1b 100644 --- a/rosetta/services/tx_format_test.go +++ b/rosetta/services/tx_format_test.go @@ -81,7 +81,7 @@ func testFormatStakingTransaction( Status: hmytypes.ReceiptStatusSuccessful, GasUsed: gasUsed, } - rosettaTx, rosettaError := FormatTransaction(tx, receipt) + rosettaTx, rosettaError := FormatTransaction(tx, receipt, []byte{}) if rosettaError != nil { t.Fatal(rosettaError) } @@ -136,7 +136,7 @@ func testFormatPlainTransaction( Status: hmytypes.ReceiptStatusSuccessful, GasUsed: gasUsed, } - rosettaTx, rosettaError := FormatTransaction(tx, receipt) + rosettaTx, rosettaError := FormatTransaction(tx, receipt, []byte{}) if rosettaError != nil { t.Fatal(rosettaError) } @@ -152,7 +152,7 @@ func testFormatPlainTransaction( if rosettaTx.Operations[0].Type != common.ExpendGasOperation { t.Error("Expected 1st operation to be gas") } - if rosettaTx.Operations[1].Type != common.TransferNativeOperation { + if rosettaTx.Operations[1].Type != common.NativeTransferOperation { t.Error("Expected 2nd operation to transfer related") } if rosettaTx.Operations[1].Metadata != nil { @@ -324,7 +324,7 @@ func testFormatCrossShardSenderTransaction( Status: hmytypes.ReceiptStatusSuccessful, GasUsed: gasUsed, } - rosettaTx, rosettaError := FormatTransaction(tx, receipt) + rosettaTx, rosettaError := FormatTransaction(tx, receipt, []byte{}) if rosettaError != nil { t.Fatal(rosettaError) } @@ -340,7 +340,7 @@ func testFormatCrossShardSenderTransaction( if rosettaTx.Operations[0].Type != common.ExpendGasOperation { t.Error("Expected 1st operation to be gas") } - if rosettaTx.Operations[1].Type != common.CrossShardTransferNativeOperation { + if rosettaTx.Operations[1].Type != common.NativeCrossShardTransferOperation { t.Error("Expected 2nd operation to cross-shard transfer related") } if reflect.DeepEqual(rosettaTx.Operations[1].Metadata, map[string]interface{}{}) { @@ -396,7 +396,7 @@ func TestFormatCrossShardReceiverTransaction(t *testing.T) { OperationIdentifier: &types.OperationIdentifier{ Index: 0, // There is no gas expenditure for cross-shard payout }, - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Status: common.SuccessOperationStatus.Status, Account: receiverAccID, Amount: &types.Amount{ diff --git a/rosetta/services/tx_operation.go b/rosetta/services/tx_operation.go index 4d65d90d1..682e6645c 100644 --- a/rosetta/services/tx_operation.go +++ b/rosetta/services/tx_operation.go @@ -210,7 +210,7 @@ func newTransferNativeOperations( receiverAddress := *tx.To() // Common elements - opType := common.TransferNativeOperation + opType := common.NativeTransferOperation opStatus := common.SuccessOperationStatus.Status if receipt.Status == hmytypes.ReceiptStatusFailed { if len(tx.Data()) > 0 { @@ -309,7 +309,7 @@ func newCrossShardSenderTransferNativeOperations( RelatedOperations: []*types.OperationIdentifier{ startingOperationID, }, - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Status: common.SuccessOperationStatus.Status, Account: senderAccountID, Amount: &types.Amount{ @@ -326,10 +326,7 @@ func newContractCreationNativeOperations( startingOperationID *types.OperationIdentifier, tx *hmytypes.Transaction, txReceipt *hmytypes.Receipt, senderAddress ethcommon.Address, ) ([]*types.Operation, *types.Error) { - senderAccountID, rosettaError := newAccountIdentifier(senderAddress) - if rosettaError != nil { - return nil, rosettaError - } + // TODO: correct the contract creation transaction... // Set execution status as necessary status := common.SuccessOperationStatus.Status @@ -341,24 +338,50 @@ func newContractCreationNativeOperations( return nil, rosettaError } + // Subtraction operation elements + subOperationID := &types.OperationIdentifier{ + Index: startingOperationID.Index + 1, + } + subRelatedID := []*types.OperationIdentifier{ + startingOperationID, + } + subAccountID, rosettaError := newAccountIdentifier(senderAddress) + if rosettaError != nil { + return nil, rosettaError + } + subAmount := &types.Amount{ + Value: negativeBigValue(tx.Value()), + Currency: &common.NativeCurrency, + } + + // Addition operation elements + addOperationID := &types.OperationIdentifier{ + Index: subOperationID.Index + 1, + } + addRelatedID := []*types.OperationIdentifier{ + subOperationID, + } + addAmount := &types.Amount{ + Value: tx.Value().String(), + Currency: &common.NativeCurrency, + } + return []*types.Operation{ { - OperationIdentifier: &types.OperationIdentifier{ - Index: startingOperationID.Index + 1, - }, - RelatedOperations: []*types.OperationIdentifier{ - startingOperationID, - }, - Type: common.ContractCreationOperation, - Status: status, - Account: senderAccountID, - Amount: &types.Amount{ - Value: negativeBigValue(tx.Value()), - Currency: &common.NativeCurrency, - }, - Metadata: map[string]interface{}{ - "contract_address": contractAddressID, - }, + OperationIdentifier: subOperationID, + RelatedOperations: subRelatedID, + Type: common.ContractCreationOperation, + Status: status, + Account: subAccountID, + Amount: subAmount, + }, + { + OperationIdentifier: addOperationID, + RelatedOperations: addRelatedID, + Type: common.ContractCreationOperation, + Status: status, + Account: contractAddressID, + Amount: addAmount, }, }, nil } diff --git a/rosetta/services/tx_operation_components.go b/rosetta/services/tx_operation_components.go index 405aaa899..285af55a4 100644 --- a/rosetta/services/tx_operation_components.go +++ b/rosetta/services/tx_operation_components.go @@ -57,7 +57,7 @@ func GetOperationComponents( return getTransferOperationComponents(operations) } switch operations[0].Type { - case common.CrossShardTransferNativeOperation: + case common.NativeCrossShardTransferOperation: return getCrossShardOperationComponents(operations[0]) case common.ContractCreationOperation: return getContractCreationOperationComponents(operations[0]) @@ -78,7 +78,7 @@ func getTransferOperationComponents( }) } op0, op1 := operations[0], operations[1] - if op0.Type != common.TransferNativeOperation || op1.Type != common.TransferNativeOperation { + if op0.Type != common.NativeTransferOperation || op1.Type != common.NativeTransferOperation { return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ "message": "invalid operation type(s) for same shard transfer", }) @@ -187,7 +187,7 @@ func getCrossShardOperationComponents( "message": "operation must have account sender/from & receiver/to identifiers for cross shard transfer", }) } - if types.Hash(operation.Account) != types.Hash(components.From) { + if operation.Account.Address != components.From.Address { return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{ "message": "operation account identifier does not match sender/from identifiers for cross shard transfer", }) diff --git a/rosetta/services/tx_operation_components_test.go b/rosetta/services/tx_operation_components_test.go index dff96ad3f..416c3ecc4 100644 --- a/rosetta/services/tx_operation_components_test.go +++ b/rosetta/services/tx_operation_components_test.go @@ -124,7 +124,7 @@ func TestGetCrossShardOperationComponents(t *testing.T) { // test valid operations refOperation := &types.Operation{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Amount: refAmount, Account: refFrom, Metadata: refMetadataMap, @@ -148,7 +148,7 @@ func TestGetCrossShardOperationComponents(t *testing.T) { // test nil amount _, rosettaError = getCrossShardOperationComponents(&types.Operation{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Amount: nil, Account: refFrom, Metadata: refMetadataMap, @@ -159,7 +159,7 @@ func TestGetCrossShardOperationComponents(t *testing.T) { // test positive amount _, rosettaError = getCrossShardOperationComponents(&types.Operation{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Amount: &types.Amount{ Value: "12000", Currency: &common.NativeCurrency, @@ -173,7 +173,7 @@ func TestGetCrossShardOperationComponents(t *testing.T) { // test different/unsupported currency _, rosettaError = getCrossShardOperationComponents(&types.Operation{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Amount: &types.Amount{ Value: "-12000", Currency: &types.Currency{ @@ -190,7 +190,7 @@ func TestGetCrossShardOperationComponents(t *testing.T) { // test nil account _, rosettaError = getCrossShardOperationComponents(&types.Operation{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Amount: refAmount, Account: nil, Metadata: refMetadataMap, @@ -201,7 +201,7 @@ func TestGetCrossShardOperationComponents(t *testing.T) { // test no metadata _, rosettaError = getCrossShardOperationComponents(&types.Operation{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Amount: refAmount, Account: refFrom, }) @@ -224,7 +224,7 @@ func TestGetCrossShardOperationComponents(t *testing.T) { t.Fatal(err) } _, rosettaError = getCrossShardOperationComponents(&types.Operation{ - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Amount: refAmount, Account: refFrom, Metadata: badMetadataMap, @@ -266,7 +266,7 @@ func TestGetTransferOperationComponents(t *testing.T) { OperationIdentifier: &types.OperationIdentifier{ Index: 0, }, - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, Amount: refFromAmount, Account: refFrom, }, @@ -279,7 +279,7 @@ func TestGetTransferOperationComponents(t *testing.T) { Index: 0, }, }, - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, Amount: refToAmount, Account: refTo, }, @@ -345,20 +345,20 @@ func TestGetTransferOperationComponents(t *testing.T) { // test invalid operation refOperations[0].Type = common.ExpendGasOperation - refOperations[1].Type = common.TransferNativeOperation + refOperations[1].Type = common.NativeTransferOperation _, rosettaError = getTransferOperationComponents(refOperations) if rosettaError == nil { t.Error("expected error") } // test invalid operation sender - refOperations[0].Type = common.TransferNativeOperation + refOperations[0].Type = common.NativeTransferOperation refOperations[1].Type = common.ExpendGasOperation _, rosettaError = getTransferOperationComponents(refOperations) if rosettaError == nil { t.Error("expected error") } - refOperations[1].Type = common.TransferNativeOperation + refOperations[1].Type = common.NativeTransferOperation // test nil amount refOperations[0].Amount = nil @@ -517,7 +517,7 @@ func TestGetOperationComponents(t *testing.T) { OperationIdentifier: &types.OperationIdentifier{ Index: 0, }, - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, Amount: refFromAmount, Account: refFrom, }, @@ -530,7 +530,7 @@ func TestGetOperationComponents(t *testing.T) { Index: 0, }, }, - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, Amount: refToAmount, Account: refTo, }, @@ -551,7 +551,7 @@ func TestGetOperationComponents(t *testing.T) { } _, rosettaError = GetOperationComponents([]*types.Operation{ { - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Amount: refFromAmount, Account: refFrom, Metadata: refMetadataMap, diff --git a/rosetta/services/tx_operation_test.go b/rosetta/services/tx_operation_test.go index 854ce5ce7..de29462f3 100644 --- a/rosetta/services/tx_operation_test.go +++ b/rosetta/services/tx_operation_test.go @@ -368,7 +368,7 @@ func TestNewTransferNativeOperations(t *testing.T) { Index: startingOpID.Index, }, }, - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, Status: common.ContractFailureOperationStatus.Status, Account: senderAccID, Amount: &types.Amount{ @@ -385,7 +385,7 @@ func TestNewTransferNativeOperations(t *testing.T) { Index: startingOpID.Index + 1, }, }, - Type: common.TransferNativeOperation, + Type: common.NativeTransferOperation, Status: common.ContractFailureOperationStatus.Status, Account: receiverAccID, Amount: &types.Amount{ @@ -461,7 +461,7 @@ func TestNewCrossShardSenderTransferNativeOperations(t *testing.T) { RelatedOperations: []*types.OperationIdentifier{ startingOpID, }, - Type: common.CrossShardTransferNativeOperation, + Type: common.NativeCrossShardTransferOperation, Status: common.SuccessOperationStatus.Status, Account: senderAccID, Amount: &types.Amount{ @@ -527,8 +527,22 @@ func TestNewContractCreationNativeOperations(t *testing.T) { Value: negativeBigValue(tx.Value()), Currency: &common.NativeCurrency, }, - Metadata: map[string]interface{}{ - "contract_address": contractAddressID, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: startingOpID.Index + 2, + }, + RelatedOperations: []*types.OperationIdentifier{ + { + Index: startingOpID.Index + 1, + }, + }, + Type: common.ContractCreationOperation, + Status: common.ContractFailureOperationStatus.Status, + Account: contractAddressID, + Amount: &types.Amount{ + Value: tx.Value().String(), + Currency: &common.NativeCurrency, }, }, } @@ -549,6 +563,7 @@ func TestNewContractCreationNativeOperations(t *testing.T) { // Test successful contract creation refOperations[0].Status = common.SuccessOperationStatus.Status + refOperations[1].Status = common.SuccessOperationStatus.Status receipt.Status = hmytypes.ReceiptStatusSuccessful // Indicate successful tx operations, rosettaError = newContractCreationNativeOperations(startingOpID, tx, receipt, senderAddr) if rosettaError != nil { diff --git a/rpc/contract.go b/rpc/contract.go index 4cc3df90b..43d9fb6df 100644 --- a/rpc/contract.go +++ b/rpc/contract.go @@ -50,7 +50,7 @@ func (s *PublicContractService) Call( blockNum := blockNumber.EthBlockNumber() // Execute call - result, err := doCall(ctx, s.hmy, args, blockNum, vm.Config{}, CallTimeout, s.hmy.RPCGasCap) + result, err := DoEVMCall(ctx, s.hmy, args, blockNum, vm.Config{}, CallTimeout, s.hmy.RPCGasCap) if err != nil { return nil, err } @@ -99,8 +99,8 @@ func (s *PublicContractService) GetStorageAt( return res[:], state.Error() } -// docall executes an EVM call -func doCall( +// DoEVMCall executes an EVM call +func DoEVMCall( ctx context.Context, hmy *hmy.Harmony, args CallArgs, blockNum rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int, ) (core.ExecutionResult, error) { diff --git a/rpc/transaction.go b/rpc/transaction.go index aa78166a0..f95e6589b 100644 --- a/rpc/transaction.go +++ b/rpc/transaction.go @@ -725,7 +725,7 @@ func EstimateGas( executable := func(gas uint64) bool { args.Gas = (*hexutil.Uint64)(&gas) - result, err := doCall(ctx, hmy, args, blockNum, vm.Config{}, 0, big.NewInt(int64(max))) + result, err := DoEVMCall(ctx, hmy, args, blockNum, vm.Config{}, 0, big.NewInt(int64(max))) if err != nil || result.VMErr == vm.ErrCodeStoreOutOfGas || result.VMErr == vm.ErrOutOfGas { return false } diff --git a/shard/committee/assignment.go b/shard/committee/assignment.go index a2ee08c3c..b9507f0d2 100644 --- a/shard/committee/assignment.go +++ b/shard/committee/assignment.go @@ -227,7 +227,7 @@ func IsEligibleForEPoSAuction(snapshot *staking.ValidatorSnapshot, validator *st } } -// Blockchain is a subset of Engine.Blockchain, just enough to do assignment +// ChainReader is a subset of Engine.Blockchain, just enough to do assignment type ChainReader interface { // ReadShardState retrieves sharding state given the epoch number. // This api reads the shard state cached or saved on the chaindb.