Merge pull request #1870 from rlan35/staking_tx

Synchronize shard chains with beacon chain after stakingEpoch. Add PreStakingEpoch
pull/1872/head
Rongjian Lan 5 years ago committed by GitHub
commit 9cd64ef00a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      block/factory/factory.go
  2. 2
      cmd/client/txgen/main.go
  3. 11
      cmd/harmony/main.go
  4. 12
      cmd/staking/root.go
  5. 133
      consensus/consensus_service.go
  6. 1
      consensus/consensus_v2.go
  7. 8
      consensus/quorum/one-node-staked-vote.go
  8. 35
      core/blockchain.go
  9. 1
      core/state_transition.go
  10. 4
      internal/chain/engine.go
  11. 51
      internal/params/config.go
  12. 12
      internal/utils/utils.go
  13. 28
      node/node.go
  14. 2
      node/node_genesis.go
  15. 54
      node/node_handler.go
  16. 27
      node/node_newblock.go
  17. 68
      node/worker/worker.go
  18. 80
      shard/committee/assignment.go
  19. 10
      shard/shard_state.go
  20. 29
      test/configs/local-resharding.txt

@ -30,11 +30,11 @@ func NewFactory(chainConfig *params.ChainConfig) Factory {
func (f *factory) NewHeader(epoch *big.Int) *block.Header {
var impl blockif.Header
switch {
case epoch.Cmp(f.chainConfig.StakingEpoch) >= 0:
case f.chainConfig.IsPreStaking(epoch):
impl = v3.NewHeader()
case epoch.Cmp(f.chainConfig.CrossLinkEpoch) >= 0:
case f.chainConfig.IsCrossLink(epoch):
impl = v2.NewHeader()
case epoch.Cmp(f.chainConfig.CrossTxEpoch) >= 0:
case f.chainConfig.IsCrossTx(epoch):
impl = v1.NewHeader()
default:
impl = v0.NewHeader()

@ -219,7 +219,7 @@ syncLoop:
Msg("Error when adding new block")
}
stateMutex.Lock()
if err := txGen.Worker.UpdateCurrent(block.Coinbase()); err != nil {
if err := txGen.Worker.UpdateCurrent(); err != nil {
utils.Logger().Warn().Err(err).Msg("(*Worker).UpdateCurrent failed")
}
stateMutex.Unlock()

@ -195,6 +195,7 @@ func setupInitialAccount() (isLeader bool) {
pubKey := setupConsensusKey(nodeconfig.GetDefaultConfig())
reshardingEpoch := genesisShardingConfig.ReshardingEpoch()
// TODO: after staking, what if the FN validator uses the old bls pub keys?
if reshardingEpoch != nil && len(reshardingEpoch) > 0 {
for _, epoch := range reshardingEpoch {
config := shard.Schedule.InstanceForEpoch(epoch)
@ -208,12 +209,14 @@ func setupInitialAccount() (isLeader bool) {
}
if initialAccount == nil {
fmt.Fprintf(os.Stderr, "ERROR cannot find your BLS key in the genesis/FN tables: %s\n", pubKey.SerializeToHexStr())
os.Exit(100)
initialAccount.ShardID = uint32(*shardID)
initialAccount.BlsPublicKey = pubKey.SerializeToHexStr()
blsAddressBytes := pubKey.GetAddress()
initialAccount.Address = hex.EncodeToString(blsAddressBytes[:])
} else {
fmt.Printf("My Genesis Account: %v\n", *initialAccount)
}
fmt.Printf("My Genesis Account: %v\n", *initialAccount)
return isLeader
}

@ -56,11 +56,15 @@ var (
rate = "0.15"
testAccounts = []string{
"one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy",
"one12fuf7x9rgtdgqg7vgq0962c556m3p7afsxgvll"}
"one103q7qe5t2505lypvltkqtddaef5tzfxwsse4z7",
"one1uyshu2jgv8w465yc8kkny36thlt2wvel89tcmg",
"one1r4zyyjqrulf935a479sgqlpa78kz7zlcg2jfen",
"one1p7ht2d4kl8ve7a8jxw746yfnx4wnfxtp8jqxwe"}
testBLSPubKeys = []string{
"65f55eb3052f9e9f632b2923be594ba77c55543f5c58ee1454b9cfd658d25e06373b0f7d42a19c84768139ea294f6204",
"02c8ff0b88f313717bc3a627d2f8bb172ba3ad3bb9ba3ecb8eed4b7c878653d3d4faf769876c528b73f343967f74a917"}
"678ec9670899bf6af85b877058bea4fc1301a5a3a376987e826e3ca150b80e3eaadffedad0fedfa111576fa76ded980c",
"a547a9bf6fdde4f4934cde21473748861a3cc0fe8bbb5e57225a29f483b05b72531f002f8187675743d819c955a86100",
"fc4b9c535ee91f015efff3f32fbb9d32cdd9bfc8a837bb3eee89b8fff653c7af2050a4e147ebe5c7233dc2d5df06ee0a",
"ca86e551ee42adaaa6477322d7db869d3e203c00d7b86c82ebee629ad79cb6d57b8f3db28336778ec2180e56a8e07296"}
)
func (s *staker) run(cmd *cobra.Command, args []string) error {

@ -428,15 +428,29 @@ func (consensus *Consensus) getLeaderPubKeyFromCoinbase(header *block.Header) (*
)
}
committerKey := new(bls.PublicKey)
isStaking := consensus.ChainReader.Config().IsStaking(header.Epoch())
for _, member := range committee.Slots {
if member.EcdsaAddress == header.Coinbase() {
err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey)
if err != nil {
return nil, ctxerror.New("cannot convert BLS public key",
"blsPublicKey", member.BlsPublicKey,
"coinbaseAddr", header.Coinbase()).WithCause(err)
if isStaking {
// After staking the coinbase address will be the address of bls public key
if utils.GetAddressFromBlsPubKeyBytes(member.BlsPublicKey[:]) == header.Coinbase() {
err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey)
if err != nil {
return nil, ctxerror.New("cannot convert BLS public key",
"blsPublicKey", member.BlsPublicKey,
"coinbaseAddr", header.Coinbase()).WithCause(err)
}
return committerKey, nil
}
} else {
if member.EcdsaAddress == header.Coinbase() {
err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey)
if err != nil {
return nil, ctxerror.New("cannot convert BLS public key",
"blsPublicKey", member.BlsPublicKey,
"coinbaseAddr", header.Coinbase()).WithCause(err)
}
return committerKey, nil
}
return committerKey, nil
}
}
return nil, ctxerror.New("cannot find corresponding BLS Public Key", "coinbaseAddr", header.Coinbase())
@ -453,25 +467,94 @@ func (consensus *Consensus) getLeaderPubKeyFromCoinbase(header *block.Header) (*
// (b) node in committed but has any err during processing: Syncing mode
// (c) node in committed and everything looks good: Normal mode
func (consensus *Consensus) UpdateConsensusInformation() Mode {
curHeader := consensus.ChainReader.CurrentHeader()
curEpoch := curHeader.Epoch()
nextEpoch := new(big.Int).Add(curHeader.Epoch(), common.Big1)
if (consensus.ChainReader.Config().IsStaking(nextEpoch) && len(curHeader.ShardState()) > 0 && !consensus.ChainReader.Config().IsStaking(curEpoch)) ||
(consensus.ChainReader.Config().IsStaking(curEpoch) && consensus.Decider.Policy() != quorum.SuperMajorityStake) {
prevSubCommitteeDump := consensus.Decider.JSON()
consensus.Decider = quorum.NewDecider(quorum.SuperMajorityStake)
consensus.Decider.SetShardIDProvider(func() (uint32, error) {
return consensus.ShardID, nil
})
s, err := committee.WithStakingEnabled.ReadFromDB(
nextEpoch, consensus.ChainReader,
)
if err != nil {
utils.Logger().Error().
Err(err).
Uint32("shard", consensus.ShardID).
Msg("Error when reading staking committee")
return Syncing
}
if _, err := consensus.Decider.SetVoters(
s.FindCommitteeByID(consensus.ShardID).Slots,
); err != nil {
utils.Logger().Error().
Err(err).
Uint32("shard", consensus.ShardID).
Msg("Error when updating voting power")
return Syncing
}
utils.Logger().Info().
Uint64("block-number", curHeader.Number().Uint64()).
Uint64("curEpoch", curHeader.Epoch().Uint64()).
Uint32("shard-id", consensus.ShardID).
RawJSON("prev-subcommittee", []byte(prevSubCommitteeDump)).
RawJSON("current-subcommittee", []byte(consensus.Decider.JSON())).
Msg("changing committee")
}
pubKeys := []*bls.PublicKey{}
hasError := false
header := consensus.ChainReader.CurrentHeader()
epoch := header.Epoch()
curPubKeys := committee.WithStakingEnabled.ComputePublicKeys(
epoch, consensus.ChainReader,
)[int(header.ShardID())]
// TODO: change GetCommitteePublicKeys to read from DB
curShardState, err := committee.WithStakingEnabled.ReadFromDB(
curEpoch, consensus.ChainReader,
)
if err != nil {
utils.Logger().Error().
Err(err).
Uint32("shard", consensus.ShardID).
Msg("Error retrieving current shard state")
return Syncing
}
curCommittee := curShardState.FindCommitteeByID(curHeader.ShardID())
curPubKeys := committee.WithStakingEnabled.GetCommitteePublicKeys(
curCommittee,
)
consensus.numPrevPubKeys = len(curPubKeys)
consensus.getLogger().Info().Msg("[UpdateConsensusInformation] Updating.....")
if shard.Schedule.IsLastBlock(header.Number().Uint64()) {
// increase epoch by one if it's the last block
consensus.SetEpochNum(epoch.Uint64() + 1)
consensus.getLogger().Info().Uint64("headerNum", header.Number().Uint64()).
Msg("[UpdateConsensusInformation] Epoch updated for next epoch")
pubKeys = committee.WithStakingEnabled.ComputePublicKeys(
new(big.Int).Add(epoch, common.Big1), consensus.ChainReader,
)[int(header.ShardID())]
if len(curHeader.ShardState()) > 0 {
// increase curEpoch by one if it's the last block
consensus.SetEpochNum(curEpoch.Uint64() + 1)
consensus.getLogger().Info().Uint64("headerNum", curHeader.Number().Uint64()).
Msg("[UpdateConsensusInformation] Epoch updated for nextEpoch curEpoch")
nextShardState, err := committee.WithStakingEnabled.ReadFromDB(
nextEpoch, consensus.ChainReader,
)
if err != nil {
utils.Logger().Error().
Err(err).
Uint32("shard", consensus.ShardID).
Msg("Error retrieving nextEpoch shard state")
return Syncing
}
nextCommittee := nextShardState.FindCommitteeByID(curHeader.ShardID())
pubKeys = committee.WithStakingEnabled.GetCommitteePublicKeys(
nextCommittee,
)
} else {
consensus.SetEpochNum(epoch.Uint64())
consensus.SetEpochNum(curEpoch.Uint64())
pubKeys = curPubKeys
}
@ -488,10 +571,10 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode {
Msg("[UpdateConsensusInformation] Successfully updated public keys")
consensus.UpdatePublicKeys(pubKeys)
// take care of possible leader change during the epoch
if !shard.Schedule.IsLastBlock(header.Number().Uint64()) &&
header.Number().Uint64() != 0 {
leaderPubKey, err := consensus.getLeaderPubKeyFromCoinbase(header)
// take care of possible leader change during the curEpoch
if !shard.Schedule.IsLastBlock(curHeader.Number().Uint64()) &&
curHeader.Number().Uint64() != 0 {
leaderPubKey, err := consensus.getLeaderPubKeyFromCoinbase(curHeader)
if err != nil || leaderPubKey == nil {
consensus.getLogger().Debug().Err(err).
Msg("[SYNC] Unable to get leaderPubKey from coinbase")

@ -830,6 +830,7 @@ func (consensus *Consensus) finalizeCommits() {
utils.Logger().Info().
Uint64("blockNum", block.NumberU64()).
Uint64("epochNum", block.Epoch().Uint64()).
Uint64("ViewId", block.Header().ViewID().Uint64()).
Str("blockHash", block.Hash().String()).
Int("index", consensus.Decider.IndexOf(consensus.PubKey)).

@ -148,10 +148,10 @@ func (v *stakedVoteWeight) SetVoters(
v.stakedTotal = numeric.ZeroDec()
for i := range staked {
if staked[i].StakeWithDelegationApplied == nil {
if staked[i].TotalStake == nil {
v.hmySlotCount++
} else {
v.stakedTotal = v.stakedTotal.Add(*staked[i].StakeWithDelegationApplied)
v.stakedTotal = v.stakedTotal.Add(*staked[i].TotalStake)
}
}
@ -169,9 +169,9 @@ func (v *stakedVoteWeight) SetVoters(
}
// Real Staker
if staked[i].StakeWithDelegationApplied != nil {
if staked[i].TotalStake != nil {
member.isHarmonyNode = false
member.effectivePercent = staked[i].StakeWithDelegationApplied.
member.effectivePercent = staked[i].TotalStake.
Quo(v.stakedTotal).
Mul(stakersShare)
theirPercentage = theirPercentage.Add(member.effectivePercent)

@ -28,6 +28,7 @@ import (
"time"
"github.com/harmony-one/harmony/crypto/bls"
lru "github.com/hashicorp/golang-lru"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock"
@ -48,9 +49,7 @@ 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/shard/committee"
staking "github.com/harmony-one/harmony/staking/types"
lru "github.com/hashicorp/golang-lru"
)
var (
@ -1156,7 +1155,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
shard := (*shardState)[i]
for j := range shard.Slots {
slot := shard.Slots[j]
if slot.StakeWithDelegationApplied != nil { // For external validator
if slot.TotalStake != nil { // For external validator
_, ok := processed[slot.EcdsaAddress]
if !ok {
processed[slot.EcdsaAddress] = struct{}{}
@ -1961,36 +1960,6 @@ func (bc *BlockChain) GetVrfByNumber(number uint64) []byte {
return header.Vrf()
}
// GetShardState returns the shard state for the given epoch,
// creating one if needed.
func (bc *BlockChain) GetShardState(epoch *big.Int) (shard.State, error) {
shardState, err := bc.ReadShardState(epoch)
if err == nil { // TODO ek – distinguish ErrNotFound
return shardState, err
}
if epoch.Cmp(big.NewInt(GenesisEpoch)) == 0 {
shardState, err = committee.WithStakingEnabled.Compute(
big.NewInt(GenesisEpoch), bc.Config(), nil,
)
} else {
prevEpoch := new(big.Int).Sub(epoch, common.Big1)
shardState, err = committee.WithStakingEnabled.ReadFromDB(
prevEpoch, bc,
)
}
if err != nil {
return nil, err
}
err = bc.WriteShardState(epoch, shardState)
if err != nil {
return nil, err
}
utils.Logger().Debug().Str("epoch", epoch.String()).Msg("saved new shard state")
return shardState, nil
}
// ChainDb returns the database
func (bc *BlockChain) ChainDb() ethdb.Database { return bc.db }

@ -243,6 +243,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
}
}
st.refundGas()
// TODO: need to move the gas fee to the general block rewards
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
return ret, st.gasUsed(), vmerr != nil, err

@ -225,7 +225,7 @@ func QuorumForBlock(
) (quorum int, err error) {
var ss shard.State
if reCalculate {
ss, _ = committee.WithStakingEnabled.Compute(h.Epoch(), chain.Config(), chain)
ss, _ = committee.WithStakingEnabled.Compute(h.Epoch(), chain)
} else {
ss, err = chain.ReadShardState(h.Epoch())
if err != nil {
@ -284,7 +284,7 @@ func GetPublicKeys(chain engine.ChainReader, header *block.Header, reCalculate b
var shardState shard.State
var err error
if reCalculate {
shardState, _ = committee.WithStakingEnabled.Compute(header.Epoch(), chain.Config(), chain)
shardState, _ = committee.WithStakingEnabled.Compute(header.Epoch(), chain)
} else {
shardState, err = chain.ReadShardState(header.Epoch())
if err != nil {

@ -23,33 +23,36 @@ var EpochTBD = big.NewInt(10000000)
var (
// MainnetChainConfig is the chain parameters to run a node on the main network.
MainnetChainConfig = &ChainConfig{
ChainID: MainnetChainID,
CrossTxEpoch: big.NewInt(28),
CrossLinkEpoch: EpochTBD,
StakingEpoch: EpochTBD,
EIP155Epoch: big.NewInt(28),
S3Epoch: big.NewInt(28),
ChainID: MainnetChainID,
CrossTxEpoch: big.NewInt(28),
CrossLinkEpoch: EpochTBD,
StakingEpoch: EpochTBD,
PreStakingEpoch: EpochTBD,
EIP155Epoch: big.NewInt(28),
S3Epoch: big.NewInt(28),
}
// TestnetChainConfig contains the chain parameters to run a node on the harmony test network.
TestnetChainConfig = &ChainConfig{
ChainID: TestnetChainID,
CrossTxEpoch: big.NewInt(0),
CrossLinkEpoch: big.NewInt(0),
StakingEpoch: big.NewInt(3),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
ChainID: TestnetChainID,
CrossTxEpoch: big.NewInt(0),
CrossLinkEpoch: big.NewInt(2),
StakingEpoch: big.NewInt(3),
PreStakingEpoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
}
// PangaeaChainConfig contains the chain parameters for the Pangaea network.
// All features except for CrossLink are enabled at launch.
PangaeaChainConfig = &ChainConfig{
ChainID: PangaeaChainID,
CrossTxEpoch: big.NewInt(0),
CrossLinkEpoch: EpochTBD,
StakingEpoch: EpochTBD,
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
ChainID: PangaeaChainID,
CrossTxEpoch: big.NewInt(0),
CrossLinkEpoch: EpochTBD,
StakingEpoch: EpochTBD,
PreStakingEpoch: EpochTBD,
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
}
// AllProtocolChanges ...
@ -60,6 +63,7 @@ var (
big.NewInt(0), // CrossTxEpoch
big.NewInt(0), // CrossLinkEpoch
big.NewInt(0), // StakingEpoch
big.NewInt(0), // PreStakingEpoch
big.NewInt(0), // EIP155Epoch
big.NewInt(0), // S3Epoch
}
@ -72,6 +76,7 @@ var (
big.NewInt(0), // CrossTxEpoch
big.NewInt(0), // CrossLinkEpoch
big.NewInt(0), // StakingEpoch
big.NewInt(0), // PreStakingEpoch
big.NewInt(0), // EIP155Epoch
big.NewInt(0), // S3Epoch
}
@ -108,9 +113,12 @@ type ChainConfig struct {
// cross-shard links.
CrossLinkEpoch *big.Int `json:"crossLinkEpoch,omitempty"`
// StakingEpoch is the epoch we allow staking transactions
// StakingEpoch is the epoch when shard assign takes staking into account
StakingEpoch *big.Int `json:"stakingEpoch,omitempty"`
// PreStakingEpoch is the epoch we allow staking transactions
PreStakingEpoch *big.Int `json:"preStakingEpoch,omitempty"`
EIP155Epoch *big.Int `json:"eip155Epoch,omitempty"` // EIP155 hard fork epoch (include EIP158 too)
S3Epoch *big.Int `json:"s3Epoch,omitempty"` // S3 epoch is the first epoch containing S3 mainnet and all ethereum update up to Constantinople
}
@ -151,6 +159,11 @@ func (c *ChainConfig) IsStaking(epoch *big.Int) bool {
return isForked(c.StakingEpoch, epoch)
}
// IsPreStaking determines whether staking transactions are allowed
func (c *ChainConfig) IsPreStaking(epoch *big.Int) bool {
return isForked(c.PreStakingEpoch, epoch)
}
// IsCrossLink returns whether epoch is either equal to the CrossLink fork epoch or greater.
func (c *ChainConfig) IsCrossLink(epoch *big.Int) bool {
return isForked(c.CrossLinkEpoch, epoch)

@ -86,6 +86,18 @@ func GetAddressFromBlsPubKey(pubKey *bls.PublicKey) common.Address {
return addr
}
// GetAddressFromBlsPubKeyBytes return the address object from bls pub key.
func GetAddressFromBlsPubKeyBytes(pubKeyBytes []byte) common.Address {
pubKey := &bls.PublicKey{}
err := pubKey.Deserialize(pubKeyBytes[:])
addr := common.Address{}
if err != nil {
addrBytes := pubKey.GetAddress()
addr.SetBytes(addrBytes[:])
}
return addr
}
// GenKeyP2P generates a pair of RSA keys used in libp2p host
func GenKeyP2P(ip, port string) (p2p_crypto.PrivKey, p2p_crypto.PubKey, error) {
r := mrand.New(mrand.NewSource(int64(GetUniqueIDFromIPPort(ip, port))))

@ -278,17 +278,20 @@ func (node *Node) addPendingTransactions(newTxs types.Transactions) {
// Add new staking transactions to the pending staking transaction list.
func (node *Node) addPendingStakingTransactions(newStakingTxs staking.StakingTransactions) {
txPoolLimit := 1000 // TODO: incorporate staking txn into TxPool
node.pendingStakingTxMutex.Lock()
for _, tx := range newStakingTxs {
if _, ok := node.pendingStakingTransactions[tx.Hash()]; !ok {
node.pendingStakingTransactions[tx.Hash()] = tx
}
if len(node.pendingStakingTransactions) > txPoolLimit {
break
if node.Blockchain().Config().IsPreStaking(node.Worker.GetNewEpoch()) {
node.pendingStakingTxMutex.Lock()
for _, tx := range newStakingTxs {
if _, ok := node.pendingStakingTransactions[tx.Hash()]; !ok {
node.pendingStakingTransactions[tx.Hash()] = tx
}
if len(node.pendingStakingTransactions) > txPoolLimit {
break
}
}
utils.Logger().Info().Int("length of newStakingTxs", len(newStakingTxs)).Int("totalPending", len(node.pendingStakingTransactions)).Msg("Got more staking transactions")
node.pendingStakingTxMutex.Unlock()
}
utils.Logger().Info().Int("length of newStakingTxs", len(newStakingTxs)).Int("totalPending", len(node.pendingStakingTransactions)).Msg("Got more staking transactions")
node.pendingStakingTxMutex.Unlock()
}
// AddPendingStakingTransaction staking transactions
@ -486,9 +489,12 @@ func (node *Node) InitConsensusWithValidators() (err error) {
Uint32("shardID", shardID).
Uint64("epoch", epoch.Uint64()).
Msg("[InitConsensusWithValidators] Try To Get PublicKeys")
pubKeys := committee.WithStakingEnabled.ComputePublicKeys(
shardState, err := committee.WithStakingEnabled.Compute(
epoch, node.Consensus.ChainReader,
)[int(shardID)]
)
pubKeys := committee.WithStakingEnabled.GetCommitteePublicKeys(
shardState.FindCommitteeByID(shardID),
)
if len(pubKeys) == 0 {
utils.Logger().Error().
Uint32("shardID", shardID).

@ -41,7 +41,7 @@ type genesisInitializer struct {
// InitChainDB sets up a new genesis block in the database for the given shard.
func (gi *genesisInitializer) InitChainDB(db ethdb.Database, shardID uint32) error {
shardState, _ := committee.WithStakingEnabled.Compute(
big.NewInt(core.GenesisEpoch), &gi.node.chainConfig, nil,
big.NewInt(core.GenesisEpoch), nil,
)
if shardID != shard.BeaconChainShardID {
// store only the local shard for shard chains

@ -8,7 +8,6 @@ import (
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/harmony-one/bls/ffi/go/bls"
@ -16,7 +15,6 @@ import (
proto_discovery "github.com/harmony-one/harmony/api/proto/discovery"
proto_node "github.com/harmony-one/harmony/api/proto/node"
"github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/consensus/quorum"
"github.com/harmony-one/harmony/core/types"
bls2 "github.com/harmony-one/harmony/crypto/bls"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
@ -26,7 +24,6 @@ import (
"github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/p2p/host"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/shard/committee"
staking "github.com/harmony-one/harmony/staking/types"
libp2p_peer "github.com/libp2p/go-libp2p-core/peer"
)
@ -345,7 +342,12 @@ func (node *Node) PostConsensusProcessing(newBlock *types.Block, commitSigAndBit
node.BroadcastCXReceipts(newBlock, commitSigAndBitmap)
} else {
utils.Logger().Info().
Uint64("BlockNum", newBlock.NumberU64()).
Uint64("blockNum", newBlock.NumberU64()).
Uint64("epochNum", newBlock.Epoch().Uint64()).
Uint64("ViewId", newBlock.Header().ViewID().Uint64()).
Str("blockHash", newBlock.Hash().String()).
Int("numTxns", len(newBlock.Transactions())).
Int("numStakingTxns", len(newBlock.StakingTransactions())).
Msg("BINGO !!! Reached Consensus")
// 15% of the validator also need to do broadcasting
rand.Seed(time.Now().UTC().UnixNano())
@ -377,51 +379,7 @@ func (node *Node) PostConsensusProcessing(newBlock *types.Block, commitSigAndBit
// Update consensus keys at last so the change of leader status doesn't mess up normal flow
if len(newBlock.Header().ShardState()) > 0 {
next := new(big.Int).Add(newBlock.Epoch(), common.Big1)
if node.chainConfig.StakingEpoch.Cmp(next) == 0 &&
node.Consensus.Decider.Policy() != quorum.SuperMajorityStake {
node.Consensus.Decider = quorum.NewDecider(quorum.SuperMajorityStake)
node.Consensus.Decider.SetShardIDProvider(func() (uint32, error) {
return node.Consensus.ShardID, nil
})
s, _ := committee.WithStakingEnabled.Compute(
next, &node.chainConfig, node.Consensus.ChainReader,
)
prevSubCommitteeDump := node.Consensus.Decider.JSON()
if _, err := node.Consensus.Decider.SetVoters(
s.FindCommitteeByID(node.Consensus.ShardID).Slots,
); err != nil {
utils.Logger().Error().
Err(err).
Uint32("shard", node.Consensus.ShardID).
Msg("Error when updating voting power")
return
}
utils.Logger().Info().
Uint64("block-number", newBlock.Number().Uint64()).
Uint64("epoch", newBlock.Epoch().Uint64()).
Uint32("shard-id", node.Consensus.ShardID).
RawJSON("prev-subcommittee", []byte(prevSubCommitteeDump)).
RawJSON("current-subcommittee", []byte(node.Consensus.Decider.JSON())).
Msg("changing committee")
}
// TODO Need to refactor UpdateConsensusInformation so can fold the following logic
// into UCI - todo because UCI mutates state & called in overloaded contexts
node.Consensus.UpdateConsensusInformation()
if shard.Schedule.IsLastBlock(newBlock.Number().Uint64()) {
if node.chainConfig.StakingEpoch.Cmp(next) == 0 {
// Hit this case again, need after UpdateConsensus
curPubKeys := committee.WithStakingEnabled.ComputePublicKeys(
next, node.Consensus.ChainReader,
)[int(node.Consensus.ShardID)]
node.Consensus.Decider.UpdateParticipants(curPubKeys)
}
}
}
// TODO chao: uncomment this after beacon syncing is stable

@ -79,6 +79,16 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
// Update worker's current header and state data in preparation to propose/process new transactions
coinbase := node.Consensus.SelfAddress
// After staking, all coinbase will be the address of bls pub key
if node.Blockchain().Config().IsStaking(node.Worker.GetNewEpoch()) {
addr := common.Address{}
blsPubKeyBytes := node.Consensus.PubKey.GetAddress()
addr.SetBytes(blsPubKeyBytes[:])
coinbase = addr
}
node.Worker.UpdateCurrent()
// Prepare transactions including staking transactions
pending, err := node.TxPool.Pending()
if err != nil {
@ -88,14 +98,16 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
// TODO: integrate staking transaction into tx pool
pendingStakingTransactions := types2.StakingTransactions{}
node.pendingStakingTxMutex.Lock()
for _, tx := range node.pendingStakingTransactions {
pendingStakingTransactions = append(pendingStakingTransactions, tx)
// Only process staking transactions after pre-staking epoch happened.
if node.Blockchain().Config().IsPreStaking(node.Worker.GetNewEpoch()) {
node.pendingStakingTxMutex.Lock()
for _, tx := range node.pendingStakingTransactions {
pendingStakingTransactions = append(pendingStakingTransactions, tx)
}
node.pendingStakingTransactions = make(map[common.Hash]*types2.StakingTransaction)
node.pendingStakingTxMutex.Unlock()
}
node.pendingStakingTransactions = make(map[common.Hash]*types2.StakingTransaction)
node.pendingStakingTxMutex.Unlock()
node.Worker.UpdateCurrent(coinbase)
if err := node.Worker.CommitTransactions(pending, pendingStakingTransactions, coinbase); err != nil {
utils.Logger().Error().Err(err).Msg("cannot commit transactions")
return nil, err
@ -119,8 +131,9 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
}
// Prepare shard state
// NOTE: this will potentially override shard chain's epoch to beacon chain's epoch during staking migration period.
shardState, err := node.Worker.SuperCommitteeForNextEpoch(
node.Consensus.ShardID, node.Blockchain(),
node.Consensus.ShardID, node.Beaconchain(),
)
if err != nil {

@ -215,7 +215,7 @@ func (w *Worker) CommitReceipts(receiptsList []*types.CXReceiptsProof) error {
}
// UpdateCurrent updates the current environment with the current state and header.
func (w *Worker) UpdateCurrent(coinbase common.Address) error {
func (w *Worker) UpdateCurrent() error {
parent := w.chain.CurrentBlock()
num := parent.Number()
timestamp := time.Now().Unix()
@ -227,7 +227,6 @@ func (w *Worker) UpdateCurrent(coinbase common.Address) error {
GasLimit(core.CalcGasLimit(parent, w.gasFloor, w.gasCeil)).
Time(big.NewInt(timestamp)).
ShardID(w.chain.ShardID()).
Coinbase(coinbase).
Header()
return w.makeCurrent(parent, header)
}
@ -288,39 +287,73 @@ func (w *Worker) SuperCommitteeForNextEpoch(
) (shard.State, error) {
var (
nextCommittee shard.State
oops error
err error
)
switch shardID {
case shard.BeaconChainShardID:
if shard.Schedule.IsLastBlock(w.current.header.Number().Uint64()) {
nextCommittee, oops = committee.WithStakingEnabled.Compute(
nextCommittee, err = committee.WithStakingEnabled.Compute(
new(big.Int).Add(w.current.header.Epoch(), common.Big1),
w.config,
beacon,
)
}
default:
// WARN When we first enable staking, this condition may not be robust by itself.
if w.config.IsStaking(w.current.header.Epoch()) {
switch beacon.CurrentHeader().Epoch().Cmp(w.current.header.Epoch()) {
// TODO: needs to make sure beacon chain sync works.
beaconEpoch := beacon.CurrentHeader().Epoch()
nextEpoch := new(big.Int).Add(w.current.header.Epoch(), common.Big1)
if w.config.IsStaking(nextEpoch) {
// If next epoch is staking epoch, I should wait and listen for beacon chain for epoch changes
switch beaconEpoch.Cmp(w.current.header.Epoch()) {
case 1:
nextCommittee, oops = committee.WithStakingEnabled.ReadFromDB(
beacon.CurrentHeader().Epoch(), beacon,
// If beacon chain is bigger than shard chain in epoch, it means I should catch up with beacon chain now
nextCommittee, err = committee.WithStakingEnabled.ReadFromDB(
beaconEpoch, beacon,
)
// Set this block's epoch to be beaconEpoch - 1, so the next block will have beaconEpoch
// This shouldn't be exactly beaconEpoch because the next block will have beaconEpoch + 1
blockEpoch := big.NewInt(0).Set(beaconEpoch).Sub(beaconEpoch, big.NewInt(1))
utils.Logger().Debug().
Uint64("blockNum", w.current.header.Number().Uint64()).
Uint64("myPrevEpoch", w.current.header.Epoch().Uint64()).
Uint64("myCurEpoch", blockEpoch.Uint64()).
Msg("Propose new epoch as beacon chain's epoch")
w.current.header.SetEpoch(blockEpoch)
case 0:
// If it's same epoch, no need to propose new shard state (new epoch change)
case -1:
// If beacon chain is behind, shard chain should wait for the beacon chain by not changing epochs.
}
} else {
if shard.Schedule.IsLastBlock(w.current.header.Number().Uint64()) {
nextCommittee, oops = committee.WithStakingEnabled.Compute(
new(big.Int).Add(w.current.header.Epoch(), common.Big1),
w.config,
beacon,
if w.config.IsStaking(beaconEpoch) {
// If I am not even in the last epoch before staking epoch and beacon chain is already in staking epoch,
// I should just catch up with beacon chain's epoch
nextCommittee, err = committee.WithStakingEnabled.ReadFromDB(
beaconEpoch, beacon,
)
blockEpoch := big.NewInt(0).Set(beaconEpoch).Sub(beaconEpoch, big.NewInt(1))
utils.Logger().Debug().
Uint64("blockNum", w.current.header.Number().Uint64()).
Uint64("myPrevEpoch", w.current.header.Epoch().Uint64()).
Uint64("myCurEpoch", blockEpoch.Uint64()).
Msg("Propose one-time catch up with beacon chain's epoch")
// Set this block's epoch to be beaconEpoch - 1, so the next block will have beaconEpoch
w.current.header.SetEpoch(blockEpoch)
} else {
// If I are not in staking nor has beacon chain proposed a staking-based shard state,
// do pre-staking committee calculation
if shard.Schedule.IsLastBlock(w.current.header.Number().Uint64()) {
nextCommittee, err = committee.WithStakingEnabled.Compute(
new(big.Int).Add(w.current.header.Epoch(), common.Big1),
w.chain,
)
}
}
}
}
return nextCommittee, oops
return nextCommittee, err
}
// FinalizeNewBlock generate a new block for the next consensus round.
@ -364,6 +397,7 @@ func (w *Worker) FinalizeNewBlock(sig []byte, signers []byte, viewID uint64, coi
s := w.current.state.Copy()
copyHeader := types.CopyHeader(w.current.header)
// TODO: feed coinbase into here so the proposer gets extra rewards.
block, err := w.engine.Finalize(w.chain, copyHeader, s, w.current.txs, w.current.receipts, w.current.outcxs, w.current.incxs, w.current.stakingTxs)
if err != nil {
return nil, ctxerror.New("cannot finalize block").WithCause(err)

@ -8,7 +8,6 @@ import (
"github.com/harmony-one/harmony/block"
common2 "github.com/harmony-one/harmony/internal/common"
shardingconfig "github.com/harmony-one/harmony/internal/configs/sharding"
"github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard"
@ -19,22 +18,14 @@ import (
// ValidatorListProvider ..
type ValidatorListProvider interface {
Compute(
epoch *big.Int, config *params.ChainConfig, reader DataProvider,
epoch *big.Int, reader DataProvider,
) (shard.State, error)
ReadFromDB(epoch *big.Int, reader DataProvider) (shard.State, error)
}
// PublicKeysProvider per epoch
type PublicKeysProvider interface {
ComputePublicKeys(epoch *big.Int, reader DataProvider) [][]*bls.PublicKey
ReadPublicKeysFromDB(
hash common.Hash, reader DataProvider,
) ([]*bls.PublicKey, error)
GetCommitteePublicKeys(committee *shard.Committee) []*bls.PublicKey
}
// Reader is committee.Reader and it is the API that committee membership assignment needs
type Reader interface {
PublicKeysProvider
ValidatorListProvider
}
@ -123,7 +114,6 @@ func eposStakedCommittee(
// TODO benchmark difference if went with data structure that sorts on insert
for i := range candidates {
// TODO Should be using .ValidatorStakingWithDelegation, not implemented yet
validator, err := stakerReader.ReadValidatorData(candidates[i])
validatorStake := big.NewInt(0)
for _, delegation := range validator.Delegations {
@ -184,59 +174,19 @@ func eposStakedCommittee(
return superComm, nil
}
// ComputePublicKeys produces publicKeys of entire supercommittee per epoch
func (def partialStakingEnabled) ComputePublicKeys(
epoch *big.Int, d DataProvider,
) [][]*bls.PublicKey {
config := d.Config()
superComm, _ := def.Compute(epoch, config, d)
// GetCommitteePublicKeys returns the public keys of a shard
func (def partialStakingEnabled) GetCommitteePublicKeys(committee *shard.Committee) []*bls.PublicKey {
allIdentities := make([]*bls.PublicKey, len(committee.Slots))
allIdentities := make([][]*bls.PublicKey, len(superComm))
for i := range superComm {
allIdentities[i] = make([]*bls.PublicKey, len(superComm[i].Slots))
for j := range superComm[i].Slots {
identity := &bls.PublicKey{}
superComm[i].Slots[j].BlsPublicKey.ToLibBLSPublicKey(identity)
allIdentities[i][j] = identity
}
for i := range committee.Slots {
identity := &bls.PublicKey{}
committee.Slots[i].BlsPublicKey.ToLibBLSPublicKey(identity)
allIdentities[i] = identity
}
return allIdentities
}
func (def partialStakingEnabled) ReadPublicKeysFromDB(
h common.Hash, reader DataProvider,
) ([]*bls.PublicKey, error) {
header := reader.GetHeaderByHash(h)
shardID := header.ShardID()
superCommittee, err := reader.ReadShardState(header.Epoch())
if err != nil {
return nil, err
}
subCommittee := superCommittee.FindCommitteeByID(shardID)
if subCommittee == nil {
return nil, ctxerror.New("cannot find shard in the shard state",
"blockNumber", header.Number(),
"shardID", header.ShardID(),
)
}
committerKeys := []*bls.PublicKey{}
for i := range subCommittee.Slots {
committerKey := new(bls.PublicKey)
err := subCommittee.Slots[i].BlsPublicKey.ToLibBLSPublicKey(committerKey)
if err != nil {
return nil, ctxerror.New("cannot convert BLS public key",
"blsPublicKey", subCommittee.Slots[i].BlsPublicKey).WithCause(err)
}
committerKeys = append(committerKeys, committerKey)
}
return committerKeys, nil
return nil, nil
}
func (def partialStakingEnabled) ReadFromDB(
epoch *big.Int, reader DataProvider,
) (newSuperComm shard.State, err error) {
@ -245,10 +195,18 @@ func (def partialStakingEnabled) ReadFromDB(
// ReadFromComputation is single entry point for reading the State of the network
func (def partialStakingEnabled) Compute(
epoch *big.Int, config *params.ChainConfig, stakerReader DataProvider,
epoch *big.Int, stakerReader DataProvider,
) (newSuperComm shard.State, err error) {
preStaking := true
if stakerReader != nil {
config := stakerReader.Config()
if config.IsStaking(epoch) {
preStaking = false
}
}
instance := shard.Schedule.InstanceForEpoch(epoch)
if !config.IsStaking(epoch) {
if preStaking {
return preStakingEnabledCommittee(instance), nil
}
stakedSlots :=

@ -39,7 +39,7 @@ type Slot struct {
EcdsaAddress common.Address `json:"ecdsa-address"`
BlsPublicKey BlsPublicKey `json:"bls-pubkey"`
// nil means our node, 0 means not active, > 0 means staked node
StakeWithDelegationApplied *numeric.Dec `json:"staked-validator" rlp:"nil"`
TotalStake *numeric.Dec `json:"total-stake" rlp:"nil"`
}
// SlotList is a list of SlotList.
@ -71,7 +71,7 @@ func (ss State) JSON() string {
for j := range ss[i].Slots {
n := ss[i].Slots[j]
dump[i].NodeList[j].BlsPublicKey = n.BlsPublicKey
dump[i].NodeList[j].StakeWithDelegationApplied = n.StakeWithDelegationApplied
dump[i].NodeList[j].TotalStake = n.TotalStake
dump[i].NodeList[j].EcdsaAddress = common2.MustAddressToBech32(n.EcdsaAddress)
}
}
@ -266,5 +266,9 @@ func (n Slot) Serialize() []byte {
}
func (n Slot) String() string {
return "ECDSA: " + common2.MustAddressToBech32(n.EcdsaAddress) + ", BLS: " + hex.EncodeToString(n.BlsPublicKey[:])
total := "nil"
if n.TotalStake != nil {
total = n.TotalStake.String()
}
return "ECDSA: " + common2.MustAddressToBech32(n.EcdsaAddress) + ", BLS: " + hex.EncodeToString(n.BlsPublicKey[:]) + ", TotalStake: " + total
}

@ -8,19 +8,18 @@
127.0.0.1 9007 validator one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9 c4e4708b6cf2a2ceeb59981677e9821eebafc5cf483fb5364a28fa604cc0ce69beeed40f3f03815c9e196fdaec5f1097
127.0.0.1 9008 validator one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc 86dc2fdc2ceec18f6923b99fd86a68405c132e1005cf1df72dca75db0adfaeb53d201d66af37916d61f079f34f21fb96
127.0.0.1 9009 validator one1658znfwf40epvy7e46cqrmzyy54h4n0qa73nep 49d15743b36334399f9985feb0753430a2b287b2d68b84495bbb15381854cbf01bca9d1d9f4c9c8f18509b2bfa6bd40f
127.0.0.1 9010 validator one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe 52ecce5f64db21cbe374c9268188f5d2cdd5bec1a3112276a350349860e35fb81f8cfe447a311e0550d961cf25cb988d
127.0.0.1 9011 validator one1uyshu2jgv8w465yc8kkny36thlt2wvel89tcmg a547a9bf6fdde4f4934cde21473748861a3cc0fe8bbb5e57225a29f483b05b72531f002f8187675743d819c955a86100
127.0.0.1 9012 validator one103q7qe5t2505lypvltkqtddaef5tzfxwsse4z7 678ec9670899bf6af85b877058bea4fc1301a5a3a376987e826e3ca150b80e3eaadffedad0fedfa111576fa76ded980c
127.0.0.1 9013 validator one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k 63f479f249c59f0486fda8caa2ffb247209489dae009dfde6144ff38c370230963d360dffd318cfb26c213320e89a512
127.0.0.1 9099 explorer
127.0.0.1 9010 validator one1z05g55zamqzfw9qs432n33gycdmyvs38xjemyl 95117937cd8c09acd2dfae847d74041a67834ea88662a7cbed1e170350bc329e53db151e5a0ef3e712e35287ae954818
127.0.0.1 9011 validator one1ljznytjyn269azvszjlcqvpcj6hjm822yrcp2e 68ae289d73332872ec8d04ac256ca0f5453c88ad392730c5741b6055bc3ec3d086ab03637713a29f459177aaa8340615
127.0.0.1 9100 validator one1ghkz3frhske7emk79p7v2afmj4a5t0kmjyt4s5 eca09c1808b729ca56f1b5a6a287c6e1c3ae09e29ccf7efa35453471fcab07d9f73cee249e2b91f5ee44eb9618be3904
127.0.0.1 9101 validator one1d7jfnr6yraxnrycgaemyktkmhmajhp8kl0yahv f47238daef97d60deedbde5302d05dea5de67608f11f406576e363661f7dcbc4a1385948549b31a6c70f6fde8a391486
127.0.0.1 9102 validator one1r4zyyjqrulf935a479sgqlpa78kz7zlcg2jfen fc4b9c535ee91f015efff3f32fbb9d32cdd9bfc8a837bb3eee89b8fff653c7af2050a4e147ebe5c7233dc2d5df06ee0a
127.0.0.1 9103 validator one1p7ht2d4kl8ve7a8jxw746yfnx4wnfxtp8jqxwe ca86e551ee42adaaa6477322d7db869d3e203c00d7b86c82ebee629ad79cb6d57b8f3db28336778ec2180e56a8e07296
127.0.0.1 9104 validator one1z05g55zamqzfw9qs432n33gycdmyvs38xjemyl 95117937cd8c09acd2dfae847d74041a67834ea88662a7cbed1e170350bc329e53db151e5a0ef3e712e35287ae954818
127.0.0.1 9105 validator one1ljznytjyn269azvszjlcqvpcj6hjm822yrcp2e 68ae289d73332872ec8d04ac256ca0f5453c88ad392730c5741b6055bc3ec3d086ab03637713a29f459177aaa8340615
127.0.0.1 9107 validator one1uyshu2jgv8w465yc8kkny36thlt2wvel89tcmg a547a9bf6fdde4f4934cde21473748861a3cc0fe8bbb5e57225a29f483b05b72531f002f8187675743d819c955a86100
127.0.0.1 9108 validator one103q7qe5t2505lypvltkqtddaef5tzfxwsse4z7 678ec9670899bf6af85b877058bea4fc1301a5a3a376987e826e3ca150b80e3eaadffedad0fedfa111576fa76ded980c
127.0.0.1 9109 validator one1658znfwf40epvy7e46cqrmzyy54h4n0qa73nep 576d3c48294e00d6be4a22b07b66a870ddee03052fe48a5abbd180222e5d5a1f8946a78d55b025de21635fd743bbad90
127.0.0.1 9110 validator one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc 16513c487a6bb76f37219f3c2927a4f281f9dd3fd6ed2e3a64e500de6545cf391dd973cc228d24f9bd01efe94912e714
127.0.0.1 9100 validator one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe 52ecce5f64db21cbe374c9268188f5d2cdd5bec1a3112276a350349860e35fb81f8cfe447a311e0550d961cf25cb988d
127.0.0.1 9101 validator one1uyshu2jgv8w465yc8kkny36thlt2wvel89tcmg a547a9bf6fdde4f4934cde21473748861a3cc0fe8bbb5e57225a29f483b05b72531f002f8187675743d819c955a86100
127.0.0.1 9102 validator one103q7qe5t2505lypvltkqtddaef5tzfxwsse4z7 678ec9670899bf6af85b877058bea4fc1301a5a3a376987e826e3ca150b80e3eaadffedad0fedfa111576fa76ded980c
127.0.0.1 9103 validator one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k 63f479f249c59f0486fda8caa2ffb247209489dae009dfde6144ff38c370230963d360dffd318cfb26c213320e89a512
127.0.0.1 9104 validator one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc 16513c487a6bb76f37219f3c2927a4f281f9dd3fd6ed2e3a64e500de6545cf391dd973cc228d24f9bd01efe94912e714
127.0.0.1 9105 validator one1658znfwf40epvy7e46cqrmzyy54h4n0qa73nep 576d3c48294e00d6be4a22b07b66a870ddee03052fe48a5abbd180222e5d5a1f8946a78d55b025de21635fd743bbad90
127.0.0.1 9106 validator one1ghkz3frhske7emk79p7v2afmj4a5t0kmjyt4s5 eca09c1808b729ca56f1b5a6a287c6e1c3ae09e29ccf7efa35453471fcab07d9f73cee249e2b91f5ee44eb9618be3904
127.0.0.1 9107 validator one1d7jfnr6yraxnrycgaemyktkmhmajhp8kl0yahv f47238daef97d60deedbde5302d05dea5de67608f11f406576e363661f7dcbc4a1385948549b31a6c70f6fde8a391486
127.0.0.1 9108 validator one1r4zyyjqrulf935a479sgqlpa78kz7zlcg2jfen fc4b9c535ee91f015efff3f32fbb9d32cdd9bfc8a837bb3eee89b8fff653c7af2050a4e147ebe5c7233dc2d5df06ee0a
127.0.0.1 9109 validator one1p7ht2d4kl8ve7a8jxw746yfnx4wnfxtp8jqxwe ca86e551ee42adaaa6477322d7db869d3e203c00d7b86c82ebee629ad79cb6d57b8f3db28336778ec2180e56a8e07296
127.0.0.1 9099 explorer
Loading…
Cancel
Save