区块链的简单理解以及python的简单实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luokezhen/article/details/79436942

最近看了不少关于区块链的资讯, 里面大多数都是在鼓吹这个技术有多么长远的未来. 看得多了, 自然也就产生了想要一探究竟的想法. 但是那些文章里都是些自己造的或者强行翻译过来的各种名词, 并没有具体的例子, 虽然看了但是也不能产生更加清晰的认知. 所以, 在csdn上找了一些简单的区块链示例, 尝试着写了一下.


区块类

区块链, 从名字来理解, 就是区块构成的链条. 到此, 两个基本的类已经呼之欲出了. 一个是区块类, 还有区块的容器类

class BlockChain(object):
    pass

class Block(object):
    pass

先来了解一下区块. 一个区块有七个基本的属性 :

  1. index: 就是该区块的序号, 你是第多少个区块, 也叫做链高度
  2. hash: 就是这个区块的id
  3. previous hash: 就是上一个区块的id
  4. tempstamp: 时间戳
  5. difficulty: 该区块的难度指数
  6. nonce: 随机数, 用于产生下一个区块
  7. data: 用来存储这个区块对应的信息

至此, 应该能写出这个类的基本代码了. 还要有一个获取该区块信息的方法, 以便于我们查看这个区块信息

# 区块类
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

猜你喜欢

转载自blog.csdn.net/luokezhen/article/details/79436942
今日推荐