Skip to content

Conversation

theStack
Copy link
Contributor

This PR adds a dedicated functional test for XORed block data/undo file support (bitcoind option -blocksxor, see PR #28052). In order to verify that the XOR pattern has been applied, the {blk,rev}*.dat files are rewritten un-XORed manually by the test while the node is shut down; the node is then started again with -blocksxor=0, and both the data and undo files are verified via the verifychain RPC (with checklevel=2). Note that starting bitcoind with -blocksxor=0 fails if a xor key is present already, which is also tested explicitly.

Fixes #30599.

@DrahtBot
Copy link
Contributor

DrahtBot commented Aug 14, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK maflcko, ismaelsadeeq, glozow
Concept ACK TheCharlatan
Approach ACK tdb3

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

@DrahtBot DrahtBot added the Tests label Aug 14, 2024
@theStack theStack changed the title test: add functional test for XORed block/undo files (-blockxor option) test: add functional test for XORed block/undo files (-blocksxor option) Aug 14, 2024
@theStack theStack force-pushed the 202408-add_blocksxor_test branch from 86b80ce to fb2ea1e Compare August 14, 2024 15:35
@theStack theStack force-pushed the 202408-add_blocksxor_test branch from fb2ea1e to faa1b9b Compare August 14, 2024 15:38
@TheCharlatan
Copy link
Contributor

Concept ACK

Copy link
Contributor

@tdb3 tdb3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach ACK.
Nice additions.
Left a comment and a nit.

@@ -311,6 +311,13 @@ def sha256sum_file(filename):
return h.digest()


def util_xor(data, key, *, offset):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitty nit: Since this is being moved, could update to use a more descriptive function name (e.g. xor_bytes). Feel free to ignore.

Comment on lines +58 to +61
self.start_node(0, extra_args=['-blocksxor=0'])
# checklevel=2 -> verify block validity + undo data
# nblocks=0 -> verify all blocks
node.verifychain(checklevel=2, nblocks=0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The help for -blocksxor mentions The created XOR-key will be zeros for an existing blocksdir or when -blocksxor=0 is set.

After the verifychain, could test that a new xor.dat is created (consisting of all zeros) when the node is started with no xor.dat and with -blocksxor=0. Something like:

assert read_xor_key(node=node) == bytes(8)

or maybe lift NUM_XOR_BYTES out of read_xor_key to enable

assert read_xor_key(node=node) == bytes(NUM_XOR_BYTES)

or even add a constant in util.py

NULL_BLK_XOR_KEY = bytes(NUM_XOR_BYTES) # all-0 xor key

to enable

assert read_xor_key(node=node) == NULL_BLK_XOR_KEY

Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK faa1b9b

@@ -508,6 +515,12 @@ def check_node_connections(*, node, num_in, num_out):
assert_equal(info["connections_out"], num_out)


def read_xor_key(*, node):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Any reason to make this a global function and pass a node to it? Seems clearer to just make it a member function on the node, like blocks_path(), no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Any reason to make this a global function and pass a node to it? Seems clearer to just make it a member function on the node, like blocks_path(), no?

No strong reason. Personally I first look into util.py for helpers and tend to forget that there are also some implemented as TestNode methods, but I agree that the latter makes sense. Could be picked up in a follow-up, e.g. #30669.

@DrahtBot DrahtBot requested review from tdb3 and TheCharlatan August 15, 2024 05:53
@maflcko maflcko added this to the 28.0 milestone Aug 15, 2024
Comment on lines +51 to +54
self.log.info("Check that restarting with 'blocksxor=0' fails if XOR key is present")
node.assert_start_raises_init_error(['-blocksxor=0'],
'The blocksdir XOR-key can not be disabled when a random key was already stored!',
match=ErrorMatch.PARTIAL_REGEX)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails for me locally, because somehow I have 0000000000000000 as my xor key... but it fails consistently. Unsure if that's supposed to be possible?

Copy link
Member

@maflcko maflcko Aug 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a leftover previous bitcoind was used? Can you try make clean, just to double check? Otherwise, the combined log could be useful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a if (opts.use_xor) assert(xor_key != decltype(xor_key){}); which hit, so not an old bitcoind. Attaching combined log.

 node0 2024-08-15T16:40:41.093882Z [init] [node/blockstorage.cpp:1184] [InitBlocksdirXorKey] Using obfuscation key for blocksdir *.dat files (/tmp/bitcoin_func_test__c38k83u/node0/regtest/blocks): '0000000000000000' 

logs_30657.txt

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a if (opts.use_xor) assert(xor_key != decltype(xor_key){}); which hit, so not an old bitcoind. Attaching combined log.

Can you also print/log/debug the result of fs::is_empty(opts.blocks_dir) and fs::exists(xor_key_path)?

Copy link
Member

@glozow glozow Aug 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logs_30657_2.txt

Ah interesting, fs::is_empty(opts.blocks_dir) is 0

^so clearly this isn't a problem with the test

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess fs::is_empty(opts.blocks_dir) could be replaced by not fs::exists("blk0000.dat")?

Copy link
Member

@glozow glozow Aug 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw the sleepy version doesn't seem to use 0 as xor key when I start a node (tried a few times with mainnet, regtest) normally. I also never have problems when I use a non-tmp folder. So maybe your guess about temp directory paranormal activity is correct.

Going to merge this test since there's not a problem with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found this here because I saw #30833 and it got me to look into blocksxor for the first time. I see the same failures @glozow describes (also on macos) and I can confirm the findings here about the use of the temp folders because it does not happen when the test uses a clean chain, e.g. using a clean chain like this made the test robust for me: fjahr@ef00b8e I didn't find anything new beyond that so far but I thought it should be noted that it's not on @glozow 's machine alone.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made the test robust for me: fjahr@ef00b8e

Thanks for checking. Using a clean chain means that the cache won't be used, which normally lives on the storage unit where the build dir lives, so only the temp dir should be used. However, given the prior result that a non-tmp folder was generally fine, I would have expected the opposite result (Test would be less fragile by using the persisted cache on a non-tmp dir). However, I don't have a macOS to test this, so I don't know for sure what is going on. Also, if this "fixes" the test, it will just silence the symptoms, but it may be better to fix the underlying issue itself. The suggested replacement in #30657 (comment) may be a good try.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess fs::is_empty(opts.blocks_dir) could be replaced by not fs::exists("blk0000.dat")?

I think that alone wouldn't work if the user upgrades a pruned node to v28?

Anyway, I was going to revisit this today but I simply don't see the failures anymore currently 🤷‍♂️ I will get back to it if I see them occur again.

Copy link
Member

@ismaelsadeeq ismaelsadeeq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested ACK faa1b9b

@glozow
Copy link
Member

glozow commented Aug 16, 2024

ACK faa1b9b

@glozow glozow merged commit 6474132 into bitcoin:master Aug 16, 2024
16 checks passed
@theStack theStack deleted the 202408-add_blocksxor_test branch August 20, 2024 13:28
achow101 added a commit that referenced this pull request Aug 26, 2024
e1d5dd7 test: check xor.dat recreated when missing (tdb3)
d161096 test: add null block xor key (tdb3)
1ad999b refactor: lift NUM_XOR_BYTES (tdb3)
d839958 refactor: move read_xor_key() to TestNode (tdb3)
d43948c refactor: use unlink rather than os.remove (tdb3)
c8176f7 test: add blocks_key_path (tdb3)

Pull request description:

  Builds on PR #30657.

  Refactors `read_xor_key()` from `util.py` to `test_node.py` (comment #30657 (comment))

  Adds a check that `xor.dat` is created when missing (comment #30657 (comment))

  Help states:
  ```
  -blocksxor
         Whether an XOR-key applies to blocksdir *.dat files. The created XOR-key
         will be zeros for an existing blocksdir or when `-blocksxor=0` is
         set, and random for a freshly initialized blocksdir. (default: 1)
  ```

ACKs for top commit:
  maflcko:
    ACK e1d5dd7
  achow101:
    ACK e1d5dd7
  theStack:
    re-ACK e1d5dd7
  brunoerg:
    reACK e1d5dd7

Tree-SHA512: 325912ef646ec88e0a58e1ece263a2b04cbc06497e8fe5fcd603e509e80c6bcf84b09dd52dfac60e23013f07fc2b2f6db851ed0598649c3593f452c4a1424bd9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Follow-up ideas for XOR blocksdir *.dat files
8 participants