主页 > imtoken怎么退出账号 > 以太坊源码分析(四)——初始化创世块
以太坊源码分析(四)——初始化创世块
一、简介
通过前几章学习了以太坊的基本架构后,我们通过搭建单节点来讲解代码,覆盖以太坊的主要流程。在本节中,您将学习:如何初始化创世块
2. 代码研究 2.1 准备工作
本系列文章主要研究以太坊的源码,因此以太坊的编译工作不再详细展开。 有需要的可以参考这篇文章。
2.2 genesis.json文件
假设您已经在 goland 中正确设置了项目,然后使用示例创世文件来初始化您自己的私有网络创世块。
{
"config": {
"chainId": 399,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0
},
"alloc": {
"0x0000000000000000000000000000000000000001": {
"balance": "0x84595161401484a000000"
},
},
"coinbase": "0x0000000000000000000000000000000000000000",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0x2fefd8",
"nonce": "0x000000000000ff42",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00"
}
接下来,让我们看看 initGenesis 函数的作用。
2.3 initGenesis函数
当在命令行参数中设置“init”命令时调用 initGenesis 函数,并使用给定 Json 格式的创世文件初始化创世块。 如果失败,创世文件将不会写入创世块。
// initGenesis will initialise the given JSON format genesis file and writes it as
// the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
func initGenesis(ctx *cli.Context) error {
// Make sure we have a valid genesis JSON
genesisPath := ctx.Args().First()
if len(genesisPath) == 0 {
utils.Fatalf("Must supply path to genesis JSON file")
}
file, err := os.Open(genesisPath)
if err != nil {
utils.Fatalf("Failed to read genesis file: %v", err)
}
defer file.Close()
genesis := new(core.Genesis)
if err := json.NewDecoder(file).Decode(genesis); err != nil {
utils.Fatalf("invalid genesis file: %v", err)
}
// Open an initialise both full and light databases
stack := makeFullNode(ctx)
for _, name := range []string{"chaindata", "lightchaindata"} {
chaindb, err := stack.OpenDatabase(name, 0, 0)
if err != nil {
utils.Fatalf("Failed to open database: %v", err)
}
_, hash, err := core.SetupGenesisBlock(chaindb, genesis)
if err != nil {
utils.Fatalf("Failed to write genesis block: %v", err)
}
log.Info("Successfully wrote genesis state", "database", name, "hash", hash)
}
return nil
}
该函数执行如下:
2.4 core.SetupGenesisBlock函数
在 initGenesis 函数中,我们看到设置 Genesis 块的内容是由 SetupGenesisBlock 函数完成的。 如果你是一个不熟悉以太坊的学生,直接看这个函数的逻辑可能很容易搞糊涂。 还是一样,我们可以先看注解:
// 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.
注释更详细地描述了该功能的功能和主要分支:
SetupGenesisBlock 函数用于写入或更新数据库中的创世块。 根据参数的不同,会出现以下4种情况:
函数结果影响创世块中的链配置,如果(update configuration)与链配置兼容,即不在本地头块下指定分叉块,则更新保存的链配置。 如果(更新的配置)与链配置冲突,将报告配置冲突错误,并返回新的、未写入的创世配置。
从中我们可以得到两条信息:
1)应用成功的新配置会保存到创世块(数据库)中,这也是main函数。
2)如果写入/更新了新的创世配置文件,首先会影响链配置,即如果要更新链配置,只需重新初始化链配置即可,前提是更新后的配置不兼容中的数据库配置冲突。
SetupGenesisBlock 函数:
func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
if genesis != nil && genesis.Config == nil {
return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
}
// 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
}
// Check whether the genesis block is already written.
if genesis != nil {
hash := genesis.ToBlock(nil).Hash()
if hash != stored {
return genesis.Config, hash, &GenesisMismatchError{stored, hash}
}
}
// Get the existing chain configuration.
newcfg := genesis.configOrDefault(stored)
storedcfg, err := GetChainConfig(db, stored)
if err != nil {
if err == ErrChainConfigNotFound {
// 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
}
// 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
}
// 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")
}
compatErr := storedcfg.CheckCompatible(newcfg, height)
if compatErr != nil && height != 0 && compatErr.RewindTo != 0 {
return newcfg, stored, compatErr
}
return newcfg, stored, WriteChainConfig(db, stored, newcfg)
}
函数逻辑可以分为两部分:
1. 以下是数据库中不存在创世块的逻辑
2.下面是数据库中创世块的逻辑
审查
让我们重温前面的步骤,回头看看这个函数想要做什么:
1)如评论中所述,SetupGenesisBlock函数会返回链配置,并在特定时间,保存Genesis fast配置,同时更新链配置
2)函数进入后,先检查输入参数
3)然后从数据库中获取保存的区块hash,判断hash(即区块)是否存在
4)评论中提到,当数据库中不存在创世块时,使用默认的创世块配置或提供的入参配置,通过genesis.Commit()函数将区块提交至数据库。
5)如果创世块已经存在于数据库中,执行如下逻辑:
6) 将genesis中的内容应用到statedb中,比较本次配置生成的创世块的hash是否与数据库中的创世块的hash相同。 如果不是,则返回genesis的配置并报错(做Compatibility判断,genesis生成的创世块不能)。 通过对比调用这个函数的情况,如果这个函数发生错误,或者非compatErr错误,那么调用函数会报错退出,即只会使用两个配置相同的init操作,并且函数在这里不会报错退出,但是在退出之前,数据库中创建信息的数据部分会被修改。
7) 接下来,从数据库中获取链的配置信息。 在genesis.Commit()函数中,最后写入链配置信息。 如果无法从数据库中获取链配置,则将newcfg配置写入数据库并退出。 exit error code是写数据库时的错误码,即如果写数据库时没有出现错误码,则函数正常结束。 newcfg是通过genesis合成得到并存储的链配置。 如果 genesis 存在,则使用其配置,否则使用默认配置。 一般来说以太坊源码解析以太坊源码解析,这一步还是处理异常的。
8) 接下来,检查兼容性。 在此之前,先解决特殊情况,即对于数据库中的创世块不存在且数据库中的创世块不是主网创世块,则退出。 然后检查兼容性。 这里的逻辑其实不是很清楚。 以后有时间再看。
三、总结
在这一章中,我主要找到了初始化创世块的函数initGenesis函数,然后看了看如何设置创世块的逻辑。 感觉前面还好,后面的逻辑有点不清楚,需要进一步研究。