前言
這段時間一直在做區塊鏈公鏈項目開發,主要是基於bitcoin-core源碼進行開發,理解區塊鏈原理及基礎概念;個人同時對於以太坊也感興趣,所以準備拿go-ethereum學習一番,過程會持續幾個月,這裡把學習筆記記錄下來;本人現在對ethereum也是菜鳥小白,這篇文章主要是針對go-ethereum小白,大牛就請繞過吧。
現在開始吧
區塊鏈基本概念:交易、區塊、區塊鏈,是區塊鏈中的核心基礎,今天就從這幾個概念入手分析吧。(這裡忍不住多說幾句,任何科學領域基礎概念真的很重要,工作中遇到的很多問題都是因為基本概念理解不到位,解決問題時需要把基本概念重新理解一遍;依然記得若干年之前南京大學徐家福教授的演講,一位同學問怎麼才能學好電腦,徐教授什麼話也沒說,拿起粉筆在黑板上顫抖著手寫到:“基礎概念,基礎概念,基礎概念”,大家知道基礎知識的重要性了吧。如果大家不知道徐教授就去百度吧,新中國電腦領域的創始人之一,學術界流行一句話“北有楊芙清(清華的),南有徐家福(南大的)”)。
交易
core/types/transaction.go
type Transaction struct {
data txdata //交易的內容,在txdata類型中儲存
// caches 以下三個欄位是否只在memory裡儲存?
hash atomic.Value // 交易的hash
size atomic.Value // 交易的大小
from atomic.Value // 交易的發起方
}
type txdata struct {
AccountNonce uint64 // account nonce?幹什麼的,不知道
Price *big.Int // gasprice
GasLimit uint64 // gaslimit,我們知道在寫智能合約時設定gas limit,能夠防止程式異常消耗太多的gas
Recipient *common.Address // 交易的receiver, nil means contract creation
Amount *big.Int //交易以太的數量??有待確認
Payload []byte //交易可以攜帶payload,智能合約的位元組碼存放在此??有待確認
// Signature values,和簽名相關,暫不深究
V *big.Int
R *big.Int
S *big.Int
// This is only used when marshaling to JSON.
Hash *common.Hash
}
以上是交易的結構體定義,下面看一下和交易相關的函數和方法(golang 既有函數的概念,又有方法的概念)。
func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte)
*Transaction {
return newTransaction(nonce, &to, amount, gasLimit, gasPrice, data)
}
func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction {
return newTransaction(nonce, nil, amount, gasLimit, gasPrice, data)
}
上面的函數,NewTransaction是建立普通的交易,NewContractCreation是用來建立智能合約,兩者都調用了內建函式(非匯出)newTransaction,區別很明顯就是NewContractCreation把交易接收地址設定為nil。
func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte)
*Transaction {
if len(data) > 0 {
data = common.CopyBytes(data)
}
d := txdata{
AccountNonce: nonce,
Recipient: to,
Payload: data,
Amount: new(big.Int),
GasLimit: gasLimit,
Price: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
}
if amount != nil {
d.Amount.Set(amount)
}
if gasPrice != nil {
d.Price.Set(gasPrice)
}
return &Transaction{data: d}
}
以上函數簡單,先建立txdata對象,然後初始化資料成員。
下面的EncodeRLP與DecodeRLP主要負責交易的 RLP編解碼。
// EncodeRLP implements rlp.Encoder
func (tx *Transaction) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &tx.data)
}
// DecodeRLP implements rlp.Decoder
func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
_, size, _ := s.Kind()
err := s.Decode(&tx.data)
if err == nil {
tx.size.Store(common.StorageSize(rlp.ListSize(size)))
}
return err
}
這裡先簡單介紹幾個與交易相關的函數和方法,更多的函數和方法大家可以查閱原始碼。不展開的原因我是想先追求整體理解,然後再是具體實現。
區塊
core/types/block.go
先看一下區塊頭的資料結構:
// Header represents a block header in the Ethereum blockchain.
type Header struct {
ParentHash common.Hash //上一個區塊的hash,用來把區塊組織成鏈
UncleHash common.Hash //unclehash,理解不是太深入,後面再說
Coinbase common.Address //POW共識演算法coinbase交易對應的地址
Root common.Hash // state trie tree Root
TxHash common.Hash // transactions Root
ReceiptHash common.Hash // receipts Root
Bloom Bloom //應該是bloom filter,暫不深入
Difficulty *big.Int //POW共識演算法的難度值,會隨著區塊鏈高度進行調整
Number *big.Int // 區塊高度
GasLimit uint64 // 區塊頭的gaslimit??還不太清楚是什麼作用
GasUsed uint64 //整個區塊中交易的消耗的gas?有待確認
Time *big.Int //出塊時間
Extra []byte
MixDigest common.Hash //暫時不知道用來幹什麼
Nonce BlockNonce //POW共識演算法中的nonce
}
下面的方法擷取block header 的hash值:
// Hash returns the block hash of the header, which is simply the keccak256 hash of its
// RLP encoding.
func (h *Header) Hash() common.Hash {
return rlpHash(h)
}
// HashNoNonce returns the hash which is used as input for the proof-of-work search.
func (h *Header) HashNoNonce() common.Hash {
return rlpHash([]interface{}{
h.ParentHash,
h.UncleHash,
h.Coinbase,
h.Root,
h.TxHash,
h.ReceiptHash,
h.Bloom,
h.Difficulty,
h.Number,
h.GasLimit,
h.GasUsed,
h.Time,
h.Extra,
})
}
func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewKeccak256()
rlp.Encode(hw, x)
hw.Sum(h[:0])
return h
}
以下是區塊體的資料結構:
// Body is a simple (mutable, non-safe) data container for storing and moving
// a block's data contents (transactions and uncles) together.
type Body struct {
Transactions []*Transaction //區塊體中的所有交易
Uncles []*Header//還不太明白這個欄位作用,難道和分叉有關?
}
區塊的資料結構如下:
// Block represents an entire block in the Ethereum 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{} //標記本區塊是從哪個對端節點收到的
}
// StorageBlock defines the RLP encoding of a Block stored in the
// state database. The StorageBlock encoding contains fields that
// would otherwise need to be recomputed.
type StorageBlock Block
// "external" block encoding. used for eth protocol, etc.
type extblock struct {
Header *Header
Txs []*Transaction
Uncles []*Header
}
//以上的Block資料結構是在記憶體中儲存的,在資料庫中實際儲存的結構體是 storageblock,定義如下:
// "storage" block encoding. used for database.
type storageblock struct {
Header *Header
Txs []*Transaction
Uncles []*Header
TD *big.Int
}
下面簡單分析下建立block的函數NewBlock:
// NewBlock creates a new block. The input data is copied,
// changes to header and to the field values will not affect the block.
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
// are ignored and set to values derived from the given txs, uncles and receipts.
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt) *Block {
b := &Block{header: CopyHeader(header), td: new(big.Int)} //建立block 對象
// TODO: panic if len(txs) != len(receipts)
if len(txs) == 0 {
b.header.TxHash = EmptyRootHash //如果區塊中沒有交易,TXHash賦值為 EmptyRootHash
} else {
b.header.TxHash = DeriveSha(Transactions(txs))
b.transactions = make(Transactions, len(txs))
copy(b.transactions, txs) //建立並拷貝tx的副本
}
if len(receipts) == 0 {
b.header.ReceiptHash = EmptyRootHash
} else {
b.header.ReceiptHash = DeriveSha(Receipts(receipts))
b.header.Bloom = CreateBloom(receipts)
}
if len(uncles) == 0 {
b.header.UncleHash = EmptyUncleHash
} else {
b.header.UncleHash = CalcUncleHash(uncles)
b.uncles = make([]*Header, len(uncles))
for i := range uncles {
b.uncles[i] = CopyHeader(uncles[i])
}
}
return b
}
// NewBlockWithHeader creates a block with the given header data. The
// header data is copied, changes to header and to the field values
// will not affect the block.
func NewBlockWithHeader(header *Header) *Block {
return &Block{header: CopyHeader(header)}
}
// DecodeRLP與 EncodeRLP方法是block的RLP編解碼的具體實現:
// DecodeRLP decodes the Ethereum
func (b *Block) DecodeRLP(s *rlp.Stream) error {
var eb extblock
_, size, _ := s.Kind()
if err := s.Decode(&eb); err != nil {
return err
}
b.header, b.uncles, b.transactions = eb.Header, eb.Uncles, eb.Txs
b.size.Store(common.StorageSize(rlp.ListSize(size)))
return nil
}
// EncodeRLP serializes b into the Ethereum RLP block format.
func (b *Block) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, extblock{
Header: b.header,
Txs: b.transactions,
Uncles: b.uncles,
})
}
這部分簡單介紹了區塊頭、區塊體、區塊、資料庫區塊的資料結構和方法,詳情請參考源碼。
區塊鏈
core/blockchain.go
介紹了交易、區塊頭、區塊後,我們來看下區塊鏈的資料結構:
// 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.
type BlockChain struct {
chainConfig *params.ChainConfig // Chain & network configuration 字面意思是鏈和網路的配置,這些配置暫不深究
cacheConfig *CacheConfig // Cache configuration for pruning
db ethdb.Database // Low level persistent database to store final content in,底層level db
triegc *prque.Prque // Priority queue mapping block numbers to tries to gc 不知道什麼作用
gcproc time.Duration // Accumulates canonical block processing for trie dumping
hc *HeaderChain
rmLogsFeed event.Feed // event.Feed存放著訂閱者資訊,blockchain有事件發生時通知訂閱者
chainFeed event.Feed
chainSideFeed event.Feed
chainHeadFeed event.Feed
logsFeed event.Feed
scope event.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 atomic.Value // 當前區塊
currentFastBlock atomic.Value // 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 // 共識演算法引擎,不同演算法識別了Engine介面
processor Processor // block processor interface,非常重要的資料成員
validator Validator // block and state validator interface,非常重要的資料成員
vmConfig vm.Config // 虛擬機器的配置
badBlocks *lru.Cache // Bad block cache
}
以上就是區塊鏈的資料結構,好多資料成員不明白什麼作用,沒有問題直接跳過,隨著分析的不斷深入,我們就會理解。
// NewBlockChain returns a fully initialised block chain using information available in the database. It initialises the default Ethereum Validator and Processor.
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config) (*BlockChain, error) {
// 如果 cache config 為空白,建立對象
if cacheConfig == nil {
cacheConfig = &CacheConfig{
TrieNodeLimit: 256 * 1024 * 1024,
TrieTimeLimit: 5 * time.Minute,
}
}
// 初始化各種資料成員
bodyCache, _ := lru.New(bodyCacheLimit)
bodyRLPCache, _ := lru.New(bodyCacheLimit)
blockCache, _ := lru.New(blockCacheLimit)
futureBlocks, _ := lru.New(maxFutureBlocks)
badBlocks, _ := lru.New(badBlockLimit)
// 建立區塊鏈對象
bc := &BlockChain{
chainConfig: chainConfig,
cacheConfig: cacheConfig,
db: db,
triegc: prque.New(),
stateCache: state.NewDatabase(db),
quit: make(chan struct{}),
bodyCache: bodyCache,
bodyRLPCache: bodyRLPCache,
blockCache: blockCache,
futureBlocks: futureBlocks,
engine: engine,
vmConfig: vmConfig,
badBlocks: badBlocks,
}
// 建立區塊鏈的驗證器和處理器,後面會看到這兩個資料成員非常重要
bc.SetValidator(NewBlockValidator(chainConfig, bc, engine))
bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine))
var err error
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.getProcInterrupt)
if err != nil {
return nil, err
}
// 擷取創世區塊
bc.genesisBlock = bc.GetBlockByNumber(0)
if bc.genesisBlock == nil {
return nil, ErrNoGenesis
}
if err := bc.loadLastState(); err != nil {
return nil, err
}
// Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
for hash := range BadHashes {
if header := bc.GetHeaderByHash(hash); header != nil {
// get the canonical block corresponding to the offending header's number
headerByNumber := bc.GetHeaderByNumber(header.Number.Uint64())
// make sure the headerByNumber (if present) is in our current canonical chain
if headerByNumber != nil && headerByNumber.Hash() == header.Hash() {
log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
bc.SetHead(header.Number.Uint64() - 1)
log.Error("Chain rewind was successful, resuming normal operation")
}
}
}
// Take ownership of this particular state
go bc.update()
return bc, nil
}
以上就是初始化區塊鏈的方法,BlockChain還有好多重要方法,由於篇幅有限,這裡就不一一展開了,以後分析需要時回來再看。下面簡單標記了幾個重要方法,詳情可以參考代碼。
// loadLastState loads the last known chain state from the database. This method
// assumes that the chain manager mutex is held.
func (bc *BlockChain) loadLastState() error {
......
}
// SetHead rewinds the local chain to a new head. In the case of headers, everythingabove the new head will be deleted and the new one set. In the case of blocks though, the head may be further rewound if block bodies are missing (non-archive nodes after a fast sync).
func (bc *BlockChain) SetHead(head uint64) error {
......
}
// CurrentBlock retrieves the current head block of the canonical chain. The block is retrieved from the blockchain's internal cache.
func (bc *BlockChain) CurrentBlock() *types.Block {
......
}
// SetProcessor sets the processor required for making state modifications.
func (bc *BlockChain) SetProcessor(processor Processor) {
......
}
// SetValidator sets the validator which is used to validate incoming blocks.
func (bc *BlockChain) SetValidator(validator Validator) {
......
}
// Validator returns the current validator.
func (bc *BlockChain) Validator() Validator {
......
}
// Processor returns the current processor.
func (bc *BlockChain) Processor() Processor {
......
}
總結:
這篇文章簡單介紹了go ethetheum源碼中交易、區塊、區塊鏈的資料結構以及初始化方法,希望大家有所瞭解。下一篇文章將會介紹交易池,看一下交易是如何被打包成區塊的,區塊是如何被廣播到網路上的。由於筆者也是菜鳥,很多概念理解不深入,不理解的概念暫且放過,分析深入後就會理解的。