├── .gitignore ├── Block.py ├── Blockchain.py ├── MerkleTrees.py ├── README.md ├── __init__.py ├── blockchain.py ├── db.py ├── genisus_private.pem ├── genisus_public.pem ├── miniblockchain1.jpg ├── miniblockchain2.jpg ├── miniblockchain3.jpg ├── p2p ├── __init__.py ├── constant.py ├── kbucketset.py ├── nearestnodes.py ├── node.py └── packet.py ├── run.py ├── script.py ├── simulation_test.py ├── tools.py ├── transaction.py ├── util.py └── wallet.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | ./idea/ 3 | peer/ 4 | test.py 5 | *.pyc -------------------------------------------------------------------------------- /Block.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | import json 4 | 5 | 6 | class Block(object): 7 | def __init__( 8 | self, 9 | index, 10 | previous_hash, 11 | timestamp, 12 | nonce, 13 | current_hash, 14 | difficulty): 15 | """ 16 | 区块结构 17 | :param index: 区块索引 18 | :param previous_hash: 前一区块地址 19 | :param timestamp: 时间戳 20 | :param nonce: 当前区块POW共识过程的随机数 21 | :param current_hash: 当前区块的目标哈希值 22 | :param difficulty: 难度系数 23 | """ 24 | # header 25 | self.index = index 26 | self.previous_hash = previous_hash 27 | self.timestamp = timestamp 28 | self.nonce = nonce 29 | self.current_hash = current_hash 30 | self.difficulty = difficulty 31 | self.merkleroot = None 32 | 33 | # body 34 | self.transactions = None # 对象数组 35 | 36 | def get_transactions(self): 37 | return self.transactions 38 | 39 | def json_output(self): 40 | output = { 41 | 'index': self.index, 42 | 'previous_hash': self.previous_hash, 43 | 'timestamp': self.timestamp, 44 | 'nonce': self.nonce, 45 | 'current_hash': self.current_hash, 46 | 'difficulty': self.difficulty, 47 | 'merkleroot': self.merkleroot, 48 | 'transactions': [tx.json_output() for tx in self.transactions] 49 | } 50 | return output 51 | 52 | def __str__(self): 53 | return json.dumps( 54 | self.json_output(), 55 | default=lambda obj: obj.__dict__, 56 | sort_keys=True, 57 | indent=4) 58 | -------------------------------------------------------------------------------- /Blockchain.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from binascii import hexlify, Error 3 | from time import time 4 | 5 | import rsa 6 | 7 | import db 8 | import wallet 9 | from Block import Block 10 | from MerkleTrees import MerkleTrees 11 | from transaction import * 12 | from util import * 13 | 14 | try: 15 | from urllib.parse import urlparse 16 | except ImportError: 17 | from urlparse import urlparse 18 | 19 | 20 | class Blockchain(object): 21 | def __init__(self, genisus_node=False): 22 | """ 23 | 24 | :param genisus_node: 判断是否是创世节点,如果是则读取本地(genisus_public.pem和genisus_private.pem)密钥对, 25 | 创始区块的第一笔交易就是奖励给genisus_public.pem 26 | """ 27 | self.difficulty = 4 28 | self.current_transactions = [] 29 | self.wallet = wallet.Wallet(genisus_node) 30 | genius_block = self.get_genius_block() # 创世区块 31 | db.write_to_db(self.wallet.address, genius_block) 32 | self.candidate_blocks = {} 33 | 34 | def get_genisus_pubkey(self): 35 | """ 36 | 获取创世节点的公钥。对象 37 | :return: 38 | """ 39 | with open('genisus_public.pem', 'r') as f: 40 | pubkey = rsa.PublicKey.load_pkcs1(f.read().encode()) 41 | return pubkey 42 | 43 | def get_genius_block(self): 44 | """ 45 | 创建创始区块 46 | :return: 返回对象 47 | """ 48 | txin = TxInput(None, -1, None, None) 49 | pubkey_hash = Script.sha160(str(self.get_genisus_pubkey())) 50 | txoutput = TxOutput(100, pubkey_hash) 51 | coinbase_tx = Transaction([txin], [txoutput], 1496518102) # 创世区块的第一笔交易 52 | transactions = [coinbase_tx] 53 | 54 | merkletrees = MerkleTrees(transactions) 55 | merkleroot = merkletrees.get_root_leaf() 56 | nonce = 0 57 | 58 | genish_block_hash = calculate_hash(index=0, 59 | previous_hash='00000000000000000000000000000000000000000000000000000000000000', 60 | timestamp=1496518102, 61 | merkleroot=merkleroot, 62 | nonce=nonce, 63 | difficulty=self.difficulty) 64 | while genish_block_hash[0:self.difficulty] != '0' * self.difficulty: 65 | genish_block_hash = calculate_hash(index=0, 66 | previous_hash='00000000000000000000000000000000000000000000000000000000000000', 67 | timestamp=1496518102, 68 | merkleroot=merkleroot, 69 | nonce=nonce, 70 | difficulty=self.difficulty) 71 | nonce += 1 72 | genius_block = Block(index=0, 73 | previous_hash='00000000000000000000000000000000000000000000000000000000000000', 74 | timestamp=1496518102, 75 | nonce=nonce, 76 | current_hash=genish_block_hash, 77 | difficulty=self.difficulty) 78 | genius_block.merkleroot = merkleroot 79 | genius_block.transactions = transactions 80 | 81 | return genius_block 82 | 83 | def new_coinbase_tx(self, address): 84 | """ 85 | 创世块,区块的第一笔交易,用于奖励矿工 86 | :param address: 钱包接收地址 87 | :return: 对象 88 | """ 89 | txin = TxInput(None, -1, None, None) 90 | pubkey_hash = hexlify(Wallet.b58decode(address)).decode('utf8') 91 | txoutput = TxOutput(10, pubkey_hash) 92 | tx = Transaction([txin], [txoutput], int(time())) 93 | return tx 94 | 95 | def generate_block(self, merkleroot, next_timestamp, next_nonce): 96 | """ 97 | 创建区块 98 | :param merkleroot: 默克尔树根节点 99 | :param next_timestamp: 100 | :param next_nonce: 101 | :return: 102 | """ 103 | previous_block = self.get_last_block() 104 | next_index = previous_block.index + 1 105 | previous_hash = previous_block.current_hash 106 | 107 | next_block = Block( 108 | index=next_index, 109 | previous_hash=previous_hash, 110 | timestamp=next_timestamp, 111 | nonce=next_nonce, 112 | current_hash=calculate_hash(next_index, previous_hash, next_timestamp, merkleroot, next_nonce, 113 | self.difficulty), 114 | difficulty=self.difficulty 115 | ) 116 | next_block.merkleroot = merkleroot 117 | 118 | return next_block 119 | 120 | def trimmed_copy_tx(self, tx): 121 | """ 122 | 被修剪后的带待签名副本,该副本包含交易中的所有输入和输出, 123 | 但是TxInput.signature和TxInput.pubkey被设置未None 124 | :param tx: 对象,待修剪、拷贝的交易 125 | :return: 126 | """ 127 | inputs = list() 128 | outputs = list() 129 | 130 | for txin in tx.txins: 131 | inputs.append(TxInput(txin.prev_txid, txin.prev_tx_out_idx, None, None)) 132 | 133 | for txout in tx.txouts: 134 | outputs.append(TxOutput(txout.value, txout.pubkey_hash)) 135 | 136 | return Transaction(inputs, outputs, tx.timestamp) 137 | 138 | def sign(self, tx, privkey, prev_txid_2_tx): 139 | """ 140 | 用私钥对交易tx签名 141 | :param tx: 对象,待签名对象 142 | :param privkey: 对象,私钥 143 | :param prev_txid_2_tx: ,txid与tx的映射 144 | :return: 145 | """ 146 | if tx.is_coinbase(): 147 | return 148 | 149 | tx_copy = self.trimmed_copy_tx(tx) # 每个输入的signature和pubkey设置为None 150 | 151 | signature_flag = dict() 152 | for in_idx in range(len(tx_copy.txins)): 153 | txin = tx_copy.txins[-1 - in_idx] 154 | prev_tx = prev_txid_2_tx[txin.prev_txid] 155 | 156 | txin.signature = None 157 | txin.pubkey = prev_tx.txouts[txin.prev_tx_out_idx].pubkey_hash 158 | data = tx_copy.get_hash() # 待签名数据 159 | txin.pubkey = None 160 | 161 | signature = rsa.sign(data.encode(), privkey, 'SHA-256') 162 | signature_flag[in_idx] = signature 163 | 164 | for in_idx in range(len(tx_copy.txins)): 165 | if in_idx in signature_flag: 166 | tx.txins[in_idx].signature = signature_flag[in_idx] 167 | 168 | def sign_transaction(self, tx, privkey): 169 | """ 170 | 171 | :param tx: 对象,待签名对象 172 | :param privkey: 对象,私钥 173 | :return: 174 | """ 175 | 176 | prev_txid_2_tx = dict() 177 | for txin in tx.txins: 178 | prev_txid_2_tx[txin.prev_txid] = self.find_transaction(txin.prev_txid) 179 | self.sign(tx, privkey, prev_txid_2_tx) 180 | 181 | def verify_transaction(self, tx): 182 | """ 183 | 验证一笔交易 184 | 185 | :param tx: 对象 186 | :return: 187 | """ 188 | 189 | prev_txid_2_tx = dict() 190 | for txin in tx.txins: 191 | prev_txid_2_tx[txin.prev_txid] = self.find_transaction(txin.prev_txid) 192 | 193 | return self.verify(tx, prev_txid_2_tx) 194 | 195 | def verify(self, tx, prev_txid_2_tx): 196 | """ 197 | 验证一笔交易 198 | rsa.verify(data.encode(), signature, pubkey) 199 | :param tx: 对象 200 | :param prev_txid_2_tx: ,txid与tx的映射 201 | :return: 202 | """ 203 | if tx.is_coinbase(): 204 | return True 205 | tx_copy = self.trimmed_copy_tx(tx) # 每个输入的signature和pubkey设置未None 206 | for in_idx in range(len(tx_copy.txins)): 207 | # 校验每一个输入的有效性(即检查解锁脚本是否有效) 208 | txin = tx_copy.txins[-1 - in_idx] 209 | prev_tx = prev_txid_2_tx[txin.prev_txid] 210 | if not prev_tx: 211 | # 区块不存在 212 | return False 213 | txin.signature = None 214 | txin.pubkey = prev_tx.txouts[txin.prev_tx_out_idx].pubkey_hash 215 | data = tx_copy.get_hash() # 待签名数据 216 | 217 | txin.pubkey = None 218 | 219 | scriptSig = [tx.txins[in_idx].signature, tx.txins[in_idx].pubkey] # 解锁脚本 220 | scriptPubKey = prev_tx.txouts[txin.prev_tx_out_idx].get_scriptPubKey() # 锁定脚本 221 | 222 | if not Script.check_tx_script(data, scriptSig, scriptPubKey): 223 | return False 224 | 225 | return True 226 | 227 | def find_transaction(self, txid): 228 | """ 229 | 通过交易id找到一笔Tx交易 230 | :param txid: 交易id 231 | :return: 232 | """ 233 | # 在区块中寻找(已确认的交易) 234 | block_height = db.get_block_height(self.wallet.address) 235 | 236 | for index in range(block_height): 237 | block = db.get_block_data_by_index(self.wallet.address, index) 238 | # 1.获取区块下的所有的交易 239 | transactions = block.get_transactions() 240 | for tx in transactions: 241 | if tx.txid == txid: 242 | return tx 243 | 244 | # 在交易池中寻找(未确认的交易)TODO待确认 245 | for k in range(len(self.current_transactions)): 246 | uncomfirmed_tx = self.current_transactions[-1 - k] 247 | if uncomfirmed_tx.txid == txid: 248 | return uncomfirmed_tx 249 | return None 250 | 251 | def get_balance(self, address): 252 | """ 253 | 获取address地址的余额 254 | :param address: 钱包地址 255 | :return: 256 | """ 257 | balance, _ = self.find_spendalbe_outputs(address) 258 | return balance 259 | 260 | def get_wallet_address(self): 261 | return self.wallet.address 262 | 263 | def new_utxo_transaction(self, from_addr, to_addr, amount): 264 | """ 265 | from_addr向to_addr发送amount量的货币,步骤: 266 | Step1:首先从区块中获取from_addr下未使用过的TxOutput输出(未使用的UTXO) 267 | Step2:获取上一步TxOutput列表中value之和sum,并与amount相比 268 | Step3:如果sum=amount,则from_addr足够余额用于交易,多出的部分用于找零 269 | Step4:构造Transaction对象,并传入此次交易的输入和输出 270 | Step5:输入检查交易的有效性,输出设置锁定脚本 271 | Step6:广播 272 | 273 | :param from_addr: ,发送方钱包地址 274 | :param to_addr: ,接收方钱包地址 275 | :param amount: ,金额 276 | :return: 277 | """ 278 | inputs = list() 279 | outputs = list() 280 | balance, unspent_txout_list = self.find_spendalbe_outputs(from_addr) 281 | if balance < amount: 282 | print 'not enough money!' 283 | return None 284 | 285 | # 构造Tx的输入 286 | for txid, out_idx, txout in unspent_txout_list: 287 | txin = TxInput(txid, out_idx, None, self.wallet.pubkey) 288 | inputs.append(txin) 289 | 290 | # 构造Tx的输出 291 | txout = TxOutput(amount, hexlify(Wallet.b58decode(to_addr)).decode('utf8')) 292 | outputs.append(txout) 293 | if balance > amount: 294 | # 找零 295 | outputs.append(TxOutput(balance - amount, Script.sha160(str(self.wallet.pubkey)))) 296 | 297 | tx = Transaction(inputs, outputs, int(time())) 298 | self.sign_transaction(tx, self.wallet.privkey) # 签名Tx 299 | 300 | self.current_transactions.append(tx) 301 | db.write_unconfirmed_tx_to_db(self.wallet.address, tx) 302 | return tx 303 | 304 | def get_balance_by_db(self, from_addr): 305 | """ 306 | 获取from_addr可以用于交易的TxOutput(未使用过的),读取交易池和区块链的本地副本,避免被加锁 307 | :param from_addr: 发送方钱包地址 308 | :return: 309 | """ 310 | unspent_txout_list = list() 311 | spent_txout_list = list() 312 | balance = 0 313 | # Step1:遍历交易池中已经发生过的交易(未打包进区块,未确认) 314 | # 备注:要从最新的交易开始遍历!!!!! 315 | current_transactions = db.get_all_unconfirmed_tx(self.wallet.address) 316 | current_transactions = sorted(current_transactions, key=lambda x: x.timestamp, reverse=False) 317 | for i in range(len(current_transactions)): 318 | unconfirmed_tx = current_transactions[len(current_transactions) - 1 - i] 319 | txid = unconfirmed_tx.txid 320 | 321 | # 遍历当前交易下所有的TxInput 322 | if not unconfirmed_tx.is_coinbase(): 323 | # print 'txid:', txid 324 | # 记录当前tx下被from_addr被使用过的上一次交易的输出,即记录txid和out_idx 325 | for txin in unconfirmed_tx.txins: 326 | if txin.can_unlock_txoutput_with(from_addr): 327 | spent_txid = txin.prev_txid 328 | spent_tx_out_idx = txin.prev_tx_out_idx 329 | spent_txout_list.append((spent_txid, spent_tx_out_idx)) 330 | 331 | # 遍历交易下所有的未使用过的TxOutput 332 | for out_idx in range(len(unconfirmed_tx.txouts)): 333 | txout = unconfirmed_tx.txouts[out_idx] 334 | if not (txid, out_idx) in spent_txout_list: 335 | if txout.can_be_unlocked_with(from_addr): 336 | unspent_txout_list.append((txid, out_idx, txout)) 337 | # -------------------------------------------------- 338 | 339 | # Step2:获取from_addr下可以未使用过的TxOutput(打包在区块,已确认) 340 | block_height = db.get_block_height(self.wallet.address) 341 | 342 | for i in range(block_height): 343 | block = db.get_block_data_by_index(self.wallet.address, block_height - 1 - i) 344 | # 1.获取区块下的所有的交易 345 | transactions = block.get_transactions() 346 | # 备注:要从最新的交易开始遍历!!!!! 347 | for k in range(len(transactions)): 348 | tx = transactions[len(transactions) - 1 - k] 349 | if not self.verify_transaction(tx): # 校验交易是否有效 350 | continue 351 | txid = tx.txid # 当前交易的id 352 | 353 | # 2.遍历某个交易下所有的TxInput 354 | if not tx.is_coinbase(): 355 | # 记录当前tx下被from_addr被使用过的上一次交易的输出,即记录txid和out_idx 356 | for txin in tx.txins: 357 | if txin.can_unlock_txoutput_with(from_addr): 358 | spent_txid = txin.prev_txid 359 | spent_tx_out_idx = txin.prev_tx_out_idx 360 | spent_txout_list.append((spent_txid, spent_tx_out_idx)) 361 | 362 | # 3.遍历某个交易下所有的未使用过的TxOutput 363 | for out_idx in range(len(tx.txouts)): 364 | txout = tx.txouts[out_idx] 365 | if not (txid, out_idx) in spent_txout_list: 366 | if txout.can_be_unlocked_with(from_addr): 367 | unspent_txout_list.append((txid, out_idx, txout)) 368 | 369 | # Step2:计算这些未使用过的TxOutput输出之和 370 | for txid, out_idx, txout in unspent_txout_list: 371 | balance += txout.value 372 | return balance 373 | 374 | def find_spendalbe_outputs(self, from_addr): 375 | """ 376 | 获取from_addr可以用于交易的TxOutput(未使用过的), 377 | :param from_addr: 发送方钱包地址 378 | :return: 379 | """ 380 | unspent_txout_list = list() 381 | spent_txout_list = list() 382 | balance = 0 383 | # Step0:遍历交易池中已经发生过的交易(未打包进区块,未确认) 384 | # 备注:要从最新的交易开始遍历!!!!! 385 | for i in range(len(self.current_transactions)): 386 | unconfirmed_tx = self.current_transactions[len(self.current_transactions) - 1 - i] 387 | txid = unconfirmed_tx.txid 388 | 389 | # 遍历当前交易下所有的TxInput 390 | if not unconfirmed_tx.is_coinbase(): 391 | # print 'txid:', txid 392 | # 记录当前tx下被from_addr被使用过的上一次交易的输出,即记录txid和out_idx 393 | for txin in unconfirmed_tx.txins: 394 | if txin.can_unlock_txoutput_with(from_addr): 395 | spent_txid = txin.prev_txid 396 | spent_tx_out_idx = txin.prev_tx_out_idx 397 | spent_txout_list.append((spent_txid, spent_tx_out_idx)) 398 | 399 | # 遍历交易下所有的未使用过的TxOutput 400 | for out_idx in range(len(unconfirmed_tx.txouts)): 401 | txout = unconfirmed_tx.txouts[out_idx] 402 | if not (txid, out_idx) in spent_txout_list: 403 | if txout.can_be_unlocked_with(from_addr): 404 | unspent_txout_list.append((txid, out_idx, txout)) 405 | # -------------------------------------------------- 406 | 407 | # Step1:获取from_addr下可以未使用过的TxOutput(打包在区块,已确认) 408 | block_height = db.get_block_height(self.wallet.address) 409 | 410 | for i in range(block_height): 411 | block = db.get_block_data_by_index(self.wallet.address, block_height - 1 - i) 412 | # 1.获取区块下的所有的交易 413 | transactions = block.get_transactions() 414 | # 备注:要从最新的交易开始遍历!!!!! 415 | for k in range(len(transactions)): 416 | tx = transactions[len(transactions) - 1 - k] 417 | if not self.verify_transaction(tx): # 校验交易是否有效 418 | print '[Info] invalid tx', tx.txid, 'block index:', i 419 | continue 420 | txid = tx.txid # 当前交易的id 421 | 422 | # 2.遍历某个交易下所有的TxInput 423 | if not tx.is_coinbase(): 424 | # 记录当前tx下被from_addr被使用过的上一次交易的输出,即记录txid和out_idx 425 | for txin in tx.txins: 426 | if txin.can_unlock_txoutput_with(from_addr): 427 | spent_txid = txin.prev_txid 428 | spent_tx_out_idx = txin.prev_tx_out_idx 429 | spent_txout_list.append((spent_txid, spent_tx_out_idx)) 430 | 431 | # 3.遍历某个交易下所有的未使用过的TxOutput 432 | for out_idx in range(len(tx.txouts)): 433 | txout = tx.txouts[out_idx] 434 | if not (txid, out_idx) in spent_txout_list: 435 | if txout.can_be_unlocked_with(from_addr): 436 | unspent_txout_list.append((txid, out_idx, txout)) 437 | 438 | # Step2:计算这些未使用过的TxOutput输出之和 439 | for txid, out_idx, txout in unspent_txout_list: 440 | balance += txout.value 441 | return balance, unspent_txout_list 442 | 443 | def do_mine(self): 444 | """ 445 | 进行挖矿,验证当前节点收集的交易,并将有效交易打包成区块 446 | 447 | :return: 448 | """ 449 | 450 | nonce = 0 451 | timestamp = int(time()) 452 | # print('Minning a block...') 453 | new_block_found = False 454 | new_block_attempt = None 455 | 456 | # 验证每一笔交易的有效性(备注:从最新的开始验证) 457 | for idx in range(len(self.current_transactions)): 458 | tx = self.current_transactions[-1 - idx] 459 | if not self.verify_transaction(tx): 460 | txid = tx.txid 461 | print "[Info] Invalid transaction, remove it, tx:" 462 | raise Error("[Error] do mine:Invalid transaction, remove it. Txid:" + txid) 463 | 464 | if len(self.current_transactions) < 5: 465 | # 至少要有5个以上的交易才可以开始进行挖矿 466 | raise Error("[Error] Not enough valid transactions!") 467 | 468 | # TODO 469 | # 给工作量证明的节点提供奖励 470 | # 发送者为"0" 表明新挖出的币 471 | # coinbase_tx = self.new_coinbase_tx(self.get_wallet_address()) 472 | # valid_transactions.append(coinbase_tx) 473 | self.current_transactions = sorted(self.current_transactions, key=lambda x: x.timestamp, 474 | reverse=False) # 时间由小到大排 475 | 476 | merkletrees = MerkleTrees(self.current_transactions) 477 | merkleroot = merkletrees.get_root_leaf() 478 | 479 | previous_block = self.get_last_block() 480 | next_index = previous_block.index + 1 481 | previous_hash = previous_block.current_hash 482 | 483 | while not new_block_found: 484 | cal_hash = calculate_hash(next_index, previous_hash, timestamp, merkleroot, nonce, self.difficulty) 485 | 486 | if cal_hash[0:self.difficulty] == '0' * self.difficulty: 487 | new_block_attempt = self.generate_block(merkleroot, timestamp, nonce) 488 | end_timestamp = int(time()) 489 | cos_timestamp = end_timestamp - timestamp 490 | print '[Info] New block found with nonce ' + str(nonce) + ' in ' + str( 491 | round(cos_timestamp, 2)) + ' seconds.' 492 | 493 | # 交易按照timestamp从旧到新(小->大) 494 | new_block_attempt.transactions = self.current_transactions 495 | # 将所有交易保存成Merkle树 496 | new_block_attempt.merkleroot = merkleroot 497 | 498 | db.write_to_db(self.wallet.address, new_block_attempt) 499 | self.current_transactions = [] 500 | db.clear_unconfirmed_tx_from_disk(self.wallet.address) 501 | 502 | new_block_found = True 503 | else: 504 | nonce += 1 505 | 506 | return new_block_attempt 507 | 508 | def get_last_block(self): 509 | block_height = db.get_block_height(self.wallet.address) 510 | return db.get_block_data_by_index(self.wallet.address, block_height - 1) 511 | 512 | # def is_same_block(self, block1, block2): 513 | # if block1.index != block2.index: 514 | # return False 515 | # elif block1.previous_hash != block2.previous_hash: 516 | # return False 517 | # elif block1.timestamp != block2.timestamp: 518 | # return False 519 | # elif block1.merkleroot != block2.merkleroot: 520 | # return False 521 | # elif block1.current_hash != block2.current_hash: 522 | # return False 523 | # return True 524 | # 525 | # def __is_valid_new_block(self, new_block, previous_block): 526 | # """ 527 | # 1.校验index是否相邻 528 | # 2.校验hash 529 | # :param new_block: 530 | # :param previous_block: 531 | # :return: 532 | # """ 533 | # new_block_hash = calculate_block_hash(new_block) 534 | # if previous_block.index + 1 != new_block.index: 535 | # print('Indices Do Not Match Up') 536 | # return False 537 | # elif previous_block.current_hash != new_block.previous_hash: 538 | # print('Previous hash does not match') 539 | # return False 540 | # elif new_block_hash != new_block.current_hash: 541 | # print('Hash is invalid') 542 | # return False 543 | # return True 544 | 545 | def set_consensus_chain(self): 546 | # 通过POW机制选取nonce最大的链作为公共链 547 | for block_index in self.candidate_blocks.keys(): 548 | if block_index <= db.get_block_height(self.get_wallet_address()) - 1: 549 | curr_block = db.get_block_data_by_index(self.get_wallet_address(), block_index) 550 | max_nonce_block = curr_block 551 | for candidate_block in self.candidate_blocks[block_index]: 552 | if (candidate_block.previous_hash == curr_block.previous_hash) and ( 553 | candidate_block.nonce > max_nonce_block.nonce): 554 | max_nonce_block = candidate_block 555 | 556 | # 校验每一笔交易 557 | valid_flag = True 558 | for idx in range(len(max_nonce_block.transactions)): 559 | tx = max_nonce_block.transactions[-1 - idx] 560 | if not self.verify_transaction(tx): 561 | valid_flag = False 562 | 563 | if valid_flag and max_nonce_block.current_hash != curr_block.current_hash: 564 | print '[Info] consensusing, replace with new block', max_nonce_block.current_hash 565 | db.write_to_db(self.get_wallet_address(), max_nonce_block) 566 | 567 | def json_output(self): 568 | output = { 569 | 'wallet_address': self.wallet.address, 570 | 'difficulty': self.difficulty, 571 | 'chain': [block.json_output() for block in db.get_all_blocks(self.wallet.address)], 572 | } 573 | return output 574 | -------------------------------------------------------------------------------- /MerkleTrees.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import hashlib 3 | from collections import OrderedDict 4 | 5 | from transaction import Transaction 6 | 7 | 8 | class MerkleTrees(object): 9 | def __init__(self, transaction_list=None): 10 | self.transaction_list = transaction_list 11 | self.transactions = transaction_list 12 | self.transaction_tree = OrderedDict() 13 | self.create_tree() 14 | 15 | def create_tree(self): 16 | transaction_list = self.transaction_list 17 | transaction_tree = self.transaction_tree 18 | temp_transaction = [] 19 | 20 | if len(transaction_list) != 1: 21 | # print transaction_list 22 | 23 | for index in range(0, len(transaction_list), 2): 24 | left_leaf = transaction_list[index] 25 | 26 | if index + 1 != len(transaction_list): 27 | right_leaf = transaction_list[index + 1] 28 | else: 29 | right_leaf = transaction_list[index] 30 | 31 | if isinstance(left_leaf, Transaction): 32 | left_leaf = left_leaf.txid 33 | if isinstance(right_leaf, Transaction): 34 | right_leaf = right_leaf.txid 35 | 36 | left_leaf_hash = hashlib.sha256(left_leaf).hexdigest() # 左边叶子节点的哈希值 37 | right_leaf_hash = hashlib.sha256(right_leaf).hexdigest() # 右边叶子节点的哈希值 38 | 39 | transaction_tree[left_leaf] = left_leaf_hash 40 | transaction_tree[right_leaf] = right_leaf_hash 41 | 42 | temp_transaction.append(left_leaf_hash + right_leaf_hash) 43 | 44 | self.transaction_list = temp_transaction 45 | self.transaction_tree = transaction_tree 46 | self.create_tree() 47 | else: 48 | root_leaf = transaction_list[0] 49 | if isinstance(root_leaf, Transaction): 50 | root_leaf = root_leaf.txid 51 | else: 52 | root_leaf = root_leaf 53 | 54 | root_leaf_hash = hashlib.sha256(root_leaf).hexdigest() 55 | transaction_tree[root_leaf] = root_leaf_hash 56 | self.transaction_tree = transaction_tree 57 | 58 | def get_transaction_tree(self): 59 | return self.transaction_tree 60 | 61 | def get_transaction_list(self): 62 | return self.transactions 63 | 64 | def get_root_leaf(self): 65 | last_key = self.transaction_tree.keys()[-1] 66 | return self.transaction_tree[last_key] 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 迷你区块链(mini blockchain in python) 2 | 3 | ------ 4 | ## 一、介绍 5 | 6 | 该项目主要是通过实践的方法来加深学习和理解区块链的相关原理知识,覆盖区块链的所有核心功能: 7 | > * 网络节点通讯 8 | > * 挖矿 9 | > * 钱包 10 | > * 交易和验证 11 | > * 共识机制 12 | > * 链式结构 13 | 14 | 涉及的如下知识点: 15 | 16 | > * 密码学基础 17 | > * 数字证书 18 | > * Merkle树和SPV机制 19 | > * Kademlia算法、P2P技术 20 | > * 区块链共识机制 21 | > * 比特币交易原理 22 | > * 区块链网络设计 23 | 24 | 参考我写的文章:[区块链学习指南](https://shuwoom.com/?p=916) 25 | 26 | ![miniblockchain1](miniblockchain1.jpg) 27 | ![miniblockchain2](miniblockchain2.jpg) 28 | ![miniblockchain3](miniblockchain3.jpg) 29 | 30 | ## 二、项目组成 31 | 1、迷你区块链客户端 32 | 2、迷你区块链浏览器(独立) 33 | 34 | ## 三、使用方法 35 | 36 | ### **Step1:启动迷你区块链客户端(模拟3个节点)** 37 | 测试demo中一共使用3个节点,大家可以根据需要自己调节。 38 | 39 | python run.py -p 5000 & 40 | 41 | python run.py -p 5001 & 42 | 43 | python run.py -p 5002 & 44 | 45 | ------ 46 | 47 | ### **Step2:模拟交易** 48 | 执行:python simulation_test.py 49 | 50 | 模拟节点之间的交易行为,启动后大家可以看到有交易输出。 51 | 52 | ------ 53 | 54 | ### **Step3:迷你区块链的json api接口调用** 55 | 区块链客户端实现了json api,可以直接通过api调用来获取各个节点之间的信息: 56 | (1)获取区块链高度 57 | 58 | 请求: 59 | 60 | http://127.0.0.1:5001/height 61 | 62 | 返回: 63 | 64 | {"code": 0, "value": 2} 65 | 66 | (2)获取钱包余额 67 | 68 | 请求: 69 | 70 | http://127.0.0.1:5001/balance?address=3Q7KKThJr5qcT7hM189AkVtqYLS8 71 | 72 | 返回: 73 | ``` 74 | { 75 | 76 | "address": "3Q7KKThJr5qcT7hM189AkVtqYLS8", 77 | "balance": 24 78 | } 79 | ``` 80 | 81 | (3)获取区块信息 82 | 83 | 请求: 84 | 85 | http://127.0.0.1:5001/block_info?height=1 86 | 87 | 返回: 88 | ``` 89 | { 90 | "current_hash": "00003b4ff8851ab57625b8d6a432c9288659ce91f52a5b1854b37a34c07ffee8", 91 | "difficulty": 4, 92 | "index": 0, 93 | "merkleroot": "24b486df77c70ba076f92e1ccd078f8073d596c73a29baf0a8f3aea948a54815", 94 | "nonce": 7534, 95 | "previous_hash": "00000000000000000000000000000000000000000000000000000000000000", 96 | "timestamp": 1496518102, 97 | "transactions": [ 98 | { 99 | "amount": 0, 100 | "receivers": [ 101 | { 102 | "receiver": "23DvZ2MzRoGhQk4vSUDcVsvJDEVb", 103 | "value": 100 104 | } 105 | ], 106 | "senders": [], 107 | "timestamp": 1496518102, 108 | "txid": "ab9d943d8c126189f80484a0c8ade846038797968bf90fd1ed9e88f2f46f54fa" 109 | } 110 | ] 111 | } 112 | ``` 113 | (4)创建一笔交易 114 | 请求: 115 | http://127.0.0.1:5000/transactions/new 116 | ``` 117 | { 118 | 'amount': 1, 119 | 'sender': u'23DvZ2MzRoGhQk4vSUDcVsvJDEVb', 120 | 'receiver': u'2dgzLQJgWKhdt3ETnf3tFA9m4ZpK' 121 | } 122 | ``` 123 | 返回: 124 | ``` 125 | { 126 | "message": "new transaction been created successfully!", 127 | "current_transactions": [ 128 | { 129 | "timestamp": 1538373019, 130 | "txouts": [ 131 | { 132 | "value": 30, 133 | "scriptPubKey": [ 134 | "OP_DUP", 135 | "OP_HASH160", 136 | "752f3a7934aa60d041250346e24b1d7a2886dbb0", 137 | "OP_EQUALVERIFY", 138 | "OP_CHECKSIG" 139 | ] 140 | }, 141 | { 142 | "value": 14, 143 | "scriptPubKey": [ 144 | "OP_DUP", 145 | "OP_HASH160", 146 | "ac280f709f81ec4eca086f1abfd0f3bec8baf395", 147 | "OP_EQUALVERIFY", 148 | "OP_CHECKSIG" 149 | ] 150 | } 151 | ], 152 | "txins": [ 153 | { 154 | "prev_tx_out_idx": 1, 155 | "pubkey_hash": "ac280f709f81ec4eca086f1abfd0f3bec8baf395", 156 | "prev_txid": "2f096e332b2371cd141d1f279c35fb820e3e5256c1a071c5a49636c2fcd9be3d", 157 | "signature": "e751d626ef0c147455ef0487b85f108ba56be57575f5133341e5e638dc697f94" 158 | } 159 | ], 160 | "txid": "8851e30bd03cd81b396df1904a17a87b89a30092a92572f7a66fce7a25a634c4" 161 | }, 162 | { 163 | "timestamp": 1538373058, 164 | "txouts": [ 165 | { 166 | "value": 9, 167 | "scriptPubKey": [ 168 | "OP_DUP", 169 | "OP_HASH160", 170 | "752f3a7934aa60d041250346e24b1d7a2886dbb0", 171 | "OP_EQUALVERIFY", 172 | "OP_CHECKSIG" 173 | ] 174 | }, 175 | { 176 | "value": 46, 177 | "scriptPubKey": [ 178 | "OP_DUP", 179 | "OP_HASH160", 180 | "4a879c658a42fc162f8cb3c740a4e0c4b63e80de", 181 | "OP_EQUALVERIFY", 182 | "OP_CHECKSIG" 183 | ] 184 | } 185 | ], 186 | "txins": [ 187 | { 188 | "prev_tx_out_idx": 0, 189 | "pubkey_hash": "4a879c658a42fc162f8cb3c740a4e0c4b63e80de", 190 | "prev_txid": "2f096e332b2371cd141d1f279c35fb820e3e5256c1a071c5a49636c2fcd9be3d", 191 | "signature": "0ff0b888d0dd468f1c6e37d1ab13d17f63747346dc0105b5e4974be71023079f" 192 | }, 193 | { 194 | "prev_tx_out_idx": 0, 195 | "pubkey_hash": "4a879c658a42fc162f8cb3c740a4e0c4b63e80de", 196 | "prev_txid": "72fb55e194e4d7dc71ab020483c61737c6a5b2e2474480cac4187f0633961423", 197 | "signature": "de72585b9c7c6a47d1adc2851244232581cc5f3e0a4633d754736677bdea0a57" 198 | }, 199 | { 200 | "prev_tx_out_idx": 1, 201 | "pubkey_hash": "4a879c658a42fc162f8cb3c740a4e0c4b63e80de", 202 | "prev_txid": "3bc92a17262ce2cc1fe7f34ef946fd10e0b9667f223b697bfaf520e5f443be11", 203 | "signature": "2871832295c53372ee44d9950676cb4675506676a9dce04db54e48b72b40a2b1" 204 | } 205 | ], 206 | "txid": "252d4e493ff514e87f3c00d9adcb635aba5f7c3b7890b7ccc1e17d73c23d1bf4" 207 | }, 208 | { 209 | "timestamp": 1538373093, 210 | "txouts": [ 211 | { 212 | "value": 10, 213 | "scriptPubKey": [ 214 | "OP_DUP", 215 | "OP_HASH160", 216 | "ac280f709f81ec4eca086f1abfd0f3bec8baf395", 217 | "OP_EQUALVERIFY", 218 | "OP_CHECKSIG" 219 | ] 220 | }, 221 | { 222 | "value": 36, 223 | "scriptPubKey": [ 224 | "OP_DUP", 225 | "OP_HASH160", 226 | "4a879c658a42fc162f8cb3c740a4e0c4b63e80de", 227 | "OP_EQUALVERIFY", 228 | "OP_CHECKSIG" 229 | ] 230 | } 231 | ], 232 | "txins": [ 233 | { 234 | "prev_tx_out_idx": 1, 235 | "pubkey_hash": "4a879c658a42fc162f8cb3c740a4e0c4b63e80de", 236 | "prev_txid": "252d4e493ff514e87f3c00d9adcb635aba5f7c3b7890b7ccc1e17d73c23d1bf4", 237 | "signature": "fc33dea5c178d38c4d98623e7a0fe881b9c6268088788b34c81b4b49a1cbe385" 238 | } 239 | ], 240 | "txid": "31cabe8dd4c9b7b03b9159409bc1929acbcefe361099bad5839c17cce497af8e" 241 | }, 242 | { 243 | "timestamp": 1538373570, 244 | "txouts": [ 245 | { 246 | "value": 1, 247 | "scriptPubKey": [ 248 | "OP_DUP", 249 | "OP_HASH160", 250 | "752f3a7934aa60d041250346e24b1d7a2886dbb0", 251 | "OP_EQUALVERIFY", 252 | "OP_CHECKSIG" 253 | ] 254 | }, 255 | { 256 | "value": 35, 257 | "scriptPubKey": [ 258 | "OP_DUP", 259 | "OP_HASH160", 260 | "4a879c658a42fc162f8cb3c740a4e0c4b63e80de", 261 | "OP_EQUALVERIFY", 262 | "OP_CHECKSIG" 263 | ] 264 | } 265 | ], 266 | "txins": [ 267 | { 268 | "prev_tx_out_idx": 1, 269 | "pubkey_hash": "4a879c658a42fc162f8cb3c740a4e0c4b63e80de", 270 | "prev_txid": "31cabe8dd4c9b7b03b9159409bc1929acbcefe361099bad5839c17cce497af8e", 271 | "signature": "adf76d1f32fb122fe2551b3e3059d64d9d11787f58cea37ec8e7ff1b3b540dd7" 272 | } 273 | ], 274 | "txid": "5bc1e91e8156710706934ee109976a71bdbac04b0d83e081844decb2a4d6ed15" 275 | } 276 | ] 277 | } 278 | 279 | ------ 280 | 281 | ``` 282 | ### **Step4:迷你区块链浏览器** 283 | 当然,如果不想用接口来查看去快练心性,可以通过区块链浏览器来查看,只需将mini_blockchain下的browser文件夹移到nginx的www目录下,打开如下网址即可: 284 | http://127.0.0.1/browser 285 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanchao/mini_blockchain/92ebc240e933d944ac06f3fcd06f15c82de06d9f/__init__.py -------------------------------------------------------------------------------- /blockchain.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from binascii import hexlify, Error 3 | from time import time 4 | 5 | import rsa 6 | 7 | import db 8 | import wallet 9 | from Block import Block 10 | from MerkleTrees import MerkleTrees 11 | from transaction import * 12 | from util import * 13 | 14 | try: 15 | from urllib.parse import urlparse 16 | except ImportError: 17 | from urlparse import urlparse 18 | 19 | 20 | class Blockchain(object): 21 | def __init__(self, genisus_node=False): 22 | """ 23 | 24 | :param genisus_node: 判断是否是创世节点,如果是则读取本地(genisus_public.pem和genisus_private.pem)密钥对, 25 | 创始区块的第一笔交易就是奖励给genisus_public.pem 26 | """ 27 | self.difficulty = 4 28 | self.current_transactions = [] 29 | self.wallet = wallet.Wallet(genisus_node) 30 | genius_block = self.get_genius_block() # 创世区块 31 | db.write_to_db(self.wallet.address, genius_block) 32 | self.candidate_blocks = {} 33 | 34 | def get_genisus_pubkey(self): 35 | """ 36 | 获取创世节点的公钥。对象 37 | :return: 38 | """ 39 | with open('genisus_public.pem', 'r') as f: 40 | pubkey = rsa.PublicKey.load_pkcs1(f.read().encode()) 41 | return pubkey 42 | 43 | def get_genius_block(self): 44 | """ 45 | 创建创始区块 46 | :return: 返回对象 47 | """ 48 | txin = TxInput(None, -1, None, None) 49 | pubkey_hash = Script.sha160(str(self.get_genisus_pubkey())) 50 | txoutput = TxOutput(100, pubkey_hash) 51 | coinbase_tx = Transaction([txin], [txoutput], 1496518102) # 创世区块的第一笔交易 52 | transactions = [coinbase_tx] 53 | 54 | merkletrees = MerkleTrees(transactions) 55 | merkleroot = merkletrees.get_root_leaf() 56 | nonce = 0 57 | 58 | genish_block_hash = calculate_hash(index=0, 59 | previous_hash='00000000000000000000000000000000000000000000000000000000000000', 60 | timestamp=1496518102, 61 | merkleroot=merkleroot, 62 | nonce=nonce, 63 | difficulty=self.difficulty) 64 | while genish_block_hash[0:self.difficulty] != '0' * self.difficulty: 65 | genish_block_hash = calculate_hash(index=0, 66 | previous_hash='00000000000000000000000000000000000000000000000000000000000000', 67 | timestamp=1496518102, 68 | merkleroot=merkleroot, 69 | nonce=nonce, 70 | difficulty=self.difficulty) 71 | nonce += 1 72 | genius_block = Block(index=0, 73 | previous_hash='00000000000000000000000000000000000000000000000000000000000000', 74 | timestamp=1496518102, 75 | nonce=nonce, 76 | current_hash=genish_block_hash, 77 | difficulty=self.difficulty) 78 | genius_block.merkleroot = merkleroot 79 | genius_block.transactions = transactions 80 | 81 | return genius_block 82 | 83 | def new_coinbase_tx(self, address): 84 | """ 85 | 创世块,区块的第一笔交易,用于奖励矿工 86 | :param address: 钱包接收地址 87 | :return: 对象 88 | """ 89 | txin = TxInput(None, -1, None, None) 90 | pubkey_hash = hexlify(Wallet.b58decode(address)).decode('utf8') 91 | txoutput = TxOutput(10, pubkey_hash) 92 | tx = Transaction([txin], [txoutput], int(time())) 93 | return tx 94 | 95 | def generate_block(self, merkleroot, next_timestamp, next_nonce): 96 | """ 97 | 创建区块 98 | :param merkleroot: 默克尔树根节点 99 | :param next_timestamp: 100 | :param next_nonce: 101 | :return: 102 | """ 103 | previous_block = self.get_last_block() 104 | next_index = previous_block.index + 1 105 | previous_hash = previous_block.current_hash 106 | 107 | next_block = Block( 108 | index=next_index, 109 | previous_hash=previous_hash, 110 | timestamp=next_timestamp, 111 | nonce=next_nonce, 112 | current_hash=calculate_hash(next_index, previous_hash, next_timestamp, merkleroot, next_nonce, 113 | self.difficulty), 114 | difficulty=self.difficulty 115 | ) 116 | next_block.merkleroot = merkleroot 117 | 118 | return next_block 119 | 120 | def trimmed_copy_tx(self, tx): 121 | """ 122 | 被修剪后的带待签名副本,该副本包含交易中的所有输入和输出, 123 | 但是TxInput.signature和TxInput.pubkey被设置未None 124 | :param tx: 对象,待修剪、拷贝的交易 125 | :return: 126 | """ 127 | inputs = list() 128 | outputs = list() 129 | 130 | for txin in tx.txins: 131 | inputs.append(TxInput(txin.prev_txid, txin.prev_tx_out_idx, None, None)) 132 | 133 | for txout in tx.txouts: 134 | outputs.append(TxOutput(txout.value, txout.pubkey_hash)) 135 | 136 | return Transaction(inputs, outputs, tx.timestamp) 137 | 138 | def sign(self, tx, privkey, prev_txid_2_tx): 139 | """ 140 | 用私钥对交易tx签名 141 | :param tx: 对象,待签名对象 142 | :param privkey: 对象,私钥 143 | :param prev_txid_2_tx: ,txid与tx的映射 144 | :return: 145 | """ 146 | if tx.is_coinbase(): 147 | return 148 | 149 | tx_copy = self.trimmed_copy_tx(tx) # 每个输入的signature和pubkey设置为None 150 | 151 | signature_flag = dict() 152 | for in_idx in range(len(tx_copy.txins)): 153 | txin = tx_copy.txins[-1 - in_idx] 154 | prev_tx = prev_txid_2_tx[txin.prev_txid] 155 | 156 | txin.signature = None 157 | txin.pubkey = prev_tx.txouts[txin.prev_tx_out_idx].pubkey_hash 158 | data = tx_copy.get_hash() # 待签名数据 159 | txin.pubkey = None 160 | 161 | signature = rsa.sign(data.encode(), privkey, 'SHA-256') 162 | signature_flag[in_idx] = signature 163 | 164 | for in_idx in range(len(tx_copy.txins)): 165 | if in_idx in signature_flag: 166 | tx.txins[in_idx].signature = signature_flag[in_idx] 167 | 168 | def sign_transaction(self, tx, privkey): 169 | """ 170 | 171 | :param tx: 对象,待签名对象 172 | :param privkey: 对象,私钥 173 | :return: 174 | """ 175 | 176 | prev_txid_2_tx = dict() 177 | for txin in tx.txins: 178 | prev_txid_2_tx[txin.prev_txid] = self.find_transaction(txin.prev_txid) 179 | self.sign(tx, privkey, prev_txid_2_tx) 180 | 181 | def verify_transaction(self, tx): 182 | """ 183 | 验证一笔交易 184 | 185 | :param tx: 对象 186 | :return: 187 | """ 188 | 189 | prev_txid_2_tx = dict() 190 | for txin in tx.txins: 191 | prev_txid_2_tx[txin.prev_txid] = self.find_transaction(txin.prev_txid) 192 | 193 | return self.verify(tx, prev_txid_2_tx) 194 | 195 | def verify(self, tx, prev_txid_2_tx): 196 | """ 197 | 验证一笔交易 198 | rsa.verify(data.encode(), signature, pubkey) 199 | :param tx: 对象 200 | :param prev_txid_2_tx: ,txid与tx的映射 201 | :return: 202 | """ 203 | if tx.is_coinbase(): 204 | return True 205 | tx_copy = self.trimmed_copy_tx(tx) # 每个输入的signature和pubkey设置未None 206 | for in_idx in range(len(tx_copy.txins)): 207 | # 校验每一个输入的有效性(即检查解锁脚本是否有效) 208 | txin = tx_copy.txins[-1 - in_idx] 209 | prev_tx = prev_txid_2_tx[txin.prev_txid] 210 | if not prev_tx: 211 | # 区块不存在 212 | return False 213 | txin.signature = None 214 | txin.pubkey = prev_tx.txouts[txin.prev_tx_out_idx].pubkey_hash 215 | data = tx_copy.get_hash() # 待签名数据 216 | 217 | txin.pubkey = None 218 | 219 | scriptSig = [tx.txins[in_idx].signature, tx.txins[in_idx].pubkey] # 解锁脚本 220 | scriptPubKey = prev_tx.txouts[txin.prev_tx_out_idx].get_scriptPubKey() # 锁定脚本 221 | 222 | if not Script.check_tx_script(data, scriptSig, scriptPubKey): 223 | return False 224 | 225 | return True 226 | 227 | def find_transaction(self, txid): 228 | """ 229 | 通过交易id找到一笔Tx交易 230 | :param txid: 交易id 231 | :return: 232 | """ 233 | # 在区块中寻找(已确认的交易) 234 | block_height = db.get_block_height(self.wallet.address) 235 | 236 | for index in range(block_height): 237 | block = db.get_block_data_by_index(self.wallet.address, index) 238 | # 1.获取区块下的所有的交易 239 | transactions = block.get_transactions() 240 | for tx in transactions: 241 | if tx.txid == txid: 242 | return tx 243 | 244 | # 在交易池中寻找(未确认的交易)TODO待确认 245 | for k in range(len(self.current_transactions)): 246 | uncomfirmed_tx = self.current_transactions[-1 - k] 247 | if uncomfirmed_tx.txid == txid: 248 | return uncomfirmed_tx 249 | return None 250 | 251 | def get_balance(self, address): 252 | """ 253 | 获取address地址的余额 254 | :param address: 钱包地址 255 | :return: 256 | """ 257 | balance, _ = self.find_spendalbe_outputs(address) 258 | return balance 259 | 260 | def get_wallet_address(self): 261 | return self.wallet.address 262 | 263 | def new_utxo_transaction(self, from_addr, to_addr, amount): 264 | """ 265 | from_addr向to_addr发送amount量的货币,步骤: 266 | Step1:首先从区块中获取from_addr下未使用过的TxOutput输出(未使用的UTXO) 267 | Step2:获取上一步TxOutput列表中value之和sum,并与amount相比 268 | Step3:如果sum=amount,则from_addr足够余额用于交易,多出的部分用于找零 269 | Step4:构造Transaction对象,并传入此次交易的输入和输出 270 | Step5:输入检查交易的有效性,输出设置锁定脚本 271 | Step6:广播 272 | 273 | :param from_addr: ,发送方钱包地址 274 | :param to_addr: ,接收方钱包地址 275 | :param amount: ,金额 276 | :return: 277 | """ 278 | inputs = list() 279 | outputs = list() 280 | balance, unspent_txout_list = self.find_spendalbe_outputs(from_addr) 281 | if balance < amount: 282 | print 'not enough money!' 283 | return None 284 | 285 | # 构造Tx的输入 286 | for txid, out_idx, txout in unspent_txout_list: 287 | txin = TxInput(txid, out_idx, None, self.wallet.pubkey) 288 | inputs.append(txin) 289 | 290 | # 构造Tx的输出 291 | txout = TxOutput(amount, hexlify(Wallet.b58decode(to_addr)).decode('utf8')) 292 | outputs.append(txout) 293 | if balance > amount: 294 | # 找零 295 | outputs.append(TxOutput(balance - amount, Script.sha160(str(self.wallet.pubkey)))) 296 | 297 | tx = Transaction(inputs, outputs, int(time())) 298 | self.sign_transaction(tx, self.wallet.privkey) # 签名Tx 299 | 300 | self.current_transactions.append(tx) 301 | db.write_unconfirmed_tx_to_db(self.wallet.address, tx) 302 | return tx 303 | 304 | def get_balance_by_db(self, from_addr): 305 | """ 306 | 获取from_addr可以用于交易的TxOutput(未使用过的),读取交易池和区块链的本地副本,避免被加锁 307 | :param from_addr: 发送方钱包地址 308 | :return: 309 | """ 310 | unspent_txout_list = list() 311 | spent_txout_list = list() 312 | balance = 0 313 | # Step1:遍历交易池中已经发生过的交易(未打包进区块,未确认) 314 | # 备注:要从最新的交易开始遍历!!!!! 315 | current_transactions = db.get_all_unconfirmed_tx(self.wallet.address) 316 | current_transactions = sorted(current_transactions, key=lambda x: x.timestamp, reverse=False) 317 | for i in range(len(current_transactions)): 318 | unconfirmed_tx = current_transactions[len(current_transactions) - 1 - i] 319 | txid = unconfirmed_tx.txid 320 | 321 | # 遍历当前交易下所有的TxInput 322 | if not unconfirmed_tx.is_coinbase(): 323 | # print 'txid:', txid 324 | # 记录当前tx下被from_addr被使用过的上一次交易的输出,即记录txid和out_idx 325 | for txin in unconfirmed_tx.txins: 326 | if txin.can_unlock_txoutput_with(from_addr): 327 | spent_txid = txin.prev_txid 328 | spent_tx_out_idx = txin.prev_tx_out_idx 329 | spent_txout_list.append((spent_txid, spent_tx_out_idx)) 330 | 331 | # 遍历交易下所有的未使用过的TxOutput 332 | for out_idx in range(len(unconfirmed_tx.txouts)): 333 | txout = unconfirmed_tx.txouts[out_idx] 334 | if not (txid, out_idx) in spent_txout_list: 335 | if txout.can_be_unlocked_with(from_addr): 336 | unspent_txout_list.append((txid, out_idx, txout)) 337 | # -------------------------------------------------- 338 | 339 | # Step2:获取from_addr下可以未使用过的TxOutput(打包在区块,已确认) 340 | block_height = db.get_block_height(self.wallet.address) 341 | 342 | for i in range(block_height): 343 | block = db.get_block_data_by_index(self.wallet.address, block_height - 1 - i) 344 | # 1.获取区块下的所有的交易 345 | transactions = block.get_transactions() 346 | # 备注:要从最新的交易开始遍历!!!!! 347 | for k in range(len(transactions)): 348 | tx = transactions[len(transactions) - 1 - k] 349 | if not self.verify_transaction(tx): # 校验交易是否有效 350 | continue 351 | txid = tx.txid # 当前交易的id 352 | 353 | # 2.遍历某个交易下所有的TxInput 354 | if not tx.is_coinbase(): 355 | # 记录当前tx下被from_addr被使用过的上一次交易的输出,即记录txid和out_idx 356 | for txin in tx.txins: 357 | if txin.can_unlock_txoutput_with(from_addr): 358 | spent_txid = txin.prev_txid 359 | spent_tx_out_idx = txin.prev_tx_out_idx 360 | spent_txout_list.append((spent_txid, spent_tx_out_idx)) 361 | 362 | # 3.遍历某个交易下所有的未使用过的TxOutput 363 | for out_idx in range(len(tx.txouts)): 364 | txout = tx.txouts[out_idx] 365 | if not (txid, out_idx) in spent_txout_list: 366 | if txout.can_be_unlocked_with(from_addr): 367 | unspent_txout_list.append((txid, out_idx, txout)) 368 | 369 | # Step2:计算这些未使用过的TxOutput输出之和 370 | for txid, out_idx, txout in unspent_txout_list: 371 | balance += txout.value 372 | return balance 373 | 374 | def find_spendalbe_outputs(self, from_addr): 375 | """ 376 | 获取from_addr可以用于交易的TxOutput(未使用过的), 377 | :param from_addr: 发送方钱包地址 378 | :return: 379 | """ 380 | unspent_txout_list = list() 381 | spent_txout_list = list() 382 | balance = 0 383 | # Step0:遍历交易池中已经发生过的交易(未打包进区块,未确认) 384 | # 备注:要从最新的交易开始遍历!!!!! 385 | for i in range(len(self.current_transactions)): 386 | unconfirmed_tx = self.current_transactions[len(self.current_transactions) - 1 - i] 387 | txid = unconfirmed_tx.txid 388 | 389 | # 遍历当前交易下所有的TxInput 390 | if not unconfirmed_tx.is_coinbase(): 391 | # print 'txid:', txid 392 | # 记录当前tx下被from_addr被使用过的上一次交易的输出,即记录txid和out_idx 393 | for txin in unconfirmed_tx.txins: 394 | if txin.can_unlock_txoutput_with(from_addr): 395 | spent_txid = txin.prev_txid 396 | spent_tx_out_idx = txin.prev_tx_out_idx 397 | spent_txout_list.append((spent_txid, spent_tx_out_idx)) 398 | 399 | # 遍历交易下所有的未使用过的TxOutput 400 | for out_idx in range(len(unconfirmed_tx.txouts)): 401 | txout = unconfirmed_tx.txouts[out_idx] 402 | if not (txid, out_idx) in spent_txout_list: 403 | if txout.can_be_unlocked_with(from_addr): 404 | unspent_txout_list.append((txid, out_idx, txout)) 405 | # -------------------------------------------------- 406 | 407 | # Step1:获取from_addr下可以未使用过的TxOutput(打包在区块,已确认) 408 | block_height = db.get_block_height(self.wallet.address) 409 | 410 | for i in range(block_height): 411 | block = db.get_block_data_by_index(self.wallet.address, block_height - 1 - i) 412 | # 1.获取区块下的所有的交易 413 | transactions = block.get_transactions() 414 | # 备注:要从最新的交易开始遍历!!!!! 415 | for k in range(len(transactions)): 416 | tx = transactions[len(transactions) - 1 - k] 417 | if not self.verify_transaction(tx): # 校验交易是否有效 418 | print '[Info] invalid tx', tx.txid, 'block index:', i 419 | continue 420 | txid = tx.txid # 当前交易的id 421 | 422 | # 2.遍历某个交易下所有的TxInput 423 | if not tx.is_coinbase(): 424 | # 记录当前tx下被from_addr被使用过的上一次交易的输出,即记录txid和out_idx 425 | for txin in tx.txins: 426 | if txin.can_unlock_txoutput_with(from_addr): 427 | spent_txid = txin.prev_txid 428 | spent_tx_out_idx = txin.prev_tx_out_idx 429 | spent_txout_list.append((spent_txid, spent_tx_out_idx)) 430 | 431 | # 3.遍历某个交易下所有的未使用过的TxOutput 432 | for out_idx in range(len(tx.txouts)): 433 | txout = tx.txouts[out_idx] 434 | if not (txid, out_idx) in spent_txout_list: 435 | if txout.can_be_unlocked_with(from_addr): 436 | unspent_txout_list.append((txid, out_idx, txout)) 437 | 438 | # Step2:计算这些未使用过的TxOutput输出之和 439 | for txid, out_idx, txout in unspent_txout_list: 440 | balance += txout.value 441 | return balance, unspent_txout_list 442 | 443 | def do_mine(self): 444 | """ 445 | 进行挖矿,验证当前节点收集的交易,并将有效交易打包成区块 446 | 447 | :return: 448 | """ 449 | 450 | nonce = 0 451 | timestamp = int(time()) 452 | # print('Minning a block...') 453 | new_block_found = False 454 | new_block_attempt = None 455 | 456 | # 验证每一笔交易的有效性(备注:从最新的开始验证) 457 | for idx in range(len(self.current_transactions)): 458 | tx = self.current_transactions[-1 - idx] 459 | if not self.verify_transaction(tx): 460 | txid = tx.txid 461 | print "[Info] Invalid transaction, remove it, tx:" 462 | raise Error("[Error] do mine:Invalid transaction, remove it. Txid:" + txid) 463 | 464 | if len(self.current_transactions) < 5: 465 | # 至少要有5个以上的交易才可以开始进行挖矿 466 | raise Error("[Error] Not enough valid transactions!") 467 | 468 | # TODO 469 | # 给工作量证明的节点提供奖励 470 | # 发送者为"0" 表明新挖出的币 471 | # coinbase_tx = self.new_coinbase_tx(self.get_wallet_address()) 472 | # valid_transactions.append(coinbase_tx) 473 | self.current_transactions = sorted(self.current_transactions, key=lambda x: x.timestamp, 474 | reverse=False) # 时间由小到大排 475 | 476 | merkletrees = MerkleTrees(self.current_transactions) 477 | merkleroot = merkletrees.get_root_leaf() 478 | 479 | previous_block = self.get_last_block() 480 | next_index = previous_block.index + 1 481 | previous_hash = previous_block.current_hash 482 | 483 | while not new_block_found: 484 | cal_hash = calculate_hash(next_index, previous_hash, timestamp, merkleroot, nonce, self.difficulty) 485 | 486 | if cal_hash[0:self.difficulty] == '0' * self.difficulty: 487 | new_block_attempt = self.generate_block(merkleroot, timestamp, nonce) 488 | end_timestamp = int(time()) 489 | cos_timestamp = end_timestamp - timestamp 490 | print '[Info] New block found with nonce ' + str(nonce) + ' in ' + str( 491 | round(cos_timestamp, 2)) + ' seconds.' 492 | 493 | # 交易按照timestamp从旧到新(小->大) 494 | new_block_attempt.transactions = self.current_transactions 495 | # 将所有交易保存成Merkle树 496 | new_block_attempt.merkleroot = merkleroot 497 | 498 | db.write_to_db(self.wallet.address, new_block_attempt) 499 | self.current_transactions = [] 500 | db.clear_unconfirmed_tx_from_disk(self.wallet.address) 501 | 502 | new_block_found = True 503 | else: 504 | nonce += 1 505 | 506 | return new_block_attempt 507 | 508 | def get_last_block(self): 509 | block_height = db.get_block_height(self.wallet.address) 510 | return db.get_block_data_by_index(self.wallet.address, block_height - 1) 511 | 512 | # def is_same_block(self, block1, block2): 513 | # if block1.index != block2.index: 514 | # return False 515 | # elif block1.previous_hash != block2.previous_hash: 516 | # return False 517 | # elif block1.timestamp != block2.timestamp: 518 | # return False 519 | # elif block1.merkleroot != block2.merkleroot: 520 | # return False 521 | # elif block1.current_hash != block2.current_hash: 522 | # return False 523 | # return True 524 | # 525 | # def __is_valid_new_block(self, new_block, previous_block): 526 | # """ 527 | # 1.校验index是否相邻 528 | # 2.校验hash 529 | # :param new_block: 530 | # :param previous_block: 531 | # :return: 532 | # """ 533 | # new_block_hash = calculate_block_hash(new_block) 534 | # if previous_block.index + 1 != new_block.index: 535 | # print('Indices Do Not Match Up') 536 | # return False 537 | # elif previous_block.current_hash != new_block.previous_hash: 538 | # print('Previous hash does not match') 539 | # return False 540 | # elif new_block_hash != new_block.current_hash: 541 | # print('Hash is invalid') 542 | # return False 543 | # return True 544 | 545 | def set_consensus_chain(self): 546 | # 通过POW机制选取nonce最大的链作为公共链 547 | for block_index in self.candidate_blocks.keys(): 548 | if block_index <= db.get_block_height(self.get_wallet_address()) - 1: 549 | curr_block = db.get_block_data_by_index(self.get_wallet_address(), block_index) 550 | max_nonce_block = curr_block 551 | for candidate_block in self.candidate_blocks[block_index]: 552 | if (candidate_block.previous_hash == curr_block.previous_hash) and ( 553 | candidate_block.nonce > max_nonce_block.nonce): 554 | max_nonce_block = candidate_block 555 | 556 | # 校验每一笔交易 557 | valid_flag = True 558 | for idx in range(len(max_nonce_block.transactions)): 559 | tx = max_nonce_block.transactions[-1 - idx] 560 | if not self.verify_transaction(tx): 561 | valid_flag = False 562 | 563 | if valid_flag and max_nonce_block.current_hash != curr_block.current_hash: 564 | print '[Info] consensusing, replace with new block', max_nonce_block.current_hash 565 | db.write_to_db(self.get_wallet_address(), max_nonce_block) 566 | 567 | def json_output(self): 568 | output = { 569 | 'wallet_address': self.wallet.address, 570 | 'difficulty': self.difficulty, 571 | 'chain': [block.json_output() for block in db.get_all_blocks(self.wallet.address)], 572 | } 573 | return output 574 | -------------------------------------------------------------------------------- /db.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import ConfigParser 3 | import json 4 | import os 5 | import pickle 6 | from time import time 7 | 8 | 9 | def get_block_height(wallet_address): 10 | cf = ConfigParser.ConfigParser() 11 | cf.read(wallet_address + '/miniblockchain.conf') 12 | block_height = 1 13 | try: 14 | block_height = cf.get('meta', 'block_height') 15 | except ConfigParser.NoSectionError as e: 16 | pass 17 | return int(block_height) 18 | 19 | 20 | def write_unconfirmed_tx_to_db(wallet_address, tx): 21 | if not os.path.isdir(wallet_address): 22 | os.mkdir(wallet_address) 23 | if not os.path.isdir(wallet_address + '/unconfirmed_tx'): 24 | os.mkdir(wallet_address + '/unconfirmed_tx') 25 | 26 | # 写入到blockheader.db中 27 | cf = ConfigParser.ConfigParser() 28 | cf.read(wallet_address + '/miniblockchain.conf') 29 | 30 | unconfirmed_tx_counts = 0 31 | try: 32 | unconfirmed_tx_counts = int(cf.get('meta', 'unconfirmed_tx_counts')) 33 | except ConfigParser.NoOptionError as e: 34 | cf.set('meta', 'unconfirmed_tx_counts', unconfirmed_tx_counts) 35 | 36 | try: 37 | cf.get('unconfirmed_tx_index', str(unconfirmed_tx_counts)) 38 | except ConfigParser.NoSectionError as e: 39 | cf.add_section('unconfirmed_tx_index') 40 | except ConfigParser.NoOptionError as e: 41 | pass 42 | 43 | cf.set('unconfirmed_tx_index', str(unconfirmed_tx_counts), str(tx.txid)) 44 | cf.set('meta', 'unconfirmed_tx_counts', 1 + int(unconfirmed_tx_counts)) 45 | 46 | with open(wallet_address + '/miniblockchain.conf', 'w+') as f: 47 | cf.write(f) 48 | 49 | with open(wallet_address + '/unconfirmed_tx/' + tx.txid, 'wb') as f: 50 | pickle.dump(tx, f) 51 | 52 | 53 | def get_all_unconfirmed_tx(wallet_address): 54 | cf = ConfigParser.ConfigParser() 55 | cf.read(wallet_address + '/miniblockchain.conf') 56 | 57 | tx_list = list() 58 | unconfirmed_tx_counts = 0 59 | try: 60 | unconfirmed_tx_counts = int(cf.get('meta', 'unconfirmed_tx_counts')) 61 | except ConfigParser.NoSectionError as e: 62 | pass 63 | except ConfigParser.NoOptionError as e: 64 | pass 65 | 66 | for index in range(unconfirmed_tx_counts): 67 | txid = cf.get('unconfirmed_tx_index', str(index)) 68 | tx = get_tx_by_txid(wallet_address, txid) 69 | tx_list.append(tx) 70 | 71 | return tx_list 72 | 73 | 74 | def get_tx_by_txid(wallet_address, txid): 75 | with open(wallet_address + '/unconfirmed_tx/' + txid, 'rb') as f: 76 | obj = pickle.load(f) 77 | return obj 78 | 79 | 80 | def clear_unconfirmed_tx_from_disk(wallet_address): 81 | cf = ConfigParser.ConfigParser() 82 | cf.read(wallet_address + '/miniblockchain.conf') 83 | 84 | unconfirmed_tx_counts = 0 85 | try: 86 | unconfirmed_tx_counts = int(cf.get('meta', 'unconfirmed_tx_counts')) 87 | except ConfigParser.NoSectionError as e: 88 | pass 89 | 90 | if unconfirmed_tx_counts == 0: 91 | return 92 | 93 | for index in range(unconfirmed_tx_counts): 94 | txid = cf.get('unconfirmed_tx_index', str(index)) 95 | os.remove(wallet_address + '/unconfirmed_tx/' + txid) 96 | 97 | cf.remove_option('meta', 'unconfirmed_tx_counts') 98 | cf.remove_section('unconfirmed_tx_index') 99 | 100 | with open(wallet_address + '/miniblockchain.conf', 'w+') as f: 101 | cf.write(f) 102 | 103 | 104 | def write_to_db(wallet_address, block): 105 | if not os.path.isdir(wallet_address): 106 | os.mkdir(wallet_address) 107 | cf = ConfigParser.ConfigParser() 108 | cf.read(wallet_address + '/miniblockchain.conf') 109 | 110 | block_height = 1 111 | try: 112 | block_height = cf.get('meta', 'block_height') 113 | except ConfigParser.NoSectionError as e: 114 | cf.add_section('meta') 115 | cf.set('meta', 'block_height', block_height) 116 | 117 | try: 118 | cf.get('index', str(block.index)) 119 | except ConfigParser.NoSectionError as e: 120 | cf.add_section('index') 121 | except ConfigParser.NoOptionError as e: 122 | cf.set('meta', 'block_height', 1 + int(block_height)) 123 | 124 | cf.set('index', str(block.index), str(block.current_hash)) 125 | 126 | with open(wallet_address + '/miniblockchain.conf', 'w+') as f: 127 | cf.write(f) 128 | 129 | with open(wallet_address + '/' + block.current_hash, 'wb') as f: 130 | pickle.dump(block, f) 131 | 132 | 133 | def get_block_hash(wallet_address, index): 134 | cf = ConfigParser.ConfigParser() 135 | cf.read(wallet_address + '/miniblockchain.conf') 136 | 137 | block_hash = None 138 | try: 139 | block_hash = cf.get('index', str(index)) 140 | except ConfigParser.NoSectionError as e: 141 | pass 142 | finally: 143 | return block_hash 144 | 145 | 146 | def get_block_data_by_hash(wallet_address, block_hash): 147 | with open(wallet_address + '/' + block_hash, 'rb') as f: 148 | obj = pickle.load(f) 149 | return obj 150 | 151 | 152 | def get_block_data_by_index(wallet_address, index): 153 | block_hash = get_block_hash(wallet_address, index) 154 | return get_block_data_by_hash(wallet_address, block_hash) 155 | 156 | 157 | def get_all_blocks(wallet_address): 158 | chain = list() 159 | block_counts = get_block_height(wallet_address) 160 | for index in range(block_counts): 161 | block_hash = get_block_hash(wallet_address, index) 162 | chain.append(get_block_data_by_hash(wallet_address, block_hash)) 163 | return chain 164 | -------------------------------------------------------------------------------- /genisus_private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICYAIBAAKBgQCSIhrlDpYShUBByK+WF2vbmozPrCORRGf/ESXUeRDguFqPvW2X 3 | 8q757j0QbSuzyc1Fx3VwmjaIT8wOSLohKZKBCz8lnwqM0BfRQICthsn9awXgu3tp 4 | VL7sCIeSF4UYwtkaJusIiUVl5/1tPxHs615fGJWJq0UdQaGvTgdURSbyVwIDAQAB 5 | AoGAOiw7ep3A3iSPfOCQDXbLaANxNKa5DfYmVCKWZavALUUWQAxPmWJxh2rwgh6D 6 | fDHEdpe9R5MMTF0/xRvsQGQvZU2sNNhA2ebVOB4mMCPcURYWCbJXjT14IC7rfwPp 7 | YnkpiCHxP+HBS2xjMyf63vk7tKR/zCfzm9tsDAKqKuFUYPECRQCrQpiuYxp2Znch 8 | szwR15q0HUpU8/HdCQlKdBK2fa4M2Y1xPqNjpOlQ2B8zCS3aOQ/9nhbVaRy0LqFD 9 | sUjPPJsTqbaSyQI9ANpwsv1MHkpYGlYSrCItFS1oRoD/foxByVdVORCtarA0z9xe 10 | Am8kEi9HcnZf2jsYvqHAeQsTtuPw0PvMHwJEXL0+csilztHj1zL452yKkNh/pQtI 11 | wPogttmuPHZIZxrz9gwGbHIkCixOkNN6qf5Wg281TDGUYpoRp9d75wUZsQcpH8kC 12 | PE0rvYBREO5w27UG2bslNDMbgLT4DkwcvbXVzNhAe82OitSufauoEaiUVDLPwDha 13 | kJZyehDYwSccH6ilPwJFAIhBzCQ61A2zfEJlgKeuX3OJGq1pywpnv+vFyqnDc4T6 14 | oEW9kfnrAI+6x4L4jyyHOWMNfkAPajtkQ+YwxZqUmKL8ixr0 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /genisus_public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIGJAoGBAJIiGuUOlhKFQEHIr5YXa9uajM+sI5FEZ/8RJdR5EOC4Wo+9bZfyrvnu 3 | PRBtK7PJzUXHdXCaNohPzA5IuiEpkoELPyWfCozQF9FAgK2Gyf1rBeC7e2lUvuwI 4 | h5IXhRjC2Rom6wiJRWXn/W0/EezrXl8YlYmrRR1Boa9OB1RFJvJXAgMBAAE= 5 | -----END RSA PUBLIC KEY----- 6 | -------------------------------------------------------------------------------- /miniblockchain1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanchao/mini_blockchain/92ebc240e933d944ac06f3fcd06f15c82de06d9f/miniblockchain1.jpg -------------------------------------------------------------------------------- /miniblockchain2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanchao/mini_blockchain/92ebc240e933d944ac06f3fcd06f15c82de06d9f/miniblockchain2.jpg -------------------------------------------------------------------------------- /miniblockchain3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanchao/mini_blockchain/92ebc240e933d944ac06f3fcd06f15c82de06d9f/miniblockchain3.jpg -------------------------------------------------------------------------------- /p2p/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guanchao/mini_blockchain/92ebc240e933d944ac06f3fcd06f15c82de06d9f/p2p/__init__.py -------------------------------------------------------------------------------- /p2p/constant.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | K = 20 # bucket的大小 3 | BITS = 128 # bucket中节点的位数 4 | ALPHA = 3 -------------------------------------------------------------------------------- /p2p/kbucketset.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import heapq 3 | import threading 4 | 5 | import constant 6 | 7 | class KBucketSet(object): 8 | """ 9 | 备注: 10 | bucket中node ID和data key同构,好处: 11 | 1.当hash值空间足够大的时候,随机碰撞可以忽略不计,确保了node ID的唯一性 12 | 2.可以简化路由算法 13 | 14 | 参考:https://program-think.blogspot.com/2017/09/Introduction-DHT-Kademlia-Chord.html#head-7 15 | """ 16 | def __init__(self, current_node_id, k=constant.K, bits=constant.BITS): 17 | self.node_id = current_node_id 18 | self.k = k 19 | self.buckets = [list() for _ in range(bits)] # 128个k桶 20 | self.lock = threading.Lock() 21 | 22 | def __is_node_in_bucket(self, node, bucket): 23 | for peer in bucket: 24 | if peer == node: 25 | return True 26 | return False 27 | 28 | def __node_index_in_bucket(self, node, bucket): 29 | for i in range(len(bucket)): 30 | if node == bucket[i]: 31 | return i 32 | return -1 33 | 34 | def get_all_nodes(self): 35 | all_nodes = [] 36 | for bucket in self.buckets: 37 | for node in bucket: 38 | all_nodes.append(node) 39 | return all_nodes 40 | 41 | 42 | def insert(self, node): 43 | """ 44 | 往相应的bucket位置插入新节点 45 | 1.如果该bucket没有满且节点不重复,则插入到bucket中 46 | 2.如果该bucket没有满且节点重复,替换该节点 47 | 3.如果该bucket满且节点不重复,则剔除该新节点 48 | 4.如果该bucket满且节点重复,替换该节点 49 | 50 | :param node: 51 | :return: 52 | """ 53 | if self.node_id == node.node_id: 54 | return 55 | bucket_number = self.get_bucket_number(node.node_id) 56 | with self.lock: 57 | bucket = self.buckets[bucket_number] 58 | if len(bucket) < self.k: # bucket未满 59 | if self.__is_node_in_bucket(node, bucket): 60 | bucket.pop(self.__node_index_in_bucket(node, bucket)) 61 | bucket.append(node) 62 | else: # bucket已满 63 | if node.triple() in bucket: 64 | bucket.pop(self.__node_index_in_bucket(node, bucket)) 65 | else: 66 | pass # 丢弃新节点 67 | 68 | def nearest_nodes(self, node_id): 69 | """ 70 | 获取离node_id节点最近的k个节点(通过最小堆获取) 71 | :param node_id: 节点id 72 | :return: 73 | """ 74 | def get_distance_with_node_id(other_node): 75 | return node_id ^ other_node.node_id 76 | 77 | with self.lock: 78 | all_nodes = [] 79 | for bucket in self.buckets: 80 | for node in bucket: 81 | all_nodes.append(node) 82 | 83 | nearest_nodes = heapq.nsmallest(self.k, all_nodes, get_distance_with_node_id) 84 | return nearest_nodes 85 | 86 | def __get_distance(self, node_id1, node_id2): 87 | """ 88 | 通过异或获取kad中两个节点之间的距离 89 | :param node_id1: 90 | :param node_id2: 91 | :return: 92 | """ 93 | return node_id1 ^ node_id2 94 | 95 | def get_bucket_number(self, node_id): 96 | """ 97 | 获取目标节点相对于当前节点所在的bucket位置 98 | :param node_id: 99 | :return: 100 | """ 101 | distance = self.__get_distance(self.node_id, node_id) 102 | length = -1 103 | while distance: 104 | distance >>= 1 105 | length += 1 106 | return max(0, length) 107 | 108 | def get_bucket(self, bucket_number): 109 | return self.buckets[bucket_number] 110 | 111 | def exist(self, node_id): 112 | for bucket in self.buckets: 113 | for node in bucket: 114 | if node.node_id == node_id: 115 | return True 116 | return False 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /p2p/nearestnodes.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import threading 3 | 4 | from p2p import constant 5 | 6 | 7 | class KNearestNodesUtil(object): 8 | """ 9 | 用于更新保存距离某个节点最近的K个节点 10 | """ 11 | 12 | def __init__(self, node_id, k=constant.K): 13 | self.k = k 14 | self.node_id = node_id 15 | self.list = list() # 保存更新距离node id最近的k个节点,例如:[(Node obj, boolean)...] 16 | self.lock = threading.Lock() 17 | self.target_value = None 18 | 19 | def set_target_value(self, value): 20 | self.target_value = value 21 | 22 | def get_target_value(self): 23 | return self.target_value 24 | 25 | def update(self, nodes): 26 | """ 27 | 更新距离key值最近的K个节点 28 | :param key: 29 | :return: 30 | """ 31 | for node in nodes: 32 | self.__update_node(node) 33 | 34 | def __update_node(self, other_node): 35 | """ 36 | 更新nearest_nodes数据,获取当前node id节点跟other_node的距离,并更新nearest_nodes节点列表 37 | :param other_node: 38 | :return: 39 | """ 40 | if self.node_id == other_node.node_id: 41 | return 42 | with self.lock: 43 | for i in range(len(self.list)): 44 | node, _ = self.list[i] 45 | if other_node == node: 46 | # other_node已在nearest_nodes中,无需更新 47 | break 48 | if other_node.node_id ^ self.node_id < node.node_id ^ self.node_id: 49 | self.list.insert(i, (other_node, False)) # 按最小值从低到高排列节点 50 | self.list = self.list[:self.k] # 取距离最近的前k个节点 51 | break 52 | else: 53 | if len(self.list) < self.k: 54 | self.list.append((other_node, False)) 55 | 56 | if len(self.list) == 0: 57 | self.list.append((other_node, False)) 58 | 59 | def get_unvisited_nearest_nodes(self, alpha): 60 | if self.target_value: 61 | return [] 62 | unvisited_nearest_nodes = [] 63 | with self.lock: 64 | for node, flag in self.list: 65 | if not flag: 66 | unvisited_nearest_nodes.append(node) 67 | if len(unvisited_nearest_nodes) >= alpha: 68 | break 69 | return unvisited_nearest_nodes 70 | 71 | def mark(self, visited_node): 72 | """ 73 | 标记node节点已访问过 74 | :param visited_node: 75 | :return: 76 | """ 77 | with self.lock: 78 | for i in range(len(self.list)): 79 | node = self.list[i][0] 80 | if node == visited_node: 81 | self.list[i] = (node, True) 82 | 83 | def is_complete(self): 84 | """ 85 | 迭代结束的条件: 86 | 1.找到key值对应的value 87 | 2.找到距离目标节点最近的K个节点(这K个节点都访问过) 88 | :return: 89 | """ 90 | with self.lock: 91 | if self.target_value: 92 | return True 93 | 94 | if len(self.list) == 0: 95 | return True 96 | 97 | for node, flag in self.list: 98 | if not flag: 99 | return False 100 | return True 101 | 102 | def get_result_nodes(self): 103 | # TODO 有重复 104 | nodes = list() 105 | tmp_node_id = list() 106 | for node, flag in self.list: 107 | if node.node_id not in tmp_node_id: 108 | nodes.append(node) 109 | tmp_node_id.append(node.node_id) 110 | return nodes 111 | -------------------------------------------------------------------------------- /p2p/node.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import hashlib 3 | import json 4 | import SocketServer 5 | import pickle 6 | import threading 7 | import random 8 | 9 | import time 10 | from binascii import Error 11 | 12 | import zlib 13 | 14 | import db 15 | from blockchain import Blockchain 16 | from p2p import constant 17 | from p2p.kbucketset import KBucketSet 18 | from p2p.nearestnodes import KNearestNodesUtil 19 | from p2p import packet 20 | from p2p.packet import Version, Verack 21 | 22 | 23 | class ProcessMessages(SocketServer.BaseRequestHandler): 24 | """ 25 | 服务端消息处理中心 26 | """ 27 | 28 | def handle(self): 29 | """ 30 | 覆盖实现SocketServer.BaseRequestHandler类的handle方法 31 | 专门接收处理来自服务端的请求 32 | 33 | 备注:self.client_address是BaseRequestHandler的成员变量,记录连接到服务端的client地址 34 | :return: 35 | """ 36 | msg_obj = pickle.loads(zlib.decompress(self.request[0])) 37 | 38 | command = msg_obj.payloadcommand = msg_obj.command 39 | payload = msg_obj.payloadcommand = msg_obj.payload 40 | 41 | # print 'Handle ', command, 'from ', self.client_address 42 | # print command, 'payload:', payload 43 | 44 | if command == "ping": 45 | self.handle_ping(payload) 46 | elif command == "pong": 47 | self.handle_pong(payload) 48 | elif command == "find_neighbors": 49 | self.handle_find_neighbors(payload) 50 | elif command == "found_neighbors": 51 | self.handle_found_neighbors(payload) 52 | elif command == "find_value": 53 | self.handle_find_value(payload) 54 | elif command == "found_value": 55 | self.handle_found_value(payload) 56 | elif command == "store": 57 | self.handle_store(payload) 58 | elif command == "sendtx": 59 | self.handle_sendtx(payload) 60 | elif command == "sendblock": 61 | self.handle_sendblock(payload) 62 | elif command == "version": 63 | self.handle_version(payload) 64 | elif command == "verack": 65 | self.handle_verack(payload) 66 | 67 | # client_node_id = payload.from_id 68 | # if self.server.node_manager.buckets.exist(client_node_id): 69 | # self.server.node_manager.alive_nodes[client_node_id] = int(time.time()) # 更新节点联系时间 70 | 71 | def handle_ping(self, payload): 72 | socket = self.request[1] 73 | client_ip, client_port = self.client_address 74 | client_node_id = payload.from_id 75 | 76 | self.server.node_manager.pong(socket, client_node_id, (client_ip, client_port)) 77 | 78 | def handle_pong(self, payload): 79 | pass 80 | 81 | def handle_find_neighbors(self, payload): 82 | socket = self.request[1] 83 | client_ip, client_port = self.client_address 84 | client_node_id = payload.from_id 85 | 86 | node_id = payload.target_id 87 | rpc_id = payload.rpc_id 88 | nearest_nodes = self.server.node_manager.buckets.nearest_nodes(node_id) 89 | if not nearest_nodes: 90 | nearest_nodes.append(self.server.node_manager.client) 91 | nearest_nodes_triple = [node.triple() for node in nearest_nodes] 92 | self.server.node_manager.found_neighbors(node_id, rpc_id, nearest_nodes_triple, socket, client_node_id, 93 | (client_ip, client_port)) 94 | 95 | def handle_found_neighbors(self, payload): 96 | rpc_id = payload.rpc_id 97 | k_nearest_nodes_util = self.server.node_manager.rpc_ids[rpc_id] 98 | del self.server.node_manager.rpc_ids[rpc_id] 99 | nearest_nodes = [Node(*node) for node in payload.neighbors] 100 | k_nearest_nodes_util.update(nearest_nodes) 101 | 102 | def handle_find_value(self, payload): 103 | socket = self.request[1] 104 | client_ip, client_port = self.client_address 105 | client_node_id = payload.from_id 106 | 107 | key = payload.key 108 | rpc_id = payload.rpc_id 109 | if str(key) in self.server.node_manager.data: 110 | value = self.server.node_manager.data[str(key)] 111 | self.server.node_manager.found_value(key, value, rpc_id, socket, client_node_id, (client_ip, client_port)) 112 | else: 113 | nearest_nodes = self.server.node_manager.buckets.nearest_nodes(key) 114 | if not nearest_nodes: 115 | nearest_nodes.append(self.server.node_manager.client) 116 | nearest_nodes_triple = [node.triple() for node in nearest_nodes] 117 | self.server.node_manager.found_neighbors(key, rpc_id, nearest_nodes_triple, socket, client_node_id, 118 | (client_ip, client_port)) 119 | 120 | def handle_found_value(self, payload): 121 | rpc_id = payload.rpc_id 122 | value = payload.value 123 | k_nearest_nodes_util = self.server.node_manager.rpc_ids[rpc_id] 124 | del self.server.node_manager.rpc_ids[rpc_id] 125 | k_nearest_nodes_util.set_target_value(value) 126 | 127 | def handle_store(self, payload): 128 | key = payload.key 129 | value = payload.value 130 | self.server.node_manager.data[str(key)] = value 131 | 132 | def handle_sendtx(self, payload): 133 | new_tx = payload 134 | with self.server.node_manager.lock: 135 | blockchain = self.server.node_manager.blockchain 136 | 137 | # 判断区块中是否存在 138 | if blockchain.find_transaction(new_tx.txid): 139 | return 140 | # 判断交易池中是否存在 141 | for k in range(len(blockchain.current_transactions)): 142 | uncomfirmed_tx = blockchain.current_transactions[-1 - k] 143 | if uncomfirmed_tx.txid == new_tx.txid: 144 | return 145 | 146 | blockchain.current_transactions.append(new_tx) 147 | db.write_unconfirmed_tx_to_db(blockchain.wallet.address, new_tx) 148 | 149 | def handle_sendblock(self, payload): 150 | new_block = payload 151 | with self.server.node_manager.lock: 152 | blockchain = self.server.node_manager.blockchain 153 | block_height = db.get_block_height(blockchain.wallet.address) 154 | latest_block = db.get_block_data_by_index(blockchain.get_wallet_address(), block_height - 1) 155 | 156 | if (latest_block.current_hash == new_block.previous_hash) and (latest_block.index + 1 == new_block.index): 157 | 158 | # 校验交易是否有效 159 | is_valid = True 160 | for idx in range(len(new_block.transactions)): 161 | tx = new_block.transactions[-1 - idx] 162 | if not blockchain.verify_transaction(tx): 163 | is_valid = False 164 | break 165 | 166 | if is_valid: 167 | db.write_to_db(blockchain.wallet.address, new_block) 168 | # 重新挖矿 169 | blockchain.current_transactions = [] 170 | db.clear_unconfirmed_tx_from_disk(blockchain.wallet.address) 171 | else: 172 | self.add_to_candidate_blocks(blockchain, new_block) 173 | 174 | blockchain.set_consensus_chain() 175 | 176 | def handle_version(self, payload): 177 | version = payload.version 178 | if version != 1: 179 | # 版本不一样,拒绝 180 | print '[Warn] invalid version, ignore!!' 181 | pass 182 | else: 183 | client_ip, client_port = self.client_address 184 | client_node_id = payload.from_id 185 | new_node = Node(client_ip, client_port, client_node_id) 186 | new_node.version = 1 187 | self.server.node_manager.buckets.insert(new_node) 188 | blockchain = self.server.node_manager.blockchain 189 | 190 | block_counts = db.get_block_height(blockchain.get_wallet_address()) 191 | verack = Verack(1, int(time.time()), self.server.node_manager.node_id, client_node_id, 192 | block_counts) 193 | self.server.node_manager.sendverck(new_node, verack) 194 | 195 | if payload.best_height > block_counts: 196 | # TODO 检查best_height,同步区块链 197 | pass 198 | 199 | def handle_verack(self, payload): 200 | version = payload.version 201 | if version != 1: 202 | # 版本不一样,拒绝 203 | print '[Warn] invalid version, ignore!!' 204 | pass 205 | else: 206 | client_node_id = payload.from_id 207 | client_ip, client_port = self.client_address 208 | new_node = Node(client_ip, client_port, client_node_id) 209 | new_node.version = 1 210 | self.server.node_manager.buckets.insert(new_node) 211 | blockchain = self.server.node_manager.blockchain 212 | 213 | if payload.best_height > db.get_block_height(blockchain.get_wallet_address()): 214 | # TODO 检查best_height,同步区块链 215 | pass 216 | 217 | def add_to_candidate_blocks(self, blockchain, new_block): 218 | if new_block.index in blockchain.candidate_blocks.keys(): 219 | blockchain.candidate_blocks[new_block.index].add(new_block) 220 | else: 221 | blockchain.candidate_blocks[new_block.index] = set() 222 | blockchain.candidate_blocks[new_block.index].add(new_block) 223 | 224 | 225 | class Server(SocketServer.ThreadingUDPServer): 226 | """ 227 | 接收消息,并做相应处理 228 | """ 229 | 230 | def __init__(self, address, handler): 231 | SocketServer.UDPServer.__init__(self, address, handler) 232 | self.lock = threading.Lock() 233 | 234 | 235 | class Node(object): 236 | """ 237 | P2P网络的节点,发送消息。实现Kad中的PING、FIND_NODE、FIND_VALUE和STORE消息 238 | """ 239 | 240 | def __init__(self, ip, port, client_node_id): 241 | self.ip = ip 242 | self.port = port 243 | self.node_id = client_node_id 244 | self.version = None 245 | 246 | def __repr__(self): 247 | return json.dumps(self.__dict__) 248 | 249 | def __eq__(self, other): 250 | if self.__class__ == other.__class__ \ 251 | and self.ip == other.ip \ 252 | and self.port == other.port \ 253 | and self.node_id == other.node_id: 254 | return True 255 | return False 256 | 257 | def triple(self): 258 | return (self.ip, self.port, self.node_id) 259 | 260 | def ping(self, sock, target_node_address, message): 261 | sock.sendto(zlib.compress(message), target_node_address) 262 | 263 | def pong(self, sock, target_node_address, message): 264 | sock.sendto(zlib.compress(message), target_node_address) 265 | 266 | def find_neighbors(self, sock, target_node_address, message): 267 | sock.sendto(zlib.compress(message), target_node_address) 268 | 269 | def found_neighbors(self, sock, target_node_address, message): 270 | sock.sendto(zlib.compress(message), target_node_address) 271 | 272 | def find_value(self, sock, target_node_address, message): 273 | sock.sendto(zlib.compress(message), target_node_address) 274 | 275 | def found_value(self, sock, target_node_address, message): 276 | sock.sendto(zlib.compress(message), target_node_address) 277 | 278 | def store(self, sock, target_node_address, message): 279 | sock.sendto(zlib.compress(message), target_node_address) 280 | 281 | def sendtx(self, sock, target_node_address, message): 282 | ret = sock.sendto(zlib.compress(message), target_node_address) 283 | 284 | def sendblock(self, sock, target_node_address, message): 285 | ret = sock.sendto(zlib.compress(message), target_node_address) 286 | 287 | def sendversion(self, sock, target_node_address, message): 288 | sock.sendto(zlib.compress(message), target_node_address) 289 | 290 | def sendverack(self, sock, target_node_address, message): 291 | sock.sendto(zlib.compress(message), target_node_address) 292 | 293 | 294 | class NodeManager(object): 295 | """ 296 | P2P网络中每个节点同时提供Server和Client的作用 297 | 298 | 节点之间互相通信(发送+接收),实现的kad协议算法的4中操作,分别是: 299 | 1.PING:检测节点是否在线 300 | 2.STORE:在某个节点上存储key、value 301 | 3.FIND NODE:返回对方节点桶中距离请求key最近的k个节点 302 | 4.FIND VALUE:与FIND NODE类似,不过返回的是相应key的value 303 | 304 | """ 305 | 306 | def __init__(self, ip, port=0, genisus_node=False): 307 | self.ip = ip 308 | self.port = port 309 | self.node_id = self.__random_id() 310 | self.address = (self.ip, self.port) 311 | self.buckets = KBucketSet(self.node_id) 312 | # 每个消息都有一个唯一的rpc_id,用于标识节点之间的通信(该rpc_id由发起方生成,并由接收方返回), 313 | # 这样可以避免节点收到多个从同一个节点发送的消息时无法区分 314 | self.rpc_ids = {} # rpc_ids被多个线程共享,需要加锁 315 | 316 | self.lock = threading.Lock() # 备注,由于blockchain数据被多个线程共享使用(矿工线程、消息处理线程),需要加锁 317 | 318 | self.server = Server(self.address, ProcessMessages) 319 | self.port = self.server.server_address[1] 320 | self.client = Node(self.ip, self.port, self.node_id) 321 | self.data = {} 322 | 323 | self.alive_nodes = {} # {"xxxx":"2018-03-12 22:00:00",....} 324 | 325 | self.server.node_manager = self 326 | self.blockchain = Blockchain(genisus_node) 327 | 328 | # 消息处理 329 | self.processmessages_thread = threading.Thread(target=self.server.serve_forever) 330 | self.processmessages_thread.daemon = True 331 | self.processmessages_thread.start() 332 | 333 | # 消息发送 TODO 334 | # self.sendmessages_thread = threading.Thread(target=self.sendmessages) 335 | # self.sendmessages_thread.daemon = True 336 | # self.sendmessages_thread.start() 337 | 338 | # 矿工线程 339 | self.minner_thread = threading.Thread(target=self.minner) 340 | self.minner_thread.daemon = True 341 | self.minner_thread.start() 342 | 343 | print '[Info] start new node', self.ip, self.port, self.node_id 344 | 345 | def ping(self, sock, server_node_id, target_node_address): 346 | payload = packet.Ping(self.node_id, server_node_id) 347 | msg_obj = packet.Message("ping", payload) 348 | msg_bytes = pickle.dumps(msg_obj) 349 | self.client.ping(sock, target_node_address, msg_bytes) 350 | 351 | def pong(self, sock, server_node_id, target_node_address): 352 | """ 353 | 发送对ping请求的响应消息 354 | :param sock: Server端监听返回的客户端连接 355 | :param target_node_address: 目标节点的地址 356 | :return: 357 | """ 358 | payload = packet.Pong(self.node_id, server_node_id) 359 | msg_obj = packet.Message("pong", payload) 360 | msg_bytes = pickle.dumps(msg_obj) 361 | self.client.pong(sock, target_node_address, msg_bytes) 362 | 363 | def find_neighbors(self, node_id, rpc_id, sock, server_node_id, server_node_address): 364 | payload = packet.FindNeighbors(node_id, self.node_id, server_node_id, rpc_id) 365 | msg_obj = packet.Message("find_neighbors", payload) 366 | msg_bytes = pickle.dumps(msg_obj) 367 | self.client.find_neighbors(sock, server_node_address, msg_bytes) 368 | 369 | def found_neighbors(self, node_id, rpc_id, neighbors, sock, server_node_id, server_node_address): 370 | payload = packet.FoundNeighbors(node_id, self.node_id, server_node_id, rpc_id, neighbors) 371 | msg_obj = packet.Message("found_neighbors", payload) 372 | msg_bytes = pickle.dumps(msg_obj) 373 | self.client.found_neighbors(sock, server_node_address, msg_bytes) 374 | 375 | def find_value(self, key, rpc_id, sock, server_node_id, target_node_address): 376 | payload = packet.FindValue(key, self.node_id, server_node_id, rpc_id) 377 | msg_obj = packet.Message("find_value", payload) 378 | msg_bytes = pickle.dumps(msg_obj) 379 | self.client.find_value(sock, target_node_address, msg_bytes) 380 | 381 | def found_value(self, key, value, rpc_id, sock, server_node_id, target_node_address): 382 | payload = packet.FoundValue(key, value, self.node_id, server_node_id, rpc_id) 383 | msg_obj = packet.Message("found_value", payload) 384 | msg_bytes = pickle.dumps(msg_obj) 385 | self.client.found_value(sock, target_node_address, msg_bytes) 386 | 387 | def store(self, key, value, sock, server_node_id, target_node_address): 388 | payload = packet.Store(key, value, self.node_id, server_node_id) 389 | msg_obj = packet.Message("store", payload) 390 | msg_bytes = pickle.dumps(msg_obj) 391 | self.client.store(sock, target_node_address, msg_bytes) 392 | 393 | def iterative_find_nodes(self, key, seed_node=None): 394 | """ 395 | 找到距离目标节点最近的K个节点 396 | :param server_node_id: 397 | :param seed_nodes: 398 | :return: 399 | """ 400 | k_nearest_nodes_util = KNearestNodesUtil(key) 401 | k_nearest_nodes_util.update(self.buckets.nearest_nodes(key)) 402 | if seed_node: 403 | rpc_id = self.__get_rpc_id() 404 | self.rpc_ids[rpc_id] = k_nearest_nodes_util 405 | self.find_neighbors(key, rpc_id, self.server.socket, key, 406 | (seed_node.ip, seed_node.port)) 407 | 408 | while (not k_nearest_nodes_util.is_complete()) or seed_node: # 循环迭代直至距离目标节点最近的K个节点都找出来 409 | # 限制同时向ALPHA(3)个邻近节点发送FIND NODE请求 410 | unvisited_nearest_nodes = k_nearest_nodes_util.get_unvisited_nearest_nodes(constant.ALPHA) 411 | for node in unvisited_nearest_nodes: 412 | k_nearest_nodes_util.mark(node) 413 | rpc_id = self.__get_rpc_id() 414 | self.rpc_ids[rpc_id] = k_nearest_nodes_util 415 | self.find_neighbors(key, rpc_id, self.server.socket, key, 416 | (node.ip, node.port)) 417 | time.sleep(1) 418 | seed_node = None 419 | 420 | return k_nearest_nodes_util.get_result_nodes() 421 | 422 | def iterative_find_value(self, key): 423 | k_nearest_nodes_util = KNearestNodesUtil(key) 424 | k_nearest_nodes_util.update(self.buckets.nearest_nodes(key)) 425 | while not k_nearest_nodes_util.is_complete(): 426 | # 限制同时向ALPHA(3)个邻近节点发送FIND NODE请求 427 | unvisited_nearest_nodes = k_nearest_nodes_util.get_unvisited_nearest_nodes(constant.ALPHA) 428 | for node in unvisited_nearest_nodes: 429 | k_nearest_nodes_util.mark(node) 430 | rpc_id = self.__get_rpc_id() 431 | self.rpc_ids[rpc_id] = k_nearest_nodes_util 432 | self.find_value(key, rpc_id, self.server.socket, node.node_id, (node.ip, node.port)) 433 | 434 | time.sleep(1) 435 | 436 | return k_nearest_nodes_util.get_target_value() 437 | 438 | def bootstrap(self, seed_nodes=[]): 439 | """ 440 | 根据初始节点引导初始化 441 | :param seed_nodes: 种子节点列表 442 | :return: 443 | """ 444 | for seed_node in seed_nodes: 445 | # 握手 446 | self.sendversion(seed_node, 447 | Version(1, int(time.time()), self.node_id, seed_node.node_id, 448 | db.get_block_height(self.blockchain.get_wallet_address()))) 449 | 450 | for seed_node in seed_nodes: 451 | self.iterative_find_nodes(self.client.node_id, seed_node) 452 | 453 | if len(seed_nodes) == 0: 454 | for seed_node in self.buckets.get_all_nodes(): 455 | self.iterative_find_nodes(self.client.node_id, seed_node) 456 | 457 | def __hash_function(self, key): 458 | return int(hashlib.md5(key.encode('ascii')).hexdigest(), 16) 459 | 460 | def __get_rpc_id(self): 461 | return random.getrandbits(constant.BITS) 462 | 463 | def __random_id(self): 464 | return random.randint(0, (2 ** constant.BITS) - 1) 465 | 466 | def hearbeat(self, node, bucket, node_idx): 467 | # buckets在15分钟内节点未改变过需要进行refresh操作(对buckets中的每个节点发起find node操作) 468 | # 如果所有节点都有返回响应,则该buckets不需要经常更新 469 | # 如果有节点没有返回响应,则该buckets需要定期更新保证buckets的完整性 470 | node_id = node.node_id 471 | ip = node.ip 472 | port = node.port 473 | 474 | tm = int(time.time()) 475 | if tm - int(self.alive_nodes[node_id]) > 1800: 476 | # 节点的更新时间超过1min,认为已下线,移除该节点 477 | bucket.pop(node_idx) 478 | self.alive_nodes.pop(node_id) 479 | 480 | # print '[Info] heartbeat....' 481 | self.ping(self.server.socket, node_id, (ip, port)) 482 | 483 | def minner(self): 484 | while True: 485 | # blockchain多个线程共享使用,需要加锁 486 | time.sleep(10) 487 | 488 | try: 489 | with self.lock: 490 | new_block = self.blockchain.do_mine() 491 | # 广播区块 492 | # TODO 检测包大小,太大会导致发送失败 493 | self.sendblock(new_block) 494 | except Error as e: 495 | pass 496 | 497 | self.blockchain.set_consensus_chain() # pow机制保证最长辆(nonce之和最大的链) 498 | 499 | def set_data(self, key, value): 500 | """ 501 | 数据存放: 502 | 1.首先发起节点定位K个距离key最近的节点 503 | 2.发起节点对这K个节点发送STORE消息 504 | 3.收到STORE消息的K个节点保存(key, value)数据 505 | 506 | :param key: 507 | :param value: 508 | :return: 509 | """ 510 | data_key = self.__hash_function(key) 511 | k_nearest_ndoes = self.iterative_find_nodes(data_key) 512 | if not k_nearest_ndoes: 513 | self.data[str(data_key)] = value 514 | for node in k_nearest_ndoes: 515 | self.store(data_key, value, self.server.socket, node.node_id, (node.ip, node.port)) 516 | 517 | def get_data(self, key): 518 | """ 519 | 读取数据 520 | 1.当前节点收到查询数据的请求(获取key对应的value) 521 | 2.当前节点首先检测自己是否保存了该数据,如果有则返回key对应的value 522 | 3.如果当前节点没有保存该数据,则计算获取距离key值最近的K个节点,分别向这K个节点发送FIND VALUE的操作进行查询 523 | 4.收到FIND VALUE请求操作的节点也进行上述(2)~(3)的过程(递归处理) 524 | :param key: 525 | :param value: 526 | :return: 527 | """ 528 | data_key = self.__hash_function(key) 529 | if str(data_key) in self.data: 530 | return self.data[str(data_key)] 531 | value = self.iterative_find_value(data_key) 532 | if value: 533 | return value 534 | else: 535 | raise KeyError 536 | 537 | def sendtx(self, tx): 538 | """ 539 | 广播一個交易 540 | :param tx: 541 | :return: 542 | """ 543 | data_key = self.__hash_function(tx.txid) 544 | k_nearest_ndoes = self.iterative_find_nodes(data_key) 545 | if not k_nearest_ndoes: 546 | self.data[data_key] = tx 547 | for node in k_nearest_ndoes: 548 | tx.from_id = self.node_id 549 | msg_obj = packet.Message("sendtx", tx) 550 | msg_bytes = pickle.dumps(msg_obj) 551 | self.client.sendtx(self.server.socket, (node.ip, node.port), msg_bytes) 552 | 553 | def sendblock(self, block): 554 | """ 555 | 广播一个block 556 | :param block: 557 | :return: 558 | """ 559 | data_key = self.__hash_function(block.current_hash) 560 | k_nearest_ndoes = self.iterative_find_nodes(data_key) 561 | if not k_nearest_ndoes: 562 | self.data[data_key] = block 563 | for node in k_nearest_ndoes: 564 | block.from_id = self.node_id 565 | msg_obj = packet.Message("sendblock", block) 566 | msg_bytes = pickle.dumps(msg_obj) 567 | print '[Info] send block', node.ip, node.port, block.current_hash 568 | self.client.sendblock(self.server.socket, (node.ip, node.port), msg_bytes) 569 | 570 | def sendversion(self, node, version): 571 | msg_obj = packet.Message("version", version) 572 | msg_bytes = pickle.dumps(msg_obj) 573 | self.client.sendversion(self.server.socket, (node.ip, node.port), msg_bytes) 574 | 575 | def sendverck(self, node, verack): 576 | msg_obj = packet.Message("verack", verack) 577 | msg_bytes = pickle.dumps(msg_obj) 578 | self.client.sendverack(self.server.socket, (node.ip, node.port), msg_bytes) 579 | 580 | # def sendmessages(self): 581 | # while True: 582 | # for bucket in self.buckets.buckets: 583 | # for i in range(len(bucket)): 584 | # node = bucket[i] 585 | # # 要先完成握手才可以进行其他操作 586 | # if node.version == None: 587 | # continue 588 | # 589 | # # hearbeat 590 | # # self.hearbeat(node, bucket, i) 591 | # 592 | # # 发送addr消息,告诉对方节点自己所拥有的节点信息 593 | # # TODO 594 | # 595 | # # 发送getaddr消息,获取尽量多的节点 596 | # # TODO 597 | # 598 | # # 发送inv消息,请求一个区块哈希的列表 599 | # # TODO 600 | # 601 | # # 发送getdata消息,用于请求某个块或交易的完整信息 602 | # # TODO 603 | # 604 | # time.sleep(10) 605 | -------------------------------------------------------------------------------- /p2p/packet.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | 4 | 5 | class Message(object): 6 | def __init__(self, command, payload): 7 | """ 8 | :param command: 命令 9 | :param payload: 每个command对应的payload不一样 10 | """ 11 | self.command = command 12 | self.payload = payload 13 | 14 | 15 | class Version(object): 16 | def __init__(self, version, timestamp, from_id, to_id, best_height): 17 | self.version = version 18 | self.timestamp = timestamp 19 | self.from_id = from_id 20 | self.to_id = to_id 21 | self.best_height = best_height 22 | 23 | 24 | class Verack(object): 25 | def __init__(self, version, timestamp, from_id, to_id, best_height): 26 | self.version = version 27 | self.timestamp = timestamp 28 | self.from_id = from_id 29 | self.to_id = to_id 30 | self.best_height = best_height 31 | 32 | 33 | class Ping(object): 34 | def __init__(self, from_id, to_id): 35 | self.from_id = from_id 36 | self.to_id = to_id 37 | 38 | def __str__(self): 39 | return json.dumps(self.__dict__) 40 | 41 | 42 | class Pong(object): 43 | def __init__(self, from_id, to_id): 44 | self.from_id = from_id 45 | self.to_id = to_id 46 | 47 | def __str__(self): 48 | return json.dumps(self.__dict__) 49 | 50 | 51 | class FindNeighbors(object): 52 | def __init__(self, target_id, from_id, to_id, rpc_id): 53 | self.target_id = target_id 54 | self.from_id = from_id 55 | self.to_id = to_id 56 | self.rpc_id = rpc_id 57 | 58 | def __str__(self): 59 | return json.dumps(self.__dict__) 60 | 61 | 62 | class FoundNeighbors(object): 63 | def __init__(self, target_id, from_id, to_id, rpc_id, neighbors): 64 | self.target_id = target_id 65 | self.from_id = from_id 66 | self.to_id = to_id 67 | self.rpc_id = rpc_id 68 | self.neighbors = neighbors 69 | 70 | def __str__(self): 71 | return json.dumps(self.__dict__) 72 | 73 | 74 | class FindValue(object): 75 | def __init__(self, key, from_id, to_id, rpc_id): 76 | self.key = key 77 | self.from_id = from_id 78 | self.to_id = to_id 79 | self.rpc_id = rpc_id 80 | 81 | def __str__(self): 82 | return json.dumps(self.__dict__) 83 | 84 | 85 | class FoundValue(object): 86 | def __init__(self, key, value, from_id, to_id, rpc_id): 87 | self.key = key 88 | self.value = value 89 | self.from_id = from_id 90 | self.to_id = to_id 91 | self.rpc_id = rpc_id 92 | 93 | def __str__(self): 94 | return json.dumps(self.__dict__) 95 | 96 | 97 | class Store(object): 98 | def __init__(self, key, value, from_id, to_id): 99 | self.key = key 100 | self.value = value 101 | self.from_id = from_id 102 | self.to_id = to_id 103 | 104 | def __str__(self): 105 | return json.dumps(self.__dict__) 106 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import json 3 | import time, os 4 | import shutil 5 | from flask import Flask, jsonify, request 6 | 7 | import db 8 | from p2p.node import NodeManager, Node 9 | from script import Script, get_address_from_ripemd160 10 | from wallet import Wallet 11 | 12 | try: 13 | from urllib.parse import urlparse 14 | except ImportError: 15 | from urlparse import urlparse 16 | 17 | """ 18 | 简化的区块链 19 | 20 | 功能: 21 | 1.实现P2P(DHT)网络 22 | 2.可以进行挖矿 23 | 3.可以进行交易 24 | 4.可以进行共识 25 | 26 | """ 27 | 28 | app = Flask(__name__) 29 | 30 | 31 | # @app.route('/candidates', methods=['GET']) 32 | # def candidates(): 33 | # output = json.dumps(blockchain.candidate_blocks, default=lambda obj: obj.__dict__, indent=4) 34 | # return output, 200 35 | 36 | 37 | # @app.route('/ping', methods=['POST']) 38 | # def ping(): 39 | # values = request.get_json() 40 | # required = ['node_id', 'ip', 'port'] 41 | # if not all(k in values for k in required): 42 | # return 'Missing values', 400 43 | # 44 | # node_id = values['node_id'] 45 | # ip = values['ip'] 46 | # port = values['port'] 47 | # 48 | # node_manager.ping(node_manager.server.socket, long(node_id), (str(ip), int(port))) 49 | # return 'ok', 200 50 | 51 | 52 | # @app.route('/all_nodes', methods=['GET']) 53 | # def get_all_nodes(): 54 | # all_nodes = node_manager.buckets.get_all_nodes() 55 | # output = json.dumps(all_nodes, default=lambda obj: obj.__dict__, indent=4) 56 | # return output, 200 57 | 58 | 59 | # @app.route('/all_data', methods=['GET']) 60 | # def get_all_data(): 61 | # datas = node_manager.data 62 | # output = json.dumps(datas, default=lambda obj: obj.__dict__, indent=4) 63 | # return output, 200 64 | 65 | 66 | # @app.route('/unconfirmed_tx', methods=['GET']) 67 | # def get_unconfirmed_tx(): 68 | # datas = [tx.json_output() for tx in blockchain.current_transactions] 69 | # output = json.dumps(datas, default=lambda obj: obj.__dict__, indent=4) 70 | # return output, 200 71 | 72 | 73 | @app.route('/bootstrap', methods=['POST']) 74 | def bootstrap(): 75 | values = request.get_json() 76 | required = ['seeds'] 77 | if not all(k in values for k in required): 78 | return 'Missing values', 400 79 | seeds = values['seeds'] 80 | # print json.dumps(seeds, default=lambda obj: obj.__dict__, indent=4) 81 | seed_nodes = list() 82 | for seed in seeds: 83 | seed_nodes.append(Node(seed['ip'], seed['port'], seed['node_id'])) 84 | node_manager.bootstrap(seed_nodes) 85 | 86 | all_nodes = node_manager.buckets.get_all_nodes() 87 | output = json.dumps(all_nodes, default=lambda obj: obj.__dict__, indent=4) 88 | return output, 200 89 | 90 | 91 | @app.route('/curr_node', methods=['GET']) 92 | def curr_node(): 93 | output = { 94 | 'node_id': node_manager.node_id, 95 | 'ip': node_manager.ip, 96 | 'port': node_manager.port, 97 | 'wallet': blockchain.get_wallet_address(), 98 | 'pubkey_hash': Script.sha160(str(blockchain.wallet.pubkey)) 99 | } 100 | output = json.dumps(output, default=lambda obj: obj.__dict__, indent=4) 101 | return output, 200 102 | 103 | 104 | 105 | # @app.route('/chain', methods=['GET']) 106 | # def full_chain(): 107 | # output = { 108 | # 'length': db.get_block_height(blockchain.wallet.address), 109 | # 'chain': blockchain.json_output(), 110 | # } 111 | # json_output = json.dumps(output, indent=4) 112 | # return json_output, 200 113 | 114 | 115 | @app.route('/transactions/new', methods=['POST']) 116 | def new_transaction(): 117 | values = request.get_json() 118 | required = ['sender', 'receiver', 'amount'] 119 | if not all(k in values for k in required): 120 | return 'Missing values', 400 121 | 122 | # Create a new Transaction 123 | new_tx = blockchain.new_utxo_transaction(values['sender'], values['receiver'], values['amount']) 124 | 125 | if new_tx: 126 | # 广播交易 127 | node_manager.sendtx(new_tx) 128 | output = { 129 | 'message': 'new transaction been created successfully!', 130 | 'current_transactions': [tx.json_output() for tx in blockchain.current_transactions] 131 | } 132 | json_output = json.dumps(output, indent=4) 133 | return json_output, 200 134 | 135 | else: 136 | response = {'message': "Not enough funds!"} 137 | return jsonify(response), 200 138 | 139 | 140 | @app.route('/balance', methods=['GET']) 141 | def get_balance(): 142 | address = request.args.get('address') 143 | response = { 144 | 'address': address, 145 | 'balance': blockchain.get_balance_by_db(address) 146 | } 147 | return jsonify(response), 200 148 | 149 | 150 | @app.route('/node_info', methods=['POST']) 151 | def node_info(): 152 | values = request.get_json() 153 | required = ['ip', 'port'] 154 | if not all(k in values for k in required): 155 | return 'Missing values', 400 156 | 157 | ip = values['ip'] 158 | port = values['port'] 159 | block_height = db.get_block_height(blockchain.wallet.address) 160 | latest_block = db.get_block_data_by_index(blockchain.wallet.address, block_height - 1) 161 | block_hash = latest_block.current_hash 162 | timestamp = latest_block.timestamp 163 | 164 | time_local = time.localtime(timestamp) 165 | 166 | response = { 167 | 'address': ip + ':' + str(port), 168 | 'block_height': block_height, 169 | 'block_hash': block_hash, 170 | 'wallet_address': blockchain.wallet.address, 171 | # 'balance': blockchain.get_balance(blockchain.wallet.address), 172 | 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S", time_local) 173 | } 174 | return jsonify(response), 200 175 | 176 | 177 | @app.route('/tx_info', methods=['GET']) 178 | def tx_info(): 179 | values = request.get_json() 180 | 181 | block_index = int(request.args.get('block_index')) 182 | txid = request.args.get('txid') 183 | 184 | block = db.get_block_data_by_index(blockchain.wallet.address, block_index) 185 | for tx in block.transactions: 186 | if tx.txid == txid: 187 | return json.dumps(tx.json_output()), 200 188 | 189 | return 'not exist!', 200 190 | 191 | 192 | @app.route('/unconfirm_tx_info', methods=['GET']) 193 | def unconfirm_tx_info(): 194 | txid = request.args.get('txid') 195 | 196 | for tx in db.get_all_unconfirmed_tx(blockchain.wallet.address): 197 | if tx.txid == txid: 198 | return json.dumps(tx.json_output()), 200 199 | 200 | return 'not exist!', 200 201 | 202 | 203 | @app.route('/height', methods=['GET']) 204 | def block_height(): 205 | response = { 206 | 'code': 0, 207 | 'value': db.get_block_height(blockchain.wallet.address) 208 | } 209 | return json.dumps(response), 200 210 | 211 | 212 | @app.route('/latest_tx', methods=['GET']) 213 | def latest_tx(): 214 | json_transaction = list() 215 | for tx in db.get_all_unconfirmed_tx(blockchain.wallet.address): 216 | txins = tx.txins 217 | txouts = tx.txouts 218 | 219 | from_addr = list() 220 | to_addr = list() 221 | amount = 0 222 | for txin in txins: 223 | if txin.prev_tx_out_idx != -1: 224 | pubkey_hash = Wallet.get_address(txin.pubkey) 225 | if pubkey_hash not in from_addr: 226 | from_addr.append(pubkey_hash) 227 | 228 | for txout in txouts: 229 | value = txout.value 230 | script_pub_key = txout.scriptPubKey 231 | if len(script_pub_key) == 5: 232 | recv_addr = get_address_from_ripemd160(script_pub_key[2]) 233 | to_addr.append({'receiver': recv_addr, 'value': value}) 234 | 235 | new_tx = { 236 | 'txid': tx.txid, 237 | 'senders': from_addr, 238 | 'receivers': to_addr, 239 | 'amount': amount, 240 | 'timestamp': tx.timestamp 241 | } 242 | 243 | json_transaction.append(new_tx) 244 | 245 | response = { 246 | 'latest_tx': json_transaction 247 | } 248 | return json.dumps(response), 200 249 | 250 | 251 | @app.route('/block_info', methods=['GET']) 252 | def block_info(): 253 | height = request.args.get('height') 254 | block_index = int(height) - 1 255 | 256 | block = db.get_block_data_by_index(blockchain.wallet.address, block_index) 257 | 258 | json_transaction = list() 259 | for tx in block.transactions: 260 | txins = tx.txins 261 | txouts = tx.txouts 262 | 263 | from_addr = list() 264 | to_addr = list() 265 | amount = 0 266 | for txin in txins: 267 | if txin.prev_tx_out_idx != -1: 268 | address = Wallet.get_address(txin.pubkey) 269 | if address not in from_addr: 270 | from_addr.append(address) 271 | 272 | for txout in txouts: 273 | value = txout.value 274 | script_pub_key = txout.scriptPubKey 275 | if len(script_pub_key) == 5: 276 | recv_addr = get_address_from_ripemd160(script_pub_key[2]) 277 | to_addr.append({'receiver': recv_addr, 'value': value}) 278 | 279 | new_tx = { 280 | 'txid': tx.txid, 281 | 'senders': from_addr, 282 | 'receivers': to_addr, 283 | 'amount': amount, 284 | 'timestamp': tx.timestamp 285 | } 286 | 287 | json_transaction.append(new_tx) 288 | 289 | response = { 290 | 'index': block.index, 291 | 'current_hash': block.current_hash, 292 | 'previous_hash': block.previous_hash, 293 | 'timestamp': block.timestamp, 294 | 'merkleroot': block.merkleroot, 295 | 'difficulty': block.difficulty, 296 | 'nonce': block.nonce, 297 | 'transactions': json_transaction 298 | } 299 | 300 | return jsonify(response), 200 301 | 302 | 303 | if __name__ == '__main__': 304 | from argparse import ArgumentParser 305 | 306 | parser = ArgumentParser() 307 | parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on') 308 | args = parser.parse_args() 309 | port = args.port 310 | 311 | if port == 5000: 312 | genisus_node = True 313 | else: 314 | genisus_node = False 315 | 316 | node_manager = NodeManager('localhost', 0, genisus_node) 317 | blockchain = node_manager.blockchain 318 | 319 | print "Wallet address: %s" % blockchain.get_wallet_address() 320 | 321 | app.run(host='0.0.0.0', port=port) 322 | -------------------------------------------------------------------------------- /script.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | # 参考:https://en.bitcoin.it/wiki/Script 4 | import hashlib 5 | from binascii import hexlify, unhexlify 6 | 7 | import rsa 8 | 9 | from wallet import Wallet 10 | 11 | OP_DUP = 0x76 # 118, 复制栈顶元素 12 | OP_HASH160 = 0xa9 # 169,sha256(ripemd160(栈顶元素)) 13 | OP_CHECKSIG = 0xac # 172, Input:sig+pubkey Output:True/False 校验签名 14 | OP_EQUALVERIFY = 0x88 # 136 校验是否相等 15 | 16 | 17 | class Script(object): 18 | """ 19 | 交易脚本:解锁脚本 + 锁定脚本 20 | 解锁脚本 21 | 22 | 23 | 锁定脚本 24 | OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG 25 | """ 26 | 27 | @staticmethod 28 | def sign(privkey, data): 29 | """ 30 | 签名 31 | :param privkey: 32 | :param data: 33 | :return: 34 | """ 35 | return rsa.sign(data.encode(), privkey, 'SHA-256') 36 | 37 | @staticmethod 38 | def verify(data, signature, pubkey): 39 | """ 40 | 验证签名 41 | :param data: 42 | :param signature: 43 | :param pubkey: 44 | :return: 45 | """ 46 | if data == None or signature == None or pubkey == None: 47 | return False 48 | try: 49 | rsa.verify(data, signature, pubkey) 50 | except rsa.pkcs1.VerificationError: 51 | return False 52 | return True 53 | 54 | # @staticmethod 55 | # def encode_scriptPubKey(scriptPubKey): 56 | # """ 57 | # 将scriptPubKey指令数组转换成指令字符串 58 | # :param scriptPubKey: 59 | # :return: 60 | # """ 61 | # scriptPubKey_str = "" 62 | # for i in range(len(scriptPubKey)): 63 | # element = scriptPubKey[i] 64 | # if element == OP_DUP \ 65 | # or element == OP_EQUALVERIFY \ 66 | # or element == OP_CHECKSIG: 67 | # scriptPubKey_str += hex(element)[2:] 68 | # elif element == OP_HASH160: 69 | # scriptPubKey_str = scriptPubKey_str + hex(element)[2:] + hex(len(scriptPubKey[i + 1]))[2:] 70 | # else: 71 | # scriptPubKey_str += element 72 | # 73 | # return scriptPubKey_str 74 | # 75 | # @staticmethod 76 | # def decode_scriptPubKey(scriptPubKey_str): 77 | # idx = 0 78 | # scriptPubKey = list() 79 | # while idx < len(scriptPubKey_str): 80 | # opcode = scriptPubKey_str[idx:idx + 2] 81 | # opcode = int('0x' + opcode, 16) 82 | # scriptPubKey.append(opcode) 83 | # if opcode == OP_DUP or opcode == OP_EQUALVERIFY or opcode == OP_CHECKSIG: 84 | # idx += 2 85 | # elif opcode == OP_HASH160: 86 | # idx += 2 87 | # count = int('0x' + scriptPubKey_str[idx:idx + 2], 16) 88 | # idx += 2 89 | # sha160_str = scriptPubKey_str[idx:idx + count] 90 | # scriptPubKey.append(sha160_str) 91 | # idx += count 92 | # return scriptPubKey 93 | 94 | @staticmethod 95 | def sha160(data): 96 | """ 97 | 先sha256,再ripemd160 98 | :param data: 99 | :return: 100 | """ 101 | sha = hashlib.sha256(data.encode('utf-8')) 102 | hash_256_value = sha.hexdigest() 103 | obj = hashlib.new('ripemd160', hash_256_value.encode('utf-8')) 104 | ripemd_160_value = obj.hexdigest() 105 | return ripemd_160_value 106 | 107 | @staticmethod 108 | def check_tx_script(data, scriptSig, scriptPubKey): 109 | """ 110 | 检查交易脚本是否有效 111 | :param data: 原数据 112 | :param scriptSig: 队列,解锁脚本: 113 | :param scriptPubKey: 队列,锁定脚本 114 | :return: 115 | """ 116 | stack = Stack() 117 | for element in scriptSig: 118 | stack.push(element) 119 | # stack.output() 120 | 121 | for element in scriptPubKey: 122 | if element == OP_DUP: 123 | top = stack.peek() 124 | stack.push(top) 125 | elif element == OP_HASH160: 126 | top = str(stack.pop()) 127 | stack.push(Script.sha160(top)) 128 | elif element == OP_EQUALVERIFY: 129 | top_1 = stack.pop() 130 | top_2 = stack.pop() 131 | if top_1 != top_2: 132 | return False 133 | elif element == OP_CHECKSIG: 134 | pubkey = stack.pop() 135 | signature = stack.pop() 136 | result = Script.verify(data, signature, pubkey) 137 | stack.push(result) 138 | else: 139 | stack.push(element) 140 | # stack.output() 141 | 142 | if stack.size() == 1 and stack.peek() == True: 143 | return True 144 | else: 145 | return False 146 | 147 | 148 | class Stack: 149 | def __init__(self): 150 | self.items = [] 151 | 152 | def is_empty(self): 153 | return len(self.items) == 0 154 | 155 | def push(self, item): 156 | self.items.append(item) 157 | 158 | def pop(self): 159 | return self.items.pop() 160 | 161 | def peek(self): 162 | if not self.is_empty(): 163 | return self.items[len(self.items) - 1] 164 | 165 | def size(self): 166 | return len(self.items) 167 | 168 | 169 | def get_address_from_ripemd160(ripemd_hash): 170 | Wallet.b58encode(unhexlify(ripemd_hash.decode('utf8'))) 171 | return Wallet.b58encode(unhexlify(ripemd_hash.decode('utf8'))) 172 | -------------------------------------------------------------------------------- /simulation_test.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import json 3 | import random 4 | import time 5 | import urllib2 6 | 7 | 8 | def bootstrap(address, seeds): 9 | data = { 10 | "seeds": seeds 11 | } 12 | req = urllib2.Request("http://" + address + "/bootstrap", 13 | json.dumps(data), 14 | {"Content-Type": "application/json"}) 15 | res_data = urllib2.urlopen(req) 16 | res = res_data.read() 17 | return res 18 | 19 | 20 | def run(): 21 | node1 = get_node_info("127.0.0.1:5000") 22 | node2 = get_node_info("127.0.0.1:5001") 23 | node3 = get_node_info("127.0.0.1:5002") 24 | 25 | node1_seeds = [ 26 | {"node_id": node2["node_id"], "ip":node2["ip"], "port":node2["port"]}, 27 | {"node_id": node3["node_id"], "ip": node3["ip"], "port": node3["port"]} 28 | ] 29 | print node1_seeds 30 | bootstrap("127.0.0.1:5000", node1_seeds) 31 | 32 | node2_seeds = [ 33 | {"node_id": node1["node_id"], "ip": node1["ip"], "port": node1["port"]}, 34 | {"node_id": node3["node_id"], "ip": node3["ip"], "port": node3["port"]} 35 | ] 36 | bootstrap("127.0.0.1:5001", node2_seeds) 37 | 38 | node3_seeds = [ 39 | {"node_id": node2["node_id"], "ip": node2["ip"], "port": node2["port"]}, 40 | {"node_id": node1["node_id"], "ip": node1["ip"], "port": node1["port"]} 41 | ] 42 | bootstrap("127.0.0.1:5002", node3_seeds) 43 | 44 | time.sleep(30) 45 | 46 | node1_wallet = node1["wallet"] 47 | node2_wallet = node2["wallet"] 48 | node3_wallet = node3["wallet"] 49 | 50 | while True: 51 | 52 | # node1 发送给node2 node3 53 | node1_balance = get_balance("127.0.0.1:5000", node1_wallet) 54 | node1_balance = node1_balance['balance'] 55 | if node1_balance > 0: 56 | amount = random.randint(1, node1_balance) 57 | print 'send from node1 to node2 with amount:'+str(amount) 58 | simulate_tx("127.0.0.1:5000", node1_wallet, node2_wallet, amount) 59 | time.sleep(random.randint(20,30)) 60 | 61 | node1_balance = get_balance("127.0.0.1:5000", node1_wallet) 62 | node1_balance = node1_balance['balance'] 63 | if node1_balance > 0: 64 | amount = random.randint(1, node1_balance) 65 | print 'send from node1 to node3 with amount:' + str(amount) 66 | simulate_tx("127.0.0.1:5000", node1_wallet, node3_wallet, amount) 67 | time.sleep(random.randint(20,30)) 68 | 69 | # node2 发送给node1 node3 70 | node2_balance = get_balance("127.0.0.1:5001", node2_wallet) 71 | node2_balance = node2_balance['balance'] 72 | if node2_balance > 0: 73 | amount = random.randint(1, node2_balance) 74 | print 'send from node2 to node1 with amount:' + str(amount) 75 | simulate_tx("127.0.0.1:5001", node2_wallet, node1_wallet, amount) 76 | time.sleep(random.randint(20,30)) 77 | 78 | node2_balance = get_balance("127.0.0.1:5001", node2_wallet) 79 | node2_balance = node2_balance['balance'] 80 | if node2_balance > 0: 81 | amount = random.randint(1, node2_balance) 82 | print 'send from node2 to node3 with amount:' + str(amount) 83 | simulate_tx("127.0.0.1:5001", node2_wallet, node3_wallet, amount) 84 | time.sleep(random.randint(20,30)) 85 | # 86 | # node3 发送给node1 node2 87 | node3_balance = get_balance("127.0.0.1:5002", node3_wallet) 88 | node3_balance = node3_balance['balance'] 89 | if node3_balance > 0: 90 | amount = random.randint(1, node3_balance) 91 | print 'send from node3 to node1 with amount:' + str(amount) 92 | simulate_tx("127.0.0.1:5002", node3_wallet, node1_wallet, amount) 93 | time.sleep(random.randint(20,30)) 94 | 95 | node3_balance = get_balance("127.0.0.1:5002", node3_wallet) 96 | node3_balance = node3_balance['balance'] 97 | if node3_balance > 0: 98 | amount = random.randint(1, node3_balance) 99 | print 'send from node3 to node2 with amount:' + str(amount) 100 | simulate_tx("127.0.0.1:5002", node3_wallet, node2_wallet, amount) 101 | time.sleep(random.randint(20,30)) 102 | 103 | time.sleep(10) 104 | 105 | 106 | def simulate_tx(address, sender, receiver, amount): 107 | data = { 108 | "sender": sender, 109 | "receiver": receiver, 110 | "amount": amount 111 | } 112 | 113 | req = urllib2.Request(url="http://" + address + "/transactions/new", 114 | headers={"Content-Type": "application/json"}, data=json.dumps(data)) 115 | res_data = urllib2.urlopen(req) 116 | res = res_data.read() 117 | return res 118 | 119 | 120 | def get_balance(address, wallet_addres): 121 | req = urllib2.Request(url="http://" + address + "/balance?address=" + wallet_addres, 122 | headers={"Content-Type": "application/json"}) 123 | 124 | res_data = urllib2.urlopen(req) 125 | res = res_data.read() 126 | return json.loads(res) 127 | 128 | 129 | def get_node_info(address): 130 | req = urllib2.Request(url="http://" + address + "/curr_node", 131 | headers={"Content-Type": "application/json"}) 132 | 133 | res_data = urllib2.urlopen(req) 134 | res = res_data.read() 135 | return json.loads(res) 136 | 137 | 138 | run() 139 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import rsa 3 | 4 | 5 | def create_genisus_keypair(): 6 | # 第一个节点的密钥对 7 | pubkey, privkey = rsa.newkeys(1024) 8 | with open('genisus_public.pem', 'w+') as f: 9 | f.write(pubkey.save_pkcs1().decode()) 10 | 11 | with open('genisus_private.pem', 'w+') as f: 12 | f.write(privkey.save_pkcs1().decode()) 13 | 14 | # 导入密钥 15 | with open('genisus_private.pem', 'r') as f: 16 | privkey = rsa.PrivateKey.load_pkcs1(f.read().encode()) 17 | 18 | with open('genisus_public.pem', 'r') as f: 19 | pubkey = rsa.PublicKey.load_pkcs1(f.read().encode()) 20 | 21 | # 明文 22 | message = 'hello world' 23 | 24 | # 公钥加密 25 | crypto = rsa.encrypt(message.encode(), pubkey) 26 | 27 | # 私钥解密 28 | message = rsa.decrypt(crypto, privkey).decode() 29 | print(message) 30 | 31 | 32 | # 明文 33 | message = 'hello world' 34 | # 导入密钥 35 | with open('genisus_private.pem', 'r') as f: 36 | privkey = rsa.PrivateKey.load_pkcs1(f.read().encode()) 37 | 38 | with open('genisus_public.pem', 'r') as f: 39 | pubkey = rsa.PublicKey.load_pkcs1(f.read().encode()) 40 | # 私钥签名 41 | signature = rsa.sign(message.encode(), privkey, 'SHA-1') 42 | 43 | # 公钥验证 44 | try: 45 | rsa.verify(message.encode(), signature, pubkey) 46 | except rsa.pkcs1.VerificationError: 47 | print 'invalid' -------------------------------------------------------------------------------- /transaction.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | import hashlib 4 | import json 5 | from binascii import hexlify 6 | 7 | import util 8 | from script import OP_DUP, OP_HASH160, Script, OP_EQUALVERIFY, OP_CHECKSIG 9 | from wallet import Wallet 10 | 11 | 12 | class Transaction(object): 13 | def __init__(self, txins, txouts, timestamp): 14 | """ 15 | 16 | :param txid: 交易id 17 | :param txin: 数组 18 | :param txout: 数组 19 | """ 20 | self.txins = txins 21 | self.txouts = txouts 22 | self.timestamp = timestamp 23 | self.txid = self.get_txid() 24 | 25 | def get_txid(self): 26 | value = str(self.timestamp) + ",".join(str(txin) for txin in self.txins) + ",".join( 27 | str(txout) for txout in self.txouts) 28 | sha = hashlib.sha256(value.encode('utf-8')) 29 | return str(sha.hexdigest()) 30 | 31 | def get_hash(self): 32 | sha = hashlib.sha256(self.__str__()) 33 | return sha.hexdigest() 34 | 35 | def is_coinbase(self): 36 | """ 37 | coinbase不存在输入,txins为None 38 | :return: 39 | """ 40 | return self.txins[0].prev_txid == None 41 | 42 | def json_output(self): 43 | output = { 44 | 'txid': self.txid, 45 | 'timestamp': self.timestamp, 46 | 'txins': [txin.json_output() for txin in self.txins], 47 | 'txouts': [txout.json_output() for txout in self.txouts] 48 | } 49 | return output 50 | 51 | def __str__(self): 52 | return json.dumps(self.json_output(), default=lambda obj: obj.__dict__, sort_keys=True, indent=4) 53 | 54 | 55 | class TxInput(object): 56 | """ 57 | 一个输入引用之前的一个输出 58 | """ 59 | 60 | def __init__(self, prev_txid, out_idx, signature, pubkey): 61 | """ 62 | 63 | :param prev_txid: 指向之前的交易id 64 | :param out_idx: 存储上一笔交易中所引用输出的索引 65 | :param script_sig: script_sig是一个脚本,提供了可解锁输出结构里面ScriptPubKey字段的数据(实际就是确认这个输出中公钥对应的私钥持有者是否本人)。 66 | """ 67 | self.prev_txid = prev_txid 68 | self.prev_tx_out_idx = out_idx 69 | self.signature = signature 70 | self.pubkey = pubkey 71 | 72 | def can_unlock_txoutput_with(self, address): 73 | """ 74 | 检测当前输入的 75 | :param address: 76 | :return: 77 | """ 78 | return Wallet.get_address(self.pubkey) == address 79 | 80 | def json_output(self): 81 | output = { 82 | 'prev_txid': self.prev_txid, 83 | 'prev_tx_out_idx': self.prev_tx_out_idx, 84 | 'signature': util.get_hash(self.signature) if self.signature != None else "", 85 | 'pubkey_hash': Script.sha160(str(self.pubkey)) if self.pubkey != None else "" 86 | } 87 | return output 88 | 89 | def __str__(self): 90 | return json.dumps(self.json_output(), default=lambda obj: obj.__dict__, sort_keys=True, indent=4) 91 | 92 | 93 | class TxOutput(object): 94 | """ 95 | 货币存储在TxOutput中,一个用户钱包的余额相当于某个地址下未使用过的TxOutput中value之和 96 | """ 97 | 98 | def __init__(self, value, pubkey_hash): 99 | """ 100 | 101 | :param value: 一定量的货币 102 | :param pubkey_hash: ,锁定脚本,要使用这个输出,必须要解锁该脚本。pubkey_hash=sha256(ripemd160(pubkey)) 103 | """ 104 | self.value = value 105 | self.pubkey_hash = pubkey_hash 106 | self.scriptPubKey = None 107 | 108 | self.lock(pubkey_hash) 109 | 110 | def get_scriptPubKey(self): 111 | return self.scriptPubKey 112 | 113 | def can_be_unlocked_with(self, address): 114 | """ 115 | 判断当前输出,address钱包地址是否可用 116 | :param address: 钱包地址 117 | :return: 118 | """ 119 | return self.pubkey_hash == hexlify(Wallet.b58decode(address)).decode('utf8') 120 | 121 | def lock(self, pubkey_hash): 122 | """ 123 | 锁定输出只有pubkey本人才能使用 124 | :param pubkey_hash: ,锁定脚本,要使用这个输出,必须要解锁该脚本。pubkey_hash=sha256(ripemd160(pubkey)) 125 | :return: 126 | """ 127 | self.scriptPubKey = [OP_DUP, OP_HASH160, pubkey_hash, OP_EQUALVERIFY, OP_CHECKSIG] 128 | 129 | def __str__(self): 130 | return json.dumps(self.json_output(), default=lambda obj: obj.__dict__, sort_keys=True, indent=4) 131 | 132 | def get_opcode_name(self, opcode): 133 | if opcode == OP_DUP: return "OP_DUP" 134 | if opcode == OP_HASH160: return "OP_HASH160" 135 | if opcode == OP_EQUALVERIFY: return "OP_EQUALVERIFY" 136 | if opcode == OP_CHECKSIG: return "OP_CHECKSIG" 137 | return opcode 138 | 139 | def json_output(self): 140 | output = { 141 | 'value': self.value, 142 | 'scriptPubKey': [self.get_opcode_name(opcode) for opcode in self.scriptPubKey] 143 | } 144 | return output 145 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import hashlib 3 | 4 | 5 | def calculate_hash(index, previous_hash, timestamp, merkleroot, nonce, difficulty): 6 | value = str(index) + str(previous_hash) + str(timestamp) + str(merkleroot) + str(nonce) + str(difficulty) 7 | sha = hashlib.sha256(value.encode('utf-8')) 8 | return str(sha.hexdigest()) 9 | 10 | 11 | def calculate_block_hash(block): 12 | return calculate_hash(index=block.index, 13 | previous_hash=block.previous_hash, 14 | timestamp=block.timestamp, 15 | merkleroot=block.merkleroot, 16 | nonce=block.nonce, 17 | difficulty=block.difficulty) 18 | 19 | 20 | def check_block(block): 21 | """ 22 | 校验区块 23 | :param block: 24 | :return: 25 | """ 26 | cal_hash = calculate_hash(index=block.index, 27 | previous_hash=block.previous_hash, 28 | timestamp=block.timestamp, 29 | merkleroot=block.merkleroot, 30 | nonce=block.nonce, 31 | difficulty=block.difficulty) 32 | 33 | if (cal_hash[0:block.difficulty] == '0' * block.difficulty) \ 34 | and (block.current_hash == calculate_block_hash(block)): 35 | return True 36 | else: 37 | return False 38 | 39 | 40 | def get_hash(data): 41 | sha = hashlib.sha256(data) 42 | return sha.hexdigest() 43 | -------------------------------------------------------------------------------- /wallet.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import hashlib 3 | from binascii import unhexlify, hexlify, Error 4 | 5 | import rsa 6 | 7 | 8 | class Wallet(object): 9 | """ 10 | 简化钱包,一个钱包只包含一个密钥对(公钥+私钥) 11 | """ 12 | 13 | def __init__(self, genisus_node): 14 | if genisus_node: 15 | pubkey, privkey = self.get_genisus_keypair() 16 | else: 17 | pubkey, privkey = rsa.newkeys(1024) 18 | 19 | self.pubkey = pubkey 20 | self.privkey = privkey 21 | self.address = Wallet.get_address(self.pubkey) 22 | 23 | def get_genisus_keypair(self): 24 | with open('genisus_public.pem', 'r') as f: 25 | pubkey = rsa.PublicKey.load_pkcs1(f.read().encode()) 26 | 27 | with open('genisus_private.pem', 'r') as f: 28 | privkey = rsa.PrivateKey.load_pkcs1(f.read().encode()) 29 | 30 | return pubkey, privkey 31 | 32 | @staticmethod 33 | def get_address(pubkey): 34 | """ 35 | 钱包地址 36 | :param pubkey: 对象,公钥 37 | :return: 38 | """ 39 | sha = hashlib.sha256(str(pubkey)) 40 | hash_256_value = sha.hexdigest() 41 | obj = hashlib.new('ripemd160', hash_256_value.encode('utf-8')) 42 | ripemd_160_bytes = obj.digest() 43 | return Wallet.b58encode(ripemd_160_bytes) 44 | 45 | @staticmethod 46 | def b58encode(b): 47 | """ 48 | 将bytes转换成base58字符串 49 | :param b: 50 | :return: 51 | """ 52 | b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 53 | n = int('0x0' + hexlify(b).decode('utf8'), 16) 54 | 55 | res = [] 56 | while n > 0: 57 | n, r = divmod(n, 58) 58 | res.append(b58_digits[r]) 59 | res = ''.join(res[::-1]) 60 | 61 | import sys 62 | czero = b'\x00' 63 | if sys.version > '3': 64 | # In Python3 indexing a bytes returns numbers, not characters. 65 | czero = 0 66 | pad = 0 67 | for c in b: 68 | if c == czero: 69 | pad += 1 70 | else: 71 | break 72 | return b58_digits[0] * pad + res 73 | 74 | @staticmethod 75 | def b58decode(s): 76 | """ 77 | 将base58编码的字符串转换成bytes数组 78 | :param s: base58编码后的字符串 79 | :return: 80 | """ 81 | b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 82 | if not s: 83 | return b'' 84 | 85 | n = 0 86 | for c in s: 87 | n *= 58 88 | if c not in b58_digits: 89 | raise Error('Character %r is not a valid base58 character' % c) 90 | digit = b58_digits.index(c) 91 | n += digit 92 | 93 | h = '%x' % n 94 | if len(h) % 2: 95 | h = '0' + h 96 | res = unhexlify(h.encode('utf8')) 97 | 98 | pad = 0 99 | for c in s[:-1]: 100 | if c == b58_digits[0]: 101 | pad += 1 102 | else: 103 | break 104 | return b'\x00' * pad + res 105 | --------------------------------------------------------------------------------