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

cmd/hivechain: add ability to create valid clique chain #938

Merged
merged 8 commits into from
Oct 31, 2023
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
118 changes: 87 additions & 31 deletions cmd/hivechain/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@ import (
"math/big"
"strings"

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
"golang.org/x/exp/slices"
)
Expand All @@ -22,6 +29,7 @@ type generatorConfig struct {
// genesis options
forkInterval int // number of blocks between forks
lastFork string // last enabled fork
clique bool // create a clique chain

// chain options
txInterval int // frequency of blocks containing transactions
Expand Down Expand Up @@ -95,66 +103,100 @@ func (cfg *generatorConfig) createBlockModifiers() (list []*modifierInstance) {
return list
}

// run produces a chain.
// run produces a chain and writes it.
func (g *generator) run() error {
db := rawdb.NewMemoryDatabase()
engine := g.createConsensusEngine(db)

// Init genesis block.
trieconfig := *trie.HashDefaults
trieconfig.Preimages = true
triedb := trie.NewDatabase(db, &trieconfig)
genesis := g.genesis.MustCommit(db, triedb)
config := g.genesis.Config

powEngine := ethash.NewFaker()
posEngine := beacon.New(powEngine)
engine := posEngine

// Create the PoW chain.
chain, _ := core.GenerateChain(config, genesis, engine, db, g.cfg.chainLength, g.modifyBlock)
// Create the blocks.
chain, _ := core.GenerateChain(g.genesis.Config, genesis, engine, db, g.cfg.chainLength, g.modifyBlock)

// Import the chain. This runs all block validation rules.
bc, err := g.importChain(engine, chain)
if err != nil {
return err
}

g.blockchain = bc
return g.write()
}

func (g *generator) createConsensusEngine(db ethdb.Database) consensus.Engine {
var inner consensus.Engine
if g.genesis.Config.Clique != nil {
cliqueEngine := clique.New(g.genesis.Config.Clique, db)
cliqueEngine.Authorize(cliqueSignerAddr, func(signer accounts.Account, mimeType string, message []byte) ([]byte, error) {
sig, err := crypto.Sign(crypto.Keccak256(message), cliqueSignerKey)
return sig, err
})
inner = instaSeal{cliqueEngine}
} else {
inner = ethash.NewFaker()
}
return beacon.New(inner)
}

func (g *generator) importChain(engine consensus.Engine, chain []*types.Block) (*core.BlockChain, error) {
db := rawdb.NewMemoryDatabase()
cacheconfig := core.DefaultCacheConfigWithScheme("hash")
cacheconfig.Preimages = true
vmconfig := vm.Config{EnablePreimageRecording: true}
blockchain, err := core.NewBlockChain(db, cacheconfig, g.genesis, nil, engine, vmconfig, nil, nil)
if err != nil {
return fmt.Errorf("can't create blockchain: %v", err)
}
defer blockchain.Stop()
if i, err := blockchain.InsertChain(chain); err != nil {
return fmt.Errorf("chain validation error (block %d): %v", chain[i].Number(), err)
return nil, fmt.Errorf("can't create blockchain: %v", err)
}

// Write the outputs.
g.blockchain = blockchain
return g.write()
i, err := blockchain.InsertChain(chain)
if err != nil {
blockchain.Stop()
return nil, fmt.Errorf("chain validation error (block %d): %v", chain[i].Number(), err)
}
return blockchain, nil
}

func (g *generator) modifyBlock(i int, gen *core.BlockGen) {
fmt.Println("generating block", gen.Number())
if g.genesis.Config.Clique != nil {
g.setClique(i, gen)
}
g.setDifficulty(i, gen)
g.runModifiers(i, gen)
}

func (g *generator) setClique(i int, gen *core.BlockGen) {
mergeblock := g.genesis.Config.MergeNetsplitBlock
if mergeblock != nil && gen.Number().Cmp(mergeblock) >= 0 {
return
}

gen.SetCoinbase(cliqueSignerAddr)
// Add a positive vote to keep the signer in the set.
gen.SetNonce(types.BlockNonce{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
// The clique engine requires the block to have blank extra-data of the correct length
// before sealing.
gen.SetExtra(make([]byte, 32+65))
}

func (g *generator) setDifficulty(i int, gen *core.BlockGen) {
chaincfg := g.genesis.Config
mergeblock := chaincfg.MergeNetsplitBlock
if mergeblock == nil {
mergeblock = new(big.Int).SetUint64(math.MaxUint64)
}
mergecmp := gen.Number().Cmp(mergeblock)
if mergecmp > 0 {
switch gen.Number().Cmp(mergeblock) {
case 1:
gen.SetPoS()
return
}

prev := gen.PrevBlock(i - 1)
diff := ethash.CalcDifficulty(g.genesis.Config, gen.Timestamp(), prev.Header())
if mergecmp == 0 {
case 0:
gen.SetPoS()
chaincfg.TerminalTotalDifficulty = new(big.Int).Set(g.td)
} else {
g.td = g.td.Add(g.td, diff)
gen.SetDifficulty(diff)
default:
g.td = g.td.Add(g.td, gen.Difficulty())
}
}

Expand All @@ -165,10 +207,6 @@ func (g *generator) runModifiers(i int, gen *core.BlockGen) {
}

ctx := &genBlockContext{index: i, block: gen, gen: g}
if gen.Number().Uint64() > 0 {
prev := gen.PrevBlock(-1)
ctx.gasLimit = core.CalcGasLimit(prev.GasLimit(), g.genesis.GasLimit)
}

// Modifier scheduling: we cycle through the available modifiers until enough have
// executed successfully. It also stops when all of them return false from apply()
Expand All @@ -188,3 +226,21 @@ func (g *generator) runModifiers(i int, gen *core.BlockGen) {
}
}
}

// instaSeal wraps a consensus engine with instant block sealing. When a block is produced
// using FinalizeAndAssemble, it also applies Seal.
type instaSeal struct{ consensus.Engine }

// FinalizeAndAssemble implements consensus.Engine, accumulating the block and uncle rewards,
// setting the final state and assembling the block.
func (e instaSeal) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) {
block, err := e.Engine.FinalizeAndAssemble(chain, header, state, txs, uncles, receipts, withdrawals)
if err != nil {
return nil, err
}
sealedBlock := make(chan *types.Block, 1)
if err = e.Engine.Seal(chain, block, sealedBlock, nil); err != nil {
return nil, err
}
return <-sealedBlock, nil
}
52 changes: 44 additions & 8 deletions cmd/hivechain/genesis.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
package main

import (
"crypto/ecdsa"
"fmt"
"math/big"
"strings"

"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"golang.org/x/exp/slices"
)

var initialBalance, _ = new(big.Int).SetString("1000000000000000000000000000000000000", 10)

const (
ethashMinimumDifficulty = 131072
genesisBaseFee = params.InitialBaseFee
blocktimeSec = 10 // hard-coded in core.GenerateChain
genesisBaseFee = params.InitialBaseFee
blocktimeSec = 10 // hard-coded in core.GenerateChain
cliqueEpoch = 30000
)

var (
cliqueSignerKey = knownAccounts[8].key
cliqueSignerAddr = crypto.PubkeyToAddress(cliqueSignerKey.PublicKey)
)

// Ethereum mainnet forks in order of introduction.
Expand Down Expand Up @@ -52,7 +59,16 @@ func (cfg *generatorConfig) createChainConfig() *params.ChainConfig {

chainid, _ := new(big.Int).SetString("3503995874084926", 10)
chaincfg.ChainID = chainid
chaincfg.Ethash = new(params.EthashConfig)

// Set consensus algorithm.
if cfg.clique {
chaincfg.Clique = &params.CliqueConfig{
Period: blocktimeSec,
Epoch: cliqueEpoch,
}
} else {
chaincfg.Ethash = new(params.EthashConfig)
}

// Apply forks.
forks := cfg.forkBlocks()
Expand Down Expand Up @@ -104,20 +120,31 @@ func (cfg *generatorConfig) createChainConfig() *params.ChainConfig {
// Special case for merged-from-genesis networks.
// Need to assign TTD here because the genesis block won't be processed by GenerateChain.
if chaincfg.MergeNetsplitBlock != nil && chaincfg.MergeNetsplitBlock.Sign() == 0 {
chaincfg.TerminalTotalDifficulty = big.NewInt(ethashMinimumDifficulty)
chaincfg.TerminalTotalDifficulty = cfg.genesisDifficulty()
}

return chaincfg
}

func (cfg *generatorConfig) genesisDifficulty() *big.Int {
if cfg.clique {
return big.NewInt(1)
}
return new(big.Int).Set(params.MinimumDifficulty)
}

// createGenesis creates the genesis block and config.
func (cfg *generatorConfig) createGenesis() *core.Genesis {
var g core.Genesis
g.Config = cfg.createChainConfig()

// Block attributes.
g.Difficulty = big.NewInt(ethashMinimumDifficulty)
g.ExtraData = []byte("hivechain")
g.Difficulty = cfg.genesisDifficulty()
if cfg.clique {
g.ExtraData = cliqueInit(cliqueSignerKey)
} else {
g.ExtraData = []byte("hivechain")
}
g.GasLimit = params.GenesisGasLimit * 8
zero := new(big.Int)
if g.Config.IsLondon(zero) {
Expand Down Expand Up @@ -160,7 +187,7 @@ func (cfg *generatorConfig) forkBlocks() map[string]uint64 {

// lastForkIndex returns the index of the latest enabled for in allForkNames.
func (cfg *generatorConfig) lastForkIndex() int {
if cfg.lastFork == "" {
if cfg.lastFork == "" || cfg.lastFork == "frontier" {
return len(allForkNames) - 1
}
index := slices.Index(allForkNames, strings.ToLower(cfg.lastFork))
Expand All @@ -173,3 +200,12 @@ func (cfg *generatorConfig) lastForkIndex() int {
func (cfg *generatorConfig) blockTimestamp(num uint64) uint64 {
return num * blocktimeSec
}

// cliqueInit creates the genesis extradata for a clique network with one signer.
func cliqueInit(signer *ecdsa.PrivateKey) []byte {
vanity := make([]byte, 32)
copy(vanity, "hivechain")
d := append(vanity, cliqueSignerAddr[:]...)
d = append(d, make([]byte, 65)...) // signature
return d
}
1 change: 1 addition & 0 deletions cmd/hivechain/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func generateCommand(args []string) {
flag.IntVar(&cfg.forkInterval, "fork-interval", 0, "Number of blocks between fork activations")
flag.StringVar(&cfg.outputDir, "outdir", ".", "Destination directory")
flag.StringVar(&cfg.lastFork, "lastfork", "", "Name of the last fork to activate")
flag.BoolVar(&cfg.clique, "clique", false, "Create a clique chain")
flag.CommandLine.Parse(args)

if *outlist != "" {
Expand Down
11 changes: 2 additions & 9 deletions cmd/hivechain/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ type genBlockContext struct {
index int
block *core.BlockGen
gen *generator

gasLimit uint64
txCount int
}

// Number returns the block number.
Expand All @@ -47,7 +44,7 @@ func (ctx *genBlockContext) Timestamp() uint64 {

// HasGas reports whether the block still has more than the given amount of gas left.
func (ctx *genBlockContext) HasGas(gas uint64) bool {
return ctx.gasLimit > gas
return ctx.block.Gas() > gas
}

// AddNewTx adds a transaction into the block.
Expand All @@ -56,11 +53,7 @@ func (ctx *genBlockContext) AddNewTx(sender *genAccount, data types.TxData) *typ
if err != nil {
panic(err)
}
if ctx.gasLimit < tx.Gas() {
panic("not enough gas for tx")
}
ctx.block.AddTx(tx)
ctx.gasLimit -= tx.Gas()
return tx
}

Expand Down Expand Up @@ -99,7 +92,7 @@ func (ctx *genBlockContext) AccountNonce(addr common.Address) uint64 {

// Signer returns a signer for the current block.
func (ctx *genBlockContext) Signer() types.Signer {
return types.MakeSigner(ctx.ChainConfig(), ctx.block.Number(), ctx.block.Timestamp())
return ctx.block.Signer()
}

// ChainConfig returns the chain config.
Expand Down
10 changes: 10 additions & 0 deletions cmd/hivechain/output_forkenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@ import (
func (g *generator) writeForkEnv() error {
cfg := g.genesis.Config
env := make(map[string]string)

// basic settings
env["HIVE_CHAIN_ID"] = fmt.Sprint(cfg.ChainID)
env["HIVE_NETWORK_ID"] = fmt.Sprint(cfg.ChainID)

// config consensus algorithm
if cfg.Clique != nil {
env["HIVE_CLIQUE_PERIOD"] = fmt.Sprint(cfg.Clique.Period)
} else {
env["HIVE_SKIP_POW"] = "1"
}

// forks
setNum := func(hive string, blocknum *big.Int) {
if blocknum != nil {
env[hive] = blocknum.Text(10)
Expand Down
Loading