diff --git a/consensus/posv/posv.go b/consensus/posv/posv.go index 06c9c94367..29c3da567d 100644 --- a/consensus/posv/posv.go +++ b/consensus/posv/posv.go @@ -19,12 +19,13 @@ package posv import ( "bytes" "errors" + "fmt" "math/big" "math/rand" + "strconv" "sync" "time" - "fmt" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -47,6 +48,7 @@ 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 + M2ByteLength = 4 ) type Masternode struct { @@ -132,6 +134,8 @@ var ( // errUnauthorized is returned if a header is signed by a non-authorized entity. errUnauthorized = errors.New("unauthorized") + errFailedDoubleValidation = errors.New("wrong pair of creator-validator in double validation") + // errWaitTransactions is returned if an empty block is attempted to be sealed // 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. @@ -578,6 +582,8 @@ func (c *Posv) VerifySeal(chain consensus.ChainReader, header *types.Header) err // consensus protocol requirements. The method accepts an optional list of parent // headers that aren't yet part of the local blockchain to generate the snapshots // from. +// verifySeal also checks the pair of creator-validator set in the header satisfies +// the double validation. func (c *Posv) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { // Verifying the genesis block is not supported number := header.Number.Uint64() @@ -591,7 +597,7 @@ func (c *Posv) verifySeal(chain consensus.ChainReader, header *types.Header, par } // Resolve the authorization key and check against signers - signer, err := ecrecover(header, c.signatures) + creator, err := ecrecover(header, c.signatures) if err != nil { return err } @@ -604,22 +610,22 @@ func (c *Posv) verifySeal(chain consensus.ChainReader, header *types.Header, par for _, n := range snap.GetSigners() { nstring = append(nstring, n.String()) } - if _, ok := snap.Signers[signer]; !ok { + if _, ok := snap.Signers[creator]; !ok { valid := false for _, m := range masternodes { - if m == signer { + if m == creator { valid = true break } } if !valid { - log.Debug("Unauthorized signer found", "block number", number, "signer", signer.String(), "masternodes", mstring, "snapshot from parent block", nstring) + log.Debug("Unauthorized creator found", "block number", number, "creator", creator.String(), "masternodes", mstring, "snapshot from parent block", nstring) return errUnauthorized } } if len(masternodes) > 1 { for seen, recent := range snap.Recents { - if recent == signer { + if recent == creator { // Signer is among recents, only fail if the current block doesn't shift it out // There is only case that we don't allow signer to create two continuous blocks. if limit := uint64(2); seen > number-limit { @@ -631,9 +637,50 @@ func (c *Posv) verifySeal(chain consensus.ChainReader, header *types.Header, par } } } + + // header must contain validator info following double validation design + validator, err := RecoverValidator(header) + if err != nil { + return err + } + + // verify validator + assignedValidator, err := c.getValidator(creator, snap, chain, header) + if err != nil { + return err + } + if validator != assignedValidator { + log.Debug("Bad block detected. Header contains wrong pair of creator-validator", "creator", creator, "assigned validator", assignedValidator, "wrong validator", validator) + return errFailedDoubleValidation + } return nil } +func (c *Posv) getValidator(creator common.Address, snap *Snapshot, chain consensus.ChainReader, header *types.Header) (common.Address, error) { + epoch := c.config.Epoch + no := header.Number.Uint64() + cpNo := no + if no%epoch != 0 { + cpNo = no - (no % epoch) + } + if cpNo == 0 { + return common.Address{}, nil + } + cpHeader := chain.GetHeaderByNumber(cpNo) + if cpHeader == nil { + if no%epoch == 0 { + cpHeader = header + } else { + return common.Address{}, fmt.Errorf("couldn't find checkpoint header") + } + } + m, err := GetM1M2FromCheckpointHeader(cpHeader) + if err != nil { + return common.Address{}, err + } + return m[creator], nil +} + // Prepare implements consensus.Engine, preparing all the consensus fields of the // header for running the transactions on top. func (c *Posv) Prepare(chain consensus.ChainReader, header *types.Header) error { @@ -926,3 +973,50 @@ func RemovePenaltiesFromBlock(chain consensus.ChainReader, signers []common.Addr } return signers } + +// Get masternodes address from checkpoint Header. +func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address { + masternodes := make([]common.Address, (len(checkpointHeader.Extra)-extraVanity-extraSeal)/common.AddressLength) + for i := 0; i < len(masternodes); i++ { + copy(masternodes[i][:], checkpointHeader.Extra[extraVanity+i*common.AddressLength:]) + } + return masternodes +} + +// Get m2 list from checkpoint block. +func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header) (map[common.Address]common.Address, error) { + if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 { + return nil, errors.New("This block is not checkpoint block epoc.") + } + m1m2 := map[common.Address]common.Address{} + // Get signers from this block. + masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader) + validators := ExtractValidatorsFromBytes(checkpointHeader.Validators) + + if len(validators) < len(masternodes) { + return nil, errors.New("len(m2) is less than len(m1)") + } + if len(masternodes) > 0 { + for i, m1 := range masternodes { + m1m2[m1] = masternodes[validators[i]%int64(len(masternodes))] + } + } + return m1m2, nil +} + +// Extract validators from byte array. +func ExtractValidatorsFromBytes(byteValidators []byte) []int64 { + lenValidator := len(byteValidators) / M2ByteLength + var validators []int64 + for i := 0; i < lenValidator; i++ { + trimByte := bytes.Trim(byteValidators[i*M2ByteLength:(i+1)*M2ByteLength], "\x00") + intNumber, err := strconv.Atoi(string(trimByte)) + if err != nil { + log.Error("Can not convert string to integer", "error", err) + return []int64{} + } + validators = append(validators, int64(intNumber)) + } + + return validators +} diff --git a/contracts/utils.go b/contracts/utils.go index 6046baed85..0d9a6f9a9e 100644 --- a/contracts/utils.go +++ b/contracts/utils.go @@ -23,11 +23,18 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io" + "math/big" + "math/rand" + "strconv" + "time" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/posv" "github.com/ethereum/go-ethereum/contracts/blocksigner/contract" randomizeContract "github.com/ethereum/go-ethereum/contracts/randomize/contract" contractValidator "github.com/ethereum/go-ethereum/contracts/validator/contract" @@ -37,18 +44,11 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/pkg/errors" - "io" - "math/big" - "math/rand" - "strconv" - "time" ) const ( - M2ByteLength = 4 - extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity - extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal + extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity + extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal ) type rewardLog struct { @@ -256,30 +256,13 @@ func BuildValidatorFromM2(listM2 []int64) []byte { var validatorBytes []byte for _, numberM2 := range listM2 { // Convert number to byte. - m2Byte := common.LeftPadBytes([]byte(fmt.Sprintf("%d", numberM2)), M2ByteLength) + m2Byte := common.LeftPadBytes([]byte(fmt.Sprintf("%d", numberM2)), posv.M2ByteLength) validatorBytes = append(validatorBytes, m2Byte...) } return validatorBytes } -// Extract validators from byte array. -func ExtractValidatorsFromBytes(byteValidators []byte) []int64 { - lenValidator := len(byteValidators) / M2ByteLength - var validators []int64 - for i := 0; i < lenValidator; i++ { - trimByte := bytes.Trim(byteValidators[i*M2ByteLength:(i+1)*M2ByteLength], "\x00") - intNumber, err := strconv.Atoi(string(trimByte)) - if err != nil { - log.Error("Can not convert string to integer", "error", err) - return []int64{} - } - validators = append(validators, int64(intNumber)) - } - - return validators -} - // Decode validator hex string. func DecodeValidatorsHexData(validatorsStr string) ([]int64, error) { validatorsByte, err := hexutil.Decode(validatorsStr) @@ -287,7 +270,7 @@ func DecodeValidatorsHexData(validatorsStr string) ([]int64, error) { return nil, err } - return ExtractValidatorsFromBytes(validatorsByte), nil + return posv.ExtractValidatorsFromBytes(validatorsByte), nil } // Decrypt randomize from secrets and opening. @@ -558,33 +541,3 @@ func isInt(strNumber string) bool { return false } } - -// Get masternodes address from checkpoint Header. -func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address { - masternodes := make([]common.Address, (len(checkpointHeader.Extra)-extraVanity-extraSeal)/common.AddressLength) - for i := 0; i < len(masternodes); i++ { - copy(masternodes[i][:], checkpointHeader.Extra[extraVanity+i*common.AddressLength:]) - } - return masternodes -} - -// Get m2 list from checkpoint block. -func GetM1M2FromCheckpointBlock(checkpointBlock *types.Block) (map[common.Address]common.Address, error) { - if checkpointBlock.Number().Int64()%common.EpocBlockRandomize != 0 { - return nil, errors.New("This block is not checkpoint block epoc.") - } - m1m2 := map[common.Address]common.Address{} - // Get signers from this block. - masternodes := GetMasternodesFromCheckpointHeader(checkpointBlock.Header()) - validators := ExtractValidatorsFromBytes(checkpointBlock.Header().Validators) - - if len(validators) < len(masternodes) { - return nil, errors.New("len(m2) is less than len(m1)") - } - if len(masternodes) > 0 { - for i, m1 := range masternodes { - m1m2[m1] = masternodes[validators[i]%int64(len(masternodes))] - } - } - return m1m2, nil -} diff --git a/eth/backend.go b/eth/backend.go index 8213bf66fc..ceb638718a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -24,7 +24,6 @@ import ( "runtime" "sync" "sync/atomic" - "time" "bytes" "github.com/ethereum/go-ethereum/accounts" @@ -189,60 +188,6 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { if eth.chainConfig.Posv != nil { c := eth.engine.(*posv.Posv) - // Hook double validation - doubleValidateHook := func(block *types.Block) error { - parentBlk := eth.blockchain.GetBlockByHash(block.ParentHash()) - if parentBlk == nil { - return fmt.Errorf("Fail to get parent block for hash: %v", block.ParentHash()) - } - snap, err := c.GetSnapshot(eth.blockchain, parentBlk.Header()) - if err != nil { - if err == consensus.ErrUnknownAncestor { - log.Warn("Block chain forked.", "error", err) - } - return fmt.Errorf("Fail to get snapshot for sign tx validator: %v", err) - } - if _, authorized := snap.Signers[eth.etherbase]; authorized { - m2, err := getM2(snap, eth, block) - if err != nil { - return fmt.Errorf("Fail to validate M2 condition for importing block: %v", err) - } - if eth.etherbase != m2 { - txCh := make(chan core.TxPreEvent, txChanSize) - subEvent := eth.txPool.SubscribeSpecialTxPreEvent(txCh) - defer subEvent.Unsubscribe() - // firstly, look into pending txPool - pendingMap, err := eth.txPool.Pending() - if err != nil { - log.Warn("Fail to get txPool pending", "err", err, "Continue with empty txPool pending.") - //reset pendingMap - pendingMap = map[common.Address]types.Transactions{} - } - txsSentFromM2 := pendingMap[m2] - if len(txsSentFromM2) > 0 { - for _, tx := range txsSentFromM2 { - if tx.To().String() == common.BlockSigners { - return nil - } - } - } - //then wait until signTx from m2 comes into txPool - select { - case event := <-txCh: - from, err := eth.txPool.GetSender(event.Tx) - if (err == nil) && (event.Tx.To().String() == common.BlockSigners) && (from == m2) { - return nil - } - //timeout 10s - case <-time.After(time.Duration(10) * time.Second): - return fmt.Errorf("Time out waiting for confirmation from m2") - } - } - return nil - } - return fmt.Errorf("This address is not authorized to validate block") - } - signHook := func(block *types.Block) error { if err := contracts.CreateTransactionSign(chainConfig, eth.txPool, eth.accountManager, block, chainDb); err != nil { return fmt.Errorf("Fail to create tx sign for importing block: %v", err) @@ -250,7 +195,6 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { return nil } - eth.protocolManager.fetcher.SetDoubleValidateHook(doubleValidateHook) eth.protocolManager.fetcher.SetSignHook(signHook) // Hook prepares validators M2 for the current epoch @@ -372,28 +316,6 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { return eth, nil } -func getM2(snap *posv.Snapshot, eth *Ethereum, block *types.Block) (common.Address, error) { - epoch := eth.chainConfig.Posv.Epoch - no := block.NumberU64() - cpNo := no - if no%epoch != 0 { - cpNo = no - (no % epoch) - } - if cpNo == 0 { - return eth.etherbase, nil - } - cpBlk := eth.blockchain.GetBlockByNumber(cpNo) - m, err := contracts.GetM1M2FromCheckpointBlock(cpBlk) - if err != nil { - return common.Address{}, err - } - m1, err := posv.WhoIsCreator(snap, block.Header()) - if err != nil { - return common.Address{}, err - } - return m[m1], nil -} - func makeExtraData(extra []byte) []byte { if len(extra) == 0 { // create default extradata