Merge pull request #234 from ngtuna/double-validation-refactor

move DV to posv package
This commit is contained in:
Tuna 2018-10-24 18:01:22 +07:00 committed by GitHub
commit 0ae81138f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 111 additions and 142 deletions

View file

@ -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
}

View file

@ -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,16 +44,9 @@ 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
)
@ -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
}

View file

@ -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