Skip to content

Commit 50d423d

Browse files
committed
BIP ???: Serve block undo data
1 parent 8c07800 commit 50d423d

File tree

10 files changed

+602
-0
lines changed

10 files changed

+602
-0
lines changed

src/compressor.h

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,162 @@ struct ScriptCompression
9595
}
9696
};
9797

98+
enum class ReconstructableScriptType: unsigned char {
99+
Unknown = 0x00,
100+
P2pkh = 0x01,
101+
P2pkEven = 0x02,
102+
P2pkOdd = 0x03,
103+
P2pkUncompressed = 0x04,
104+
P2sh = 0x06,
105+
P2wsh = 0x07,
106+
P2wpkh = 0x08,
107+
P2tr = 0x09,
108+
};
109+
110+
template <typename Stream>
111+
void Serialize(Stream& s, ReconstructableScriptType type)
112+
{
113+
::Serialize(s, static_cast<uint8_t>(type));
114+
}
115+
116+
struct ReconstructableScript {
117+
template<typename Stream>
118+
void Ser(Stream& s, const CScript& script)
119+
{
120+
if (script.IsPayToTaproot()) {
121+
::Serialize(s, ReconstructableScriptType::P2tr);
122+
::Serialize(s, MakeByteSpan(script).subspan(2, 32));
123+
return;
124+
}
125+
if (script.IsPayToWitnessScriptHash()) {
126+
::Serialize(s, ReconstructableScriptType::P2wsh);
127+
::Serialize(s, MakeByteSpan(script).subspan(2, 32));
128+
return;
129+
}
130+
if (script.size() == 22 && script[0] == OP_0 && script[1] == 20) {
131+
::Serialize(s, ReconstructableScriptType::P2wpkh);
132+
::Serialize(s, MakeByteSpan(script).subspan(2, 20));
133+
return;
134+
}
135+
if (script.IsPayToScriptHash()) {
136+
::Serialize(s, ReconstructableScriptType::P2sh);
137+
::Serialize(s, MakeByteSpan(script).subspan(2, 20));
138+
return;
139+
}
140+
if (script.size() == 25 && script[0] == OP_DUP && script[1] == OP_HASH160
141+
&& script[2] == 20 && script[23] == OP_EQUALVERIFY
142+
&& script[24] == OP_CHECKSIG) {
143+
::Serialize(s, ReconstructableScriptType::P2pkh);
144+
::Serialize(s, MakeByteSpan(script).subspan(3, 20));
145+
return;
146+
}
147+
if (script.size() == 35 && script[0] == 33 && script[34] == OP_CHECKSIG) {
148+
if (script[1] == 0x02) {
149+
::Serialize(s, ReconstructableScriptType::P2pkEven);
150+
::Serialize(s, MakeByteSpan(script).subspan(2, 32));
151+
return;
152+
}
153+
if (script[1] == 0x03) {
154+
::Serialize(s, ReconstructableScriptType::P2pkOdd);
155+
::Serialize(s, MakeByteSpan(script).subspan(2, 32));
156+
return;
157+
}
158+
}
159+
if (script.size() == 67 && script[0] == 65 && script[66] == OP_CHECKSIG && script[1] == 0x04) {
160+
::Serialize(s, ReconstructableScriptType::P2pkUncompressed);
161+
::Serialize(s, MakeByteSpan(script).subspan(2, 64));
162+
return;
163+
}
164+
::Serialize(s, ReconstructableScriptType::Unknown);
165+
::Serialize(s, script);
166+
}
167+
168+
template<typename Stream>
169+
void Unser(Stream& s, CScript& script)
170+
{
171+
unsigned char type;
172+
::Unserialize(s, type);
173+
ReconstructableScriptType s_type{type};
174+
switch (s_type) {
175+
case ReconstructableScriptType::P2tr: {
176+
std::array<unsigned char, 32> x_only;
177+
::Unserialize(s, std::span{x_only});
178+
script.resize(34);
179+
script[0] = OP_1;
180+
script[1] = 32;
181+
memcpy(&script[2], x_only.data(), 32);
182+
break;
183+
}
184+
case ReconstructableScriptType::P2wsh: {
185+
uint256 hash;
186+
::Unserialize(s, hash);
187+
script.resize(34);
188+
script[0] = OP_0;
189+
script[1] = 32;
190+
memcpy(&script[2], hash.data(), 32);
191+
break;
192+
}
193+
case ReconstructableScriptType::P2wpkh: {
194+
uint160 hash;
195+
::Unserialize(s, hash);
196+
script.resize(22);
197+
script[0] = OP_0;
198+
script[1] = 20;
199+
memcpy(&script[2], hash.data(), 20);
200+
break;
201+
}
202+
case ReconstructableScriptType::P2sh: {
203+
uint160 hash;
204+
::Unserialize(s, hash);
205+
script.resize(23);
206+
script[0] = OP_HASH160;
207+
script[1] = 20;
208+
memcpy(&script[2], hash.data(), 20);
209+
script[22] = OP_EQUAL;
210+
break;
211+
}
212+
case ReconstructableScriptType::P2pkh: {
213+
uint160 hash;
214+
::Unserialize(s, hash);
215+
script.resize(25);
216+
script[0] = OP_DUP;
217+
script[1] = OP_HASH160;
218+
script[2] = 20;
219+
memcpy(&script[3], hash.data(), 20);
220+
script[23] = OP_EQUALVERIFY;
221+
script[24] = OP_CHECKSIG;
222+
break;
223+
}
224+
case ReconstructableScriptType::P2pkEven:
225+
case ReconstructableScriptType::P2pkOdd: {
226+
std::array<unsigned char, 32> xcoord;
227+
::Unserialize(s, std::span{xcoord});
228+
script.resize(35);
229+
script[0] = 33;
230+
script[1] = type;
231+
memcpy(&script[2], xcoord.data(), 32);
232+
script[34] = OP_CHECKSIG;
233+
break;
234+
}
235+
case ReconstructableScriptType::P2pkUncompressed: {
236+
std::array<unsigned char, 64> public_key;
237+
::Unserialize(s, std::span{public_key});
238+
script.resize(67);
239+
script[0] = 65;
240+
script[1] = 0x04;
241+
memcpy(&script[2], public_key.data(), 64);
242+
script[66] = OP_CHECKSIG;
243+
break;
244+
}
245+
case ReconstructableScriptType::Unknown: {
246+
::Unserialize(s, script);
247+
break;
248+
}
249+
default: break;
250+
}
251+
}
252+
};
253+
98254
struct AmountCompression
99255
{
100256
template<typename Stream, typename I> void Ser(Stream& s, I val)

src/init.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
557557
argsman.AddArg("-v2transport", strprintf("Support v2 transport (default: %u)", DEFAULT_V2_TRANSPORT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
558558
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
559559
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
560+
argsman.AddArg("-peerblockinputs", strprintf("Serve historical inputs to blocks (default: %u)", DEFAULT_PEERBLOCKUNDO), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
560561
argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
561562
argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet3: %u, testnet4: %u, signet: %u, regtest: %u). Not relevant for I2P (see doc/i2p.md). If set to a value x, the default onion listening port will be set to x+1.", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), testnet4ChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
562563
const std::string proxy_doc_for_value =
@@ -984,6 +985,13 @@ bool AppInitParameterInteraction(const ArgsManager& args)
984985
g_local_services = ServiceFlags(g_local_services | NODE_COMPACT_FILTERS);
985986
}
986987

988+
if (args.GetBoolArg("-peerblockinputs", DEFAULT_PEERBLOCKUNDO)) {
989+
if (args.GetIntArg("-prune", 0)) {
990+
return InitError(_("Prune mode is incompatible with serving block inputs."));
991+
}
992+
g_local_services = ServiceFlags(g_local_services | NODE_BLOCK_UNDO);
993+
}
994+
987995
if (args.GetIntArg("-prune", 0)) {
988996
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX))
989997
return InitError(_("Prune mode is incompatible with -txindex."));

src/net_processing.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include <tinyformat.h>
5757
#include <txmempool.h>
5858
#include <uint256.h>
59+
#include <undo.h>
5960
#include <util/check.h>
6061
#include <util/strencodings.h>
6162
#include <util/time.h>
@@ -1076,6 +1077,8 @@ class PeerManagerImpl final : public PeerManager
10761077
*/
10771078
void ProcessGetCFCheckPt(CNode& node, Peer& peer, DataStream& vRecv);
10781079

1080+
void ProcessGetBlockUndo(CNode& node, Peer& peer, DataStream& vRecv);
1081+
10791082
/** Checks if address relay is permitted with peer. If needed, initializes
10801083
* the m_addr_known bloom filter and sets m_addr_relay_enabled to true.
10811084
*
@@ -3394,6 +3397,48 @@ void PeerManagerImpl::ProcessGetCFCheckPt(CNode& node, Peer& peer, DataStream& v
33943397
headers);
33953398
}
33963399

3400+
void PeerManagerImpl::ProcessGetBlockUndo(CNode& pfrom, Peer& peer, DataStream& vRecv)
3401+
{
3402+
uint256 blockhash;
3403+
vRecv >> blockhash;
3404+
uint32_t cutoff;
3405+
vRecv >> cutoff;
3406+
if ((peer.m_our_services & NODE_BLOCK_UNDO) != NODE_BLOCK_UNDO) {
3407+
LogDebug(BCLog::NET, "ignoring unsupported block undo request, %s\n", pfrom.DisconnectMsg(fLogIPs));
3408+
pfrom.fDisconnect = true;
3409+
return;
3410+
}
3411+
CBlockIndex* pindex{nullptr};
3412+
{
3413+
LOCK(cs_main);
3414+
pindex = m_chainman.m_blockman.LookupBlockIndex(blockhash);
3415+
if (!pindex) {
3416+
return;
3417+
}
3418+
if (!BlockRequestAllowed(pindex)) {
3419+
LogDebug(BCLog::NET, "%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom.GetId());
3420+
return;
3421+
}
3422+
// disconnect node in case we have reached the outbound limit for serving historical blocks
3423+
if (m_connman.OutboundTargetReached(true) &&
3424+
(((m_chainman.m_best_header != nullptr) && (m_chainman.m_best_header->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE))) &&
3425+
!pfrom.HasPermission(NetPermissionFlags::Download) // nodes with the download permission may exceed target
3426+
) {
3427+
LogDebug(BCLog::NET, "historical block serving limit reached, %s\n", pfrom.DisconnectMsg(fLogIPs));
3428+
pfrom.fDisconnect = true;
3429+
return;
3430+
}
3431+
}
3432+
CBlockUndo block_undo{};
3433+
if (m_chainman.m_blockman.ReadBlockUndo(block_undo, *pindex)) {
3434+
auto hash = pindex->GetBlockHash();
3435+
NetworkBlockUndo undo{hash, block_undo, cutoff};
3436+
MakeAndPushMessage(pfrom, NetMsgType::BLOCKUNDO, undo);
3437+
} else {
3438+
LogError("Cannot load block undo from disk, %s\n", pfrom.DisconnectMsg(fLogIPs));
3439+
}
3440+
}
3441+
33973442
void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked)
33983443
{
33993444
bool new_block{false};
@@ -5120,6 +5165,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
51205165
return;
51215166
}
51225167

5168+
if (msg_type == NetMsgType::GETBLOCKUNDO) {
5169+
ProcessGetBlockUndo(pfrom, *peer, vRecv);
5170+
}
5171+
51235172
if (msg_type == NetMsgType::NOTFOUND) {
51245173
std::vector<CInv> vInv;
51255174
vRecv >> vInv;

src/net_processing.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ static constexpr bool DEFAULT_TXRECONCILIATION_ENABLE{false};
4242
static const uint32_t DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN{100};
4343
static const bool DEFAULT_PEERBLOOMFILTERS = false;
4444
static const bool DEFAULT_PEERBLOCKFILTERS = false;
45+
static const bool DEFAULT_PEERBLOCKUNDO = false;
4546
/** Maximum number of outstanding CMPCTBLOCK requests for the same block. */
4647
static const unsigned int MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK = 3;
4748
/** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends

src/protocol.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ static std::string serviceFlagToStr(size_t bit)
9999
case NODE_COMPACT_FILTERS: return "COMPACT_FILTERS";
100100
case NODE_NETWORK_LIMITED: return "NETWORK_LIMITED";
101101
case NODE_P2P_V2: return "P2P_V2";
102+
case NODE_BLOCK_UNDO: return "BLOCK_UNDO";
102103
// Not using default, so we get warned when a case is missing
103104
}
104105

src/protocol.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ inline constexpr const char* GETBLOCKTXN{"getblocktxn"};
216216
* @since protocol version 70014 as described by BIP 152
217217
*/
218218
inline constexpr const char* BLOCKTXN{"blocktxn"};
219+
220+
inline constexpr const char* GETBLOCKUNDO{"getbundo"};
221+
222+
inline constexpr const char* BLOCKUNDO{"bundo"};
219223
/**
220224
* getcfilters requests compact filters for a range of blocks.
221225
* Only available with service bit NODE_COMPACT_FILTERS as described by
@@ -303,6 +307,8 @@ inline const std::array ALL_NET_MESSAGE_TYPES{std::to_array<std::string>({
303307
NetMsgType::CFCHECKPT,
304308
NetMsgType::WTXIDRELAY,
305309
NetMsgType::SENDTXRCNCL,
310+
NetMsgType::BLOCKUNDO,
311+
NetMsgType::GETBLOCKUNDO,
306312
})};
307313

308314
/** nServices flags */
@@ -336,6 +342,7 @@ enum ServiceFlags : uint64_t {
336342
// collisions and other cases where nodes may be advertising a service they
337343
// do not actually support. Other service bits should be allocated via the
338344
// BIP process.
345+
NODE_BLOCK_UNDO = (1 << 21),
339346
};
340347

341348
/**

src/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ add_executable(test_bitcoin
120120
txvalidation_tests.cpp
121121
txvalidationcache_tests.cpp
122122
uint256_tests.cpp
123+
undo_tests.cpp
123124
util_check_tests.cpp
124125
util_expected_tests.cpp
125126
util_string_tests.cpp

0 commit comments

Comments
 (0)