I have yet to meet a person who understands blockchain but don't believe in it. ——by CZ
本文将在前文基础上使用python实现一条简单的链。
源码地址PYchain/blockchain
创世区块
第一个区块——创始区块的信息直接定义,以后的区块信息要在挖矿时计算得出。 区块内容格式如下图函数定义:
- index代表区块高度,代表是第几个区块,从0开始
- merkle_tree是transaction里的交易哈希之后最后得到的默克尔根
- previous_hash 是前一个区块的哈希值
- proof是该区块的工作量证明
- timestamp代表区块创建时间
- transaction包括当前区块包含的交易
def genesis_block(self):
block = {
'index': 0,
'merkle_tree': '000000',
'previous_hash': '000000',
'proof': 138,
'timestamp': 1559273178.3706222,
'transactions': [],
}
self.chain.append(block)
创建链
链用一个列表来表示,将上边的创世区块加进来,至此,区块 + 链 = 区块链!!!
self.chain = []
self.genesis_block()
挖矿奖励交易
当你通过工作量证明组装区块时,区块中的第一笔交易必须是挖矿奖励。 挖矿奖励amount为50,接受者是你的公钥,发送者是系统(Satoshi只是一个名称,可以是任意字符) 为了以后计算merkle root,还需计算这条交易的哈希值,用txhash表示。
# 初始化coin_base奖励
self.mine_transaction = {'amount': 50, 'recipient': str(self.public_key), 'sender': 'Satoshi'}
self.coin_base = [[self.mine_transaction, {'txhash': sha_256(self.mine_transaction)}]]
挖矿
挖矿(proof of work)就是找到一个符合条件的新区块。 对区块的各个内容填充:
- 计算当前区块的高度
- 计算默克尔根
- 将proof初始化为0
- 得到当前时间
- 将挖矿交易、本地的交易和接收到的交易加入transactions
def proof_of_work(self):
# coin_base交易结构[[{tx},{txhash}]]
proof = 0
t = time()
mk = [self.coin_base[0][1]['txhash']]
for tx in self.current_transactions:
mk.append(tx[2]['txhash'])
block = {
'index': len(self.chain),
'merkle_tree': merkle_tree(mk),
'previous_hash': sha_256(self.chain[-1]),
'proof': proof,
'timestamp': t,
'transactions': self.coin_base + self.current_transactions + self.receive_transactions,
}
while self.valid_proof(block) is False:
proof += 1
block['proof'] = proof
self.current_transactions = []
self.receive_transactions = []
self.chain.append(block)
self.utxo_pool(block)
return block
下面开始做工作量证明,计算组装区块的哈希值,如果哈希值的前2位(difficult设置为2)不等于'00',’proof加1,继续计算组装区块的哈希,直到找到一个满足条件的proof值,这个proof满足区块哈希之后前两位为0。完成工作量证明之后,将当前交易池清空,因为已经产生了一个区块将这些交易信息包含了,将区块加到链上。(比特币当前的难度大约需要前19位为0)
def valid_proof(block, difficulity=DIFFICULTY):
guess_hash = sha_256(block)
return guess_hash[:difficulity] == "0"*difficulity
计算公钥和当前余额
计算你的公钥当前拥有多少钱,公钥的收入支出有:
* 挖矿
* 别人支付给你
* 你支付给别人
当有新区块产生时,如果区块是当前节点挖出的话,那么区块transaction里的第一条交易的接受者就是当前节点的公钥,挖矿奖励,余额+50,遍历剩余交易,如果交易的发送者(sender)是节点公钥的话,余额减去相应的amount,余下同理。
def utxo_pool(self, block):
# 交易提交之前检验余额是否足够
if block['transactions'][0][0]['recipient'] == str(self.public_key):
self.amount = self.amount + 50
for tx in block['transactions'][1:]:
if tx[0]['sender'] == str(self.public_key):
self.amount = self.amount - tx[0]['amount']
if tx[0]['recipient'] == str(self.public_key):
self.amount = self.amount + tx[0]['amount']
提交交易
def sub_transaction(self, recipient, amount):
# 交易结构[[t1,t2,t3]]
if amount > self.amount:
return False
else:
t1 = {# 字典顺序不能修改,否则网络传输过程中改变最终hash值
'amount': amount,
'recipient': recipient,
'sender': str(self.public_key),
}
signature = sign(t1, self.private_key)
t2 = {'signature': signature}
t3 = {'txhash': sha_256(t1)}
tx = [t1, t2, t3]
self.current_transactions.append(tx)
return True
支付给其他人,就是提交交易,这条消息被写入区块,交易完成。 首先查询余额,余额足够才能正确提交交易。 然后填写交易信息t1,填写支付金额,填写接受者,发送者为当前节点公钥;然后对t1签名,然后计算交易哈希,最后将完整的一条交易信息加入本地交易池,注意加入交易池不代表交易完成,只有当交易被写入区块才算交易完成
节点交互
上边的内容你已经拥有一条链了,可以通过工作量证明进行挖矿,可以提交交易(使用币),可以查询自己的余额,慢慢该链的长度变长。 接下来,当有一个新的节点加入时,两个节点有两条链,虽然创世区块是一样的,但之后的区块完全不一样,比特币的规则是谁的链长谁是真的链。
首先得到邻居节点(neighbor)的链的长度和链. 然后判断谁的链长度最长,如果邻居的链长度是较长的,检验邻居链的合法性,合法后将自身链转变为邻居链 当自身链被替换后,需要重置账户余额,遍历新链来计算,并将交易池清空 。
def resolve_conflicts(self):
new_chain = None
max_length = len(self.chain)
if len(self.neighbor) == 0:
pass
else:
for node in self.neighbor:
response = requests.get(f'http://{node}/chain')
if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']
if length > max_length and self.valid_chain(chain):
max_length = length
new_chain = chain
if new_chain:
self.chain = new_chain
self.amount = 0
for block in self.chain[1:]:
self.utxo_pool(block)
self.current_transactions = []
self.receive_transactions = []
return True
return False
判断链的合法性有以下几个方面,从链的创始区块开始,每个节点都要独立验证链上所有信息的合法性。
- 每个区块的的previous_hash是否是上一个区块的哈希
- 每个区块的工作量证明是否正确
- 每个区块包含的交易是否正确
def valid_chain(self, chain):
last_block = chain[0]
current_index = 1
while current_index < len(chain):
block = chain[current_index]
last_block_hash = sha_256(last_block)
if block['previous_hash'] != last_block_hash:
self.msg.append('hash wrong')
return False
if not self.valid_proof(block):
self.msg.append('proof wrong')
return False
if not self.valid_block_transaction(block):
self.msg.append('t wrong')
return False
last_block = block
current_index += 1
return True
关于如何验证交易正确与否,就是验签。
def valid_block_transaction(block):
transactions = block['transactions'][1:]
if len(transactions) == 0:
return True
else:
for tx in transactions:
if not (verify_sign(tx[0], eval(tx[0]['sender']), tx[1]['signature'])):
return False
return True
参考资料
Mastering Bitcoin Andreas Antonopoulos
Learn Blockchains by Building One
https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
A Practical Introduction to Blockchain with Python http://adilmoujahid.com/posts/2018/03/intro-blockchain-bitcoin-python/