Skip to content

Commit daed70b

Browse files
committed
Merge compact-blocks: BIP 152 relay, LevelDB pruning, verified cascade
BIP 152 compact blocks: - SipHash-2-4 restored for short TX ID generation - Full send/receive path with mempool-based block reconstruction - getblocktxn/blocktxn fallback for missing transactions - sendcmpct(false, 2) negotiation after verack - wtxidrelay (BIP 339) peer state tracking - 15 unit tests for compact block operations LevelDB pruning (fixes 4GB RSS crash): - Bounded load at startup: only newest chain_length*2+10 shares - Evicted shares removed from LevelDB during run_think() trim - SharechainStorage::remove_share() exposed to node layer Safe verified↔chain cascade (fixes potential UAF): - Deferred destruction: trim chain → cascade to verified → destroy - ShareChain::trim() deferred_destroy parameter - ShareChain::remove() owns_data parameter - Matches Python p2pool node.py:355-398 pattern 308 tests passing. RSS stable at ~69MB (was crashing at 4068MB).
2 parents 077c2ca + b966ea3 commit daed70b

15 files changed

Lines changed: 1441 additions & 41 deletions

File tree

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ jobs:
8484
core_test sharechain_test share_test \
8585
test_threading test_weights \
8686
test_header_chain test_mempool test_template_builder \
87-
test_doge_chain \
87+
test_doge_chain test_compact_blocks \
8888
test_phase0_live test_phase1_live test_phase2_live \
8989
test_phase3_live test_phase4_embedded test_phase4_live \
9090
-j$(nproc)

docs/FUTURE.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,43 @@ At 308K shares × ~4.5 KB/share = **~1.39 GB RAM**. Three-tier architecture:
148148
Epoch size: 720 shares (~3 hours). Each epoch summary replaces 720 full
149149
share objects (~216 KB) with one ~100-byte record — **2,160× compression**.
150150
151+
#### P1: Compact Share Relay (BIP 152 Inspired)
152+
153+
Applying compact block concepts to sharechain relay. Since all peers maintain
154+
similar PPLNS window state, redundancy across consecutive shares is extreme:
155+
156+
**Problem:** Each share ~4.5 KB includes a full coinbase transaction with all
157+
PPLNS payout outputs. Consecutive shares have ~95% identical coinbase data
158+
(same output scripts, slightly different amounts).
159+
160+
**Approach** — three complementary techniques:
161+
162+
| Technique | Savings | How |
163+
|-----------|---------|-----|
164+
| **Short-ID transaction refs** | ~80% of tx data | SipHash-2-4 short IDs for share coinbase inputs/outputs, reconstruct from PPLNS state |
165+
| **Delta-encoded coinbases** | ~90% of coinbase | Only transmit changed amount deltas vs. previous share's coinbase |
166+
| **Prefilled-only new outputs** | Variable | New miners entering PPLNS window get full output; existing miners referenced by index |
167+
168+
**Wire format sketch:**
169+
170+
```
171+
CompactShare {
172+
share_header // ~200 bytes (same as today)
173+
coinbase_delta // varint-encoded amount deltas per output index
174+
new_outputs[] // PrefilledOutput { index, script, amount } for new PPLNS entrants
175+
removed_indexes[] // output indexes dropped from PPLNS window
176+
nonce // SipHash key derivation (reuse BIP 152 pattern)
177+
}
178+
```
179+
180+
**Expected compression:** ~4.5 KB → ~300-500 bytes per share relay (~90%).
181+
At 308K window and 30-second share period, reduces sustained relay bandwidth
182+
from ~150 bytes/s to ~15 bytes/s per peer.
183+
184+
**Dependency:** Requires all peers to maintain synchronized PPLNS state to
185+
reconstruct full shares. Fallback: request full share via `getsharedata`
186+
(analogous to `getblocktxn`).
187+
151188
#### P1: Adaptive Window Consensus Integration
152189
153190
- Share validation must agree on `adaptive_chain_length` given the same
@@ -204,9 +241,12 @@ delivery to a configurable URL (`--block-webhook URL`).
204241
### Historical Worker Stats DB — Not Started
205242
Persist per-worker statistics to SQLite with configurable retention.
206243
207-
### Direct Peer Block Broadcast — Not Started
208-
Parallel P2P broadcast of found blocks to multiple full nodes to reduce
209-
orphan rate. High impact for solo miners.
244+
### Direct Peer Block Broadcast — In Progress (BIP 152)
245+
Compact block relay via BIP 152 (sendcmpct/cmpctblock/getblocktxn/blocktxn).
246+
Reduces block relay bandwidth by ~90-95% using SipHash-2-4 short transaction
247+
IDs. Foundation committed: data structures, SipHash, peer negotiation.
248+
Remaining: wire send path (BuildCompactBlock → cmpctblock), receive path
249+
(reconstruct from mempool), multi-peer broadcast.
210250
211251
---
212252

src/btclibs/crypto/siphash.cpp

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright (c) 2016-2018 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "siphash.h"
6+
7+
#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
8+
9+
#define SIPROUND do { \
10+
v0 += v1; v1 = ROTL(v1, 13); v1 ^= v0; \
11+
v0 = ROTL(v0, 32); \
12+
v2 += v3; v3 = ROTL(v3, 16); v3 ^= v2; \
13+
v0 += v3; v3 = ROTL(v3, 21); v3 ^= v0; \
14+
v2 += v1; v1 = ROTL(v1, 17); v1 ^= v2; \
15+
v2 = ROTL(v2, 32); \
16+
} while (0)
17+
18+
CSipHasher::CSipHasher(uint64_t k0, uint64_t k1)
19+
{
20+
v[0] = 0x736f6d6570736575ULL ^ k0;
21+
v[1] = 0x646f72616e646f6dULL ^ k1;
22+
v[2] = 0x6c7967656e657261ULL ^ k0;
23+
v[3] = 0x7465646279746573ULL ^ k1;
24+
count = 0;
25+
tmp = 0;
26+
}
27+
28+
CSipHasher& CSipHasher::Write(uint64_t data)
29+
{
30+
uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
31+
32+
assert(count % 8 == 0);
33+
34+
v3 ^= data;
35+
SIPROUND;
36+
SIPROUND;
37+
v0 ^= data;
38+
39+
v[0] = v0;
40+
v[1] = v1;
41+
v[2] = v2;
42+
v[3] = v3;
43+
44+
count += 8;
45+
return *this;
46+
}
47+
48+
CSipHasher& CSipHasher::Write(const unsigned char* data, size_t size)
49+
{
50+
uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
51+
uint64_t t = tmp;
52+
uint8_t c = count;
53+
54+
while (size--) {
55+
t |= ((uint64_t)(*(data++))) << (8 * (c % 8));
56+
c++;
57+
if ((c & 7) == 0) {
58+
v3 ^= t;
59+
SIPROUND;
60+
SIPROUND;
61+
v0 ^= t;
62+
t = 0;
63+
}
64+
}
65+
66+
v[0] = v0;
67+
v[1] = v1;
68+
v[2] = v2;
69+
v[3] = v3;
70+
count = c;
71+
tmp = t;
72+
73+
return *this;
74+
}
75+
76+
uint64_t CSipHasher::Finalize() const
77+
{
78+
uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
79+
80+
uint64_t t = tmp | (((uint64_t)count) << 56);
81+
82+
v3 ^= t;
83+
SIPROUND;
84+
SIPROUND;
85+
v0 ^= t;
86+
v2 ^= 0xFF;
87+
SIPROUND;
88+
SIPROUND;
89+
SIPROUND;
90+
SIPROUND;
91+
return v0 ^ v1 ^ v2 ^ v3;
92+
}
93+
94+
uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val)
95+
{
96+
/* Specialized implementation for efficiency */
97+
uint64_t d = val.GetUint64(0);
98+
99+
uint64_t v0 = 0x736f6d6570736575ULL ^ k0;
100+
uint64_t v1 = 0x646f72616e646f6dULL ^ k1;
101+
uint64_t v2 = 0x6c7967656e657261ULL ^ k0;
102+
uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d;
103+
104+
SIPROUND;
105+
SIPROUND;
106+
v0 ^= d;
107+
d = val.GetUint64(1);
108+
v3 ^= d;
109+
SIPROUND;
110+
SIPROUND;
111+
v0 ^= d;
112+
d = val.GetUint64(2);
113+
v3 ^= d;
114+
SIPROUND;
115+
SIPROUND;
116+
v0 ^= d;
117+
d = val.GetUint64(3);
118+
v3 ^= d;
119+
SIPROUND;
120+
SIPROUND;
121+
v0 ^= d;
122+
v3 ^= ((uint64_t)4) << 59;
123+
SIPROUND;
124+
SIPROUND;
125+
v0 ^= ((uint64_t)4) << 59;
126+
v2 ^= 0xFF;
127+
SIPROUND;
128+
SIPROUND;
129+
SIPROUND;
130+
SIPROUND;
131+
return v0 ^ v1 ^ v2 ^ v3;
132+
}
133+
134+
uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra)
135+
{
136+
/* Specialized implementation for efficiency */
137+
uint64_t d = val.GetUint64(0);
138+
139+
uint64_t v0 = 0x736f6d6570736575ULL ^ k0;
140+
uint64_t v1 = 0x646f72616e646f6dULL ^ k1;
141+
uint64_t v2 = 0x6c7967656e657261ULL ^ k0;
142+
uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d;
143+
144+
SIPROUND;
145+
SIPROUND;
146+
v0 ^= d;
147+
d = val.GetUint64(1);
148+
v3 ^= d;
149+
SIPROUND;
150+
SIPROUND;
151+
v0 ^= d;
152+
d = val.GetUint64(2);
153+
v3 ^= d;
154+
SIPROUND;
155+
SIPROUND;
156+
v0 ^= d;
157+
d = val.GetUint64(3);
158+
v3 ^= d;
159+
SIPROUND;
160+
SIPROUND;
161+
v0 ^= d;
162+
d = (((uint64_t)36) << 56) | extra;
163+
v3 ^= d;
164+
SIPROUND;
165+
SIPROUND;
166+
v0 ^= d;
167+
v2 ^= 0xFF;
168+
SIPROUND;
169+
SIPROUND;
170+
SIPROUND;
171+
SIPROUND;
172+
return v0 ^ v1 ^ v2 ^ v3;
173+
}

src/btclibs/crypto/siphash.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2016-2018 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_CRYPTO_SIPHASH_H
6+
#define BITCOIN_CRYPTO_SIPHASH_H
7+
8+
#include <stdint.h>
9+
10+
#include <core/uint256.hpp>
11+
12+
/** SipHash-2-4 */
13+
class CSipHasher
14+
{
15+
private:
16+
uint64_t v[4];
17+
uint64_t tmp;
18+
uint8_t count; // Only the low 8 bits of the input size matter.
19+
20+
public:
21+
/** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */
22+
CSipHasher(uint64_t k0, uint64_t k1);
23+
/** Hash a 64-bit integer worth of data
24+
* It is treated as if this was the little-endian interpretation of 8 bytes.
25+
* This function can only be used when a multiple of 8 bytes have been written so far.
26+
*/
27+
CSipHasher& Write(uint64_t data);
28+
/** Hash arbitrary bytes. */
29+
CSipHasher& Write(const unsigned char* data, size_t size);
30+
/** Compute the 64-bit SipHash-2-4 of the data written so far. The object remains untouched. */
31+
uint64_t Finalize() const;
32+
};
33+
34+
/** Optimized SipHash-2-4 implementation for uint256.
35+
*
36+
* It is identical to:
37+
* SipHasher(k0, k1)
38+
* .Write(val.GetUint64(0))
39+
* .Write(val.GetUint64(1))
40+
* .Write(val.GetUint64(2))
41+
* .Write(val.GetUint64(3))
42+
* .Finalize()
43+
*/
44+
uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val);
45+
uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra);
46+
47+
#endif // BITCOIN_CRYPTO_SIPHASH_H

src/c2pool/storage/sharechain_storage.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,19 @@ bool SharechainStorage::has_share(const uint256& hash)
155155
if (!m_leveldb_store) {
156156
return false;
157157
}
158-
158+
159159
return m_leveldb_store->has_share(hash);
160160
}
161161

162+
bool SharechainStorage::remove_share(const uint256& hash)
163+
{
164+
if (!m_leveldb_store) {
165+
return false;
166+
}
167+
168+
return m_leveldb_store->remove_share(hash);
169+
}
170+
162171
void SharechainStorage::log_storage_stats()
163172
{
164173
if (!m_leveldb_store) {

src/c2pool/storage/sharechain_storage.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ class SharechainStorage {
9898
* @return True if share exists
9999
*/
100100
bool has_share(const uint256& hash);
101+
102+
/**
103+
* @brief Remove a share from storage
104+
* @param hash Share hash to remove
105+
* @return True if successfully removed
106+
*/
107+
bool remove_share(const uint256& hash);
101108

102109
/**
103110
* @brief Log storage statistics

src/core/uint256.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ class base_uint
6363
return &pn[0];
6464
}
6565

66+
/// Get a 64-bit chunk at position pos (0-3 for uint256).
67+
/// Used by SipHash (BIP 152 compact blocks).
68+
uint64_t GetUint64(int pos) const
69+
{
70+
return static_cast<uint64_t>(pn[pos * 2])
71+
| (static_cast<uint64_t>(pn[pos * 2 + 1]) << 32);
72+
}
73+
6674
uint32_t* end()
6775
{
6876
return &pn[WIDTH];

0 commit comments

Comments
 (0)