def make_genesis(self) -> HashedBlock: b = Block( 0, # block num None, # parent hash BlockConfig(DEFAULT_DIFFICULTY), []) hb = HashedBlock(b) while not hb.hash_meets_difficulty(): hb.replace_mining_entropy(os.urandom(32)) return hb
def request_successors(self, parent_hash: Hash, peer: Peer) -> Optional[List[HashedBlock]]: payload = {"parent_hex_hash": parent_hash.hex()} obj = self._peer_get(peer, "/blocks", payload) if obj is None: self.l.debug("No successors from peer", peer) return None if "blocks" not in obj: self.l.debug("Couldn't turn response into list of blocks", peer, obj) return None new_blocks: List[HashedBlock] = [] for block_obj in obj["blocks"]: try: b = HashedBlock.from_dict(block_obj) except KeyError as e: self.l.debug("Invalid block object from peer", peer, obj) return None new_blocks.append(b) return new_blocks
def add_block(self, block: HashedBlock) -> None: if self.storage.has_hash(block.mining_hash()): self.l.debug("Already have block", block) return elif self.block_is_valid(block): self.l.debug("Store block", block) self.storage.add_block(block) for txn in block.block.transactions: for out in txn.transaction.outputs: self.l.debug("Add UXTO", txn.txn_hash(), out.output_id) self.uxto_storage.add_output(txn.txn_hash(), out.to_addr, out.output_id) for inp in txn.transaction.inputs: out = self.get_transaction_output( inp.output_block_hash, inp.output_transaction_hash, inp.output_id) self.l.debug("Claim UXTO", inp.output_transaction_hash, inp.output_id) self.uxto_storage.mark_claimed(inp.output_transaction_hash, inp.output_id) self._cleanup_outstanding_transactions(block) self._abandon_blocks() else: raise InvalidBlockError("Block is invalid")
def get_by_hash(self, block_hash: Hash) -> Optional[HashedBlock]: c = self._conn.cursor() c.execute(GET_BY_HASH_SQL, (block_hash.raw_sha256, )) res = c.fetchone() if res: return HashedBlock.deserialize(res[0]) else: return None
def get_range(self, lower: int, upper: int) -> List[HashedBlock]: args = { "lower": lower, "upper": upper, } c = self._conn.cursor() c.execute(GET_RANGE_SQL, args) return list(map(lambda r: HashedBlock.deserialize(r[0]), c))
def block_should_be_abandoned(self, block: HashedBlock) -> bool: """ Either the block is in the master chain or it's within 10 blocks of the current head. """ return not (self.block_is_in_master_chain(block) or (self.get_head().block_num() - block.block_num() < ABANDONMENT_DEPTH))
def get_head(self) -> HashedBlock: c = self._conn.cursor() c.execute(GET_HEAD_SQL) res = c.fetchall() if len(res) == 0: raise Exception("No head") elif len(res) > 1: raise Exception("Multiple heads") else: return HashedBlock.deserialize(res[0][0])
def add_block(self, block: HashedBlock) -> None: c = self._conn.cursor() c.execute(GET_HEIGHT_SQL) height = c.fetchone()[0] if height is None: height = -1 if block.block_num() > height: is_head = True c.execute(CLEAR_HEAD_SQL) else: is_head = False if block.parent_mining_hash() is None: parent_hash = None else: parent_hash = block.parent_mining_hash().raw_sha256 args = { "hash": block.mining_hash().raw_sha256, "parent_hash": parent_hash, "block_num": block.block_num(), "is_head": is_head, "serialized": block.serialize(), } c.execute(ADD_BLOCK_SQL, args) self._conn.commit()
def __init__(self, storage: BlockChainStorage, transaction_storage: TransactionStorage, uxto_storage: UXTOStorage, cfg: Config) -> None: self.storage = storage self.transaction_storage = transaction_storage self.uxto_storage = uxto_storage self.l = DBLogger(self, cfg) genesis = storage.get_genesis() if genesis is None: self.l.info("Storage didn't have genesis. Added.") storage.add_block(HashedBlock.genesis())
def request_head(self, peer: Peer) -> Optional[HashedBlock]: head_hash = self.request_head_hash(peer) if head_hash is None: self.l.debug("Can't get head block from peer", peer) return None obj = self._peer_get(peer, "/block", {"hex_hash": head_hash.hex()}) try: h = HashedBlock.from_dict(obj) except KeyError as e: self.l.debug("Invalid block from peer {}".format(peer), exc=e) return None return h
def post(self) -> None: ser = self.request.body.decode('utf-8') hb = HashedBlock.deserialize(ser) if self.chain.storage.has_hash(hb.mining_hash()): self.set_status(200) self.write(util.generic_ok_response("already have that one")) return if not self.chain.storage.has_hash(hb.parent_mining_hash()): self.set_status(400) self.write(util.error_response("unknown parent")) return self.l.info("New block", hb.block_num(), hb.mining_hash()) self.chain.add_block(hb) self.set_status(200) self.write(util.generic_ok_response())
def request_block(self, block_hash: Hash, peer: Peer) -> Optional[HashedBlock]: obj = self._peer_get(peer, "/block", {"hex_hash": block_hash.hex()}) if obj is None: self.l.debug("No HTTP response from peer {}".format(peer)) return None if obj is None: self.l.debug("No block from peer", peer) return None try: hb = HashedBlock.from_dict(obj) except KeyError as e: self.l.debug("Invalid serialized block from peer {}".format(peer), exc=e) return None return hb
def block_is_valid(self, block: HashedBlock) -> bool: if self.storage.has_hash(block.parent_mining_hash()): parent = self.storage.get_by_hash(block.parent_mining_hash()) else: self.l.warn("Parent with hash {} not known".format( block.parent_mining_hash().hex())) return False if self.block_should_be_abandoned(block): self.l.warn("Block should be abandoned", block) return False difficulty = self.get_difficulty(parent) if block.block.block_config.difficulty != difficulty: self.l.warn( "Unexpected difficulty {} for block {} ({}), expected {}". format(block.block.block_config.difficulty, block.block_num(), block.mining_hash().hex(), difficulty)) return False if not block.hash_meets_difficulty(): self.l.warn("Block hash doesn't meet the set difficulty") return False if block.block_num() != parent.block_num() + 1: self.l.warn("Block number isn't parent+1") return False n_rewards = 0 for transaction in block.block.transactions: if not self.transaction_is_valid(transaction): self.l.warn("Transaction is invalid") return False if transaction.is_reward(): n_rewards += 1 if n_rewards != 1: self.l.warn( "Invalid number of rewards ({}) in transaction w/ sig{}". format(n_rewards, transaction.signature)) return False return True
def mine_on(self, parent: HashedBlock, difficulty: int) -> Optional[HashedBlock]: reward = self.make_reward() config = BlockConfig(difficulty) txns = self.transaction_storage.get_all_transactions() txns.append(reward) self.l.debug("Mining on {} txns".format(len(txns))) block = Block(parent.block_num() + 1, parent.mining_hash(), config, txns) hb = HashedBlock(block) start = time.time() while time.time() - start < 1.0: hb.replace_mining_entropy(os.urandom(32)) if hb.hash_meets_difficulty(): return hb return None
def get_by_parent_hash(self, parent_hash: Hash) -> List[HashedBlock]: c = self._conn.cursor() c.execute(GET_BY_PARENT_HASH_SQL, (parent_hash.raw_sha256, )) return list(map(lambda r: HashedBlock.deserialize(r[0]), c))
def genesis_is_valid(block: HashedBlock, l: DBLogger) -> bool: return block.mining_hash() == HashedBlock.genesis().mining_hash()
def get_by_block_num(self, block_num: int) -> List[HashedBlock]: c = self._conn.cursor() c.execute(GET_BY_NUM_SQL, (block_num, )) return list(map(lambda r: HashedBlock.deserialize(r[0]), c))
def get_all_non_genesis_in_order(self) -> List[HashedBlock]: c = self._conn.cursor() c.execute(GET_ALL_NON_GENESIS_IN_ORDER_SQL) return list(map(lambda r: HashedBlock.deserialize(r[0]), c))