1import os 2import time 3 4import infra.basetest 5 6 7class TestBitcoin(infra.basetest.BRTest): 8 # infra.basetest.BASIC_TOOLCHAIN_CONFIG cannot be used as it does 9 # not include BR2_TOOLCHAIN_SUPPORTS_ALWAYS_LOCKFREE_ATOMIC_INTS 10 # needed by bitcoin. This config also uses an ext4 rootfs as 11 # bitcoind needs some free disk space to start (so we avoid having 12 # a larger initrd in RAM). 13 config = \ 14 """ 15 BR2_aarch64=y 16 BR2_TOOLCHAIN_EXTERNAL=y 17 BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0" 18 BR2_LINUX_KERNEL=y 19 BR2_LINUX_KERNEL_CUSTOM_VERSION=y 20 BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.1.81" 21 BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y 22 BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux.config" 23 BR2_PACKAGE_BITCOIN=y 24 BR2_PACKAGE_BITCOIN_WALLET=y 25 BR2_TARGET_ROOTFS_EXT2=y 26 BR2_TARGET_ROOTFS_EXT2_4=y 27 BR2_TARGET_ROOTFS_EXT2_SIZE="256M" 28 # BR2_TARGET_ROOTFS_TAR is not set 29 """ 30 # Command prefix for the bitcoin command line interface. 31 cli_cmd = "bitcoin-cli -regtest" 32 33 def create_btc_wallet(self, wallet_name): 34 """Create an empty wallet.""" 35 cmd = f"{self.cli_cmd} -named createwallet wallet_name={wallet_name}" 36 self.assertRunOk(cmd) 37 38 def gen_btc_address(self, wallet_name): 39 """Generate an address in a wallet.""" 40 cmd = f"{self.cli_cmd} -rpcwallet={wallet_name} getnewaddress" 41 out, ret = self.emulator.run(cmd) 42 self.assertEqual(ret, 0) 43 return out[0] 44 45 def init_wallet(self, wallet_name): 46 """Create a wallet and generate an address in it.""" 47 self.create_btc_wallet(wallet_name) 48 return self.gen_btc_address(wallet_name) 49 50 def get_wallet_balance(self, wallet): 51 """Return the (confirmed) balance of a wallet.""" 52 cmd = f"{self.cli_cmd} -rpcwallet={wallet} getbalance" 53 out, ret = self.emulator.run(cmd) 54 self.assertEqual(ret, 0) 55 return float(out[0]) 56 57 def get_wallet_unconfirmed_balance(self, wallet): 58 """Return the unconfirmed balance of a wallet.""" 59 cmd = f"{self.cli_cmd} -rpcwallet={wallet} getunconfirmedbalance" 60 out, ret = self.emulator.run(cmd) 61 self.assertEqual(ret, 0) 62 return float(out[0]) 63 64 def get_block_count(self): 65 """Returns the height of the most-work fully-validated chain.""" 66 cmd = f"{self.cli_cmd} getblockcount" 67 out, ret = self.emulator.run(cmd) 68 self.assertEqual(ret, 0) 69 return int(out[0]) 70 71 def test_run(self): 72 drive = os.path.join(self.builddir, "images", "rootfs.ext4") 73 kern = os.path.join(self.builddir, "images", "Image") 74 self.emulator.boot(arch="aarch64", 75 kernel=kern, 76 kernel_cmdline=["root=/dev/vda console=ttyAMA0"], 77 options=["-M", "virt", 78 "-cpu", "cortex-a53", 79 "-m", "256M", 80 "-drive", f"file={drive},if=virtio,format=raw"]) 81 self.emulator.login() 82 83 # Values for the test. 84 wallet1 = "AliceWallet" 85 wallet2 = "BobWallet" 86 btc_test_amount = 10 87 btc_fee = 0.00001 88 req_blk_count = 101 89 90 # Check the binary can execute. 91 self.assertRunOk("bitcoind --version") 92 93 # This cleanup is useful when run-test -k is used. It makes 94 # this test idempotent. Since the drive storage is preserved 95 # between reboots, this cleanup will make sure the test always 96 # starts from a clean state. 97 cmd = "rm -rf ~/.bitcoin" 98 self.assertRunOk(cmd) 99 100 # The bitcoin daemon is not started. A client ping is expected 101 # to fail. 102 ping_cmd = f"{self.cli_cmd} ping" 103 _, ret = self.emulator.run(ping_cmd) 104 self.assertNotEqual(ret, 0) 105 106 # Start the daemon. 107 cmd = f"bitcoind -regtest -daemonwait -fallbackfee={btc_fee:f}" 108 self.assertRunOk(cmd) 109 110 time.sleep(2 * self.timeout_multiplier) 111 112 # Now the daemon is started, the ping is expected to succeed. 113 self.assertRunOk(ping_cmd) 114 115 # We create two wallets and addresses. 116 btc_addr1 = self.init_wallet(wallet1) 117 btc_addr2 = self.init_wallet(wallet2) 118 119 # Since the regression test block chain is at its genesis 120 # block, we expect a height of zero. 121 cur_blk_cnt = self.get_block_count() 122 self.assertEqual(cur_blk_cnt, 0) 123 124 # We also expect our wallets to be empty. 125 for wallet in [wallet1, wallet2]: 126 balance = self.get_wallet_balance(wallet) 127 self.assertAlmostEqual(balance, 0.0) 128 129 # We request the generation of several blocks for address 130 # #1. We should receive the 50 BTC reward at this address. 131 cmd = self.cli_cmd 132 cmd += f" generatetoaddress {req_blk_count} {btc_addr1}" 133 self.assertRunOk(cmd) 134 135 # We should now see the previously created blocks. 136 cur_blk_cnt = self.get_block_count() 137 self.assertEqual(cur_blk_cnt, req_blk_count) 138 139 # We should also see the 50 BTC reward in the wallet #1. 140 balance = self.get_wallet_balance(wallet1) 141 self.assertAlmostEqual(balance, 50.0) 142 143 # The wallet #2 should still be empty. 144 balance = self.get_wallet_balance(wallet2) 145 self.assertAlmostEqual(balance, 0.0) 146 147 # We send an amount from wallet #1 to #2. 148 cmd = f"{self.cli_cmd} -rpcwallet={wallet1}" 149 cmd += f" sendtoaddress {btc_addr2} {btc_test_amount}" 150 self.assertRunOk(cmd) 151 152 # The wallet #1 balance is expected to be subtracted by the 153 # spent amount and the transaction fees. 154 expected_balance = 50 - btc_test_amount - btc_fee 155 balance = self.get_wallet_balance(wallet1) 156 self.assertAlmostEqual(balance, expected_balance, places=4) 157 158 # The transaction is sent, but not confirmed yet. So we should 159 # still see a (confirmed) balance of zero. 160 balance = self.get_wallet_balance(wallet2) 161 self.assertAlmostEqual(balance, 0.0) 162 163 # We should see the transferred amount in the unconfirmed 164 # balance. 165 balance = self.get_wallet_unconfirmed_balance(wallet2) 166 self.assertAlmostEqual(balance, btc_test_amount) 167 168 # We generate 1 block to address #2. This action will confirm 169 # the previous transaction (but this will not give the 50 BTC 170 # reward). 171 cmd = f"{self.cli_cmd} generatetoaddress 1 {btc_addr2}" 172 self.assertRunOk(cmd) 173 174 # We should see one more block. 175 cur_blk_cnt = self.get_block_count() 176 self.assertEqual(cur_blk_cnt, req_blk_count + 1) 177 178 # We should now see the amount in the confirmed balance. 179 balance = self.get_wallet_balance(wallet2) 180 self.assertAlmostEqual(balance, btc_test_amount) 181 182 # The unconfirmed balance should now be zero. 183 balance = self.get_wallet_unconfirmed_balance(wallet2) 184 self.assertAlmostEqual(balance, 0.0) 185