接触区块链,大都是从比特币开始,从比特币的特性到源码实现学习,POW是重点中的重点,而且POW也是以太坊的共识算法,可以说POW是真正的随机选取节点的共识算法,下面就以比特币为例,学习下POW共识算法
POW:proof of work, 翻译过来是工作量证明,即用一定量的工作量来换取记账权利,再比特币中,就是计算一个数学难题,谁先算出来并广播出来,谁就有权利记账,具体的:
区块构成:
区块由BlockHeader + Transaction list构成
nVersion: 版本号
hashPrevBlock: 父区块哈希,即SHA256(SHA256(父区块头))
hashMerkleRoot: Transhaction list的Merkle树根哈希,可以快速验证某个交易是否存在某个区块中
nTime: 时间戳,区块产生时间
nBits: 区块的难度目标,32bit,其中高8bit是指数参数 c,底24bit是底数a, 由此计算出难度值就是a * 2^(8*(c -3))
nNonce: 就是工作量证明里寻找的随机值,
这样POW算法就是计算SHA256(SHA256(区块头)) ,寻找1个nonce,是的最后的哈希值小于上面计算的难度值
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
{
bool fNegative;
bool fOverflow;
arith_uint256 bnTarget;
bnTarget.SetCompact(nBits, &fNegative, &fOverflow);
// Check range
if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit))
return false;
// Check proof of work matches claimed amount
if (UintToArith256(hash) > bnTarget)
return false;
return true;
}
难度值是动态调整的,进而可以控制找到nonce的时间,也就控制了出块时间,难度值的调整算法是根据前面2016个区块的实际时间跟理论时间(理论上1个区块是10分钟,则2016块就是20160分钟)的比值来调剂,实际时间短,即出块速度快,就增大难度,使得后面出块速度慢下来,反之加快出块速度,具体看算法
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
assert(pindexLast != nullptr);
unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();
// Only change once per difficulty adjustment interval
// 判断是否是新的2016个区块 不等于0 即还在1个2016区块调节步长内,继续用父区块的nBits
if ((pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0)
{
if (params.fPowAllowMinDifficultyBlocks)
{
// Special difficulty rule for testnet:
// If the new block's timestamp is more than 2* 10 minutes
// then allow mining of a min-difficulty block.
if (pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing*2)
return nProofOfWorkLimit;
else
{
// Return the last non-special-min-difficulty-rules-block
const CBlockIndex* pindex = pindexLast;
while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)
pindex = pindex->pprev;
return pindex->nBits;
}
}
return pindexLast->nBits;
}
//等于0,即开始进入新的1个2016步长
// Go back by what we want to be 14 days worth of blocks
int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);
assert(nHeightFirst >= 0);
const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);
assert(pindexFirst);
//计算新的nBits
return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);
}
unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params)
{
if (params.fPowNoRetargeting)
return pindexLast->nBits;
// Limit adjustment step
int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
if (nActualTimespan < params.nPowTargetTimespan/4)
nActualTimespan = params.nPowTargetTimespan/4;
if (nActualTimespan > params.nPowTargetTimespan*4)
nActualTimespan = params.nPowTargetTimespan*4;
// Retarget
const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
arith_uint256 bnNew;
//根据父区块的nBits计算难度值
bnNew.SetCompact(pindexLast->nBits);
// 进行调节
bnNew *= nActualTimespan;
bnNew /= params.nPowTargetTimespan;
if (bnNew > bnPowLimit)
bnNew = bnPowLimit;
//转成nBits形式
return bnNew.GetCompact();
}
这样由区块的构成元素,挖矿的计算过程,难度的调节算法可以总结比特币挖矿的的整个过程:
1. 构建区块,包括将交易池中的交易打包进区块的交易列表中,区块头填充各个参数(父区块hash,merkle树根hash,时间戳,版本号)
2. 判断目标值是否调整,确定nBits
3. 挖矿,寻找nonce,计算区块头的hash值,指导找到一个nonce,使得hash值,小于难度值
调用python的hashlib库模拟了个简单的pow算法
import hashlib
import time
class Block:
def __init__(self, index, timestamp, tx, parentHash=""):
self.index = index
self.parentHash = parentHash
self.tx = tx
self.timestamp = timestamp
self.nonce = 1
self.hash = self.calculateHash()
def calculateHash(self):
sha256 = hashlib.sha256()
string = str(self.index) + str(self.parentHash) + str(self.tx) + str(self.timestamp) + str(self.nonce)
sha256.update(string.encode('utf-8'))
return sha256.hexdigest()
def makeNewBlock(self, difficulty):
while(self.hash[0:difficulty] != str(0).zfill(difficulty)):
self.nonce += 1
self.hash = self.calculateHash()
class BlockChain:
def __init__(self):
self.chain = [Block(0, "12/22/2018", "genesis block")]
self.difficulty = 5;
def addBlock(self, newBlock):
newBlock.parentHash = self.chain[len(self.chain) - 1].hash
newBlock.makeNewBlock(self.difficulty)
self.chain.append(newBlock)
testBlockChain = BlockChain()
firstBlockStartTime = time.time()
print("the first block time is: " + str(firstBlockStartTime))
testBlockChain.addBlock(Block(1, "12/23/2018", "{tx:10}"))
firstBlockEndTime = time.time()
print("time spend for first block is: " + str(firstBlockEndTime - firstBlockStartTime))
secondBlockStartTime = time.time()
print("the second block time is: " + str(secondBlockStartTime))
testBlockChain.addBlock(Block(1, "12/24/2018", "{tx:20}"))
secondBlockEndTime = time.time()
print("time spend for second block is: " + str(secondBlockEndTime - secondBlockStartTime))