diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 7b92bd71e8b1..beefbfc4af95 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -3,6 +3,7 @@ package parlia import ( "bytes" "context" + "encoding/hex" "errors" "fmt" "io" @@ -25,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/systemcontracts" "github.com/ethereum/go-ethereum/core/types" @@ -45,8 +47,9 @@ const ( checkpointInterval = 1024 // Number of blocks after which to save the snapshot to the database defaultEpochLength = uint64(100) // Default number of blocks of checkpoint to update validatorSet from contract - 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 + nextForkHashSize = 4 // Fixed number of extra-data suffix bytes reserved for nextForkHash. validatorBytesLength = common.AddressLength wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers @@ -188,7 +191,8 @@ func ParliaRLP(header *types.Header, chainId *big.Int) []byte { type Parlia struct { chainConfig *params.ChainConfig // Chain config config *params.ParliaConfig // Consensus engine configuration parameters for parlia consensus - db ethdb.Database // Database to store and retrieve snapshot checkpoints + genesisHash common.Hash + db ethdb.Database // Database to store and retrieve snapshot checkpoints recentSnaps *lru.ARCCache // Snapshots for recent block to speed up signatures *lru.ARCCache // Signatures of recent blocks to speed up mining @@ -214,6 +218,7 @@ func New( chainConfig *params.ChainConfig, db ethdb.Database, ethAPI *ethapi.PublicBlockChainAPI, + genesisHash common.Hash, ) *Parlia { // get parlia config parliaConfig := chainConfig.Parlia @@ -243,6 +248,7 @@ func New( c := &Parlia{ chainConfig: chainConfig, config: parliaConfig, + genesisHash: genesisHash, db: db, ethAPI: ethAPI, recentSnaps: recentSnaps, @@ -599,10 +605,12 @@ func (p *Parlia) Prepare(chain consensus.ChainReader, header *types.Header) erro header.Difficulty = CalcDifficulty(snap, p.val) // Ensure the extra data has all it's components - if len(header.Extra) < extraVanity { - header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) + if len(header.Extra) < extraVanity-nextForkHashSize { + header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-nextForkHashSize-len(header.Extra))...) } - header.Extra = header.Extra[:extraVanity] + header.Extra = header.Extra[:extraVanity-nextForkHashSize] + nextForkHash := forkid.NextForkHash(p.chainConfig, p.genesisHash, number) + header.Extra = append(header.Extra, nextForkHash[:]...) if number%p.config.Epoch == 0 { newValidators, err := p.getCurrentValidators(header.ParentHash) @@ -638,6 +646,16 @@ func (p *Parlia) Prepare(chain consensus.ChainReader, header *types.Header) erro // rewards given. func (p *Parlia) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction, uncles []*types.Header, receipts *[]*types.Receipt, systemTxs *[]*types.Transaction, usedGas *uint64) error { + // warn if not in majority fork + number := header.Number.Uint64() + snap, err := p.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + panic(err) + } + nextForkHash := forkid.NextForkHash(p.chainConfig, p.genesisHash, number) + if !snap.isMajorityFork(hex.EncodeToString(nextForkHash[:])) { + log.Warn("there is a possible fork, and your client is not the majority. Please check...", "nextForkHash", hex.EncodeToString(nextForkHash[:])) + } // If the block is a epoch end block, verify the validator list // The verification can only be done when the state is ready, it can't be done in VerifyHeader. if header.Number.Uint64()%p.config.Epoch == 0 { @@ -666,11 +684,6 @@ func (p *Parlia) Finalize(chain consensus.ChainReader, header *types.Header, sta } } if header.Difficulty.Cmp(diffInTurn) != 0 { - number := header.Number.Uint64() - snap, err := p.snapshot(chain, number-1, header.ParentHash, nil) - if err != nil { - panic(err) - } spoiledVal := snap.supposeValidator() signedRecently := false for _, recent := range snap.Recents { @@ -689,7 +702,7 @@ func (p *Parlia) Finalize(chain consensus.ChainReader, header *types.Header, sta } } val := header.Coinbase - err := p.distributeIncoming(val, state, header, cx, txs, receipts, systemTxs, usedGas, false) + err = p.distributeIncoming(val, state, header, cx, txs, receipts, systemTxs, usedGas, false) if err != nil { panic(err) } diff --git a/consensus/parlia/snapshot.go b/consensus/parlia/snapshot.go index bd944c3bfa26..0b3ff3f519dc 100644 --- a/consensus/parlia/snapshot.go +++ b/consensus/parlia/snapshot.go @@ -18,6 +18,7 @@ package parlia import ( "bytes" + "encoding/hex" "encoding/json" "errors" "math/big" @@ -38,10 +39,11 @@ type Snapshot struct { ethAPI *ethapi.PublicBlockChainAPI sigCache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover - Number uint64 `json:"number"` // Block number where the snapshot was created - Hash common.Hash `json:"hash"` // Block hash where the snapshot was created - Validators map[common.Address]struct{} `json:"validators"` // Set of authorized validators at this moment - Recents map[uint64]common.Address `json:"recents"` // Set of recent validators for spam protections + Number uint64 `json:"number"` // Block number where the snapshot was created + Hash common.Hash `json:"hash"` // Block hash where the snapshot was created + Validators map[common.Address]struct{} `json:"validators"` // Set of authorized validators at this moment + Recents map[uint64]common.Address `json:"recents"` // Set of recent validators for spam protections + RecentForkHashes map[uint64]string `json:"recent_fork_hashes"` // Set of recent forkHash } // newSnapshot creates a new snapshot with the specified startup parameters. This @@ -56,13 +58,14 @@ func newSnapshot( ethAPI *ethapi.PublicBlockChainAPI, ) *Snapshot { snap := &Snapshot{ - config: config, - ethAPI: ethAPI, - sigCache: sigCache, - Number: number, - Hash: hash, - Recents: make(map[uint64]common.Address), - Validators: make(map[common.Address]struct{}), + config: config, + ethAPI: ethAPI, + sigCache: sigCache, + Number: number, + Hash: hash, + Recents: make(map[uint64]common.Address), + RecentForkHashes: make(map[uint64]string), + Validators: make(map[common.Address]struct{}), } for _, v := range validators { snap.Validators[v] = struct{}{} @@ -106,13 +109,14 @@ func (s *Snapshot) store(db ethdb.Database) error { // copy creates a deep copy of the snapshot func (s *Snapshot) copy() *Snapshot { cpy := &Snapshot{ - config: s.config, - ethAPI: s.ethAPI, - sigCache: s.sigCache, - Number: s.Number, - Hash: s.Hash, - Validators: make(map[common.Address]struct{}), - Recents: make(map[uint64]common.Address), + config: s.config, + ethAPI: s.ethAPI, + sigCache: s.sigCache, + Number: s.Number, + Hash: s.Hash, + Validators: make(map[common.Address]struct{}), + Recents: make(map[uint64]common.Address), + RecentForkHashes: make(map[uint64]string), } for v := range s.Validators { @@ -121,9 +125,22 @@ func (s *Snapshot) copy() *Snapshot { for block, v := range s.Recents { cpy.Recents[block] = v } + for block, id := range s.RecentForkHashes { + cpy.RecentForkHashes[block] = id + } return cpy } +func (s *Snapshot) isMajorityFork(forkHash string) bool { + ally := 0 + for _, h := range s.RecentForkHashes { + if h == forkHash { + ally++ + } + } + return ally > len(s.RecentForkHashes)/2 +} + func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainReader, parents []*types.Header, chainId *big.Int) (*Snapshot, error) { // Allow passing in no headers for cleaner code if len(headers) == 0 { @@ -153,6 +170,9 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainReader, p if limit := uint64(len(snap.Validators)/2 + 1); number >= limit { delete(snap.Recents, number-limit) } + if limit := uint64(len(snap.Validators)); number >= limit { + delete(snap.RecentForkHashes, number-limit) + } // Resolve the authorization key and check against signers validator, err := ecrecover(header, s.sigCache, chainId) if err != nil { @@ -191,8 +211,16 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainReader, p delete(snap.Recents, number-uint64(newLimit)-uint64(i)) } } + oldLimit = len(snap.Validators) + newLimit = len(newVals) + if newLimit < oldLimit { + for i := 0; i < oldLimit-newLimit; i++ { + delete(snap.RecentForkHashes, number-uint64(newLimit)-uint64(i)) + } + } snap.Validators = newVals } + snap.RecentForkHashes[number] = hex.EncodeToString(header.Extra[extraVanity-nextForkHashSize : extraVanity]) } snap.Number += uint64(len(headers)) snap.Hash = headers[len(headers)-1].Hash() diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index e433db44608c..d37688a31591 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -62,6 +62,28 @@ func NewID(chain *core.BlockChain) ID { ) } +func NextForkHash(config *params.ChainConfig, genesis common.Hash, head uint64) [4]byte { + // Calculate the starting checksum from the genesis hash + hash := crc32.ChecksumIEEE(genesis[:]) + + // Calculate the current fork checksum and the next fork block + var next uint64 + for _, fork := range gatherForks(config) { + if fork <= head { + // Fork already passed, checksum the previous hash and the fork number + hash = checksumUpdate(hash, fork) + continue + } + next = fork + break + } + if next == 0 { + return checksumToBytes(hash) + } else { + return checksumToBytes(checksumUpdate(hash, next)) + } +} + // newID is the internal version of NewID, which takes extracted values as its // arguments instead of a chain. The reason is to allow testing the IDs without // having to simulate an entire blockchain. diff --git a/eth/backend.go b/eth/backend.go index 7126509fdbf3..961603bd65fd 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -158,7 +158,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil} ethAPI := ethapi.NewPublicBlockChainAPI(eth.APIBackend) - eth.engine = CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb, ethAPI) + eth.engine = CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb, ethAPI, genesisHash) bcVersion := rawdb.ReadDatabaseVersion(chainDb) var dbVer = "" @@ -243,21 +243,21 @@ func makeExtraData(extra []byte) []byte { runtime.GOOS, }) } - if uint64(len(extra)) > params.MaximumExtraDataSize { - log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize) + if uint64(len(extra)) > params.MaximumExtraDataSize-params.ForkIDSize { + log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize-params.ForkIDSize) extra = nil } return extra } // CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service -func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, ee *ethapi.PublicBlockChainAPI) consensus.Engine { +func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, ee *ethapi.PublicBlockChainAPI, genesisHash common.Hash) consensus.Engine { // If proof-of-authority is requested, set it up if chainConfig.Clique != nil { return clique.New(chainConfig.Clique, db) } if chainConfig.Parlia != nil { - return parlia.New(chainConfig, db, ee) + return parlia.New(chainConfig, db, ee, genesisHash) } // Otherwise assume proof-of-work switch config.PowMode { diff --git a/les/client.go b/les/client.go index 5d5df47be166..0b88d8235591 100644 --- a/les/client.go +++ b/les/client.go @@ -102,7 +102,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { eventMux: ctx.EventMux, reqDist: newRequestDistributor(peers, &mclock.System{}), accountManager: ctx.AccountManager, - engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb, nil), + engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb, nil, genesisHash), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), serverPool: newServerPool(chainDb, config.UltraLightServers), diff --git a/params/protocol_params.go b/params/protocol_params.go index e0c09f3ced4f..155ef86dde53 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -24,6 +24,7 @@ const ( GenesisGasLimit uint64 = 4712388 // Gas limit of the Genesis block. MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis. + ForkIDSize uint64 = 4 // The length of fork id ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction. SloadGas uint64 = 50 // Multiplied by the number of 32-byte words that are copied (round up) for any *COPY operation and added. CallValueTransferGas uint64 = 9000 // Paid for CALL when the value transfer is non-zero.