Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[R4R]support fork id in header; elegant upgrade #53

Merged
merged 2 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package parlia
import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"io"
Expand All @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -243,6 +248,7 @@ func New(
c := &Parlia{
chainConfig: chainConfig,
config: parliaConfig,
genesisHash: genesisHash,
db: db,
ethAPI: ethAPI,
recentSnaps: recentSnaps,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down
64 changes: 46 additions & 18 deletions consensus/parlia/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package parlia

import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"math/big"
Expand All @@ -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
Expand All @@ -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{}{}
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to ensure if the length of s.RecentForkHashes is closed to the length of current validatorset?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated.

}

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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
22 changes: 22 additions & 0 deletions core/forkid/forkid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<nil>"
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion les/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down