A cumulative collection of fixes/improvements:

* Manage shard chains as first class citizen
* Bring core.BlockChain's read/write semantics cleaner
* Reimplement the block reward in terms of on-chain committee info, not
  hardcoded genesis committee.
pull/839/head
Eugene Kim 6 years ago
parent b75896f534
commit 664c6c1513
  1. 5
      cmd/client/txgen/main.go
  2. 5
      cmd/client/wallet/main.go
  3. 23
      cmd/harmony/main.go
  4. 89
      consensus/consensus.go
  5. 7
      consensus/consensus_service.go
  6. 6
      consensus/engine/consensus_engine.go
  7. 6
      contracts/contract_caller.go
  8. 81
      core/blockchain.go
  9. 2
      core/chain_makers.go
  10. 37
      core/resharding.go
  11. 22
      core/types/block.go
  12. 1
      go.mod
  13. 9
      internal/configs/node/config.go
  14. 34
      internal/shardchain/dbfactory.go
  15. 8
      internal/shardchain/dbinit.go
  16. 127
      internal/shardchain/shardchains.go
  17. 6
      node/contract.go
  18. 2
      node/contract_test.go
  19. 184
      node/node.go
  20. 58
      node/node_genesis.go
  21. 374
      node/node_handler.go
  22. 6
      node/node_handler_test.go
  23. 71
      node/node_newblock.go
  24. 14
      node/node_syncing.go
  25. 18
      node/node_test.go
  26. 2
      node/rpc.go
  27. 10
      node/service_setup.go
  28. 2
      node/staking.go
  29. 2
      node/staking_test.go
  30. 51
      test/deploy.sh

@ -13,11 +13,13 @@ import (
"github.com/harmony-one/harmony/consensus"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/internal/shardchain"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
bls2 "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/api/client"
proto_node "github.com/harmony-one/harmony/api/proto/node"
"github.com/harmony-one/harmony/core/types"
@ -97,7 +99,8 @@ func setUpTXGen() *node.Node {
os.Exit(1)
}
consensusObj, err := consensus.New(myhost, uint32(shardID), p2p.Peer{}, nil)
txGen := node.New(myhost, consensusObj, nil, false) //Changed it : no longer archival node.
chainDBFactory := &shardchain.MemDBFactory{}
txGen := node.New(myhost, consensusObj, chainDBFactory, false) //Changed it : no longer archival node.
txGen.Client = client.NewClient(txGen.GetHost(), uint32(shardID))
consensusObj.SetStakeInfoFinder(gsif)
consensusObj.ChainReader = txGen.Blockchain()

@ -18,12 +18,14 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/harmony-one/harmony/api/client"
clientService "github.com/harmony-one/harmony/api/client/service"
proto_node "github.com/harmony-one/harmony/api/proto/node"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/internal/shardchain"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/node"
"github.com/harmony-one/harmony/p2p"
@ -236,7 +238,8 @@ func createWalletNode() *node.Node {
if err != nil {
panic(err)
}
w := node.New(host, nil, nil, false)
chainDBFactory := &shardchain.MemDBFactory{}
w := node.New(host, nil, chainDBFactory, false)
w.Client = client.NewClient(w.GetHost(), uint32(shardID))
w.NodeConfig.SetRole(nodeconfig.ClientNode)

@ -22,6 +22,7 @@ import (
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
hmykey "github.com/harmony-one/harmony/internal/keystore"
"github.com/harmony-one/harmony/internal/profiler"
"github.com/harmony-one/harmony/internal/shardchain"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/internal/utils/contract"
"github.com/harmony-one/harmony/node"
@ -109,6 +110,9 @@ var (
myPass = ""
// logging verbosity
verbosity = flag.Int("verbosity", 5, "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail (default: 5)")
// dbDir is the database directory.
dbDir = flag.String("db_dir", "", "blockchain database directory")
)
func initSetup() {
@ -229,16 +233,6 @@ func createGlobalConfig() *nodeconfig.ConfigType {
}
// Key Setup ================= [End]
// Initialize leveldb for main blockchain and beacon.
if nodeConfig.MainDB, err = InitLDBDatabase(*ip, *port, *freshDB, false); err != nil {
panic(err)
}
if myShardID != 0 {
if nodeConfig.BeaconDB, err = InitLDBDatabase(*ip, *port, *freshDB, true); err != nil {
panic(err)
}
}
nodeConfig.SelfPeer = p2p.Peer{IP: *ip, Port: *port, ConsensusPubKey: nodeConfig.ConsensusPubKey}
if *accountIndex%core.GenesisShardSize == 0 { // The first node in a shard is the leader at genesis
@ -258,6 +252,8 @@ func createGlobalConfig() *nodeconfig.ConfigType {
nodeConfig.Host.AddPeer(&nodeConfig.Leader)
nodeConfig.DBDir = *dbDir
return nodeConfig
}
@ -273,7 +269,8 @@ func setUpConsensusAndNode(nodeConfig *nodeconfig.ConfigType) *node.Node {
currentConsensus.MinPeers = *minPeers
// Current node.
currentNode := node.New(nodeConfig.Host, currentConsensus, nodeConfig.MainDB, *isArchival)
chainDBFactory := &shardchain.LDBFactory{RootDir: nodeConfig.DBDir}
currentNode := node.New(nodeConfig.Host, currentConsensus, chainDBFactory, *isArchival)
currentNode.NodeConfig.SetRole(nodeconfig.NewNode)
currentNode.StakingAccount = myAccount
utils.GetLogInstance().Info("node account set",
@ -348,10 +345,6 @@ func setUpConsensusAndNode(nodeConfig *nodeconfig.ConfigType) *node.Node {
currentNode.Consensus.RegisterRndChannel(dRand.RndChannel)
currentNode.DRand = dRand
if currentConsensus.ShardID != 0 {
currentNode.AddBeaconChainDatabase(nodeConfig.BeaconDB)
}
// This needs to be executed after consensus and drand are setup
if !*isNewNode || *shardID > -1 { // initial staking new node doesn't need to initialize shard state
currentNode.InitShardState(*shardID == -1 && !*isNewNode) // TODO: Have a better why to distinguish non-genesis node

@ -2,13 +2,13 @@
package consensus // consensus
import (
"errors"
"math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/harmony-one/bls/ffi/go/bls"
@ -272,61 +272,74 @@ func New(host p2p.Host, ShardID uint32, leader p2p.Peer, blsPriKey *bls.SecretKe
return &consensus, nil
}
// AccumulateRewards credits the coinbase of the given block with the mining
// accumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func (consensus *Consensus) accumulateRewards(
config *params.ChainConfig, state *state.DB, header *types.Header,
func accumulateRewards(
bc consensus_engine.ChainReader, state *state.DB, header *types.Header,
) error {
logger := utils.GetLogInstance().New("parentHash", header.ParentHash)
if header.ParentHash == (common.Hash{}) {
// This block is a genesis block,
// without a parent block whose signer to reward.
logger := header.Logger(utils.GetLogInstance())
getLogger := func() log.Logger { return utils.WithCallerSkip(logger, 1) }
blockNum := header.Number.Uint64()
if blockNum == 0 {
// Epoch block doesn't have any reward
return nil
}
if consensus.ChainReader == nil {
return errors.New("ChainReader is nil")
parentHeader := bc.GetHeaderByNumber(blockNum - 1)
if parentHeader == nil {
return ctxerror.New("cannot find parent block header in DB",
"parentBlockNumber", blockNum - 1)
}
parent := consensus.ChainReader.GetHeaderByHash(header.ParentHash)
if parent == nil {
return ctxerror.New("cannot retrieve parent header",
"parentHash", header.ParentHash,
shardState, err := bc.ReadShardState(parentHeader.Epoch)
if err != nil {
return ctxerror.New("cannot read shard state",
"epoch", parentHeader.Epoch,
).WithCause(err)
}
parentShardState := shardState.FindCommitteeByID(parentHeader.ShardID)
if parentShardState == nil {
return ctxerror.New("cannot find shard in the shard state",
"parentBlockNumber", parentHeader.Number,
"shardID", parentHeader.ShardID,
)
}
mask, err := bls_cosi.NewMask(consensus.PublicKeys, nil)
var committerKeys []*bls.PublicKey
for _, member := range parentShardState.NodeList {
committerKey := new(bls.PublicKey)
err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey)
if err != nil {
return ctxerror.New("cannot convert BLS public key",
"blsPublicKey", member.BlsPublicKey).WithCause(err)
}
committerKeys = append(committerKeys, committerKey)
}
mask, err := bls_cosi.NewMask(committerKeys, nil)
if err != nil {
return ctxerror.New("cannot create group sig mask").WithCause(err)
}
logger.Debug("accumulateRewards: setting group sig mask",
"destLen", mask.Len(),
"srcLen", len(parent.CommitBitmap),
)
if err := mask.SetMask(parent.CommitBitmap); err != nil {
if err := mask.SetMask(parentHeader.CommitBitmap); err != nil {
return ctxerror.New("cannot set group sig mask bits").WithCause(err)
}
totalAmount := big.NewInt(0)
numAccounts := 0
signingKeys := mask.GetPubKeyFromMask(true)
for _, key := range signingKeys {
stakeInfos := consensus.stakeInfoFinder.FindStakeInfoByNodeKey(key)
if len(stakeInfos) == 0 {
logger.Error("accumulateRewards: node has no stake info",
"nodeKey", key.GetHexString())
for idx, member := range parentShardState.NodeList {
if signed, err := mask.IndexEnabled(idx); err != nil {
return ctxerror.New("cannot check for committer bit",
"committerIndex", idx,
).WithCause(err)
} else if !signed {
continue
}
numAccounts += len(stakeInfos)
for _, stakeInfo := range stakeInfos {
utils.GetLogInstance().Info("accumulateRewards: rewarding",
"block", header.Hash(),
"account", stakeInfo.Account,
"node", stakeInfo.BlsPublicKey.Hex(),
"amount", BlockReward)
state.AddBalance(stakeInfo.Account, BlockReward)
totalAmount = new(big.Int).Add(totalAmount, BlockReward)
}
numAccounts++
account := common.HexToAddress(member.EcdsaAddress)
getLogger().Info("rewarding block signer",
"account", account,
"node", member.BlsPublicKey.Hex(),
"amount", BlockReward)
state.AddBalance(account, BlockReward)
totalAmount = new(big.Int).Add(totalAmount, BlockReward)
}
logger.Debug("accumulateRewards: paid out block reward",
"numSigs", len(signingKeys),
getLogger().Debug("paid out block reward",
"numAccounts", numAccounts,
"totalAmount", totalAmount)
return nil

@ -12,6 +12,9 @@ import (
"github.com/ethereum/go-ethereum/rlp"
protobuf "github.com/golang/protobuf/proto"
"github.com/harmony-one/bls/ffi/go/bls"
libp2p_peer "github.com/libp2p/go-libp2p-peer"
"golang.org/x/crypto/sha3"
proto_discovery "github.com/harmony-one/harmony/api/proto/discovery"
msg_pb "github.com/harmony-one/harmony/api/proto/message"
consensus_engine "github.com/harmony-one/harmony/consensus/engine"
@ -22,8 +25,6 @@ import (
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/p2p/host"
libp2p_peer "github.com/libp2p/go-libp2p-peer"
"golang.org/x/crypto/sha3"
)
// WaitForNewRandomness listens to the RndChannel to receive new VDF randomness.
@ -241,7 +242,7 @@ func (consensus *Consensus) VerifySeal(chain consensus_engine.ChainReader, heade
func (consensus *Consensus) Finalize(chain consensus_engine.ChainReader, header *types.Header, state *state.DB, txs []*types.Transaction, receipts []*types.Receipt) (*types.Block, error) {
// Accumulate any block and uncle rewards and commit the final state root
// Header seems complete, assemble into a block and return
consensus.accumulateRewards(chain.Config(), state, header)
accumulateRewards(chain, state, header)
header.Root = state.IntermediateRoot(false)
return types.NewBlock(header, txs, receipts), nil
}

@ -1,8 +1,11 @@
package engine
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types"
)
@ -28,6 +31,9 @@ type ChainReader interface {
// GetBlock retrieves a block from the database by hash and number.
GetBlock(hash common.Hash, number uint64) *types.Block
// ReadShardState retrieves sharding state given the epoch number.
ReadShardState(epoch *big.Int) (types.ShardState, error)
}
// Engine is an algorithm agnostic consensus engine.

@ -6,8 +6,8 @@ import (
"github.com/harmony-one/harmony/internal/utils"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
@ -15,7 +15,6 @@ import (
// ContractCaller is used to call smart contract locally
type ContractCaller struct {
database *ethdb.Database
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
mu sync.Mutex
@ -23,9 +22,8 @@ type ContractCaller struct {
}
// NewContractCaller initialize a new contract caller.
func NewContractCaller(db *ethdb.Database, bc *core.BlockChain, config *params.ChainConfig) *ContractCaller {
func NewContractCaller(bc *core.BlockChain, config *params.ChainConfig) *ContractCaller {
cc := ContractCaller{}
cc.database = db
cc.blockchain = bc
cc.mu = sync.Mutex{}
cc.config = config

@ -72,7 +72,7 @@ const (
// BlocksPerEpoch is the number of blocks in one epoch
// currently set to small number for testing
// in future, this need to be adjusted dynamically instead of constant
BlocksPerEpoch = 10000
BlocksPerEpoch = 5
// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
BlockChainVersion = 3
@ -132,7 +132,7 @@ type BlockChain struct {
blockCache *lru.Cache // Cache for the most recent entire blocks
futureBlocks *lru.Cache // future blocks are blocks added for later processing
shardStateCache *lru.Cache
epochCache *lru.Cache // Cache epoch number → first block number
epochCache *lru.Cache // Cache epoch number → first block number
quit chan struct{} // blockchain quit channel
running int32 // running must be called atomically
@ -1656,27 +1656,32 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript
return bc.scope.Track(bc.logsFeed.Subscribe(ch))
}
// GetShardState retrieves sharding state given block hash and block number
func (bc *BlockChain) GetShardState(hash common.Hash, number uint64) types.ShardState {
if cached, ok := bc.shardStateCache.Get(hash); ok {
// ReadShardState retrieves sharding state given the epoch number.
func (bc *BlockChain) ReadShardState(epoch *big.Int) (types.ShardState, error) {
cacheKey := string(epoch.Bytes())
if cached, ok := bc.shardStateCache.Get(cacheKey); ok {
shardState := cached.(types.ShardState)
return shardState
return shardState, nil
}
shardState := rawdb.ReadShardState(bc.db, hash, number)
if shardState == nil {
return nil
shardState, err := rawdb.ReadShardState(bc.db, epoch)
if err != nil {
return nil, err
}
bc.shardStateCache.Add(hash, shardState)
return shardState
bc.shardStateCache.Add(cacheKey, shardState)
return shardState, nil
}
// GetShardStateByNumber retrieves sharding state given the block number
func (bc *BlockChain) GetShardStateByNumber(number uint64) types.ShardState {
hash := rawdb.ReadCanonicalHash(bc.db, number)
if hash == (common.Hash{}) {
return nil
// WriteShardState saves the given sharding state under the given epoch number.
func (bc *BlockChain) WriteShardState(
epoch *big.Int, shardState types.ShardState,
) error {
err := rawdb.WriteShardState(bc.db, epoch, shardState)
if err != nil {
return err
}
return bc.GetShardState(hash, number)
cacheKey := string(epoch.Bytes())
bc.shardStateCache.Add(cacheKey, shardState)
return nil
}
// GetRandSeedByNumber retrieves the rand seed given the block number, return 0 if not exist
@ -1697,35 +1702,25 @@ func (bc *BlockChain) GetRandPreimageByNumber(number uint64) [32]byte {
return header.RandPreimage
}
// GetNewShardState will calculate (if not exist) and get the new shard state for epoch block or nil if block is not epoch block
// epoch block is where the new shard state stored
func (bc *BlockChain) GetNewShardState(block *types.Block, stakeInfo *map[common.Address]*structs.StakeInfo) types.ShardState {
hash := block.Hash()
// just ignore non-epoch block
if !IsEpochBlock(block) {
return nil
// GetShardState returns the shard state for the given epoch,
// creating one if needed.
func (bc *BlockChain) GetShardState(
epoch *big.Int, stakeInfo *map[common.Address]*structs.StakeInfo,
) (types.ShardState, error) {
shardState, err := bc.ReadShardState(epoch)
if err == nil { // TODO ek – distinguish ErrNotFound
return shardState, err
}
number := block.NumberU64()
shardState := bc.GetShardState(hash, number)
if shardState == nil {
epoch := GetEpochFromBlockNumber(number)
shardState = CalculateNewShardState(bc, epoch, stakeInfo)
bc.shardStateCache.Add(hash, shardState)
shardState, err = CalculateNewShardState(bc, epoch, stakeInfo)
if err != nil {
return nil, err
}
return shardState
}
// StoreNewShardState insert new shard state into epoch block
func (bc *BlockChain) StoreNewShardState(block *types.Block, stakeInfo *map[common.Address]*structs.StakeInfo) types.ShardState {
// write state into db.
shardState := bc.GetNewShardState(block, stakeInfo)
if shardState != nil {
hash := block.Hash()
number := block.NumberU64()
rawdb.WriteShardState(bc.db, hash, number, shardState)
utils.GetLogInstance().Debug("[Resharding] Saved new shard state successfully", "epoch", GetEpochFromBlockNumber(block.NumberU64()))
err = bc.WriteShardState(epoch, shardState)
if err != nil {
return nil, err
}
return shardState
utils.GetLogger().Debug("saved new shard state", "epoch", epoch)
return shardState, nil
}
// ChainDb returns the database

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
consensus_engine "github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types"
@ -273,3 +274,4 @@ func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil }
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
func (cr *fakeChainReader) ReadShardState(epoch *big.Int) (types.ShardState, error) { return nil, nil }

@ -2,6 +2,7 @@ package core
import (
"encoding/binary"
"math/big"
"math/rand"
"sort"
@ -9,6 +10,7 @@ import (
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/contracts/structs"
"github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/internal/utils/contract"
@ -25,7 +27,7 @@ const (
// GenesisShardNum is the number of shard at genesis
GenesisShardNum = 4
// GenesisShardSize is the size of each shard at genesis
GenesisShardSize = 50
GenesisShardSize = 5
// CuckooRate is the percentage of nodes getting reshuffled in the second step of cuckoo resharding.
CuckooRate = 0.1
)
@ -34,7 +36,7 @@ const (
type ShardingState struct {
epoch uint64 // current epoch
rnd uint64 // random seed for resharding
numShards int
numShards int // TODO ek – equal to len(shardState); remove this
shardState types.ShardState
}
@ -157,26 +159,37 @@ func GetEpochFromBlockNumber(blockNumber uint64) uint64 {
}
// GetShardingStateFromBlockChain will retrieve random seed and shard map from beacon chain for given a epoch
func GetShardingStateFromBlockChain(bc *BlockChain, epoch uint64) *ShardingState {
number := GetBlockNumberFromEpoch(epoch)
shardState := bc.GetShardStateByNumber(number)
func GetShardingStateFromBlockChain(bc *BlockChain, epoch *big.Int) (*ShardingState, error) {
shardState, err := bc.ReadShardState(epoch)
if err != nil {
return nil, err
}
rndSeedBytes := bc.GetRandSeedByNumber(number)
blockNumber := GetBlockNumberFromEpoch(epoch.Uint64())
rndSeedBytes := bc.GetRandSeedByNumber(blockNumber)
rndSeed := binary.BigEndian.Uint64(rndSeedBytes[:])
return &ShardingState{epoch: epoch, rnd: rndSeed, shardState: shardState, numShards: len(shardState)}
return &ShardingState{epoch: epoch.Uint64(), rnd: rndSeed, shardState: shardState, numShards: len(shardState)}, nil
}
// CalculateNewShardState get sharding state from previous epoch and calculate sharding state for new epoch
func CalculateNewShardState(bc *BlockChain, epoch uint64, stakeInfo *map[common.Address]*structs.StakeInfo) types.ShardState {
if epoch == GenesisEpoch {
return GetInitShardState()
func CalculateNewShardState(
bc *BlockChain, epoch *big.Int,
stakeInfo *map[common.Address]*structs.StakeInfo,
) (types.ShardState, error) {
if epoch.Cmp(big.NewInt(GenesisEpoch)) == 0 {
return GetInitShardState(), nil
}
prevEpoch := new(big.Int).Sub(epoch, common.Big1)
ss, err := GetShardingStateFromBlockChain(bc, prevEpoch)
if err != nil {
return nil, ctxerror.New("cannot retrieve previous sharding state").
WithCause(err)
}
ss := GetShardingStateFromBlockChain(bc, epoch-1)
newNodeList := ss.UpdateShardingState(stakeInfo)
utils.GetLogInstance().Info("Cuckoo Rate", "percentage", CuckooRate)
ss.Reshard(newNodeList, CuckooRate)
return ss.shardState
return ss.shardState, nil
}
// UpdateShardingState remove the unstaked nodes and returns the newly staked node Ids.

@ -120,6 +120,18 @@ func (h *Header) Size() common.StorageSize {
return common.StorageSize(unsafe.Sizeof(*h)) + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+h.Time.BitLen())/8)
}
// Logger returns a sub-logger with block contexts added.
func (h *Header) Logger(logger log.Logger) log.Logger {
return logger.New(
"blockHash", h.Hash(),
"blockShard", h.ShardID,
"blockEpoch", h.Epoch,
"blockNumber", h.Number,
)
}
// Log15Ctx returns the contexts
func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewLegacyKeccak256()
rlp.Encode(hw, x)
@ -489,13 +501,5 @@ func (b *Block) AddShardState(shardState ShardState) {
// Logger returns a sub-logger with block contexts added.
func (b *Block) Logger(logger log.Logger) log.Logger {
// Avoid using b.Hash() 'cause it fixates/caches calculated hash into the
// block and we don't want to fixate a premature hash from a half-built
// header.
return logger.New(
"blockHash", b.header.Hash(),
"blockShard", b.header.ShardID,
"blockEpoch", b.header.Epoch,
"blockNumber", b.header.Number,
)
return b.header.Logger(logger)
}

@ -41,6 +41,7 @@ require (
github.com/multiformats/go-multiaddr v0.0.2
github.com/multiformats/go-multiaddr-net v0.0.1
github.com/pborman/uuid v1.2.0
github.com/pkg/errors v0.8.1
github.com/rjeczalik/notify v0.9.2
github.com/rs/cors v1.6.0 // indirect
github.com/shirou/gopsutil v2.18.12+incompatible

@ -9,10 +9,10 @@ import (
"fmt"
"sync"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/p2p"
p2p_crypto "github.com/libp2p/go-libp2p-crypto"
"github.com/harmony-one/harmony/p2p"
)
// Role defines a role of a node.
@ -76,8 +76,9 @@ type ConfigType struct {
P2pPriKey p2p_crypto.PrivKey
ConsensusPriKey *bls.SecretKey
ConsensusPubKey *bls.PublicKey
MainDB *ethdb.LDBDatabase
BeaconDB *ethdb.LDBDatabase
// Database directory
DBDir string
SelfPeer p2p.Peer
Leader p2p.Peer

@ -0,0 +1,34 @@
package shardchain
import (
"fmt"
"path"
"github.com/ethereum/go-ethereum/ethdb"
)
// DBFactory is a blockchain database factory.
type DBFactory interface {
// NewChainDB returns a new database for the blockchain for
// given shard.
NewChainDB(shardID uint32) (ethdb.Database, error)
}
// LDBFactory is a LDB-backed blockchain database factory.
type LDBFactory struct {
RootDir string // directory in which to put shard databases in.
}
// NewChainDB returns a new LDB for the blockchain for given shard.
func (f *LDBFactory) NewChainDB(shardID uint32) (ethdb.Database, error) {
dir := path.Join(f.RootDir, fmt.Sprintf("harmony_db_%d", shardID))
return ethdb.NewLDBDatabase(dir, 0, 0)
}
// MemDBFactory is a memory-backed blockchain database factory.
type MemDBFactory struct{}
// NewChainDB returns a new memDB for the blockchain for given shard.
func (f *MemDBFactory) NewChainDB(shardID uint32) (ethdb.Database, error) {
return ethdb.NewMemDatabase(), nil
}

@ -0,0 +1,8 @@
package shardchain
import "github.com/ethereum/go-ethereum/ethdb"
// DBInitializer initializes a newly created chain database.
type DBInitializer interface {
InitChainDB(db ethdb.Database, shardID uint32) error
}

@ -0,0 +1,127 @@
package shardchain
import (
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
"github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/utils"
)
// Collection is a collection of per-shard blockchains.
type Collection interface {
// ShardChain returns the blockchain for the given shard,
// opening one as necessary.
ShardChain(shardID uint32) (*core.BlockChain, error)
// CloseShardChain closes the given shard chain.
CloseShardChain(shardID uint32) error
// Close closes all shard chains.
Close() error
}
type collection struct {
dbFactory DBFactory
dbInit DBInitializer
engine engine.Engine
mtx sync.Mutex
pool map[uint32]*core.BlockChain
}
func NewCollection(
dbFactory DBFactory, dbInit DBInitializer, engine engine.Engine,
) *collection {
return &collection{
dbFactory: dbFactory,
dbInit: dbInit,
engine: engine,
pool: make(map[uint32]*core.BlockChain),
}
}
// ShardChain returns the blockchain for the given shard,
// opening one as necessary.
func (sc *collection) ShardChain(shardID uint32) (*core.BlockChain, error) {
sc.mtx.Lock()
defer sc.mtx.Unlock()
if bc, ok := sc.pool[shardID]; ok {
return bc, nil
}
var db ethdb.Database
defer func() {
if db != nil {
db.Close()
}
}()
var err error
if db, err = sc.dbFactory.NewChainDB(shardID); err != nil {
// NewChainDB may return incompletely initialized DB;
// avoid closing it.
db = nil
return nil, ctxerror.New("cannot open chain database").WithCause(err)
}
if rawdb.ReadCanonicalHash(db, 0) == (common.Hash{}) {
utils.GetLogger().Info("initializing a new chain database",
"shardID", shardID)
if err := sc.dbInit.InitChainDB(db, shardID); err != nil {
return nil, ctxerror.New("cannot initialize a new chain database").
WithCause(err)
}
}
var cacheConfig *core.CacheConfig
// TODO ek – archival
if false {
cacheConfig = &core.CacheConfig{Disabled: true}
}
chainConfig := *params.TestChainConfig
chainConfig.ChainID.SetUint64(uint64(shardID))
bc, err := core.NewBlockChain(
db, cacheConfig, &chainConfig, sc.engine, vm.Config{}, nil,
)
if err != nil {
return nil, ctxerror.New("cannot create blockchain").WithCause(err)
}
db = nil // don't close
sc.pool[shardID] = bc
return bc, nil
}
// CloseShardChain closes the given shard chain.
func (sc *collection) CloseShardChain(shardID uint32) error {
sc.mtx.Lock()
defer sc.mtx.Unlock()
bc, ok := sc.pool[shardID]
if !ok {
return ctxerror.New("shard chain not found", "shardID", shardID)
}
utils.GetLogger().Info("closing shard chain", "shardID", shardID)
delete(sc.pool, shardID)
bc.Stop()
bc.ChainDb().Close()
utils.GetLogger().Info("closed shard chain", "shardID", shardID)
return nil
}
// Close closes all shard chains.
func (sc *collection) Close() error {
newPool := make(map[uint32]*core.BlockChain)
sc.mtx.Lock()
oldPool := sc.pool
sc.pool = newPool
sc.mtx.Unlock()
for shardID, bc := range oldPool {
utils.GetLogger().Info("closing shard chain", "shardID", shardID)
bc.Stop()
bc.ChainDb().Close()
utils.GetLogger().Info("closed shard chain", "shardID", shardID)
}
return nil
}

@ -85,7 +85,7 @@ func (node *Node) QueryStakeInfo() *structs.StakeInfoReturnValue {
priKey := contract_constants.GenesisBeaconAccountPriKey
deployerAddress := crypto.PubkeyToAddress(priKey.PublicKey)
state, err := node.blockchain.State()
state, err := node.Blockchain().State()
if err != nil {
utils.GetLogInstance().Error("Failed to get blockchain state", "error", err)
@ -131,7 +131,7 @@ func (node *Node) getDeployedStakingContract() common.Address {
// GetNonceOfAddress returns nonce of an address.
func (node *Node) GetNonceOfAddress(address common.Address) uint64 {
state, err := node.blockchain.State()
state, err := node.Blockchain().State()
if err != nil {
log.Error("Failed to get chain state", "Error", err)
return 0
@ -141,7 +141,7 @@ func (node *Node) GetNonceOfAddress(address common.Address) uint64 {
// GetBalanceOfAddress returns balance of an address.
func (node *Node) GetBalanceOfAddress(address common.Address) (*big.Int, error) {
state, err := node.blockchain.State()
state, err := node.Blockchain().State()
if err != nil {
log.Error("Failed to get chain state", "Error", err)
return nil, err

@ -23,7 +23,7 @@ func prepareNode(t *testing.T) *Node {
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
return New(host, consensus, nil, false)
return New(host, consensus, testDBFactory, false)
}

@ -2,17 +2,18 @@ package node
import (
"crypto/ecdsa"
"encoding/hex"
"fmt"
"math/big"
"os"
"sync"
"time"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/accounts"
"github.com/harmony-one/harmony/api/client"
clientService "github.com/harmony-one/harmony/api/client/service"
@ -29,6 +30,8 @@ import (
"github.com/harmony-one/harmony/crypto/pki"
"github.com/harmony-one/harmony/drand"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/shardchain"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/node/worker"
"github.com/harmony-one/harmony/p2p"
@ -94,9 +97,8 @@ type Node struct {
pendingTxMutex sync.Mutex
DRand *drand.DRand // The instance for distributed randomness protocol
blockchain *core.BlockChain // The blockchain for the shard where this node belongs
beaconChain *core.BlockChain // The blockchain for beacon chain.
db *ethdb.LDBDatabase // LevelDB to store blockchain.
// Shard databases
shardChains shardchain.Collection
ClientPeer *p2p.Peer // The peer for the harmony tx generator client, used for leaders to return proof-of-accept
Client *client.Client // The presence of a client object means this node will also act as a client
@ -183,11 +185,40 @@ type Node struct {
ContractCaller *contracts.ContractCaller
accountManager *accounts.Manager
// Next shard state
nextShardState struct {
// The received master shard state
master *types.EpochShardState
// When for a leader to propose the next shard state,
// or for a validator to wait for a proposal before view change.
proposeTime time.Time
}
isFirstTime bool // the node was started with a fresh database
}
// Blockchain returns the blockchain from node
// Blockchain returns the blockchain for the node's current shard.
func (node *Node) Blockchain() *core.BlockChain {
return node.blockchain
shardID := node.Consensus.ShardID
bc, err := node.shardChains.ShardChain(shardID)
if err != nil {
err = ctxerror.New("cannot get shard chain", "shardID", shardID).
WithCause(err)
panic(err) //ctxerror.Log15(utils.GetLogger().Crit, err)
}
return bc
}
// Beaconchain returns the beaconchain from node.
func (node *Node) Beaconchain() *core.BlockChain {
bc, err := node.shardChains.ShardChain(0)
if err != nil {
err = ctxerror.New("cannot get beaconchain").WithCause(err)
ctxerror.Log15(utils.GetLogger().Crit, err)
}
return bc
}
// Add new transactions to the pending transaction list
@ -226,7 +257,7 @@ func (node *Node) StartServer() {
// Currently used for stats reporting purpose
func (node *Node) countNumTransactionsInBlockchain() int {
count := 0
for block := node.blockchain.CurrentBlock(); block != nil; block = node.blockchain.GetBlockByHash(block.Header().ParentHash) {
for block := node.Blockchain().CurrentBlock(); block != nil; block = node.Blockchain().GetBlockByHash(block.Header().ParentHash) {
count += len(block.Transactions())
}
return count
@ -238,10 +269,9 @@ func (node *Node) GetSyncID() [SyncIDLength]byte {
}
// New creates a new node.
func New(host p2p.Host, consensusObj *consensus.Consensus, db ethdb.Database, isArchival bool) *Node {
func New(host p2p.Host, consensusObj *consensus.Consensus, chainDBFactory shardchain.DBFactory, isArchival bool) *Node {
var chain *core.BlockChain
var err error
var isFirstTime bool // if cannot get blockchain from database, then isFirstTime = true
node := Node{}
copy(node.syncID[:], GenerateRandomString(SyncIDLength))
@ -250,39 +280,24 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, db ethdb.Database, is
node.SelfPeer = host.GetSelfPeer()
}
// Create test keys. Genesis will later need this.
node.TestBankKeys, err = CreateTestBankKeys(FakeAddressNumber)
if err != nil {
utils.GetLogInstance().Crit("Error while creating test keys",
"error", err)
}
// TODO ek – make db directory configurable
node.shardChains = shardchain.NewCollection(
chainDBFactory, &genesisInitializer{&node}, consensusObj)
if host != nil && consensusObj != nil {
// Consensus and associated channel to communicate blocks
node.Consensus = consensusObj
// TODO(ricl): placeholder. Set the account manager to node.accountManager
// // Ensure that the AccountManager method works before the node has started.
// // We rely on this in cmd/geth.
// am, ephemeralKeystore, err := makeAccountManager(conf)
// if err != nil {
// return nil, err
// }
// node.accountManager = am
// Init db
database := db
if database == nil {
database = ethdb.NewMemDatabase()
chain, err = node.GenesisBlockSetup(database, consensusObj.ShardID, false)
isFirstTime = true
} else {
chain, err = node.InitBlockChainFromDB(db, node.Consensus, isArchival)
isFirstTime = false
if err != nil || chain == nil || chain.CurrentBlock().NumberU64() <= 0 {
chain, err = node.GenesisBlockSetup(database, consensusObj.ShardID, isArchival)
isFirstTime = true
}
}
if err != nil {
utils.GetLogInstance().Error("Error when setup blockchain", "err", err)
os.Exit(1)
}
node.blockchain = chain
// Load the chains.
chain = node.Blockchain() // this also sets node.isFirstTime if the DB is fresh
_ = node.Beaconchain()
node.BlockChannel = make(chan *types.Block)
node.ConfirmedBlockChannel = make(chan *types.Block)
@ -296,7 +311,7 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, db ethdb.Database, is
// Add Faucet contract to all shards, so that on testnet, we can demo wallet in explorer
// TODO (leo): we need to have support of cross-shard tx later so that the token can be transferred from beacon chain shard to other tx shards.
if isFirstTime {
if node.isFirstTime {
// Setup one time smart contracts
node.AddFaucetContractToPendingTransactions()
} else {
@ -305,7 +320,7 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, db ethdb.Database, is
if node.Consensus.ShardID == 0 {
// Contracts only exist in beacon chain
if isFirstTime {
if node.isFirstTime {
// Setup one time smart contracts
node.CurrentStakes = make(map[common.Address]*structs.StakeInfo)
node.AddStakingContractToPendingTransactions() //This will save the latest information about staked nodes in current staked
@ -313,7 +328,7 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, db ethdb.Database, is
node.AddContractKeyAndAddress(scStaking)
}
}
if isFirstTime {
if node.isFirstTime {
// TODO(minhdoan): Think of a better approach to deploy smart contract.
// This is temporary for demo purpose.
node.AddLotteryContract()
@ -324,7 +339,7 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, db ethdb.Database, is
}
}
node.ContractCaller = contracts.NewContractCaller(&db, node.blockchain, params.TestChainConfig)
node.ContractCaller = contracts.NewContractCaller(node.Blockchain(), params.TestChainConfig)
if consensusObj != nil && nodeconfig.GetDefaultConfig().IsLeader() {
node.State = NodeLeader
@ -359,40 +374,47 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, db ethdb.Database, is
}
// InitShardState initialize genesis shard state and update committee pub keys for consensus and drand
func (node *Node) InitShardState(isGenesis bool) {
shardState := types.ShardState{}
if isGenesis {
// Store the genesis shard state into db.
if node.Consensus != nil {
if node.Consensus.ShardID == 0 {
shardState = node.blockchain.StoreNewShardState(node.blockchain.CurrentBlock(), nil)
} else {
shardState = node.beaconChain.StoreNewShardState(node.beaconChain.CurrentBlock(), nil)
}
}
} else {
epochShardState, err := node.retrieveEpochShardState()
if err != nil {
utils.GetLogInstance().Error("[Shard State] Failed to decode epoch shard state", "error", err)
}
utils.GetLogInstance().Info("Successfully loaded epoch shard state")
shardState = epochShardState.ShardState
func (node *Node) InitShardState(isGenesis bool) (err error) {
logger := utils.GetLogInstance().New("isGenesis", isGenesis)
getLogger := func() log.Logger { return utils.WithCallerSkip(logger, 1) }
if node.Consensus == nil {
getLogger().Crit("consensus is nil; cannot figure out shard ID")
}
shardID := node.Consensus.ShardID
logger = logger.New("shardID", shardID)
getLogger().Info("initializing shard state")
// Get genesis epoch shard state from chain
genesisEpoch := big.NewInt(core.GenesisEpoch)
shardState, err := node.Beaconchain().GetShardState(genesisEpoch, nil)
if err != nil {
return ctxerror.New("cannot read genesis shard state").WithCause(err)
}
getLogger().Info("Successfully loaded epoch shard state")
// Update validator public keys
for _, shard := range shardState {
if shard.ShardID == node.Consensus.ShardID {
pubKeys := []*bls.PublicKey{}
for _, node := range shard.NodeList {
blsPubKey := &bls.PublicKey{}
blsPubKey.Deserialize(node.BlsPublicKey[:])
pubKeys = append(pubKeys, blsPubKey)
}
node.Consensus.UpdatePublicKeys(pubKeys)
node.DRand.UpdatePublicKeys(pubKeys)
break
committee := shardState.FindCommitteeByID(shardID)
if committee == nil {
return ctxerror.New("our shard is not found in genesis shard state",
"shardID", shardID)
}
pubKeys := []*bls.PublicKey{}
for _, node := range committee.NodeList {
pubKey := &bls.PublicKey{}
pubKeyBytes := node.BlsPublicKey[:]
err = pubKey.Deserialize(pubKeyBytes)
if err != nil {
return ctxerror.New("cannot deserialize BLS public key",
"shardID", shardID,
"pubKeyBytes", hex.EncodeToString(pubKeyBytes),
).WithCause(err)
}
pubKeys = append(pubKeys, pubKey)
}
getLogger().Info("initialized shard state", "numPubKeys", len(pubKeys))
node.Consensus.UpdatePublicKeys(pubKeys)
node.DRand.UpdatePublicKeys(pubKeys)
return nil
}
// AddPeers adds neighbors nodes
@ -467,22 +489,6 @@ func (node *Node) initNodeConfiguration() (service.NodeConfig, chan p2p.Peer) {
return nodeConfig, chanPeer
}
// AddBeaconChainDatabase adds database support for beaconchain blocks on normal sharding nodes (not BeaconChain node)
func (node *Node) AddBeaconChainDatabase(db ethdb.Database) {
database := db
if database == nil {
database = ethdb.NewMemDatabase()
}
// TODO (chao) currently we use the same genesis block as normal shard
chain, err := node.GenesisBlockSetup(database, 0, true)
if err != nil {
utils.GetLogInstance().Error("Error when doing genesis setup")
os.Exit(1)
}
node.beaconChain = chain
node.BeaconWorker = worker.New(params.TestChainConfig, chain, &consensus.Consensus{}, pki.GetAddressFromPublicKey(node.SelfPeer.ConsensusPubKey), node.Consensus.ShardID)
}
// InitBlockChainFromDB retrieves the latest blockchain and state available from the local database
func (node *Node) InitBlockChainFromDB(db ethdb.Database, consensus *consensus.Consensus, isArchival bool) (*core.BlockChain, error) {
chainConfig := params.TestChainConfig

@ -5,7 +5,6 @@ import (
"math/big"
"math/rand"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
@ -13,7 +12,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/internal/utils/contract"
)
@ -26,8 +25,24 @@ const (
InitFreeFundInEther = 100
)
// genesisInitializer is a shardchain.DBInitializer adapter.
type genesisInitializer struct {
node *Node
}
// InitChainDB sets up a new genesis block in the database for the given shard.
func (gi *genesisInitializer) InitChainDB(db ethdb.Database, shardID uint32) error {
return gi.node.SetupGenesisBlock(db, shardID)
}
// GenesisBlockSetup setups a genesis blockchain.
func (node *Node) GenesisBlockSetup(db ethdb.Database, shardID uint32, isArchival bool) (*core.BlockChain, error) {
func (node *Node) SetupGenesisBlock(db ethdb.Database, shardID uint32) error {
utils.GetLogger().Info("setting up a brand new chain database",
"shardID", shardID)
if shardID == node.Consensus.ShardID {
node.isFirstTime = true
}
// Initialize genesis block and blockchain
// Tests account for txgen to use
@ -61,37 +76,44 @@ func (node *Node) GenesisBlockSetup(db ethdb.Database, shardID uint32, isArchiva
}
// Store genesis block into db.
gspec.MustCommit(db)
cacheConfig := core.CacheConfig{}
if isArchival {
cacheConfig = core.CacheConfig{Disabled: true, TrieNodeLimit: 256 * 1024 * 1024, TrieTimeLimit: 30 * time.Second}
_, err := gspec.Commit(db)
return err
}
// CreateTestBankKeys deterministically generates testing addresses.
func CreateTestBankKeys(numAddresses int) (keys []*ecdsa.PrivateKey, err error) {
rand.Seed(0)
bytes := make([]byte, 1000000)
for i := range bytes {
bytes[i] = byte(rand.Intn(100))
}
reader := strings.NewReader(string(bytes))
for i := 0; i < numAddresses; i++ {
key, err := ecdsa.GenerateKey(crypto.S256(), reader)
if err != nil {
return nil, err
}
keys = append(keys, key)
}
return core.NewBlockChain(db, &cacheConfig, gspec.Config, node.Consensus, vm.Config{}, nil)
return keys, nil
}
// CreateGenesisAllocWithTestingAddresses create the genesis block allocation that contains deterministically
// generated testing addresses with tokens. This is mostly used for generated simulated transactions in txgen.
// TODO: Remove it later when moving to production.
func (node *Node) CreateGenesisAllocWithTestingAddresses(numAddress int) core.GenesisAlloc {
rand.Seed(0)
len := 1000000
bytes := make([]byte, len)
for i := 0; i < len; i++ {
bytes[i] = byte(rand.Intn(100))
}
reader := strings.NewReader(string(bytes))
genesisAloc := make(core.GenesisAlloc)
for i := 0; i < numAddress; i++ {
testBankKey, _ := ecdsa.GenerateKey(crypto.S256(), reader)
for _, testBankKey := range node.TestBankKeys {
testBankAddress := crypto.PubkeyToAddress(testBankKey.PublicKey)
testBankFunds := big.NewInt(InitFreeFundInEther)
testBankFunds = testBankFunds.Mul(testBankFunds, big.NewInt(params.Ether))
genesisAloc[testBankAddress] = core.GenesisAccount{Balance: testBankFunds}
node.TestBankKeys = append(node.TestBankKeys, testBankKey)
}
return genesisAloc
}
// AddNodeAddressesToGenesisAlloc adds to the genesis block allocation the accounts used for network validators/nodes,
// including the account used by the nodes of the initial beacon chain and later new nodes.
func AddNodeAddressesToGenesisAlloc(genesisAlloc core.GenesisAlloc) {

@ -3,11 +3,8 @@ package node
import (
"bytes"
"context"
"encoding/gob"
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"math"
"math/big"
"os"
@ -17,7 +14,9 @@ import (
"syscall"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
pb "github.com/golang/protobuf/proto"
"github.com/harmony-one/bls/ffi/go/bls"
@ -179,7 +178,9 @@ func (node *Node) messageHandler(content []byte, sender string) {
case proto_node.PONG:
node.pongMessageHandler(msgPayload)
case proto_node.ShardState:
node.epochShardStateMessageHandler(msgPayload)
if err := node.epochShardStateMessageHandler(msgPayload); err != nil {
ctxerror.Log15(utils.GetLogger().Warn, err)
}
}
default:
utils.GetLogInstance().Error("Unknown", "MsgCategory", msgCategory)
@ -253,7 +254,7 @@ func (node *Node) BroadcastNewBlock(newBlock *types.Block) {
func (node *Node) VerifyNewBlock(newBlock *types.Block) error {
// TODO ek – where do we verify parent-child invariants,
// e.g. "child.Number == child.IsGenesis() ? 0 : parent.Number+1"?
err := node.blockchain.ValidateNewBlock(newBlock, pki.GetAddressFromPublicKey(node.SelfPeer.ConsensusPubKey))
err := node.Blockchain().ValidateNewBlock(newBlock, pki.GetAddressFromPublicKey(node.SelfPeer.ConsensusPubKey))
if err != nil {
return ctxerror.New("failed to ValidateNewBlock",
"blockHash", newBlock.Hash(),
@ -264,7 +265,7 @@ func (node *Node) VerifyNewBlock(newBlock *types.Block) error {
// TODO: verify the vrf randomness
_ = newBlock.Header().RandPreimage
err = node.ValidateNewShardState(newBlock, &node.CurrentStakes)
err = node.validateNewShardState(newBlock, &node.CurrentStakes)
if err != nil {
return ctxerror.New("failed to verify sharding state").WithCause(err)
}
@ -276,25 +277,42 @@ var BigMaxUint64 = new(big.Int).SetBytes([]byte{
255, 255, 255, 255, 255, 255, 255, 255,
})
// ValidateNewShardState validate whether the new shard state root matches
func (node *Node) ValidateNewShardState(block *types.Block, stakeInfo *map[common.Address]*structs.StakeInfo) error {
// TODO ek – how does RLP handle nil versus zero-sized slices? same?
proposedShardState := block.Header().ShardState
if len(proposedShardState) == 0 {
// For now, beacon validators simply wait until the beacon leader
// proposes a new sharding state.
// TODO ek – invoke view change if leader continues epoch for too long
// validateNewShardState validate whether the new shard state root matches
func (node *Node) validateNewShardState(block *types.Block, stakeInfo *map[common.Address]*structs.StakeInfo) error {
// Common case first – blocks without resharding proposal
header := block.Header()
if header.ShardStateHash == (common.Hash{}) {
// No new shard state was proposed
if block.ShardID() == 0 {
if core.IsEpochLastBlock(block) {
// TODO ek - invoke view change
return errors.New("beacon leader did not propose resharding")
}
} else {
if node.nextShardState.master != nil &&
!time.Now().Before(node.nextShardState.proposeTime) {
// TODO ek – invoke view change
return errors.New("regular leader did not propose resharding")
}
}
// We aren't expecting to reshard, so proceed to sign
return nil
}
proposed := header.ShardState
if block.ShardID() == 0 {
// Beacon validators independently recalculate the master state and
// compare it against the proposed copy.
nextEpoch := core.GetEpochFromBlockNumber(block.NumberU64()) + 1
nextEpoch := new(big.Int).Add(block.Header().Epoch, common.Big1)
// TODO ek – this may be called from regular shards,
// for vetting beacon chain blocks received during block syncing.
// DRand may or or may not get in the way. Test this out.
expected := core.CalculateNewShardState(node.blockchain, nextEpoch, stakeInfo)
if types.CompareShardState(expected, proposedShardState) != 0 {
expected, err := core.CalculateNewShardState(
node.Blockchain(), nextEpoch, stakeInfo)
if err != nil {
return ctxerror.New("cannot calculate expected shard state").
WithCause(err)
}
if types.CompareShardState(expected, proposed) != 0 {
// TODO ek – log state proposal differences
// TODO ek – this error should trigger view change
return errors.New("shard state proposal is different from expected")
@ -307,62 +325,45 @@ func (node *Node) ValidateNewShardState(block *types.Block, stakeInfo *map[commo
// The sanity check for the master proposal is done earlier,
// when the beacon block containing the master proposal is received
// and before it is admitted into the local beacon chain.
if len(proposedShardState) != 1 {
// TODO ek – this error should trigger view change
return ctxerror.New(
"regular resharding proposal has incorrect number of shards",
"numShards", len(proposedShardState))
}
proposed := &proposedShardState[0]
if proposed.ShardID != block.ShardID() {
return ctxerror.New(
"regular resharding proposal has incorrect shard ID",
"blockShardID", block.ShardID(),
"proposalShardID", proposed.ShardID)
}
epoch := block.Header().Epoch
// TODO ek – this check is due to uint64-based block number
// processing and is only a temporary hack. Very unlikely to hit
// this in testnet, but still logically necessary.
if epoch.Cmp(common.Big0) < 0 || epoch.Cmp(BigMaxUint64) > 0 {
return ctxerror.New("block epoch out of range",
"epoch", block.Header().Epoch)
}
epochLastBlockNum := core.GetLastBlockNumberFromEpoch(epoch.Uint64())
epochLastBlock := node.beaconChain.GetBlockByNumber(epochLastBlockNum)
if epochLastBlock == nil {
// TODO ek - restore this check once the leader is made to delay
// proposal until it thinks that the quorum has synchronized
// through the end of beacon chain.
// See the corresponding to-do in proposeLocalShardState.
//return ctxerror.New("cannot find epoch-last block of beacon chain",
// "epoch", block.Header().Epoch,
// "epochLastBlockNum", epochLastBlockNum)
// For now just agree to the leader proposal if we aren't sure.
return nil
}
masterProposal := epochLastBlock.Header().ShardState
//
// TODO ek – fetch masterProposal from beaconchain instead
masterProposal := node.nextShardState.master.ShardState
expected := masterProposal.FindCommitteeByID(block.ShardID())
if expected == nil {
// The beacon committee “disowned” our shard,
// which means that this is the last epoch for us for now.
// The local proposal should reflect this by having an empty
// table with no leader.
if len(proposed.NodeList) != 0 {
// TODO ek – this error should trigger view change
switch len(proposed) {
case 0:
// Proposal to discontinue shard
if expected != nil {
// TODO ek – invoke view change
return errors.New(
"leader proposed to continue against beacon decision")
"leader proposed to disband against beacon decision")
}
case 1:
// Proposal to continue shard
proposed := proposed[0]
// Sanity check: Shard ID should match
if proposed.ShardID != block.ShardID() {
// TODO ek – invoke view change
return ctxerror.New("proposal has incorrect shard ID",
"proposedShard", proposed.ShardID,
"blockShard", block.ShardID())
}
if types.CompareNodeID(&proposed.Leader, &types.NodeID{}) != 0 {
// TODO ek – this error should trigger view change
// Did beaconchain say we are no more?
if expected == nil {
// TODO ek – invoke view change
return errors.New(
"leader proposed empty committee with non-empty leader")
"leader proposed to continue against beacon decision")
}
} else if types.CompareCommittee(expected, proposed) != 0 {
// TODO ek – log differences
// TODO ek – this error should trigger view change
return errors.New("proposal differs from one in beacon chain")
// Did beaconchain say the same proposal?
if types.CompareCommittee(expected, &proposed) != 0 {
// TODO ek – log differences
// TODO ek – invoke view change
return errors.New("proposal differs from one in beacon chain")
}
default:
// TODO ek – invoke view change
return ctxerror.New(
"regular resharding proposal has incorrect number of shards",
"numShards", len(proposed))
}
}
return nil
@ -418,35 +419,37 @@ func (node *Node) PostConsensusProcessing(newBlock *types.Block) {
// TODO: update staking information once per epoch.
node.UpdateStakingList(node.QueryStakeInfo())
node.printStakingList()
// TODO ek – this is a temp hack until beacon chain sync is fixed
if len(newBlock.Header().ShardState) > 0 && node.Consensus.IsLeader {
}
newBlockHeader := newBlock.Header()
if newBlockHeader.ShardStateHash != (common.Hash{}) {
if node.Consensus.ShardID == 0 {
// TODO ek – this is a temp hack until beacon chain sync is fixed
// End-of-epoch block on beacon chain; block's EpochState is the
// master resharding table. Broadcast it to the network.
epochShardStateMessage := proto_node.ConstructEpochShardStateMessage(
types.EpochShardState{
Epoch: newBlock.Header().Epoch.Uint64() + 1,
ShardState: newBlock.Header().ShardState,
},
)
err := node.host.SendMessageToGroups(
[]p2p.GroupID{node.NodeConfig.GetClientGroupID()},
host.ConstructP2pMessage(byte(0), epochShardStateMessage))
if err != nil {
if err := node.broadcastEpochShardState(newBlock); err != nil {
e := ctxerror.New("cannot broadcast shard state").WithCause(err)
ctxerror.Log15(utils.GetLogInstance().Error, e)
}
}
}
if core.IsEpochLastBlock(newBlock) {
// TODO ek – wait for beacon chain's last block to be available
// TODO ek - retrieve the global resharding assignment
// TODO ek – if needed, (start to) move to another shard
node.transitionIntoNextEpoch(newBlockHeader.ShardState)
}
}
func (node *Node) broadcastEpochShardState(newBlock *types.Block) error {
epochShardStateMessage := proto_node.ConstructEpochShardStateMessage(
types.EpochShardState{
Epoch: newBlock.Header().Epoch.Uint64() + 1,
ShardState: newBlock.Header().ShardState,
},
)
return node.host.SendMessageToGroups(
[]p2p.GroupID{node.NodeConfig.GetClientGroupID()},
host.ConstructP2pMessage(byte(0), epochShardStateMessage))
}
// AddNewBlock is usedd to add new block into the blockchain.
func (node *Node) AddNewBlock(newBlock *types.Block) {
blockNum, err := node.blockchain.InsertChain([]*types.Block{newBlock})
blockNum, err := node.Blockchain().InsertChain([]*types.Block{newBlock})
if err != nil {
utils.GetLogInstance().Debug("Error adding new block to blockchain", "blockNum", blockNum, "hash", newBlock.Header().Hash(), "Error", err)
} else {
@ -583,7 +586,10 @@ func (node *Node) pongMessageHandler(msgPayload []byte) int {
}
if pong.ShardID != node.Consensus.ShardID {
utils.GetLogInstance().Error("Received Pong message for the wrong shard", "receivedShardID", pong.ShardID)
utils.GetLogInstance().Error(
"Received Pong message for the wrong shard",
"receivedShardID", pong.ShardID,
"expectedShardID", node.Consensus.ShardID)
return 0
}
@ -657,89 +663,123 @@ func (node *Node) pongMessageHandler(msgPayload []byte) int {
return 0
}
func (node *Node) epochShardStateMessageHandler(msgPayload []byte) int {
utils.GetLogInstance().Error("[Received new shard state]")
func (node *Node) epochShardStateMessageHandler(msgPayload []byte) error {
logger := utils.GetLogInstance()
getLogger := func() log.Logger { return utils.WithCallerSkip(logger, 1) }
epochShardState, err := proto_node.DeserializeEpochShardStateFromMessage(msgPayload)
if err != nil {
utils.GetLogInstance().Error("Can't get shard state Message", "error", err)
return -1
return ctxerror.New("Can't get shard state message").WithCause(err)
}
if (node.Consensus != nil && node.Consensus.ShardID != 0) || node.NodeConfig.Role() == nodeconfig.NewNode {
node.processEpochShardState(epochShardState)
if node.Consensus == nil && node.NodeConfig.Role() != nodeconfig.NewNode {
return nil
}
return 0
// Remember the master sharding state if the epoch ID matches.
curEpoch := node.Blockchain().CurrentBlock().Header().Epoch
expectedEpoch := new(big.Int).Add(curEpoch, common.Big1)
receivedEpoch := big.NewInt(int64(epochShardState.Epoch))
if receivedEpoch.Cmp(expectedEpoch) != 0 {
return ctxerror.New("invalid epoch in epoch shard state message",
"receivedEpoch", receivedEpoch,
"expectedEpoch", expectedEpoch)
}
getLogger().Info("received new shard state", "epoch", receivedEpoch)
node.nextShardState.master = epochShardState
if node.Consensus.IsLeader {
// Wait a bit to allow the master table to reach other validators.
node.nextShardState.proposeTime = time.Now().Add(5 * time.Second)
} else {
// Wait a bit to allow the master table to reach the leader,
// and to allow the leader to propose next shard state based upon it.
node.nextShardState.proposeTime = time.Now().Add(15 * time.Second)
}
// TODO ek – this should be done from replaying beaconchain once
// beaconchain sync is fixed
err = node.Beaconchain().WriteShardState(
receivedEpoch, epochShardState.ShardState)
if err != nil {
return ctxerror.New("cannot store shard state", "epoch", receivedEpoch).
WithCause(err)
}
return nil
}
func (node *Node) processEpochShardState(epochShardState *types.EpochShardState) {
shardState := epochShardState.ShardState
epoch := epochShardState.Epoch
func (node *Node) transitionIntoNextEpoch(shardState types.ShardState) {
logger := utils.GetLogInstance()
getLogger := func() log.Logger { return utils.WithCallerSkip(logger, 1) }
logger = logger.New(
"blsPubKey", hex.EncodeToString(node.Consensus.PubKey.Serialize()),
"curShard", node.Blockchain().ShardID(),
"curLeader", node.Consensus.IsLeader)
for _, c := range shardState {
utils.GetLogInstance().Debug("new shard information", "shardID", c.ShardID, "NodeList", c.NodeList)
logger.Debug("new shard information",
"shardID", c.ShardID,
"nodeList", c.NodeList)
}
myShardID, isNextLeader := findRoleInShardState(
node.Consensus.PubKey, shardState)
logger = logger.New(
"nextShard", myShardID,
"nextLeader", isNextLeader)
if myShardID == math.MaxUint32 {
getLogger().Info("Somehow I got kicked out. Exiting")
os.Exit(8) // 8 represents it's a loop and the program restart itself
}
myShardID := uint32(math.MaxUint32)
isNextLeader := false
myBlsPubKey := node.Consensus.PubKey.Serialize()
myShardState := types.Committee{}
for _, shard := range shardState {
for _, nodeID := range shard.NodeList {
if bytes.Compare(nodeID.BlsPublicKey[:], myBlsPubKey) == 0 {
myShardID = shard.ShardID
isNextLeader = shard.Leader == nodeID
myShardState = shard
}
myShardState := shardState[myShardID]
// Update public keys
var publicKeys []*bls.PublicKey
for idx, nodeID := range myShardState.NodeList {
key := &bls.PublicKey{}
err := key.Deserialize(nodeID.BlsPublicKey[:])
if err != nil {
getLogger().Error("Failed to deserialize BLS public key in shard state",
"idx", idx,
"error", err)
}
publicKeys = append(publicKeys, key)
}
node.Consensus.UpdatePublicKeys(publicKeys)
node.DRand.UpdatePublicKeys(publicKeys)
if myShardID != uint32(math.MaxUint32) {
// Update public keys
ss := myShardState
publicKeys := []*bls.PublicKey{}
for _, nodeID := range ss.NodeList {
key := &bls.PublicKey{}
err := key.Deserialize(nodeID.BlsPublicKey[:])
if err != nil {
utils.GetLogInstance().Error("Failed to deserialize BLS public key in shard state", "error", err)
}
publicKeys = append(publicKeys, key)
if node.Blockchain().ShardID() == myShardID {
getLogger().Info("staying in the same shard")
} else {
getLogger().Info("moving to another shard")
if err := node.shardChains.Close(); err != nil {
getLogger().Error("cannot close shard chains", "error", err)
}
node.Consensus.UpdatePublicKeys(publicKeys)
node.DRand.UpdatePublicKeys(publicKeys)
restartProcess(getRestartArguments(myShardID))
}
}
aboutLeader := ""
if nodeconfig.GetDefaultConfig().IsLeader() {
aboutLeader = "I am not leader anymore"
if isNextLeader {
aboutLeader = "I am still leader"
}
} else {
aboutLeader = "I am still validator"
if isNextLeader {
aboutLeader = "I become the leader"
func findRoleInShardState(
key *bls.PublicKey, state types.ShardState,
) (shardID uint32, isLeader bool) {
keyBytes := key.Serialize()
for idx, shard := range state {
for _, nodeID := range shard.NodeList {
if bytes.Compare(nodeID.BlsPublicKey[:], keyBytes) == 0 {
return uint32(idx), shard.Leader == nodeID
}
}
if node.blockchain.ShardID() == myShardID {
utils.GetLogInstance().Info(fmt.Sprintf("[Resharded][epoch:%d] I stay at shard %d, %s", epoch, myShardID, aboutLeader), "BlsPubKey", hex.EncodeToString(myBlsPubKey))
} else {
utils.GetLogInstance().Info(fmt.Sprintf("[Resharded][epoch:%d] I got resharded to shard %d from shard %d, %s", epoch, myShardID, node.blockchain.ShardID(), aboutLeader), "BlsPubKey", hex.EncodeToString(myBlsPubKey))
node.storeEpochShardState(epochShardState)
}
return math.MaxUint32, false
}
execFile, err := getBinaryPath()
if err != nil {
utils.GetLogInstance().Crit("Failed to get program path when restarting program", "error", err, "file", execFile)
}
args := getRestartArguments(myShardID)
utils.GetLogInstance().Info("Restarting program", "args", args, "env", os.Environ())
err = syscall.Exec(execFile, args, os.Environ())
if err != nil {
utils.GetLogInstance().Crit("Failed to restart program after resharding", "error", err)
}
}
} else {
utils.GetLogInstance().Info(fmt.Sprintf("[Resharded][epoch:%d] Somehow I got kicked out. Exiting", epoch), "BlsPubKey", hex.EncodeToString(myBlsPubKey))
os.Exit(8) // 8 represents it's a loop and the program restart itself
func restartProcess(args []string) {
execFile, err := getBinaryPath()
if err != nil {
utils.GetLogInstance().Crit("Failed to get program path when restarting program", "error", err, "file", execFile)
}
utils.GetLogInstance().Info("Restarting program", "args", args, "env", os.Environ())
err = syscall.Exec(execFile, args, os.Environ())
if err != nil {
utils.GetLogInstance().Crit("Failed to restart program after resharding", "error", err)
}
panic("syscall.Exec() is not supposed to return")
}
func getRestartArguments(myShardID uint32) []string {
@ -780,38 +820,6 @@ func getBinaryPath() (argv0 string, err error) {
return
}
// Stores the epoch shard state into local file
// TODO: think about storing it into level db.
func (node *Node) storeEpochShardState(epochShardState *types.EpochShardState) {
byteBuffer := bytes.NewBuffer([]byte{})
encoder := gob.NewEncoder(byteBuffer)
err := encoder.Encode(epochShardState)
if err != nil {
utils.GetLogInstance().Error("[Resharded] Failed to encode epoch shard state", "error", err)
}
err = ioutil.WriteFile("./epoch_shard_state"+node.SelfPeer.IP+node.SelfPeer.Port, byteBuffer.Bytes(), 0644)
if err != nil {
utils.GetLogInstance().Error("[Resharded] Failed to store epoch shard state in local file", "error", err)
}
}
func (node *Node) retrieveEpochShardState() (*types.EpochShardState, error) {
b, err := ioutil.ReadFile("./epoch_shard_state" + node.SelfPeer.IP + node.SelfPeer.Port)
if err != nil {
utils.GetLogInstance().Error("[Resharded] Failed to retrieve epoch shard state", "error", err)
}
epochShardState := new(types.EpochShardState)
r := bytes.NewBuffer(b)
decoder := gob.NewDecoder(r)
err = decoder.Decode(epochShardState)
if err != nil {
return nil, fmt.Errorf("Decode local epoch shard state error")
}
return epochShardState, nil
}
// ConsensusMessageHandler passes received message in node_handler to consensus
func (node *Node) ConsensusMessageHandler(msgPayload []byte) {
if node.Consensus.ConsensusVersion == "v1" {

@ -23,7 +23,7 @@ func TestAddNewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
node := New(host, consensus, nil, false)
node := New(host, consensus, testDBFactory, false)
selectedTxs := node.getTransactionsForNewBlock(MaxNumberOfTransactionsPerBlock)
node.Worker.CommitTransactions(selectedTxs)
@ -31,7 +31,7 @@ func TestAddNewBlock(t *testing.T) {
node.AddNewBlock(block)
if node.blockchain.CurrentBlock().NumberU64() != 1 {
if node.Blockchain().CurrentBlock().NumberU64() != 1 {
t.Error("New block is not added successfully")
}
}
@ -48,7 +48,7 @@ func TestVerifyNewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
node := New(host, consensus, nil, false)
node := New(host, consensus, testDBFactory, false)
selectedTxs := node.getTransactionsForNewBlock(MaxNumberOfTransactionsPerBlock)
node.Worker.CommitTransactions(selectedTxs)

@ -4,6 +4,9 @@ import (
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/utils"
@ -53,7 +56,7 @@ func (node *Node) WaitForConsensusReady(readySignal chan struct{}, stopChan chan
firstTime = false
}
if len(node.pendingTransactions) >= threshold {
utils.GetLogInstance().Debug("PROPOSING NEW BLOCK ------------------------------------------------", "blockNum", node.blockchain.CurrentBlock().NumberU64()+1, "threshold", threshold, "pendingTransactions", len(node.pendingTransactions))
utils.GetLogInstance().Debug("PROPOSING NEW BLOCK ------------------------------------------------", "blockNum", node.Blockchain().CurrentBlock().NumberU64()+1, "threshold", threshold, "pendingTransactions", len(node.pendingTransactions))
// Normal tx block consensus
selectedTxs := node.getTransactionsForNewBlock(MaxNumberOfTransactionsPerBlock)
if len(selectedTxs) != 0 {
@ -123,7 +126,7 @@ func (node *Node) WaitForConsensusReadyv2(readySignal chan struct{}, stopChan ch
if len(selectedTxs) == 0 {
continue
}
utils.GetLogInstance().Debug("PROPOSING NEW BLOCK ------------------------------------------------", "blockNum", node.blockchain.CurrentBlock().NumberU64()+1, "threshold", threshold, "selectedTxs", len(selectedTxs))
utils.GetLogInstance().Debug("PROPOSING NEW BLOCK ------------------------------------------------", "blockNum", node.Blockchain().CurrentBlock().NumberU64()+1, "threshold", threshold, "selectedTxs", len(selectedTxs))
node.Worker.CommitTransactions(selectedTxs)
block, err := node.Worker.Commit()
if err != nil {
@ -143,52 +146,56 @@ func (node *Node) WaitForConsensusReadyv2(readySignal chan struct{}, stopChan ch
}()
}
func (node *Node) proposeShardState(block *types.Block) {
if node.Consensus.ShardID == 0 {
node.proposeBeaconShardState(block)
} else {
func (node *Node) proposeShardState(block *types.Block) error {
switch node.Consensus.ShardID {
case 0:
return node.proposeBeaconShardState(block)
default:
node.proposeLocalShardState(block)
return nil
}
}
func (node *Node) proposeBeaconShardState(block *types.Block) {
func (node *Node) proposeBeaconShardState(block *types.Block) error {
// TODO ek - replace this with variable epoch logic.
if !core.IsEpochLastBlock(block) {
// We haven't reached the end of this epoch; don't propose yet.
return
return nil
}
nextEpoch := new(big.Int).Add(block.Header().Epoch, common.Big1)
shardState, err := core.CalculateNewShardState(
node.Blockchain(), nextEpoch, &node.CurrentStakes)
if err != nil {
return err
}
nextEpoch := core.GetEpochFromBlockNumber(block.NumberU64()) + 1
shardState := core.CalculateNewShardState(node.blockchain, nextEpoch, &node.CurrentStakes)
block.AddShardState(shardState)
}
func (node *Node) beaconEpochEndBlock(epoch *big.Int) *types.Block {
// TODO ek – replace this with variable epoch logic.
lastBlockNumber := core.GetLastBlockNumberFromEpoch(epoch.Uint64())
return node.beaconChain.GetBlockByNumber(lastBlockNumber)
return nil
}
func (node *Node) proposeLocalShardState(block *types.Block) {
epochLastBlock := node.beaconEpochEndBlock(block.Header().Epoch)
if epochLastBlock == nil {
// Beacon committee has yet to end epoch; don't propose yet.
logger := block.Logger(utils.GetLogInstance())
getLogger := func() log.Logger { return utils.WithCallerSkip(logger, 1) }
// TODO ek – read this from beaconchain once BC sync is fixed
if node.nextShardState.master == nil {
getLogger().Debug("yet to receive master proposal from beaconchain")
return
}
logger = logger.New(
"nextEpoch", node.nextShardState.master.Epoch,
"proposeTime", node.nextShardState.proposeTime)
if time.Now().Before(node.nextShardState.proposeTime) {
getLogger().Debug("still waiting for shard state to propagate")
return
}
// TODO ek – delay proposal until we are fairly sure that the quorum has
// synchronized through the end of epoch on the beacon chain.
// Otherwise our proposal may fail to reach consensus.
// For now we are fine because we cheat by disabling the validator check.
shardState := make(types.ShardState, 1)
beaconShardState := epochLastBlock.Header().ShardState
committee := beaconShardState.FindCommitteeByID(block.ShardID())
masterShardState := node.nextShardState.master.ShardState
var localShardState types.ShardState
committee := masterShardState.FindCommitteeByID(block.ShardID())
if committee != nil {
// Propose as announced in beacon chain.
shardState[0] = *committee
getLogger().Info("found local shard info; proposing it")
localShardState = append(localShardState, *committee)
} else {
utils.GetLogInstance().Info(
"beacon committee disowned us; proposing to disband",
"shardID", block.ShardID())
getLogger().Info("beacon committee disowned us; proposing nothing")
// Leave local proposal empty to signal the end of shard (disbanding).
}
block.AddShardState(shardState)
block.AddShardState(localShardState)
}

@ -44,7 +44,7 @@ func (node *Node) getNeighborPeers(neighbor *sync.Map) []p2p.Peer {
// DoSyncWithoutConsensus gets sync-ed to blockchain without joining consensus
func (node *Node) DoSyncWithoutConsensus() {
go node.DoSyncing(node.blockchain, node.Worker, node.GetSyncingPeers, false) //Don't join consensus
go node.DoSyncing(node.Blockchain(), node.Worker, node.GetSyncingPeers, false) //Don't join consensus
}
// GetBeaconSyncingPeers returns a list of peers for beaconchain syncing
@ -73,7 +73,7 @@ func (node *Node) DoBeaconSyncing() {
}
}
node.beaconSync.AddLastMileBlock(beaconBlock)
node.beaconSync.SyncLoop(node.beaconChain, node.BeaconWorker, false, true)
node.beaconSync.SyncLoop(node.Beaconchain(), node.BeaconWorker, false, true)
}
}
}
@ -139,7 +139,7 @@ func (node *Node) SupportSyncing() {
go node.SendNewBlockToUnsync()
if node.NodeConfig.Role() != nodeconfig.ShardLeader && node.NodeConfig.Role() != nodeconfig.BeaconLeader {
go node.DoSyncing(node.blockchain, node.Worker, node.GetSyncingPeers, true)
go node.DoSyncing(node.Blockchain(), node.Worker, node.GetSyncingPeers, true)
}
}
@ -202,12 +202,12 @@ func (node *Node) CalculateResponse(request *downloader_pb.DownloaderRequest) (*
case downloader_pb.DownloaderRequest_HEADER:
var startHeaderHash []byte
if request.BlockHash == nil {
tmp := node.blockchain.Genesis().Hash()
tmp := node.Blockchain().Genesis().Hash()
startHeaderHash = tmp[:]
} else {
startHeaderHash = request.BlockHash
}
for block := node.blockchain.CurrentBlock(); block != nil; block = node.blockchain.GetBlockByHash(block.Header().ParentHash) {
for block := node.Blockchain().CurrentBlock(); block != nil; block = node.Blockchain().GetBlockByHash(block.Header().ParentHash) {
blockHash := block.Hash()
if bytes.Compare(blockHash[:], startHeaderHash) == 0 {
break
@ -219,7 +219,7 @@ func (node *Node) CalculateResponse(request *downloader_pb.DownloaderRequest) (*
for _, bytes := range request.Hashes {
var hash common.Hash
hash.SetBytes(bytes)
block := node.blockchain.GetBlockByHash(hash)
block := node.Blockchain().GetBlockByHash(hash)
if block == nil {
continue
}
@ -230,7 +230,7 @@ func (node *Node) CalculateResponse(request *downloader_pb.DownloaderRequest) (*
}
case downloader_pb.DownloaderRequest_BLOCKHEIGHT:
response.BlockHeight = node.blockchain.CurrentBlock().NumberU64()
response.BlockHeight = node.Blockchain().CurrentBlock().NumberU64()
// this is the out of sync node acts as grpc server side
case downloader_pb.DownloaderRequest_NEWBLOCK:

@ -7,8 +7,10 @@ import (
"time"
bls2 "github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/internal/shardchain"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/drand"
proto_discovery "github.com/harmony-one/harmony/api/proto/discovery"
@ -19,6 +21,8 @@ import (
"github.com/harmony-one/harmony/p2p/p2pimpl"
)
var testDBFactory = &shardchain.MemDBFactory{}
func TestNewNode(t *testing.T) {
pubKey := bls2.RandPrivateKey().GetPublicKey()
leader := p2p.Peer{IP: "127.0.0.1", Port: "8882", ConsensusPubKey: pubKey}
@ -31,16 +35,16 @@ func TestNewNode(t *testing.T) {
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
node := New(host, consensus, nil, false)
node := New(host, consensus, testDBFactory, false)
if node.Consensus == nil {
t.Error("Consensus is not initialized for the node")
}
if node.blockchain == nil {
if node.Blockchain() == nil {
t.Error("Blockchain is not initialized for the node")
}
if node.blockchain.CurrentBlock() == nil {
if node.Blockchain().CurrentBlock() == nil {
t.Error("Genesis block is not initialized for the node")
}
}
@ -58,7 +62,7 @@ func TestGetSyncingPeers(t *testing.T) {
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
node := New(host, consensus, nil, false)
node := New(host, consensus, testDBFactory, false)
peer := p2p.Peer{IP: "127.0.0.1", Port: "8000"}
peer2 := p2p.Peer{IP: "127.0.0.1", Port: "8001"}
node.Neighbors.Store("minh", peer)
@ -102,7 +106,7 @@ func TestAddPeers(t *testing.T) {
}
dRand := drand.New(host, 0, []p2p.Peer{leader, validator}, leader, nil, nil)
node := New(host, consensus, nil, false)
node := New(host, consensus, testDBFactory, false)
node.DRand = dRand
r1 := node.AddPeers(peers1)
e1 := 2
@ -148,7 +152,7 @@ func TestAddBeaconPeer(t *testing.T) {
}
dRand := drand.New(host, 0, []p2p.Peer{leader, validator}, leader, nil, nil)
node := New(host, consensus, nil, false)
node := New(host, consensus, testDBFactory, false)
node.DRand = dRand
for _, p := range peers1 {
ret := node.AddBeaconPeer(p)
@ -220,7 +224,7 @@ func TestPingPongHandler(t *testing.T) {
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
node := New(host, consensus, nil, false)
node := New(host, consensus, testDBFactory, false)
//go sendPingMessage(leader)
go sendPongMessage(node, leader)
go exitServer()

@ -42,7 +42,7 @@ var (
// StartRPC start RPC service
func (node *Node) StartRPC(nodePort string) error {
// Gather all the possible APIs to surface
apiBackend = core.NewBackend(node.blockchain, node.TxPool, node.accountManager)
apiBackend = core.NewBackend(node.Blockchain(), node.TxPool, node.accountManager)
apis := hmyapi.GetAPIs(apiBackend)
for _, service := range node.serviceManager.GetServices() {

@ -37,14 +37,14 @@ func (node *Node) setupForShardLeader() {
// Register new block service.
node.serviceManager.RegisterService(service.BlockProposal, blockproposal.New(node.Consensus.ReadySignal, node.WaitForConsensusReadyv2))
// Register client support service.
node.serviceManager.RegisterService(service.ClientSupport, clientsupport.New(node.blockchain.State, node.CallFaucetContract, node.getDeployedStakingContract, node.SelfPeer.IP, node.SelfPeer.Port))
node.serviceManager.RegisterService(service.ClientSupport, clientsupport.New(node.Blockchain().State, node.CallFaucetContract, node.getDeployedStakingContract, node.SelfPeer.IP, node.SelfPeer.Port))
}
func (node *Node) setupForShardValidator() {
nodeConfig, chanPeer := node.initNodeConfiguration()
// Register client support service.
node.serviceManager.RegisterService(service.ClientSupport, clientsupport.New(node.blockchain.State, node.CallFaucetContract, node.getDeployedStakingContract, node.SelfPeer.IP, node.SelfPeer.Port))
node.serviceManager.RegisterService(service.ClientSupport, clientsupport.New(node.Blockchain().State, node.CallFaucetContract, node.getDeployedStakingContract, node.SelfPeer.IP, node.SelfPeer.Port))
// Register peer discovery service. "0" is the beacon shard ID. No need to do staking for beacon chain node.
node.serviceManager.RegisterService(service.PeerDiscovery, discovery.New(node.host, nodeConfig, chanPeer, node.AddBeaconPeer))
// Register networkinfo service. "0" is the beacon shard ID
@ -69,7 +69,7 @@ func (node *Node) setupForBeaconLeader() {
// Register new block service.
node.serviceManager.RegisterService(service.BlockProposal, blockproposal.New(node.Consensus.ReadySignal, node.WaitForConsensusReadyv2))
// Register client support service.
node.serviceManager.RegisterService(service.ClientSupport, clientsupport.New(node.blockchain.State, node.CallFaucetContract, node.getDeployedStakingContract, node.SelfPeer.IP, node.SelfPeer.Port))
node.serviceManager.RegisterService(service.ClientSupport, clientsupport.New(node.Blockchain().State, node.CallFaucetContract, node.getDeployedStakingContract, node.SelfPeer.IP, node.SelfPeer.Port))
// TODO(minhdoan): We will remove the old client support and use the new client support which uses new message protocol.
// Register client new support service.
node.serviceManager.RegisterService(service.RestClientSupport, restclientsupport.New(
@ -86,7 +86,7 @@ func (node *Node) setupForBeaconValidator() {
nodeConfig, chanPeer := node.initNodeConfiguration()
// Register client support service.
node.serviceManager.RegisterService(service.ClientSupport, clientsupport.New(node.blockchain.State, node.CallFaucetContract, node.getDeployedStakingContract, node.SelfPeer.IP, node.SelfPeer.Port))
node.serviceManager.RegisterService(service.ClientSupport, clientsupport.New(node.Blockchain().State, node.CallFaucetContract, node.getDeployedStakingContract, node.SelfPeer.IP, node.SelfPeer.Port))
// Register peer discovery service. No need to do staking for beacon chain node.
node.serviceManager.RegisterService(service.PeerDiscovery, discovery.New(node.host, nodeConfig, chanPeer, nil))
// Register networkinfo service.
@ -104,7 +104,7 @@ func (node *Node) setupForNewNode() {
nodeConfig, chanPeer := node.initNodeConfiguration()
// Register staking service.
node.serviceManager.RegisterService(service.Staking, staking.New(node.host, node.StakingAccount, node.beaconChain, node.NodeConfig.ConsensusPubKey))
node.serviceManager.RegisterService(service.Staking, staking.New(node.host, node.StakingAccount, node.Beaconchain(), node.NodeConfig.ConsensusPubKey))
// Register peer discovery service. "0" is the beacon shard ID
node.serviceManager.RegisterService(service.PeerDiscovery, discovery.New(node.host, nodeConfig, chanPeer, node.AddBeaconPeer))
// Register networkinfo service. "0" is the beacon shard ID

@ -41,7 +41,7 @@ func (node *Node) UpdateStakingList(stakeInfoReturnValue *structs.StakeInfoRetur
lockPeriodCount := stakeInfoReturnValue.LockPeriodCounts[i]
startEpoch := core.GetEpochFromBlockNumber(blockNum.Uint64())
curEpoch := core.GetEpochFromBlockNumber(node.blockchain.CurrentBlock().NumberU64())
curEpoch := core.GetEpochFromBlockNumber(node.Blockchain().CurrentBlock().NumberU64())
if startEpoch == curEpoch {
continue // The token are counted into stakes at the beginning of next epoch.

@ -37,7 +37,7 @@ func TestUpdateStakingList(t *testing.T) {
if err != nil {
t.Fatalf("Cannot craeate consensus: %v", err)
}
node := New(host, consensus, nil, false)
node := New(host, consensus, testDBFactory, false)
for i := 0; i < 5; i++ {
selectedTxs := node.getTransactionsForNewBlock(MaxNumberOfTransactionsPerBlock)

@ -69,7 +69,7 @@ EOU
exit 0
}
DB=
DB=false
TXGEN=true
DURATION=60
MIN=5
@ -80,7 +80,7 @@ DRYRUN=
while getopts "hdtD:m:s:nS" option; do
case $option in
h) usage ;;
d) DB='-db_supported' ;;
d) DB=true ;;
t) TXGEN=false ;;
D) DURATION=$OPTARG ;;
m) MIN=$OPTARG ;;
@ -127,9 +127,15 @@ echo "launching boot node ..."
$DRYRUN $ROOT/bin/bootnode -port 19876 > $log_folder/bootnode.log 2>&1 | tee -a $LOG_FILE &
sleep 1
BN_MA=$(grep "BN_MA" $log_folder/bootnode.log | awk -F\= ' { print $2 } ')
HMY_OPT2=" -bootnodes $BN_MA"
echo "bootnode launched." + " $BN_MA"
HMY_OPT3=" -is_genesis"
unset -v base_args
declare -a base_args args
base_args=(-log_folder "${log_folder}" -min_peers "${MIN}" -bootnodes "${BN_MA}")
if "${DB}"
then
base_args=("${base_args[@]}" -db_supported)
fi
NUM_NN=0
@ -139,27 +145,20 @@ sleep 2
i=0
while IFS='' read -r line || [[ -n "$line" ]]; do
IFS=' ' read ip port mode shardID <<< $line
if [ "$mode" == "leader" ]; then
echo "launching leader ..."
$DRYRUN $ROOT/bin/harmony -ip $ip -port $port -log_folder $log_folder $DB -account_index $i -min_peers $MIN $HMY_OPT2 $HMY_OPT3 -key /tmp/$ip-$port.key -is_leader 2>&1 | tee -a $LOG_FILE &
fi
if [ "$mode" == "leader_archival" ]; then
echo "launching leader ..."
$DRYRUN $ROOT/bin/harmony -ip $ip -port $port -log_folder $log_folder $DB -account_index $i -min_peers $MIN $HMY_OPT2 -key /tmp/$ip-$port.key -is_leader -is_archival 2>&1 | tee -a $LOG_FILE &
fi
if [ "$mode" == "validator" ]; then
echo "launching validator ..."
$DRYRUN $ROOT/bin/harmony -ip $ip -port $port -log_folder $log_folder $DB -account_index $i -min_peers $MIN $HMY_OPT2 $HMY_OPT3 -key /tmp/$ip-$port.key 2>&1 | tee -a $LOG_FILE &
fi
if [ "$mode" == "archival" ]; then
echo "launching archival node ... wait"
$DRYRUN $ROOT/bin/harmony -ip $ip -port $port -log_folder $log_folder $DB -account_index $i -min_peers $MIN $HMY_OPT2 -key /tmp/$ip-$port.key -is_archival 2>&1 | tee -a $LOG_FILE &
fi
if [[ "$mode" == "newnode" && "$SYNC" == "true" ]]; then
(( NUM_NN += 30 ))
echo "launching new node ..."
(sleep $NUM_NN; $DRYRUN $ROOT/bin/harmony -ip $ip -port $port -log_folder $log_folder $DB -account_index $i -min_peers $MIN $HMY_OPT2 -key /tmp/$ip-$port.key 2>&1 | tee -a $LOG_FILE ) &
fi
args=("${base_args[@]}" -ip "${ip}" -port "${port}" -account_index "${i}" -key "/tmp/${ip}-${port}.key" -db_dir "db-${ip}-${port}")
case "${mode}" in
leader*|validator*) args=("${args[@]}" -is_genesis);;
esac
case "${mode}" in leader*) args=("${args[@]}" -is_leader);; esac
case "${mode}" in *archival|archival) args=("${args[@]}" -is_archival);; esac
case "${mode}" in
newnode)
"${SYNC}" || continue
sleep "${NUM_NN}"
NUM_NN=$((${NUM_NN} + 30))
;;
esac
$DRYRUN "${ROOT}/bin/harmony" "${args[@]}" 2>&1 | tee -a "${LOG_FILE}" &
i=$((i+1))
done < $config
@ -169,7 +168,7 @@ if [ "$TXGEN" == "true" ]; then
line=$(grep client $config)
IFS=' ' read ip port mode shardID <<< $line
if [ "$mode" == "client" ]; then
$DRYRUN $ROOT/bin/txgen -log_folder $log_folder -duration $DURATION -ip $ip -port $port $HMY_OPT2 2>&1 | tee -a $LOG_FILE
$DRYRUN $ROOT/bin/txgen -log_folder $log_folder -duration $DURATION -ip $ip -port $port $HMY_OPT2 > $LOG_FILE 2>&1
fi
else
sleep $DURATION

Loading…
Cancel
Save