diff options
Diffstat (limited to 'core/genesis.go')
-rw-r--r-- | core/genesis.go | 378 |
1 files changed, 217 insertions, 161 deletions
diff --git a/core/genesis.go b/core/genesis.go index 9fa49f4d0..08d4ae2d1 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -17,230 +17,286 @@ package core import ( - "compress/bzip2" - "compress/gzip" - "encoding/base64" - "encoding/json" + "errors" "fmt" - "io" - "io/ioutil" "math/big" "strings" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" ) -// WriteGenesisBlock writes the genesis block to the database as block number 0 -func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block, error) { - contents, err := ioutil.ReadAll(reader) - if err != nil { - return nil, err - } +//go:generate gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go +//go:generate gencodec -type GenesisAccount -field-override genesisAccountMarshaling -out gen_genesis_account.go - var genesis struct { - ChainConfig *params.ChainConfig `json:"config"` - Nonce string - Timestamp string - ParentHash string - ExtraData string - GasLimit string - Difficulty string - Mixhash string - Coinbase string - Alloc map[string]struct { - Code string - Storage map[string]string - Balance string - Nonce string - } - } +var errGenesisNoConfig = errors.New("genesis has no chain configuration") - if err := json.Unmarshal(contents, &genesis); err != nil { - return nil, err - } - if genesis.ChainConfig == nil { - genesis.ChainConfig = params.AllProtocolChanges +// Genesis specifies the header fields, state of a genesis block. It also defines hard +// fork switch-over blocks through the chain configuration. +type Genesis struct { + Config *params.ChainConfig `json:"config" optional:"true"` + Nonce uint64 `json:"nonce" optional:"true"` + Timestamp uint64 `json:"timestamp" optional:"true"` + ParentHash common.Hash `json:"parentHash" optional:"true"` + ExtraData []byte `json:"extraData" optional:"true"` + GasLimit uint64 `json:"gasLimit"` + Difficulty *big.Int `json:"difficulty"` + Mixhash common.Hash `json:"mixHash" optional:"true"` + Coinbase common.Address `json:"coinbase" optional:"true"` + Alloc GenesisAlloc `json:"alloc"` +} + +// GenesisAlloc specifies the initial state that is part of the genesis block. +type GenesisAlloc map[common.Address]GenesisAccount + +// GenesisAccount is an account in the state of the genesis block. +type GenesisAccount struct { + Code []byte `json:"code" optional:"true"` + Storage map[common.Hash]common.Hash `json:"storage" optional:"true"` + Balance *big.Int `json:"balance"` + Nonce uint64 `json:"nonce" optional:"true"` +} + +// field type overrides for gencodec +type genesisSpecMarshaling struct { + Nonce math.HexOrDecimal64 + Timestamp math.HexOrDecimal64 + ExtraData hexutil.Bytes + GasLimit math.HexOrDecimal64 + Difficulty *math.HexOrDecimal256 + Alloc map[common.UnprefixedAddress]GenesisAccount +} +type genesisAccountMarshaling struct { + Code hexutil.Bytes + Balance *math.HexOrDecimal256 + Nonce math.HexOrDecimal64 +} + +// GenesisMismatchError is raised when trying to overwrite an existing +// genesis block with an incompatible one. +type GenesisMismatchError struct { + Stored, New common.Hash +} + +func (e *GenesisMismatchError) Error() string { + return fmt.Sprintf("wrong genesis block in database (have %x, new %x)", e.Stored[:8], e.New[:8]) +} + +// SetupGenesisBlock writes or updates the genesis block in db. +// The block that will be used is: +// +// genesis == nil genesis != nil +// +------------------------------------------ +// db has no genesis | main-net default | genesis +// db has genesis | from DB | genesis (if compatible) +// +// The stored chain configuration will be updated if it is compatible (i.e. does not +// specify a fork block below the local head block). In case of a conflict, the +// error is a *params.ConfigCompatError and the new, unwritten config is returned. +// +// The returned chain configuration is never nil. +func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) { + if genesis != nil && genesis.Config == nil { + return params.AllProtocolChanges, common.Hash{}, errGenesisNoConfig } - // creating with empty hash always works - statedb, _ := state.New(common.Hash{}, chainDb) - for addr, account := range genesis.Alloc { - balance, ok := math.ParseBig256(account.Balance) - if !ok { - return nil, fmt.Errorf("invalid balance for account %s: %q", addr, account.Balance) - } - nonce, ok := math.ParseUint64(account.Nonce) - if !ok { - return nil, fmt.Errorf("invalid nonce for account %s: %q", addr, account.Nonce) + // Just commit the new block if there is no stored genesis block. + stored := GetCanonicalHash(db, 0) + if (stored == common.Hash{}) { + if genesis == nil { + log.Info("Writing default main-net genesis block") + genesis = DefaultGenesisBlock() + } else { + log.Info("Writing custom genesis block") } + block, err := genesis.Commit(db) + return genesis.Config, block.Hash(), err + } - address := common.HexToAddress(addr) - statedb.AddBalance(address, balance) - statedb.SetCode(address, common.FromHex(account.Code)) - statedb.SetNonce(address, nonce) - for key, value := range account.Storage { - statedb.SetState(address, common.HexToHash(key), common.HexToHash(value)) + // Check whether the genesis block is already written. + if genesis != nil { + block, _ := genesis.ToBlock() + hash := block.Hash() + if hash != stored { + return genesis.Config, block.Hash(), &GenesisMismatchError{stored, hash} } } - root, stateBatch := statedb.CommitBatch(false) - difficulty, ok := math.ParseBig256(genesis.Difficulty) - if !ok { - return nil, fmt.Errorf("invalid difficulty: %q", genesis.Difficulty) + // Get the existing chain configuration. + newcfg := genesis.configOrDefault(stored) + storedcfg, err := GetChainConfig(db, stored) + if err != nil { + if err == ChainConfigNotFoundErr { + // This case happens if a genesis write was interrupted. + log.Warn("Found genesis block without chain config") + err = WriteChainConfig(db, stored, newcfg) + } + return newcfg, stored, err } - gaslimit, ok := math.ParseUint64(genesis.GasLimit) - if !ok { - return nil, fmt.Errorf("invalid gas limit: %q", genesis.GasLimit) + // Special case: don't change the existing config of a non-mainnet chain if no new + // config is supplied. These chains would get AllProtocolChanges (and a compat error) + // if we just continued here. + if genesis == nil && stored != params.MainNetGenesisHash { + return storedcfg, stored, nil } - nonce, ok := math.ParseUint64(genesis.Nonce) - if !ok { - return nil, fmt.Errorf("invalid nonce: %q", genesis.Nonce) + + // Check config compatibility and write the config. Compatibility errors + // are returned to the caller unless we're already at block zero. + height := GetBlockNumber(db, GetHeadHeaderHash(db)) + if height == missingNumber { + return newcfg, stored, fmt.Errorf("missing block number for head header hash") } - timestamp, ok := math.ParseBig256(genesis.Timestamp) - if !ok { - return nil, fmt.Errorf("invalid timestamp: %q", genesis.Timestamp) + compatErr := storedcfg.CheckCompatible(newcfg, height) + if compatErr != nil && height != 0 && compatErr.RewindTo != 0 { + return newcfg, stored, compatErr } + return newcfg, stored, WriteChainConfig(db, stored, newcfg) +} - block := types.NewBlock(&types.Header{ - Nonce: types.EncodeNonce(nonce), - Time: timestamp, - ParentHash: common.HexToHash(genesis.ParentHash), - Extra: common.FromHex(genesis.ExtraData), - GasLimit: new(big.Int).SetUint64(gaslimit), - Difficulty: difficulty, - MixDigest: common.HexToHash(genesis.Mixhash), - Coinbase: common.HexToAddress(genesis.Coinbase), - Root: root, - }, nil, nil, nil) +func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { + switch { + case g != nil: + return g.Config + case ghash == params.MainNetGenesisHash: + return params.MainnetChainConfig + case ghash == params.TestNetGenesisHash: + return params.TestnetChainConfig + default: + return params.AllProtocolChanges + } +} - if block := GetBlock(chainDb, block.Hash(), block.NumberU64()); block != nil { - log.Info("Genesis block known, writing canonical number") - err := WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) - if err != nil { - return nil, err +// ToBlock creates the block and state of a genesis specification. +func (g *Genesis) ToBlock() (*types.Block, *state.StateDB) { + db, _ := ethdb.NewMemDatabase() + statedb, _ := state.New(common.Hash{}, db) + for addr, account := range g.Alloc { + statedb.AddBalance(addr, account.Balance) + statedb.SetCode(addr, account.Code) + statedb.SetNonce(addr, account.Nonce) + for key, value := range account.Storage { + statedb.SetState(addr, key, value) } - return block, nil } + root := statedb.IntermediateRoot(false) + head := &types.Header{ + Nonce: types.EncodeNonce(g.Nonce), + Time: new(big.Int).SetUint64(g.Timestamp), + ParentHash: g.ParentHash, + Extra: g.ExtraData, + GasLimit: new(big.Int).SetUint64(g.GasLimit), + Difficulty: g.Difficulty, + MixDigest: g.Mixhash, + Coinbase: g.Coinbase, + Root: root, + } + if g.GasLimit == 0 { + head.GasLimit = params.GenesisGasLimit + } + if g.Difficulty == nil { + head.Difficulty = params.GenesisDifficulty + } + return types.NewBlock(head, nil, nil, nil), statedb +} - if err := stateBatch.Write(); err != nil { +// Commit writes the block and state of a genesis specification to the database. +// The block is committed as the canonical head block. +func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) { + block, statedb := g.ToBlock() + if _, err := statedb.CommitTo(db, false); err != nil { return nil, fmt.Errorf("cannot write state: %v", err) } - if err := WriteTd(chainDb, block.Hash(), block.NumberU64(), difficulty); err != nil { + if err := WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty); err != nil { return nil, err } - if err := WriteBlock(chainDb, block); err != nil { + if err := WriteBlock(db, block); err != nil { return nil, err } - if err := WriteBlockReceipts(chainDb, block.Hash(), block.NumberU64(), nil); err != nil { + if err := WriteBlockReceipts(db, block.Hash(), block.NumberU64(), nil); err != nil { return nil, err } - if err := WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()); err != nil { + if err := WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil { return nil, err } - if err := WriteHeadBlockHash(chainDb, block.Hash()); err != nil { + if err := WriteHeadBlockHash(db, block.Hash()); err != nil { return nil, err } - if err := WriteChainConfig(chainDb, block.Hash(), genesis.ChainConfig); err != nil { + if err := WriteHeadHeaderHash(db, block.Hash()); err != nil { return nil, err } - return block, nil -} - -// GenesisBlockForTesting creates a block in which addr has the given wei balance. -// The state trie of the block is written to db. the passed db needs to contain a state root -func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block { - statedb, _ := state.New(common.Hash{}, db) - obj := statedb.GetOrNewStateObject(addr) - obj.SetBalance(balance) - root, err := statedb.Commit(false) - if err != nil { - panic(fmt.Sprintf("cannot write state: %v", err)) + config := g.Config + if config == nil { + config = params.AllProtocolChanges } - block := types.NewBlock(&types.Header{ - Difficulty: params.GenesisDifficulty, - GasLimit: params.GenesisGasLimit, - Root: root, - }, nil, nil, nil) - return block + return block, WriteChainConfig(db, block.Hash(), config) } -type GenesisAccount struct { - Address common.Address - Balance *big.Int -} - -func WriteGenesisBlockForTesting(db ethdb.Database, accounts ...GenesisAccount) *types.Block { - accountJson := "{" - for i, account := range accounts { - if i != 0 { - accountJson += "," - } - accountJson += fmt.Sprintf(`"0x%x":{"balance":"%d"}`, account.Address, account.Balance) - } - accountJson += "}" - - testGenesis := fmt.Sprintf(`{ - "nonce":"0x%x", - "gasLimit":"0x%x", - "difficulty":"0x%x", - "alloc": %s -}`, types.EncodeNonce(0), params.GenesisGasLimit.Bytes(), params.GenesisDifficulty.Bytes(), accountJson) - block, err := WriteGenesisBlock(db, strings.NewReader(testGenesis)) +// MustCommit writes the genesis block and state to db, panicking on error. +// The block is committed as the canonical head block. +func (g *Genesis) MustCommit(db ethdb.Database) *types.Block { + block, err := g.Commit(db) if err != nil { panic(err) } return block } -// WriteDefaultGenesisBlock assembles the official Ethereum genesis block and -// writes it - along with all associated state - into a chain database. -func WriteDefaultGenesisBlock(chainDb ethdb.Database) (*types.Block, error) { - return WriteGenesisBlock(chainDb, strings.NewReader(DefaultGenesisBlock())) +// GenesisBlockForTesting creates and writes a block in which addr has the given wei balance. +func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block { + g := Genesis{Alloc: GenesisAlloc{addr: {Balance: balance}}} + return g.MustCommit(db) } -// WriteTestNetGenesisBlock assembles the test network genesis block and -// writes it - along with all associated state - into a chain database. -func WriteTestNetGenesisBlock(chainDb ethdb.Database) (*types.Block, error) { - return WriteGenesisBlock(chainDb, strings.NewReader(DefaultTestnetGenesisBlock())) +// DefaultGenesisBlock returns the Ethereum main net genesis block. +func DefaultGenesisBlock() *Genesis { + return &Genesis{ + Config: params.MainnetChainConfig, + Nonce: 66, + ExtraData: hexutil.MustDecode("0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"), + GasLimit: 5000, + Difficulty: big.NewInt(17179869184), + Alloc: decodePrealloc(mainnetAllocData), + } } -// DefaultGenesisBlock assembles a JSON string representing the default Ethereum -// genesis block. -func DefaultGenesisBlock() string { - reader, err := gzip.NewReader(base64.NewDecoder(base64.StdEncoding, strings.NewReader(defaultGenesisBlock))) - if err != nil { - panic(fmt.Sprintf("failed to access default genesis: %v", err)) - } - blob, err := ioutil.ReadAll(reader) - if err != nil { - panic(fmt.Sprintf("failed to load default genesis: %v", err)) +// DefaultTestnetGenesisBlock returns the Ropsten network genesis block. +func DefaultTestnetGenesisBlock() *Genesis { + return &Genesis{ + Config: params.TestnetChainConfig, + Nonce: 66, + ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"), + GasLimit: 16777216, + Difficulty: big.NewInt(1048576), + Alloc: decodePrealloc(testnetAllocData), } - return string(blob) } -// DefaultTestnetGenesisBlock assembles a JSON string representing the default Ethereum -// test network genesis block. -func DefaultTestnetGenesisBlock() string { - reader := bzip2.NewReader(base64.NewDecoder(base64.StdEncoding, strings.NewReader(defaultTestnetGenesisBlock))) - blob, err := ioutil.ReadAll(reader) - if err != nil { - panic(fmt.Sprintf("failed to load default genesis: %v", err)) +// DevGenesisBlock returns the 'geth --dev' genesis block. +func DevGenesisBlock() *Genesis { + return &Genesis{ + Config: params.AllProtocolChanges, + Nonce: 42, + GasLimit: 4712388, + Difficulty: big.NewInt(131072), + Alloc: decodePrealloc(devAllocData), } - return string(blob) } -// DevGenesisBlock assembles a JSON string representing a local dev genesis block. -func DevGenesisBlock() string { - reader := bzip2.NewReader(base64.NewDecoder(base64.StdEncoding, strings.NewReader(defaultDevnetGenesisBlock))) - blob, err := ioutil.ReadAll(reader) - if err != nil { - panic(fmt.Sprintf("failed to load dev genesis: %v", err)) +func decodePrealloc(data string) GenesisAlloc { + var p []struct{ Addr, Balance *big.Int } + if err := rlp.NewStream(strings.NewReader(data), 0).Decode(&p); err != nil { + panic(err) + } + ga := make(GenesisAlloc, len(p)) + for _, account := range p { + ga[common.BigToAddress(account.Addr)] = GenesisAccount{Balance: account.Balance} } - return string(blob) + return ga } |