最近看了不少关于区块链的资讯, 里面大多数都是在鼓吹这个技术有多么长远的未来. 看得多了, 自然也就产生了想要一探究竟的想法. 但是那些文章里都是些自己造的或者强行翻译过来的各种名词, 并没有具体的例子, 虽然看了但是也不能产生更加清晰的认知. 所以, 在csdn上找了一些简单的区块链示例, 尝试着写了一下.
区块链, 从名字来理解, 就是区块构成的链条. 到此, 两个基本的类已经呼之欲出了. 一个是区块类, 还有区块的容器类
class BlockChain(object):
pass
class Block(object):
pass
先来了解一下区块. 一个区块有七个基本的属性 :
至此, 应该能写出这个类的基本代码了. 还要有一个获取该区块信息的方法, 以便于我们查看这个区块信息
# 区块类
class Block(object):
# 一个区块有七个基本的属性, 分别是序号, id, 上一个区块id, 随机数, 难度系数, 时间戳, 还有一个区块体, 记录了交易信息
def __init__(self):
self.index = None
# 区块的id
self.hash = None
self.previous_hash = None
self.nonce = None
self.difficulty = None
self.timestamp = None
self.transaction_data = None
# 以字典的形式记录这个区块的信息, 并返回
def get_block_info(self):
block_info = {
'Index': self.index,
'Hash': self.hash,
'Previous_hash': self.previous_hash,
'Nonce': self.nonce,
'Difficulty': self.difficulty,
'Timestamp': self.timestamp,
'Transaction_data': self.transaction_data,
}
return block_info
那么区块是如何生成的呢?答案就是由矿工挖出来的. 区块里一个关键的属性就是这个区块的id, 它是通过hashlib包里的sha256计算方法计算出来的. 但是计算为什么叫做挖矿呢? 因为我们可以给这个id一个限制条件, 那么矿工就需要不停地改变内容去计算结果, 直到满足这个条件为止, 条件设置的越苛刻, 尝试的次数就越多, 计算时间也越长. 这就是挖矿的过程.
所以, 可以尝试着写出矿工类. 有一个挖矿的方法mine, 并且记录耗时, 这里我们设置的规则为id最后一位为0
class Pitman(object):
# 定义矿工的挖矿方法, 需要的参数为该区块的序号, 之前的id, 交易信息
def mine(self, index, previous_hash, transaction_data):
# print('我要开始挖了')
# 开始时间
begin_time = time.time()
block = Block()
block.index = index
block.previous_hash = previous_hash
block.transaction_data = transaction_data
block.timestamp = time.time()
# 根据之前的id和交易信息生成这个区块的id和随机数, 还有困难系数
block.difficulty, block.hash, block.nonce = self.gen_hash(previous_hash, transaction_data)
# 结束时间
end_time = time.time()
# 花费的时间
spend_time = end_time - begin_time
# print('我挖完了')
return block, spend_time
上面的代码中记录了耗时, 使用了我们的区块类, 并设置了对应的属性, 至于id值, 我们另写一个方法来获取
@staticmethod
def gen_hash(previous_hash, transaction_data):
# 随机数, 从1到99999随机取值
nonce = random.randrange(1, 99999)
difficulty = 0
# 先生成一个字符串, 然后尝试, 不符合要求再修改
guess = str(previous_hash) + str(nonce) + transaction_data
# 计算出id
res = hashlib.sha256(guess.encode()).hexdigest()
# 验证生成的id是否符合要求, 这里设定的要求是最后一位为0
while res[-1] != '0':
# 每尝试一次, 难度系数就+1
difficulty += 1
nonce += difficulty
guess = previous_hash + str(nonce) + transaction_data
res = hashlib.sha256(guess.encode()).hexdigest()
# 得到符合要求的id后, 返回难度系数, id值和随机数
return difficulty, res, nonce
我们希望可以同时让多个矿工进行挖矿
# 自定义线程类
class MyThread(Thread):
def __init__(self, target, args=()):
super(MyThread, self).__init__()
self.func = target
self.arg = args
# self.result = None
def run(self):
self.result = self.func(*self.arg)
def get_result(self):
try:
return self.result
except Exception as e:
print('自定义线程获取结果时发生了错误:', e)
return None
继承线程类并重写run方法
每一个区块都是根据上一个区块的id产生的, 那么第一个区块没有上一个区块, 就需要我们来自己设置, 第一个区块被叫做创世区块
# 首先, 创建一个区块链类
class BlockChain(object):
def __init__(self, hash_num):
# 存储区块链对象, 区块的容器
self.chain_list = []
# 矿工的容器
self.pitman_list = []
# 然后再容器中填入6个矿工
for i in range(6):
self.pitman_list.append(Pitman)
# 存储每个阶段产生的区块
self.result_list = []
# 创建区块的方法, 如果当前生成的区块为第一个区块,则产生创世区块
self.gen_block(hash_num)
因为每个区块都是由上一个区块计算出来的, 所以我们需要一个获取上一个区块的方法, 也就是此时区块链中最后一个区块
# 获取最后一个区块
@property
def get_last_block(self):
if len(self.chain_list):
return self.chain_list[-1]
return None
还需要交易信息, 这里我们随机生成一个就好
# 随机生成一份交易信息, 交易信息就是json字符串
def get_trans(self):
dict_data = {
# random.sample可以从一个序列中随机获取指定数量的元素
'sender': ''.join(random.sample(string.ascii_letters + string.digits, 8)),
'recipient': ''.join(random.sample(string.ascii_letters + string.digits, 8)),
# 相当于random.choice(range(1, 10000))
'amount': random.randrange(1, 10000),
}
return json.dumps(dict_data)
接下来就是生成新区块了, 这里需要区分它是否是创世区块, 因为生成规则不同
如果是创世区块
# 生成新区块的方法
def gen_block(self, initial_hash=None):
# 根据传参判断是否是创世区块
# 如果是创世区块
if initial_hash:
# 先用区块类定义一个区块
block = Block()
# 然后对创建好的对象的实例属性进行设置
block.index = 0
block.nonce = random.randrange(0, 99999)
block.previous_hash = '0'
# 写到此, 我并不知道这个0是怎么来的, 以后是不是还要赋其他值?
# 已经了解了, 0就是创世区块, 第一个, 所以是0
block.difficulty = 0
# 区块的交易信息
block.transaction_data = self.get_trans()
# 哈希值
# guess = f'{block.previousHash}{block.nonce}{block.transactionData}'.encode()
# 这个写法我没有看懂
# 看懂了, 是字符串格式化的另一种写法: f写法
guess = str(block.index) + str(block.nonce) + block.previous_hash
block.hash = hashlib.sha256(guess.encode()).hexdigest()
block.timestamp = time.time()
self.chain_list.append(block)
否则
# 如果不是创世区块
else:
# 先启动六个矿工开始挖矿
for pitman in self.pitman_list:
# for i in range(len(self.pitman_list)):
# todo: 参数先不写, 以后在写
# 参数为这个矿工类, 链此时的长度, 最后一个区块id, 交易信息
t = MyThread(target=pitman.mine, args=(pitman,
len(self.chain_list),
# 获取当前这个区块链的最后一个区块的id
self.get_last_block.get_block_info()['Hash'],
# 获取交易信息
self.get_trans()))
# t = MyThread(target=self.pitman_list[i].mine, )
t.start()
t.join()
# 存储挖出来的区块
self.result_list.append(t.get_result())
print("All blocks generated by pitmen:")
# 挖完了之后就该打印挖到的区块了
# 上一个循环是同时启动六个矿工的线程开始运行, 等运行都完毕之后, 才开始继续主程序的运行, 是这样的吗?
for result in self.result_list:
print(result[0].get_block_info())
# 获取新的区块
# 先找到这个符合标准的区块
# 先取出来第一个挖出来的区块
first_block = self.result_list[0][0]
# 再获取第一个区块计算耗费的时间, 转换成标准小数
min_time = Decimal(self.result_list[0][1])
# 去寻找那个用时最短的矿工挖出来的区块
for i in range(1, len(self.result_list)):
if Decimal(self.result_list[i][1]) < min_time:
first_block = self.result_list[i][0]
# 找到以后存储
self.chain_list.append(first_block)
# 清空结果列表
self.result_list = []
展示区块链信息方法
def show_chain(self):
for block in self.chain_list:
print(block.get_block_info())
至此, 一个完整的简单区块链写完了, 跑一下
if __name__ == '__main__':
chain = BlockChain(1)
for i in range(20):
chain.gen_block()
chain.show_chain()
完整代码:
https://github.com/luokezhen/lkz/tree/master/%E5%8C%BA%E5%9D%97%E9%93%BE
参考资料:
http://blog.csdn.net/bmwgaara/article/details/79059007
以上均为我个人的学习感悟, 欢迎批评指正
QQ: 1396737599
微信: 18500094110