Skip to content

Commit

Permalink
cmd/hivechain: add ability to create valid clique chain (#938)
Browse files Browse the repository at this point in the history
The hivechain tool can now create a signed clique chain. It can be important to have a chain with
actually valid block seal in some test scenarios, and go-ethereum can't mine ethash blocks anymore,
so clique was the only option.

This pulls in a go-ethereum update to get access to an improved GenerateChain implementation.
  • Loading branch information
fjl authored Oct 31, 2023
1 parent a15bd62 commit 3a8450a
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 99 deletions.
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

0 comments on commit 3a8450a

Please sign in to comment.