def __init__(self, node, blockchain, txpool, process_pool, log): Observable.__init__(self) self.node = node self.blockchain = blockchain self.txpool = txpool self.log = log self.items_to_download = deque() self.requested_tx = {} self.orphan_tx = {} self.process_pool = process_pool self.txverifier = TxVerifier(self.blockchain.database.runmode) node.subscribe((VersionExchangeService.EVT_MESSAGE, MSG_INV), self.on_inv) node.subscribe((VersionExchangeService.EVT_MESSAGE, MSG_TX), self.on_tx) self.node.subscribe (Node.EVT_DISCONNECTED, self.on_peer_disconnected)
def __init__(self, runmode): self.runmode = runmode self.block_serializer = BlockSerializer() self.tx_verifier = TxVerifier(self.runmode)
class BlockVerifier(): def __init__(self, runmode): self.runmode = runmode self.block_serializer = BlockSerializer() self.tx_verifier = TxVerifier(self.runmode) """ basic_check: run tests that don't require context or the block parent. """ def basic_checks(self, hash, block): self.check_size_limit(hash, block) self.check_proof_of_work(hash, block) self.check_coinbase(hash, block) self.check_transactions(hash, block) self.check_merkle_root(hash, block) def check_size_limit(self, hash, block): #FIXME: don't serialize the block here (add get_serialize_size() in serialize objects #or cache previoulsy serialized block) if (not block.rawdata): block.rawdata = self.block_serializer.serialize(block) if len(block.rawdata) > MAX_BLOCK_SIZE: raise Exception("block size limit exceeded") def check_proof_of_work(self, hash, block): target = block.blockheader.target() if (target <= Uint256.zero() or target > PROOF_OF_WORK_LIMIT[self.runmode]): raise Exception("proof of work: value out of range : %x" % (block.blockheader.bits)) if (hash > target): raise Exception("proof of work: hash doesn't match target hash:%s, target:%s" % (hash, target)) # not out of context: (check elsewhere) # def check_timestamp(self, hash, block): # block.blockheader.time > time.time def check_coinbase(self, hash, block): if not len(block.transactions): raise Exception("Block has no transactions" ) if not block.transactions[0].iscoinbase(): raise Exception("block's first transactions is not coinbase" ) for tx in block.transactions[1:]: if tx.iscoinbase(): raise Exception("more than one coinbase" ) def check_transactions(self, hash, block): for tx in block.transactions: err = self.tx_verifier.basic_checks(tx) if err: return err def check_merkle_root(self, hash, block): merkle = compute_merkle_root(block.transactions) if merkle != block.blockheader.hash_merkle: raise Exception("merkel root incorrect for block %s: %s != %s" % (str(hash), str(merkle), str(block.blockheader.hash_merkle)) ) """ accept_block: check block after finding the parent block. """ #AcceptBlock: main.cpp:1445 def accept_block(self, hash, block, blockchain): prevblockhandle = blockchain.get_block_handle(block.blockheader.hash_prev) self.check_target(blockchain, prevblockhandle, hash, block) self.check_timestamp(blockchain, prevblockhandle, hash, block) self.check_tx_finalized(prevblockhandle, hash, block) self.check_checkpoints(prevblockhandle, hash, block) def check_target(self, blockchain, prevblockhandle, hash, block): #Check proof of work target if (blockchain.get_next_work_required(prevblockhandle.hash, block) != block.blockheader.bits): raise Exception("Incorrect difficulty target, found:%08x != required:%08x in block %s" % (block.blockheader.bits, blockchain.get_next_work_required(prevblockhandle.hash, block), hash) ) def check_timestamp(self, blockchain, prevblockhandle, hash, block): if (block.blockheader.time <= blockchain.get_median_time_past(prevblockhandle.hash)): raise Exception("block's timestamp is smaller than the median of past %d block: %d <= %d" % (MEDIAN_TIME_SPAN, prevblockhandle.get_blockheader().time , prevblockhandle.get_median_time_past())) def check_tx_finalized(self, prevblockhandle, hash, block): height = prevblockhandle.get_height()+1 #Check that all transactions are finalized (can this be done somewhere else?) for tx in block.transactions: if not tx.isfinal(height, block.blockheader.time): raise Exception("transaction is not final: %s" % str(hash_tx(tx))) def check_checkpoints(self, prevblockhandle, hash, block): height = prevblockhandle.get_height()+1 if not verify_checkpoints(self.runmode, height, hash): raise Exception("blockchain checkpoint error: height:%d value:%s != %s" % (height, hash, str(get_checkpoint(self.runmode, height))))
class TxDownloadService(Observable): EVT_ADDED_ORPHAN_TX = Observable.createevent() EVT_REMOVED_ORPHAN_TX = Observable.createevent() def __init__(self, node, blockchain, txpool, process_pool, log): Observable.__init__(self) self.node = node self.blockchain = blockchain self.txpool = txpool self.log = log self.items_to_download = deque() self.requested_tx = {} self.orphan_tx = {} self.process_pool = process_pool self.txverifier = TxVerifier(self.blockchain.database.runmode) node.subscribe((VersionExchangeService.EVT_MESSAGE, MSG_INV), self.on_inv) node.subscribe((VersionExchangeService.EVT_MESSAGE, MSG_TX), self.on_tx) self.node.subscribe (Node.EVT_DISCONNECTED, self.on_peer_disconnected) def on_inv(self, event): peer, message = event.handler, event.message for item in message.items: if item.type == INV_TX: if (not self.txpool.contains_transaction(item.hash) and not item.hash in self.requested_tx and not item.hash in self.orphan_tx): self.requested_tx[item.hash] = peer self.log.info("Downloading transactions from %s: %s" % (str(peer), str(item))) self.node.send_message(peer, GetdataMessage([item])) @asynch_method def on_tx(self, event): peer, message = event.handler, event.message hash = hash_tx(message.tx) self.log.info("on_tx hash:%s" % (str(hash))) if (hash not in self.requested_tx): self.misbehaving(peer, "peer sending unrequest 'tx'") return del self.requested_tx[hash] #check for orphan transactions try: yield self.verified_add_tx(message.tx) except Exception as e: self.log.error("peer %s sending errorneous 'tx': %s" %(str(peer), str(e))) self.misbehaving(peer, "peer sending errorneous 'tx': ") #relay transaction def misbehaving(self, peer, reason): self.cleanup_peer_tasks(peer) self.node.misbehaving(peer, reason) def cleanup_peer_tasks(self, peer): toremove = [txhash for txhash, p in self.requested_tx.iteritems() if p==peer] for h in toremove: del self.requested_tx[txhash] def on_peer_disconnected(self, event): self.cleanup_peer_tasks(event.handler) @asynch_method def verified_add_tx(self, tx): txhash = hash_tx(tx) if self.txpool.contains_transaction(txhash): return self.txverifier.basic_checks(tx) if tx.iscoinbase(): raise Exception("Coinbase transactions aren't allowed in memory pool") #check for orphan transactions. contains_txins = [self.blockchain.contains_transaction(txin.previous_output.hash) for txin in tx.in_list] if not all(contains_txins): self.log.info("Adding orphan tx %s" % str(txhash)) self.orphan_tx[txhash] = tx self.fire(self.EVT_ADDED_ORPHAN_TX, hash=txhash) return self.log.info("Connecting tx %s" % str(txhash)) self.blockchain.connect_pool_tx(tx, self.blockchain.get_height() + 1, self.process_pool) self.log.info("Adding tx %s" % str(tx)) self.txpool.add_tx(txhash, tx) yield
class BlockVerifier(): def __init__(self, runmode): self.runmode = runmode self.block_serializer = BlockSerializer() self.tx_verifier = TxVerifier(self.runmode) """ basic_check: run tests that don't require context or the block parent. """ def basic_checks(self, hash, block): self.check_size_limit(hash, block) self.check_proof_of_work(hash, block) self.check_coinbase(hash, block) self.check_transactions(hash, block) self.check_merkle_root(hash, block) def check_size_limit(self, hash, block): #FIXME: don't serialize the block here (add get_serialize_size() in serialize objects #or cache previoulsy serialized block) if (not block.rawdata): block.rawdata = self.block_serializer.serialize(block) if len(block.rawdata) > MAX_BLOCK_SIZE: raise Exception("block size limit exceeded") def check_proof_of_work(self, hash, block): target = block.blockheader.target() if (target <= Uint256.zero() or target > PROOF_OF_WORK_LIMIT[self.runmode]): raise Exception("proof of work: value out of range : %x" % (block.blockheader.bits)) if (hash > target): raise Exception( "proof of work: hash doesn't match target hash:%s, target:%s" % (hash, target)) # not out of context: (check elsewhere) # def check_timestamp(self, hash, block): # block.blockheader.time > time.time def check_coinbase(self, hash, block): if not len(block.transactions): raise Exception("Block has no transactions") if not block.transactions[0].iscoinbase(): raise Exception("block's first transactions is not coinbase") for tx in block.transactions[1:]: if tx.iscoinbase(): raise Exception("more than one coinbase") def check_transactions(self, hash, block): for tx in block.transactions: err = self.tx_verifier.basic_checks(tx) if err: return err def check_merkle_root(self, hash, block): merkle = compute_merkle_root(block.transactions) if merkle != block.blockheader.hash_merkle: raise Exception( "merkel root incorrect for block %s: %s != %s" % (str(hash), str(merkle), str(block.blockheader.hash_merkle))) """ accept_block: check block after finding the parent block. """ #AcceptBlock: main.cpp:1445 def accept_block(self, hash, block, blockchain): prevblockhandle = blockchain.get_block_handle( block.blockheader.hash_prev) self.check_target(blockchain, prevblockhandle, hash, block) self.check_timestamp(blockchain, prevblockhandle, hash, block) self.check_tx_finalized(prevblockhandle, hash, block) self.check_checkpoints(prevblockhandle, hash, block) def check_target(self, blockchain, prevblockhandle, hash, block): #Check proof of work target if (blockchain.get_next_work_required(prevblockhandle.hash, block) != block.blockheader.bits): raise Exception( "Incorrect difficulty target, found:%08x != required:%08x in block %s" % (block.blockheader.bits, blockchain.get_next_work_required(prevblockhandle.hash, block), hash)) def check_timestamp(self, blockchain, prevblockhandle, hash, block): if (block.blockheader.time <= blockchain.get_median_time_past( prevblockhandle.hash)): raise Exception( "block's timestamp is smaller than the median of past %d block: %d <= %d" % (MEDIAN_TIME_SPAN, prevblockhandle.get_blockheader().time, prevblockhandle.get_median_time_past())) def check_tx_finalized(self, prevblockhandle, hash, block): height = prevblockhandle.get_height() + 1 #Check that all transactions are finalized (can this be done somewhere else?) for tx in block.transactions: if not tx.isfinal(height, block.blockheader.time): raise Exception("transaction is not final: %s" % str(hash_tx(tx))) def check_checkpoints(self, prevblockhandle, hash, block): height = prevblockhandle.get_height() + 1 if not verify_checkpoints(self.runmode, height, hash): raise Exception( "blockchain checkpoint error: height:%d value:%s != %s" % (height, hash, str(get_checkpoint(self.runmode, height))))