diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 799384985a..89423e8b55 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -103,6 +103,10 @@ func (w *wizard) makeGenesis() { copy(genesis.ExtraData[32+i*common.AddressLength:], signer[:]) } + fmt.Println() + fmt.Println("How many blocks per checkpoint? (default = 990)") + genesis.Config.Clique.RewardCheckpoint = uint64(w.readDefaultInt(990)) + default: log.Crit("Invalid consensus engine choice", "choice", choice) } diff --git a/cmd/tomo/main.go b/cmd/tomo/main.go index fd2bcde89c..756869d5de 100644 --- a/cmd/tomo/main.go +++ b/cmd/tomo/main.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/debug" @@ -287,19 +288,66 @@ func startNode(ctx *cli.Context, stack *node.Node) { if err := stack.Service(ðereum); err != nil { utils.Fatalf("Ethereum service not running: %v", err) } - // Use a reduced number of threads if requested - if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 { - type threaded interface { - SetThreads(threads int) + go func() { + started := false + ok, err := ethereum.ValidateMiner() + if err != nil { + utils.Fatalf("Can't verify validator permission: %v", err) } - if th, ok := ethereum.Engine().(threaded); ok { - th.SetThreads(threads) + if ok { + 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) + } + } + // 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.StartMining(true); err != nil { + utils.Fatalf("Failed to start mining: %v", err) + } + started = true + log.Info("Enabled mining node!!!") } - } - // 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.StartMining(true); err != nil { - utils.Fatalf("Failed to start mining: %v", err) - } + defer close(core.Checkpoint) + + for range core.Checkpoint { + log.Info("Checkpoint!!! It's time to reconcile node's state...") + ok, err := ethereum.ValidateMiner() + 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) + } + } + // 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.StartMining(true); err != nil { + utils.Fatalf("Failed to start mining: %v", err) + } + started = true + log.Info("Enabled mining node!!!") + } + } + }() } } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 2aa4648552..f5b4ef1972 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -25,6 +25,7 @@ import ( "sync" "time" + "encoding/json" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -39,7 +40,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - lru "github.com/hashicorp/golang-lru" + "github.com/hashicorp/golang-lru" ) const ( @@ -47,7 +48,8 @@ const ( 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" ) // Clique proof-of-authority protocol constants. @@ -277,9 +279,6 @@ func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header, } // Checkpoint blocks need to enforce zero beneficiary checkpoint := (number % c.config.Epoch) == 0 - if checkpoint && header.Coinbase != (common.Address{}) { - return errInvalidCheckpointBeneficiary - } // Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints if !bytes.Equal(header.Nonce[:], nonceAuthVote) && !bytes.Equal(header.Nonce[:], nonceDropVote) { return errInvalidVote @@ -367,6 +366,41 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *type return c.verifySeal(chain, header, parents) } +func (c *Clique) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*Snapshot, error) { + number := header.Number.Uint64() + log.Trace("take snapshot", "number", number, "hash", header.Hash()) + snap, err := c.snapshot(chain, number, header.Hash(), nil) + if err != nil { + return nil, err + } + return snap, nil +} + +func position(list []common.Address, x common.Address) int { + for i, item := range list { + if item == x { + return i + } + } + return -1 +} + +func YourTurn(snap *Snapshot, header *types.Header, cur common.Address) (bool, error) { + if header.Number.Uint64() == 0 { + // Not check signer for genesis block. + return true, nil + } + + pre, err := ecrecover(header, snap.sigcache) + if err != nil { + return false, err + } + preIndex := position(snap.signers(), pre) + curIndex := position(snap.signers(), cur) + log.Info("Debugging info", "number of masternodes", len(snap.signers()), "previous", pre, "position", preIndex, "current", cur, "position", curIndex) + return (preIndex+1)%len(snap.signers()) == curIndex, nil +} + // snapshot retrieves the authorization snapshot at a given point in time. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) { // Search for a snapshot in memory or on disk for checkpoints @@ -572,10 +606,9 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // set block reward // FIXME: unit Ether could be too plump - chainReward := new(big.Int).SetUint64(chain.Config().Clique.Reward * params.Ether) - - reward := new(big.Int).Set(chainReward) - state.AddBalance(header.Coinbase, reward) + if err := c.accumulateRewards(chain, state, header); err != nil { + return nil, err + } // No block rewards in PoA, so the state remains as is and uncles are dropped header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) @@ -690,3 +723,55 @@ func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API { Public: false, }} } + +func (c *Clique) accumulateRewards(chain consensus.ChainReader, state *state.StateDB, header *types.Header) error { + type rewardLog struct { + Sign uint64 `json:"sign"` + Reward float64 `json:"reward"` + } + + number := header.Number.Uint64() + rCheckpoint := chain.Config().Clique.RewardCheckpoint + + if number > 0 && rCheckpoint > 0 && number%rCheckpoint == 0 { + // Not reward for singer of genesis block and only calculate reward at checkpoint block. + parentHeader := chain.GetHeaderByHash(header.ParentHash) + startBlockNumber := number - rCheckpoint + 1 + endBlockNumber := parentHeader.Number.Uint64() + signers := make(map[common.Address]*rewardLog) + totalSigner := uint64(0) + + for i := startBlockNumber; i <= endBlockNumber; i++ { + blockHeader := chain.GetHeaderByNumber(i) + if signer, err := ecrecover(blockHeader, c.signatures); err != nil { + return err + } else { + _, exist := signers[signer] + if exist { + signers[signer].Sign++ + } else { + signers[signer] = &rewardLog{1, 0} + } + totalSigner++ + } + } + + chainReward := new(big.Int).SetUint64(chain.Config().Clique.Reward * params.Ether) + // Update balance reward. + calcReward := new(big.Int) + for signer, rLog := range signers { + calcReward.Mul(chainReward, new(big.Int).SetUint64(rLog.Sign)) + calcReward.Div(calcReward, new(big.Int).SetUint64(totalSigner)) + rLog.Reward = float64(calcReward.Int64()) + + state.AddBalance(signer, calcReward) + } + jsonSigners, err := json.Marshal(signers) + if err != nil { + return err + } + log.Info("TOMO - Calculate reward at checkpoint", "startBlock", startBlockNumber, "endBlock", endBlockNumber, "signers", string(jsonSigners), "totalSigner", totalSigner, "totalReward", chainReward) + } + + return nil +} diff --git a/core/blockchain.go b/core/blockchain.go index b33eb85a44..1d55df9394 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -47,8 +47,8 @@ import ( var ( blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) - - ErrNoGenesis = errors.New("Genesis not found in chain") + Checkpoint = make(chan int) + ErrNoGenesis = errors.New("Genesis not found in chain") ) const ( @@ -1185,6 +1185,11 @@ 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 + } + } } // Append a single chain head event if we've progressed the chain if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() { diff --git a/eth/backend.go b/eth/backend.go index 94aad23101..9f31f935c6 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -334,6 +334,29 @@ func (self *Ethereum) SetEtherbase(etherbase common.Address) { self.miner.SetEtherbase(etherbase) } +// ValidateMiner checks if node's address is in set of validators +func (s *Ethereum) ValidateMiner() (bool, error) { + eb, err := s.Etherbase() + if err != nil { + return false, err + } + if s.chainConfig.Clique != nil { + //check if miner's wallet is in set of validators + c := s.engine.(*clique.Clique) + snap, err := c.GetSnapshot(s.blockchain, s.blockchain.CurrentHeader()) + if err != nil { + return false, fmt.Errorf("Can't verify miner: %v", err) + } + if _, authorized := snap.Signers[eb]; !authorized { + //This miner doesn't belong to set of validators + return false, nil + } + } else { + return false, fmt.Errorf("Only verify miners in Clique protocol") + } + return true, nil +} + func (s *Ethereum) StartMining(local bool) error { eb, err := s.Etherbase() if err != nil { diff --git a/miner/worker.go b/miner/worker.go index 15395ae0b9..fdd69a0301 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" @@ -397,6 +398,29 @@ func (self *worker) commitNewWork() { tstart := time.Now() parent := self.chain.CurrentBlock() + // Only try to commit new work if we are mining + if atomic.LoadInt32(&self.mining) == 1 { + // check if we are right after parent's coinbase in the list + // only go with Clique + if self.config.Clique != nil { + c := self.engine.(*clique.Clique) + snap, err := c.GetSnapshot(self.chain, parent.Header()) + if err != nil { + log.Error("Failed when trying to commit new work", "err", err) + return + } + ok, err := clique.YourTurn(snap, parent.Header(), self.coinbase) + if err != nil { + log.Error("Failed when trying to commit new work", "err", err) + return + } + if !ok { + log.Info("Not our turn to commit block. Wait for next time") + return + } + } + } + tstamp := tstart.Unix() if parent.Time().Cmp(new(big.Int).SetInt64(tstamp)) >= 0 { tstamp = parent.Time().Int64() + 1 @@ -488,6 +512,9 @@ 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 + } self.push(work) } diff --git a/params/config.go b/params/config.go index 868ed1ff84..e6ce0828d5 100644 --- a/params/config.go +++ b/params/config.go @@ -133,9 +133,10 @@ func (c *EthashConfig) String() string { // CliqueConfig is the consensus engine configs for proof-of-authority based sealing. type CliqueConfig struct { - Period uint64 `json:"period"` // Number of seconds between blocks to enforce - Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint - Reward uint64 `json:"reward"` // Block reward - unit Ether + Period uint64 `json:"period"` // Number of seconds between blocks to enforce + Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint + Reward uint64 `json:"reward"` // Block reward - unit Ether + RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards. } // String implements the stringer interface, returning the consensus engine details.