kernel: Add function to read block undo data from disk to C header

This adds functions for reading the undo data from disk with a retrieved
block tree entry. The undo data of a block contains all the spent
script pubkeys of all the transactions in a block. For ease of
understanding the undo data is renamed to spent outputs with seperate
data structures exposed for a block's and a transaction's spent outputs.

In normal operations undo data is used during re-orgs. This data might
also be useful for building external indexes, or to scan for silent
payment transactions.

Internally the block undo data contains a vector of transaction undo
data which contains a vector of the coins consumed. The coins are all
int the order of the transaction inputs of the consuming transactions.
Each coin can be used to retrieve a transaction output and in turn a
script pubkey and amount.

This translates to the three-level hierarchy the api provides: Block
spent outputs contain transaction spent outputs, which contain
individual coins. Each coin includes the associated output, the height
of the block is contained in, and whether it is from a coinbase
transaction.
This commit is contained in:
TheCharlatan
2024-06-01 14:47:56 +02:00
parent 09d0f62638
commit f5d5d1213c
4 changed files with 420 additions and 4 deletions

View File

@@ -701,11 +701,13 @@ void chainman_mainnet_validation_test(TestDirectory& test_directory)
check_equal(read_block.value().ToBytes(), raw_block);
// Check that we can read the previous block
auto tip_2{tip.GetPrevious()};
auto read_block_2{chainman->ReadBlock(tip_2.value())};
BlockTreeEntry tip_2{*tip.GetPrevious()};
Block read_block_2{*chainman->ReadBlock(tip_2)};
BOOST_CHECK_EQUAL(chainman->ReadBlockSpentOutputs(tip_2).Count(), 0);
BOOST_CHECK_EQUAL(chainman->ReadBlockSpentOutputs(tip).Count(), 0);
// It should be an error if we go another block back, since the genesis has no ancestor
BOOST_CHECK(!tip_2.value().GetPrevious());
BOOST_CHECK(!tip_2.GetPrevious());
// If we try to validate it again, it should be a duplicate
BOOST_CHECK(chainman->ProcessBlock(block, &new_block));
@@ -782,6 +784,48 @@ BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests)
auto read_block_2 = chainman->ReadBlock(tip_2).value();
check_equal(read_block_2.ToBytes(), hex_string_to_byte_vec(REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 2]));
// Read spent outputs for current tip and its previous block
BlockSpentOutputs block_spent_outputs{chainman->ReadBlockSpentOutputs(tip)};
BlockSpentOutputs block_spent_outputs_prev{chainman->ReadBlockSpentOutputs(*tip.GetPrevious())};
CheckHandle(block_spent_outputs, block_spent_outputs_prev);
CheckRange(block_spent_outputs_prev.TxsSpentOutputs(), block_spent_outputs_prev.Count());
BOOST_CHECK_EQUAL(block_spent_outputs.Count(), 1);
// Get transaction spent outputs from the last transaction in the two blocks
TransactionSpentOutputsView transaction_spent_outputs{block_spent_outputs.GetTxSpentOutputs(block_spent_outputs.Count() - 1)};
TransactionSpentOutputs owned_transaction_spent_outputs{transaction_spent_outputs};
TransactionSpentOutputs owned_transaction_spent_outputs_prev{block_spent_outputs_prev.GetTxSpentOutputs(block_spent_outputs_prev.Count() - 1)};
CheckHandle(owned_transaction_spent_outputs, owned_transaction_spent_outputs_prev);
CheckRange(transaction_spent_outputs.Coins(), transaction_spent_outputs.Count());
// Get the last coin from the transaction spent outputs
CoinView coin{transaction_spent_outputs.GetCoin(transaction_spent_outputs.Count() - 1)};
BOOST_CHECK(!coin.IsCoinbase());
Coin owned_coin{coin};
Coin owned_coin_prev{owned_transaction_spent_outputs_prev.GetCoin(owned_transaction_spent_outputs_prev.Count() - 1)};
CheckHandle(owned_coin, owned_coin_prev);
// Validate coin properties
TransactionOutputView output = coin.GetOutput();
uint32_t coin_height = coin.GetConfirmationHeight();
BOOST_CHECK_EQUAL(coin_height, 205);
BOOST_CHECK_EQUAL(output.Amount(), 100000000);
// Test script pubkey serialization
auto script_pubkey = output.GetScriptPubkey();
auto script_pubkey_bytes{script_pubkey.ToBytes()};
BOOST_CHECK_EQUAL(script_pubkey_bytes.size(), 22);
auto round_trip_script_pubkey{ScriptPubkey(script_pubkey_bytes)};
BOOST_CHECK_EQUAL(round_trip_script_pubkey.ToBytes().size(), 22);
for (const auto tx_spent_outputs : block_spent_outputs.TxsSpentOutputs()) {
for (const auto coins : tx_spent_outputs.Coins()) {
BOOST_CHECK_GT(coins.GetOutput().Amount(), 1);
}
}
std::filesystem::remove_all(test_directory.m_directory / "blocks" / "blk00000.dat");
BOOST_CHECK(!chainman->ReadBlock(tip_2));
BOOST_CHECK(!chainman->ReadBlock(tip_2).has_value());
std::filesystem::remove_all(test_directory.m_directory / "blocks" / "rev00000.dat");
BOOST_CHECK_THROW(chainman->ReadBlockSpentOutputs(tip), std::runtime_error);
}