python区块链简单模拟【05】

新增内容:构建去中心化网络

import socket      #套接字,利用三元组【ip地址,协议,端口】可以进行网络间通信
import threading   #线程
import pickle

# 定义一个全局列表保存所有节点
NODE_LIST = []


class Node(threading.Thread):  #继承与线程
    def __init__(self, name, port, host="localhost"):
        threading.Thread.__init__(self, name=name)
        self.host = host           #  服务器地址,本地电脑都设为localhost
        self.port = port           # 每个节点对应一个唯一的端口号
        self.name = name           # 唯一的节点名称
        self.wallet = Wallet()
        self.blockchain = None    # 用来存储一个区块链副本  账本
        
    def run(self):
        """
            节点运行
        """
        self.init_blockchain()    # 初始化区块链
        
        # 在指定端口进行监听
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)      #获取TCP/ip  套接字sock
        sock.bind((self.host, self.port))                             #绑定主机,端口号到套接字sock
        NODE_LIST.append({
    
    
            "name": self.name,
            "host": self.host,
            "port": self.port
        })
        
        sock.listen(10)                                            #开始TCP监听
        print(self.name, "运行中...")
        
        while True:       # 不断处理其他节点发送的请求
            connection,address = sock.accept()                    #被动接受TCP客户的连接,(阻塞式)等待连接的到来
            try:
                print(self.name, "处理请求内容...")
                self.handle_request(connection)
            except socket.timeout:  
                print('超时!')
            except Exception as e:
                print(e, )
            connection.close()
            
            
            
            
    
    def handle_request(self, connection):
        data = []
        
        while True:    # 不断读取请求数据直至读取完成
            buf = connection.recv(1024)  
            if not buf: # 若读取不到新的数据则退出
                break
            data.append(buf)
            if len(buf) < 1024:   # 若读取到的数据长度小于规定长度,说明数据读取完成,退出
                break
        t = pickle.loads(b''.join(data)) #从pickle格式的文件中读取数据并转换为Python的类型。
        print("数据接受完成,判断数据的  类 :交易,区块,初始化" )
        
        if isinstance(t, Transaction):  # 如果t是新交易类  消息
            print("处理交易请求...")
            if verify_sign(t.pubkey, str(t), t.signature):   #验证交易,公钥验证签名
                # 验证交易签名没问题,生成一个新的区块
                print(self.name, "验证交易成功")
            
                new_block = Block(transactions=[t], prev_hash="")
                print(self.name, "生成新的区块...")
                
                w = ProofOfWork(new_block, self.wallet)
                block = w.mine()                              #挖矿,挖到正确的区块哈希值,此处block就是新的区块,主要是找到了符合要求的nonce值
                print(self.name, "将新区块添加到区块链中")
                self.blockchain.add_block(block)
                
                
                print(self.name, "将新区块广播到网络中...")
                self.broadcast_new_block(block)
                
                
            else:
                print(self.name, "交易验证失败!")   #签名不对
                
        elif isinstance(t, Block):   #如果t是新区块类  消息
            print("处理新区块请求...")
            if self.verify_block(t):  
                print(self.name, "区块验证成功")  
                self.blockchain.add_block(t)
                print(self.name, "添加新区块成功")
            else:
                print(self.name, "区块验证失败!")
                
                
        else:  # 如果不是新区块消息,默认为初始化消息类型,返回本地区块链内容
            print("是我是我,我是初始化,我要返回我的区块链信息")
            connection.send(pickle.dumps(self.blockchain))
            
    def verify_block(self, block):
        """
            验证区块有效性   是否是符合难度的区块哈希值,找到了正确的nonce值
        """
        message = hashlib.sha256()
        message.update(str(block.prev_hash).encode('utf-8'))
        # 更新区块中的交易数据
        # message.update(str(self.block.data).encode('utf-8'))
        message.update(str(block.transactions).encode('utf-8'))
        message.update(str(block.timestamp).encode('utf-8'))
        message.update(str(block.nonce).encode('utf-8'))
        digest = message.hexdigest()

        prefix = '0' * DIFFICULTY
        return digest.startswith(prefix)
            
    def broadcast_new_block(self, block):
        """
            将新生成的区块广播到网络中其他节点
        """
        for node in NODE_LIST:     #遍历节点中的每一个节点,把新的区块广播给除了自己的所有节点
            host =node['host']
            port = node['port']
            
            if host == self.host and port == self.port:
                print(self.name, "忽略自身节点")
            else:
                print(self.name, "广播新区块至 %s" % (node['name']))
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.connect((host, port))    # 连接到网络中的节点
                sock.send(pickle.dumps(block))   # 发送新区块
                sock.close()          # 发送完成后关闭连接
                
    def init_blockchain(self):
        """
            初始化当前节点的区块链
        """
        #PER_BYTE = 1024
        if NODE_LIST:                # 若当前网络中已存在其他节点,则从第一个节点从获取区块链信息
            host = NODE_LIST[0]['host']
            port = NODE_LIST[0]['port']
            name = NODE_LIST[0]["name"]
            print(self.name, "发送初始化请求 %s" % (name))
            print("开始让节点1发送请求")
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    #获取TCP/ip套接字
            sock.connect((host, port))    # 连接到网络中的第一个节点
            sock.send(pickle.dumps('INIT'))   # 发送初始化请求   pickle.dumps将Python数据转换为pickle格式的bytes字串
            print("请求成功")
            
            data = []
            print("开始接受节点1的connect返回的信息")
            while True:          # 读取区块链信息,直至完全获取后退出
                buf = sock.recv(1024)
                print("接收中")
                if not buf:
                    print("接收完毕,接空")
                    break
                
                data.append(buf)
                if len(buf) < 1024:
                    print("太短了,完毕")
                    break
            sock.close()   # 获取完成后关闭连接
            
            # 将获取的区块链信息赋值到当前节点
            self.blockchain = pickle.loads(b''.join(data))
            print(self.name, "初始化完成.")
            
        else:
            # 如果是网络中的第一个节点,初始化一个创世区块
            block = Block(transactions=[], prev_hash="")
            w = ProofOfWork(block, self.wallet)
            genesis_block = w.mine()
            self.blockchain = BlockChain()
            self.blockchain.add_block(genesis_block)
            print("生成创世区块")
    
    def submit_transaction(self, transaction):   #遍历节点中的每一个节点,把新的交易广播给除了自己的所有节点
         for node in NODE_LIST:
            host =node['host']
            port = node['port']
            
            if host == self.host and port == self.port:
                print(self.name, "忽略自身节点")
            else:
                print(self.name, "广播新区块至 %s:%s" % (self.host, self.port))
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
                sock.connect((node["host"], node["port"]))  
                sock.send(pickle.dumps(transaction)) 
                sock.close()

        
    def get_balance(self):
        balance = 0
        for block in self.blockchain.blocks:
            for t in block.transactions:
                if t.sender == self.wallet.address.decode():
                    balance -= t.amount
                elif t.recipient == self.wallet.address.decode():
                    balance += t.amount
        print("当前拥有%.1f个加密货币" % (balance))
    
    def print_blockchain(self):
        print("区块链包含区块个数: %d\n" % len(self.blockchain.blocks))
        for block in self.blockchain.blocks:
            print("上个区块哈希:%s" % block.prev_hash)
            print("区块内容:%s" % block.transactions)
            print("区块哈希:%s" % block.hash)
            print("\n") 
# 初始化节点1

node1 = Node("节点1", 8000)
node1.start()   #启动线程  调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();直接调用 run() 方法
node1.print_blockchain()  #输出区块信息

在这里插入图片描述

node2 = Node("节点2", 8001)
node2.start() 
node2.print_blockchain()

在这里插入图片描述

node1.get_balance()
node2.get_balance()

在这里插入图片描述

#创建交易
new_transaction = Transaction(
    sender=node1.wallet.address,
    recipient=node2.wallet.address,
    amount=0.3
)
sig = node1.wallet.sign(str(new_transaction))  #私钥签名
new_transaction.set_sign(sig, node1.wallet.pubkey)#发送公钥,和签名,给验证者验证
node1.submit_transaction(new_transaction)    #广播交易

在这里插入图片描述node1.print_blockchain()

node2.print_blockchain()

在这里插入图片描述

node1.get_balance()
node2.get_balance()

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43491496/article/details/135200877