diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go index c4a78eb02c..ef527fecd8 100644 --- a/cmd/XDC/main.go +++ b/cmd/XDC/main.go @@ -26,8 +26,11 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" @@ -37,6 +40,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "gopkg.in/urfave/cli.v1" + validatorContract "github.com/ethereum/go-ethereum/contracts/validator/contract" ) const ( @@ -46,7 +50,6 @@ const ( var ( // Git SHA1 commit hash of the release (set via linker flags) gitCommit = "" - // The app that holds all commands and flags. app = utils.NewApp(gitCommit, "the go-ethereum command line interface") // flags that configure the node @@ -314,39 +317,78 @@ func startNode(ctx *cli.Context, stack *node.Node) { started = true log.Info("Enabled mining node!!!") } - defer close(core.Checkpoint) + defer close(core.CheckpointCh) + defer close(core.M1Ch) + for { + select { + case <-core.CheckpointCh: + log.Info("Checkpoint!!! It's time to reconcile node's state...") + ok, err := ethereum.ValidateStaker() + if err != nil { + utils.Fatalf("Can't verify masternode permission: %v", err) + } + if !ok { + log.Info("Only masternode can propose and verify blocks. Cancelling mining on this node...") + if started { + ethereum.StopMining() + started = false + } + log.Info("Cancelled mining mode!!!") + } else if !started { + log.Info("Masternode found. Enabling mining mode...") + // Use a reduced number of threads if requested + if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 { + type threaded interface { + SetThreads(threads int) + } + if th, ok := ethereum.Engine().(threaded); ok { + th.SetThreads(threads) + } + } + // Set the gas price to the limits from the CLI and start mining + ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name)) + if err := ethereum.StartStaking(true); err != nil { + utils.Fatalf("Failed to start mining: %v", err) + } + started = true + log.Info("Enabled mining node!!!") + } + case <-core.M1Ch: + log.Info("It's time to update new set of masternodes for the next epoch...") + // get masternodes information from smart contract + client, err := ethclient.Dial(stack.IPCEndpoint()) + if err != nil { + utils.Fatalf("Fail to connect RPC", "error", err) + } + addr := common.HexToAddress(common.Validator) + validator, err := validatorContract.NewXDCValidator(addr, client) + if err != nil { + utils.Fatalf("Fail to get validator smc", "error", err) + } + opts := new(bind.CallOpts) + candidates, err := validator.GetCandidates(opts) + if err != nil { + utils.Fatalf("Can't get list of candidates", "error", err) + } - for range core.Checkpoint { - log.Info("Checkpoint!!! It's time to reconcile node's state...") - ok, err := ethereum.ValidateStaker() - if err != nil { - utils.Fatalf("Can't verify validator permission: %v", err) - } - if !ok { - log.Info("Only validator can mine blocks. Cancelling mining on this node...") - if started { - ethereum.StopMining() - started = false - } - log.Info("Cancelled mining mode!!!") - } else if !started { - log.Info("Validator found. Enabling mining mode...") - // Use a reduced number of threads if requested - if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 { - type threaded interface { - SetThreads(threads int) - } - if th, ok := ethereum.Engine().(threaded); ok { - th.SetThreads(threads) + var ms []clique.Masternode + for _, candidate := range candidates { + v, err := validator.GetCandidateCap(opts, candidate) + if err != nil { + log.Warn("Can't get cap of a candidate. Will ignore him", "address", candidate, "error", err) } + ms = append(ms, clique.Masternode{candidate, v.Int64()}) } - // Set the gas price to the limits from the CLI and start mining - ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name)) - if err := ethereum.StartStaking(true); err != nil { - utils.Fatalf("Failed to start mining: %v", err) + // order by cap + sort.Slice(ms, func(i, j int) bool { + return ms[i].Stake > ms[j].Stake + }) + // update masternodes + err = ethereum.UpdateMasternodes(ms) + if err != nil { + utils.Fatalf("Can't update masternodes", "error", err) } - started = true - log.Info("Enabled mining node!!!") + log.Info("Masternodes are ready for the next epoch") } } }() diff --git a/common/types.go b/common/types.go index 78766f2221..cde9b77247 100644 --- a/common/types.go +++ b/common/types.go @@ -31,6 +31,7 @@ const ( HashLength = 32 AddressLength = 20 BlockSigners = "0x0000000000000000000000000000000000000089" + Validator = "0x0000000000000000000000000000000000000088" ) var ( diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 48fd76d44e..65d4eb560c 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -43,14 +43,18 @@ import ( ) const ( - checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database - inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory - inmemorySignatures = 4096 // Number of recent block signatures to keep in memory + checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database + inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory + inmemorySignatures = 4096 // Number of recent block signatures to keep in memory + wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers - wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers - genesisCoinBase = "0x0000000000000000000000000000000000000000" ) +type Masternode struct { + Address common.Address + Stake int64 +} + // Clique proof-of-authority protocol constants. var ( epochLength = uint64(30000) // Default number of blocks after which to checkpoint and reset the pending votes @@ -377,6 +381,10 @@ func (c *Clique) GetSnapshot(chain consensus.ChainReader, header *types.Header) return snap, nil } +func (c *Clique) StoreSnapshot(snap *Snapshot) error { + return snap.store(c.db) +} + func position(list []common.Address, x common.Address) int { for i, item := range list { if item == x { @@ -613,7 +621,6 @@ func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, sta if c.HookReward != nil && number%rCheckpoint == 0 { if err := c.HookReward(chain, state, header); err != nil { return nil, err - } } diff --git a/core/blockchain.go b/core/blockchain.go index 7755abe63b..6da1cd0119 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -47,7 +47,8 @@ import ( var ( blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) - Checkpoint = make(chan int) + CheckpointCh = make(chan int) + M1Ch = make(chan int) ErrNoGenesis = errors.New("Genesis not found in chain") ) @@ -61,6 +62,7 @@ const ( // BlockChainVersion ensures that an incompatible database forces a resync from scratch. BlockChainVersion = 3 + M1Gap = 10 ) // CacheConfig contains the configuration values for the trie caching/pruning @@ -1185,9 +1187,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty stats.processed++ stats.usedGas += usedGas stats.report(chain, i, bc.stateCache.TrieDB().Size()) - if i == len(chain)-1 { - if (bc.chainConfig.Clique != nil) && (chain[i].NumberU64()%bc.chainConfig.Clique.Epoch) == 0 { - Checkpoint <- 1 + if i == len(chain)-1 && bc.chainConfig.Clique != nil { + // epoch block + if (chain[i].NumberU64() % bc.chainConfig.Clique.Epoch) == 0 { + CheckpointCh <- 1 + } + // prepare set of masternodes for the next epoch + if (chain[i].NumberU64() % bc.chainConfig.Clique.Epoch) == (bc.chainConfig.Clique.Epoch - M1Gap) { + M1Ch <- 1 } } } diff --git a/eth/backend.go b/eth/backend.go index b78dc2d78a..123a356413 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -53,6 +53,8 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +const NumOfMasternodes = 99 + type LesServer interface { Start(srvr *p2p.Server) Stop() @@ -420,6 +422,33 @@ func (s *Ethereum) ValidateStaker() (bool, error) { return true, nil } +// Store new set of masternodes into local db +func (s *Ethereum) UpdateMasternodes(ms []clique.Masternode) error { + // get snapshot from local db + if s.chainConfig.Clique == nil { + return errors.New("not clique") + } + c := s.engine.(*clique.Clique) + snap, err := c.GetSnapshot(s.blockchain, s.blockchain.CurrentHeader()) + if err != nil { + return err + } + + snap.Signers = make(map[common.Address]struct{}) + for i, m := range ms { + if i == NumOfMasternodes { + break + } + snap.Signers[m.Address] = struct{}{} + } + err = c.StoreSnapshot(snap) + if err != nil { + return err + } + log.Trace("Stored masternodes snapshot to db", "number", snap.Number, "hash", snap.Hash) + return nil +} + func (s *Ethereum) StartStaking(local bool) error { eb, err := s.Etherbase() if err != nil { diff --git a/miner/worker.go b/miner/worker.go index 702091e77d..8f4c43df2c 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -530,8 +530,15 @@ func (self *worker) commitNewWork() { log.Info("Commit new mining work", "number", work.Block.Number(), "txs", work.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart))) self.unconfirmed.Shift(work.Block.NumberU64() - 1) } - if (work.config.Clique != nil) && (work.Block.NumberU64()%work.config.Clique.Epoch) == 0 { - core.Checkpoint <- 1 + if work.config.Clique != nil { + // epoch block + if (work.Block.NumberU64() % work.config.Clique.Epoch) == 0 { + core.CheckpointCh <- 1 + } + // prepare set of masternodes for the next epoch + if (work.Block.NumberU64() % work.config.Clique.Epoch) == (work.config.Clique.Epoch - core.M1Gap) { + core.M1Ch <- 1 + } } self.push(work) }