diff --git a/consensus/posv/api.go b/consensus/posv/api.go index 33c0e77772..fe68560c8a 100644 --- a/consensus/posv/api.go +++ b/consensus/posv/api.go @@ -71,7 +71,7 @@ func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) { if err != nil { return nil, err } - return snap.signers(), nil + return snap.GetSigners(), nil } // GetSignersAtHash retrieves the state snapshot at a given block. @@ -84,7 +84,7 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) { if err != nil { return nil, err } - return snap.signers(), nil + return snap.GetSigners(), nil } // Proposals returns the current proposals the node tries to uphold and vote on. diff --git a/consensus/posv/posv.go b/consensus/posv/posv.go index 3cb9137878..3f63137c36 100644 --- a/consensus/posv/posv.go +++ b/consensus/posv/posv.go @@ -136,6 +136,8 @@ var ( // on an instant chain (0 second period). It's important to refuse these as the // block reward is zero, so an empty block just bloats the chain... fast. errWaitTransactions = errors.New("waiting for transactions") + + ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint block") ) // SignerFn is a signer callback function to request a hash to be signed by a @@ -213,9 +215,10 @@ type Posv struct { signFn clique.SignerFn // Signer function to authorize hashes with lock sync.RWMutex // Protects the signer fields - HookReward func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) error - HookPenalty func(chain consensus.ChainReader, blockNumberEpoc uint64) ([]common.Address, error) - HookPrepare func(header *types.Header, signers []common.Address) error + HookReward func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) error + HookPenalty func(chain consensus.ChainReader, blockNumberEpoc uint64) ([]common.Address, error) + HookValidator func(header *types.Header, signers []common.Address) error + HookVerifyMNs func(header *types.Header, signers []common.Address) error } // New creates a Posv proof-of-stake-voting consensus engine with the initial @@ -379,7 +382,7 @@ func (c *Posv) verifyCascadingFields(chain consensus.ChainReader, header *types. return errInvalidCheckpointPenalties } } - signers := snap.signers() + signers := snap.GetSigners() signers = common.RemoveItemFromArray(signers, penPenalties) for i := 1; i <= common.LimitPenaltyEpoch; i++ { if number > uint64(i)*c.config.Epoch { @@ -391,6 +394,12 @@ func (c *Posv) verifyCascadingFields(chain consensus.ChainReader, header *types. if !bytes.Equal(header.Extra[extraVanity:extraSuffix], byteMasterNodes) { return errInvalidCheckpointSigners } + if c.HookVerifyMNs != nil { + err := c.HookVerifyMNs(header, signers) + if err != nil { + return err + } + } } // All basic checks passed, verify the seal and return return c.verifySeal(chain, header, parents) @@ -581,7 +590,7 @@ func (c *Posv) verifySeal(chain consensus.ChainReader, header *types.Header, par mstring = append(mstring, m.String()) } nstring := []string{} - for _, n := range snap.signers() { + for _, n := range snap.GetSigners() { nstring = append(nstring, n.String()) } if _, ok := snap.Signers[signer]; !ok { @@ -656,7 +665,7 @@ func (c *Posv) Prepare(chain consensus.ChainReader, header *types.Header) error header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) } header.Extra = header.Extra[:extraVanity] - signers := snap.signers() + signers := snap.GetSigners() if number%c.config.Epoch == 0 { if c.HookPenalty != nil { penSigners, err := c.HookPenalty(chain, number) @@ -696,8 +705,11 @@ func (c *Posv) Prepare(chain consensus.ChainReader, header *types.Header) error if header.Time.Int64() < time.Now().Unix() { header.Time = big.NewInt(time.Now().Unix()) } - if c.HookPrepare != nil { - c.HookPrepare(header, signers) + if c.HookValidator != nil { + c.HookValidator(header, signers) + if err != nil { + return err + } } return nil } @@ -710,7 +722,7 @@ func (c *Posv) UpdateMasternodes(chain consensus.ChainReader, header *types.Head if err != nil { return err } - currentSigners := snap.signers() + currentSigners := snap.GetSigners() proposedSigners := make(map[common.Address]struct{}) // count all addresses in ms to be masternode for _, m := range ms { @@ -724,7 +736,7 @@ func (c *Posv) UpdateMasternodes(chain consensus.ChainReader, header *types.Head } } nm := []string{} - newSigners := snap.signers() + newSigners := snap.GetSigners() for _, n := range newSigners { nm = append(nm, n.String()) } diff --git a/consensus/posv/snapshot.go b/consensus/posv/snapshot.go index 5240a7e3f8..bc75b0d320 100644 --- a/consensus/posv/snapshot.go +++ b/consensus/posv/snapshot.go @@ -286,7 +286,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { } // signers retrieves the list of authorized signers in ascending order. -func (s *Snapshot) signers() []common.Address { +func (s *Snapshot) GetSigners() []common.Address { signers := make([]common.Address, 0, len(s.Signers)) for signer := range s.Signers { signers = append(signers, signer) @@ -303,7 +303,7 @@ func (s *Snapshot) signers() []common.Address { // inturn returns if a signer at a given block height is in-turn or not. func (s *Snapshot) inturn(number uint64, signer common.Address) bool { - signers, offset := s.signers(), 0 + signers, offset := s.GetSigners(), 0 for offset < len(signers) && signers[offset] != signer { offset++ } diff --git a/core/error.go b/core/error.go index d55c7840e6..86b093b151 100644 --- a/core/error.go +++ b/core/error.go @@ -34,4 +34,6 @@ var ( ErrNonceTooHigh = errors.New("nonce too high") ErrNotPoSV = errors.New("Posv not found in config") + + ErrNotFoundM1 = errors.New("list M1 not found ") ) diff --git a/eth/backend.go b/eth/backend.go index c67ed1bad6..be7e7275df 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -25,6 +25,7 @@ import ( "sync" "sync/atomic" + "bytes" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -189,7 +190,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { if eth.chainConfig.Posv != nil { c := eth.engine.(*posv.Posv) - // Inject hook for send tx sign to smartcontract after insert block into chain. + // Hook sends tx sign to smartcontract after inserting block to chain. importedHook := func(block *types.Block) { snap, err := c.GetSnapshot(eth.blockchain, block.Header()) if err != nil { @@ -209,42 +210,20 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { } eth.protocolManager.fetcher.SetImportedHook(importedHook) - // Hook will process when preparing block. - c.HookPrepare = func(header *types.Header, signers []common.Address) error { - client, err := eth.blockchain.GetClient() - if err != nil { - log.Error("Fail to connect IPC client for penalty.", "error", err) - } + // Hook prepares validators M2 for the current epoch + c.HookValidator = func(header *types.Header, signers []common.Address) error { number := header.Number.Int64() - // Check m2 exists on chaindb. - // Get secrets and opening at epoc block checkpoint. if number > 0 && number%common.EpocBlockRandomize == 0 { - var candidates []int64 - lenSigners := int64(len(signers)) - - if lenSigners > 0 { - for _, addr := range signers { - random, err := contracts.GetRandomizeFromContract(client, addr) - if err != nil { - log.Error("Fail to get random m2 from contract.", "error", err) - } - candidates = append(candidates, random) - } - - // Get randomize m2 list. - m2, err := contracts.GenM2FromRandomize(candidates, lenSigners) - if err != nil { - log.Error("Can not get m2 from randomize SC", "error", err) - } - if len(m2) > 0 { - header.Validators = contracts.BuildValidatorFromM2(m2) - log.Debug("New set Validators", "m2", m2, "number", header.Number.Uint64()) - } + validators, err := GetValidators(eth.blockchain, signers) + if err != nil { + return err } + header.Validators = validators } return nil } - // Hook penalty. + + // Hook scans for bad masternodes and decide to penalty them c.HookPenalty = func(chain consensus.ChainReader, blockNumberEpoc uint64) ([]common.Address, error) { client, err := eth.blockchain.GetClient() if err != nil { @@ -285,7 +264,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { return []common.Address{}, nil } - // Hook reward for posv validator. + // Hook calculates reward for masternodes c.HookReward = func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) error { client, err := eth.blockchain.GetClient() if err != nil { @@ -330,6 +309,21 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { return nil } + + // Hook verifies masternodes set + c.HookVerifyMNs = func(header *types.Header, signers []common.Address) error { + number := header.Number.Int64() + if number > 0 && number%common.EpocBlockRandomize == 0 { + validators, err := GetValidators(eth.blockchain, signers) + if err != nil { + return err + } + if !bytes.Equal(header.Validators, validators) { + return posv.ErrInvalidCheckpointValidators + } + } + return nil + } } return eth, nil @@ -606,3 +600,37 @@ func (s *Ethereum) Stop() error { return nil } + +func GetValidators(bc *core.BlockChain, masternodes []common.Address) ([]byte, error) { + if bc.Config().Posv == nil { + return nil, core.ErrNotPoSV + } + client, err := bc.GetClient() + if err != nil { + return nil, err + } + // Check m2 exists on chaindb. + // Get secrets and opening at epoc block checkpoint. + + var candidates []int64 + if err != nil { + return nil, err + } + lenSigners := int64(len(masternodes)) + if lenSigners > 0 { + for _, addr := range masternodes { + random, err := contracts.GetRandomizeFromContract(client, addr) + if err != nil { + return nil, err + } + candidates = append(candidates, random) + } + // Get randomize m2 list. + m2, err := contracts.GenM2FromRandomize(candidates, lenSigners) + if err != nil { + return nil, err + } + return contracts.BuildValidatorFromM2(m2), nil + } + return nil, core.ErrNotFoundM1 +}