Files
pallectrum/tests/test_blockchain.py

461 lines
29 KiB
Python
Raw Permalink Normal View History

import shutil
import tempfile
import os
from electrum import constants, blockchain
from electrum.simple_config import SimpleConfig
from electrum.blockchain import Blockchain, deserialize_header, hash_header, InvalidHeader
from electrum.util import bfh, make_dir
from . import ElectrumTestCase
class TestBlockchain(ElectrumTestCase):
HEADERS = {
'A': deserialize_header(bfh("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f2002000000"), 0),
'B': deserialize_header(bfh("0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f186c8dfd970a4545f79916bc1d75c9d00432f57c89209bf3bb115b7612848f509c25f45bffff7f2000000000"), 1),
'C': deserialize_header(bfh("00000020686bdfc6a3db73d5d93e8c9663a720a26ecb1ef20eb05af11b36cdbc57c19f7ebf2cbf153013a1c54abaf70e95198fcef2f3059cc6b4d0f7e876808e7d24d11cc825f45bffff7f2000000000"), 2),
'D': deserialize_header(bfh("00000020122baa14f3ef54985ae546d1611559e3f487bd2a0f46e8dbb52fbacc9e237972e71019d7feecd9b8596eca9a67032c5f4641b23b5d731dc393e37de7f9c2f299e725f45bffff7f2000000000"), 3),
'E': deserialize_header(bfh("00000020f8016f7ef3a17d557afe05d4ea7ab6bde1b2247b7643896c1b63d43a1598b747a3586da94c71753f27c075f57f44faf913c31177a0957bbda42e7699e3a2141aed25f45bffff7f2001000000"), 4),
'F': deserialize_header(bfh("000000201d589c6643c1d121d73b0573e5ee58ab575b8fdf16d507e7e915c5fbfbbfd05e7aee1d692d1615c3bdf52c291032144ce9e3b258a473c17c745047f3431ff8e2ee25f45bffff7f2000000000"), 5),
'O': deserialize_header(bfh("00000020b833ed46eea01d4c980f59feee44a66aa1162748b6801029565d1466790c405c3a141ce635cbb1cd2b3a4fcdd0a3380517845ba41736c82a79cab535d31128066526f45bffff7f2001000000"), 6),
'P': deserialize_header(bfh("00000020abe8e119d1877c9dc0dc502d1a253fb9a67967c57732d2f71ee0280e8381ff0a9690c2fe7c1a4450c74dc908fe94dd96c3b0637d51475e9e06a78e944a0c7fe28126f45bffff7f2000000000"), 7),
'Q': deserialize_header(bfh("000000202ce41d94eb70e1518bc1f72523f84a903f9705d967481e324876e1f8cf4d3452148be228a4c3f2061bafe7efdfc4a8d5a94759464b9b5c619994d45dfcaf49e1a126f45bffff7f2000000000"), 8),
'R': deserialize_header(bfh("00000020552755b6c59f3d51e361d16281842a4e166007799665b5daed86a063dd89857415681cb2d00ff889193f6a68a93f5096aeb2d84ca0af6185a462555822552221a626f45bffff7f2000000000"), 9),
'S': deserialize_header(bfh("00000020a13a491cbefc93cd1bb1938f19957e22a134faf14c7dee951c45533e2c750f239dc087fc977b06c24a69c682d1afd1020e6dc1f087571ccec66310a786e1548fab26f45bffff7f2000000000"), 10),
'T': deserialize_header(bfh("00000020dbf3a9b55dfefbaf8b6e43a89cf833fa2e208bbc0c1c5d76c0d71b9e4a65337803b243756c25053253aeda309604363460a3911015929e68705bd89dff6fe064b026f45bffff7f2002000000"), 11),
'U': deserialize_header(bfh("000000203d0932b3b0c78eccb39a595a28ae4a7c966388648d7783fd1305ec8d40d4fe5fd67cb902a7d807cee7676cb543feec3e053aa824d5dfb528d5b94f9760313d9db726f45bffff7f2001000000"), 12),
'G': deserialize_header(bfh("00000020b833ed46eea01d4c980f59feee44a66aa1162748b6801029565d1466790c405c3a141ce635cbb1cd2b3a4fcdd0a3380517845ba41736c82a79cab535d31128066928f45bffff7f2001000000"), 6),
'H': deserialize_header(bfh("00000020e19e687f6e7f83ca394c114144dbbbc4f3f9c9450f66331a125413702a2e1a719690c2fe7c1a4450c74dc908fe94dd96c3b0637d51475e9e06a78e944a0c7fe26a28f45bffff7f2002000000"), 7),
'I': deserialize_header(bfh("0000002009dcb3b158293c89d7cf7ceeb513add122ebc3880a850f47afbb2747f5e48c54148be228a4c3f2061bafe7efdfc4a8d5a94759464b9b5c619994d45dfcaf49e16a28f45bffff7f2000000000"), 8),
'J': deserialize_header(bfh("000000206a65f3bdd3374a5a6c4538008ba0b0a560b8566291f9ef4280ab877627a1742815681cb2d00ff889193f6a68a93f5096aeb2d84ca0af6185a462555822552221c928f45bffff7f2000000000"), 9),
'K': deserialize_header(bfh("00000020bb3b421653548991998f96f8ba486b652fdb07ca16e9cee30ece033547cd1a6e9dc087fc977b06c24a69c682d1afd1020e6dc1f087571ccec66310a786e1548fca28f45bffff7f2000000000"), 10),
'L': deserialize_header(bfh("00000020c391d74d37c24a130f4bf4737932bdf9e206dd4fad22860ec5408978eb55d46303b243756c25053253aeda309604363460a3911015929e68705bd89dff6fe064ca28f45bffff7f2000000000"), 11),
'M': deserialize_header(bfh("000000206a65f3bdd3374a5a6c4538008ba0b0a560b8566291f9ef4280ab877627a1742815681cb2d00ff889193f6a68a93f5096aeb2d84ca0af6185a4625558225522214229f45bffff7f2000000000"), 9),
'N': deserialize_header(bfh("00000020383dab38b57f98aa9b4f0d5ff868bc674b4828d76766bf048296f4c45fff680a9dc087fc977b06c24a69c682d1afd1020e6dc1f087571ccec66310a786e1548f4329f45bffff7f2003000000"), 10),
'X': deserialize_header(bfh("0000002067f1857f54b7fef732cb4940f7d1b339472b3514660711a820330fd09d8fba6b03b243756c25053253aeda309604363460a3911015929e68705bd89dff6fe0649b29f45bffff7f2002000000"), 11),
'Y': deserialize_header(bfh("00000020db33c9768a9e5f7c37d0f09aad88d48165946c87d08f7d63793f07b5c08c527fd67cb902a7d807cee7676cb543feec3e053aa824d5dfb528d5b94f9760313d9d9b29f45bffff7f2000000000"), 12),
'Z': deserialize_header(bfh("0000002047822b67940e337fda38be6f13390b3596e4dea2549250256879722073824e7f0f2596c29203f8a0f71ae94193092dc8f113be3dbee4579f1e649fa3d6dcc38c622ef45bffff7f2003000000"), 13),
}
# tree of headers:
# - M <- N <- X <- Y <- Z
# /
# - G <- H <- I <- J <- K <- L
# /
# A <- B <- C <- D <- E <- F <- O <- P <- Q <- R <- S <- T <- U
@classmethod
def setUpClass(cls):
super().setUpClass()
constants.BitcoinRegtest.set_as_network()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
constants.Palladium.set_as_network()
def setUp(self):
super().setUp()
self.data_dir = self.electrum_path
make_dir(os.path.join(self.data_dir, 'forks'))
self.config = SimpleConfig({'electrum_path': self.data_dir})
blockchain.blockchains = {}
def _append_header(self, chain: Blockchain, header: dict):
self.assertTrue(chain.can_connect(header))
chain.save_header(header)
def test_get_height_of_last_common_block_with_chain(self):
blockchain.blockchains[constants.net.GENESIS] = chain_u = Blockchain(
config=self.config, forkpoint=0, parent=None,
forkpoint_hash=constants.net.GENESIS, prev_hash=None)
open(chain_u.path(), 'w+').close()
self._append_header(chain_u, self.HEADERS['A'])
self._append_header(chain_u, self.HEADERS['B'])
self._append_header(chain_u, self.HEADERS['C'])
self._append_header(chain_u, self.HEADERS['D'])
self._append_header(chain_u, self.HEADERS['E'])
self._append_header(chain_u, self.HEADERS['F'])
self._append_header(chain_u, self.HEADERS['O'])
self._append_header(chain_u, self.HEADERS['P'])
self._append_header(chain_u, self.HEADERS['Q'])
chain_l = chain_u.fork(self.HEADERS['G'])
self._append_header(chain_l, self.HEADERS['H'])
self._append_header(chain_l, self.HEADERS['I'])
self._append_header(chain_l, self.HEADERS['J'])
self._append_header(chain_l, self.HEADERS['K'])
self._append_header(chain_l, self.HEADERS['L'])
self.assertEqual({chain_u: 8, chain_l: 5}, chain_u.get_parent_heights())
self.assertEqual({chain_l: 11}, chain_l.get_parent_heights())
chain_z = chain_l.fork(self.HEADERS['M'])
self._append_header(chain_z, self.HEADERS['N'])
self._append_header(chain_z, self.HEADERS['X'])
self._append_header(chain_z, self.HEADERS['Y'])
self._append_header(chain_z, self.HEADERS['Z'])
self.assertEqual({chain_u: 8, chain_z: 5}, chain_u.get_parent_heights())
self.assertEqual({chain_l: 11, chain_z: 8}, chain_l.get_parent_heights())
self.assertEqual({chain_z: 13}, chain_z.get_parent_heights())
self.assertEqual(5, chain_u.get_height_of_last_common_block_with_chain(chain_l))
self.assertEqual(5, chain_l.get_height_of_last_common_block_with_chain(chain_u))
self.assertEqual(5, chain_u.get_height_of_last_common_block_with_chain(chain_z))
self.assertEqual(5, chain_z.get_height_of_last_common_block_with_chain(chain_u))
self.assertEqual(8, chain_l.get_height_of_last_common_block_with_chain(chain_z))
self.assertEqual(8, chain_z.get_height_of_last_common_block_with_chain(chain_l))
self._append_header(chain_u, self.HEADERS['R'])
self._append_header(chain_u, self.HEADERS['S'])
self._append_header(chain_u, self.HEADERS['T'])
self._append_header(chain_u, self.HEADERS['U'])
self.assertEqual({chain_u: 12, chain_z: 5}, chain_u.get_parent_heights())
self.assertEqual({chain_l: 11, chain_z: 8}, chain_l.get_parent_heights())
self.assertEqual({chain_z: 13}, chain_z.get_parent_heights())
self.assertEqual(5, chain_u.get_height_of_last_common_block_with_chain(chain_l))
self.assertEqual(5, chain_l.get_height_of_last_common_block_with_chain(chain_u))
self.assertEqual(5, chain_u.get_height_of_last_common_block_with_chain(chain_z))
self.assertEqual(5, chain_z.get_height_of_last_common_block_with_chain(chain_u))
self.assertEqual(8, chain_l.get_height_of_last_common_block_with_chain(chain_z))
self.assertEqual(8, chain_z.get_height_of_last_common_block_with_chain(chain_l))
def test_parents_after_forking(self):
blockchain.blockchains[constants.net.GENESIS] = chain_u = Blockchain(
config=self.config, forkpoint=0, parent=None,
forkpoint_hash=constants.net.GENESIS, prev_hash=None)
open(chain_u.path(), 'w+').close()
self._append_header(chain_u, self.HEADERS['A'])
self._append_header(chain_u, self.HEADERS['B'])
self._append_header(chain_u, self.HEADERS['C'])
self._append_header(chain_u, self.HEADERS['D'])
self._append_header(chain_u, self.HEADERS['E'])
self._append_header(chain_u, self.HEADERS['F'])
self._append_header(chain_u, self.HEADERS['O'])
self._append_header(chain_u, self.HEADERS['P'])
self._append_header(chain_u, self.HEADERS['Q'])
self.assertEqual(None, chain_u.parent)
chain_l = chain_u.fork(self.HEADERS['G'])
self._append_header(chain_l, self.HEADERS['H'])
self._append_header(chain_l, self.HEADERS['I'])
self._append_header(chain_l, self.HEADERS['J'])
self._append_header(chain_l, self.HEADERS['K'])
self._append_header(chain_l, self.HEADERS['L'])
self.assertEqual(None, chain_l.parent)
self.assertEqual(chain_l, chain_u.parent)
chain_z = chain_l.fork(self.HEADERS['M'])
self._append_header(chain_z, self.HEADERS['N'])
self._append_header(chain_z, self.HEADERS['X'])
self._append_header(chain_z, self.HEADERS['Y'])
self._append_header(chain_z, self.HEADERS['Z'])
self.assertEqual(chain_z, chain_u.parent)
self.assertEqual(chain_z, chain_l.parent)
self.assertEqual(None, chain_z.parent)
self._append_header(chain_u, self.HEADERS['R'])
self._append_header(chain_u, self.HEADERS['S'])
self._append_header(chain_u, self.HEADERS['T'])
self._append_header(chain_u, self.HEADERS['U'])
self.assertEqual(chain_z, chain_u.parent)
self.assertEqual(chain_z, chain_l.parent)
self.assertEqual(None, chain_z.parent)
def test_forking_and_swapping(self):
blockchain.blockchains[constants.net.GENESIS] = chain_u = Blockchain(
config=self.config, forkpoint=0, parent=None,
forkpoint_hash=constants.net.GENESIS, prev_hash=None)
open(chain_u.path(), 'w+').close()
self._append_header(chain_u, self.HEADERS['A'])
self._append_header(chain_u, self.HEADERS['B'])
self._append_header(chain_u, self.HEADERS['C'])
self._append_header(chain_u, self.HEADERS['D'])
self._append_header(chain_u, self.HEADERS['E'])
self._append_header(chain_u, self.HEADERS['F'])
self._append_header(chain_u, self.HEADERS['O'])
self._append_header(chain_u, self.HEADERS['P'])
self._append_header(chain_u, self.HEADERS['Q'])
self._append_header(chain_u, self.HEADERS['R'])
chain_l = chain_u.fork(self.HEADERS['G'])
self._append_header(chain_l, self.HEADERS['H'])
self._append_header(chain_l, self.HEADERS['I'])
self._append_header(chain_l, self.HEADERS['J'])
# do checks
self.assertEqual(2, len(blockchain.blockchains))
self.assertEqual(1, len(os.listdir(os.path.join(self.data_dir, "forks"))))
self.assertEqual(0, chain_u.forkpoint)
self.assertEqual(None, chain_u.parent)
self.assertEqual(constants.net.GENESIS, chain_u._forkpoint_hash)
self.assertEqual(None, chain_u._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "blockchain_headers"), chain_u.path())
self.assertEqual(10 * 80, os.stat(chain_u.path()).st_size)
self.assertEqual(6, chain_l.forkpoint)
self.assertEqual(chain_u, chain_l.parent)
self.assertEqual(hash_header(self.HEADERS['G']), chain_l._forkpoint_hash)
self.assertEqual(hash_header(self.HEADERS['F']), chain_l._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "forks", "fork2_6_5c400c7966145d56291080b6482716a16aa644eefe590f984c1da0ee46ed33b8_711a2e2a701354121a33660f45c9f9f3c4bbdb4441114c39ca837f6e7f689ee1"), chain_l.path())
self.assertEqual(4 * 80, os.stat(chain_l.path()).st_size)
self._append_header(chain_l, self.HEADERS['K'])
# chains were swapped, do checks
self.assertEqual(2, len(blockchain.blockchains))
self.assertEqual(1, len(os.listdir(os.path.join(self.data_dir, "forks"))))
self.assertEqual(6, chain_u.forkpoint)
self.assertEqual(chain_l, chain_u.parent)
self.assertEqual(hash_header(self.HEADERS['O']), chain_u._forkpoint_hash)
self.assertEqual(hash_header(self.HEADERS['F']), chain_u._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "forks", "fork2_6_5c400c7966145d56291080b6482716a16aa644eefe590f984c1da0ee46ed33b8_aff81830e28e01ef7d23277c56779a6b93f251a2d50dcc09d7c87d119e1e8ab"), chain_u.path())
self.assertEqual(4 * 80, os.stat(chain_u.path()).st_size)
self.assertEqual(0, chain_l.forkpoint)
self.assertEqual(None, chain_l.parent)
self.assertEqual(constants.net.GENESIS, chain_l._forkpoint_hash)
self.assertEqual(None, chain_l._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "blockchain_headers"), chain_l.path())
self.assertEqual(11 * 80, os.stat(chain_l.path()).st_size)
for b in (chain_u, chain_l):
self.assertTrue(all([b.can_connect(b.read_header(i), check_height=False) for i in range(b.height())]))
self._append_header(chain_u, self.HEADERS['S'])
self._append_header(chain_u, self.HEADERS['T'])
self._append_header(chain_u, self.HEADERS['U'])
self._append_header(chain_l, self.HEADERS['L'])
chain_z = chain_l.fork(self.HEADERS['M'])
self._append_header(chain_z, self.HEADERS['N'])
self._append_header(chain_z, self.HEADERS['X'])
self._append_header(chain_z, self.HEADERS['Y'])
self._append_header(chain_z, self.HEADERS['Z'])
# chain_z became best chain, do checks
self.assertEqual(3, len(blockchain.blockchains))
self.assertEqual(2, len(os.listdir(os.path.join(self.data_dir, "forks"))))
self.assertEqual(0, chain_z.forkpoint)
self.assertEqual(None, chain_z.parent)
self.assertEqual(constants.net.GENESIS, chain_z._forkpoint_hash)
self.assertEqual(None, chain_z._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "blockchain_headers"), chain_z.path())
self.assertEqual(14 * 80, os.stat(chain_z.path()).st_size)
self.assertEqual(9, chain_l.forkpoint)
self.assertEqual(chain_z, chain_l.parent)
self.assertEqual(hash_header(self.HEADERS['J']), chain_l._forkpoint_hash)
self.assertEqual(hash_header(self.HEADERS['I']), chain_l._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "forks", "fork2_9_2874a1277687ab8042eff9916256b860a5b0a08b0038456c5a4a37d3bdf3656a_6e1acd473503ce0ee3cee916ca07db2f656b48baf8968f999189545316423bbb"), chain_l.path())
self.assertEqual(3 * 80, os.stat(chain_l.path()).st_size)
self.assertEqual(6, chain_u.forkpoint)
self.assertEqual(chain_z, chain_u.parent)
self.assertEqual(hash_header(self.HEADERS['O']), chain_u._forkpoint_hash)
self.assertEqual(hash_header(self.HEADERS['F']), chain_u._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "forks", "fork2_6_5c400c7966145d56291080b6482716a16aa644eefe590f984c1da0ee46ed33b8_aff81830e28e01ef7d23277c56779a6b93f251a2d50dcc09d7c87d119e1e8ab"), chain_u.path())
self.assertEqual(7 * 80, os.stat(chain_u.path()).st_size)
for b in (chain_u, chain_l, chain_z):
self.assertTrue(all([b.can_connect(b.read_header(i), check_height=False) for i in range(b.height())]))
self.assertEqual(constants.net.GENESIS, chain_z.get_hash(0))
self.assertEqual(hash_header(self.HEADERS['F']), chain_z.get_hash(5))
self.assertEqual(hash_header(self.HEADERS['G']), chain_z.get_hash(6))
self.assertEqual(hash_header(self.HEADERS['I']), chain_z.get_hash(8))
self.assertEqual(hash_header(self.HEADERS['M']), chain_z.get_hash(9))
self.assertEqual(hash_header(self.HEADERS['Z']), chain_z.get_hash(13))
def test_doing_multiple_swaps_after_single_new_header(self):
blockchain.blockchains[constants.net.GENESIS] = chain_u = Blockchain(
config=self.config, forkpoint=0, parent=None,
forkpoint_hash=constants.net.GENESIS, prev_hash=None)
open(chain_u.path(), 'w+').close()
self._append_header(chain_u, self.HEADERS['A'])
self._append_header(chain_u, self.HEADERS['B'])
self._append_header(chain_u, self.HEADERS['C'])
self._append_header(chain_u, self.HEADERS['D'])
self._append_header(chain_u, self.HEADERS['E'])
self._append_header(chain_u, self.HEADERS['F'])
self._append_header(chain_u, self.HEADERS['O'])
self._append_header(chain_u, self.HEADERS['P'])
self._append_header(chain_u, self.HEADERS['Q'])
self._append_header(chain_u, self.HEADERS['R'])
self._append_header(chain_u, self.HEADERS['S'])
self.assertEqual(1, len(blockchain.blockchains))
self.assertEqual(0, len(os.listdir(os.path.join(self.data_dir, "forks"))))
chain_l = chain_u.fork(self.HEADERS['G'])
self._append_header(chain_l, self.HEADERS['H'])
self._append_header(chain_l, self.HEADERS['I'])
self._append_header(chain_l, self.HEADERS['J'])
self._append_header(chain_l, self.HEADERS['K'])
# now chain_u is best chain, but it's tied with chain_l
self.assertEqual(2, len(blockchain.blockchains))
self.assertEqual(1, len(os.listdir(os.path.join(self.data_dir, "forks"))))
chain_z = chain_l.fork(self.HEADERS['M'])
self._append_header(chain_z, self.HEADERS['N'])
self._append_header(chain_z, self.HEADERS['X'])
self.assertEqual(3, len(blockchain.blockchains))
self.assertEqual(2, len(os.listdir(os.path.join(self.data_dir, "forks"))))
# chain_z became best chain, do checks
self.assertEqual(0, chain_z.forkpoint)
self.assertEqual(None, chain_z.parent)
self.assertEqual(constants.net.GENESIS, chain_z._forkpoint_hash)
self.assertEqual(None, chain_z._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "blockchain_headers"), chain_z.path())
self.assertEqual(12 * 80, os.stat(chain_z.path()).st_size)
self.assertEqual(9, chain_l.forkpoint)
self.assertEqual(chain_z, chain_l.parent)
self.assertEqual(hash_header(self.HEADERS['J']), chain_l._forkpoint_hash)
self.assertEqual(hash_header(self.HEADERS['I']), chain_l._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "forks", "fork2_9_2874a1277687ab8042eff9916256b860a5b0a08b0038456c5a4a37d3bdf3656a_6e1acd473503ce0ee3cee916ca07db2f656b48baf8968f999189545316423bbb"), chain_l.path())
self.assertEqual(2 * 80, os.stat(chain_l.path()).st_size)
self.assertEqual(6, chain_u.forkpoint)
self.assertEqual(chain_z, chain_u.parent)
self.assertEqual(hash_header(self.HEADERS['O']), chain_u._forkpoint_hash)
self.assertEqual(hash_header(self.HEADERS['F']), chain_u._prev_hash)
self.assertEqual(os.path.join(self.data_dir, "forks", "fork2_6_5c400c7966145d56291080b6482716a16aa644eefe590f984c1da0ee46ed33b8_aff81830e28e01ef7d23277c56779a6b93f251a2d50dcc09d7c87d119e1e8ab"), chain_u.path())
self.assertEqual(5 * 80, os.stat(chain_u.path()).st_size)
self.assertEqual(constants.net.GENESIS, chain_z.get_hash(0))
self.assertEqual(hash_header(self.HEADERS['F']), chain_z.get_hash(5))
self.assertEqual(hash_header(self.HEADERS['G']), chain_z.get_hash(6))
self.assertEqual(hash_header(self.HEADERS['I']), chain_z.get_hash(8))
self.assertEqual(hash_header(self.HEADERS['M']), chain_z.get_hash(9))
self.assertEqual(hash_header(self.HEADERS['X']), chain_z.get_hash(11))
for b in (chain_u, chain_l, chain_z):
self.assertTrue(all([b.can_connect(b.read_header(i), check_height=False) for i in range(b.height())]))
def get_chains_that_contain_header_helper(self, header: dict):
height = header['block_height']
header_hash = hash_header(header)
return blockchain.get_chains_that_contain_header(height, header_hash)
def test_get_chains_that_contain_header(self):
blockchain.blockchains[constants.net.GENESIS] = chain_u = Blockchain(
config=self.config, forkpoint=0, parent=None,
forkpoint_hash=constants.net.GENESIS, prev_hash=None)
open(chain_u.path(), 'w+').close()
self._append_header(chain_u, self.HEADERS['A'])
self._append_header(chain_u, self.HEADERS['B'])
self._append_header(chain_u, self.HEADERS['C'])
self._append_header(chain_u, self.HEADERS['D'])
self._append_header(chain_u, self.HEADERS['E'])
self._append_header(chain_u, self.HEADERS['F'])
self._append_header(chain_u, self.HEADERS['O'])
self._append_header(chain_u, self.HEADERS['P'])
self._append_header(chain_u, self.HEADERS['Q'])
chain_l = chain_u.fork(self.HEADERS['G'])
self._append_header(chain_l, self.HEADERS['H'])
self._append_header(chain_l, self.HEADERS['I'])
self._append_header(chain_l, self.HEADERS['J'])
self._append_header(chain_l, self.HEADERS['K'])
self._append_header(chain_l, self.HEADERS['L'])
chain_z = chain_l.fork(self.HEADERS['M'])
self.assertEqual([chain_l, chain_z, chain_u], self.get_chains_that_contain_header_helper(self.HEADERS['A']))
self.assertEqual([chain_l, chain_z, chain_u], self.get_chains_that_contain_header_helper(self.HEADERS['C']))
self.assertEqual([chain_l, chain_z, chain_u], self.get_chains_that_contain_header_helper(self.HEADERS['F']))
self.assertEqual([chain_l, chain_z], self.get_chains_that_contain_header_helper(self.HEADERS['G']))
self.assertEqual([chain_l, chain_z], self.get_chains_that_contain_header_helper(self.HEADERS['I']))
self.assertEqual([chain_z], self.get_chains_that_contain_header_helper(self.HEADERS['M']))
self.assertEqual([chain_l], self.get_chains_that_contain_header_helper(self.HEADERS['K']))
self._append_header(chain_z, self.HEADERS['N'])
self._append_header(chain_z, self.HEADERS['X'])
self._append_header(chain_z, self.HEADERS['Y'])
self._append_header(chain_z, self.HEADERS['Z'])
self.assertEqual([chain_z, chain_l, chain_u], self.get_chains_that_contain_header_helper(self.HEADERS['A']))
self.assertEqual([chain_z, chain_l, chain_u], self.get_chains_that_contain_header_helper(self.HEADERS['C']))
self.assertEqual([chain_z, chain_l, chain_u], self.get_chains_that_contain_header_helper(self.HEADERS['F']))
self.assertEqual([chain_u], self.get_chains_that_contain_header_helper(self.HEADERS['O']))
self.assertEqual([chain_z, chain_l], self.get_chains_that_contain_header_helper(self.HEADERS['I']))
blockchain: fix bugs in bits_to_target and target_to_bits This fixes three bugs: - too large targets: the fixme in target_to_bits, which meant that we could not handle targets where the first bit was non-zero. This however cannot happen due to these being over MAX_TARGET. (difficulty 1) - too small targets: in bits_to_target, very small targets were not handled well: ``` >>> Blockchain.bits_to_target(0x03008000) 32768 ``` We could not process headers with targets smaller than the above value. (note that these small targets would only occur at astronomically high mining difficulty) - non-canonically encoded targets: we would not accept headers that had targets encoded in compact form (nBits) in a non-canonical way. I think this bug could actually be triggered by mining such a header. E.g. consider the target "1167130406913723784571467005534932607254396928" ``` Blockchain.target_to_bits(1167130406913723784571467005534932607254396928).to_bytes(4, "big").hex() '13345600' ``` Bitcoin Core when used to e.g. mine a block would encode this target as 0x13345600 in compact form. However, AFAICT, when validating Bitcoin Core would equally accept 0x14003456 which decodes to the same target. We were however rejecting such non-canonical compact nBits. ``` >>> from electrum.blockchain import Blockchain >>> Blockchain.bits_to_target(0x14003456) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/user/wspace/electrum/electrum/blockchain.py", line 548, in bits_to_target raise Exception("Second part of bits should be in [0x8000, 0x7fffff]") Exception: Second part of bits should be in [0x8000, 0x7fffff] >>> Blockchain.bits_to_target(0x13345600) 1167130406913723784571467005534932607254396928 ```
2021-11-13 04:31:08 +01:00
def test_target_to_bits(self):
# https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/arith_uint256.h#L269
self.assertEqual(0x05123456, Blockchain.target_to_bits(0x1234560000))
self.assertEqual(0x0600c0de, Blockchain.target_to_bits(0xc0de000000))
# tests from https://github.com/bitcoin/bitcoin/blob/a7d17daa5cd8bf6398d5f8d7e77290009407d6ea/src/test/arith_uint256_tests.cpp#L411
tuples = (
(0, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x00123456, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x01003456, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x02000056, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x03000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x04000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x00923456, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x01803456, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x02800056, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x03800000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x04800000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0),
(0x01123456, 0x0000000000000000000000000000000000000000000000000000000000000012, 0x01120000),
(0x02123456, 0x0000000000000000000000000000000000000000000000000000000000001234, 0x02123400),
(0x03123456, 0x0000000000000000000000000000000000000000000000000000000000123456, 0x03123456),
(0x04123456, 0x0000000000000000000000000000000000000000000000000000000012345600, 0x04123456),
(0x05009234, 0x0000000000000000000000000000000000000000000000000000000092340000, 0x05009234),
(0x20123456, 0x1234560000000000000000000000000000000000000000000000000000000000, 0x20123456),
)
for nbits1, target, nbits2 in tuples:
with self.subTest(original_compact_nbits=nbits1.to_bytes(length=4, byteorder="big").hex()):
num = Blockchain.bits_to_target(nbits1)
self.assertEqual(target, num)
self.assertEqual(nbits2, Blockchain.target_to_bits(num))
# Make sure that we don't generate compacts with the 0x00800000 bit set
self.assertEqual(0x02008000, Blockchain.target_to_bits(0x80))
with self.assertRaises(InvalidHeader): # target cannot be negative
blockchain: fix bugs in bits_to_target and target_to_bits This fixes three bugs: - too large targets: the fixme in target_to_bits, which meant that we could not handle targets where the first bit was non-zero. This however cannot happen due to these being over MAX_TARGET. (difficulty 1) - too small targets: in bits_to_target, very small targets were not handled well: ``` >>> Blockchain.bits_to_target(0x03008000) 32768 ``` We could not process headers with targets smaller than the above value. (note that these small targets would only occur at astronomically high mining difficulty) - non-canonically encoded targets: we would not accept headers that had targets encoded in compact form (nBits) in a non-canonical way. I think this bug could actually be triggered by mining such a header. E.g. consider the target "1167130406913723784571467005534932607254396928" ``` Blockchain.target_to_bits(1167130406913723784571467005534932607254396928).to_bytes(4, "big").hex() '13345600' ``` Bitcoin Core when used to e.g. mine a block would encode this target as 0x13345600 in compact form. However, AFAICT, when validating Bitcoin Core would equally accept 0x14003456 which decodes to the same target. We were however rejecting such non-canonical compact nBits. ``` >>> from electrum.blockchain import Blockchain >>> Blockchain.bits_to_target(0x14003456) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/user/wspace/electrum/electrum/blockchain.py", line 548, in bits_to_target raise Exception("Second part of bits should be in [0x8000, 0x7fffff]") Exception: Second part of bits should be in [0x8000, 0x7fffff] >>> Blockchain.bits_to_target(0x13345600) 1167130406913723784571467005534932607254396928 ```
2021-11-13 04:31:08 +01:00
Blockchain.bits_to_target(0x01fedcba)
with self.assertRaises(InvalidHeader): # target cannot be negative
blockchain: fix bugs in bits_to_target and target_to_bits This fixes three bugs: - too large targets: the fixme in target_to_bits, which meant that we could not handle targets where the first bit was non-zero. This however cannot happen due to these being over MAX_TARGET. (difficulty 1) - too small targets: in bits_to_target, very small targets were not handled well: ``` >>> Blockchain.bits_to_target(0x03008000) 32768 ``` We could not process headers with targets smaller than the above value. (note that these small targets would only occur at astronomically high mining difficulty) - non-canonically encoded targets: we would not accept headers that had targets encoded in compact form (nBits) in a non-canonical way. I think this bug could actually be triggered by mining such a header. E.g. consider the target "1167130406913723784571467005534932607254396928" ``` Blockchain.target_to_bits(1167130406913723784571467005534932607254396928).to_bytes(4, "big").hex() '13345600' ``` Bitcoin Core when used to e.g. mine a block would encode this target as 0x13345600 in compact form. However, AFAICT, when validating Bitcoin Core would equally accept 0x14003456 which decodes to the same target. We were however rejecting such non-canonical compact nBits. ``` >>> from electrum.blockchain import Blockchain >>> Blockchain.bits_to_target(0x14003456) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/user/wspace/electrum/electrum/blockchain.py", line 548, in bits_to_target raise Exception("Second part of bits should be in [0x8000, 0x7fffff]") Exception: Second part of bits should be in [0x8000, 0x7fffff] >>> Blockchain.bits_to_target(0x13345600) 1167130406913723784571467005534932607254396928 ```
2021-11-13 04:31:08 +01:00
Blockchain.bits_to_target(0x04923456)
with self.assertRaises(InvalidHeader): # overflow
blockchain: fix bugs in bits_to_target and target_to_bits This fixes three bugs: - too large targets: the fixme in target_to_bits, which meant that we could not handle targets where the first bit was non-zero. This however cannot happen due to these being over MAX_TARGET. (difficulty 1) - too small targets: in bits_to_target, very small targets were not handled well: ``` >>> Blockchain.bits_to_target(0x03008000) 32768 ``` We could not process headers with targets smaller than the above value. (note that these small targets would only occur at astronomically high mining difficulty) - non-canonically encoded targets: we would not accept headers that had targets encoded in compact form (nBits) in a non-canonical way. I think this bug could actually be triggered by mining such a header. E.g. consider the target "1167130406913723784571467005534932607254396928" ``` Blockchain.target_to_bits(1167130406913723784571467005534932607254396928).to_bytes(4, "big").hex() '13345600' ``` Bitcoin Core when used to e.g. mine a block would encode this target as 0x13345600 in compact form. However, AFAICT, when validating Bitcoin Core would equally accept 0x14003456 which decodes to the same target. We were however rejecting such non-canonical compact nBits. ``` >>> from electrum.blockchain import Blockchain >>> Blockchain.bits_to_target(0x14003456) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/user/wspace/electrum/electrum/blockchain.py", line 548, in bits_to_target raise Exception("Second part of bits should be in [0x8000, 0x7fffff]") Exception: Second part of bits should be in [0x8000, 0x7fffff] >>> Blockchain.bits_to_target(0x13345600) 1167130406913723784571467005534932607254396928 ```
2021-11-13 04:31:08 +01:00
Blockchain.bits_to_target(0xff123456)
class TestVerifyHeader(ElectrumTestCase):
# Data for Bitcoin block header #100.
valid_header = "0100000095194b8567fe2e8bbda931afd01a7acd399b9325cb54683e64129bcd00000000660802c98f18fd34fd16d61c63cf447568370124ac5f3be626c2e1c3c9f0052d19a76949ffff001d33f3c25d"
target = Blockchain.bits_to_target(0x1d00ffff)
prev_hash = "00000000cd9b12643e6854cb25939b39cd7a1ad0af31a9bd8b2efe67854b1995"
def setUp(self):
super().setUp()
self.header = deserialize_header(bfh(self.valid_header), 100)
def test_valid_header(self):
Blockchain.verify_header(self.header, self.prev_hash, self.target)
def test_expected_hash_mismatch(self):
with self.assertRaises(InvalidHeader):
Blockchain.verify_header(self.header, self.prev_hash, self.target,
expected_header_hash="foo")
def test_prev_hash_mismatch(self):
with self.assertRaises(InvalidHeader):
Blockchain.verify_header(self.header, "foo", self.target)
def test_target_mismatch(self):
with self.assertRaises(InvalidHeader):
other_target = Blockchain.bits_to_target(0x1d00eeee)
Blockchain.verify_header(self.header, self.prev_hash, other_target)
def test_insufficient_pow(self):
with self.assertRaises(InvalidHeader):
self.header["nonce"] = 42
Blockchain.verify_header(self.header, self.prev_hash, self.target)