Esempio n. 1
0
 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
Esempio n. 2
0
    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
Esempio n. 3
0
    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")
Esempio n. 4
0
 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
Esempio n. 5
0
 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))
Esempio n. 6
0
 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))
Esempio n. 7
0
    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])
Esempio n. 8
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()
Esempio n. 9
0
    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())
Esempio n. 10
0
    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
Esempio n. 11
0
    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())
Esempio n. 12
0
    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
Esempio n. 13
0
    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
Esempio n. 14
0
    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
Esempio n. 15
0
 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))
Esempio n. 16
0
 def genesis_is_valid(block: HashedBlock, l: DBLogger) -> bool:
     return block.mining_hash() == HashedBlock.genesis().mining_hash()
Esempio n. 17
0
 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))
Esempio n. 18
0
 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))