Buff

越努力越幸运


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

GO-HPB源码解读--挖矿流程(四)

发表于 2019-07-21 | 分类于 HPB源码解读

接着上一章,我们看到了这章来看了go self.mine(work, self.quitCurrentOp)这部分代码,现在来看下具体挖矿算法的流程。
mine方法中调用engine.GenBlockWithSig方法,返回的result结果传递给returnCh通道,而returnCh通道的获取工作是在worker中进行处理的。上一节我们理了一下producer绑定CpuAgent的流程,在worker.New的时候,通过go worker.handlerSelfMinedBlock()这行代码启了一个协程去获取通道里的挖矿结果。

1
2
3
4
5
6
7
8
9
10
11
func (self *CpuAgent) mine(work *Work, stop <-chan struct{}) {
if result, err := self.engine.GenBlockWithSig(self.chain, work.Block, stop); result != nil {
log.Info("Successfully sealed new block", "number -> ", result.Number(), "hash -> ", result.Hash(),"difficulty -> ",result.Difficulty())
self.returnCh <- &Result{work, result}
} else {
if err != nil {
log.Warn("Block sealing failed", "err", err)
}
self.returnCh <- nil
}
}

生成区块的过程:

  1. 数据合法性校验
  2. 获取签名者signer和签名方法signFn,这两个数据是在挖矿流程(二)中的StartMining方法中设置的,代码是promeengine.Authorize(eb, wallet.SignHash),即signer是当前节点的挖矿地址coinbase,signFn是对应钱包的签名方法,即keystoreWallet类的签名方法
  3. voting.GetHpbNodeSnap是获取高性能节点列表,其中有重新投票节点的过程,投票这个机制单独解读,这里就先不说了。
  4. 设置本地节点类型,是高性能节点还是候选节点
  5. 如果自己的当前节点不是包含在高性能节点中,则返回错误consensus.ErrUnauthorized
  6. 接着要判断一下自己当前节点是否轮到进行签名(挖矿),如果没轮到,需要通过高性能节点列表中自己的位置和正在挖矿的位置两个关键参数计算出自己需要等待的一个时间段,
  7. 时间到了的话就进行签名,签名方法为signFn,这个方法直接使用coinbase对应的私钥进行ECDSA签名
  8. 签名完了之后,把签名结果放置到header.Extra里,并返回最终block
阅读全文 »

GO-HPB源码解读--挖矿流程(三)

发表于 2019-07-21 | 分类于 HPB源码解读

在挖矿流程(三)的最后,看到在go self.mine(work, self.quitCurrentOp)中要传递一个work对象进去。因为挖矿流程需要使用到大量的work数据,所以单独分析一下work的数据结构和初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Work is the workers current environment and holds
// all of the current state information
type Work struct {
config *config.ChainConfig
signer types.Signer

state *state.StateDB // apply state changes here
ancestors *set.Set // ancestor set (used for checking uncle parent validity)
family *set.Set // family set (used for checking uncle invalidity)
uncles *set.Set // uncle set
tcount int // tx count in cycle

Block *types.Block // the new block

header *types.Header
txs []*types.Transaction
receipts []*types.Receipt

createdAt time.Time
}
阅读全文 »

GO-HPB源码解读--挖矿流程(二)

发表于 2019-07-21 | 分类于 HPB源码解读

这章来看下HPB的挖矿流程。在节点启动的时候通过flag参数mine指定是否进行挖矿,当然也可以在节点启动后通过API来调用,启动流程代码调用顺序是go-hpb/cmd/ghpb/main.go-main->ghpb->startNode`。
在startNode方法最后部署代码可以看到,首先判断MiningEnabledFlag和RoleType两个参数后,设置交易的GasPrice后(系统默认为defaultGasPrice = 50 * config.Shannon),开启挖矿。

阅读全文 »

GO-HPB源码解读--挖矿流程(一)

发表于 2019-07-21 | 分类于 HPB源码解读

在开析流程前先看下主要的数据结构,挖矿工作主要由三个对象来完成的,miner、worker、cpuAgent,其中cpuAgent实现Producer接口类

  • miner是入口,负责与外部交互,控制挖矿流程的启停操作
  • worker管理Work任务,他代理着Producer接口类,也就是代理着具体挖矿执行着
  • cpuAgent负责具体挖矿计算工作
阅读全文 »

GO-HPB源码解读--LevelDB操作

发表于 2019-07-21 | 分类于 HPB源码解读

HPB使用LevelDB存储数据。go-hpb/blockchain/database_util.go文件封装了LevelDB的操作。因为LevelDB是<k, v>数据库,所以所有数据的存储都需要指定k。以下代码列出了所有需要保存的数据的k或者k的前缀。

阅读全文 »

GO-HPB源码解读--多帐号解锁

发表于 2019-07-21 | 分类于 HPB源码解读

在启动节点时,解锁参数可以同时指定多个帐号,格式为
--unlock "addr1,addr2,addr3..." --password "pwd",其中unlock参数为多个帐号的地址,用逗号分隔;password参数为口令文件,文件中的口令每行一个口令。
在创建帐号时,命令./ghpb --datadir node/data account init可以多次执行,每次执行后,会在路径node/data/keystore下生成多个帐号文件,比如UTC–2018-11-29T08-21-25.565157387Z–3e8aadb68222c70b309e87cd5c27e97950879076。当节点启动指定多个帐号时,会加载对应的帐号文件,并解锁。下边看一下解锁的代码。

启动节点入口main.go的init方法,调用过程为init()–>ghpb–>startNode()

1
2
3
4
5
6
7
func init() {
// Initialize the CLI app and start Geth
app.Action = ghpb
app.HideVersion = true // we have a command to print the version
app.Copyright = "Copyright 2013-2018 The go-hpb Authors "
app.Commands = []cli.Command{
...省略...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ghpb is the main entry point into the system if no special subcommand is ran.
// It creates a default node based on the command line arguments and runs it in
// blocking mode, waiting for it to be shut down.
func ghpb(ctx *cli.Context) error {
cfg := MakeConfigNode(ctx)
hpbnode, err := createNode(cfg)
if err != nil {
utils.Fatalf("Failed to create node")
return err
}
startNode(ctx, hpbnode, cfg)
hpbnode.Wait()
return nil
}

在startNode方法中

  1. 首先获取keyStore的指针
  2. MakePasswordList方法解析–password参数指定的口令文件,文件中的内容每一行对应一个口令
  3. 获取帐号列表,进行遍历解锁
  4. 后边是启动节点,并启动一个协程,开启钱包事件监听。这部分内容后续章节中再进行解读。下边看下遍历时,每个帐号是怎么使用口令解锁的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    // 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, conf *config.HpbConfig) {

    // Unlock any account specifically requested
    ks := stack.AccountManager().KeyStore().(*keystore.KeyStore)

    passwords := utils.MakePasswordList(ctx)
    unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
    for i, account := range unlocks {
    if trimmed := strings.TrimSpace(account); trimmed != "" {
    unlockAccount(ctx, ks, trimmed, i, passwords)
    }
    }

    if unlocks[0] != "" {
    account, err := utils.MakeAddress(ks, strings.TrimSpace(unlocks[0]))
    if err != nil {
    utils.Fatalf("Could not list accounts: %v", err)
    }
    conf.Node.DefaultAddress = account.Address
    }

    //set rpc aii
    //utils.SetNodeAPI(&conf.Node, stack)
    // Start up the node itself
    utils.StartNode(stack)
    // Register wallet event handlers to open and auto-derive wallets
    events := make(chan accounts.WalletEvent, 16)
    stack.AccountManager().Subscribe(events)

    go func() {

    // Open any wallets already attached
    for _, wallet := range stack.AccountManager().Wallets() {
    if err := wallet.Open(""); err != nil {
    log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
    }
    }
    // Listen for wallet event till termination
    for event := range events {
    switch event.Kind {
    case accounts.WalletArrived:
    if err := event.Wallet.Open(""); err != nil {
    log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
    }
    case accounts.WalletOpened:
    status, _ := event.Wallet.Status()
    log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)

    case accounts.WalletDropped:
    log.Info("Old wallet dropped", "url", event.Wallet.URL())
    event.Wallet.Close()
    }
    }
    }()

    // Start auxiliary services if enabled
    if ctx.GlobalBool(utils.MiningEnabledFlag.Name) && (conf.Network.RoleType == "") {
    // Set the gas price to the limits from the CLI and start mining
    stack.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name))
    if err := stack.StartMining(true); err != nil {
    utils.Fatalf("Failed to start mining: %v", err)
    }
    }
    }

在unlockAccount方法中,每个帐号会进行尝试3次解锁。每次会从口令列表中取出一个口令进行解锁帐号,如果解锁成功则返回,失败则继续。方法getPassPhrase每次从众多口令中取一个口令的方式是以下部分代码实现的 ,如果口令个数大于3个的话,则每次都会取最后一个。不是很理解为何要这样做,按理说应该每个帐号对应一个口令的。😂😂😂

1
2
3
4
5
6
if len(passwords) > 0 {
if i < len(passwords) {
return passwords[i]
}
return passwords[len(passwords)-1]
}

GO-HPB源码解读--数据结构

发表于 2019-07-21 | 分类于 HPB源码解读

区块链本身就是通过特定的机制来生成和维护数据,使数据具有极高的稳定性和可靠性。所以了解区块链,首先要必须熟悉其数据结构,然后才能更好的理解数据运算机制也就是区块链的运行原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// BlockChain represents the canonical chain given a database with a genesis
// block. The Blockchain manages chain imports, reverts, chain reorganisations.
//
// Importing blocks in to the block chain happens according to the set of rules
// defined by the two stage Validator. Processing of blocks is done using the
// Processor which processes the included transaction. The validation of the state
// is done in the second part of the Validator. Failing results in aborting of
// the import.
//
// The BlockChain also helps in returning blocks from **any** chain included
// in the database as well as blocks that represents the canonical chain. It's
// important to note that GetBlock can return any block and does not need to be
// included in the canonical one where as GetBlockByNumber always represents the
// canonical chain.

<!-- more -->

type BlockChain struct {
config *config.ChainConfig // chain & network configuration
hc *HeaderChain
chainDb hpbdb.Database
rmLogsFeed sub.Feed
chainFeed sub.Feed
chainSideFeed sub.Feed
chainHeadFeed sub.Feed
logsFeed sub.Feed
scope sub.SubscriptionScope
genesisBlock *types.Block
mu sync.RWMutex // global mutex for locking chain operations
chainmu sync.RWMutex // blockchain insertion lock
procmu sync.RWMutex // block processor lock
checkpoint int // checkpoint counts towards the new checkpoint
currentBlock *types.Block // Current head of the block chain
currentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!)
stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
blockCache *lru.Cache // Cache for the most recent entire blocks
futureBlocks *lru.Cache // future blocks are blocks added for later processing
quit chan struct{} // blockchain quit channel
running int32 // running must be called atomically
// procInterrupt must be atomically called
procInterrupt int32 // interrupt signaler for block processing
wg sync.WaitGroup // chain processing wait group for shutting down
engine consensus.Engine
processor Processor // block processor interface
validator Validator // block and state validator interface
badBlocks *lru.Cache // Bad block cache
}
字段 类型 含义 备注
config *config.ChainConfig 定义了chainID和监控参数
hc *HeaderChain 区块头组成的链
chainDb hpbdb.Database 数据库
rmLogsFeed sub.Feed
chainFeed sub.Feed
chainSideFeed sub.Feed
chainHeadFeed sub.Feed
logsFeed sub.Feed
scope sub.SubscriptionScope
genesisBlock *types.Block
mu sync.RWMutex 区块链的全局锁
chainmu sync.RWMutex 区块链插入锁
procmu sync.RWMutex 区块处理的锁
checkpoint int
currentBlock *types.Block 主链的头区块
currentFastBlock *types.Block 快速同步模式下链的头区块,这种情况下可能比主链长
stateCache state.Database
bodyCache *lru.Cache 最近区块体的缓存信息
bodyRLPCache *lru.Cache 最近区块体的RLP格式的缓存信息
blockCache *lru.Cache 最近区块的缓存
futureBlocks *lru.Cache 将来等待上链的区块
quit chan struct{}
running int32
procInterrupt int32
wg sync.WaitGroup
engine consensus.Engine 验证区块的引擎
processor Processor 执行区块交易的接口
validator Validator 验证区块和state有效性的接口
badBlocks *lru.Cache 验证失败的区块缓存,来自DAO事件

关键的元素:

  1. db:连接到底层数据储存,即leveldb;
  2. hc:headerchain区块头链,由blockchain额外维护的另一条链,由于Header和Block的储存空间是有很大差别的,但同时Block的Hash值就是Header(RLP)的Hash值,所以维护一个headerchain可以用于快速延长链,验证通过后再下载blockchain,或者可以与blockchain进行相互验证;
  3. genesisBlock:创始区块;
  4. currentBlock:当前区块,blockchain中并不是储存链所有的block,而是通过currentBlock向前回溯直到genesisBlock,这样就构成了区块链。
  5. bodyCache、bodyRLPCache、blockCache、futureBlocks:区块链中的缓存结构,用于加快区块链的读取和构建;
  6. engine:是consensus模块中的接口,用来验证block的接口;
  7. processor:执行区块链交易的接口,收到一个新的区块时,要对区块中的所有交易执行一遍,一方面是验证,一方面是更新worldState;
  8. validator:验证数据有效性的接口
  9. futureBlocks:收到的区块时间大于当前头区块时间15s而小于30s的区块,可作为当前节点待处理的区块。

下边是区块的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Block represents an entire block in the Hpb blockchain.
type Block struct {
header *Header
uncles []*Header
transactions Transactions

// caches
hash atomic.Value
size atomic.Value

// Td is used by package core to store the total difficulty
// of the chain up to and including the block.
td *big.Int

// These fields are used by package eth to track
// inter-peer block relay.
ReceivedAt time.Time
ReceivedFrom interface{}
}
字段 类型 含义 备注
header *Header 当前区块头
uncles []*Header 叔区块,因出块速度快,导致分叉概率高,为了增加矿工挖矿积极性,对于分叉后的无用区块也进行打包,奖励矿工,那么这些无用区块就是叔区块。同时为了为了抵消整个Ethereum网络中那些计算能力特别强大的节点会对区块的产生有过大的影响力,防止这些节点破坏“去中心化”这个根本宗旨
transactions Transactions 交易集合
hash atomic.Value 区块的哈希,是从Header中缓存下来的,等于Header中的哈希值,是header中除nonce和mixDigest数据外的rlp的hash,这样相同的交易内容的相同该区块hash是一样,哪怕是由不同的节点创建出来的。区块的header和body是分开存储的,key都是前缀加上当前hash
size atomic.Value
td *big.Int td是totalDifficulty的缩写,表示从创世区块到当前区块的所有Difficulty之和,而每个区块的Difficulty是保存在header中的
ReceivedAt time.Time 区块生成时间
ReceivedFrom interface{} 区块生成来源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Header represents a block header in the Hpb blockchain.
type Header struct {
ParentHash common.Hash `json:"parentHash" `
UncleHash common.Hash `json:"sha3Uncles" `
Coinbase common.Address `json:"miner" `
CandAddress common.Address `json:"candAddress" `
ComdAddress common.Address `json:"comdAddress" `
VoteIndex *big.Int `json:"voteIndex" `
Root common.Hash `json:"stateRoot" `
TxHash common.Hash `json:"transactionsRoot" `
ReceiptHash common.Hash `json:"receiptsRoot" `
Bloom Bloom `json:"logsBloom" `
Difficulty *big.Int `json:"difficulty" `
Number *big.Int `json:"number" `
GasLimit *big.Int `json:"gasLimit" `
GasUsed *big.Int `json:"gasUsed" `
Time *big.Int `json:"timestamp" `
Extra []byte `json:"extraData" `
MixDigest common.Hash `json:"mixHash" `
Nonce BlockNonce `json:"nonce" `
HardwareRandom []byte `json:"hardwareRandom" `
}
字段 类型 含义 备注
ParentHash common.Hash 父区块的hash
UncleHash common.Hash 叔区块的hash
Coinbase common.Address 挖矿生成该区块的地址,挖矿是由节点来完成的,每个full节点都个默认的Coinbase地址,用来接收挖矿(矿工费和打包区块的奖金)奖励
CandAddress common.Address ?
ComdAddress common.Address ?
VoteIndex *big.Int 投票索引
Root common.Hash 存储账户(合约帐户和用户帐户)状态的Merkle树的根节点的哈希,StateDB中的“state Trie”的根节点的RLP哈希值。Block中,每个账户以stateObject对象表示,账户以Address为唯一标示,其信息在相关交易(Transaction)的执行中被修改。所有账户对象可以逐个插入一个Merkle-PatricaTrie(MPT)结构里,形成“state Trie”。
TxHash common.Hash Block中交易树 “tx Trie”的根节点的RLP哈希值。Block的成员变量transactions中所有的tx对象,被逐个插入一个MPT结构,形成“tx Trie”。
ReceiptHash common.Hash “Receipt Trie”的根节点的RLP哈希值。Block的所有Transaction执行完后会生成一个Receipt数组,这个数组中的所有Receipt被逐个插入一个MPT结构中,最后形成”Receipt Trie”
Bloom Bloom Bloom过滤器(Filter),用来快速判断一个参数Log对象是否存在于一组已知的Log集合中。
Difficulty *big.Int 区块的难度。Block的Difficulty由共识算法基于parentBlock的Time和Difficulty计算得出,它会应用在区块的‘挖掘’阶段。
Number *big.Int 区块的序号。Block的Number等于其父区块Number +1。
GasLimit *big.Int 区块内所有Gas消耗的上限。该数值在区块创建时赋值,与父区块的数据相关。具体来说,根据父区块的GasUsed同创世块的GasLimit * 2/3的大小关系来计算得出。
GasUsed *big.Int 区块内所有Transaction执行时所实际消耗的Gas总和。
Time *big.Int 区块“应该”被创建的时间。由共识算法确定,一般来说,要么等于parentBlock.Time + 10s,要么等于当前系统时间。区块开始打包时间戳(调用Engine.Prepare函数的时候设置)
Extra []byte 扩展信息
MixDigest common.Hash 区块头除去Nonce, mixDigest数据的hash+nonce的RLP的hash值
Nonce BlockNonce 一个64bit的哈希数,它被用于POW等挖块算法,暴力碰撞得出,该哈希值与MixDigest哈希值一起证明该区块上已经进行了足够的计算,用于证明挖矿成功
HardwareRandom []byte BOE硬件随机数
1
2
3
4
type Body struct {
Transactions []*Transaction
Uncles []*Header
}
字段 类型 含义 备注
Transactions []*Transaction 交易集合
Uncles []*Header 叔区块头集合
1
2
3
4
5
6
7
8
type Transaction struct {
data txdata
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
fromP2P bool
}
字段 类型 含义 备注
data txdata 交易数据
hash atomic.Value
size atomic.Value
from atomic.Value tx的转帐转出方地址,就是对该tx对象作ECDSA签名计算时所用的公钥publicKey
fromP2P bool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Price *big.Int `json:"gasPrice" gencodec:"required"`
GasLimit *big.Int `json:"gas" gencodec:"required"`
Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value" gencodec:"required"`
Payload []byte `json:"input" gencodec:"required"`
// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"`
}
字段 类型 含义 备注
AccountNonce uint64 发送者发起的交易总数
Price *big.Int 交易的Gas价格
GasLimit *big.Int 交易允许消耗的最大Gas
Recipient *common.Address 交易接收者地址,
Amount *big.Int 交易额
Payload []byte 它既可以作为所创建合约的指令数组,其中每一个byte作为一个单独的虚拟机指令;也可以作为数据数组,由合约指令进行操作。合约由以太坊虚拟机(Ethereum Virtual Machine, EVM)创建并执行。
V *big.Int tx的数字签名(ECDSA),是一个长度为65bytes的字节数组,它被截成三段放进tx中,前32bytes赋值给成员变量R, 再32bytes赋值给S,末1byte赋给V
R *big.Int 同上
S *big.Int 同上
Hash *common.Hash 交易HAsh,只在JSON转换时使用
以上便是区块链中最主要的“区块链”数据结构,有些字段描述不是很清楚,后续再逐步完善。

GO-HPB源码解读--节点初始化

发表于 2019-07-21 | 分类于 HPB源码解读

HPB在创建帐号时使用的命令是 ./ghpb --datadir node/data init gensis.json
通过代码看下节点初始化做了什么事情

init命令定义在chaincmd.go文件中,关于urfave和utils.MigrateFlags在创建帐号中做过说明,这里直接看下initGenesis做了什么事情。

initGenesis方法流程:

  1. MakeConfigNode方法配置节点的默认参数信息,这个时候如果很多参数没有通过命令很输入进来,程序会把所有的参数信息设置为默认值
  2. 加载文件gensis.json文件,文件名通过init的flag参数输入进行。后边我们对gensis.json文件解读一下。
  3. 创建bc.Genesis对象,然后通过gensis.json文件的信息进行赋值
  4. db.OpenDatabase创建数据库
  5. bc.SetupGenesisBlockf进行初始块的生成和写入文件
阅读全文 »

GO-HPB源码解读--创建帐号

发表于 2019-07-21 | 分类于 HPB源码解读

HPB在创建帐号时使用的命令是 ./ghpb --datadir node/data account init
通过代码看下创建流程

go-hpb源码命令架构使用的是urfave/cli,不知道是不是这样读这个名称:you are 废物,这里有个不错的帖子介绍,可以学习下。主要对命令进行了封装,我们主要关心命令所对应的业务实现逻辑即可,使用起来非常方便。阅读也从这个地方开始。

account命令定义在accountcmd.go文件中,通过源码可以看到account命令有list、new、update、import四个子命令。每一个命令都有flags参数。

阅读全文 »

Hello World

发表于 2019-07-20

摘要显示

阅读全文 »
1…34

40 日志
7 分类
13 标签
© 2019
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4