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])