Skip to content

Commit fa35925

Browse files
author
MarcoFalke
committed
Add -blocksxor boolean option
1 parent fa7f7ac commit fa35925

File tree

8 files changed

+78
-7
lines changed

8 files changed

+78
-7
lines changed

doc/files.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ Subdirectory | File(s) | Description
4747
-------------------|-----------------------|------------
4848
`blocks/` | | Blocks directory; can be specified by `-blocksdir` option (except for `blocks/index/`)
4949
`blocks/index/` | LevelDB database | Block index; `-blocksdir` option does not affect this path
50-
`blocks/` | `blkNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Actual Bitcoin blocks (in network format, dumped in raw on disk, 128 MiB per file)
50+
`blocks/` | `blkNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Actual Bitcoin blocks (dumped in network format, 128 MiB per file)
5151
`blocks/` | `revNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Block undo data (custom format)
52+
`blocks/` | `xor.dat` | Rolling XOR pattern for block and undo data files
5253
`chainstate/` | LevelDB database | Blockchain state (a compact representation of all currently unspent transaction outputs (UTXOs) and metadata about the transactions they are from)
5354
`indexes/txindex/` | LevelDB database | Transaction index; *optional*, used if `-txindex=1`
5455
`indexes/blockfilter/basic/db/` | LevelDB database | Blockfilter index LevelDB database for the basic filtertype; *optional*, used if `-blockfilterindex=basic`

doc/release-notes-28052.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Blockstorage
2+
============
3+
4+
Block files are now XOR'd by default with a key stored in the blocksdir.
5+
Previous releases of Bitcoin Core or previous external software will not be able to read the blocksdir with a non-zero XOR-key.
6+
Refer to the `-blocksxor` help for more details.

src/init.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,13 @@ void SetupServerArgs(ArgsManager& argsman)
469469
#endif
470470
argsman.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
471471
argsman.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat files (default: <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
472+
argsman.AddArg("-blocksxor",
473+
strprintf("Whether an XOR-key applies to blocksdir *.dat files. "
474+
"The created XOR-key will be zeros for an existing blocksdir or when `-blocksxor=0` is "
475+
"set, and random for a freshly initialized blocksdir. "
476+
"(default: %u)",
477+
kernel::DEFAULT_XOR_BLOCKSDIR),
478+
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
472479
argsman.AddArg("-fastprune", "Use smaller block files and lower minimum prune height for testing purposes", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
473480
#if HAVE_SYSTEM
474481
argsman.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@@ -1534,7 +1541,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15341541
}
15351542
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024));
15361543

1537-
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown), chainman_opts, blockman_opts);
1544+
try {
1545+
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown), chainman_opts, blockman_opts);
1546+
} catch (std::exception& e) {
1547+
return InitError(strprintf(Untranslated("Failed to initialize ChainstateManager: %s"), e.what()));
1548+
}
15381549
ChainstateManager& chainman = *node.chainman;
15391550

15401551
// This is defined and set here instead of inline in validation.h to avoid a hard

src/kernel/blockmanager_opts.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ class CChainParams;
1414

1515
namespace kernel {
1616

17+
static constexpr bool DEFAULT_XOR_BLOCKSDIR{true};
18+
1719
/**
1820
* An options struct for `BlockManager`, more ergonomically referred to as
1921
* `BlockManager::Options` due to the using-declaration in `BlockManager`.
2022
*/
2123
struct BlockManagerOpts {
2224
const CChainParams& chainparams;
25+
bool use_xor{DEFAULT_XOR_BLOCKSDIR};
2326
uint64_t prune_target{0};
2427
bool fast_prune{false};
2528
const fs::path blocks_dir;

src/node/blockmanager_args.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
namespace node {
1717
util::Result<void> ApplyArgsManOptions(const ArgsManager& args, BlockManager::Options& opts)
1818
{
19+
if (auto value{args.GetBoolArg("-blocksxor")}) opts.use_xor = *value;
1920
// block pruning; get the amount of disk space (in MiB) to allot for block & undo files
2021
int64_t nPruneArg{args.GetIntArg("-prune", opts.prune_target)};
2122
if (nPruneArg < 0) {

src/node/blockstorage.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <pow.h>
2020
#include <primitives/block.h>
2121
#include <primitives/transaction.h>
22+
#include <random.h>
2223
#include <reverse_iterator.h>
2324
#include <serialize.h>
2425
#include <signet.h>
@@ -1144,9 +1145,43 @@ FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight)
11441145
return blockPos;
11451146
}
11461147

1148+
static auto InitBlocksdirXorKey(const BlockManager::Options& opts)
1149+
{
1150+
// Bytes are serialized without length indicator, so this is also the exact
1151+
// size of the XOR-key file.
1152+
std::array<std::byte, 8> xor_key{};
1153+
1154+
if (opts.use_xor && fs::is_empty(opts.blocks_dir)) {
1155+
// Only use random fresh key when the boolean option is set and on the
1156+
// very first start of the program.
1157+
FastRandomContext{}.fillrand(xor_key);
1158+
}
1159+
1160+
const fs::path xor_key_path{opts.blocks_dir / "xor.dat"};
1161+
if (fs::exists(xor_key_path)) {
1162+
// A pre-existing xor key file has priority.
1163+
AutoFile xor_key_file{fsbridge::fopen(xor_key_path, "rb")};
1164+
xor_key_file >> xor_key;
1165+
} else {
1166+
// Create initial or missing xor key file
1167+
AutoFile xor_key_file{fsbridge::fopen(xor_key_path, "wbx")};
1168+
xor_key_file << xor_key;
1169+
}
1170+
// If the user disabled the key, it must be zero.
1171+
if (!opts.use_xor && xor_key != decltype(xor_key){}) {
1172+
throw std::runtime_error{
1173+
strprintf("The blocksdir XOR-key can not be disabled when a random key was already stored! "
1174+
"Stored key: '%s', stored path: '%s'.",
1175+
HexStr(xor_key), fs::PathToString(xor_key_path)),
1176+
};
1177+
}
1178+
LogInfo("Using obfuscation key for blocksdir *.dat files (%s): '%s'\n", fs::PathToString(opts.blocks_dir), HexStr(xor_key));
1179+
return std::vector<std::byte>{xor_key.begin(), xor_key.end()};
1180+
}
1181+
11471182
BlockManager::BlockManager(const util::SignalInterrupt& interrupt, Options opts)
11481183
: m_prune_mode{opts.prune_target > 0},
1149-
m_xor_key{},
1184+
m_xor_key{InitBlocksdirXorKey(opts)},
11501185
m_opts{std::move(opts)},
11511186
m_block_file_seq{FlatFileSeq{m_opts.blocks_dir, "blk", m_opts.fast_prune ? 0x4000 /* 16kB */ : BLOCKFILE_CHUNK_SIZE}},
11521187
m_undo_file_seq{FlatFileSeq{m_opts.blocks_dir, "rev", UNDOFILE_CHUNK_SIZE}},

test/functional/feature_loadblock.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ def set_test_params(self):
2626
self.setup_clean_chain = True
2727
self.num_nodes = 2
2828
self.supports_cli = False
29+
self.extra_args = [
30+
["-blocksxor=0"], # TODO: The linearize scripts should be adjusted to apply any XOR
31+
[],
32+
]
2933

3034
def run_test(self):
3135
self.nodes[1].setnetworkactive(state=False)

test/functional/feature_reindex.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,19 @@ def out_of_order(self):
3939
# we're generating them rather than getting them from peers), so to
4040
# test out-of-order handling, swap blocks 1 and 2 on disk.
4141
blk0 = self.nodes[0].blocks_path / "blk00000.dat"
42+
with open(self.nodes[0].blocks_path / "xor.dat", "rb") as xor_f:
43+
NUM_XOR_BYTES = 8 # From InitBlocksdirXorKey::xor_key.size()
44+
xor_dat = xor_f.read(NUM_XOR_BYTES)
45+
46+
def util_xor(data, key, *, offset):
47+
data = bytearray(data)
48+
for i in range(len(data)):
49+
data[i] ^= key[(i + offset) % len(key)]
50+
return bytes(data)
51+
4252
with open(blk0, 'r+b') as bf:
4353
# Read at least the first few blocks (including genesis)
44-
b = bf.read(2000)
54+
b = util_xor(bf.read(2000), xor_dat, offset=0)
4555

4656
# Find the offsets of blocks 2, 3, and 4 (the first 3 blocks beyond genesis)
4757
# by searching for the regtest marker bytes (see pchMessageStart).
@@ -55,12 +65,12 @@ def find_block(b, start):
5565
b4_start = find_block(b, b3_start)
5666

5767
# Blocks 2 and 3 should be the same size.
58-
assert_equal(b3_start-b2_start, b4_start-b3_start)
68+
assert_equal(b3_start - b2_start, b4_start - b3_start)
5969

6070
# Swap the second and third blocks (don't disturb the genesis block).
6171
bf.seek(b2_start)
62-
bf.write(b[b3_start:b4_start])
63-
bf.write(b[b2_start:b3_start])
72+
bf.write(util_xor(b[b3_start:b4_start], xor_dat, offset=b2_start))
73+
bf.write(util_xor(b[b2_start:b3_start], xor_dat, offset=b3_start))
6474

6575
# The reindexing code should detect and accommodate out of order blocks.
6676
with self.nodes[0].assert_debug_log([

0 commit comments

Comments
 (0)