Exemple #1
0
    def handle_get_data_message_received(
            self, header: MessageHeader,
            get_data_message: GetDataMessage) -> None:
        if get_data_message.data_type != DATA_BLOCK:
            raise NotImplementedError(
                "We can only deal w/ DATA_BLOCK GetDataMessage objects for now"
            )

        coinstate = self.local_peer.chain_manager.coinstate

        if get_data_message.hash not in coinstate.block_by_hash:
            # we simply silently ignore GetDataMessage for hashes we don't have... future work: inc banscore, or ...
            self.local_peer.logger.debug(
                "%15s ConnectedRemotePeer.handle_data_message_received for unknown hash %s"
                % (self.host, human(get_data_message.hash)))
            return

        data_message = DataMessage(
            DATA_BLOCK, coinstate.block_by_hash[get_data_message.hash])

        self.local_peer.logger.debug(
            "%15s ConnectedRemotePeer.handle_data_message_received for hash %s h. %s"
            % (self.host, human(get_data_message.hash),
               coinstate.block_by_hash[get_data_message.hash].height))
        self.send_message(data_message, prev_header=header)
Exemple #2
0
    def handle_inventory_message_received(self, header: MessageHeader,
                                          message: InventoryMessage) -> None:
        self.local_peer.logger.info(
            "%15s ConnectedRemotePeer.handle_inventory_message_received(%d)" %
            (self.host, len(message.items)))
        if len(message.items) > 0:
            self.local_peer.logger.info(
                "%15s %s .. %s" % (self.host, human(
                    message.items[0].hash), human(message.items[-1].hash)))

        if len(message.items) > GET_BLOCKS_INVENTORY_SIZE:
            raise Exception("Inventory msg too big")

        if message.items == []:
            self.local_peer.logger.info(
                "%15s ConnectedRemotePeer.last_empty_inventory_response_at set"
                % self.host)
            self.last_empty_inventory_response_at = int(
                time())  # TODO time() as a pass-along?
            self.waiting_for_inventory = False
            return

        self.inventory_messages.append(InventoryMessageState(header, message))
        self.check_inventory_messages()

        # speed optimization: go ahead and ask for more inventory now, there is no reason to wait
        get_blocks_message = GetBlocksMessage([message.items[-1].hash])
        self.send_message(get_blocks_message, prev_header=header)
Exemple #3
0
    def add_transaction_to_pool(self, transaction: Transaction) -> bool:
        with self.lock:
            self.local_peer.logger.info(
                "%15s ChainManager.add_transaction_to_pool(%s)" %
                ("", human(transaction.hash())))

            try:
                validate_non_coinbase_transaction_by_itself(transaction)

                assert self.coinstate.current_chain_hash

                validate_non_coinbase_transaction_in_coinstate(
                    transaction, self.coinstate.current_chain_hash,
                    self.coinstate)

                # Horribly inefficiently implemented (AKA 'room for improvement)
                validate_no_duplicate_output_references_in_transactions(
                    self.transaction_pool + [transaction])

                #  we don't do validate_no_duplicate_transactions here (assuming it's just been done before
                #  add_transaction_to_pool).

            except ValidateTransactionError as e:
                # TODO: dirty hack at this particular point... to allow for e.g. out-of-order transactions to not take
                # down the whole peer, but this should more specifically match for a short list of OK problems.
                self.local_peer.logger.warning("%15s INVALID transaction %s" %
                                               ("", str(e)))
                self.local_peer.disk_interface.save_transaction_for_debugging(
                    transaction)

                return False  # not successful

            self.transaction_pool.append(transaction)

        return True  # successfully added
Exemple #4
0
    def show_chain_stats(self) -> None:
        coinstate = self.chain_manager.coinstate

        def get_block_timespan_factor(n: int) -> float:
            # Current block duration over past n block as a factor of DESIRED_BLOCK_TIMESPAN, e.g. 0.5 for twice desired
            # speed
            diff = coinstate.head(
            ).timestamp - coinstate.at_head.block_by_height[
                coinstate.head().height - n].timestamp
            return diff / (DESIRED_BLOCK_TIMESPAN * n)  # type: ignore

        def get_network_hash_rate(n: int) -> float:
            total_over_blocks = sum(
                calc_work(coinstate.at_head.block_by_height[
                    coinstate.head().height - i].target) for i in range(n))

            diff = coinstate.head(
            ).timestamp - coinstate.at_head.block_by_height[
                coinstate.head().height - n].timestamp

            return total_over_blocks / diff  # type: ignore

        print("WASTELAND STATS")
        print("Current target: ", human(coinstate.head().target))
        print("Current work:   ", calc_work(coinstate.head().target))
        print("Timespan factor:", get_block_timespan_factor(100))
        print("Hash rate:      ", get_network_hash_rate(100))
    def handle_get_blocks_message_received(self, header: MessageHeader, message: GetBlocksMessage) -> None:
        self.local_peer.logger.info("%15s ConnectedRemotePeer.handle_get_blocks_message_received()" % self.host)

        coinstate = self.local_peer.chain_manager.coinstate
        self.local_peer.logger.debug("%15s ... at coinstate %s" % (self.host, coinstate))
        for potential_start_hash in message.potential_start_hashes:
            self.local_peer.logger.debug("%15s ... psh %s" % (self.host, human(potential_start_hash)))
            if potential_start_hash in coinstate.block_by_hash:
                start_height = coinstate.block_by_hash[potential_start_hash].height + 1  # + 1: sent hash is last known
                if start_height not in coinstate.by_height_at_head():
                    # we have no new info
                    self.local_peer.logger.debug("%15s ... no new info" % self.host)
                    self.send_message(InventoryMessage([]), prev_header=header)
                    return

                if coinstate.by_height_at_head()[start_height].previous_block_hash == potential_start_hash:
                    # this final if checks that this particular potential_start_hash is on our active chain
                    break
        else:  # no break
            start_height = 1  # genesis is last known
        max_height = coinstate.head().height + 1  # + 1: range is exclusive, but we need to send this last block also
        items = [
            InventoryItem(DATA_BLOCK, coinstate.by_height_at_head()[height].hash())
            for height in range(start_height, min(start_height + GET_BLOCKS_INVENTORY_SIZE, max_height))
        ]
        self.local_peer.logger.info("%15s ... returning from start_height=%d, %d items"
                                    % (self.host, start_height, len(items)))
        self.send_message(InventoryMessage(items), prev_header=header)
Exemple #6
0
    def handle_block_received(self, header: MessageHeader,
                              message: DataMessage) -> None:

        block: Block = message.data  # type: ignore

        coinstate_prior = self.local_peer.chain_manager.coinstate

        block_hash = block.hash()
        self.remove_from_inventory(block_hash)

        if block_hash not in coinstate_prior.block_by_hash:

            if block.header.summary.previous_block_hash not in coinstate_prior.block_by_hash:
                # This is not common, so it doesn't need special handling.
                self.local_peer.logger.info(
                    "%15s at height=%d, block received out of order for height=%d: %s"
                    % (self.host, coinstate_prior.head().height, block.height,
                       human(block_hash)))
                return

            validate_block_by_itself(block, int(time()))
            coinstate_changed = coinstate_prior.add_block_no_validation(block)

            if header.in_response_to == 0 or block.height % IBD_VALIDATION_SKIP == 0:
                # Validation is very slow, and we don't have to validate every block in a blockchain, so
                # during IBD, we only validate every Nth block where N := IBD_VALIDATION_SKIP.
                # Because the BLOCKS are part of a CHAIN of hashes, every valid block[n] guarantees a valid
                # block[n-1]. Just to keep things clean, we avoid writing unvalidated blocks to disk until
                # their next "validated descendent" is encountered (this is unnecessary, but neat).
                # During normal operation (non-IBD) we just validate every block because we're not in a hurry.
                try:
                    validate_block_in_coinstate(block,
                                                coinstate_prior)  # very slow

                except Exception:
                    self.local_peer.logger.info(
                        "%15s INVALID block: %s" %
                        (self.host, traceback.format_exc()))
                    if self.local_peer.chain_manager.last_known_valid_coinstate:
                        self.local_peer.chain_manager.set_coinstate(
                            self.local_peer.chain_manager.
                            last_known_valid_coinstate)
                    return

                self.local_peer.chain_manager.set_coinstate(coinstate_changed,
                                                            validated=True)
                self.local_peer.disk_interface.write_chain_cache_to_disk(
                    coinstate_changed)
            else:
                self.local_peer.chain_manager.set_coinstate(coinstate_changed,
                                                            validated=False)

            if block == coinstate_changed.head(
            ) and header.in_response_to == 0:
                # "header.in_response_to == 0" is being used as a bit of a proxy for "not in IBD" here, but it would be
                # better to check for that state more explicitly. We don't want to broadcast blocks while in IBD,
                # because in that state the fact that some block is our new head doesn't mean at all that we're talking
                # about the real chain's new head, and only the latter is relevant to the rest of the world.
                self.local_peer.network_manager.broadcast_block(block)
Exemple #7
0
def main() -> None:
    parser = DefaultArgumentParser()
    parser.add_argument("annotation", help="Some text to help you remember a meaning for this receive address.")
    args = parser.parse_args()
    configure_logging_from_args(args)

    wallet = open_or_init_wallet()
    public_key = wallet.get_annotated_public_key(args.annotation)
    save_wallet(wallet)

    print("SKE" + human(public_key) + "PTI")
Exemple #8
0
def read_chain_from_disk() -> CoinState:
    if os.path.isfile('chain.cache'):
        print("Reading cached chain")
        with open('chain.cache', 'rb') as file:
            coinstate = CoinState.load(lambda: pickle.load(file))
    else:
        coinstate = CoinState.zero()

    rewrite = False

    if os.path.isdir('chain'):
        # the code below is no longer needed by normal users, but some old testcases still rely on it:
        for filename in sorted(os.listdir('chain')):
            (height_str, hash_str) = filename.split("-")
            (height, hash) = (int(height_str), computer(hash_str))

            if hash not in coinstate.block_by_hash:

                if height % 1000 == 0:
                    print(filename)

                if os.path.getsize(f"chain/{filename}") == 0:
                    print("Stopping at empty block file: %s" % filename)
                    break

                with open(Path("chain") / filename, 'rb') as f:
                    try:
                        block = Block.stream_deserialize(f)
                    except Exception as e:
                        raise Exception("Corrupted block on disk: %s" % filename) from e
                    try:
                        coinstate = coinstate.add_block_no_validation(block)
                    except Exception:
                        print("Failed to add block at height=%d, previous_hash=%s"
                              % (block.height, human(block.header.summary.previous_block_hash)))
                        break

                rewrite = True

    if rewrite:
        DiskInterface().write_chain_cache_to_disk(coinstate)

    return coinstate
Exemple #9
0
 def broadcast_block(self, block: Block) -> None:
     self.local_peer.logger.info("%15s ChainManager.broadcast_block(%s)" %
                                 ("", human(block.hash())))
     self.broadcast_message(DataMessage(DATA_BLOCK, block))
Exemple #10
0
def build_explorer(coinstate):
    assert os.getenv("EXPLORER_DIR")
    explorer_dir = Path(os.getenv("EXPLORER_DIR"))

    public_key_balances_2 = immutables.Map()

    for height in range(coinstate.head().height + 1):
        print("Block", height)
        block = coinstate.at_head.block_by_height[height]
        potential_message = block.transactions[0].inputs[0].signature.signature

        if all([(32 <= b < 127) or (b == 10) for b in potential_message]):
            msg = "```\n" + str(potential_message, encoding="ascii") + "\n```"
        else:
            msg = ""

        unspent_transaction_outs = get_unspent_transaction_outs_before_block(coinstate, block)
        public_key_balances_2 = build_pkb2_block(coinstate, block, public_key_balances_2)

        with open(explorer_dir / (human(block.hash()) + '.md'), 'w') as block_f:

            block_f.write(f"""## Block {human(block.hash())}

Attribute | Value
--- | ---
Height | {block.height}
Hash | {human(block.hash())}
Timestamp | {datetime.fromtimestamp(block.timestamp, tz=timezone.utc).isoformat()}
Target | {human(block.target)}
Merke root | {human(block.merkle_root_hash)}
Nonce | {block.nonce}

{msg}

### Transactions

Hash | Amount
--- | ---
""")

            for transaction in block.transactions:
                h = human(transaction.hash())
                v = show_coin(sum(o.value for o in transaction.outputs))
                block_f.write(f"""[{h}]({h}.md) | {v} \n""")

                with open(explorer_dir / (human(transaction.hash()) + ".md"), 'w') as transaction_f:
                    transaction_f.write(f"""## Transaction {human(transaction.hash())}

In block [{human(block.hash())}]({human(block.hash())}.md)

### Inputs

Transaction | Output Index | Value | Address
--- | --- | --- | ---
""")
                    for input in transaction.inputs:
                        output_reference = input.output_reference
                        if output_reference.hash != 32 * b'\x00':
                            output = unspent_transaction_outs[output_reference]
                            h = human(output_reference.hash)
                            v = show_coin(output.value)
                            a = "SKE" + human(output.public_key.public_key) + "PTI"

                            transaction_f.write(f"""[{h}]({h}.md) | {output_reference.index} | """
                                                f"""{v} | [{a}]({a}.md)\n""")
                        else:
                            h = human(output_reference.hash)
                            v = ""
                            a = "Thin Air"

                            transaction_f.write(f"""{h} | {output_reference.index} | """
                                                f"""{v} | {a}\n""")

                    transaction_f.write("""### Outputs

Value | Address
--- | ---
""")
                    for output in transaction.outputs:
                        v = show_coin(output.value)
                        a = "SKE" + human(output.public_key.public_key) + "PTI"

                        transaction_f.write(f"""{v} | [{a}]({a}.md)\n""")

    for pk, pkb2 in public_key_balances_2.items():
        v = show_coin(pkb2.value)
        address = "SKE" + human(pk.public_key) + "PTI"
        with open(explorer_dir / (address + ".md"), 'w') as address_f:
            address_f.write(f"""## {address}

Current balance: {v}
(as of block {coinstate.head().height})

## Received in

Transaction | Output Index
--- | ---
""")
            for output_reference in pkb2.all_output_references:
                h = human(output_reference.hash)
                address_f.write(f"""[{h}]({h}.md) | {output_reference.index}\n""")

            if len(pkb2.spent_in_transactions) > 0:
                address_f.write("""
## Spent in

Transaction | ...
--- | ---
""")

                for transaction in pkb2.spent_in_transactions:
                    address_f.write(f"""{human(transaction.hash())} | ...\n""")

            else:
                address_f.write("""
## Spent in

-- not spent --
""")
Exemple #11
0
 def save_transaction_for_debugging(self, transaction: Transaction) -> None:
     with open("/tmp/%s.transaction" % human(transaction.hash()),
               'wb') as f:
         f.write(transaction.serialize())
Exemple #12
0
def read_chain_from_disk() -> CoinState:
    if os.path.isfile('chain.cache'):
        print("Reading cached chain")
        with open('chain.cache', 'rb') as file:
            coinstate = CoinState.load(lambda: pickle.load(file))
    else:
        try:
            print("Pre-download blockchain from trusted source to 'chain.zip'")
            with urllib.request.urlopen(TRUSTED_BLOCKCHAIN_ZIP) as resp:
                with open('chain.zip', 'wb') as outfile:
                    outfile.write(resp.read())
            print("Reading initial chain from zipfile")
            coinstate = CoinState.zero()
            with zipfile.ZipFile('chain.zip') as zip:
                for entry in zip.infolist():
                    if not entry.is_dir():
                        filename = entry.filename.split('/')[1]
                        height = int(filename.split("-")[0])
                        if height % 1000 == 0:
                            print(filename)

                        data = zip.read(entry)
                        block = Block.stream_deserialize(BytesIO(data))
                        coinstate = coinstate.add_block_no_validation(block)
        except Exception:
            print(
                "Error reading zip file. We'll start with an empty blockchain instead."
                + traceback.format_exc())
            coinstate = CoinState.zero()

    rewrite = False

    if os.path.isdir('chain'):
        # the code below is no longer needed by normal users, but some old testcases still rely on it:
        for filename in sorted(os.listdir('chain')):
            (height_str, hash_str) = filename.split("-")
            (height, hash) = (int(height_str), computer(hash_str))

            if hash not in coinstate.block_by_hash:

                if height % 1000 == 0:
                    print(filename)

                if os.path.getsize(f"chain/{filename}") == 0:
                    print("Stopping at empty block file: %s" % filename)
                    break

                with open(Path("chain") / filename, 'rb') as f:
                    try:
                        block = Block.stream_deserialize(f)
                    except Exception as e:
                        raise Exception("Corrupted block on disk: %s" %
                                        filename) from e
                    try:
                        coinstate = coinstate.add_block_no_validation(block)
                    except Exception:
                        print(
                            "Failed to add block at height=%d, previous_hash=%s"
                            %
                            (block.height,
                             human(block.header.summary.previous_block_hash)))
                        break

                rewrite = True

    if rewrite:
        DiskInterface().write_chain_cache_to_disk(coinstate)

    return coinstate