pull/3405/head
Rongjian Lan 4 years ago
commit bb4caa2078
  1. 5
      .travis.yml
  2. 20
      Makefile
  3. 37
      README.md
  4. 5
      cmd/harmony/config.go
  5. 3
      cmd/harmony/default.go
  6. 21
      cmd/harmony/flags.go
  7. 25
      cmd/harmony/flags_test.go
  8. 2
      cmd/harmony/main.go
  9. 2
      consensus/checks.go
  10. 8
      consensus/consensus_service.go
  11. 7
      consensus/consensus_v2.go
  12. 26
      consensus/quorum/quorum.go
  13. 44
      consensus/view_change.go
  14. 3
      consensus/view_change_msg.go
  15. 4
      consensus/view_change_test.go
  16. 112
      hmy/blockchain.go
  17. 5
      hmy/hmy.go
  18. 2
      internal/configs/node/config.go
  19. 29
      rosetta/services/block_special.go
  20. 41
      rosetta/services/tx_format.go
  21. 95
      rosetta/services/tx_format_test.go
  22. 19
      rpc/rpc.go
  23. 4
      scripts/node.sh
  24. 0
      scripts/travis_go_checker.sh
  25. 5
      scripts/travis_rosetta_checker.sh
  26. 0
      scripts/travis_rpc_checker.sh
  27. 4
      test/all.sh
  28. 8
      test/deploy.sh
  29. 4
      test/go.sh
  30. 4
      test/rosetta.sh
  31. 29
      test/rpc.sh

@ -6,8 +6,9 @@ go:
go_import_path: github.com/harmony-one/harmony
env:
- TEST="make"
- TEST="./scripts/travis_checker.sh"
- TEST="./scripts/travis_node_checker.sh"
- TEST="bash ./scripts/travis_go_checker.sh"
- TEST="bash ./scripts/travis_rpc_checker.sh"
- TEST="bash ./scripts/travis_rosetta_checker.sh"
install:
# default working directory with source code is automatically set to
# /home/travis/gopath/src/github.com/harmony-one/harmony

@ -29,8 +29,10 @@ help:
@echo "distclean - remove node files & logs created by localnet, and all libs"
@echo "test - run the entire test suite (go test & Node API test)"
@echo "test-go - run the go test (with go lint, fmt, imports, mod, and generate checks)"
@echo "test-api - run the Node API test"
@echo "test-api-attach - attach onto the Node API testing docker container for inspection"
@echo "test-rpc - run the rpc tests"
@echo "test-rpc-attach - attach onto the rpc testing docker container for inspection"
@echo "test-rosetta - run the rosetta tests"
@echo "test-rosetta-attach - attach onto the rosetta testing docker container for inspection"
@echo "linux_static - static build the harmony binary & bootnode along with the MCL & BLS libs (for linux)"
@echo "arm_static - static build the harmony binary & bootnode on ARM64 platform"
@echo "rpm - build a harmony RPM pacakge"
@ -82,11 +84,17 @@ test:
test-go:
bash ./test/go.sh
test-api:
bash ./test/api.sh run
test-rpc:
bash ./test/rpc.sh run
test-api-attach:
bash ./test/api.sh attach
test-rpc-attach:
bash ./test/rpc.sh attach
test-rosetta:
bash ./test/rosetta.sh run
test-rosetta-attach:
bash ./test/rosetta.sh attach
linux_static:
make -C $(TOP)/mcl -j8

@ -142,25 +142,36 @@ make debug-kill
To keep things consistent, we have a docker image to run all tests. **These are the same tests ran on the pull request checks**.
Note that all testing docker container binds a couple of ports to the host machine for your convince. The ports are:
* `9500` - Shard 0 RPC for a validator
* `9501` - Shard 1 RPC for a validator
* `9599` - Shard 0 RPC for an explorer
* `9598` - Shard 1 RPC for an explorer
* `9799` - Shard 0 Rosetta (for an explorer)
* `9798` - Shard 1 Rosetta (for an explorer)
* `9899` - Shard 0 WS for an explorer
* `9898` - Shard 1 WS for an explorer
> This allows you to use curl, hmy CLI, postman, rosetta-cli, etc... on your host machine to play with or probe the localnet that was used for the test.
### Go tests
To run this test do:
To run this test, do:
```bash
make test-go
```
This test runs the go tests along with go lint, go fmt, go imports, go mod, and go generate checks.
### API tests
To run this test do:
### RPC tests
To run this test, do:
```bash
make test-api
make test-rpc
```
This test starts a localnet (within the Docker container), **ensures it reaches consensus**, and runs a series of tests to ensure correct Node API behavior.
This test starts a localnet (within the Docker container), **ensures it reaches a consensus**, and runs a series of tests to ensure correct RPC behavior.
This test also acts as a preliminary integration test (more through tests are done on the testnets).
> The tests ran by this command can be found [here](https://github.com/harmony-one/harmony-test/tree/master/localnet).
If you wish to debug further with the localnet after the tests are done, open a new shell and run:
```bash
make test-api-attach
make test-rpc-attach
```
> This will open a shell in the docker container that is running the Node API tests.
>
@ -169,6 +180,20 @@ make test-api-attach
> the current block height of localnet. Reference the documentation for the CLI [here](https://docs.harmony.one/home/wallets/harmony-cli)
> for more details & commands.
### Rosetta tests
To run this test, do:
```bash
make test-rosetta
```
This test starts a localnet (within the Docker container), **ensures it reaches a consensus**, and runs the Construction & Data API checks using the [rosetta-cli](https://github.com/coinbase/rosetta-cli).
This test also acts as a preliminary integration test (more through tests are done on the testnets).
> The config for this test can be found [here](https://github.com/harmony-one/harmony-test/blob/master/localnet/configs/localnet_rosetta_test_s0.json) & [here](https://github.com/harmony-one/harmony-test/blob/master/localnet/configs/localnet_rosetta_test_s1.json)
Similar to the RPC tests, if you wish to debug further with the localnet after the tests are done, open a new shell and run:
```bash
make test-rosetta-attach
```
## License
Harmony is licensed under the MIT License. See [`LICENSE`](LICENSE) file for

@ -23,6 +23,7 @@ type harmonyConfig struct {
P2P p2pConfig
HTTP httpConfig
WS wsConfig
RPCOpt rpcOptConfig
BLSKeys blsConfig
TxPool txPoolConfig
Pprof pprofConfig
@ -117,6 +118,10 @@ type wsConfig struct {
Port int
}
type rpcOptConfig struct {
DebugEnabled bool // Enables PrivateDebugService APIs, including the EVM tracer
}
type devnetConfig struct {
NumShards int
ShardSize int

@ -36,6 +36,9 @@ var defaultConfig = harmonyConfig{
IP: "127.0.0.1",
Port: nodeconfig.DefaultWSPort,
},
RPCOpt: rpcOptConfig{
DebugEnabled: false,
},
BLSKeys: blsConfig{
KeyDir: "./.hmy/blskeys",
KeyFiles: []string{},

@ -59,6 +59,10 @@ var (
wsPortFlag,
}
rpcOptFlags = []cli.Flag{
rpcDebugEnabledFlag,
}
blsFlags = append(newBLSFlags, legacyBLSFlags...)
newBLSFlags = []cli.Flag{
@ -236,6 +240,7 @@ func getRootFlags() []cli.Flag {
flags = append(flags, p2pFlags...)
flags = append(flags, httpFlags...)
flags = append(flags, wsFlags...)
flags = append(flags, rpcOptFlags...)
flags = append(flags, blsFlags...)
flags = append(flags, consensusFlags...)
flags = append(flags, txPoolFlags...)
@ -503,6 +508,22 @@ func applyWSFlags(cmd *cobra.Command, config *harmonyConfig) {
}
}
// rpc opt flags
var (
rpcDebugEnabledFlag = cli.BoolFlag{
Name: "rpc.debug",
Usage: "enable private debug apis",
DefValue: defaultConfig.RPCOpt.DebugEnabled,
Hidden: true,
}
)
func applyRPCOptFlags(cmd *cobra.Command, config *harmonyConfig) {
if cli.IsFlagChanged(cmd, rpcDebugEnabledFlag) {
config.RPCOpt.DebugEnabled = cli.GetBoolFlagValue(cmd, rpcDebugEnabledFlag)
}
}
// bls flags
var (
blsDirFlag = cli.StringFlag{

@ -483,6 +483,31 @@ func TestWSFlags(t *testing.T) {
}
}
func TestRPCOptFlags(t *testing.T) {
tests := []struct {
args []string
expConfig rpcOptConfig
}{
{
args: []string{"--rpc.debug"},
expConfig: rpcOptConfig{
DebugEnabled: true,
},
},
}
for i, test := range tests {
ts := newFlagTestSuite(t, rpcOptFlags, applyRPCOptFlags)
hc, _ := ts.run(test.args)
if !reflect.DeepEqual(hc.RPCOpt, test.expConfig) {
t.Errorf("Test %v: \n\t%+v\n\t%+v", i, hc.RPCOpt, test.expConfig)
}
ts.tearDown()
}
}
func TestBLSFlags(t *testing.T) {
tests := []struct {
args []string

@ -191,6 +191,7 @@ func applyRootFlags(cmd *cobra.Command, config *harmonyConfig) {
applyP2PFlags(cmd, config)
applyHTTPFlags(cmd, config)
applyWSFlags(cmd, config)
applyRPCOptFlags(cmd, config)
applyBLSFlags(cmd, config)
applyConsensusFlags(cmd, config)
applyTxPoolFlags(cmd, config)
@ -312,6 +313,7 @@ func setupNodeAndRun(hc harmonyConfig) {
WSEnabled: hc.WS.Enabled,
WSIp: hc.WS.IP,
WSPort: hc.WS.Port,
DebugEnabled: hc.RPCOpt.DebugEnabled,
}
if nodeConfig.ShardID != shard.BeaconChainShardID {
utils.Logger().Info().

@ -92,7 +92,7 @@ func (consensus *Consensus) onAnnounceSanityChecks(recvMsg *FBFTMessage) bool {
"[OnAnnounce] Already in ViewChanging mode, conflicing announce, doing noop",
)
} else {
consensus.startViewChange(consensus.GetCurBlockViewID() + 1)
consensus.startViewChange()
}
}
consensus.getLogger().Debug().

@ -81,7 +81,7 @@ func (consensus *Consensus) UpdatePublicKeys(pubKeys []bls_cosi.PublicKeyWrapper
consensus.Decider.UpdateParticipants(pubKeys)
utils.Logger().Info().Msg("My Committee updated")
for i := range pubKeys {
utils.Logger().Debug().
utils.Logger().Info().
Int("index", i).
Str("BLSPubKey", pubKeys[i].Bytes.Hex()).
Msg("Member")
@ -92,6 +92,9 @@ func (consensus *Consensus) UpdatePublicKeys(pubKeys []bls_cosi.PublicKeyWrapper
consensus.LeaderPubKey = &allKeys[0]
utils.Logger().Info().
Str("info", consensus.LeaderPubKey.Bytes.Hex()).Msg("My Leader")
} else {
utils.Logger().Error().
Msg("[UpdatePublicKeys] Participants is empty")
}
consensus.pubKeyLock.Unlock()
// reset states after update public keys
@ -551,6 +554,9 @@ func (consensus *Consensus) selfCommit(payload []byte) error {
consensus.mutex.Lock()
defer consensus.mutex.Unlock()
// Have to keep the block hash so the leader can finish the commit phase of prepared block
consensus.ResetState()
copy(consensus.blockHash[:], blockHash[:])
consensus.switchPhase("selfCommit", FBFTCommit)
consensus.aggregatedPrepareSig = aggSig

@ -142,7 +142,6 @@ func (consensus *Consensus) finalCommit() {
return
}
// if leader success finalize the block, send committed message to validators
// TODO: once leader rotation is implemented, leader who is about to be switched out
// needs to send the committed message immediately so the next leader can
@ -272,13 +271,11 @@ func (consensus *Consensus) Start(
}
if k != timeoutViewChange {
consensus.getLogger().Warn().Msg("[ConsensusMainLoop] Ops Consensus Timeout!!!")
viewID := consensus.GetCurBlockViewID()
consensus.startViewChange(viewID + 1)
consensus.startViewChange()
break
} else {
consensus.getLogger().Warn().Msg("[ConsensusMainLoop] Ops View Change Timeout!!!")
viewID := consensus.GetViewChangingID()
consensus.startViewChange(viewID + 1)
consensus.startViewChange()
break
}
}

@ -2,7 +2,6 @@ package quorum
import (
"fmt"
"github.com/harmony-one/harmony/internal/configs/sharding"
"math/big"
"github.com/harmony-one/harmony/crypto/bls"
@ -11,6 +10,7 @@ import (
bls_core "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/consensus/votepower"
bls_cosi "github.com/harmony-one/harmony/crypto/bls"
shardingconfig "github.com/harmony-one/harmony/internal/configs/sharding"
"github.com/harmony-one/harmony/multibls"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
@ -216,7 +216,29 @@ func (s *cIdentities) NthNext(pubKey *bls.PublicKeyWrapper, next int) (bool, *bl
if idx != -1 {
found = true
}
idx = (idx + next) % int(s.ParticipantsCount())
numNodes := int(s.ParticipantsCount())
// sanity check to avoid out of bound access
if numNodes <= 0 || numNodes > len(s.publicKeys) {
numNodes = len(s.publicKeys)
}
idx = (idx + next) % numNodes
return found, &s.publicKeys[idx]
}
// NthNextHmy return the Nth next pubkey of Harmony nodes, next can be negative number
func (s *cIdentities) NthNextHmy(instance shardingconfig.Instance, pubKey *bls.PublicKeyWrapper, next int) (bool, *bls.PublicKeyWrapper) {
found := false
idx := s.IndexOf(pubKey.Bytes)
if idx != -1 {
found = true
}
numNodes := instance.NumHarmonyOperatedNodesPerShard()
// sanity check to avoid out of bound access
if numNodes <= 0 || numNodes > len(s.publicKeys) {
numNodes = len(s.publicKeys)
}
idx = (idx + next) % numNodes
return found, &s.publicKeys[idx]
}

@ -1,7 +1,6 @@
package consensus
import (
"github.com/harmony-one/harmony/shard"
"math/big"
"sync"
"time"
@ -14,6 +13,7 @@ import (
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/shard"
"github.com/pkg/errors"
)
@ -219,21 +219,20 @@ func createTimeout() map[TimeoutType]*utils.Timeout {
return timeouts
}
// startViewChange send a new view change
// the viewID is the current viewID
func (consensus *Consensus) startViewChange(viewID uint64) {
// startViewChange start the view change process
func (consensus *Consensus) startViewChange() {
if consensus.disableViewChange {
return
}
consensus.consensusTimeout[timeoutConsensus].Stop()
consensus.consensusTimeout[timeoutBootstrap].Stop()
consensus.current.SetMode(ViewChanging)
consensus.SetViewChangingID(viewID)
consensus.LeaderPubKey = consensus.getNextLeaderKey(viewID)
nextViewID, duration := consensus.getNextViewID()
consensus.SetViewChangingID(nextViewID)
consensus.LeaderPubKey = consensus.getNextLeaderKey(nextViewID)
duration := consensus.current.GetViewChangeDuraion()
consensus.getLogger().Warn().
Uint64("viewID", viewID).
Uint64("nextViewID", nextViewID).
Uint64("viewChangingID", consensus.GetViewChangingID()).
Dur("timeoutDuration", duration).
Str("NextLeader", consensus.LeaderPubKey.Bytes.Hex()).
@ -243,15 +242,15 @@ func (consensus *Consensus) startViewChange(viewID uint64) {
defer consensus.consensusTimeout[timeoutViewChange].Start()
// update the dictionary key if the viewID is first time received
consensus.vc.AddViewIDKeyIfNotExist(viewID, consensus.Decider.Participants())
consensus.vc.AddViewIDKeyIfNotExist(nextViewID, consensus.Decider.Participants())
// init my own payload
if err := consensus.vc.InitPayload(
consensus.FBFTLog,
viewID,
nextViewID,
consensus.blockNum,
consensus.priKey); err != nil {
consensus.getLogger().Error().Err(err).Msg("Init Payload Error")
consensus.getLogger().Error().Err(err).Msg("[startViewChange] Init Payload Error")
}
// for view change, send separate view change per public key
@ -270,19 +269,27 @@ func (consensus *Consensus) startViewChange(viewID uint64) {
true,
); err != nil {
consensus.getLogger().Err(err).
Msg("could not send out the ViewChange message")
Msg("[startViewChange] could not send out the ViewChange message")
}
}
}
// startNewView stops the current view change
func (consensus *Consensus) startNewView(viewID uint64, newLeaderPriKey *bls.PrivateKeyWrapper) error {
func (consensus *Consensus) startNewView(viewID uint64, newLeaderPriKey *bls.PrivateKeyWrapper, reset bool) error {
consensus.mutex.Lock()
defer consensus.mutex.Unlock()
if !consensus.IsViewChangingMode() {
return errors.New("not in view changing mode anymore")
}
msgToSend := consensus.constructNewViewMessage(
viewID, newLeaderPriKey,
)
if msgToSend == nil {
return errors.New("failed to construct NewView message")
}
if err := consensus.msgSender.SendWithRetry(
consensus.blockNum,
msg_pb.MessageType_NEWVIEW,
@ -298,6 +305,8 @@ func (consensus *Consensus) startNewView(viewID uint64, newLeaderPriKey *bls.Pri
Hex("M1Payload", consensus.vc.GetM1Payload()).
Msg("[startNewView] Sent NewView Messge")
consensus.msgSender.StopRetry(msg_pb.MessageType_VIEWCHANGE)
consensus.current.SetMode(Normal)
consensus.consensusTimeout[timeoutViewChange].Stop()
consensus.SetViewIDs(viewID)
@ -309,7 +318,10 @@ func (consensus *Consensus) startNewView(viewID uint64, newLeaderPriKey *bls.Pri
Str("myKey", newLeaderPriKey.Pub.Bytes.Hex()).
Msg("[startNewView] viewChange stopped. I am the New Leader")
// TODO: consider make ResetState unified and only called in one place like finalizeCommit()
if reset {
consensus.ResetState()
}
consensus.LeaderPubKey = newLeaderPriKey.Pub
return nil
@ -381,7 +393,7 @@ func (consensus *Consensus) onViewChange(msg *msg_pb.Message) {
// no previous prepared message, go straight to normal mode
// and start proposing new block
if consensus.vc.IsM1PayloadEmpty() {
if err := consensus.startNewView(recvMsg.ViewID, newLeaderPriKey); err != nil {
if err := consensus.startNewView(recvMsg.ViewID, newLeaderPriKey, true); err != nil {
consensus.getLogger().Error().Err(err).Msg("[onViewChange] startNewView failed")
return
}
@ -397,7 +409,7 @@ func (consensus *Consensus) onViewChange(msg *msg_pb.Message) {
consensus.getLogger().Error().Err(err).Msg("[onViewChange] self commit failed")
return
}
if err := consensus.startNewView(recvMsg.ViewID, newLeaderPriKey); err != nil {
if err := consensus.startNewView(recvMsg.ViewID, newLeaderPriKey, false); err != nil {
consensus.getLogger().Error().Err(err).Msg("[onViewChange] startNewView failed")
return
}
@ -508,6 +520,8 @@ func (consensus *Consensus) onNewView(msg *msg_pb.Message) {
consensus.LeaderPubKey = senderKey
consensus.ResetViewChangeState()
consensus.msgSender.StopRetry(msg_pb.MessageType_VIEWCHANGE)
// NewView message is verified, change state to normal consensus
if preparedBlock != nil {
consensus.sendCommitMessages(preparedBlock)

@ -118,6 +118,9 @@ func (consensus *Consensus) constructNewViewMessage(viewID uint64, priKey *bls.P
vcMsg.Payload, vcMsg.PreparedBlock = consensus.vc.GetPreparedBlock(consensus.FBFTLog, consensus.blockHash)
vcMsg.M2Aggsigs, vcMsg.M2Bitmap = consensus.vc.GetM2Bitmap(viewID)
vcMsg.M3Aggsigs, vcMsg.M3Bitmap = consensus.vc.GetM3Bitmap(viewID)
if vcMsg.M3Bitmap == nil || vcMsg.M3Aggsigs == nil {
return nil
}
marshaledMessage, err := consensus.signAndMarshalConsensusMessage(message, priKey.Pri)
if err != nil {

@ -87,7 +87,7 @@ func TestGetNextLeaderKeyShouldFailForStandardGeneratedConsensus(t *testing.T) {
// The below results in: "panic: runtime error: integer divide by zero"
// This happens because there's no check for if there are any participants or not in https://github.com/harmony-one/harmony/blob/main/consensus/quorum/quorum.go#L188-L197
assert.Panics(t, func() { consensus.GetNextLeaderKey(uint64(1)) })
assert.Panics(t, func() { consensus.getNextLeaderKey(uint64(1)) })
}
func TestGetNextLeaderKeyShouldSucceed(t *testing.T) {
@ -115,7 +115,7 @@ func TestGetNextLeaderKeyShouldSucceed(t *testing.T) {
assert.Equal(t, keyCount, consensus.Decider.ParticipantsCount())
consensus.LeaderPubKey = &wrappedBLSKeys[0]
nextKey := consensus.GetNextLeaderKey(uint64(1))
nextKey := consensus.getNextLeaderKey(uint64(1))
assert.Equal(t, nextKey, &wrappedBLSKeys[1])
}

@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/crypto/bls"
@ -19,6 +20,8 @@ import (
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/availability"
stakingNetwork "github.com/harmony-one/harmony/staking/network"
"github.com/pkg/errors"
)
@ -75,15 +78,10 @@ func (hmy *Harmony) GetBlockSigners(
// DetailedBlockSignerInfo contains all of the block singing information
type DetailedBlockSignerInfo struct {
// Signers is a map of addresses in the Signers for the block to
// all of the serialized BLS keys that signed said block.
Signers map[common.Address][]bls.SerializedPublicKey
// Signers are all the signers for the block
Signers shard.SlotList
// Committee when the block was signed.
Committee shard.SlotList
// TotalKeysSigned is the total number of bls keys that signed the block.
TotalKeysSigned uint
// Mask is the bitmap Mask for the block.
Mask *bls.Mask
BlockHash common.Hash
}
@ -91,31 +89,97 @@ type DetailedBlockSignerInfo struct {
func (hmy *Harmony) GetDetailedBlockSignerInfo(
ctx context.Context, blk *types.Block,
) (*DetailedBlockSignerInfo, error) {
slotList, mask, err := hmy.GetBlockSigners(
ctx, rpc.BlockNumber(blk.Number().Uint64()),
parentBlk, err := hmy.BlockByNumber(ctx, rpc.BlockNumber(blk.NumberU64()-1))
if err != nil {
return nil, err
}
parentShardState, err := hmy.BlockChain.ReadShardState(parentBlk.Epoch())
if err != nil {
return nil, err
}
committee, signers, _, err := availability.BallotResult(
parentBlk.Header(), blk.Header(), parentShardState, blk.ShardID(),
)
return &DetailedBlockSignerInfo{
Signers: signers,
Committee: committee,
BlockHash: blk.Hash(),
}, nil
}
// PreStakingBlockRewards are the rewards for a block in the pre-staking era (epoch < staking epoch).
type PreStakingBlockRewards map[common.Address]*big.Int
// GetPreStakingBlockRewards for the given block number.
// Calculated rewards are done exactly like chain.AccumulateRewardsAndCountSigs.
func (hmy *Harmony) GetPreStakingBlockRewards(
ctx context.Context, blk *types.Block,
) (PreStakingBlockRewards, error) {
if hmy.IsStakingEpoch(blk.Epoch()) {
return nil, fmt.Errorf("block %v is in staking era", blk.Number())
}
if cachedReward, ok := hmy.preStakingBlockRewardsCache.Get(blk.Hash()); ok {
return cachedReward.(PreStakingBlockRewards), nil
}
rewards := PreStakingBlockRewards{}
sigInfo, err := hmy.GetDetailedBlockSignerInfo(ctx, blk)
if err != nil {
return nil, err
}
last := big.NewInt(0)
count := big.NewInt(int64(len(sigInfo.Signers)))
for i, slot := range sigInfo.Signers {
rewardsForThisAddr, ok := rewards[slot.EcdsaAddress]
if !ok {
rewardsForThisAddr = big.NewInt(0)
}
cur := big.NewInt(0)
cur.Mul(stakingNetwork.BlockReward, big.NewInt(int64(i+1))).Div(cur, count)
reward := big.NewInt(0).Sub(cur, last)
rewards[slot.EcdsaAddress] = new(big.Int).Add(reward, rewardsForThisAddr)
last = cur
}
totalSigners := uint(0)
sigInfos := map[common.Address][]bls.SerializedPublicKey{}
for _, slot := range slotList {
if _, ok := sigInfos[slot.EcdsaAddress]; !ok {
sigInfos[slot.EcdsaAddress] = []bls.SerializedPublicKey{}
// Report tx fees of the coinbase (== leader)
receipts, err := hmy.GetReceipts(ctx, blk.Hash())
if err != nil {
return nil, err
}
if ok, err := mask.KeyEnabled(slot.BLSPublicKey); ok && err == nil {
sigInfos[slot.EcdsaAddress] = append(sigInfos[slot.EcdsaAddress], slot.BLSPublicKey)
totalSigners++
txFees := big.NewInt(0)
for _, tx := range blk.Transactions() {
dbTx, _, _, receiptIndex := rawdb.ReadTransaction(hmy.ChainDb(), tx.Hash())
if dbTx == nil {
return nil, fmt.Errorf("could not find receipt for tx: %v", tx.Hash().String())
}
if len(receipts) <= int(receiptIndex) {
return nil, fmt.Errorf("invalid receipt indext %v (>= num receipts: %v) for tx: %v",
receiptIndex, len(receipts), tx.Hash().String())
}
return &DetailedBlockSignerInfo{
Signers: sigInfos,
Committee: slotList,
TotalKeysSigned: totalSigners,
Mask: mask,
BlockHash: blk.Hash(),
}, nil
txFee := new(big.Int).Mul(tx.GasPrice(), big.NewInt(int64(receipts[receiptIndex].GasUsed)))
txFees = new(big.Int).Add(txFee, txFees)
}
for _, stx := range blk.StakingTransactions() {
dbsTx, _, _, receiptIndex := rawdb.ReadStakingTransaction(hmy.ChainDb(), stx.Hash())
if dbsTx == nil {
return nil, fmt.Errorf("could not find receipt for tx: %v", stx.Hash().String())
}
if len(receipts) <= int(receiptIndex) {
return nil, fmt.Errorf("invalid receipt indext %v (>= num receipts: %v) for tx: %v",
receiptIndex, len(receipts), stx.Hash().String())
}
txFee := new(big.Int).Mul(stx.GasPrice(), big.NewInt(int64(receipts[receiptIndex].GasUsed)))
txFees = new(big.Int).Add(txFee, txFees)
}
if amt, ok := rewards[blk.Header().Coinbase()]; ok {
rewards[blk.Header().Coinbase()] = new(big.Int).Add(amt, txFees)
} else {
rewards[blk.Header().Coinbase()] = txFees
}
hmy.preStakingBlockRewardsCache.Add(blk.Hash(), rewards)
return rewards, nil
}
// GetLatestChainHeaders ..

@ -32,6 +32,7 @@ const (
BloomBitsBlocks uint64 = 4096
leaderCacheSize = 250 // Approx number of BLS keys in committee
undelegationPayoutsCacheSize = 500 // max number of epochs to store in cache
preStakingBlockRewardsCacheSize = 1024 // max number of block rewards to store in cache
totalStakeCacheDuration = 20 // number of blocks where the returned total stake will remain the same
)
@ -67,6 +68,8 @@ type Harmony struct {
leaderCache *lru.Cache
// undelegationPayoutsCache to save on recomputation every epoch
undelegationPayoutsCache *lru.Cache
// preStakingBlockRewardsCache to save on recomputation for commonly checked blocks in epoch < staking epoch
preStakingBlockRewardsCache *lru.Cache
// totalStakeCache to save on recomputation for `totalStakeCacheDuration` blocks.
totalStakeCache *totalStakeCache
}
@ -111,6 +114,7 @@ func New(
chainDb := nodeAPI.Blockchain().ChainDB()
leaderCache, _ := lru.New(leaderCacheSize)
undelegationPayoutsCache, _ := lru.New(undelegationPayoutsCacheSize)
preStakingBlockRewardsCache, _ := lru.New(preStakingBlockRewardsCacheSize)
totalStakeCache := newTotalStakeCache(totalStakeCacheDuration)
bloomIndexer := NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms)
bloomIndexer.Start(nodeAPI.Blockchain())
@ -130,6 +134,7 @@ func New(
leaderCache: leaderCache,
totalStakeCache: totalStakeCache,
undelegationPayoutsCache: undelegationPayoutsCache,
preStakingBlockRewardsCache: preStakingBlockRewardsCache,
}
}

@ -103,6 +103,8 @@ type RPCServerConfig struct {
WSEnabled bool
WSIp string
WSPort int
DebugEnabled bool
}
// RosettaServerConfig is the config for the rosetta server

@ -155,31 +155,23 @@ func (s *BlockAPI) specialGenesisBlockTransaction(
}
// getPreStakingRewardTransactionIdentifiers is only used for the /block endpoint
// rewards for signing block n is paid out on block n+1
func (s *BlockAPI) getPreStakingRewardTransactionIdentifiers(
ctx context.Context, currBlock *hmytypes.Block,
) ([]*types.TransactionIdentifier, *types.Error) {
if currBlock.Number().Cmp(big.NewInt(1)) != 1 {
return nil, nil
}
blockNumToBeRewarded := currBlock.Number().Uint64() - 1
rewardedBlock, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNumToBeRewarded).EthBlockNumber())
if err != nil {
return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{
"message": err.Error(),
})
}
blockSigInfo, err := s.hmy.GetDetailedBlockSignerInfo(ctx, rewardedBlock)
rewards, err := s.hmy.GetPreStakingBlockRewards(ctx, currBlock)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
txIDs := []*types.TransactionIdentifier{}
for acc, signedBlsKeys := range blockSigInfo.Signers {
if len(signedBlsKeys) > 0 {
txIDs = append(txIDs, getSpecialCaseTransactionIdentifier(currBlock.Hash(), acc, SpecialPreStakingRewardTxID))
}
for addr := range rewards {
txIDs = append(txIDs, getSpecialCaseTransactionIdentifier(
currBlock.Hash(), addr, SpecialPreStakingRewardTxID,
))
}
return txIDs, nil
}
@ -221,13 +213,6 @@ func (s *BlockAPI) preStakingRewardBlockTransaction(
if rosettaError != nil {
return nil, rosettaError
}
blockNumOfSigsForReward := blk.Number().Uint64() - 1
signedBlock, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNumOfSigsForReward).EthBlockNumber())
if err != nil {
return nil, common.NewError(common.BlockNotFoundError, map[string]interface{}{
"message": err.Error(),
})
}
if blkHash.String() != blk.Hash().String() {
return nil, common.NewError(common.SanityCheckError, map[string]interface{}{
"message": fmt.Sprintf(
@ -235,13 +220,13 @@ func (s *BlockAPI) preStakingRewardBlockTransaction(
),
})
}
blockSignerInfo, err := s.hmy.GetDetailedBlockSignerInfo(ctx, signedBlock)
rewards, err := s.hmy.GetPreStakingBlockRewards(ctx, blk)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
transactions, rosettaError := FormatPreStakingRewardTransaction(txID, blockSignerInfo, address)
transactions, rosettaError := FormatPreStakingRewardTransaction(txID, rewards, address)
if rosettaError != nil {
return nil, rosettaError
}

@ -12,7 +12,6 @@ import (
"github.com/harmony-one/harmony/hmy"
internalCommon "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/rosetta/common"
stakingNetwork "github.com/harmony-one/harmony/staking/network"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
@ -176,45 +175,19 @@ func FormatGenesisTransaction(
// FormatPreStakingRewardTransaction for block rewards in pre-staking era for a given Bech-32 address.
func FormatPreStakingRewardTransaction(
txID *types.TransactionIdentifier, blockSigInfo *hmy.DetailedBlockSignerInfo, address ethcommon.Address,
txID *types.TransactionIdentifier, rewards hmy.PreStakingBlockRewards, address ethcommon.Address,
) (*types.Transaction, *types.Error) {
signatures, ok := blockSigInfo.Signers[address]
if !ok || len(signatures) == 0 {
return nil, &common.TransactionNotFoundError
}
accID, rosettaError := newAccountIdentifier(address)
if rosettaError != nil {
return nil, rosettaError
}
// Calculate rewards exactly like `AccumulateRewardsAndCountSigs` but short circuit when possible.
// WARNING: must do calculation in the order of the committee to get accurate values.
i := 0
last := big.NewInt(0)
rewardsForThisBlock := big.NewInt(0)
count := big.NewInt(int64(blockSigInfo.TotalKeysSigned))
for _, slot := range blockSigInfo.Committee {
rewardsForThisAddr := big.NewInt(0)
if keys, ok := blockSigInfo.Signers[slot.EcdsaAddress]; ok {
for range keys {
cur := big.NewInt(0)
cur.Mul(stakingNetwork.BlockReward, big.NewInt(int64(i+1))).Div(cur, count)
reward := big.NewInt(0).Sub(cur, last)
rewardsForThisAddr = new(big.Int).Add(reward, rewardsForThisAddr)
last = cur
i++
}
}
if slot.EcdsaAddress == address {
rewardsForThisBlock = rewardsForThisAddr
if !(rewardsForThisAddr.Cmp(big.NewInt(0)) > 0) {
return nil, common.NewError(common.SanityCheckError, map[string]interface{}{
"message": "expected non-zero block reward in pre-staking era for block signer",
value, ok := rewards[address]
if !ok {
return nil, common.NewError(common.TransactionNotFoundError, map[string]interface{}{
"message": fmt.Sprintf("%v does not have any rewards for block",
internalCommon.MustAddressToBech32(address)),
})
}
break
}
}
return &types.Transaction{
TransactionIdentifier: txID,
@ -227,7 +200,7 @@ func FormatPreStakingRewardTransaction(
Status: common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
Value: rewardsForThisBlock.String(),
Value: value.String(),
Currency: &common.NativeCurrency,
},
},

@ -12,12 +12,9 @@ import (
"github.com/ethereum/go-ethereum/crypto"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/hmy"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/rosetta/common"
"github.com/harmony-one/harmony/shard"
stakingNetwork "github.com/harmony-one/harmony/staking/network"
stakingTypes "github.com/harmony-one/harmony/staking/types"
"github.com/harmony-one/harmony/test/helpers"
)
@ -208,23 +205,12 @@ func TestFormatPreStakingRewardTransactionSuccess(t *testing.T) {
t.Fatal(err)
}
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
testBlockSigInfo := &hmy.DetailedBlockSignerInfo{
Signers: map[ethcommon.Address][]bls.SerializedPublicKey{
testAddr: { // Only care about length for this test
bls.SerializedPublicKey{},
bls.SerializedPublicKey{},
},
},
Committee: shard.SlotList{
{
EcdsaAddress: testAddr,
},
},
TotalKeysSigned: 150,
BlockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
testBlkHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238")
testRewards := hmy.PreStakingBlockRewards{
testAddr: big.NewInt(1),
}
refTxID := getSpecialCaseTransactionIdentifier(testBlockSigInfo.BlockHash, testAddr, SpecialPreStakingRewardTxID)
tx, rosettaError := FormatPreStakingRewardTransaction(refTxID, testBlockSigInfo, testAddr)
refTxID := getSpecialCaseTransactionIdentifier(testBlkHash, testAddr, SpecialPreStakingRewardTxID)
tx, rosettaError := FormatPreStakingRewardTransaction(refTxID, testRewards, testAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
@ -247,39 +233,6 @@ func TestFormatPreStakingRewardTransactionSuccess(t *testing.T) {
if tx.Operations[0].Status != common.SuccessOperationStatus.Status {
t.Error("expected successful operation status")
}
// Expect: myNumberOfSigForBlock * (totalAmountOfRewardsPerBlock / numOfSigsForBlock) to be my block reward amount
refAmount := new(big.Int).Mul(new(big.Int).Quo(stakingNetwork.BlockReward, big.NewInt(150)), big.NewInt(2))
fmtRefAmount := fmt.Sprintf("%v", refAmount)
if tx.Operations[0].Amount.Value != fmtRefAmount {
t.Errorf("expected operation amount to be %v not %v", fmtRefAmount, tx.Operations[0].Amount.Value)
}
testBlockSigInfo = &hmy.DetailedBlockSignerInfo{
Signers: map[ethcommon.Address][]bls.SerializedPublicKey{
testAddr: { // Only care about length for this test
bls.SerializedPublicKey{},
bls.SerializedPublicKey{},
},
},
Committee: shard.SlotList{},
TotalKeysSigned: 150,
BlockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
}
tx, rosettaError = FormatPreStakingRewardTransaction(refTxID, testBlockSigInfo, testAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if len(tx.Operations) != 1 {
t.Fatal("expected exactly 1 operation")
}
amt, err := types.AmountValue(tx.Operations[0].Amount)
if err != nil {
t.Fatal(err)
}
if amt.Cmp(big.NewInt(0)) != 0 {
t.Error("expected amount to be 0")
}
}
func TestFormatPreStakingRewardTransactionFail(t *testing.T) {
@ -288,42 +241,16 @@ func TestFormatPreStakingRewardTransactionFail(t *testing.T) {
t.Fatal(err)
}
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
testBlockSigInfo := &hmy.DetailedBlockSignerInfo{
Signers: map[ethcommon.Address][]bls.SerializedPublicKey{
testAddr: {},
},
Committee: shard.SlotList{
{
EcdsaAddress: testAddr,
},
},
TotalKeysSigned: 150,
BlockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
}
testTxID := getSpecialCaseTransactionIdentifier(testBlockSigInfo.BlockHash, testAddr, SpecialPreStakingRewardTxID)
_, rosettaError := FormatPreStakingRewardTransaction(testTxID, testBlockSigInfo, testAddr)
if rosettaError == nil {
t.Fatal("expected rosetta error")
}
if !reflect.DeepEqual(&common.TransactionNotFoundError, rosettaError) {
t.Error("expected transaction not found error")
}
testBlockSigInfo = &hmy.DetailedBlockSignerInfo{
Signers: map[ethcommon.Address][]bls.SerializedPublicKey{},
Committee: shard.SlotList{
{
EcdsaAddress: testAddr,
},
},
TotalKeysSigned: 150,
BlockHash: ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238"),
testBlkHash := ethcommon.HexToHash("0x1a06b0378d63bf589282c032f0c85b32827e3a2317c2f992f45d8f07d0caa238")
testRewards := hmy.PreStakingBlockRewards{
FormatDefaultSenderAddress: big.NewInt(1),
}
_, rosettaError = FormatPreStakingRewardTransaction(testTxID, testBlockSigInfo, testAddr)
testTxID := getSpecialCaseTransactionIdentifier(testBlkHash, testAddr, SpecialPreStakingRewardTxID)
_, rosettaError := FormatPreStakingRewardTransaction(testTxID, testRewards, testAddr)
if rosettaError == nil {
t.Fatal("expected rosetta error")
}
if !reflect.DeepEqual(&common.TransactionNotFoundError, rosettaError) {
if common.TransactionNotFoundError.Code != rosettaError.Code {
t.Error("expected transaction not found error")
}
}

@ -64,7 +64,7 @@ func (n Version) Namespace() string {
// StartServers starts the http & ws servers
func StartServers(hmy *hmy.Harmony, apis []rpc.API, config nodeconfig.RPCServerConfig) error {
apis = append(apis, getAPIs(hmy)...)
apis = append(apis, getAPIs(hmy, config.DebugEnabled)...)
if config.HTTPEnabled {
httpEndpoint = fmt.Sprintf("%v:%v", config.HTTPIp, config.HTTPPort)
@ -115,8 +115,8 @@ func StopServers() error {
}
// getAPIs returns all the API methods for the RPC interface
func getAPIs(hmy *hmy.Harmony) []rpc.API {
return []rpc.API{
func getAPIs(hmy *hmy.Harmony, debugEnable bool) []rpc.API {
publicAPIs := []rpc.API{
// Public methods
NewPublicHarmonyAPI(hmy, V1),
NewPublicHarmonyAPI(hmy, V2),
@ -130,13 +130,20 @@ func getAPIs(hmy *hmy.Harmony) []rpc.API {
NewPublicPoolAPI(hmy, V2),
NewPublicStakingAPI(hmy, V1),
NewPublicStakingAPI(hmy, V2),
// Private methods
NewPrivateDebugAPI(hmy, V1),
NewPrivateDebugAPI(hmy, V2),
// Legacy methods (subject to removal)
v1.NewPublicLegacyAPI(hmy),
v2.NewPublicLegacyAPI(hmy),
}
privateAPIs := []rpc.API{
NewPrivateDebugAPI(hmy, V1),
NewPrivateDebugAPI(hmy, V2),
}
if debugEnable {
return append(publicAPIs, privateAPIs...)
}
return publicAPIs
}
func startHTTP(apis []rpc.API) (err error) {

@ -1,6 +1,6 @@
#!/usr/bin/env bash
version="v2 20200811.1"
version="v2 20201023.0"
unset -v progname
progname="${0##*/}"
@ -56,7 +56,7 @@ function valid_ip()
function myip() {
# get ipv4 address only, right now only support ipv4 addresses
PUB_IP=$(dig -4 @resolver1.opendns.com ANY myip.opendns.com +short)
PUB_IP=$(dig TXT +short o-o.myaddr.l.google.com @ns1.google.com | awk -F'"' '{ print $2}')
if valid_ip $PUB_IP; then
msg "public IP address autodetected: $PUB_IP"
else

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
docker pull harmonyone/localnet-test
docker run -v "$DIR/../:/go/src/github.com/harmony-one/harmony" harmonyone/localnet-test -r

@ -2,4 +2,6 @@
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
bash "$DIR/kill_node.sh"
docker pull harmonyone/localnet-test
docker run -it --expose 9000-9999 -v "$DIR/../:/go/src/github.com/harmony-one/harmony" harmonyone/localnet-test
docker run -it \
-p 9500:9500 -p 9501:9501 -p 9599:9599 -p 9598:9598 -p 9799:9799 -p 9798:9798 -p 9899:9899 -p 9898:9898 \
-v "$DIR/../:/go/src/github.com/harmony-one/harmony" harmonyone/localnet-test

@ -87,6 +87,9 @@ function launch_localnet() {
echo "skip empty node"
continue
fi
if [[ $EXPOSEAPIS == "true" ]]; then
args=("${args[@]}" "--http.ip=0.0.0.0" "--ws.ip=0.0.0.0")
fi
# Setup BLS key for i-th localnet node
if [[ ! -e "$bls_key" ]]; then
@ -142,6 +145,7 @@ USAGE: $ME [OPTIONS] config_file_name [extra args to node]
-N network network type (default: $NETWORK)
-B don't build the binary
-v verbosity in log (default: $VERBOSE)
-e expose WS & HTTP ip (default: $EXPOSEAPIS)
This script will build all the binaries and start harmony and based on the configuration file.
@ -159,8 +163,9 @@ DRYRUN=
NETWORK=localnet
VERBOSE=false
NOBUILD=false
EXPOSEAPIS=false
while getopts "hD:m:s:nBN:v" option; do
while getopts "hD:m:s:nBN:ve" option; do
case ${option} in
h) usage ;;
D) DURATION=$OPTARG ;;
@ -170,6 +175,7 @@ while getopts "hD:m:s:nBN:v" option; do
B) NOBUILD=true ;;
N) NETWORK=$OPTARG ;;
v) VERBOSE=true ;;
e) EXPOSEAPIS=true ;;
*) usage ;;
esac
done

@ -1,4 +1,6 @@
#!/usr/bin/env bash
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
docker pull harmonyone/localnet-test
docker run -it -v "$DIR/../:/go/src/github.com/harmony-one/harmony" harmonyone/localnet-test -g
docker run -it \
-p 9500:9500 -p 9501:9501 -p 9599:9599 -p 9598:9598 -p 9799:9799 -p 9798:9798 -p 9899:9899 -p 9898:9898 \
-v "$DIR/../:/go/src/github.com/harmony-one/harmony" harmonyone/localnet-test -g

@ -8,7 +8,9 @@ case ${1} in
run)
docker pull harmonyone/localnet-test
docker rm "$docker_name"
docker run -it --name "$docker_name" --expose 9000-9999 -v "$DIR/../:/go/src/github.com/harmony-one/harmony" harmonyone/localnet-test -n -k
docker run -it --name "$docker_name" \
-p 9500:9500 -p 9501:9501 -p 9599:9599 -p 9598:9598 -p 9799:9799 -p 9798:9798 -p 9899:9899 -p 9898:9898 \
-v "$DIR/../:/go/src/github.com/harmony-one/harmony" harmonyone/localnet-test -r -k
;;
attach)
docker exec -it "$docker_name" /bin/bash

@ -0,0 +1,29 @@
#!/usr/bin/env bash
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
bash "$DIR/kill_node.sh"
docker_name="harmony-localnet-test"
case ${1} in
run)
docker pull harmonyone/localnet-test
docker rm "$docker_name"
docker run -it --name "$docker_name" \
-p 9500:9500 -p 9501:9501 -p 9599:9599 -p 9598:9598 -p 9799:9799 -p 9798:9798 -p 9899:9899 -p 9898:9898 \
-v "$DIR/../:/go/src/github.com/harmony-one/harmony" harmonyone/localnet-test -n -k
;;
attach)
docker exec -it "$docker_name" /bin/bash
;;
*)
echo "
Node API tests
Param: Help:
run Run the Node API tests
attach Attach onto the Node API testing docker image for inspection
"
exit 0
;;
esac
Loading…
Cancel
Save