通过创建区块链来学习区块链(1)
主要内容:
- Block的数据结构
- 挖矿的过程
- 工作量证明机制
- 工作量证明的简单实现
- 建立一条简单的测试链
环境:
区块链本质上是一个共享数据库,存储于其中的数据或信息,具有如下特点:
- 数据仅可通过共识算法以块的形式增加,不可修改或删除,以防止篡改。
- 每个区块至少会包含一个块生成时间和出块签名。
- 所有的交易数据都会被双方签名,以防止抵赖。
- 传统区块链中,新增区块中储存上一个区块的hash,并通过此hash与上一个区块相连。
区块(Block)长什么样
Block 包含下面几个要素:
- index : 索引
- timestamp :时间戳
- transactions : 交易列表
- proof : 证明(工作量证明)通过工作量证明算法计算
- previous_hash :上一个区块的哈希值,保证了区块链的不变性
block = {
'index': 1,
'timestamp': 1626857298.6391933,
'transactions': [
{
'sender': "147FA8527",
'recipient': "DA5DF1FD8",
'amount': 50,
}
],
'proof': 32740,
'previous_hash': "06e825e27e52f558e92f7be2f679caf101fbfe227e9cd8d80845029911a0f2d4"
}
class Blockchain
创建一个Blockchain类用来初始化区块链实例。
class Blockchain :
- 属性:
- chain : 列表记录Block
- transaction : 列表记录交易
- 方法:
- new_block() : 创建新的区块(Block)并加入链中(chain)
- new_transaction() : 添加新的交易(transaction)到交易记录中,并返回下一个区块的索引
- hash() : 生成区块的哈希值
- last_block() : 返回区块链中的最后一个块
方法的具体实现:
class Blockchain(object):
def __init__(self):
self.chain = []
self.current_transactions = []
self.new_block(previous_hash = 666, proof = 888)
def new_block(self, previous_hash, proof):
"""
previous_hash : <int>/<str> 前一个block的哈希值
proof :<int> 工作量证明
return -> <dict> New Block
"""
block = {}
block['index'] = len(self.chain) + 1
block['timestamp'] = time()
block['transactions'] = self.current_transactions
block['proof'] = proof
block['previous_hash'] = previous_hash or self.hash(self.chain[-1])
self.chain.append(block)
self.current_transactions = []
return block
def new_transaction(self, sender, recipient, amount):
"""
sender : <str> sender的地址
recipient : <str> recipient的地址
amount : <int> 数量
return -> <int> 索引,是当前block的后续block的索引
"""
transaction = {}
transaction['sender'] = sender
transaction['recipient'] = recipient
transaction['amount'] = amount
self.current_transactions.append(transaction)
return self.last_block['index'] + 1
@staticmethod
def hash(block):
"""
block : <dict> block
return -> <str> 哈希值
"""
block_string = json.dumps(block, sort_keys=True).encode()
hash_val = hashlib.sha256(block_string).hexdigest()
return hash_val
@property
def last_block(self):
return self.chain[-1]
? ??Blockchain类已经实现了创建区块链的大部分功能,但是还差一个很重要的功能,那就是工作量计算和验证。从new_block()可以看出,每创建一个新的区块除了要记录一个不定长的交易列表外,还要提供一个工作量证明。了解工作量证明之前先简单说一下挖矿。
矿是怎么挖的
???挖矿就是创建新的区块并添加到链上的一个过程,每当一个矿工成功创建一个区块就会获得一些加密货币作为奖励。但是在整个网络中同时有成千上万甚至几十万的矿工在挖矿,奖励有限,给谁?为了公平起见,采用了一种工作量证明机制,每个矿工在创建新区块的时候,要提供工作量证明,工作量被认可,矿工创建的区块才会被认可,并获得奖励。同时会立刻向全网广播该区块。其他矿工在收到新区块后,会对新区块进行验证,如果有效,就把它添加到区块链的尾部。在本轮工作量证明的竞争中,这个矿工胜出,而其他矿工都失败了。失败的矿工会抛弃自己当前正在计算还没有算完的区块,转而开始计算下一个区块,进行下一轮工作量证明的竞争。
工作量怎么证明的
工作量证明的本质就是计算,通过计算证明矿工的工作量,工作量证明有两个特点:
- 计算过程十分复杂
- 工作量的验证过程简单
? ??哈希算法常被用来进行工作量证明计算,矿工就是通过不断的尝试哈希计算,直到找到了一个满足要求的哈希值,此时就算是挖矿成功了。具体的工作量证明不同的区块链略有不同,但本质都是进行哈希计算。
下面通过一个简单的例子来说明一下:
? ??假设:当前网络中对创建新区块的要求是:哈希值的首位必须为0。为了尝试不同的哈希值,在挖矿过程中,我们采用一个变量
P
P
P,跟前一个区块一起进行哈希计算(为了简单起见,我们只用上一个区块中的工作量证明参与哈希计算),直到得到特定的哈希值,此时那个
P
P
P就可以作为矿工的工作量证明,否则不停的尝试新的
P
P
P。验证的方法就是,将矿工提供工作量证明
P
P
P跟前一个区块的工作量证明组合在一起进行哈希计算,检验哈希值是否满足要求。
举个例子,当前链中最后一个Block的工作量证明 proof = 888,下一个区块的工作量证明过程如下:
from hashlib import sha256
prev_proof = 888
new_proof = 0
count = 0
while sha256(f'{prev_proof}{new_proof}'.encode()).hexdigest()[0] != "0":
new_proof = new_proof + 2
count += 1
print("count : %d, new_proof : %d"%(count, new_proof))
count : 13, new_proof : 26
经过13次的计算,就找到的满足的哈希值和新的工作量证明,下面验证工作量是否有效:
new_proof = 26
sha256(f'{prev_proof}{new_proof}'.encode()).hexdigest()
>>>'06e825e27e52f558e92f7be2f679caf101fbfe227e9cd8d80845029911a0f2d4'
经过验证,工作量有效。下面继续模拟上面的过程,假设当前的要求是前两位哈希值为00:
prev_proof = 26
new_proof = 0
count = 0
while sha256(f'{prev_proof}{new_proof}'.encode()).hexdigest()[:2] != "00":
new_proof = new_proof + 2
count += 1
print("count : %d, new_proof : %d"%(count, new_proof))
count : 25, new_proof : 50
前三位哈希值为000:
prev_proof = 50
new_proof = 0
count = 0
while sha256(f'{prev_proof}{new_proof}'.encode()).hexdigest()[:3] != "000":
new_proof = new_proof + 2
count += 1
print("count : %d, new_proof : %d"%(count, new_proof))
count : 1266, new_proof : 2532
前四位哈希值为0000:
prev_proof = 2532
new_proof = 0
count = 0
while sha256(f'{prev_proof}{new_proof}'.encode()).hexdigest()[:4] != "0000":
new_proof = new_proof + 2
count += 1
print("count : %d, new_proof : %d"%(count, new_proof))
count : 6280, new_proof : 12560
前五位哈希值为00000
prev_proof = 12560
new_proof = 0
count = 0
while sha256(f'{prev_proof}{new_proof}'.encode()).hexdigest()[:5] != "00000":
new_proof = new_proof + 2
count += 1
print("count : %d, new_proof : %d"%(count, new_proof))
count : 356128, new_proof : 712256
前六位哈希值为000000:
prev_proof = 712256
new_proof = 0
count = 0
while sha256(f'{prev_proof}{new_proof}'.encode()).hexdigest()[:6] != "000000":
new_proof = new_proof + 2
count += 1
print("count : %d, new_proof : %d"%(count, new_proof))
count : 1552855, new_proof : 3105710
???从上面的模拟结果可以发现,随着网络对创建新区块的哈希值要求的变化,矿工要进行哈希计算的次数也在迅速增加,这意味着挖矿的成本也在增加。实际挖矿中,这个挖矿的难度,即哈希值前几位为0的个数会动态的调整,来保证新区块生成的效率。全网算力减少(矿工减少),难度值就会减少,反之算力增加,挖矿难度也随之增加。
工作量证明实现和验证
class Blockchain(object):
def __init__(self): ...
def new_block(self, previous_hash, proof): ...
def new_transaction(self, sender, recipient, amount): ...
@staticmethod
def hash(block):...
@property
def last_block(self): ...
def proof_of_work(self, last_proof):
"""
POW : hash(pp')
p : previous_proof, p': new_proof
last_proof: <int> 上一个区块中的proof
return -> <int> new_proof 满足哈希条件的工作量证明
"""
proof = 0
while self.valid_proof(last_proof, proof) is False:
proof += 1
return proof
@staticmethod
def valid_proof(last_proof, proof):
"""
验证POW,hash(pp')是否满足条件0000
"""
guess = f'{last_proof}{proof}'.encode()
hash_val = hashlib.sha256(guess).hexdigest()
return hash_val[:4] == "0000"
创建一条测试链
test_chain = Blockchain()
test_chain.chain
[{'index': 1,
'timestamp': 1626857298.6391933,
'transactions': [],
'proof': 888,
'previous_hash': 666}]
test_chain.new_transaction("APPLES123","BANANA234", 111)
test_chain.new_transaction("JDI83NG0C","BANANA234", 123)
test_chain.new_transaction("APPLES123","BANANA234", 345)
test_chain.new_transaction("JDI83NG0C","BANANA234", 666)
工作量证明
prev_proof = test_chain.last_block['proof']
new_proof = test_chain.proof_of_work(prev_proof)
创建新的区块
prev_hash = test_chain.hash(test_chain.last_block)
test_chain.new_block(prev_hash, new_proof)
{'index': 2,
'timestamp': 1626857779.4037974,
'transactions': [{'sender': 'APPLES123',
'recipient': 'BANANA234',
'amount': 111},
{'sender': 'JDI83NG0C', 'recipient': 'BANANA234', 'amount': 123},
{'sender': 'APPLES123', 'recipient': 'BANANA234', 'amount': 345},
{'sender': 'JDI83NG0C', 'recipient': 'BANANA234', 'amount': 666}],
'proof': 124179,
'previous_hash': '3c339eac60c9a4f1ee84d42a5c70bea4d510a57a80d833a2cbfc849768ec0964'}
继续添加新的区块
test_chain.new_transaction("TNCSSS256","JWDLH021", 111)
prev_proof = test_chain.last_block['proof']
new_proof = test_chain.proof_of_work(prev_proof)
prev_hash = test_chain.hash(test_chain.last_block)
test_chain.new_block(prev_hash, new_proof)
{'index': 3,
'timestamp': 1626858267.9535542,
'transactions': [{'sender': 'TNCSSS256',
'recipient': 'JWDLH021',
'amount': 111}],
'proof': 39221,
'previous_hash': '0d411a993912d9e20e1ccdc670ab1b8ae2062973c5c9eddca8c47a4ad3d3c9b0'}
test_chain.chain
[{'index': 1,
'timestamp': 1626857298.6391933,
'transactions': [],
'proof': 888,
'previous_hash': 666},
{'index': 2,
'timestamp': 1626857779.4037974,
'transactions': [{'sender': 'APPLES123',
'recipient': 'BANANA234',
'amount': 111},
{'sender': 'JDI83NG0C', 'recipient': 'BANANA234', 'amount': 123},
{'sender': 'APPLES123', 'recipient': 'BANANA234', 'amount': 345},
{'sender': 'JDI83NG0C', 'recipient': 'BANANA234', 'amount': 666}],
'proof': 124179,
'previous_hash': '3c339eac60c9a4f1ee84d42a5c70bea4d510a57a80d833a2cbfc849768ec0964'},
{'index': 3,
'timestamp': 1626858267.9535542,
'transactions': [{'sender': 'TNCSSS256',
'recipient': 'JWDLH021',
'amount': 111}],
'proof': 39221,
'previous_hash': '0d411a993912d9e20e1ccdc670ab1b8ae2062973c5c9eddca8c47a4ad3d3c9b0'}]
下一步:
- 创建一个web服务实现矿工节点跟区块链间的通信
- 模拟矿工跟区块链间的通信过程
|