示例#1
0
def start_mining(block_queue, transaction_queue, public_key, private_key):
    blockchain = BlockChain([args.ip_other])
    miner = Miner(blockchain, public_key)
    miner_status = False  # Checks if miner is ready to send

    # Variables for double-spending attack:
    start_attack = False
    announced_attack = False
    cease_attacks = False
    unsent_bad_tx = False
    sent_tx = False
    ignore_transactions = []
    private_fork = []
    skip_mine_count = 0
    trigger_block = 3
    trigger_block_hash = ""
    mine_private_blocks = False
    private_last_hash = ""
    original_blocks = []

    while True:
        blockchain.resolve()
        if args.attacker and start_attack:
            # Make sure new blocks don't include the bad_tx from attacker
            merkletree, ledger = miner.create_merkle(
                transaction_queue, tx_to_ignore=ignore_transactions)
        else:  # Not attacker or is an attacker and not attacking
            merkletree, ledger = miner.create_merkle(transaction_queue)

        if not cease_attacks:
            if args.attacker:
                # 'and sent_tx' ensures attacker only attacks at least one block after bad_tx is sent
                start_attack = len(
                    blockchain.cleaned_keys) > trigger_block and sent_tx
            else:
                start_attack = len(blockchain.cleaned_keys) > trigger_block

            # Send a bad transaction after trigger_block number of blocks in chain
            unsent_bad_tx = len(blockchain.cleaned_keys) == trigger_block

            # This if statement should only be True once
            # Send transaction with intent to double spend
            if args.attacker and unsent_bad_tx:
                bad_tx = Transaction(public_key, SigningKey.generate().get_verifying_key(), 50,
                                     "give me the goods", sender_pk=private_key).to_json().encode()
                print("Sending transaction with intent to double-spend...")
                print("Transaction sent.")
                ignore_transactions.append(bad_tx)
                unsent_bad_tx = False
                sent_tx = True

        while True:
            miner_status = False
            # This if statement should only be True once
            # Start of attack
            if not announced_attack and args.attacker and start_attack:
                # Take the hash of the block before the bad_tx
                trigger_block_hash = blockchain.cleaned_keys[trigger_block-1]
                private_last_hash = trigger_block_hash

                # Used to track which blocks to ignore in trying to build new longest chain
                original_blocks = copy.deepcopy(
                    blockchain.cleaned_keys[trigger_block:])
                print("=============\nSTART ATTACK!\n============")
                announced_attack = True

                # Generate new public key and empty out balance from old public key
                new_private_key = create_key()
                new_public_key = new_private_key.get_verifying_key()
                try:
                    print(
                        "Double-spending: Making transaction to empty out old account...")
                    empty_old_account = Transaction(public_key, new_public_key, amount=ledger.get_balance(
                        public_key), comment="transferring all money out", sender_pk=private_key)
                    print("sent transaction")
                except AssertionError:
                    # Old account already empty
                    pass
                public_key = new_public_key
                private_key = new_private_key

                # Take on a new identity
                miner = Miner(blockchain, new_public_key)
                # Need to create merkle again so coinbase goes to new_public_key
                merkletree, ledger = miner.create_merkle(
                    transaction_queue, tx_to_ignore=ignore_transactions)

            # If attack starts, slow down honest miner
            if not args.attacker and start_attack:
                if skip_mine_count % 500 == 0:
                    miner_status = miner.mine(merkletree, ledger)
                    # Keep range of skip_mine_count within (0,500]
                    skip_mine_count = 0
                skip_mine_count += 1
            elif args.attacker and start_attack:
                # Start mining from block before bad_tx
                miner_status = miner.mine_from_old_block(
                    merkletree, ledger, private_last_hash)
            else:
                # Mine normally if no attack
                miner_status = miner.mine(merkletree, ledger)

            mine_or_recv = ""
            # Check if mine is successful
            if miner_status:
                mine_or_recv = "Block MINED "

                if args.attacker and start_attack:
                    # sending_block might not be last block of blockchain
                    sending_block = find_private_block(
                        blockchain.chain, private_last_hash, original_blocks)
                    sending_block_header_hash = binascii.hexlify(
                        sending_block.header_hash()).decode()
                    mine_or_recv += sending_block_header_hash
                    private_fork.append(sending_block)
                    # Update private_last_hash
                    private_last_hash = sending_block_header_hash
                    if len(private_fork) == 3:
                        for block in private_fork:
                            block_data = pickle.dumps(block, protocol=2)
                            send_failed = True
                            while send_failed:
                                try:
                                    requests.post("http://"+args.ip_other +
                                                  "/block", data=block_data)
                                    send_failed = False
                                except:
                                    time.sleep(0.1)
                        private_fork = []
                        if not check_block_in_chain(blockchain, original_blocks[0]):
                            print("=============\nATTACK ENDED\n============")
                            # stop attack
                            start_attack = False
                            cease_attacks = True
                        else:
                            print(
                                "Block to void:", original_blocks[0], "Chain:", blockchain.cleaned_keys)
                            print(
                                "Block we want to void is still in chain! Continuing attack...")

                else:  # args.attacker and not start_attack or not args.attacker
                    sending_block = blockchain.last_block()
                    mine_or_recv += binascii.hexlify(
                        sending_block.header_hash()).decode()

                    data = pickle.dumps(sending_block, protocol=2)
                    send_failed = True
                    while send_failed:
                        try:
                            requests.post("http://" + args.ip_other +
                                          "/block", data=data)
                            send_failed = False
                        except:
                            time.sleep(0.2)
                break

            if miner.nonce % 1000 == 0:
                # Check if new blocks have been detected
                block_queue_status_initial = block_queue.empty()
                while not block_queue.empty():
                    mine_or_recv += "Block RECEIVED "
                    # If detected, add new block to blockchain
                    new_block = block_queue.get()
                    miner.network_block(new_block)
                    mine_or_recv += binascii.hexlify(
                        new_block.header_hash()).decode() + " "
                if not block_queue_status_initial:
                    break
                block_queue_status_initial = True
        print(color(args.color) + "PORT: {}\n".format(args.port) + mine_or_recv +
              str(miner.blockchain).split("~~~")[1])
        if start_attack and args.attacker:
            print(color(args.color) + "private fork:"+" "*14*(trigger_block-2), trigger_block_hash[:10]+" -> ",
                  [binascii.hexlify(private_block.header_hash()).decode()[:10] for private_block in private_fork])
def start_mining(block_queue, transaction_queue, blockchain_request_queue,
                 blockchain_reply_queue):
    blockchain = BlockChain(LIST_OF_MINER_IP)
    miner = Miner(blockchain, PUBLIC_KEY)
    miner_status = False
    list_of_blocks_selfish = []
    SELFISH_LENGTH = 5
    list_of_collected_selfish_blocks = []
    selfish_flush = False
    # Infinite loop
    while True:
        # Create a merkel tree from transaction queue
        merkletree, ledger = miner.create_merkle(transaction_queue)

        while True:
            # Mines the nonce every round
            miner_status = miner.mine(merkletree, ledger)
            mine_or_recv = ""
            # Check if that mine is successful
            if miner_status:
                mine_or_recv = "Block MINED "
                sending_block = blockchain.last_block()
                mine_or_recv += binascii.hexlify(
                    sending_block.header_hash()).decode()

                # Grab the last block and send to network
                # If regular miner
                if not SELFISH:
                    data = pickle.dumps(sending_block, protocol=2)
                    for miner_ip in LIST_OF_MINER_IP:
                        send_failed = True
                        while send_failed:
                            try:
                                requests.post("http://" + miner_ip + "/block",
                                              data=data)
                                send_failed = False
                            except:
                                print("Send failed.", miner_ip)
                                time.sleep(0.2)
                    sending_spv_block = SPVBlock(sending_block)
                    data = pickle.dumps(sending_spv_block, protocol=2)
                    for spv_ip in LIST_OF_SPV_IP:
                        send_failed = True
                        while send_failed:
                            try:
                                requests.post("http://" + spv_ip +
                                              "/block_header",
                                              data=data)
                                send_failed = False
                            except:
                                print("Send failed.", spv_ip)
                                time.sleep(0.2)
                # If selfish miner
                elif SELFISH:
                    mine_or_recv += "\nSELFISH MINING\n"
                    list_of_blocks_selfish.append(sending_block)
                    # It will send only every n blocks
                    if len(list_of_blocks_selfish) >= SELFISH_LENGTH:
                        mine_or_recv += "SENDING SELFISH BLOCKS\n"
                        selfish_flush = True
                        for block in list_of_blocks_selfish:
                            block_data = pickle.dumps(block, protocol=2)
                            for miner_ip in LIST_OF_MINER_IP:
                                send_failed = True
                                while send_failed:
                                    try:
                                        requests.post("http://" + miner_ip +
                                                      "/block",
                                                      data=block_data)
                                        send_failed = False
                                    except:
                                        time.sleep(0.1)
                        list_of_blocks_selfish = []
                break
            # Runs this area only once every 100 nonce, as checking queue every cycle makes it very laggy
            if miner.nonce % 100 == 0:
                # Check if new blocks have been detected
                block_queue_status_initial = block_queue.empty()
                while not block_queue.empty():
                    mine_or_recv += "Block RECEIVED "
                    new_block = block_queue.get()
                    if not SELFISH:
                        miner.network_block(new_block)
                    elif SELFISH:
                        list_of_collected_selfish_blocks.append(new_block)
                        if len(list_of_collected_selfish_blocks
                               ) > SELFISH_LENGTH or selfish_flush:
                            for i in list_of_collected_selfish_blocks:
                                miner.network_block(i)
                            for block in list_of_blocks_selfish:
                                block_data = pickle.dumps(block, protocol=2)
                                for miner_ip in LIST_OF_MINER_IP:
                                    send_failed = True
                                    while send_failed:
                                        try:
                                            requests.post("http://" +
                                                          miner_ip + "/block",
                                                          data=block_data)
                                            send_failed = False
                                        except:
                                            time.sleep(0.1)
                            miner.blockchain.resolve()
                            list_of_collected_selfish_blocks = []
                            list_of_blocks_selfish = []
                            selfish_flush = False
                    mine_or_recv += binascii.hexlify(
                        new_block.header_hash()).decode() + " "
                if not block_queue_status_initial:
                    mine_or_recv += "\n"
                    break
                # Checks if any of the endpoints requested a copy of the blockchain
                if not blockchain_request_queue.empty():
                    blockchain_request_queue.get()
                    blockchain_reply_queue.put(
                        (copy.deepcopy(blockchain.cleaned_keys),
                         copy.deepcopy(blockchain.chain),
                         copy.deepcopy(blockchain.retrieve_ledger())))
        # Section runs if the miner found a block or receives a block that has been broadcasted
        print(COLOR + "Public key: {}\n".format(PUBLIC_KEY_STRING) +
              "PORT: {}\n".format(MY_PORT) + mine_or_recv + "\n" +
              (str(miner.blockchain) if MODE ==
               1 else str(miner.blockchain).split("~~~\n")[1]))