前言:本人从12年毕业一直做的是web后台开发,虽然一直关注区块链开发但一直少有真正了解其中开发细节的欲望。因为一方面炒币的新闻实在是层出不穷,个人内心往往会把所有宣称区块链开发的公司都当作在炒作,出于对炒作的鄙视所以不愿意与之有什么瓜葛。另一方面我对devops的工作思路和微服务的概念很喜欢所以就放在对docker和k8s之类的技术上,并不想再多开一个技能分支免得啥都学不会。一年下来,终于看到炒币者出现了相当规模的失利退场,而我对整个devops的思路也理清觉得差不多可以把区块链开发的技能树点亮了,所以进行了一些线下meetup了解一下现在区块链发展的状态。机缘巧合之下了解了“初链”这个目前基于以太坊虚拟机的区块链方案。而“初链”社区对开发者是很友好的了,刚进群就给了40RMB的红包激励开发者对“初链”进行技术解析。由于本人对区块链开发也是相当基础对一些区块链的概念仅仅是了解机制的程度,所以解析难免有失误,献丑的地方希望大家不吝赐教。
本着只讲自己看懂了的部分进行讲解的原则,下面我就把我对“初链”beta版本的上线中对“初链”初始化的部分进行讲解,而其中水果链的快链和慢链之间的问题不是我现在能够理解的,且待后期学会之后慢慢道来。(说个题外话,“初链”这个名字真是绝了,中文真是博大精深^_^,听起来就像初恋,初又有初始的意思,可能是希望区块链开发都从初链开始吧,而且英文的truechain也是谐音又有“真实,认真”的意思,反正咋解释都很不错!)
讲一下查看代码的思路:根据log了解初链初始化到运行时所标记的内容->根据标记内容到源码中对应找到方法->把所有方法倒推到最起始的位置->理解代码的运行机制梳理出运行逻辑
感谢程序员写代码时提示的不同log内容,希望log能更加友好一点。
Sanitizing cache to Go's GC limits
Maximum peer count
Committee Node info:
Starting peer-to-peer node
Allocated cache and file handles
Initialised chain configuration
Initialised chain configuration
Initialising Truechain protocol
Loaded most recent local header
Loaded most recent local full block
Loaded most recent local fast block
Loaded most recent local Fastheader
Loaded most recent local full Fastblock
Loaded most recent local fast Fastblock
Loaded local transaction journal
Regenerated local transaction journal
InitNodeInfo
init mineFruit
Starting P2P networking
get committee ..
IPC endpoint opened
Transaction pool price threshold updated
start miner --miner start function
Starting mining operation
RLPx listener up
singleloop start.
Committee Info
Committee member:
start to mine
FetchFastBlock
FetchFastBlock
FetchFastBlock
FetchFastBlock
FetchFastBlock
FetchFastBlock
如log所示,最后已经开始挖矿了。
运行之前还要执行一个命令$ getrue init path/to/genesis.json
{
"config": {
"chainId": 10,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"alloc":{
"0xbd54a6c8298a70e9636d0555a77ffa412abdd71a" : { "balance" : 90000000000000000000000},
"0x3c2e0a65a023465090aaedaa6ed2975aec9ef7f9" : { "balance" : 10000000000000000000000}
},
"committee":[
{
"address": "0x76ea2f3a002431fede1141b660dbb75c26ba6d97",
"publickey": "0x04044308742b61976de7344edb8662d6d10be1c477dd46e8e4c433c1288442a79183480894107299ff7b0706490f1fb9c9b7c9e62ae62d57bd84a1e469460d8ac1"
}
]
,
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x100",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00"
}
此配置加载后在->/params/config.go中配置到初始化配置中
其中config指定了相关协议机制的升级区块所在的高度,签名算法是homestead ->eip155 -> eip158,所以从homesteadBlock之前区块都通过homestead相关算法机制来验证,homesteadBlock 到 eip155Block之间的用eip155算法来验证,依次类推(此段为摘抄,实际没懂,只看到注释中有关TheDao的配置)
alloc:不懂
committee:委员会地址和公钥---意思是选择一个自己支持的委员
coinbase:自己的收益地址
difficulty:挖矿难度
extraData:额外数据
gasLimit:以太坊要求的设置交易支出
nonce:交易只能处理一次的计数器
mixhash:与nonce组合判断计算是否足够
parentHash:父区块哈希值
timestamp:时间戳
这些配置都是以太坊需要的配置项。
项目加载配置流程(根据初链架构图推导得出)
1.配置底层服务
2.配置levelDB数据库
3.配置区块链及共识算法
4.提供api接口接收智能合约部署
$ getrue
--nodiscover //---Disables the peer discovery mechanism:关闭发现节点的机能
--singlenode //---sing node model:单节点运行模式
--bft //---无此参数
--mine //---Enable mining:激活挖矿
--etherbase 0x8a45d70f096d3581866ed27a5017a4eeec0db2a1 //---Public address for block mining rewards:收益所在的公共地址(创世块的地址)---如果不是创世块就把自己的地址写进去?
--bftkeyhex "c1581e25937d9ab91421a3e1a2667c85b0397c75a195e643109938e987acecfc" //---committee generate privatekey as hex (for testing):委员会生成的私有钥匙
--bftip "192.168.68.43"//---committee node ip:委员会节点ip
--bftport 10080 //---committee node port:委员会节点端口
下面根据源码调用顺序做一个梳理
init->geture->makeFullNode->makeConfigNode
| | |->RegisterEthService->stack.Register
| | | |->etrue.New->NewSnailBlockChain->loadLastState(慢链)
| | | |->NewPbftAgent->InitNodeInfo(初始化委员会节点)
| | | |->miner.New(挖水果)
| | | |->NewBlockChain->loadLastState(快链)
| | | |->core.NewTxPool->pool.loop(扫描交易)
| | | |->pool.journal.rotate(重新生成交易流)
| | |->SetNodeConfig(设置节点)->SetP2PConfig(设置p2p网络)
| | | | |->setNodeKey
| | | | |->setNAT(设置NAT穿透)
| | | | |->setListenAddress(监听地址)
| | | | |->setBootstrapNodes(启动网络节点)
| | | |->setIPC(IPC协议支持)
| | | |->setHTTP(http协议支持)
| | | |->setWS(webservice程序支持)
| | | |->setNodeUserIdent(用户节点标记)
| | |->SetTruechainConfig->setEtherbase(设置初链地址)
| | | |->setGPO(设置gpo)
| | | |->setTxPool(交易树)
| | | |->setEthash(区块验证)
| | | |->setBftCommitteeKey(委员会钥匙)
| | |->SetShhConfig
| |->startNode->utils.StartNode
| |->accounts.WalletEvent
| |->utils.MiningEnabledFlag.Name---读取命令行参数mine
| |->truechain.TxPool().SetGasPrice---从配置文件中读取
| |->truechain.StartMining->s.miner.Start(开始挖矿)
| |->StartMinine->Etherbase->common.Address---读取地址后生成收益节点的配置
| |->stack.Start->getCommittee(获取委员会节点)
| |->startRPC->startIPC->StartIPCEndpoint->ServeListener(启动p2p节点并提供rpc服务)
|->importCommand->MakeChain->MakeChainDatabase->OpenDatabase->NewLDBDatabase(LevelDB在此初始化)
// startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner.
func startNode(ctx *cli.Context, stack *node.Node)
func getrue(ctx *cli.Context) error {
node := makeFullNode(ctx)
startNode(ctx, node)
node.Wait()
return nil
}
func init() {
// Initialize the CLI app and start Getrue
app.Action = getrue
...
}
main中执行的只是读取参数,重点在init(golang 的init方法会在包加载的时候提前运行)方法中进行getrue命令的执行,其中我们看到了对全节点的初始化配置,开启节点运行的配置等内容
func makeFullNode(ctx *cli.Context) *node.Node {
stack, cfg := makeConfigNode(ctx)
utils.RegisterEthService(stack, &cfg.Etrue)
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
}
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
shhEnabled := enableWhisper(ctx)
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
if shhEnabled || shhAutoEnabled {
if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) {
cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name))
}
if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) {
cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name)
}
utils.RegisterShhService(stack, &cfg.Shh)
}
// Add the Truechain Stats daemon if requested.
if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
}
return stack
}
启动全节点配置注册系统参数(RegisterDashboardService)等工作
// SetNodeConfig applies node-related command line flags to the config.
func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
SetP2PConfig(ctx, &cfg.P2P)
setIPC(ctx, cfg)
setHTTP(ctx, cfg)
setWS(ctx, cfg)
setNodeUserIdent(ctx, cfg)
...
}
func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
setNodeKey(ctx, cfg)
setNAT(ctx, cfg)
setListenAddress(ctx, cfg)
setBootstrapNodes(ctx, cfg)
...
2---log.Info("Maximum peer count", "ETRUE", ethPeers, "LES", lightPeers, "total", cfg.MaxPeers)
...
if ctx.GlobalBool(DeveloperFlag.Name) {
// --dev mode can't use p2p networking.
cfg.MaxPeers = 0
cfg.ListenAddr = ":0"
cfg.NoDiscovery = true
cfg.DiscoveryV5 = false
}
}
在代码中为SetP2PConfig()方法,而此方法是SetNodeConfig所调加载各种协议:IPC,HTTP,WS,也就是p2p网络的底层设置。
func SetTruechainConfig(ctx *cli.Context, stack *node.Node, cfg *etrue.Config){
...
setEtherbase(ctx, ks, cfg)
setGPO(ctx, &cfg.GPO)
setTxPool(ctx, &cfg.TxPool)
setEthash(ctx, cfg)
...
ctx.GlobalIsSet(...)
...
setBftCommitteeKey(ctx, cfg)
}
在这里我们看到对配置文件genesis.json内参数的读取并设置,
func (n *Node) Start() error {
n.lock.Lock()
defer n.lock.Unlock()
...
n.serverConfig = n.config.P2P
n.serverConfig.PrivateKey = n.config.NodeKey()
n.serverConfig.Name = n.config.NodeName()
n.serverConfig.Logger = n.log
...
for _, constructor := range n.serviceFuncs {
//把前面的p2p网络配置进来
}
if err := n.startRPC(services); err != nil {//开启了rpc调用服务
for _, service := range services {
service.Stop()
}
running.Stop()
return err
}
}
以上代码内部对节点工作进行布置,启动rpc对外服务,配置p2p网络
func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) {
logger := log.New("database", file)
db, err := leveldb.OpenFile(file, &opt.Options{
OpenFilesCacheCapacity: handles,
BlockCacheCapacity: cache / 2 * opt.MiB,
WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally
Filter: filter.NewBloomFilter(10),
})
}
此方法在调用树中可看到是在makechain中加载的,其中就是把数据库文件数据加载出来使用。
func New(ctx *node.ServiceContext, config *Config) (*Truechain, error) {
chainDb, err := CreateDB(ctx, config, "chaindata")
chainConfig, genesisHash, genesisErr := fastchain.SetupGenesisBlock(chainDb, config.FastGenesis)
snailConfig, snailHash, snailErr := chain.SetupGenesisBlock(chainDb, config.SnailGenesis)
etrue.snailblockchain, err = chain.NewSnailBlockChain(chainDb, snailCacheConfig, etrue.chainConfig, etrue.engine, vmConfig)
etrue.txPool = core.NewTxPool(config.TxPool, etrue.chainConfig, etrue.blockchain)
etrue.snailPool = core.NewSnailPool(etrue.chainConfig, etrue.blockchain, etrue.snailblockchain, etrue.engine)
etrue.election = NewElction(etrue.blockchain, etrue.snailblockchain, etrue.config)
etrue.snailblockchain.Validator().SetElection(etrue.election, etrue.blockchain)
ethash.SetElection(etrue.election)
ethash.SetSnailChainReader(etrue.snailblockchain)
etrue.election.SetEngine(etrue.engine)
coinbase, _ := etrue.Etherbase()
etrue.agent = NewPbftAgent(etrue, etrue.chainConfig, etrue.engine, etrue.election, coinbase)
etrue.miner = miner.New(etrue, etrue.chainConfig, etrue.EventMux(), etrue.engine, etrue.election, etrue.Config().MineFruit, etrue.Config().NodeType)
etrue.miner.SetExtra(makeExtraData(config.ExtraData))
}
启动一个生成一个Truechain实体,代码注释为( New creates a new Truechain object (including the initialisation of the common Truechain object)
其中我们看到前面加载的内容以及选举和快链慢链的内容加载配置引擎、加载拜占庭委员会等工作,最后开始miner.New开始挖矿,到这里,我们可以把truechain的架构图拿出来对照一番了,
上图我们看到底层是P2P网络,LevelDB数据库,密码学算法,分片优化,其中P2P网络和levelDB是很明显在上述代码中的,初始化部分暂时分析到这里,还有很多知识点没有讲到,而且每个部分都只是先点到,后续每个模块要进行拆分分析以求达到更深的理解。
在阅读源码的时候确实是有点摸头不着脑的,区块链的知识我也是刚刚接触,其中大部分的实现和调用如果不参照log来的化根本无法下手进行分析,阅读完比特币白皮书,以太坊白皮书和初链白皮书以及黄皮书之后比较清晰的感觉得到初链底层架构的巧妙设计能够让两种不同的共识机制实现取长补短。而作为个人开发者来说要想入手开发掌握肯定就要把里面的源码吃透,否则就是一些概念的倒腾对我这种希望以实战为主的开发者来说真是有点“看得着吃不着”的感觉,而经过这次分析呢总算是理解了底层的一些架构,希望接下来的学习能够让我更加理解区块链开发。
另:初链大力支持社区力量加入,我也可以邀请大家加入,希望你在看到这个文章后加入初链社区,加群后可以把我当作推荐人,就当作你看着篇文章所得的利益共享吧^_^这也是区块链希望实现的自己拥有自己的付出换回成果的理念啊!推荐人:我是鱼饵。谢谢大家阅读。