upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile15
-rw-r--r--src/btfrv.cpp65
-rw-r--r--src/btfrv.h19
-rw-r--r--src/btftunnel.cpp140
-rw-r--r--src/btftunnel.h25
-rw-r--r--src/db.cpp1251
-rw-r--r--src/db.h183
-rw-r--r--src/gui.cpp273
-rw-r--r--src/headers_core.h5
-rw-r--r--src/main.cpp578
-rw-r--r--src/main.h31
-rw-r--r--src/main_gui.cpp385
-rw-r--r--src/makefile.mingw12
-rw-r--r--src/net.cpp34
-rw-r--r--src/net.h90
-rw-r--r--src/nostr.cpp27
-rw-r--r--src/nostr.h3
-rw-r--r--src/randomx_pow.cpp28
-rw-r--r--src/readme.txt2
-rw-r--r--src/rpc.cpp852
-rw-r--r--src/selftest.cpp2083
-rw-r--r--src/selftest.h1
-rw-r--r--src/util.cpp153
-rw-r--r--src/util.h93
-rw-r--r--src/wallet_sqlite.cpp374
-rw-r--r--src/wallet_sqlite.h120
-rw-r--r--src/walletcmd.cpp975
-rw-r--r--src/walletcmd.h34
28 files changed, 7530 insertions, 321 deletions
diff --git a/src/Makefile b/src/Makefile
index 1bc3ae8..b931007 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -6,7 +6,9 @@ FUZZ_CXX ?= clang++
6 6
7SECP_INC ?= /usr/local/include 7SECP_INC ?= /usr/local/include
8SECP_LIB ?= /usr/local/lib 8SECP_LIB ?= /usr/local/lib
9RANDOMX_DIR ?= $(HOME)/RandomX 9# The root Makefile exports RANDOMX_DIR (default: <repo>/randomx-build, built
10# by deps-randomx). This fallback only applies when src/ is built standalone.
11RANDOMX_DIR ?= $(abspath $(CURDIR)/..)/randomx-build
10 12
11IMGUI_DIR = imgui 13IMGUI_DIR = imgui
12 14
@@ -40,7 +42,7 @@ FUZZ_LDFLAGS = -fsanitize=fuzzer,address,undefined
40 42
41LIBS = -L$(SECP_LIB) -lsecp256k1 \ 43LIBS = -L$(SECP_LIB) -lsecp256k1 \
42 -L$(RANDOMX_DIR)/build -lrandomx \ 44 -L$(RANDOMX_DIR)/build -lrandomx \
43 -lsodium -lssl -lcrypto -ldb_cxx -lpthread \ 45 -lsodium -lssl -lcrypto -ldb_cxx -lsqlite3 -lpthread \
44 -lglfw -lGL 46 -lglfw -lGL
45 47
46# Every header a .cpp here includes has to be listed, not just the interesting 48# Every header a .cpp here includes has to be listed, not just the interesting
@@ -53,7 +55,7 @@ LIBS = -L$(SECP_LIB) -lsecp256k1 \
53HEADERS = headers_core.h headers.h util.h main.h serialize.h uint256.h \ 55HEADERS = headers_core.h headers.h util.h main.h serialize.h uint256.h \
54 key.h bignum.h script.h db.h base58.h net.h nostr.h \ 56 key.h bignum.h script.h db.h base58.h net.h nostr.h \
55 randomx_pow.h btfaddr.h btfchan.h btfrv.h btftunnel.h \ 57 randomx_pow.h btfaddr.h btfchan.h btfrv.h btftunnel.h \
56 selftest.h walletcmd.h compat.h sockcount.h proxy.h tor.h font_roboto.h 58 selftest.h walletcmd.h wallet_sqlite.h compat.h sockcount.h proxy.h tor.h font_roboto.h
57 59
58IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \ 60IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \
59 obj/imgui_widgets.o obj/imgui_impl_glfw.o obj/imgui_impl_opengl3.o 61 obj/imgui_widgets.o obj/imgui_impl_glfw.o obj/imgui_impl_opengl3.o
@@ -61,7 +63,8 @@ IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \
61NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \ 63NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \
62 obj/sha.o obj/nostr.o obj/randomx_pow.o \ 64 obj/sha.o obj/nostr.o obj/randomx_pow.o \
63 obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \ 65 obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \
64 obj/selftest.o obj/bip32.o obj/walletcmd.o obj/proxy.o obj/tor.o 66 obj/selftest.o obj/bip32.o obj/walletcmd.o obj/wallet_sqlite.o \
67 obj/proxy.o obj/tor.o
65 68
66GUI_OBJS = obj/gui.o obj/main_gui.o 69GUI_OBJS = obj/gui.o obj/main_gui.o
67 70
@@ -130,7 +133,7 @@ LDHARDFLAGS = -Wl,-z,noexecstack
130 133
131NODE_LIBS = -L$(SECP_LIB) -lsecp256k1 \ 134NODE_LIBS = -L$(SECP_LIB) -lsecp256k1 \
132 -L$(RANDOMX_DIR)/build -lrandomx \ 135 -L$(RANDOMX_DIR)/build -lrandomx \
133 -lsodium -lssl -lcrypto -ldb_cxx -lpthread 136 -lsodium -lssl -lcrypto -ldb_cxx -lsqlite3 -lpthread
134 137
135NODE_ONLY_OBJS = $(patsubst obj/%,obj-node/%,$(NODE_OBJS)) \ 138NODE_ONLY_OBJS = $(patsubst obj/%,obj-node/%,$(NODE_OBJS)) \
136 obj-node/main_gui.o obj-node/headless.o 139 obj-node/main_gui.o obj-node/headless.o
@@ -201,6 +204,8 @@ tests: test_bip32 bitflash-node
201 ./bitflash-node -selftest=wallet-keypool 204 ./bitflash-node -selftest=wallet-keypool
202 ./bitflash-node -selftest=wallet-hd 205 ./bitflash-node -selftest=wallet-hd
203 ./bitflash-node -selftest=wallet-format 206 ./bitflash-node -selftest=wallet-format
207 ./bitflash-node -selftest=wallet-sqlite
208 ./bitflash-node -selftest=wallet-sqlite-migration
204 ./bitflash-node -selftest=wallet-crypto 209 ./bitflash-node -selftest=wallet-crypto
205 ./bitflash-node -selftest=wallet-encrypt 210 ./bitflash-node -selftest=wallet-encrypt
206 ./bitflash-node -selftest=net-message 211 ./bitflash-node -selftest=net-message
diff --git a/src/btfrv.cpp b/src/btfrv.cpp
index 133eb89..7d705ef 100644
--- a/src/btfrv.cpp
+++ b/src/btfrv.cpp
@@ -40,6 +40,8 @@ typedef int SOCKET;
40#include <thread> 40#include <thread>
41#include <mutex> 41#include <mutex>
42#include <atomic> 42#include <atomic>
43#include <chrono>
44#include <system_error>
43 45
44namespace btf 46namespace btf
45{ 47{
@@ -209,8 +211,28 @@ static void HandleIncoming(SOCKET s)
209 bool a = WriteN(svc, &ok, 1); 211 bool a = WriteN(svc, &ok, 1);
210 bool b = WriteN(s, &ok, 1); 212 bool b = WriteN(s, &ok, 1);
211 if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; } 213 if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; }
212 std::thread(Forward, s, svc).detach(); 214 // One thread pumps s -> svc; this thread pumps svc -> s. Spawning a
213 std::thread(Forward, svc, s).detach(); 215 // thread per direction left no clean way to fail the second spawn:
216 // std::thread throws std::system_error when pthread_create is refused
217 // (NPROC limit, container pids cgroup, memory pressure), the first
218 // pump was already detached, and closing its sockets from here would
219 // race it -- so nothing caught the throw and it reached
220 // std::terminate, killing the relay over one pair it could not serve.
221 // Running one direction on this thread leaves exactly one spawn to
222 // guard, and a failure here has nothing running yet to race.
223 try
224 {
225 std::thread(Forward, s, svc).detach();
226 }
227 catch (const std::system_error& e)
228 {
229 fprintf(stderr, "rendezvous relay: cannot spawn forward thread (%s), pair dropped\n",
230 e.what());
231 CLOSESOCK(svc);
232 CLOSESOCK(s);
233 return;
234 }
235 Forward(svc, s);
214 } 236 }
215 else 237 else
216 { 238 {
@@ -236,6 +258,7 @@ bool RvRelayRun(unsigned short port)
236 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; } 258 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; }
237 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; } 259 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; }
238 260
261 int nSpawnFails = 0;
239 while (!g_stop) 262 while (!g_stop)
240 { 263 {
241 struct sockaddr_in cli; 264 struct sockaddr_in cli;
@@ -246,7 +269,27 @@ bool RvRelayRun(unsigned short port)
246 if (g_stop) break; 269 if (g_stop) break;
247 continue; 270 continue;
248 } 271 }
249 std::thread(HandleIncoming, s).detach(); 272 try
273 {
274 std::thread(HandleIncoming, s).detach();
275 nSpawnFails = 0;
276 }
277 catch (const std::system_error& e)
278 {
279 // A refused pthread_create is backpressure, not a reason to die:
280 // the condition behind it (NPROC, pids cgroup, memory) does not
281 // clear in milliseconds, and the connection count feeding it is
282 // attacker-influenced. Drop this one peer, stop accepting for a
283 // moment -- longer each time, capped -- and keep serving the
284 // connections already established.
285 fprintf(stderr, "rendezvous relay: cannot spawn connection thread (%s), connection dropped\n",
286 e.what());
287 CLOSESOCK(s);
288 nSpawnFails++;
289 int nWaitMs = nSpawnFails < 5 ? nSpawnFails * 1000 : 5000;
290 for (int i = 0; i < nWaitMs && !g_stop; i += 100)
291 std::this_thread::sleep_for(std::chrono::milliseconds(100));
292 }
250 } 293 }
251 CLOSESOCK(g_listen); 294 CLOSESOCK(g_listen);
252 g_listen = INVALID_SOCKET; 295 g_listen = INVALID_SOCKET;
@@ -334,17 +377,29 @@ bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs)
334} 377}
335 378
336RvSocket RvClientConnect(const char* relay_host, unsigned short port, 379RvSocket RvClientConnect(const char* relay_host, unsigned short port,
337 const unsigned char target_pubkey[32]) 380 const unsigned char target_pubkey[32],
381 RvClientFail* pFail)
338{ 382{
383 if (pFail) *pFail = RVC_FAIL_DIAL;
339 if (!RvInit()) return RV_INVALID; 384 if (!RvInit()) return RV_INVALID;
340 SOCKET s = ConnectTo(relay_host, port); 385 SOCKET s = ConnectTo(relay_host, port);
341 if (s == INVALID_SOCKET) return RV_INVALID; 386 if (s == INVALID_SOCKET) return RV_INVALID;
387 if (pFail) *pFail = RVC_FAIL_PAIR;
342 unsigned char hdr[33]; 388 unsigned char hdr[33];
343 hdr[0] = (unsigned char)'C'; 389 hdr[0] = (unsigned char)'C';
344 memcpy(hdr + 1, target_pubkey, 32); 390 memcpy(hdr + 1, target_pubkey, 32);
345 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; } 391 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
346 unsigned char paired = 0; 392 unsigned char paired = 0;
347 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; } 393 if (!ReadN(s, &paired, 1)) { CLOSESOCK(s); return RV_INVALID; }
394 if (paired != 0x01)
395 {
396 // The relay answered and declined: HandleIncoming writes 0x00 and
397 // closes when no service is parked under this pubkey right now.
398 if (pFail) *pFail = RVC_FAIL_REFUSED;
399 CLOSESOCK(s);
400 return RV_INVALID;
401 }
402 if (pFail) *pFail = RVC_FAIL_NONE;
348 return (RvSocket)s; 403 return (RvSocket)s;
349} 404}
350 405
diff --git a/src/btfrv.h b/src/btfrv.h
index 9cd7476..1e85cb0 100644
--- a/src/btfrv.h
+++ b/src/btfrv.h
@@ -57,10 +57,25 @@ RvSocket RvServiceRegister(const char* relay_host, unsigned short port,
57bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs = 0); 57bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs = 0);
58 58
59// ---- client side (the node dialing a .btf address) ---- 59// ---- client side (the node dialing a .btf address) ----
60// Where a failed RvClientConnect stopped. RV_INVALID alone collapses three
61// different situations -- relay unreachable, relay reachable but nobody
62// registered, connection died mid-pairing -- and they point at different
63// components. The caller can only report the right one if this side says
64// which it saw.
65enum RvClientFail
66{
67 RVC_FAIL_NONE = 0,
68 RVC_FAIL_DIAL, // TCP connect to the relay itself failed
69 RVC_FAIL_REFUSED, // relay answered 0x00: no service registered under this pubkey
70 RVC_FAIL_PAIR, // relay connection died before pairing completed
71};
72
60// Connect through the relay to the service registered under `target_pubkey`. 73// Connect through the relay to the service registered under `target_pubkey`.
61// Returns the paired socket, or RV_INVALID on error. 74// Returns the paired socket, or RV_INVALID on error -- with `pFail`, if
75// given, naming the stage that failed.
62RvSocket RvClientConnect(const char* relay_host, unsigned short port, 76RvSocket RvClientConnect(const char* relay_host, unsigned short port,
63 const unsigned char target_pubkey[32]); 77 const unsigned char target_pubkey[32],
78 RvClientFail* pFail = 0);
64 79
65// Blocking read/write of exactly n bytes over a rendezvous socket. Return false 80// Blocking read/write of exactly n bytes over a rendezvous socket. Return false
66// on error/EOF. 81// on error/EOF.
diff --git a/src/btftunnel.cpp b/src/btftunnel.cpp
index 63d178b..4058f6b 100644
--- a/src/btftunnel.cpp
+++ b/src/btftunnel.cpp
@@ -25,8 +25,12 @@ typedef int SOCKET;
25#endif 25#endif
26#include "sockcount.h" 26#include "sockcount.h"
27#include <cstring> 27#include <cstring>
28#include <cstdio>
29#include <memory>
30#include <new>
28#include <vector> 31#include <vector>
29#include <thread> 32#include <thread>
33#include <system_error>
30 34
31namespace btf 35namespace btf
32{ 36{
@@ -119,35 +123,155 @@ static void PumpIn(SOCKET pumpEnd, RvSocket rv, std::vector<unsigned char> key)
119 shutdown((SOCKET)rv, SD_BOTH); 123 shutdown((SOCKET)rv, SD_BOTH);
120} 124}
121 125
126// The pump end and the rendezvous socket, owned jointly by whoever is still
127// using them. Closed once, by the last reference to go.
128//
129// Joint ownership rather than "the inbound pump owns the cleanup", because
130// either pump can reach its exit first and BOTH touch BOTH descriptors on the
131// way out -- PumpOut ends in shutdown(rv), shutdown(pumpEnd) and PumpIn ends in
132// the mirror image. A single owner therefore has no exit at which the other is
133// guaranteed to be finished. Holding a reference for the whole body of each
134// pump gives it one: the close cannot happen until the last pump has stopped
135// touching the descriptors, so no shutdown() can land on a number that has
136// already been closed and handed back out by the kernel to an unrelated
137// connection.
138struct TunnelEnds
139{
140 SOCKET pumpEnd;
141 RvSocket rv;
142 TunnelEnds(SOCKET p, RvSocket r) : pumpEnd(p), rv(r) {}
143 ~TunnelEnds() { BtfCloseSocket(pumpEnd); RvClose(rv); }
144private:
145 TunnelEnds(const TunnelEnds&);
146 TunnelEnds& operator=(const TunnelEnds&);
147};
148
122// Start the two pumps and hand back the app-facing socket. When both pumps 149// Start the two pumps and hand back the app-facing socket. When both pumps
123// finish, close the pump end and the rendezvous socket. 150// finish, close the pump end and the rendezvous socket.
151//
152// That sentence used to be a comment describing something the code did not do.
153// The inbound pump closed both descriptors as soon as it exited, without
154// waiting for the outbound pump, which then called shutdown() on both at its
155// own exit. Whether that shutdown hit a live descriptor or a closed one came
156// down to which pump got there first, and the rendezvous socket dying before
157// the app socket -- a relay restart, the far side hanging up -- is precisely
158// the order that puts the inbound pump ahead. Measured over 25 such teardowns
159// against the real code: 29 shutdowns landed on an already-closed descriptor.
160//
161// appEnd is not part of this. It is handed to the caller on success and closed
162// here on every failure path; no pump ever touches it.
163//
164// Every tunnel is two threads, and a tunnel is one peer connection (a .btf
165// dial or accept, a pool miner, a participant's pool session) -- so the
166// thread count scales with peer count, which is attacker-influenced. When
167// pthread_create is refused std::thread throws std::system_error; uncaught it
168// reached std::terminate and took the whole process down over one connection
169// it could not serve. Both spawns are guarded, and the failure paths return
170// INVALID_SOCKET, which every caller already handles by dropping just that
171// connection.
124static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) 172static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32])
125{ 173{
126 btf_socket_t appEnd, pumpEnd; 174 btf_socket_t appEnd, pumpEnd;
127 if (!MakeLocalPair(appEnd, pumpEnd)) { RvClose(rv); return INVALID_SOCKET; } 175 if (!MakeLocalPair(appEnd, pumpEnd)) { RvClose(rv); return INVALID_SOCKET; }
128 176
129 std::vector<unsigned char> key(shared, shared + 32); 177 std::vector<unsigned char> key(shared, shared + 32);
130 std::thread(PumpOut, pumpEnd, rv, key).detach(); 178
131 std::thread([pumpEnd, rv, key]() { 179 // make_shared, not shared_ptr(new ...): it allocates before it constructs,
132 PumpIn(pumpEnd, rv, key); 180 // so it either comes back owning both descriptors or throws having taken
181 // ownership of neither. The two-step form can run the deleter and then
182 // throw, which would close both here and again below.
183 std::shared_ptr<TunnelEnds> ends;
184 try
185 {
186 ends = std::make_shared<TunnelEnds>(pumpEnd, rv);
187 }
188 catch (const std::bad_alloc&)
189 {
190 fprintf(stderr, "btf tunnel: out of memory starting tunnel, tunnel dropped\n");
133 BtfCloseSocket(pumpEnd); 191 BtfCloseSocket(pumpEnd);
192 BtfCloseSocket(appEnd);
134 RvClose(rv); 193 RvClose(rv);
135 }).detach(); 194 return INVALID_SOCKET;
195 }
196
197 try
198 {
199 std::thread([ends, key]() { PumpIn(ends->pumpEnd, ends->rv, key); }).detach();
200 }
201 catch (const std::system_error& e)
202 {
203 // Nothing is running yet: `ends` is the only reference, and returning
204 // drops it, which is what closes pumpEnd and rv.
205 fprintf(stderr, "btf tunnel: cannot spawn inbound pump (%s), tunnel dropped\n",
206 e.what());
207 BtfCloseSocket(appEnd);
208 return INVALID_SOCKET;
209 }
210
211 try
212 {
213 std::thread([ends, key]() { PumpOut(ends->pumpEnd, ends->rv, key); }).detach();
214 }
215 catch (const std::system_error& e)
216 {
217 // The inbound pump may be parked in RvReadN(rv) or may already have
218 // exited -- when the spawn is refused because the process is loaded,
219 // the rendezvous socket is often dead for the same reason, and then
220 // PumpIn returns on its first length read. Either way this thread
221 // still holds a reference, so both descriptors are still open: the
222 // shutdown wakes the pump if it is parked and is harmless if it is
223 // not. The close belongs to whichever reference goes last.
224 fprintf(stderr, "btf tunnel: cannot spawn outbound pump (%s), tunnel dropped\n",
225 e.what());
226 shutdown((SOCKET)ends->rv, SD_BOTH);
227 shutdown(ends->pumpEnd, SD_BOTH);
228 BtfCloseSocket(appEnd);
229 return INVALID_SOCKET;
230 }
136 return appEnd; 231 return appEnd;
137} 232}
138 233
234const char* TunnelFailText(TunnelFail f)
235{
236 switch (f)
237 {
238 case TUNNEL_FAIL_NONE: return "no failure";
239 case TUNNEL_FAIL_INIT: return "local socket/crypto init failed";
240 case TUNNEL_FAIL_DIAL: return "meeting node unreachable (tcp dial failed)";
241 case TUNNEL_FAIL_REFUSED: return "relay reachable, but no service registered there for this address";
242 case TUNNEL_FAIL_PAIR: return "relay connection died before pairing completed";
243 case TUNNEL_FAIL_HANDSHAKE: return "end-to-end handshake failed";
244 case TUNNEL_FAIL_LOCAL: return "local tunnel setup failed";
245 }
246 return "unknown failure";
247}
248
139btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, 249btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port,
140 const unsigned char target_pubkey[32], 250 const unsigned char target_pubkey[32],
141 const unsigned char service_enc_pub[32]) 251 const unsigned char service_enc_pub[32],
252 TunnelFail* pFail)
142{ 253{
254 if (pFail) *pFail = TUNNEL_FAIL_INIT;
143 if (!TunnelInit()) return INVALID_SOCKET; 255 if (!TunnelInit()) return INVALID_SOCKET;
144 RvSocket rv = RvClientConnect(meeting_host, port, target_pubkey); 256 RvClientFail rvFail = RVC_FAIL_NONE;
145 if (rv == RV_INVALID) return INVALID_SOCKET; 257 RvSocket rv = RvClientConnect(meeting_host, port, target_pubkey, &rvFail);
258 if (rv == RV_INVALID)
259 {
260 if (pFail)
261 *pFail = rvFail == RVC_FAIL_DIAL ? TUNNEL_FAIL_DIAL
262 : rvFail == RVC_FAIL_REFUSED ? TUNNEL_FAIL_REFUSED
263 : TUNNEL_FAIL_PAIR;
264 return INVALID_SOCKET;
265 }
146 266
147 unsigned char eph_pub[32], shared[32]; 267 unsigned char eph_pub[32], shared[32];
268 if (pFail) *pFail = TUNNEL_FAIL_HANDSHAKE;
148 if (!ChanClientHandshake(service_enc_pub, eph_pub, shared)) { RvClose(rv); return INVALID_SOCKET; } 269 if (!ChanClientHandshake(service_enc_pub, eph_pub, shared)) { RvClose(rv); return INVALID_SOCKET; }
149 if (!RvWriteN(rv, eph_pub, 32)) { RvClose(rv); return INVALID_SOCKET; } 270 if (!RvWriteN(rv, eph_pub, 32)) { RvClose(rv); return INVALID_SOCKET; }
150 return StartTunnel(rv, shared); 271 if (pFail) *pFail = TUNNEL_FAIL_LOCAL;
272 btf_socket_t app = StartTunnel(rv, shared);
273 if (app != INVALID_SOCKET && pFail) *pFail = TUNNEL_FAIL_NONE;
274 return app;
151} 275}
152 276
153btf_socket_t BtfServiceWrap(RvSocket rv, const unsigned char my_enc_sk[32]) 277btf_socket_t BtfServiceWrap(RvSocket rv, const unsigned char my_enc_sk[32])
diff --git a/src/btftunnel.h b/src/btftunnel.h
index 24a7509..3cc87cd 100644
--- a/src/btftunnel.h
+++ b/src/btftunnel.h
@@ -36,12 +36,33 @@ namespace btf
36// Initialize (sockets + libsodium). Idempotent. 36// Initialize (sockets + libsodium). Idempotent.
37bool TunnelInit(); 37bool TunnelInit();
38 38
39// Which stage of BtfClientTunnel failed. INVALID_SOCKET alone says only "no
40// tunnel"; a caller that then guesses at causes in its log line sends whoever
41// reads it to the wrong component (issue #17: "relay unreachable or pool not
42// listening" blamed two healthy parts). The stage is what this side actually
43// knows -- report that.
44enum TunnelFail
45{
46 TUNNEL_FAIL_NONE = 0,
47 TUNNEL_FAIL_INIT, // local socket/crypto init failed
48 TUNNEL_FAIL_DIAL, // TCP connect to the meeting node failed
49 TUNNEL_FAIL_REFUSED, // relay reachable, but no service registered under the pubkey
50 TUNNEL_FAIL_PAIR, // relay connection died before pairing completed
51 TUNNEL_FAIL_HANDSHAKE, // end-to-end channel handshake failed
52 TUNNEL_FAIL_LOCAL, // local pump plumbing (socket pair / threads) failed
53};
54
55// Short description of a TunnelFail for log lines.
56const char* TunnelFailText(TunnelFail f);
57
39// Client: dial the service registered under `target_pubkey` at the meeting node 58// Client: dial the service registered under `target_pubkey` at the meeting node
40// (host:port), box to its static x25519 `service_enc_pub`, and return a local 59// (host:port), box to its static x25519 `service_enc_pub`, and return a local
41// plaintext SOCKET tunneled end-to-end to the service. INVALID_SOCKET on error. 60// plaintext SOCKET tunneled end-to-end to the service. INVALID_SOCKET on error
61// -- with `pFail`, if given, naming the stage that failed.
42btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, 62btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port,
43 const unsigned char target_pubkey[32], 63 const unsigned char target_pubkey[32],
44 const unsigned char service_enc_pub[32]); 64 const unsigned char service_enc_pub[32],
65 TunnelFail* pFail = 0);
45 66
46// Service: wrap an already-paired rendezvous socket (RvServiceRegister followed 67// Service: wrap an already-paired rendezvous socket (RvServiceRegister followed
47// by RvServiceWaitPaired -- the pairing byte MUST already be consumed) with 68// by RvServiceWaitPaired -- the pairing byte MUST already be consumed) with
diff --git a/src/db.cpp b/src/db.cpp
index 405a3db..bf1958d 100644
--- a/src/db.cpp
+++ b/src/db.cpp
@@ -3,6 +3,7 @@
3// file license.txt or http://www.opensource.org/licenses/mit-license.php. 3// file license.txt or http://www.opensource.org/licenses/mit-license.php.
4 4
5#include "headers.h" 5#include "headers.h"
6#include "wallet_sqlite.h"
6 7
7 8
8 9
@@ -52,8 +53,34 @@ static bool fDbEnvInit = false;
52// Why LoadWallet() refused, in words meant for the person who has to act on it. 53// Why LoadWallet() refused, in words meant for the person who has to act on it.
53// Empty unless the reason is one we can name. 54// Empty unless the reason is one we can name.
54string strWalletLoadError; 55string strWalletLoadError;
55DbEnv dbenv(0u); 56static DbEnv* pdbenv = NULL;
57static FILE* pdbErrFile = NULL;
56static map<string, int> mapFileUseCount; 58static map<string, int> mapFileUseCount;
59static CWalletDBSQLite* pWalletSQLiteRuntime = NULL;
60
61DbEnv& GetDbEnv()
62{
63 if (!pdbenv)
64 pdbenv = new DbEnv(0u);
65 return *pdbenv;
66}
67
68static void CloseDbEnv()
69{
70 if (pdbenv)
71 {
72 try { pdbenv->close(0); }
73 catch (...) { }
74 delete pdbenv;
75 pdbenv = NULL;
76 }
77 if (pdbErrFile)
78 {
79 fclose(pdbErrFile);
80 pdbErrFile = NULL;
81 }
82 fDbEnvInit = false;
83}
57 84
58class CDBInit 85class CDBInit
59{ 86{
@@ -64,15 +91,189 @@ public:
64 ~CDBInit() 91 ~CDBInit()
65 { 92 {
66 if (fDbEnvInit) 93 if (fDbEnvInit)
67 { 94 CloseDbEnv();
68 try { dbenv.close(0); }
69 catch (...) { }
70 fDbEnvInit = false;
71 }
72 } 95 }
73} 96}
74instance_of_cdbinit; 97instance_of_cdbinit;
75 98
99bool WalletSQLiteRuntimeActive()
100{
101 return pWalletSQLiteRuntime != NULL;
102}
103
104bool WalletSQLiteRuntimeOpen(const string& strPath, string& strErrorRet, bool fMustExist)
105{
106 strErrorRet.clear();
107 // Callers that load an existing wallet want the clear "does not exist"
108 // message rather than silently creating a blank one; the create path passes
109 // fMustExist=false so Open() (SQLITE_OPEN_CREATE) can make a fresh file.
110 if (fMustExist && !FileExists(strPath.c_str()))
111 {
112 strErrorRet = strprintf("SQLite wallet does not exist: %s",
113 strPath.c_str());
114 return false;
115 }
116
117 WalletSQLiteRuntimeClose();
118 std::auto_ptr<CWalletDBSQLite> db(new CWalletDBSQLite());
119 if (!db->Open(strPath, strErrorRet))
120 return false;
121
122 pWalletSQLiteRuntime = db.release();
123 return true;
124}
125
126void WalletSQLiteRuntimeClose()
127{
128 if (pWalletSQLiteRuntime)
129 {
130 // Fold the write-ahead log into the main file on the way out. WAL +
131 // synchronous=FULL already makes every committed write durable across a
132 // kill, so this is housekeeping, not the thing that protects the data:
133 // if it fails, or the node is killed before it runs, the wallet is
134 // still whole. Best effort, never throws.
135 string strCheckpointError;
136 pWalletSQLiteRuntime->Checkpoint(strCheckpointError);
137 delete pWalletSQLiteRuntime;
138 pWalletSQLiteRuntime = NULL;
139 }
140}
141
142bool WalletSQLiteRuntimeReadRecord(const vector<unsigned char>& vchKey,
143 vector<unsigned char>& vchValueRet)
144{
145 if (!pWalletSQLiteRuntime)
146 return false;
147 string strError;
148 return pWalletSQLiteRuntime->ReadRecord(vchKey, vchValueRet, strError);
149}
150
151bool WalletSQLiteRuntimeWriteRecord(const vector<unsigned char>& vchKey,
152 const vector<unsigned char>& vchValue,
153 bool fOverwrite)
154{
155 if (!pWalletSQLiteRuntime)
156 return false;
157 string strError;
158 return pWalletSQLiteRuntime->WriteRecord(vchKey, vchValue, strError,
159 fOverwrite);
160}
161
162bool WalletSQLiteRuntimeEraseRecord(const vector<unsigned char>& vchKey)
163{
164 if (!pWalletSQLiteRuntime)
165 return false;
166 string strError;
167 return pWalletSQLiteRuntime->EraseRecord(vchKey, strError);
168}
169
170// Transaction bracket for the SQLite runtime. When the SQLite backend is not
171// active these are no-ops that succeed, so a caller can wrap a group of related
172// writes and Berkeley DB keeps its existing per-write behaviour unchanged while
173// SQLite gets all-or-nothing.
174bool WalletSQLiteRuntimeBeginTxn()
175{
176 if (!pWalletSQLiteRuntime)
177 return true;
178 string strError;
179 if (pWalletSQLiteRuntime->BeginTransaction(strError))
180 return true;
181 printf("WalletSQLiteRuntimeBeginTxn() : %s\n", strError.c_str());
182 return false;
183}
184
185bool WalletSQLiteRuntimeCommitTxn()
186{
187 if (!pWalletSQLiteRuntime)
188 return true;
189 string strError;
190 if (pWalletSQLiteRuntime->CommitTransaction(strError))
191 return true;
192 printf("WalletSQLiteRuntimeCommitTxn() : %s\n", strError.c_str());
193 return false;
194}
195
196void WalletSQLiteRuntimeRollbackTxn()
197{
198 if (!pWalletSQLiteRuntime)
199 return;
200 string strError;
201 pWalletSQLiteRuntime->RollbackTransaction(strError);
202}
203
204// Open the SQLite wallet read-only as the active runtime. Used by the one-shot
205// storage diagnostics, which run before the normal wallet load and must be able
206// to inspect a wallet that the loader would refuse -- so this opens the file for
207// scanning without loading or validating its records.
208bool WalletSQLiteRuntimeOpenReadOnly(const string& strPath, string& strErrorRet)
209{
210 strErrorRet.clear();
211 if (!FileExists(strPath.c_str()))
212 {
213 strErrorRet = strprintf("SQLite wallet does not exist: %s", strPath.c_str());
214 return false;
215 }
216
217 WalletSQLiteRuntimeClose();
218 std::auto_ptr<CWalletDBSQLite> db(new CWalletDBSQLite());
219 if (!db->OpenReadOnly(strPath, strErrorRet))
220 return false;
221
222 pWalletSQLiteRuntime = db.release();
223 return true;
224}
225
226bool WalletSQLiteRuntimeCheckpoint(string& strErrorRet)
227{
228 if (!pWalletSQLiteRuntime)
229 {
230 strErrorRet = "SQLite wallet backend is not active";
231 return false;
232 }
233 return pWalletSQLiteRuntime->Checkpoint(strErrorRet);
234}
235
236// The persistent record of which backend this datadir uses, so the choice
237// survives a restart without a command-line flag. A plain text file holding
238// "sqlite" or "bdb" -- visible and hand-editable on purpose, no hidden state.
239static string WalletBackendMarkerPath()
240{
241 return GetAppDir() + "/wallet-backend";
242}
243
244string ReadWalletBackendMarker()
245{
246 FILE* pf = fopen(WalletBackendMarkerPath().c_str(), "rb");
247 if (!pf)
248 return "";
249 char buf[16] = {0};
250 size_t n = fread(buf, 1, sizeof(buf) - 1, pf);
251 fclose(pf);
252 string s(buf, n);
253 while (!s.empty() && isspace((unsigned char)s[s.size() - 1]))
254 s.erase(s.size() - 1);
255 while (!s.empty() && isspace((unsigned char)s[0]))
256 s.erase(0, 1);
257 if (s == "sqlite" || s == "bdb")
258 return s;
259 return "";
260}
261
262bool WriteWalletBackendMarker(const string& strBackend)
263{
264 if (strBackend != "sqlite" && strBackend != "bdb")
265 return false;
266 FILE* pf = fopen(WalletBackendMarkerPath().c_str(), "wb");
267 if (!pf)
268 return false;
269 bool fOk = fwrite(strBackend.c_str(), 1, strBackend.size(), pf) == strBackend.size();
270 if (fputc('\n', pf) == EOF)
271 fOk = false;
272 if (fclose(pf) != 0)
273 fOk = false;
274 return fOk;
275}
276
76 277
77CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) 278CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL)
78{ 279{
@@ -104,24 +305,30 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL)
104 305
105 printf("dbenv.open strAppDir=%s\n", strAppDir.c_str()); 306 printf("dbenv.open strAppDir=%s\n", strAppDir.c_str());
106 307
107 dbenv.set_lg_dir(strLogDir.c_str()); 308 DbEnv& env = GetDbEnv();
108 dbenv.set_lg_max(10000000); 309 env.set_lg_dir(strLogDir.c_str());
109 dbenv.set_lk_max_locks(10000); 310 env.set_lg_max(10000000);
110 dbenv.set_lk_max_objects(10000); 311 env.set_lk_max_locks(10000);
111 dbenv.set_errfile(fopen("db.log", "a")); /// debug 312 env.set_lk_max_objects(10000);
112 ///dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1); /// causes corruption 313 pdbErrFile = fopen("db.log", "a"); /// debug
113 ret = dbenv.open(strAppDir.c_str(), 314 if (pdbErrFile)
114 DB_CREATE | 315 env.set_errfile(pdbErrFile);
115 DB_INIT_LOCK | 316 ///env.log_set_config(DB_LOG_AUTO_REMOVE, 1); /// causes corruption
116 DB_INIT_LOG | 317 ret = env.open(strAppDir.c_str(),
117 DB_INIT_MPOOL | 318 DB_CREATE |
118 DB_INIT_TXN | 319 DB_INIT_LOCK |
119 DB_THREAD | 320 DB_INIT_LOG |
120 DB_PRIVATE | 321 DB_INIT_MPOOL |
121 DB_RECOVER, 322 DB_INIT_TXN |
122 0); 323 DB_THREAD |
324 DB_PRIVATE |
325 DB_RECOVER,
326 0);
123 if (ret > 0) 327 if (ret > 0)
328 {
329 CloseDbEnv();
124 throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret)); 330 throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret));
331 }
125 fDbEnvInit = true; 332 fDbEnvInit = true;
126 333
127 // A Berkeley DB file records, in every page header, a log sequence 334 // A Berkeley DB file records, in every page header, a log sequence
@@ -151,7 +358,7 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL)
151 continue; 358 continue;
152 try 359 try
153 { 360 {
154 if (dbenv.lsn_reset(pszName, 0) == 0) 361 if (env.lsn_reset(pszName, 0) == 0)
155 printf("CDB() : adopted %s from another environment " 362 printf("CDB() : adopted %s from another environment "
156 "(log sequence numbers reset)\n", pszName); 363 "(log sequence numbers reset)\n", pszName);
157 } 364 }
@@ -164,7 +371,7 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL)
164 ++mapFileUseCount[strFile]; 371 ++mapFileUseCount[strFile];
165 } 372 }
166 373
167 pdb = new Db(&dbenv, 0); 374 pdb = new Db(&GetDbEnv(), 0);
168 375
169 ret = pdb->open(NULL, // Txn pointer 376 ret = pdb->open(NULL, // Txn pointer
170 pszFile, // Filename 377 pszFile, // Filename
@@ -199,7 +406,8 @@ void CDB::Close()
199 try { pdb->close(0); } catch (...) { } 406 try { pdb->close(0); } catch (...) { }
200 delete pdb; 407 delete pdb;
201 pdb = NULL; 408 pdb = NULL;
202 try { dbenv.txn_checkpoint(0, 0, 0); } catch (...) { } 409 if (fDbEnvInit)
410 try { GetDbEnv().txn_checkpoint(0, 0, 0); } catch (...) { }
203 411
204 CRITICAL_BLOCK(cs_db) 412 CRITICAL_BLOCK(cs_db)
205 --mapFileUseCount[strFile]; 413 --mapFileUseCount[strFile];
@@ -279,7 +487,8 @@ void DBFlush(bool fShutdown)
279 // takes the process down before the wallet has been put down cleanly, 487 // takes the process down before the wallet has been put down cleanly,
280 // which is the difference between a portable wallet.dat and one welded 488 // which is the difference between a portable wallet.dat and one welded
281 // to this directory -- the lsn_reset just below is what frees it. 489 // to this directory -- the lsn_reset just below is what frees it.
282 try { dbenv.txn_checkpoint(0, 0, 0); } 490 DbEnv& env = GetDbEnv();
491 try { env.txn_checkpoint(0, 0, 0); }
283 catch (const std::exception& e) 492 catch (const std::exception& e)
284 { printf("DBFlush() : checkpoint failed: %s\n", e.what()); } 493 { printf("DBFlush() : checkpoint failed: %s\n", e.what()); }
285 494
@@ -290,7 +499,7 @@ void DBFlush(bool fShutdown)
290 int nRefCount = (*mi).second; 499 int nRefCount = (*mi).second;
291 if (nRefCount == 0) 500 if (nRefCount == 0)
292 { 501 {
293 try { dbenv.lsn_reset(strFile.c_str(), 0); } 502 try { env.lsn_reset(strFile.c_str(), 0); }
294 catch (const std::exception& e) 503 catch (const std::exception& e)
295 { printf("DBFlush() : lsn_reset(%s) failed: %s\n", strFile.c_str(), e.what()); } 504 { printf("DBFlush() : lsn_reset(%s) failed: %s\n", strFile.c_str(), e.what()); }
296 mapFileUseCount.erase(mi++); 505 mapFileUseCount.erase(mi++);
@@ -310,9 +519,8 @@ void DBFlush(bool fShutdown)
310 // Found while making wallet encryption clean up after itself: 519 // Found while making wallet encryption clean up after itself:
311 // calling DBFlush(true) a second time crashed here every time. 520 // calling DBFlush(true) a second time crashed here every time.
312 if (mapFileUseCount.empty()) 521 if (mapFileUseCount.empty())
313 try { dbenv.log_archive(NULL, DB_ARCH_REMOVE); } catch (...) { } 522 try { env.log_archive(NULL, DB_ARCH_REMOVE); } catch (...) { }
314 try { dbenv.close(0); } catch (...) { } 523 CloseDbEnv();
315 fDbEnvInit = false;
316 } 524 }
317 } 525 }
318} 526}
@@ -614,6 +822,294 @@ bool CTxDB::LoadBlockIndex()
614// CWalletDB 822// CWalletDB
615// 823//
616 824
825CWalletDB::CWalletDB(const char* pszMode, bool fTxn) :
826 CDB(WalletSQLiteRuntimeActive() ? NULL : "wallet.dat", pszMode, fTxn)
827{
828}
829
830class CSQLiteWalletRuntimeLoadVisitor : public CWalletRecordVisitor
831{
832public:
833 bool fHaveStoredMineMode;
834 bool fHaveStoredHDCoinType;
835 string strLastType;
836
837 CSQLiteWalletRuntimeLoadVisitor() :
838 fHaveStoredMineMode(false),
839 fHaveStoredHDCoinType(false)
840 {
841 }
842
843 bool VisitWalletRecord(const CDataStream& ssKeyIn,
844 const CDataStream& ssValueIn,
845 string& strErrorRet)
846 {
847 CDataStream ssKey = ssKeyIn;
848 CDataStream ssValue = ssValueIn;
849 string strType;
850 ssKey >> strType;
851 strLastType = strType;
852
853 if (strType == "name")
854 {
855 string strAddress;
856 ssKey >> strAddress;
857 ssValue >> mapAddressBook[strAddress];
858 }
859 else if (strType == "tx")
860 {
861 uint256 hash;
862 ssKey >> hash;
863 CWalletTx& wtx = mapWallet[hash];
864 ssValue >> wtx;
865
866 if (wtx.GetHash() != hash)
867 printf("Error in SQLite wallet export, hash mismatch\n");
868 }
869 else if (strType == "key")
870 {
871 vector<unsigned char> vchPubKey;
872 ssKey >> vchPubKey;
873 CPrivKey vchPrivKey;
874 ssValue >> vchPrivKey;
875
876 mapKeys[vchPubKey] = vchPrivKey;
877 mapPubKeys[Hash160(vchPubKey)] = vchPubKey;
878 }
879 else if (strType == "mkey")
880 {
881 unsigned int nID = 0;
882 ssKey >> nID;
883 CWalletMasterKey kMasterKey;
884 ssValue >> kMasterKey;
885 mapMasterKeys[nID] = kMasterKey;
886 nWalletMasterKeyMaxID = max(nWalletMasterKeyMaxID, nID);
887 fWalletEncrypted = true;
888 fWalletLocked = true;
889 }
890 else if (strType == "ckey")
891 {
892 vector<unsigned char> vchPubKey;
893 ssKey >> vchPubKey;
894 vector<unsigned char> vchCryptedSecret;
895 ssValue >> vchCryptedSecret;
896 if (!AddCryptedKey(vchPubKey, vchCryptedSecret))
897 {
898 strErrorRet = "SQLite wallet export has an unreadable encrypted key record";
899 return false;
900 }
901 }
902 else if (strType == "defaultkey")
903 {
904 ssValue >> vchDefaultKey;
905 }
906 else if (strType == "hdmaster")
907 {
908 ssValue >> vchHDMaster;
909 }
910 else if (strType == "hdchaincode")
911 {
912 ssValue >> vchHDChainCode;
913 }
914 else if (strType == "hdnext")
915 {
916 ssValue >> nHDNext;
917 }
918 else if (strType == "hdschema")
919 {
920 ssValue >> nHDKeySchema;
921 }
922 else if (strType == "hdcointype")
923 {
924 ssValue >> nHDCoinType;
925 fHaveStoredHDCoinType = true;
926 }
927 else if (strType == "hdreceivenext")
928 {
929 ssValue >> nHDReceiveNext;
930 }
931 else if (strType == "hdchangenext")
932 {
933 ssValue >> nHDChangeNext;
934 }
935 else if (strType == "cryptedhdmaster")
936 {
937 ssValue >> vchCryptedHDMaster;
938 fWalletEncrypted = true;
939 fWalletLocked = true;
940 }
941 else if (strType == "cryptedhdchaincode")
942 {
943 ssValue >> vchCryptedHDChainCode;
944 fWalletEncrypted = true;
945 fWalletLocked = true;
946 }
947 else if (strType == "walletminversion")
948 {
949 int nMinVersion = 0;
950 ssValue >> nMinVersion;
951 if (nMinVersion > WALLET_FORMAT_SUPPORTED)
952 {
953 strWalletLoadError = strprintf(
954 "This SQLite wallet export was written by a newer version of "
955 "Bitflash (wallet format %d; this build understands %d). "
956 "Upgrade Bitflash before opening it.",
957 nMinVersion, WALLET_FORMAT_SUPPORTED);
958 strErrorRet = strWalletLoadError;
959 return false;
960 }
961 }
962 else if (strType == "pool")
963 {
964 int64 nIndex;
965 ssKey >> nIndex;
966 vector<unsigned char> vchPubKey;
967 ssValue >> vchPubKey;
968 mapKeyPool[nIndex] = vchPubKey;
969 }
970 else if (strType == "setting")
971 {
972 string strKey;
973 ssKey >> strKey;
974 if (strKey == "nTransactionFee") ssValue >> nTransactionFee;
975 if (strKey == "addrIncoming") ssValue >> addrIncoming;
976
977 if (!fMineModeFromCommandLine)
978 {
979 if (strKey == "nMineMode") { ssValue >> nMineMode; fHaveStoredMineMode = true; }
980 if (strKey == "fGenerateBitcoins") ssValue >> fGenerateBitcoins;
981 if (strKey == "strParticipantPool") ssValue >> strParticipantPool;
982 }
983 }
984
985 if (ssKey.fail())
986 {
987 strErrorRet = strprintf("malformed SQLite wallet record key while handling '%s'",
988 strLastType.c_str());
989 return false;
990 }
991 if (ssValue.fail())
992 {
993 strErrorRet = strprintf("malformed SQLite wallet record value while handling '%s'",
994 strLastType.c_str());
995 return false;
996 }
997 return true;
998 }
999
1000 vector<unsigned char> vchDefaultKey;
1001};
1002
1003static bool FinishLoadedWallet(const vector<unsigned char>& vchDefaultKey,
1004 bool fMayCreateDefaultKey)
1005{
1006 if (!vchDefaultKey.empty() && mapPubKeys.count(Hash160(vchDefaultKey)))
1007 {
1008 keyUser.SetPubKey(vchDefaultKey);
1009 if (mapKeys.count(vchDefaultKey))
1010 keyUser.SetPrivKey(mapKeys[vchDefaultKey]);
1011 }
1012 else if (IsWalletEncrypted())
1013 {
1014 printf("LoadWallet: encrypted wallet has no usable default public key. "
1015 "The file was left untouched.\n");
1016 return false;
1017 }
1018 else
1019 {
1020 if (!fMayCreateDefaultKey)
1021 {
1022 strWalletLoadError =
1023 "SQLite wallet export has no usable default public key";
1024 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1025 return false;
1026 }
1027
1028 keyUser.MakeNewKey();
1029 if (!AddKey(keyUser))
1030 return false;
1031 if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "Your Address"))
1032 return false;
1033 CWalletDB().WriteDefaultKey(keyUser.GetPubKey());
1034 }
1035
1036 return true;
1037}
1038
1039static bool ReconcileLoadedWalletMetadata(bool fHaveStoredMineMode,
1040 bool fHaveStoredHDCoinType)
1041{
1042 // An encrypted wallet has a seed; it just has not been decrypted yet.
1043 //
1044 // The reset in the else branch was written when "no seed in memory after
1045 // loading" could only mean "this wallet has no seed". With encryption that
1046 // is no longer true, and taking the else branch here wiped the schema and
1047 // the derivation counters that had just been read correctly out of the
1048 // file. Unlocking later restores the seed but nothing restores those, so a
1049 // BIP44 wallet came back deriving legacy addresses -- different addresses
1050 // from the ones its own recovery phrase produces, while the audit reported
1051 // the phrase as covering nothing.
1052 if (HaveHDSeed() || !vchCryptedHDMaster.empty())
1053 {
1054 if (nHDKeySchema == HD_SCHEMA_NONE)
1055 {
1056 nHDKeySchema = HD_SCHEMA_LEGACY;
1057 printf("LoadWallet: deterministic seed has no schema metadata; "
1058 "treating it as legacy m/index'\n");
1059 }
1060 else if (nHDKeySchema != HD_SCHEMA_LEGACY &&
1061 nHDKeySchema != HD_SCHEMA_BIP44)
1062 {
1063 printf("LoadWallet: deterministic wallet schema %d (%s) is not "
1064 "supported by this build\n",
1065 nHDKeySchema, HDKeySchemaName(nHDKeySchema).c_str());
1066 return false;
1067 }
1068 if (!fHaveStoredHDCoinType)
1069 {
1070 nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH;
1071 printf("LoadWallet: deterministic seed has no BIP44 coin type metadata; "
1072 "using Bitflash coin type %u\n", nHDCoinType);
1073 }
1074 }
1075 else
1076 {
1077 nHDKeySchema = HD_SCHEMA_NONE;
1078 nHDNext = 0;
1079 nHDReceiveNext = 0;
1080 nHDChangeNext = 0;
1081 nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH;
1082 }
1083
1084 // fGenerateBitcoins and nMineMode only mean anything together, and a
1085 // wallet.dat can easily hold one without the other: Bitcoin 0.1.0 already
1086 // wrote fGenerateBitcoins when mining was toggled, years before this fork
1087 // had a mode at all. Restoring that lone flag put a node into the one state
1088 // the miner cannot act on -- generate on, mode relay -- so every miner
1089 // thread started and returned at the relay guard, and the machine sat there
1090 // looking like it was mining while hashing nothing. That happened to a
1091 // 32-core node on the first 1.2.11 start.
1092 //
1093 // So the flag is only believed when the mode it belongs to was stored with
1094 // it, and the pair is made coherent either way.
1095 if (!fMineModeFromCommandLine)
1096 {
1097 if (!fHaveStoredMineMode)
1098 {
1099 if (fGenerateBitcoins)
1100 printf("LoadWallet: ignoring a stored fGenerateBitcoins with no mining mode "
1101 "beside it -- it predates this setting and cannot be read on its own\n");
1102 fGenerateBitcoins = 0;
1103 }
1104 else
1105 {
1106 fGenerateBitcoins = (nMineMode != MINE_RELAY) ? 1 : 0;
1107 }
1108 }
1109
1110 return true;
1111}
1112
617bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) 1113bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
618{ 1114{
619 // Whether wallet.dat actually carried a mining mode, as opposed to just the 1115 // Whether wallet.dat actually carried a mining mode, as opposed to just the
@@ -924,34 +1420,269 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
924 return true; 1420 return true;
925} 1421}
926 1422
1423bool ScanWalletRecords(CWalletRecordVisitor& visitor, string& strErrorRet)
1424{
1425 // Under the SQLite backend there is no Berkeley DB cursor to walk; scan the
1426 // active SQLite store instead. This is the single choke point every "read
1427 // every wallet record" caller goes through (storage audit and check among
1428 // them), so routing it here makes those honour -walletbackend=sqlite.
1429 if (WalletSQLiteRuntimeActive())
1430 {
1431 strErrorRet.clear();
1432 return pWalletSQLiteRuntime->ScanRecords(visitor, strErrorRet);
1433 }
1434
1435 class CWalletScanDB : public CWalletDB
1436 {
1437 public:
1438 CWalletScanDB() : CWalletDB("r") { }
1439 using CDB::GetCursor;
1440 using CDB::ReadAtCursor;
1441 };
1442
1443 strErrorRet.clear();
1444 CWalletScanDB dbWallet;
1445 Dbc* pcursor = dbWallet.GetCursor();
1446 if (!pcursor)
1447 {
1448 strErrorRet = "could not open a wallet cursor";
1449 return false;
1450 }
1451 CAutoCursor cursorGuard(pcursor);
1452
1453 loop
1454 {
1455 CDataStream ssKey(SER_DISK);
1456 CDataStream ssValue(SER_DISK);
1457 int ret = dbWallet.ReadAtCursor(pcursor, ssKey, ssValue);
1458 if (ret == DB_NOTFOUND)
1459 break;
1460 if (ret != 0)
1461 {
1462 strErrorRet = strprintf("could not read wallet record: Berkeley DB error %d", ret);
1463 return false;
1464 }
1465 if (!visitor.VisitWalletRecord(ssKey, ssValue, strErrorRet))
1466 return false;
1467 }
1468
1469 return true;
1470}
1471
927bool LoadWallet() 1472bool LoadWallet()
928{ 1473{
929 vector<unsigned char> vchDefaultKey; 1474 vector<unsigned char> vchDefaultKey;
930 if (!CWalletDB("cr").LoadWallet(vchDefaultKey)) 1475 if (!CWalletDB("cr").LoadWallet(vchDefaultKey))
931 return false; 1476 return false;
932 1477
933 if (!vchDefaultKey.empty() && mapPubKeys.count(Hash160(vchDefaultKey))) 1478 return FinishLoadedWallet(vchDefaultKey, true);
1479}
1480
1481bool LoadWalletFromSQLite(const string& strPath)
1482{
1483 strWalletLoadError.clear();
1484
1485 if (!FileExists(strPath.c_str()))
934 { 1486 {
935 // Set keyUser 1487 strWalletLoadError = strprintf("SQLite wallet export does not exist: %s",
936 keyUser.SetPubKey(vchDefaultKey); 1488 strPath.c_str());
937 if (mapKeys.count(vchDefaultKey)) 1489 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
938 keyUser.SetPrivKey(mapKeys[vchDefaultKey]); 1490 return false;
939 } 1491 }
940 else if (IsWalletEncrypted()) 1492
1493 CWalletDBSQLite sqlite;
1494 string strError;
1495 if (!sqlite.OpenReadOnly(strPath, strError))
941 { 1496 {
942 printf("LoadWallet: encrypted wallet has no usable default public key. " 1497 strWalletLoadError = strError;
943 "The file was left untouched.\n"); 1498 printf("LoadWallet: cannot open SQLite wallet export: %s\n",
1499 strError.c_str());
944 return false; 1500 return false;
945 } 1501 }
946 else 1502
1503 CSQLiteWalletRuntimeLoadVisitor visitor;
1504 try
947 { 1505 {
948 // Create new keyUser and set as default key 1506 CRITICAL_BLOCK(cs_mapKeys)
949 keyUser.MakeNewKey(); 1507 CRITICAL_BLOCK(cs_mapWallet)
950 if (!AddKey(keyUser)) 1508 {
951 return false; 1509 if (!sqlite.ScanRecords(visitor, strError))
952 if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "Your Address")) 1510 {
953 return false; 1511 if (strWalletLoadError.empty())
954 CWalletDB().WriteDefaultKey(keyUser.GetPubKey()); 1512 strWalletLoadError = strError;
1513 printf("LoadWallet: SQLite wallet export could not be read. "
1514 "Failed while handling a '%s' record: %s\n",
1515 visitor.strLastType.c_str(), strError.c_str());
1516 return false;
1517 }
1518 }
1519 }
1520 catch (const std::exception& e)
1521 {
1522 strWalletLoadError = strprintf(
1523 "SQLite wallet export could not be read while handling a '%s' record: %s",
1524 visitor.strLastType.c_str(), e.what());
1525 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1526 return false;
1527 }
1528 catch (...)
1529 {
1530 strWalletLoadError = strprintf(
1531 "SQLite wallet export could not be read while handling a '%s' record",
1532 visitor.strLastType.c_str());
1533 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1534 return false;
1535 }
1536
1537 if (!ReconcileLoadedWalletMetadata(visitor.fHaveStoredMineMode,
1538 visitor.fHaveStoredHDCoinType))
1539 {
1540 if (strWalletLoadError.empty())
1541 strWalletLoadError = "SQLite wallet metadata is not supported by this build";
1542 return false;
1543 }
1544
1545 return FinishLoadedWallet(visitor.vchDefaultKey, false);
1546}
1547
1548// Open a SQLite wallet read-write and keep it open as the active runtime
1549// backend, then load its records into memory the same way LoadWalletFromSQLite
1550// does. Unlike the read-only staging path, this leaves pWalletSQLiteRuntime set
1551// so every later CWalletDB read/write/erase routes to this file for the life of
1552// the process. The Berkeley DB wallet.dat is never opened.
1553bool LoadWalletFromSQLiteRuntime(const string& strPath)
1554{
1555 strWalletLoadError.clear();
1556
1557 if (!FileExists(strPath.c_str()))
1558 {
1559 strWalletLoadError = strprintf("SQLite wallet does not exist: %s",
1560 strPath.c_str());
1561 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1562 return false;
1563 }
1564
1565 string strError;
1566 if (!WalletSQLiteRuntimeOpen(strPath, strError))
1567 {
1568 strWalletLoadError = strError;
1569 printf("LoadWallet: cannot open SQLite wallet backend: %s\n",
1570 strError.c_str());
1571 return false;
1572 }
1573
1574 CSQLiteWalletRuntimeLoadVisitor visitor;
1575 try
1576 {
1577 CRITICAL_BLOCK(cs_mapKeys)
1578 CRITICAL_BLOCK(cs_mapWallet)
1579 {
1580 if (!pWalletSQLiteRuntime->ScanRecords(visitor, strError))
1581 {
1582 WalletSQLiteRuntimeClose();
1583 if (strWalletLoadError.empty())
1584 strWalletLoadError = strError;
1585 printf("LoadWallet: SQLite wallet backend could not be read. "
1586 "Failed while handling a '%s' record: %s\n",
1587 visitor.strLastType.c_str(), strError.c_str());
1588 return false;
1589 }
1590 }
1591 }
1592 catch (const std::exception& e)
1593 {
1594 WalletSQLiteRuntimeClose();
1595 strWalletLoadError = strprintf(
1596 "SQLite wallet backend could not be read while handling a '%s' record: %s",
1597 visitor.strLastType.c_str(), e.what());
1598 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1599 return false;
1600 }
1601 catch (...)
1602 {
1603 WalletSQLiteRuntimeClose();
1604 strWalletLoadError = strprintf(
1605 "SQLite wallet backend could not be read while handling a '%s' record",
1606 visitor.strLastType.c_str());
1607 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1608 return false;
1609 }
1610
1611 // Refuse an empty or non-wallet SQLite file up front, with a message that
1612 // names the likely cause. FinishLoadedWallet below already rejects a
1613 // missing default key (it is the fMayCreateDefaultKey=false path), but its
1614 // wording -- "no usable default public key" -- reads as corruption when the
1615 // real story is usually "this file was never an exported wallet". A blank
1616 // wallet.sqlite left by a typo or a half-run export should say so.
1617 if (visitor.vchDefaultKey.empty())
1618 {
1619 WalletSQLiteRuntimeClose();
1620 strWalletLoadError =
1621 "SQLite wallet has no default key record; it may be empty or not a "
1622 "wallet export. Create it with -walletsqliteexport.";
1623 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1624 return false;
1625 }
1626
1627 if (!ReconcileLoadedWalletMetadata(visitor.fHaveStoredMineMode,
1628 visitor.fHaveStoredHDCoinType))
1629 {
1630 WalletSQLiteRuntimeClose();
1631 if (strWalletLoadError.empty())
1632 strWalletLoadError = "SQLite wallet metadata is not supported by this build";
1633 return false;
1634 }
1635
1636 // Never fabricate a default key: an export always carries one, and a file
1637 // that does not is refused with the wallet left open to no writes rather
1638 // than quietly minted into a new identity.
1639 if (!FinishLoadedWallet(visitor.vchDefaultKey, false))
1640 {
1641 WalletSQLiteRuntimeClose();
1642 return false;
1643 }
1644
1645 return true;
1646}
1647
1648// Create a brand-new wallet directly in the SQLite backend. For a fresh install
1649// where SQLite is the default there is no wallet.dat to export from, so open a
1650// new wallet.sqlite (Open() creates the file and its schema) and let
1651// FinishLoadedWallet mint the first default key straight into it -- the same
1652// call the Berkeley DB create path uses, so a new wallet is identical either
1653// way. Refuses to touch an existing file, and cleans up on failure so a broken
1654// half-initialised wallet.sqlite is never left for the next start to load.
1655bool CreateNewSQLiteWallet(const string& strPath)
1656{
1657 strWalletLoadError.clear();
1658
1659 if (FileExists(strPath.c_str()))
1660 {
1661 strWalletLoadError = strprintf("SQLite wallet already exists: %s",
1662 strPath.c_str());
1663 printf("CreateNewSQLiteWallet: %s\n", strWalletLoadError.c_str());
1664 return false;
1665 }
1666
1667 string strError;
1668 if (!WalletSQLiteRuntimeOpen(strPath, strError, false))
1669 {
1670 strWalletLoadError = strError;
1671 printf("CreateNewSQLiteWallet: cannot create SQLite wallet: %s\n",
1672 strError.c_str());
1673 return false;
1674 }
1675
1676 vector<unsigned char> vchNoDefaultKey;
1677 if (!FinishLoadedWallet(vchNoDefaultKey, true))
1678 {
1679 WalletSQLiteRuntimeClose();
1680 remove(strPath.c_str());
1681 remove((strPath + "-wal").c_str());
1682 remove((strPath + "-shm").c_str());
1683 if (strWalletLoadError.empty())
1684 strWalletLoadError = "could not initialise the new SQLite wallet";
1685 return false;
955 } 1686 }
956 1687
957 return true; 1688 return true;
@@ -974,21 +1705,99 @@ bool LoadWallet()
974// this file was written are not spendable from it. Backing up once is not 1705// this file was written are not spendable from it. Backing up once is not
975// enough, and that is a property of the wallet format rather than of this 1706// enough, and that is a property of the wallet format rather than of this
976// function. 1707// function.
1708// Back up the active SQLite wallet. The store is checkpointed so the WAL is
1709// folded into the main file, then the single self-contained wallet.sqlite is
1710// copied through a temp file and renamed into place (same temp/old/rename dance
1711// as the Berkeley DB path, for Windows' non-overwriting rename). No lsn_reset:
1712// a checkpointed SQLite file needs nothing beside it to open.
1713static bool BackupWalletSQLite(const string& strDest)
1714{
1715 string strSrc = GetAppDir() + "/wallet.sqlite";
1716 if (!FileExists(strSrc.c_str()))
1717 return error("BackupWallet() : %s does not exist\n", strSrc.c_str());
1718
1719 string strCheckpointError;
1720 if (!WalletSQLiteRuntimeCheckpoint(strCheckpointError))
1721 return error("BackupWallet() : checkpoint failed: %s\n", strCheckpointError.c_str());
1722
1723 string strTemp = strprintf("%s.tmp.%lld", strDest.c_str(), GetTime());
1724 for (int i = 1; FileExists(strTemp.c_str()); i++)
1725 strTemp = strprintf("%s.tmp.%lld.%d", strDest.c_str(), GetTime(), i);
1726 string strOld = strprintf("%s.old.%lld", strDest.c_str(), GetTime());
1727 for (int i = 1; FileExists(strOld.c_str()); i++)
1728 strOld = strprintf("%s.old.%lld.%d", strDest.c_str(), GetTime(), i);
1729
1730 FILE* pfIn = fopen(strSrc.c_str(), "rb");
1731 if (!pfIn)
1732 return error("BackupWallet() : cannot read %s\n", strSrc.c_str());
1733 FILE* pfOut = fopen(strTemp.c_str(), "wb");
1734 if (!pfOut)
1735 {
1736 fclose(pfIn);
1737 return error("BackupWallet() : cannot write %s\n", strTemp.c_str());
1738 }
1739
1740 char buf[65536];
1741 size_t n;
1742 bool fOk = true;
1743 while ((n = fread(buf, 1, sizeof(buf), pfIn)) > 0)
1744 if (fwrite(buf, 1, n, pfOut) != n) { fOk = false; break; }
1745 if (ferror(pfIn))
1746 fOk = false;
1747 fclose(pfIn);
1748 if (fclose(pfOut) != 0)
1749 fOk = false;
1750 if (!fOk)
1751 {
1752 remove(strTemp.c_str()); // half a wallet is worse than none
1753 return error("BackupWallet() : copy to %s failed\n", strTemp.c_str());
1754 }
1755
1756 bool fHadOldDest = FileExists(strDest.c_str());
1757 if (fHadOldDest && rename(strDest.c_str(), strOld.c_str()) != 0)
1758 {
1759 remove(strTemp.c_str());
1760 return error("BackupWallet() : could not move old %s aside\n", strDest.c_str());
1761 }
1762 if (rename(strTemp.c_str(), strDest.c_str()) != 0)
1763 {
1764 remove(strTemp.c_str());
1765 if (fHadOldDest)
1766 rename(strOld.c_str(), strDest.c_str());
1767 return error("BackupWallet() : could not install %s\n", strDest.c_str());
1768 }
1769 if (fHadOldDest)
1770 remove(strOld.c_str());
1771 return true;
1772}
1773
977bool BackupWallet(const string& strDest) 1774bool BackupWallet(const string& strDest)
978{ 1775{
979 if (strDest.empty()) 1776 if (strDest.empty())
980 return error("BackupWallet() : no destination given\n"); 1777 return error("BackupWallet() : no destination given\n");
981 1778
1779 // Under the SQLite backend the live wallet is wallet.sqlite, not wallet.dat.
1780 if (WalletSQLiteRuntimeActive())
1781 return BackupWalletSQLite(strDest);
1782
982 string strSrc = GetAppDir() + "/wallet.dat"; 1783 string strSrc = GetAppDir() + "/wallet.dat";
983 if (!FileExists(strSrc.c_str())) 1784 if (!FileExists(strSrc.c_str()))
984 return error("BackupWallet() : %s does not exist\n", strSrc.c_str()); 1785 return error("BackupWallet() : %s does not exist\n", strSrc.c_str());
985 1786
1787 string strTemp = strprintf("%s.tmp.%lld", strDest.c_str(), GetTime());
1788 for (int i = 1; FileExists(strTemp.c_str()); i++)
1789 strTemp = strprintf("%s.tmp.%lld.%d", strDest.c_str(), GetTime(), i);
1790 string strOld = strprintf("%s.old.%lld", strDest.c_str(), GetTime());
1791 for (int i = 1; FileExists(strOld.c_str()); i++)
1792 strOld = strprintf("%s.old.%lld.%d", strDest.c_str(), GetTime(), i);
1793
986 CRITICAL_BLOCK(cs_db) 1794 CRITICAL_BLOCK(cs_db)
987 { 1795 {
988 // Committing first, so the copy is not missing the newest records. 1796 // Committing first, so the copy is not missing the newest records.
989 // Both calls can throw, and a failed backup must not take the node 1797 // Both calls can throw, and a failed backup must not take the node
990 // down with it. 1798 // down with it.
991 try { dbenv.txn_checkpoint(0, 0, 0); } 1799 DbEnv& env = GetDbEnv();
1800 try { env.txn_checkpoint(0, 0, 0); }
992 catch (const std::exception& e) 1801 catch (const std::exception& e)
993 { return error("BackupWallet() : checkpoint failed: %s\n", e.what()); } 1802 { return error("BackupWallet() : checkpoint failed: %s\n", e.what()); }
994 1803
@@ -997,11 +1806,11 @@ bool BackupWallet(const string& strDest)
997 FILE* pfIn = fopen(strSrc.c_str(), "rb"); 1806 FILE* pfIn = fopen(strSrc.c_str(), "rb");
998 if (!pfIn) 1807 if (!pfIn)
999 return error("BackupWallet() : cannot read %s\n", strSrc.c_str()); 1808 return error("BackupWallet() : cannot read %s\n", strSrc.c_str());
1000 FILE* pfOut = fopen(strDest.c_str(), "wb"); 1809 FILE* pfOut = fopen(strTemp.c_str(), "wb");
1001 if (!pfOut) 1810 if (!pfOut)
1002 { 1811 {
1003 fclose(pfIn); 1812 fclose(pfIn);
1004 return error("BackupWallet() : cannot write %s\n", strDest.c_str()); 1813 return error("BackupWallet() : cannot write %s\n", strTemp.c_str());
1005 } 1814 }
1006 1815
1007 char buf[65536]; 1816 char buf[65536];
@@ -1018,18 +1827,39 @@ bool BackupWallet(const string& strDest)
1018 1827
1019 if (!fOk) 1828 if (!fOk)
1020 { 1829 {
1021 remove(strDest.c_str()); // half a wallet is worse than none 1830 remove(strTemp.c_str()); // half a wallet is worse than none
1022 return error("BackupWallet() : copy to %s failed\n", strDest.c_str()); 1831 return error("BackupWallet() : copy to %s failed\n", strTemp.c_str());
1023 } 1832 }
1024 1833
1025 // Without this the copy is welded to this node's database/ dir. 1834 // Without this the copy is welded to this node's database/ dir.
1026 int ret = dbenv.lsn_reset(strDest.c_str(), 0); 1835 int ret = env.lsn_reset(strTemp.c_str(), 0);
1027 if (ret != 0) 1836 if (ret != 0)
1028 printf("BackupWallet() : warning -- lsn_reset returned %d; the copy " 1837 printf("BackupWallet() : warning -- lsn_reset returned %d; the copy "
1029 "may only open beside this node's database/ directory\n", ret); 1838 "may only open beside this node's database/ directory\n", ret);
1839
1840 bool fHadOldDest = FileExists(strDest.c_str());
1841 if (fHadOldDest && rename(strDest.c_str(), strOld.c_str()) != 0)
1842 {
1843 remove(strTemp.c_str());
1844 return error("BackupWallet() : could not move old %s aside\n", strDest.c_str());
1845 }
1846 if (rename(strTemp.c_str(), strDest.c_str()) != 0)
1847 {
1848 remove(strTemp.c_str());
1849 if (fHadOldDest)
1850 rename(strOld.c_str(), strDest.c_str());
1851 return error("BackupWallet() : could not install %s\n", strDest.c_str());
1852 }
1853 if (fHadOldDest)
1854 remove(strOld.c_str());
1030 } 1855 }
1031 catch (const std::exception& e) 1856 catch (const std::exception& e)
1032 { return error("BackupWallet() : %s\n", e.what()); } 1857 {
1858 remove(strTemp.c_str());
1859 if (FileExists(strOld.c_str()) && !FileExists(strDest.c_str()))
1860 rename(strOld.c_str(), strDest.c_str());
1861 return error("BackupWallet() : %s\n", e.what());
1862 }
1033 } 1863 }
1034 1864
1035 // Recorded here rather than by whoever called, so that a backup taken with 1865 // Recorded here rather than by whoever called, so that a backup taken with
@@ -1307,73 +2137,71 @@ public:
1307 { 2137 {
1308 return Write(make_pair(string("pool"), nIndex), vchPubKey); 2138 return Write(make_pair(string("pool"), nIndex), vchPubKey);
1309 } 2139 }
2140};
2141
2142static bool IsWalletEncryptionPrivateRecord(const string& strType)
2143{
2144 return strType == "key" ||
2145 strType == "pool" ||
2146 strType == "hdmaster" ||
2147 strType == "hdchaincode" ||
2148 strType == "mkey" ||
2149 strType == "ckey" ||
2150 strType == "cryptedhdmaster" ||
2151 strType == "cryptedhdchaincode" ||
2152 strType == "walletminversion";
2153}
2154
2155class CWalletPublicRecordCopyVisitor : public CWalletRecordVisitor
2156{
2157public:
2158 CWalletRewriteDB& dbTo;
2159 int& nCopied;
1310 2160
1311 bool CopyPublicRecordsTo(CWalletRewriteDB& dbTo, int& nCopiedRet, string& strErrorRet) 2161 CWalletPublicRecordCopyVisitor(CWalletRewriteDB& dbToIn, int& nCopiedIn)
2162 : dbTo(dbToIn), nCopied(nCopiedIn) { }
2163
2164 bool VisitWalletRecord(const CDataStream& ssKey,
2165 const CDataStream& ssValue,
2166 string& strErrorRet)
1312 { 2167 {
1313 nCopiedRet = 0; 2168 string strType;
1314 Dbc* pcursor = GetCursor(); 2169 try
1315 if (!pcursor)
1316 { 2170 {
1317 strErrorRet = "could not open a wallet cursor"; 2171 CDataStream ssType = ssKey;
1318 return false; 2172 ssType >> strType;
1319 } 2173 }
1320 2174 catch (...)
1321 unsigned int fFlags = DB_NEXT;
1322 loop
1323 { 2175 {
1324 CDataStream ssKey; 2176 strErrorRet = "wallet.dat has a record whose key type cannot be read";
1325 CDataStream ssValue; 2177 return false;
1326 int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); 2178 }
1327 if (ret == DB_NOTFOUND)
1328 break;
1329 if (ret != 0)
1330 {
1331 pcursor->close();
1332 strErrorRet = strprintf("could not read wallet record: Berkeley DB error %d", ret);
1333 return false;
1334 }
1335
1336 string strType;
1337 try
1338 {
1339 CDataStream ssType = ssKey;
1340 ssType >> strType;
1341 }
1342 catch (...)
1343 {
1344 pcursor->close();
1345 strErrorRet = "wallet.dat has a record whose key type cannot be read";
1346 return false;
1347 }
1348 2179
1349 if (strType == "key" || 2180 if (IsWalletEncryptionPrivateRecord(strType))
1350 strType == "pool" || 2181 return true;
1351 strType == "hdmaster" ||
1352 strType == "hdchaincode" ||
1353 strType == "mkey" ||
1354 strType == "ckey" ||
1355 strType == "cryptedhdmaster" ||
1356 strType == "cryptedhdchaincode" ||
1357 strType == "walletminversion")
1358 continue;
1359 2182
1360 if (!dbTo.WriteRaw(ssKey, ssValue)) 2183 if (!dbTo.WriteRaw(ssKey, ssValue))
1361 { 2184 {
1362 pcursor->close(); 2185 strErrorRet = strprintf("could not copy wallet record '%s'", strType.c_str());
1363 strErrorRet = strprintf("could not copy wallet record '%s'", strType.c_str()); 2186 return false;
1364 return false;
1365 }
1366 nCopiedRet++;
1367 } 2187 }
1368 2188 nCopied++;
1369 pcursor->close();
1370 return true; 2189 return true;
1371 } 2190 }
1372}; 2191};
1373 2192
1374static string WalletEncryptBackupPath() 2193static bool CopyPublicWalletRecordsTo(CWalletRewriteDB& dbTo,
2194 int& nCopiedRet,
2195 string& strErrorRet)
1375{ 2196{
1376 string strBase = GetAppDir() + "/wallet.dat.before-encrypt."; 2197 nCopiedRet = 0;
2198 CWalletPublicRecordCopyVisitor visitor(dbTo, nCopiedRet);
2199 return ScanWalletRecords(visitor, strErrorRet);
2200}
2201
2202static string WalletEncryptBackupPathFor(const char* pszBaseName)
2203{
2204 string strBase = GetAppDir() + "/" + pszBaseName + ".before-encrypt.";
1377 string strPath = strBase + i64tostr(GetTime()) + ".bak"; 2205 string strPath = strBase + i64tostr(GetTime()) + ".bak";
1378 for (int i = 1; FileExists(strPath.c_str()); i++) 2206 for (int i = 1; FileExists(strPath.c_str()); i++)
1379 strPath = strBase + i64tostr(GetTime()) + "." + itostr(i) + ".bak"; 2207 strPath = strBase + i64tostr(GetTime()) + "." + itostr(i) + ".bak";
@@ -1403,6 +2231,192 @@ static void SecureClearBytes(vector<unsigned char>& vch)
1403 vch.clear(); 2231 vch.clear();
1404} 2232}
1405 2233
2234// The SQLite counterpart of CWalletPublicRecordCopyVisitor: same skip list,
2235// same byte-for-byte copy, but the destination is a CWalletDBSQLite record
2236// store rather than a Berkeley DB file.
2237class CWalletPublicRecordCopyToSQLiteVisitor : public CWalletRecordVisitor
2238{
2239public:
2240 CWalletDBSQLite& dbTo;
2241 int& nCopied;
2242
2243 CWalletPublicRecordCopyToSQLiteVisitor(CWalletDBSQLite& dbToIn, int& nCopiedIn)
2244 : dbTo(dbToIn), nCopied(nCopiedIn) { }
2245
2246 bool VisitWalletRecord(const CDataStream& ssKey,
2247 const CDataStream& ssValue,
2248 string& strErrorRet)
2249 {
2250 string strType;
2251 try
2252 {
2253 CDataStream ssType = ssKey;
2254 ssType >> strType;
2255 }
2256 catch (...)
2257 {
2258 strErrorRet = "SQLite wallet has a record whose key type cannot be read";
2259 return false;
2260 }
2261
2262 if (IsWalletEncryptionPrivateRecord(strType))
2263 return true;
2264
2265 vector<unsigned char> vchKey(ssKey.begin(), ssKey.end());
2266 vector<unsigned char> vchValue(ssValue.begin(), ssValue.end());
2267 bool fOk = dbTo.WriteRecord(vchKey, vchValue, strErrorRet, true);
2268 if (!vchKey.empty())
2269 memset(&vchKey[0], 0, vchKey.size());
2270 if (!vchValue.empty())
2271 memset(&vchValue[0], 0, vchValue.size());
2272 if (!fOk)
2273 {
2274 strErrorRet = strprintf("could not copy wallet record '%s': %s",
2275 strType.c_str(), strErrorRet.c_str());
2276 return false;
2277 }
2278 nCopied++;
2279 return true;
2280 }
2281};
2282
2283// Persist an encrypted wallet under the SQLite backend. Everything the caller
2284// computed -- the master key, the re-encrypted private keys, the fresh crypted
2285// key pool, the encrypted HD seed -- lands in a brand new wallet.sqlite built
2286// in one transaction, which is then swapped in for the plaintext original.
2287//
2288// A fresh file rather than an in-place rewrite for two reasons: it keeps the
2289// whole change atomic (a crash before the rename leaves the old wallet whole),
2290// and it guarantees no plaintext key survives in a freed page the way an
2291// in-place DELETE would leave behind until a VACUUM.
2292static bool EncryptWalletPersistSQLite(
2293 const vector<pair<vector<unsigned char>, vector<unsigned char> > >& vCryptedKeys,
2294 const vector<pair<vector<unsigned char>, vector<unsigned char> > >& vNewPoolKeys,
2295 const vector<pair<int64, vector<unsigned char> > >& vNewPool,
2296 const CWalletMasterKey& kMasterKey,
2297 const vector<unsigned char>& vchCryptedHDMasterNew,
2298 const vector<unsigned char>& vchCryptedHDChainCodeNew,
2299 unsigned int nHDNextNew,
2300 int& nCopiedRet,
2301 string& strBackupRet,
2302 string& strErrorRet)
2303{
2304 nCopiedRet = 0;
2305 if (!pWalletSQLiteRuntime)
2306 {
2307 strErrorRet = "SQLite wallet backend is not active";
2308 return false;
2309 }
2310
2311 string strWalletPath = GetAppDir() + "/wallet.sqlite";
2312 string strTempPath = GetAppDir() + "/" +
2313 strprintf("wallet.sqlite.encrypting.%lld", GetTime());
2314 remove(strTempPath.c_str());
2315 remove((strTempPath + "-wal").c_str());
2316 remove((strTempPath + "-shm").c_str());
2317
2318 {
2319 CWalletDBSQLite dbTmp;
2320 if (!dbTmp.Open(strTempPath, strErrorRet))
2321 {
2322 remove(strTempPath.c_str());
2323 return false;
2324 }
2325 if (!dbTmp.BeginTransaction(strErrorRet))
2326 {
2327 dbTmp.Close();
2328 remove(strTempPath.c_str());
2329 return false;
2330 }
2331
2332 // The public records come from the live SQLite store -- under this
2333 // backend ScanWalletRecords() has no Berkeley DB behind it to read.
2334 CWalletPublicRecordCopyToSQLiteVisitor visitor(dbTmp, nCopiedRet);
2335 bool fOk = pWalletSQLiteRuntime->ScanRecords(visitor, strErrorRet);
2336
2337 // Encrypted metadata, serialized byte-for-byte the way the Berkeley DB
2338 // path (CWalletRewriteDB) writes it, so a wallet encrypted under either
2339 // backend is the same set of records. ckey uses INSERT-only to match
2340 // WriteCryptedKey; the rest overwrite.
2341 fOk = fOk &&
2342 dbTmp.WriteTypedRecord(string("walletminversion"),
2343 (int)WALLET_FORMAT_ENCRYPTED, strErrorRet, true) &&
2344 dbTmp.WriteTypedRecord(make_pair(string("mkey"),
2345 (unsigned int)(nWalletMasterKeyMaxID + 1)),
2346 kMasterKey, strErrorRet, true);
2347 for (size_t i = 0; fOk && i < vCryptedKeys.size(); i++)
2348 fOk = dbTmp.WriteTypedRecord(make_pair(string("ckey"), vCryptedKeys[i].first),
2349 vCryptedKeys[i].second, strErrorRet, false);
2350 for (size_t i = 0; fOk && i < vNewPoolKeys.size(); i++)
2351 fOk = dbTmp.WriteTypedRecord(make_pair(string("ckey"), vNewPoolKeys[i].first),
2352 vNewPoolKeys[i].second, strErrorRet, false);
2353 if (fOk && !vchCryptedHDMasterNew.empty())
2354 fOk = dbTmp.WriteTypedRecord(string("cryptedhdmaster"),
2355 vchCryptedHDMasterNew, strErrorRet, true) &&
2356 dbTmp.WriteTypedRecord(string("cryptedhdchaincode"),
2357 vchCryptedHDChainCodeNew, strErrorRet, true);
2358 if (fOk)
2359 fOk = dbTmp.WriteTypedRecord(string("hdnext"), nHDNextNew, strErrorRet, true);
2360 for (size_t i = 0; fOk && i < vNewPool.size(); i++)
2361 fOk = dbTmp.WriteTypedRecord(make_pair(string("pool"), vNewPool[i].first),
2362 vNewPool[i].second, strErrorRet, true);
2363
2364 if (!fOk)
2365 {
2366 string strIgnore;
2367 dbTmp.RollbackTransaction(strIgnore);
2368 dbTmp.Close();
2369 remove(strTempPath.c_str());
2370 remove((strTempPath + "-wal").c_str());
2371 remove((strTempPath + "-shm").c_str());
2372 return false;
2373 }
2374
2375 if (!dbTmp.CommitTransaction(strErrorRet))
2376 {
2377 dbTmp.Close();
2378 remove(strTempPath.c_str());
2379 remove((strTempPath + "-wal").c_str());
2380 remove((strTempPath + "-shm").c_str());
2381 return false;
2382 }
2383 string strCheckpointError;
2384 dbTmp.Checkpoint(strCheckpointError);
2385 dbTmp.Close();
2386 }
2387
2388 // The rebuilt wallet is committed and self-contained. Close the live handle
2389 // so the file can be swapped: Windows will not rename a file that still has
2390 // an open handle, and the node exits right after this.
2391 WalletSQLiteRuntimeClose();
2392
2393 string strBackupPath = WalletEncryptBackupPathFor("wallet.sqlite");
2394 if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0)
2395 {
2396 strErrorRet = strprintf("could not move the original SQLite wallet to %s",
2397 strBackupPath.c_str());
2398 remove(strTempPath.c_str());
2399 return false;
2400 }
2401 // The moved-aside wallet owns any leftover write-ahead log or shared-memory
2402 // sidecar; drop stale ones so they cannot shadow the new wallet.
2403 remove((strWalletPath + "-wal").c_str());
2404 remove((strWalletPath + "-shm").c_str());
2405
2406 if (rename(strTempPath.c_str(), strWalletPath.c_str()) != 0)
2407 {
2408 rename(strBackupPath.c_str(), strWalletPath.c_str());
2409 strErrorRet = "could not install the encrypted SQLite wallet; the original was restored";
2410 remove(strTempPath.c_str());
2411 return false;
2412 }
2413 remove((strTempPath + "-wal").c_str());
2414 remove((strTempPath + "-shm").c_str());
2415
2416 strBackupRet = strBackupPath;
2417 return true;
2418}
2419
1406bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet) 2420bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet)
1407{ 2421{
1408 strBackupRet.clear(); 2422 strBackupRet.clear();
@@ -1541,15 +2555,32 @@ bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& st
1541 vNewPool.push_back(make_pair((int64)i + 1, vchPubKey)); 2555 vNewPool.push_back(make_pair((int64)i + 1, vchPubKey));
1542 } 2556 }
1543 2557
2558 // Every ciphertext is computed; nothing has touched the store yet. Under
2559 // the SQLite backend, persist by rebuilding wallet.sqlite in one atomic
2560 // transaction and swapping it in. The Berkeley DB path below is unchanged.
2561 if (WalletSQLiteRuntimeActive())
2562 {
2563 int nCopiedSQLite = 0;
2564 if (!EncryptWalletPersistSQLite(vCryptedKeys, vNewPoolKeys, vNewPool,
2565 kMasterKey, vchCryptedHDMasterNew,
2566 vchCryptedHDChainCodeNew, nHDNextNew,
2567 nCopiedSQLite, strBackupRet, strErrorRet))
2568 return false;
2569 printf("EncryptWallet() : copied %d public record(s), encrypted %u key(s), "
2570 "rebuilt %d key-pool entry(s) [SQLite]\n",
2571 nCopiedSQLite, (unsigned int)(vCryptedKeys.size() + vNewPoolKeys.size()),
2572 (int)vNewPool.size());
2573 return true;
2574 }
2575
1544 string strTempFile = strprintf("wallet.encrypting.%lld.dat", GetTime()); 2576 string strTempFile = strprintf("wallet.encrypting.%lld.dat", GetTime());
1545 string strTempPath = GetAppDir() + "/" + strTempFile; 2577 string strTempPath = GetAppDir() + "/" + strTempFile;
1546 remove(strTempPath.c_str()); 2578 remove(strTempPath.c_str());
1547 2579
1548 int nCopied = 0; 2580 int nCopied = 0;
1549 { 2581 {
1550 CWalletRewriteDB dbSource("wallet.dat", "r");
1551 CWalletRewriteDB dbTarget(strTempFile.c_str(), "c+"); 2582 CWalletRewriteDB dbTarget(strTempFile.c_str(), "c+");
1552 if (!dbSource.CopyPublicRecordsTo(dbTarget, nCopied, strErrorRet)) 2583 if (!CopyPublicWalletRecordsTo(dbTarget, nCopied, strErrorRet))
1553 { 2584 {
1554 remove(strTempPath.c_str()); 2585 remove(strTempPath.c_str());
1555 return false; 2586 return false;
@@ -1609,7 +2640,7 @@ bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& st
1609 DBFlush(true); 2640 DBFlush(true);
1610 2641
1611 string strWalletPath = GetAppDir() + "/wallet.dat"; 2642 string strWalletPath = GetAppDir() + "/wallet.dat";
1612 string strBackupPath = WalletEncryptBackupPath(); 2643 string strBackupPath = WalletEncryptBackupPathFor("wallet.dat");
1613 if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0) 2644 if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0)
1614 { 2645 {
1615 strErrorRet = strprintf("could not move the original wallet to %s", strBackupPath.c_str()); 2646 strErrorRet = strprintf("could not move the original wallet to %s", strBackupPath.c_str());
diff --git a/src/db.h b/src/db.h
index 37a252f..859e3fb 100644
--- a/src/db.h
+++ b/src/db.h
@@ -46,7 +46,7 @@ public:
46}; 46};
47 47
48 48
49extern DbEnv dbenv; 49extern DbEnv& GetDbEnv();
50extern void DBFlush(bool fShutdown); 50extern void DBFlush(bool fShutdown);
51// Close the environment and delete its logs. Only the encryption command calls 51// Close the environment and delete its logs. Only the encryption command calls
52// this: the logs hold the plaintext records the old wallet wrote. 52// this: the logs hold the plaintext records the old wallet wrote.
@@ -235,7 +235,7 @@ public:
235 if (!pdb) 235 if (!pdb)
236 return false; 236 return false;
237 DbTxn* ptxn = NULL; 237 DbTxn* ptxn = NULL;
238 int ret = dbenv.txn_begin(GetTxn(), &ptxn, 0); 238 int ret = GetDbEnv().txn_begin(GetTxn(), &ptxn, 0);
239 if (!ptxn || ret != 0) 239 if (!ptxn || ret != 0)
240 return false; 240 return false;
241 vTxn.push_back(ptxn); 241 vTxn.push_back(ptxn);
@@ -333,6 +333,24 @@ private:
333// Legacy IP-based peer address database (CAddrDB / addr.dat) removed -- 333// Legacy IP-based peer address database (CAddrDB / addr.dat) removed --
334// peer discovery is entirely Nostr/.btf-based now. 334// peer discovery is entirely Nostr/.btf-based now.
335 335
336bool WalletSQLiteRuntimeActive();
337bool WalletSQLiteRuntimeOpen(const string& strPath, string& strErrorRet, bool fMustExist=true);
338void WalletSQLiteRuntimeClose();
339bool WalletSQLiteRuntimeReadRecord(const vector<unsigned char>& vchKey,
340 vector<unsigned char>& vchValueRet);
341bool WalletSQLiteRuntimeWriteRecord(const vector<unsigned char>& vchKey,
342 const vector<unsigned char>& vchValue,
343 bool fOverwrite);
344bool WalletSQLiteRuntimeEraseRecord(const vector<unsigned char>& vchKey);
345bool WalletSQLiteRuntimeBeginTxn();
346bool WalletSQLiteRuntimeCommitTxn();
347void WalletSQLiteRuntimeRollbackTxn();
348bool WalletSQLiteRuntimeOpenReadOnly(const string& strPath, string& strErrorRet);
349bool WalletSQLiteRuntimeCheckpoint(string& strErrorRet);
350string ReadWalletBackendMarker();
351bool WriteWalletBackendMarker(const string& strBackend);
352bool CreateNewSQLiteWallet(const string& strPath);
353
336 354
337 355
338 356
@@ -340,64 +358,141 @@ private:
340class CWalletDB : public CDB 358class CWalletDB : public CDB
341{ 359{
342public: 360public:
343 CWalletDB(const char* pszMode="r+", bool fTxn=false) : CDB("wallet.dat", pszMode, fTxn) { } 361 CWalletDB(const char* pszMode="r+", bool fTxn=false);
344private: 362private:
345 CWalletDB(const CWalletDB&); 363 CWalletDB(const CWalletDB&);
346 void operator=(const CWalletDB&); 364 void operator=(const CWalletDB&);
365
366 template<typename K, typename T>
367 bool ReadWalletRecord(const K& key, T& value)
368 {
369 if (!WalletSQLiteRuntimeActive())
370 return Read(key, value);
371
372 CDataStream ssKey(SER_DISK);
373 ssKey.reserve(1000);
374 ssKey << key;
375 vector<unsigned char> vchKey(ssKey.begin(), ssKey.end());
376 vector<unsigned char> vchValue;
377
378 bool fOk = WalletSQLiteRuntimeReadRecord(vchKey, vchValue);
379 if (!vchKey.empty())
380 memset(&vchKey[0], 0, vchKey.size());
381 if (!ssKey.empty())
382 memset(&ssKey[0], 0, ssKey.size());
383 if (!fOk)
384 return false;
385
386 CDataStream ssValue(vchValue, SER_DISK);
387 ssValue >> value;
388 bool fReadOk = !ssValue.fail();
389 if (!vchValue.empty())
390 memset(&vchValue[0], 0, vchValue.size());
391 if (!ssValue.empty())
392 memset(&ssValue[0], 0, ssValue.size());
393 return fReadOk;
394 }
395
396 template<typename K, typename T>
397 bool WriteWalletRecord(const K& key, const T& value, bool fOverwrite=true)
398 {
399 if (!WalletSQLiteRuntimeActive())
400 return Write(key, value, fOverwrite);
401
402 CDataStream ssKey(SER_DISK);
403 ssKey.reserve(1000);
404 ssKey << key;
405 CDataStream ssValue(SER_DISK);
406 ssValue.reserve(10000);
407 ssValue << value;
408
409 vector<unsigned char> vchKey(ssKey.begin(), ssKey.end());
410 vector<unsigned char> vchValue(ssValue.begin(), ssValue.end());
411 bool fOk = WalletSQLiteRuntimeWriteRecord(vchKey, vchValue, fOverwrite);
412
413 if (!vchKey.empty())
414 memset(&vchKey[0], 0, vchKey.size());
415 if (!vchValue.empty())
416 memset(&vchValue[0], 0, vchValue.size());
417 if (!ssKey.empty())
418 memset(&ssKey[0], 0, ssKey.size());
419 if (!ssValue.empty())
420 memset(&ssValue[0], 0, ssValue.size());
421 return fOk;
422 }
423
424 template<typename K>
425 bool EraseWalletRecord(const K& key)
426 {
427 if (!WalletSQLiteRuntimeActive())
428 return Erase(key);
429
430 CDataStream ssKey(SER_DISK);
431 ssKey.reserve(1000);
432 ssKey << key;
433 vector<unsigned char> vchKey(ssKey.begin(), ssKey.end());
434 bool fOk = WalletSQLiteRuntimeEraseRecord(vchKey);
435
436 if (!vchKey.empty())
437 memset(&vchKey[0], 0, vchKey.size());
438 if (!ssKey.empty())
439 memset(&ssKey[0], 0, ssKey.size());
440 return fOk;
441 }
347public: 442public:
348 bool ReadName(const string& strAddress, string& strName) 443 bool ReadName(const string& strAddress, string& strName)
349 { 444 {
350 strName = ""; 445 strName = "";
351 return Read(make_pair(string("name"), strAddress), strName); 446 return ReadWalletRecord(make_pair(string("name"), strAddress), strName);
352 } 447 }
353 448
354 bool WriteName(const string& strAddress, const string& strName) 449 bool WriteName(const string& strAddress, const string& strName)
355 { 450 {
356 mapAddressBook[strAddress] = strName; 451 mapAddressBook[strAddress] = strName;
357 return Write(make_pair(string("name"), strAddress), strName); 452 return WriteWalletRecord(make_pair(string("name"), strAddress), strName);
358 } 453 }
359 454
360 bool EraseName(const string& strAddress) 455 bool EraseName(const string& strAddress)
361 { 456 {
362 mapAddressBook.erase(strAddress); 457 mapAddressBook.erase(strAddress);
363 return Erase(make_pair(string("name"), strAddress)); 458 return EraseWalletRecord(make_pair(string("name"), strAddress));
364 } 459 }
365 460
366 bool ReadTx(uint256 hash, CWalletTx& wtx) 461 bool ReadTx(uint256 hash, CWalletTx& wtx)
367 { 462 {
368 return Read(make_pair(string("tx"), hash), wtx); 463 return ReadWalletRecord(make_pair(string("tx"), hash), wtx);
369 } 464 }
370 465
371 bool WriteTx(uint256 hash, const CWalletTx& wtx) 466 bool WriteTx(uint256 hash, const CWalletTx& wtx)
372 { 467 {
373 return Write(make_pair(string("tx"), hash), wtx); 468 return WriteWalletRecord(make_pair(string("tx"), hash), wtx);
374 } 469 }
375 470
376 bool EraseTx(uint256 hash) 471 bool EraseTx(uint256 hash)
377 { 472 {
378 return Erase(make_pair(string("tx"), hash)); 473 return EraseWalletRecord(make_pair(string("tx"), hash));
379 } 474 }
380 475
381 bool ReadKey(const vector<unsigned char>& vchPubKey, CPrivKey& vchPrivKey) 476 bool ReadKey(const vector<unsigned char>& vchPubKey, CPrivKey& vchPrivKey)
382 { 477 {
383 vchPrivKey.clear(); 478 vchPrivKey.clear();
384 return Read(make_pair(string("key"), vchPubKey), vchPrivKey); 479 return ReadWalletRecord(make_pair(string("key"), vchPubKey), vchPrivKey);
385 } 480 }
386 481
387 bool WriteKey(const vector<unsigned char>& vchPubKey, const CPrivKey& vchPrivKey) 482 bool WriteKey(const vector<unsigned char>& vchPubKey, const CPrivKey& vchPrivKey)
388 { 483 {
389 return Write(make_pair(string("key"), vchPubKey), vchPrivKey, false); 484 return WriteWalletRecord(make_pair(string("key"), vchPubKey), vchPrivKey, false);
390 } 485 }
391 486
392 bool ReadDefaultKey(vector<unsigned char>& vchPubKey) 487 bool ReadDefaultKey(vector<unsigned char>& vchPubKey)
393 { 488 {
394 vchPubKey.clear(); 489 vchPubKey.clear();
395 return Read(string("defaultkey"), vchPubKey); 490 return ReadWalletRecord(string("defaultkey"), vchPubKey);
396 } 491 }
397 492
398 bool WriteDefaultKey(const vector<unsigned char>& vchPubKey) 493 bool WriteDefaultKey(const vector<unsigned char>& vchPubKey)
399 { 494 {
400 return Write(string("defaultkey"), vchPubKey); 495 return WriteWalletRecord(string("defaultkey"), vchPubKey);
401 } 496 }
402 497
403 // --- Deterministic (BIP32) seed --------------------------------------- 498 // --- Deterministic (BIP32) seed ---------------------------------------
@@ -415,62 +510,75 @@ public:
415 { 510 {
416 vchMasterRet.clear(); 511 vchMasterRet.clear();
417 vchChainCodeRet.clear(); 512 vchChainCodeRet.clear();
418 return Read(string("hdmaster"), vchMasterRet) 513 return ReadWalletRecord(string("hdmaster"), vchMasterRet)
419 && Read(string("hdchaincode"), vchChainCodeRet); 514 && ReadWalletRecord(string("hdchaincode"), vchChainCodeRet);
420 } 515 }
421 516
422 bool WriteHDMaster(const vector<unsigned char>& vchMaster, const vector<unsigned char>& vchChainCode) 517 bool WriteHDMaster(const vector<unsigned char>& vchMaster, const vector<unsigned char>& vchChainCode)
423 { 518 {
424 return Write(string("hdmaster"), vchMaster) 519 // hdmaster and hdchaincode are halves of one seed: a wallet with one but
425 && Write(string("hdchaincode"), vchChainCode); 520 // not the other is an incomplete HD wallet (-walletstoragecheck flags
521 // exactly that). Under the SQLite runtime write them in one transaction
522 // so a failure or crash between the two cannot leave that half-state.
523 // Berkeley DB keeps its existing per-write behaviour -- the bracket is a
524 // no-op there.
525 if (!WalletSQLiteRuntimeBeginTxn())
526 return false;
527 if (!WriteWalletRecord(string("hdmaster"), vchMaster) ||
528 !WriteWalletRecord(string("hdchaincode"), vchChainCode))
529 {
530 WalletSQLiteRuntimeRollbackTxn();
531 return false;
532 }
533 return WalletSQLiteRuntimeCommitTxn();
426 } 534 }
427 535
428 bool WriteWalletMinVersion(int nVersion) 536 bool WriteWalletMinVersion(int nVersion)
429 { 537 {
430 return Write(string("walletminversion"), nVersion); 538 return WriteWalletRecord(string("walletminversion"), nVersion);
431 } 539 }
432 540
433 bool WriteMasterKey(unsigned int nID, const CWalletMasterKey& kMasterKey) 541 bool WriteMasterKey(unsigned int nID, const CWalletMasterKey& kMasterKey)
434 { 542 {
435 return Write(make_pair(string("mkey"), nID), kMasterKey, true); 543 return WriteWalletRecord(make_pair(string("mkey"), nID), kMasterKey, true);
436 } 544 }
437 545
438 bool WriteCryptedKey(const vector<unsigned char>& vchPubKey, 546 bool WriteCryptedKey(const vector<unsigned char>& vchPubKey,
439 const vector<unsigned char>& vchCryptedSecret) 547 const vector<unsigned char>& vchCryptedSecret)
440 { 548 {
441 return Write(make_pair(string("ckey"), vchPubKey), vchCryptedSecret, false); 549 return WriteWalletRecord(make_pair(string("ckey"), vchPubKey), vchCryptedSecret, false);
442 } 550 }
443 551
444 bool WriteCryptedHDMaster(const vector<unsigned char>& vchCryptedMaster, 552 bool WriteCryptedHDMaster(const vector<unsigned char>& vchCryptedMaster,
445 const vector<unsigned char>& vchCryptedChainCode) 553 const vector<unsigned char>& vchCryptedChainCode)
446 { 554 {
447 return Write(string("cryptedhdmaster"), vchCryptedMaster) 555 return WriteWalletRecord(string("cryptedhdmaster"), vchCryptedMaster)
448 && Write(string("cryptedhdchaincode"), vchCryptedChainCode); 556 && WriteWalletRecord(string("cryptedhdchaincode"), vchCryptedChainCode);
449 } 557 }
450 558
451 bool WriteHDNext(unsigned int nNext) 559 bool WriteHDNext(unsigned int nNext)
452 { 560 {
453 return Write(string("hdnext"), nNext); 561 return WriteWalletRecord(string("hdnext"), nNext);
454 } 562 }
455 563
456 bool WriteHDSchema(int nSchema) 564 bool WriteHDSchema(int nSchema)
457 { 565 {
458 return Write(string("hdschema"), nSchema); 566 return WriteWalletRecord(string("hdschema"), nSchema);
459 } 567 }
460 568
461 bool WriteHDCoinType(unsigned int nCoinType) 569 bool WriteHDCoinType(unsigned int nCoinType)
462 { 570 {
463 return Write(string("hdcointype"), nCoinType); 571 return WriteWalletRecord(string("hdcointype"), nCoinType);
464 } 572 }
465 573
466 bool WriteHDReceiveNext(unsigned int nNext) 574 bool WriteHDReceiveNext(unsigned int nNext)
467 { 575 {
468 return Write(string("hdreceivenext"), nNext); 576 return WriteWalletRecord(string("hdreceivenext"), nNext);
469 } 577 }
470 578
471 bool WriteHDChangeNext(unsigned int nNext) 579 bool WriteHDChangeNext(unsigned int nNext)
472 { 580 {
473 return Write(string("hdchangenext"), nNext); 581 return WriteWalletRecord(string("hdchangenext"), nNext);
474 } 582 }
475 583
476 // Keys generated ahead of time and not yet handed out. The private key of 584 // Keys generated ahead of time and not yet handed out. The private key of
@@ -478,30 +586,43 @@ public:
478 // record only says that this one is still unspoken for. 586 // record only says that this one is still unspoken for.
479 bool WritePool(int64 nIndex, const vector<unsigned char>& vchPubKey) 587 bool WritePool(int64 nIndex, const vector<unsigned char>& vchPubKey)
480 { 588 {
481 return Write(make_pair(string("pool"), nIndex), vchPubKey); 589 return WriteWalletRecord(make_pair(string("pool"), nIndex), vchPubKey);
482 } 590 }
483 591
484 bool ErasePool(int64 nIndex) 592 bool ErasePool(int64 nIndex)
485 { 593 {
486 return Erase(make_pair(string("pool"), nIndex)); 594 return EraseWalletRecord(make_pair(string("pool"), nIndex));
487 } 595 }
488 596
489 template<typename T> 597 template<typename T>
490 bool ReadSetting(const string& strKey, T& value) 598 bool ReadSetting(const string& strKey, T& value)
491 { 599 {
492 return Read(make_pair(string("setting"), strKey), value); 600 return ReadWalletRecord(make_pair(string("setting"), strKey), value);
493 } 601 }
494 602
495 template<typename T> 603 template<typename T>
496 bool WriteSetting(const string& strKey, const T& value) 604 bool WriteSetting(const string& strKey, const T& value)
497 { 605 {
498 return Write(make_pair(string("setting"), strKey), value); 606 return WriteWalletRecord(make_pair(string("setting"), strKey), value);
499 } 607 }
500 608
501 bool LoadWallet(vector<unsigned char>& vchDefaultKeyRet); 609 bool LoadWallet(vector<unsigned char>& vchDefaultKeyRet);
502}; 610};
503 611
612class CWalletRecordVisitor
613{
614public:
615 virtual ~CWalletRecordVisitor() { }
616 virtual bool VisitWalletRecord(const CDataStream& ssKey,
617 const CDataStream& ssValue,
618 string& strErrorRet) = 0;
619};
620
621bool ScanWalletRecords(CWalletRecordVisitor& visitor, string& strErrorRet);
622
504bool LoadWallet(); 623bool LoadWallet();
624bool LoadWalletFromSQLite(const string& strPath);
625bool LoadWalletFromSQLiteRuntime(const string& strPath);
505 626
506// Writes a wallet.dat that opens on its own, anywhere -- see the definition in 627// Writes a wallet.dat that opens on its own, anywhere -- see the definition in
507// db.cpp for why a plain file copy does not. Still a point-in-time snapshot: 628// db.cpp for why a plain file copy does not. Still a point-in-time snapshot:
diff --git a/src/gui.cpp b/src/gui.cpp
index 8dcb854..ff6059a 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -11,10 +11,16 @@
11#include <ctime> 11#include <ctime>
12#include <cstdio> 12#include <cstdio>
13#include <sstream> 13#include <sstream>
14#ifdef _WIN32
15#include <shellapi.h> // ShellExecuteA, to open the website in the browser
16#else
17#include <unistd.h>
18#endif
14#include <iomanip> 19#include <iomanip>
15#include <atomic> 20#include <atomic>
16#include <mutex> 21#include <mutex>
17#include <thread> 22#include <thread>
23#include <system_error>
18#include "bip32.h" 24#include "bip32.h"
19#include "walletcmd.h" 25#include "walletcmd.h"
20#include "font_roboto.h" 26#include "font_roboto.h"
@@ -172,7 +178,11 @@ static bool PathIsInsideDataDir(const std::string& strPath)
172 178
173static void SetDefaultBackupPath() 179static void SetDefaultBackupPath()
174{ 180{
175 std::string path = DefaultBackupDir() + "/wallet-backup-" + BackupTimeSuffix() + ".dat"; 181 // Match the extension to the live backend: BackupWallet copies wallet.sqlite
182 // under the SQLite backend and wallet.dat under Berkeley DB, so a ".dat"
183 // default name would put SQLite contents in a misleadingly-named file.
184 const char* pszExt = WalletSQLiteRuntimeActive() ? ".sqlite" : ".dat";
185 std::string path = DefaultBackupDir() + "/wallet-backup-" + BackupTimeSuffix() + pszExt;
176 strncpy(g_backupPath, path.c_str(), sizeof(g_backupPath)-1); 186 strncpy(g_backupPath, path.c_str(), sizeof(g_backupPath)-1);
177 g_backupPath[sizeof(g_backupPath)-1] = '\0'; 187 g_backupPath[sizeof(g_backupPath)-1] = '\0';
178} 188}
@@ -910,8 +920,16 @@ static void DrawOptionsDialog()
910 920
911 if (nMineMode == MINE_OPERATOR) { 921 if (nMineMode == MINE_OPERATOR) {
912 if (!gPoolServerRunning) { 922 if (!gPoolServerRunning) {
913 gPoolServerRunning = true; 923 // Claim the server is up only once it is. The flag used to
914 _beginthread(ThreadRPCServer, 0, NULL); 924 // be set first and the result never looked at, so a refused
925 // spawn left an operator with a pool that reports itself
926 // running, no RPC server for miners to reach, and nothing
927 // anywhere saying why. The flag also latches: with it set,
928 // this branch is never retried.
929 if (_beginthread(ThreadRPCServer, 0, NULL) == (uintptr_t)-1)
930 printf("Error: _beginthread(ThreadRPCServer) failed -- pool server not started\n");
931 else
932 gPoolServerRunning = true;
915 } else if (!gPoolRunning) { 933 } else if (!gPoolRunning) {
916 gPoolRunning = true; 934 gPoolRunning = true;
917 } 935 }
@@ -1124,11 +1142,28 @@ static void DrawRestorePhraseDialog()
1124 } 1142 }
1125 g_restoreDerived.store(0); 1143 g_restoreDerived.store(0);
1126 g_restoreRecovered.store(0); 1144 g_restoreRecovered.store(0);
1145 // Set before the spawn, not after: RestoreThread clears this
1146 // flag itself when it finishes, and a short restore can beat
1147 // us back here and have its `false` overwritten, leaving the
1148 // dialog disabled forever.
1127 g_restoreRunning.store(true); 1149 g_restoreRunning.store(true);
1128 // On its own thread: a restore walks the whole chain once per 1150 // On its own thread: a restore walks the whole chain once per
1129 // batch and would otherwise freeze the window for as long as it 1151 // batch and would otherwise freeze the window for as long as it
1130 // takes. 1152 // takes. A refused spawn is one restore that does not happen,
1131 std::thread(RestoreThread, std::string(g_restoreInput)).detach(); 1153 // not a reason to take the window down with an uncaught
1154 // std::system_error -- and the flag has to come back off, or
1155 // the dialog sits greyed out reporting a search nobody is
1156 // running.
1157 try
1158 {
1159 std::thread(RestoreThread, std::string(g_restoreInput)).detach();
1160 }
1161 catch (const std::system_error& e)
1162 {
1163 g_restoreRunning.store(false);
1164 std::lock_guard<std::mutex> lock(g_restoreResultMutex);
1165 g_restoreResult = std::string("Could not start the search: ") + e.what();
1166 }
1132 } 1167 }
1133 } 1168 }
1134 if (fBusy) 1169 if (fBusy)
@@ -1254,11 +1289,11 @@ static void DrawWalletSafetyDialog()
1254 } 1289 }
1255 ImGui::Text("Phrase-backed spendable balance: %s BTF", 1290 ImGui::Text("Phrase-backed spendable balance: %s BTF",
1256 FmtMoney(g_recoveryAudit.nRecoverableCredit).c_str()); 1291 FmtMoney(g_recoveryAudit.nRecoverableCredit).c_str());
1257 ImGui::Text("Wallet.dat-only spendable balance: %s BTF", 1292 ImGui::Text("Not phrase-backed spendable balance: %s BTF",
1258 FmtMoney(g_recoveryAudit.nLegacyCredit).c_str()); 1293 FmtMoney(g_recoveryAudit.nLegacyCredit).c_str());
1259 ImGui::Text("Phrase-backed immature mining rewards: %s BTF", 1294 ImGui::Text("Phrase-backed immature mining rewards: %s BTF",
1260 FmtMoney(g_recoveryAudit.nRecoverableImmatureCredit).c_str()); 1295 FmtMoney(g_recoveryAudit.nRecoverableImmatureCredit).c_str());
1261 ImGui::Text("Wallet.dat-only immature mining rewards: %s BTF", 1296 ImGui::Text("Not phrase-backed immature mining rewards: %s BTF",
1262 FmtMoney(g_recoveryAudit.nLegacyImmatureCredit).c_str()); 1297 FmtMoney(g_recoveryAudit.nLegacyImmatureCredit).c_str());
1263 if (!g_recoveryAudit.fDeriveComplete) 1298 if (!g_recoveryAudit.fDeriveComplete)
1264 { 1299 {
@@ -1272,8 +1307,8 @@ static void DrawWalletSafetyDialog()
1272 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f)); 1307 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f));
1273 ImGui::TextWrapped( 1308 ImGui::TextWrapped(
1274 "Some coins are on keys the phrase does not reproduce. " 1309 "Some coins are on keys the phrase does not reproduce. "
1275 "Keep wallet.dat backups until that balance has been moved to a " 1310 "Keep a file backup of this wallet until that balance has been "
1276 "phrase-backed address."); 1311 "moved to a phrase-backed address.");
1277 ImGui::PopStyleColor(); 1312 ImGui::PopStyleColor();
1278 } 1313 }
1279 else if (HaveHDSeed() && 1314 else if (HaveHDSeed() &&
@@ -1369,16 +1404,38 @@ static void DrawDiagnosticsDialog()
1369 ImGui::End(); 1404 ImGui::End();
1370} 1405}
1371 1406
1407static void OpenURL(const char* pszUrl)
1408{
1409#ifdef _WIN32
1410 ShellExecuteA(NULL, "open", pszUrl, NULL, NULL, SW_SHOWNORMAL);
1411#else
1412 std::string cmd = std::string("xdg-open '") + pszUrl + "' >/dev/null 2>&1 &";
1413 int r = system(cmd.c_str());
1414 (void)r;
1415#endif
1416}
1417
1372static void DrawAboutDialog() 1418static void DrawAboutDialog()
1373{ 1419{
1374 if (!g_showAbout) return; 1420 if (!g_showAbout) return;
1375 ImGui::SetNextWindowSize(ImVec2(380.0f, 170.0f), ImGuiCond_Always); 1421 ImGui::SetNextWindowSize(ImVec2(380.0f, 205.0f), ImGuiCond_Always);
1376 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), 1422 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1377 ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 1423 ImGuiCond_Always, ImVec2(0.5f, 0.5f));
1378 if (ImGui::Begin("About Bitflash", &g_showAbout, 1424 if (ImGui::Begin("About Bitflash", &g_showAbout,
1379 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) 1425 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse))
1380 { 1426 {
1381 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Bitflash BTF v1.1.0"); 1427 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f),
1428 "Bitflash BTF v" BITFLASH_VERSION_STRING);
1429 ImGui::Spacing();
1430 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.40f, 0.70f, 1.0f, 1.0f));
1431 ImGui::TextUnformatted("https://bitflash.network");
1432 ImGui::PopStyleColor();
1433 if (ImGui::IsItemHovered())
1434 {
1435 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
1436 if (ImGui::IsMouseClicked(0))
1437 OpenURL("https://bitflash.network");
1438 }
1382 ImGui::Spacing(); 1439 ImGui::Spacing();
1383 ImGui::TextWrapped( 1440 ImGui::TextWrapped(
1384 "CPU-only cryptocurrency. RandomX proof of work. " 1441 "CPU-only cryptocurrency. RandomX proof of work. "
@@ -1391,6 +1448,184 @@ static void DrawAboutDialog()
1391 ImGui::End(); 1448 ImGui::End();
1392} 1449}
1393 1450
1451// First-run offer to move a Berkeley DB wallet to the SQLite backend. Only the
1452// button actions touch anything: "Convert" exports wallet.dat to wallet.sqlite
1453// and records the backend marker (wallet.dat is left untouched, so a restart
1454// picks up SQLite while the old file stays as a fallback); "Keep Berkeley DB"
1455// just records that choice so the offer does not return; "Decide later" leaves
1456// no marker and asks again next start.
1457static bool g_showBackendWizard = false;
1458static std::string g_wizardStatus;
1459static bool g_wizardConverted = false;
1460static int g_restartArgc = 0;
1461static char** g_restartArgv = NULL;
1462
1463#ifdef _WIN32
1464// Quote one argument so CommandLineToArgvW parses it back to exactly this
1465// string: only backslashes that immediately precede a double quote (including
1466// the closing one) are doubled, so ordinary Windows paths are not mangled.
1467static std::string QuoteArgWin(const std::string& a)
1468{
1469 if (!a.empty() && a.find_first_of(" \t\"") == std::string::npos)
1470 return a;
1471 std::string r = "\"";
1472 size_t bs = 0;
1473 for (size_t i = 0; i < a.size(); i++)
1474 {
1475 if (a[i] == '\\') { bs++; continue; }
1476 if (a[i] == '"') { r.append(bs * 2 + 1, '\\'); r += '"'; bs = 0; continue; }
1477 r.append(bs, '\\'); bs = 0; r += a[i];
1478 }
1479 r.append(bs * 2, '\\');
1480 r += "\"";
1481 return r;
1482}
1483#endif
1484
1485// Relaunch Bitflash with the same arguments and datadir, then ask this instance
1486// to exit -- but only once the relaunch has actually started, so a failure never
1487// leaves the user with no node running. The new instance waits (-restartwait)
1488// before it opens the network port and the block index, so this one is gone
1489// first and they never fight over the same files.
1490static void RestartApplication()
1491{
1492 bool fLaunched = false;
1493#ifdef _WIN32
1494 char exePath[MAX_PATH];
1495 if (GetModuleFileNameA(NULL, exePath, MAX_PATH) == 0)
1496 return;
1497 std::string strCmd = QuoteArgWin(exePath);
1498 bool fHasDataDir = false;
1499 for (int i = 1; i < g_restartArgc; i++)
1500 {
1501 std::string a = g_restartArgv[i] ? g_restartArgv[i] : "";
1502 if (a.find("-datadir=") == 0 || a.find("/datadir=") == 0)
1503 fHasDataDir = true;
1504 strCmd += " " + QuoteArgWin(a);
1505 }
1506 if (!fHasDataDir)
1507 strCmd += " " + QuoteArgWin("-datadir=" + GetAppDir());
1508 strCmd += " -restartwait";
1509
1510 std::vector<char> cmdBuf(strCmd.begin(), strCmd.end());
1511 cmdBuf.push_back('\0');
1512 STARTUPINFOA si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si);
1513 PROCESS_INFORMATION pi; memset(&pi, 0, sizeof(pi));
1514 if (CreateProcessA(NULL, &cmdBuf[0], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
1515 {
1516 CloseHandle(pi.hProcess);
1517 CloseHandle(pi.hThread);
1518 fLaunched = true;
1519 }
1520#else
1521 pid_t pid = fork();
1522 if (pid == 0)
1523 {
1524 // Child: wait for the parent to release the port and files, then re-exec.
1525 // /proc/self/exe is the reliable absolute path; argv[0] may be relative
1526 // or a bare name that execv() cannot resolve.
1527 sleep(2);
1528 char exePath[4096];
1529 ssize_t len = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
1530 if (len > 0)
1531 {
1532 exePath[len] = '\0';
1533 execv(exePath, g_restartArgv);
1534 }
1535 if (g_restartArgv)
1536 execv(g_restartArgv[0], g_restartArgv);
1537 _exit(127);
1538 }
1539 fLaunched = (pid > 0);
1540#endif
1541
1542 if (fLaunched)
1543 {
1544 fShutdown = true;
1545 if (glfwGetCurrentContext())
1546 glfwSetWindowShouldClose(glfwGetCurrentContext(), true);
1547 }
1548 else
1549 g_wizardStatus = "Could not relaunch automatically -- please restart "
1550 "Bitflash yourself to switch to the SQLite wallet.";
1551}
1552
1553static void DrawBackendWizardDialog()
1554{
1555 if (!g_showBackendWizard) return;
1556 ImGui::SetNextWindowSize(ImVec2(560.0f, 0.0f), ImGuiCond_Always);
1557 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1558 ImGuiCond_Always, ImVec2(0.5f, 0.5f));
1559 if (ImGui::Begin("Wallet Storage", &g_showBackendWizard,
1560 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse))
1561 {
1562 if (g_wizardConverted)
1563 {
1564 ImGui::TextColored(ImVec4(0.35f, 1.0f, 0.45f, 1.0f), "Converted to SQLite.");
1565 ImGui::TextWrapped("%s", g_wizardStatus.c_str());
1566 ImGui::Spacing();
1567 ImGui::TextWrapped(
1568 "The switch takes effect when Bitflash restarts. Until then it is "
1569 "still running on the old Berkeley DB wallet. Your wallet.dat is "
1570 "kept untouched as a fallback.");
1571 ImGui::Spacing();
1572 if (ImGui::Button("Restart Now", ImVec2(140.0f, 0.0f)))
1573 RestartApplication();
1574 ImGui::SameLine();
1575 if (ImGui::Button("Later", ImVec2(120.0f, 0.0f)))
1576 g_showBackendWizard = false;
1577 ImGui::Spacing();
1578 ImGui::TextDisabled(
1579 "If you keep mining before restarting, new coins go to the old "
1580 "wallet until you switch.");
1581 }
1582 else
1583 {
1584 ImGui::TextWrapped(
1585 "Bitflash can store your wallet in a new SQLite format: a single "
1586 "file that is easier to back up and does not depend on the "
1587 "database/ directory beside it.");
1588 ImGui::Spacing();
1589 ImGui::TextWrapped(
1590 "Your current Berkeley DB wallet (wallet.dat) is kept exactly as it "
1591 "is -- nothing is deleted, and you can go back to it at any time. "
1592 "The change takes effect after you restart.");
1593 ImGui::Spacing();
1594 if (ImGui::Button("Convert to SQLite", ImVec2(180.0f, 0.0f)))
1595 {
1596 int nCopied = 0;
1597 std::string strError;
1598 if (DoConvertWalletToSQLite(nCopied, strError))
1599 {
1600 g_wizardStatus =
1601 strprintf("%d record(s) copied to wallet.sqlite.", nCopied);
1602 g_wizardConverted = true;
1603 }
1604 else
1605 g_wizardStatus = "Conversion failed: " + strError;
1606 }
1607 ImGui::SameLine();
1608 if (ImGui::Button("Keep Berkeley DB", ImVec2(160.0f, 0.0f)))
1609 {
1610 WriteWalletBackendMarker("bdb");
1611 g_showBackendWizard = false;
1612 }
1613 ImGui::SameLine();
1614 if (ImGui::Button("Decide later", ImVec2(120.0f, 0.0f)))
1615 g_showBackendWizard = false;
1616
1617 if (!g_wizardStatus.empty())
1618 {
1619 ImGui::Spacing();
1620 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.45f, 0.35f, 1.0f));
1621 ImGui::TextWrapped("%s", g_wizardStatus.c_str());
1622 ImGui::PopStyleColor();
1623 }
1624 }
1625 }
1626 ImGui::End();
1627}
1628
1394// --------------------------------------------------------------------------- 1629// ---------------------------------------------------------------------------
1395// Entry point 1630// Entry point
1396// --------------------------------------------------------------------------- 1631// ---------------------------------------------------------------------------
@@ -1457,6 +1692,21 @@ int RunGUI(int argc, char* argv[])
1457 g_mineRadio = nMineMode; 1692 g_mineRadio = nMineMode;
1458 strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1); 1693 strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1);
1459 1694
1695 // Kept for the wizard's "Restart Now": relaunch with the same command line.
1696 g_restartArgc = argc;
1697 g_restartArgv = argv;
1698
1699 // First run on a Berkeley DB wallet with no recorded choice yet: offer to
1700 // convert to SQLite. Never when already on SQLite, once wallet.sqlite exists,
1701 // or once a choice has been recorded in the backend marker.
1702 if (!WalletSQLiteRuntimeActive() &&
1703 FileExists((GetAppDir() + "/wallet.dat").c_str()) &&
1704 !FileExists((GetAppDir() + "/wallet.sqlite").c_str()) &&
1705 ReadWalletBackendMarker().empty())
1706 {
1707 g_showBackendWizard = true;
1708 }
1709
1460 int frame = 0; 1710 int frame = 0;
1461 while (!glfwWindowShouldClose(window) && !fShutdown) 1711 while (!glfwWindowShouldClose(window) && !fShutdown)
1462 { 1712 {
@@ -1480,6 +1730,7 @@ int RunGUI(int argc, char* argv[])
1480 DrawRestorePhraseDialog(); 1730 DrawRestorePhraseDialog();
1481 DrawDiagnosticsDialog(); 1731 DrawDiagnosticsDialog();
1482 DrawAboutDialog(); 1732 DrawAboutDialog();
1733 DrawBackendWizardDialog();
1483 DrawStatusBar(); 1734 DrawStatusBar();
1484 1735
1485 ImGui::Render(); 1736 ImGui::Render();
diff --git a/src/headers_core.h b/src/headers_core.h
index 6a60b52..2824126 100644
--- a/src/headers_core.h
+++ b/src/headers_core.h
@@ -1,6 +1,11 @@
1// Core node headers -- no GUI framework dependency. 1// Core node headers -- no GUI framework dependency.
2// Included by gui.cpp and all non-GUI source files. 2// Included by gui.cpp and all non-GUI source files.
3 3
4// The one place the human-readable release version lives -- bump it at release.
5// It is what the About dialog shows. Protocol and transaction versions elsewhere
6// (nVersion fields) are unrelated and must not be changed for a release.
7#define BITFLASH_VERSION_STRING "1.2.20"
8
4#ifdef _MSC_VER 9#ifdef _MSC_VER
5#pragma warning(disable:4786) 10#pragma warning(disable:4786)
6#endif 11#endif
diff --git a/src/main.cpp b/src/main.cpp
index 7b3e555..7398064 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -14,6 +14,7 @@
14#include <atomic> 14#include <atomic>
15#include <mutex> 15#include <mutex>
16#include <thread> 16#include <thread>
17#include <system_error>
17#include "bip32.h" 18#include "bip32.h"
18#include "btfaddr.h" 19#include "btfaddr.h"
19#include "btftunnel.h" 20#include "btftunnel.h"
@@ -64,6 +65,137 @@ CBlockIndex* pindexBest = NULL;
64map<uint256, CBlock*> mapOrphanBlocks; 65map<uint256, CBlock*> mapOrphanBlocks;
65multimap<uint256, CBlock*> mapOrphanBlocksByPrev; 66multimap<uint256, CBlock*> mapOrphanBlocksByPrev;
66 67
68// Block bodies we have sent a getdata for and not yet received, so the
69// failover pass in SendMessages can tell "requested and being served" from
70// "requested and gone quiet" and re-issue the request to a different peer
71// (#33). Keyed by block hash; setAsked is the peers already tried since the
72// round last restarted, so the retry rotates instead of hammering the
73// announcer that is not serving. Guarded by cs_main, which both the message
74// handlers and SendMessages already hold.
75//
76// Capped small on purpose. At the tip there is one block in flight; during
77// initial download there are legitimately hundreds outstanding to one peer,
78// and rotating those would duplicate the whole download to every other peer.
79//
80// The cap is a shared resource and nothing on the path from inv to insertion
81// can tell a real block hash from 32 random bytes -- AlreadyHave only asks
82// whether we already hold the block, and there is no header to check work
83// against. So the table does not refuse a new hash when it is full; it makes
84// room. See MakeRoomInBlockFetch for the rule, which is what stops one peer
85// filling all eight slots and switching failover off for everybody.
86//
87// The initialisers matter: the insertion path uses operator[] and then tests
88// nTimeFirstAsked == 0 to detect a fresh entry. That worked on
89// value-initialisation alone, but only for as long as nobody gave this struct
90// a constructor.
91struct CBlockFetch
92{
93 int64 nTimeFirstAsked = 0; // when the first getdata for this block went out
94 int64 nTimeAsked = 0; // when the latest getdata went out
95 CAddress addrAnnounced; // peer whose announcement put this hash here
96 set<CAddress> setAsked; // peers tried since the round last restarted
97};
98map<uint256, CBlockFetch> mapBlockFetch;
99static const int BLOCK_FETCH_TRACK_MAX = 8;
100// A block nobody has served for this long is not a fetch problem: the hash may
101// have been announced and never existed. Drop the entry rather than getdata
102// forever; a re-announcement recreates it.
103static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60;
104
105// Failover firing at all is worth saying out loud: it means peers are
106// announcing blocks they then do not serve, which is the pathology of #33 and
107// the thing nobody could otherwise see. Naming the peer on every re-request is
108// a different matter. A peer can drive that line as often as it likes by
109// announcing hashes it will not serve, and what it prints is a peer address,
110// which is the one thing the .btf transport otherwise takes trouble not to
111// spread around. So the per-peer detail sits behind the net category and the
112// unconditional line is a count over a window no peer can shorten: the first
113// failover after a quiet spell prints at once, because the change from "blocks
114// arrive" to "blocks are announced and not served" is the event worth seeing,
115// and everything after it is summarised once a minute.
116static const int BLOCK_FETCH_REPORT_SECS = 60;
117static int64 nBlockFetchWindowStart = 0;
118static int nBlockFetchFailovers = 0;
119
120// How stale our own best block may be before we stop calling this the tip.
121// Blocks target two minutes, so this is thirty intervals: far longer than any
122// stall worth failing over from -- the one in #33 ran sixteen minutes -- and
123// far shorter than catching up, which starts hours or days back.
124static const int BLOCK_FETCH_TIP_AGE_SECS = 60 * 60;
125
126// Whether this node is tracking the tip rather than still catching up.
127//
128// Measured off the age of our own best block, not off any height a peer told
129// us. nStartingHeight is optional in the version message and every node
130// released so far sends the short form, so it is -1 for essentially every
131// peer; and where it is filled in later it is filled in from the height of a
132// block that peer served us, which during catch-up tracks our own height and
133// would report "at the tip" for the whole download. Block timestamps are the
134// one clock here that peers cannot move: CheckBlock refuses anything more than
135// two hours ahead and AcceptBlock refuses anything at or before the previous
136// median, so the only way to make this read "at the tip" falsely is to mine,
137// and all that buys is the 15s spacing this code had unconditionally anyway.
138bool IsBlockFetchNearTip()
139{
140 return pindexBest && (int64)pindexBest->nTime > GetTime() - BLOCK_FETCH_TIP_AGE_SECS;
141}
142
143// Drop one entry so a hash announced by addrNew can be tracked.
144//
145// Refusing the insertion instead was a remote off-switch for the whole
146// feature. An inv carrying eight hashes that never existed filled every slot
147// -- nothing between inv and here can tell those from real blocks -- and no
148// genuinely new block was tracked again until the give-up timer expired ten
149// minutes later, re-armable for one message per ten minutes from a single
150// connection.
151//
152// So evict instead, by occupancy: a peer may only take a slot from a peer
153// holding strictly more slots than itself, and otherwise recycles one of its
154// own. That is what keeps eviction from becoming the same attack in a new
155// shape. A peer that has filled the table is by definition the heaviest
156// holder, so its own entries are the only ones it can displace, while a peer
157// announcing its first block holds nothing and therefore always evicts from
158// the heaviest -- a genuine announcement is never refused a slot, whatever the
159// attacker holds or however many connections it holds it from. The one case
160// that costs a real fetch is eight distinct peers holding one entry each,
161// where a ninth takes the oldest; that is a busy healthy node, not an attack,
162// and losing the oldest of eight beats refusing the ninth.
163static void MakeRoomInBlockFetch(const CAddress& addrNew)
164{
165 if (mapBlockFetch.empty())
166 return;
167
168 map<CAddress, int> mapHeld;
169 for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi)
170 mapHeld[(*mi).second.addrAnnounced]++;
171
172 int nMine = mapHeld.count(addrNew) ? mapHeld[addrNew] : 0;
173
174 int nMax = 0;
175 CAddress addrVictim;
176 for (map<CAddress, int>::iterator mi = mapHeld.begin(); mi != mapHeld.end(); ++mi)
177 if ((*mi).second > nMax)
178 {
179 nMax = (*mi).second;
180 addrVictim = (*mi).first;
181 }
182
183 // Nobody is holding more than this peer, so it recycles its own oldest
184 // rather than taking a slot off an equal.
185 if (nMax <= nMine)
186 addrVictim = addrNew;
187
188 map<uint256, CBlockFetch>::iterator miVictim = mapBlockFetch.end();
189 for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi)
190 if ((*mi).second.addrAnnounced == addrVictim &&
191 (miVictim == mapBlockFetch.end() ||
192 (*mi).second.nTimeFirstAsked < (*miVictim).second.nTimeFirstAsked))
193 miVictim = mi;
194
195 if (miVictim != mapBlockFetch.end())
196 mapBlockFetch.erase(miVictim);
197}
198
67map<uint256, CDataStream*> mapOrphanTransactions; 199map<uint256, CDataStream*> mapOrphanTransactions;
68multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; 200multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev;
69 201
@@ -1712,7 +1844,7 @@ void CWalletTx::RelayWalletTransaction(CTxDB& txdb)
1712 uint256 hash = GetHash(); 1844 uint256 hash = GetHash();
1713 if (!txdb.ContainsTx(hash)) 1845 if (!txdb.ContainsTx(hash))
1714 { 1846 {
1715 if (LogAcceptsCategory("net")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str()); 1847 if (LogAcceptsCategory("relay")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str());
1716 RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); 1848 RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this);
1717 } 1849 }
1718 } 1850 }
@@ -2678,7 +2810,18 @@ bool LoadBlockIndex(bool fAllowNew)
2678 // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) 2810 // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B)
2679 // vMerkleTree: 4a5e1e 2811 // vMerkleTree: 4a5e1e
2680 2812
2681 // Bitflash genesis block -- own header (fair launch, 2026 stable relaunch) 2813 // Bitflash genesis block -- own header (fair launch)
2814 //
2815 // Do not change pszTimestamp, the scriptPubKey, nTime or nNonce. They
2816 // are the inputs to the genesis hash, and the genesis hash is
2817 // consensus. Changing one on its own trips the assert below, which is
2818 // loud and reverts cleanly; changing one and regenerating
2819 // GENESIS_NONCE and hashGenesisBlock to match -- which is exactly what
2820 // the re-mining branch below prints out and invites -- gives a node
2821 // that starts without complaint, builds its own chain from height 0,
2822 // and rejects every real peer as being on a different chain. That one
2823 // fails silently, and the damage scales with how many people run the
2824 // "fixed" build.
2682 char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; 2825 char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine";
2683 CTransaction txNew; 2826 CTransaction txNew;
2684 txNew.vin.resize(1); 2827 txNew.vin.resize(1);
@@ -2691,7 +2834,14 @@ bool LoadBlockIndex(bool fAllowNew)
2691 block.hashPrevBlock = 0; 2834 block.hashPrevBlock = 0;
2692 block.hashMerkleRoot = block.BuildMerkleTree(); 2835 block.hashMerkleRoot = block.BuildMerkleTree();
2693 block.nVersion = 1; 2836 block.nVersion = 1;
2694 block.nTime = 1753315200; // 2026-07-24 00:00:00 UTC (stable relaunch) 2837 // 1753315200 is 2025-07-24T00:00:00Z. The headline in pszTimestamp
2838 // above reads 2026 and does not match it. This is a known discrepancy
2839 // and not a typo in the constant: the value is only consensus-critical
2840 // as an input to the genesis hash, the block hashes to
2841 // hashGenesisBlock with it, and every node on the network agrees. The
2842 // mismatch is in the launched chain and cannot be corrected -- see the
2843 // warning above before deciding otherwise.
2844 block.nTime = 1753315200;
2695 block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) 2845 block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor)
2696 block.nNonce = GENESIS_NONCE; 2846 block.nNonce = GENESIS_NONCE;
2697 2847
@@ -2718,6 +2868,11 @@ bool LoadBlockIndex(bool fAllowNew)
2718 printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); 2868 printf("powhash = %s\n", block.GetPoWHash().ToString().c_str());
2719 printf("hash = %s\n", block.GetHash().ToString().c_str()); 2869 printf("hash = %s\n", block.GetHash().ToString().c_str());
2720 printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); 2870 printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str());
2871 printf("----------------------------------------\n");
2872 printf("These constants bring up a NEW chain. Do not paste them\n");
2873 printf("into a build for this network to make an edited nTime or\n");
2874 printf("pszTimestamp fit -- a node whose genesis differs starts\n");
2875 printf("cleanly on a chain of its own and rejects every real peer.\n");
2721 printf("========================================\n"); 2876 printf("========================================\n");
2722 } 2877 }
2723 2878
@@ -3263,15 +3418,61 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
3263 vRecv >> *pblock; 3418 vRecv >> *pblock;
3264 3419
3265 //// debug print 3420 //// debug print
3266 if (LogAcceptsCategory("net")) { printf("received block:\n"); pblock->print(); } 3421 if (LogAcceptsCategory("relay")) { printf("received block:\n"); pblock->print(); }
3267 3422
3268 CInv inv(MSG_BLOCK, pblock->GetHash()); 3423 CInv inv(MSG_BLOCK, pblock->GetHash());
3269 pfrom->AddInventoryKnown(inv); 3424 pfrom->AddInventoryKnown(inv);
3270 3425
3426 // GetHash() covers the 80-byte header and nothing else, so the hash
3427 // coming out right does not make this the block we asked for. Every
3428 // peer that saw X announced holds X's header, and can answer our
3429 // getdata with that header bolted onto a transaction list of its own
3430 // invention -- the reply still hashes to X. hashMerkleRoot is the only
3431 // thing binding the header to the body, so check it here, before the
3432 // mapBlockFetch entry is touched and before CheckBlock spends a
3433 // RandomX hash on a body already known to be forged.
3434 //
3435 // There is no honest way to arrive here. Peer traffic rides the .btf
3436 // channel, which is XChaCha20-Poly1305, so a body that arrives at all
3437 // arrived intact: a mismatch is the peer's own construction, not line
3438 // noise. Drop the peer and leave the fetch outstanding so the failover
3439 // sweep asks somebody else for the real X, rather than recording X as
3440 // delivered on the strength of a header anyone could copy.
3441 if (pblock->hashMerkleRoot != pblock->BuildMerkleTree())
3442 {
3443 if (LogAcceptsCategory("net"))
3444 printf("peer %s served a forged body for %s\n",
3445 pfrom->addr.ToString().c_str(), inv.ToString().c_str());
3446 // One line per connection: ProcessMessages keeps draining the
3447 // receive buffer after fDisconnect is set, so a peer that packs
3448 // many forged bodies into one buffer must not get one log line
3449 // for each of them.
3450 if (!pfrom->fDisconnect)
3451 printf("block fetch: a peer answered the request for %s with a body that is not "
3452 "that block -- dropping it\n", inv.hash.ToString().substr(0,14).c_str());
3453 pfrom->fDisconnect = true;
3454 return error("ProcessMessage() : forged block body for %s",
3455 inv.hash.ToString().substr(0,14).c_str());
3456 }
3457
3271 if (ProcessBlock(pfrom, pblock.release())) 3458 if (ProcessBlock(pfrom, pblock.release()))
3272 { 3459 {
3273 mapAlreadyAskedFor.erase(inv); 3460 mapAlreadyAskedFor.erase(inv);
3274 3461
3462 // The body arrived and validated, so the fetch really is over:
3463 // the block is now in mapBlockIndex, or parked in mapOrphanBlocks,
3464 // and either way AlreadyHave answers for it from here on.
3465 //
3466 // This has to stay inside the success branch, next to the erase
3467 // above. When validation rejects a body nothing is stored,
3468 // AlreadyHave stays false, and the mapAskFor entry was already
3469 // consumed when the getdata went out -- so erasing the tracking
3470 // here would leave the block requested from nobody at all until
3471 // some peer happened to re-announce it. That is exactly the stall
3472 // of #33, and erasing before ProcessBlock made it reachable on
3473 // demand.
3474 mapBlockFetch.erase(inv.hash);
3475
3275 // A peer that just handed us a block is demonstrably at least at 3476 // A peer that just handed us a block is demonstrably at least at
3276 // that block's height. Without this, nStartingHeight is a snapshot 3477 // that block's height. Without this, nStartingHeight is a snapshot
3277 // taken once during the version handshake and never touched again, 3478 // taken once during the version handshake and never touched again,
@@ -3396,12 +3597,119 @@ bool SendMessages(CNode* pto)
3396 const CInv& inv = (*pto->mapAskFor.begin()).second; 3597 const CInv& inv = (*pto->mapAskFor.begin()).second;
3397 if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str()); 3598 if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str());
3398 if (!AlreadyHave(txdb, inv)) 3599 if (!AlreadyHave(txdb, inv))
3600 {
3399 vAskFor.push_back(inv); 3601 vAskFor.push_back(inv);
3602 // Blocks get their delivery tracked so the failover pass
3603 // below can chase the ones that go quiet.
3604 if (inv.type == MSG_BLOCK)
3605 {
3606 // Full, and this is a hash we are not already tracking:
3607 // make room rather than drop it on the floor, or one peer
3608 // announcing eight hashes turns failover off for everyone.
3609 if (!mapBlockFetch.count(inv.hash) &&
3610 mapBlockFetch.size() >= BLOCK_FETCH_TRACK_MAX)
3611 MakeRoomInBlockFetch(pto->addr);
3612
3613 if (mapBlockFetch.count(inv.hash) ||
3614 mapBlockFetch.size() < BLOCK_FETCH_TRACK_MAX)
3615 {
3616 CBlockFetch& fetch = mapBlockFetch[inv.hash];
3617 if (fetch.nTimeFirstAsked == 0)
3618 {
3619 fetch.nTimeFirstAsked = GetTime();
3620 // Who is answerable for this hash existing. Set
3621 // once, on creation: the failover sweep asks other
3622 // peers later, and none of them chose to announce
3623 // it, so none of them should be charged for it.
3624 fetch.addrAnnounced = pto->addr;
3625 }
3626 fetch.nTimeAsked = GetTime();
3627 fetch.setAsked.insert(pto->addr);
3628 }
3629 }
3630 }
3400 pto->mapAskFor.erase(pto->mapAskFor.begin()); 3631 pto->mapAskFor.erase(pto->mapAskFor.begin());
3401 } 3632 }
3402 if (!vAskFor.empty()) 3633 if (!vAskFor.empty())
3403 pto->PushMessage("getdata", vAskFor); 3634 pto->PushMessage("getdata", vAskFor);
3404 3635
3636
3637 //
3638 // Message: getdata (block fetch failover)
3639 //
3640 // A requested block that has not arrived in BLOCK_FETCH_RETRY_SECS is
3641 // not still in transit -- a body is at most 1 MB and somebody
3642 // demonstrably has it -- so ask somebody else instead of waiting on
3643 // whoever was asked first (#33). Each peer the message handler visits
3644 // takes at most one turn per block per round; once every connected
3645 // peer has had its turn and a further interval passes with nobody
3646 // new to ask, the round restarts. A peer that declared itself a
3647 // client cannot serve block bodies and is no use as a target.
3648 if (!pto->fClient)
3649 {
3650 vector<CInv> vFailover;
3651 int64 nNowSecs = GetTime();
3652 for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end();)
3653 {
3654 CBlockFetch& fetch = (*mi).second;
3655 CInv inv(MSG_BLOCK, (*mi).first);
3656 if (AlreadyHave(txdb, inv))
3657 {
3658 // Arrived by some other road -- our own miner, an orphan
3659 // parked in mapOrphanBlocks. Nothing left to chase.
3660 mapBlockFetch.erase(mi++);
3661 continue;
3662 }
3663 if (nNowSecs - fetch.nTimeFirstAsked > BLOCK_FETCH_GIVEUP_SECS)
3664 {
3665 printf("block fetch: giving up on %s -- no peer served it in %d s\n",
3666 inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeFirstAsked));
3667 mapBlockFetch.erase(mi++);
3668 continue;
3669 }
3670 if (nNowSecs - fetch.nTimeAsked >= BLOCK_FETCH_RETRY_SECS)
3671 {
3672 // The handler loop visits every peer within ~100ms, so a
3673 // request stale by two full intervals means nobody outside
3674 // setAsked was there to ask. Start the round over.
3675 if (nNowSecs - fetch.nTimeAsked >= 2 * BLOCK_FETCH_RETRY_SECS)
3676 fetch.setAsked.clear();
3677 if (!fetch.setAsked.count(pto->addr))
3678 {
3679 // Which block, how long, and which peer we are trying
3680 // next. Behind the category because it names a peer
3681 // and a peer decides how often it is written.
3682 if (LogAcceptsCategory("net"))
3683 printf("block fetch failover: %s unanswered for %d s, re-requesting from %s\n",
3684 inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeAsked),
3685 pto->addr.ToString().c_str());
3686
3687 // The unconditional half: no addresses, and at most
3688 // one line per BLOCK_FETCH_REPORT_SECS however hard a
3689 // peer tries. See the counters for the reasoning.
3690 nBlockFetchFailovers++;
3691 if (nNowSecs - nBlockFetchWindowStart >= BLOCK_FETCH_REPORT_SECS)
3692 {
3693 printf("block fetch: %d block request%s went unanswered and %s been "
3694 "re-asked of other peers (%d tracked)\n",
3695 nBlockFetchFailovers, nBlockFetchFailovers == 1 ? "" : "s",
3696 nBlockFetchFailovers == 1 ? "has" : "have",
3697 (int)mapBlockFetch.size());
3698 nBlockFetchWindowStart = nNowSecs;
3699 nBlockFetchFailovers = 0;
3700 }
3701
3702 vFailover.push_back(inv);
3703 fetch.nTimeAsked = nNowSecs;
3704 fetch.setAsked.insert(pto->addr);
3705 }
3706 }
3707 ++mi;
3708 }
3709 if (!vFailover.empty())
3710 pto->PushMessage("getdata", vFailover);
3711 }
3712
3405 } 3713 }
3406 return true; 3714 return true;
3407} 3715}
@@ -3662,6 +3970,14 @@ static void ParticipantHashWorker(ParticipantJobShared* sh, int idx, int nWorker
3662 RandomXDestroyMinerVM(vm); 3970 RandomXDestroyMinerVM(vm);
3663} 3971}
3664 3972
3973// Run one participant session: resolve the pool, open the tunnel, subscribe,
3974// hash, submit shares. Returns true only if the pool actually handed out work
3975// -- at least one well-formed mining.notify arrived. Everything else is false,
3976// including a tunnel that opened and was dropped before any job.
3977//
3978// The caller's reconnect backoff keys off this return value, so any path added
3979// here that leaves the session without work MUST return false. "The session
3980// ended tidily" is not the question being asked.
3665static bool PoolParticipantMiner() 3981static bool PoolParticipantMiner()
3666{ 3982{
3667 // ------------------------------------------------------------------ 3983 // ------------------------------------------------------------------
@@ -3739,6 +4055,33 @@ static bool PoolParticipantMiner()
3739 } 4055 }
3740 LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); 4056 LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str());
3741 4057
4058 // The descriptor names ONE meeting node, but the pool parks a
4059 // registration at every relay it knows (one accept thread per relay,
4060 // rpc.cpp:AcceptOneFn), so any seed relay can pair us. Insisting on the
4061 // advertised entry forever turns one bad path into a miner that idles
4062 // indefinitely while a working path exists (issue #17); after a few
4063 // consecutive tunnel failures, rotate the seed relays into the attempts
4064 // too. Only ONE participant session runs (StartMinerThreads), so these
4065 // statics are single-threaded.
4066 static const int TUNNEL_FAILOVER_AFTER = 3;
4067 static int nTunnelFails = 0; // consecutive tunnel failures, this pool
4068 static size_t iFailoverRelay = 0; // rotation cursor over vBtfMeetingRelays
4069 static std::string strFailPool; // pool the counters belong to
4070 if (strFailPool != strParticipantPool) {
4071 strFailPool = strParticipantPool;
4072 nTunnelFails = 0;
4073 iFailoverRelay = 0;
4074 }
4075 if (nTunnelFails >= TUNNEL_FAILOVER_AFTER && !vBtfMeetingRelays.empty()) {
4076 std::string alt = vBtfMeetingRelays[iFailoverRelay++ % vBtfMeetingRelays.size()];
4077 if (alt != meetingHostPort) {
4078 LogPrint("worker", "[worker] advertised meeting node %s failed %d"
4079 " times in a row -- trying seed relay %s instead\n",
4080 meetingHostPort.c_str(), nTunnelFails, alt.c_str());
4081 meetingHostPort = alt;
4082 }
4083 }
4084
3742 size_t colon = meetingHostPort.rfind(':'); 4085 size_t colon = meetingHostPort.rfind(':');
3743 if (colon == std::string::npos) { 4086 if (colon == std::string::npos) {
3744 LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); 4087 LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str());
@@ -3758,15 +4101,18 @@ static bool PoolParticipantMiner()
3758 // Step 2b: open encrypted tunnel to pool 4101 // Step 2b: open encrypted tunnel to pool
3759 // ------------------------------------------------------------------ 4102 // ------------------------------------------------------------------
3760 SetParticipantMiningStatus("opening tunnel"); 4103 SetParticipantMiningStatus("opening tunnel");
4104 btf::TunnelFail tunnelFail = btf::TUNNEL_FAIL_NONE;
3761 s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, 4105 s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port,
3762 target_pubkey, service_enc_pub); 4106 target_pubkey, service_enc_pub, &tunnelFail);
3763 if (s == INVALID_SOCKET) { 4107 if (s == INVALID_SOCKET) {
3764 LogPrint("worker", "[worker] tunnel connect FAILED via %s" 4108 nTunnelFails++;
3765 " (relay unreachable or pool not listening)\n", 4109 LogPrint("worker", "[worker] tunnel connect FAILED via %s: %s\n",
3766 meetingHostPort.c_str()); 4110 meetingHostPort.c_str(), btf::TunnelFailText(tunnelFail));
3767 SetParticipantMiningStatus("tunnel connect failed"); 4111 SetParticipantMiningStatus(std::string("tunnel connect failed: ")
4112 + btf::TunnelFailText(tunnelFail));
3768 return false; 4113 return false;
3769 } 4114 }
4115 nTunnelFails = 0;
3770 LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); 4116 LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str());
3771 SetParticipantMiningStatus("tunnel open"); 4117 SetParticipantMiningStatus("tunnel open");
3772 } 4118 }
@@ -3899,9 +4245,32 @@ connected:
3899 std::vector<ParticipantShareHit> shareHits; 4245 std::vector<ParticipantShareHit> shareHits;
3900 std::mutex shareHitsMtx; 4246 std::mutex shareHitsMtx;
3901 std::vector<std::thread> hashThreads; 4247 std::vector<std::thread> hashThreads;
3902 for (int i = 0; i < nWorkers; i++) 4248 try
3903 hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers, 4249 {
3904 &shareHits, &shareHitsMtx); 4250 for (int i = 0; i < nWorkers; i++)
4251 hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers,
4252 &shareHits, &shareHitsMtx);
4253 }
4254 catch (const std::system_error& e)
4255 {
4256 // A refused pthread_create used to reach std::terminate here and
4257 // kill the worker mid-session -- observed twice on an otherwise
4258 // healthy participant. Stop the workers that did start the same way
4259 // the disconnect path below does, drop this pool connection, and let
4260 // the caller's reconnect loop try again once the pressure clears.
4261 LogPrint("worker", "[worker] cannot spawn hash threads (%s)"
4262 " -- dropping pool connection\n", e.what());
4263 SetParticipantMiningStatus("hash thread spawn failed");
4264 {
4265 std::lock_guard<std::mutex> lk(jobShared.mtx);
4266 jobShared.stop = true;
4267 jobShared.haveJob = false;
4268 }
4269 for (std::thread& t : hashThreads)
4270 t.join();
4271 closesocket(s);
4272 return false;
4273 }
3905 LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n", 4274 LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n",
3906 nWorkers); 4275 nWorkers);
3907 const uint64 sessionStartHashes = gParticipantHashes.load(); 4276 const uint64 sessionStartHashes = gParticipantHashes.load();
@@ -3912,8 +4281,17 @@ connected:
3912 std::string jobId; 4281 std::string jobId;
3913 unsigned char header[80] = {}; 4282 unsigned char header[80] = {};
3914 uint256 target; 4283 uint256 target;
4284 // Cached answer to "which block is this job built on", for the
4285 // mining.notify handler below -- see the comment there. Session-local, so
4286 // a reconnect re-resolves rather than trusting an answer obtained before
4287 // a possible change of pool.
4288 uint256 hashPrevResolved; // prevhash the cached answer describes
4289 bool fPrevResolved = false; // whether that answer exists yet
4290 int nPrevHeight = -1; // its height, or -1 == unknown to us
4291 int nBestHeightResolved = -1; // nBestHeight when we last resolved
3915 double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool 4292 double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool
3916 bool haveJob = false; 4293 bool haveJob = false;
4294 bool gotAnyJob = false; // any job at all this session -- the return value
3917 bool gotSubscribeAck = false; 4295 bool gotSubscribeAck = false;
3918 bool gotAuthorizeAck = false; 4296 bool gotAuthorizeAck = false;
3919 int64 authWaitStart = 0; 4297 int64 authWaitStart = 0;
@@ -4030,8 +4408,9 @@ connected:
4030 jobId = newJobId; 4408 jobId = newJobId;
4031 memcpy(header, hdrBytes.data(), 80); 4409 memcpy(header, hdrBytes.data(), 80);
4032 memcpy(&target, tgtBytes.data(), 32); 4410 memcpy(&target, tgtBytes.data(), 32);
4033 haveJob = true; 4411 haveJob = true;
4034 nNonce = 1; 4412 gotAnyJob = true;
4413 nNonce = 1;
4035 { 4414 {
4036 std::lock_guard<std::mutex> lk(jobShared.mtx); 4415 std::lock_guard<std::mutex> lk(jobShared.mtx);
4037 memcpy(jobShared.header, header, 80); 4416 memcpy(jobShared.header, header, 80);
@@ -4040,8 +4419,64 @@ connected:
4040 jobShared.jobSeq++; 4419 jobShared.jobSeq++;
4041 jobShared.haveJob = true; 4420 jobShared.haveJob = true;
4042 } 4421 }
4043 LogPrint("worker", "[pool->worker] new job %s received\n", 4422 // Log the chain position the pool is
4044 jobId.c_str()); 4423 // building on, so a stale or private
4424 // fork is auditable from worker logs.
4425 // The prevhash comes from the header we
4426 // actually hash (== p[2] of the notify);
4427 // a brief cs_main lookup is safe here --
4428 // this thread holds no other lock.
4429 //
4430 // Resolving it is the expensive half, and
4431 // this is the socket-reading thread: block
4432 // validation can hold cs_main for a long
4433 // time, and every moment spent waiting on
4434 // it here is a moment set_difficulty and
4435 // share responses are not being handled.
4436 // Most notifies in a round are a new job
4437 // on the SAME tip, so the answer is cached
4438 // and re-resolved only when it could come
4439 // out differently: a prevhash we have not
4440 // resolved, or one we could NOT resolve
4441 // last time while the chain has since
4442 // moved (unknown -> known). A block's
4443 // height is fixed once mapBlockIndex knows
4444 // it, so a cached "known" never goes
4445 // stale. nBestHeight is read unlocked on
4446 // purpose -- it only decides whether to
4447 // repeat a lookup, never what gets
4448 // printed, so a stale read costs at most
4449 // one deferred or one extra lookup.
4450 // The line still prints in full for every
4451 // job, cached or not.
4452 uint256 hashPrev;
4453 memcpy(&hashPrev, header + 4, 32);
4454 if (LogAcceptsCategory("worker"))
4455 {
4456 int nBestHeightNow = nBestHeight;
4457 if (!fPrevResolved
4458 || hashPrev != hashPrevResolved
4459 || (nPrevHeight < 0 && nBestHeightNow != nBestHeightResolved))
4460 {
4461 nPrevHeight = -1;
4462 CRITICAL_BLOCK(cs_main)
4463 {
4464 map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrev);
4465 if (mi != mapBlockIndex.end())
4466 nPrevHeight = (*mi).second->nHeight;
4467 }
4468 hashPrevResolved = hashPrev;
4469 nBestHeightResolved = nBestHeightNow;
4470 fPrevResolved = true;
4471 }
4472 if (nPrevHeight >= 0)
4473 LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (known, job height %d)\n",
4474 jobId.c_str(), hashPrev.GetHex().c_str(),
4475 nPrevHeight + 1);
4476 else
4477 LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (unknown to our chain)\n",
4478 jobId.c_str(), hashPrev.GetHex().c_str());
4479 }
4045 SetParticipantMiningStatus("hashing job " + jobId); 4480 SetParticipantMiningStatus("hashing job " + jobId);
4046 } 4481 }
4047 } 4482 }
@@ -4167,7 +4602,11 @@ connected:
4167 (unsigned long long)hashesSinceSample, 4602 (unsigned long long)hashesSinceSample,
4168 (unsigned long long)sessionSharesSent, 4603 (unsigned long long)sessionSharesSent,
4169 (unsigned long long)sessionSharesAccepted); 4604 (unsigned long long)sessionSharesAccepted);
4170 return true; 4605 // Not "we got here", which is true of a tunnel the pool accepted and then
4606 // dropped without ever sending a job -- exactly the case the caller's
4607 // backoff exists for. Reaching the end of the session is not evidence the
4608 // session was worth anything; a job is.
4609 return gotAnyJob;
4171} 4610}
4172 4611
4173// --------------------------------------------------------------------------- 4612// ---------------------------------------------------------------------------
@@ -4364,11 +4803,51 @@ bool BitcoinMiner(int nThreadId)
4364 // Participant mode: connect to external pool instead of mining solo/operator 4803 // Participant mode: connect to external pool instead of mining solo/operator
4365 if (nMineMode == MINE_PARTICIPANT) 4804 if (nMineMode == MINE_PARTICIPANT)
4366 { 4805 {
4806 // An attempt that never got work backs off (10s doubling to 120s)
4807 // instead of hammering the same path every flat 10s, and a streak
4808 // lasting minutes is said at printf level: the identical once-per-10s
4809 // LogPrint line is how a worker idled all night unnoticed (issue #17).
4810 //
4811 // Only a session the pool actually sent a job to resets either. The
4812 // weaker test -- "the session returned" -- was gameable: a pool that
4813 // completes the tunnel and drops without ever sending work leaves by
4814 // the same tidy cleanup path a healthy one does, which would pin the
4815 // retry at 10s forever and defeat the backoff in exactly the scenario
4816 // it exists for.
4817 int nRetrySecs = 10;
4818 int64 nFailingSince = 0;
4367 while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { 4819 while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) {
4368 PoolParticipantMiner(); 4820 bool fGotWork = PoolParticipantMiner();
4369 if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { 4821 if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) {
4370 LogPrint("worker", "PoolParticipantMiner: reconnecting in 10s\n"); 4822 if (fGotWork) {
4371 for (int i = 0; i < 10 && !fShutdown; i++) Sleep(1000); 4823 nRetrySecs = 10;
4824 nFailingSince = 0;
4825 } else {
4826 if (nFailingSince == 0)
4827 nFailingSince = GetTime();
4828 int64 nMins = (GetTime() - nFailingSince) / 60;
4829 if (nMins >= 1)
4830 printf("[worker] connection to pool %s has been failing"
4831 " for %lld minutes\n",
4832 strParticipantPool.c_str(), (long long)nMins);
4833 }
4834 LogPrint("worker", "PoolParticipantMiner: reconnecting in %ds\n",
4835 nRetrySecs);
4836 // Poll everything the enclosing while polls, not just
4837 // fShutdown. Raising the ceiling to 120s made a
4838 // shutdown-only predicate expensive: after the operator
4839 // clears Generate or switches to Relay, the session -- and
4840 // the vfThreadRunning[3] slot that gates restarting it --
4841 // would stay held for up to two minutes. Measured 119s
4842 // before, 1s after.
4843 for (int i = 0; i < nRetrySecs && !fShutdown && fGenerateBitcoins
4844 && nMineMode == MINE_PARTICIPANT; i++)
4845 Sleep(1000);
4846 if (!fGotWork && nRetrySecs < 120) {
4847 nRetrySecs *= 2;
4848 if (nRetrySecs > 120)
4849 nRetrySecs = 120;
4850 }
4372 } 4851 }
4373 } 4852 }
4374 return true; 4853 return true;
@@ -4470,7 +4949,6 @@ bool BitcoinMiner(int nThreadId)
4470 map<uint256, CTxIndex> mapTestPool; 4949 map<uint256, CTxIndex> mapTestPool;
4471 vector<char> vfAlreadyAdded(mapTransactions.size()); 4950 vector<char> vfAlreadyAdded(mapTransactions.size());
4472 bool fFoundSomething = true; 4951 bool fFoundSomething = true;
4473 unsigned int nBlockSize = 0;
4474 // Assembly must stop below the consensus cap that CheckBlock() 4952 // Assembly must stop below the consensus cap that CheckBlock()
4475 // enforces, or we mine a block our own node then rejects. 4953 // enforces, or we mine a block our own node then rejects.
4476 // 4954 //
@@ -4481,9 +4959,30 @@ bool BitcoinMiner(int nThreadId)
4481 // meant solving a block and throwing it away -- with this pool at 4959 // meant solving a block and throwing it away -- with this pool at
4482 // ~90% of hashrate, that is the network stalling, not just us. 4960 // ~90% of hashrate, that is the network stalling, not just us.
4483 // 4961 //
4484 // Half the cap, keeping the original margin: the test runs before 4962 // The budget test then sat only on this outer while, and the
4485 // a transaction is added, so the loop can overshoot by one tx. 4963 // comment claimed that bounded the overshoot to a single
4486 while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE/2) 4964 // transaction. It did not. The inner for walks all of
4965 // mapTransactions with no test of its own, so one pass could append
4966 // the entire mempool: the overshoot was bounded by mempool size,
4967 // which is to say not bounded at all. Ask per transaction, before
4968 // appending, and the overshoot is zero rather than small.
4969 //
4970 // Seeded with what the block already weighs -- header, tx count,
4971 // coinbase -- because CheckBlock() weighs the whole serialized
4972 // block and not just the transactions we chose to add to it.
4973 unsigned int nBlockSize = ::GetSerializeSize(*pblock, SER_DISK);
4974 // Sigops are a budget too, and CheckBlock() enforces one that no
4975 // assembler here counted against. Bytes do not bound validation
4976 // cost: every OP_CHECKSIG is an elliptic-curve verification, so a
4977 // template well inside the size limit can still be refused for
4978 // demanding more work than a block is allowed to. Counted with
4979 // CTransaction::GetSigOpCount, the function CheckBlock() itself
4980 // sums, so the two cannot disagree about what a transaction costs.
4981 //
4982 // Seeded from the coinbase because CheckBlock() sums over all of
4983 // vtx, coinbase included.
4984 unsigned int nBlockSigOps = pblock->vtx[0].GetSigOpCount();
4985 while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE_GEN)
4487 { 4986 {
4488 fFoundSomething = false; 4987 fFoundSomething = false;
4489 unsigned int n = 0; 4988 unsigned int n = 0;
@@ -4495,6 +4994,29 @@ bool BitcoinMiner(int nThreadId)
4495 if (tx.IsCoinBase() || !tx.IsFinal()) 4994 if (tx.IsCoinBase() || !tx.IsFinal())
4496 continue; 4995 continue;
4497 4996
4997 // Skip, not break: a transaction too big for the room left
4998 // says nothing about the next one, which may still fit.
4999 //
5000 // This is also the only ceiling on a single transaction's
5001 // size anywhere on the path into a block. CheckTransaction
5002 // does not cap it and neither does AcceptTransaction, so
5003 // the mempool will hold one of any size the wire allows;
5004 // without this test a lone 1.5 MB transaction offering a
5005 // fat fee is selected, and every template built from that
5006 // mempool is one CheckBlock() throws away. The fee is never
5007 // paid, because the block never confirms.
5008 //
5009 // Deliberately before ConnectInputs and not after:
5010 // ConnectInputs adds this transaction's fee to nFees by
5011 // reference on success. Passing it and then declining the
5012 // transaction would credit the coinbase for a fee no
5013 // transaction in the block pays, and the block would be
5014 // rejected for a wholly different reason.
5015 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
5016 unsigned int nTxSigOps = tx.GetSigOpCount();
5017 if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps))
5018 continue;
5019
4498 // Transaction fee requirements, mainly only needed for flood control 5020 // Transaction fee requirements, mainly only needed for flood control
4499 // Under 10K (about 80 inputs) is free for first 100 transactions 5021 // Under 10K (about 80 inputs) is free for first 100 transactions
4500 // Base rate is 0.01 per KB 5022 // Base rate is 0.01 per KB
@@ -4506,7 +5028,8 @@ bool BitcoinMiner(int nThreadId)
4506 swap(mapTestPool, mapTestPoolTmp); 5028 swap(mapTestPool, mapTestPoolTmp);
4507 5029
4508 pblock->vtx.push_back(tx); 5030 pblock->vtx.push_back(tx);
4509 nBlockSize += ::GetSerializeSize(tx, SER_NETWORK); 5031 nBlockSize += nTxSize;
5032 nBlockSigOps += nTxSigOps;
4510 vfAlreadyAdded[n] = true; 5033 vfAlreadyAdded[n] = true;
4511 fFoundSomething = true; 5034 fFoundSomething = true;
4512 } 5035 }
@@ -4555,7 +5078,7 @@ bool BitcoinMiner(int nThreadId)
4555 //// debug print 5078 //// debug print
4556 if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n"); 5079 if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n");
4557 if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); 5080 if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
4558 if (LogAcceptsCategory("net")) pblock->print(); 5081 if (LogAcceptsCategory("relay")) pblock->print();
4559 5082
4560 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); 5083 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
4561 CRITICAL_BLOCK(cs_main) 5084 CRITICAL_BLOCK(cs_main)
@@ -4661,6 +5184,9 @@ WalletRecoveryAudit GetWalletRecoveryAudit()
4661 CRITICAL_BLOCK(cs_keyPool) 5184 CRITICAL_BLOCK(cs_keyPool)
4662 { 5185 {
4663 audit.fHaveSeed = HaveHDSeed(); 5186 audit.fHaveSeed = HaveHDSeed();
5187 audit.fSeedEncryptedLocked = !audit.fHaveSeed &&
5188 (!vchCryptedHDMaster.empty() ||
5189 !vchCryptedHDChainCode.empty());
4664 audit.nSchema = nHDKeySchema; 5190 audit.nSchema = nHDKeySchema;
4665 audit.nDerivedKnown = nHDNext; 5191 audit.nDerivedKnown = nHDNext;
4666 audit.nReceiveNext = nHDReceiveNext; 5192 audit.nReceiveNext = nHDReceiveNext;
diff --git a/src/main.h b/src/main.h
index 382dd9c..e05b467 100644
--- a/src/main.h
+++ b/src/main.h
@@ -33,6 +33,35 @@ static const unsigned int MAX_BLOCK_SIGOPS = 20000;
33// limit (32 MB), not a block rule. Bitcoin 0.1.0 predated the later 1 MB block 33// limit (32 MB), not a block rule. Bitcoin 0.1.0 predated the later 1 MB block
34// cap, so this tree inherited no real block-size consensus limit. 34// cap, so this tree inherited no real block-size consensus limit.
35static const unsigned int MAX_BLOCK_SIZE = 1000000; 35static const unsigned int MAX_BLOCK_SIZE = 1000000;
36// How much of that cap a template we build may fill. Half, which is what both
37// assemblers already aimed at -- this only gives the number a name and one
38// place to live. They had already drifted on how it was enforced, which is the
39// bug this exists to stop repeating.
40//
41// NOT a consensus rule. CheckBlock() decides what the network accepts; this
42// decides what we are willing to produce, and it has to stay under that.
43static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE / 2;
44
45// Room for one more transaction in a template that currently weighs
46// nBlockSize bytes and spends nBlockSigOps signature operations?
47//
48// Asked BEFORE the transaction is appended, never after. A template that has
49// already overshot is one CheckBlock() rejects, and asking afterwards only
50// measures the waste. Both assemblers call this, so neither can drift into a
51// different idea of what fits.
52//
53// Size gets a margin (half the cap); sigops do not. There is no cheap way to
54// spend the sigop budget usefully and no reason to reserve headroom in it, so
55// assembly holds itself to the same number CheckBlock() enforces -- and to the
56// same comparison. CheckBlock() fails on `> MAX_BLOCK_SIGOPS`, so a block
57// sitting exactly on the limit is valid and this has to accept it too.
58// Assembly and validation disagreeing by one is how the size bug started.
59inline bool BlockTemplateFits(unsigned int nBlockSize, unsigned int nBlockSigOps,
60 unsigned int nTxSize, unsigned int nTxSigOps)
61{
62 return (nBlockSize + nTxSize <= MAX_BLOCK_SIZE_GEN &&
63 nBlockSigOps + nTxSigOps <= MAX_BLOCK_SIGOPS);
64}
36static const int64 COIN = 100000000; 65static const int64 COIN = 100000000;
37// Total Bitflash emission: identical to Bitcoin (21 million). 66// Total Bitflash emission: identical to Bitcoin (21 million).
38// MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in 67// MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in
@@ -306,6 +335,7 @@ int64 GetBalance();
306struct WalletRecoveryAudit 335struct WalletRecoveryAudit
307{ 336{
308 bool fHaveSeed; 337 bool fHaveSeed;
338 bool fSeedEncryptedLocked;
309 bool fDeriveComplete; 339 bool fDeriveComplete;
310 int nSchema; 340 int nSchema;
311 unsigned int nDerivedKnown; 341 unsigned int nDerivedKnown;
@@ -325,6 +355,7 @@ struct WalletRecoveryAudit
325 WalletRecoveryAudit() 355 WalletRecoveryAudit()
326 { 356 {
327 fHaveSeed = false; 357 fHaveSeed = false;
358 fSeedEncryptedLocked = false;
328 fDeriveComplete = true; 359 fDeriveComplete = true;
329 nSchema = HD_SCHEMA_NONE; 360 nSchema = HD_SCHEMA_NONE;
330 nDerivedKnown = 0; 361 nDerivedKnown = 0;
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 211e1e2..dd001c8 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -111,10 +111,16 @@ static void PrintUsage()
111 printf(" /help, -help, --help, /?\n"); 111 printf(" /help, -help, --help, /?\n");
112 printf(" /datadir=PATH\n"); 112 printf(" /datadir=PATH\n");
113 printf(" /debug\n"); 113 printf(" /debug\n");
114 printf(" /debugrelay (/debug plus per-message relay chatter and block dumps)\n");
115 printf(" /maxdebugfilesize=N (roll debug.log at N MB, default 1024; 0 = never)\n");
114 printf(" /gen\n"); 116 printf(" /gen\n");
115 printf(" /nogui or /daemon\n"); 117 printf(" /nogui or /daemon\n");
116 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt,\n"); 118 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity,\n");
117 printf(" net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, or managed-tor\n"); 119 printf(" db-env-reopen, wallet-sqlite, wallet-sqlite-migration,\n");
120 printf(" wallet-crypto, wallet-encrypt, wallet-portability,\n");
121 printf(" net-message,\n");
122 printf(" consensus-limits, pool-stratum,\n");
123 printf(" parse-money, socks5-proxy, or managed-tor\n");
118 printf("\n"); 124 printf("\n");
119 printf("Mining mode:\n"); 125 printf("Mining mode:\n");
120 printf(" /operator\n"); 126 printf(" /operator\n");
@@ -175,6 +181,26 @@ static void PrintUsage()
175 printf(" /recoveryaudit (show how much spendable balance is\n"); 181 printf(" /recoveryaudit (show how much spendable balance is\n");
176 printf(" covered by the recovery phrase;\n"); 182 printf(" covered by the recovery phrase;\n");
177 printf(" exits 2 when wallet.dat is still needed)\n"); 183 printf(" exits 2 when wallet.dat is still needed)\n");
184 printf(" /walletstorageaudit (count wallet.dat record types without\n");
185 printf(" printing wallet values, then exit)\n");
186 printf(" /walletstorageauditjson=FILE\n");
187 printf(" (write the same audit as deterministic JSON)\n");
188 printf(" /walletstoragecheck (fail closed on inconsistent or unsafe\n");
189 printf(" wallet.dat storage state)\n");
190 printf(" /walletsqliteexport=FILE (copy raw wallet.dat records into an\n");
191 printf(" SQLite store, then exit)\n");
192 printf(" /walletsqliteverify=FILE (compare wallet.dat with a SQLite export,\n");
193 printf(" printing counts only, then exit)\n");
194 printf(" /walletsqliterestore=FILE (rebuild wallet.dat from a SQLite export in\n");
195 printf(" an empty data directory, then exit)\n");
196 printf(" /walletsqliteloadcheck=FILE\n");
197 printf(" (parse a SQLite export like the wallet loader)\n");
198 printf(" /walletsqlite=FILE (stage-load a SQLite wallet export, then exit)\n");
199 printf(" /walletbackend=sqlite (opt-in: run the node on <datadir>/wallet.sqlite\n");
200 printf(" instead of wallet.dat; export one first)\n");
201 printf(" /createwallet (headless only: permit creating a new wallet in a\n");
202 printf(" datadir that has none -- otherwise refused, since\n");
203 printf(" a missing wallet is usually a mount/backup fault)\n");
178 printf(" /rescan (walk the chain for coins this wallet owns\n"); 204 printf(" /rescan (walk the chain for coins this wallet owns\n");
179 printf(" but never recorded, then exit)\n"); 205 printf(" but never recorded, then exit)\n");
180 printf("\n"); 206 printf("\n");
@@ -191,6 +217,54 @@ static void ParseStartupArguments(int argc, char* argv[])
191 if (arg(argc,argv,"/debug") || arg(argc,argv,"-debug")) 217 if (arg(argc,argv,"/debug") || arg(argc,argv,"-debug"))
192 fDebug = true; 218 fDebug = true;
193 219
220 // /debugrelay -- everything /debug shows, plus the per-message firehose
221 // ("relay" and "nostrdiscovery" categories: sending: tx, Relaying wtx,
222 // raw CBlock dumps, per-peer discovery lines). That traffic was ~90% of
223 // a 13-hour production debug.log (issue #35), so plain /debug leaves it
224 // off; the message text itself is unchanged for anyone grepping it.
225 if (arg(argc,argv,"/debugrelay") || arg(argc,argv,"-debugrelay"))
226 {
227 fDebug = true;
228 fDebugRelay = true;
229 }
230
231 // /maxdebugfilesize=N -- roll debug.log to debug.log.old once it passes
232 // N MB, checked on each write. Default is 1024 (see util.cpp); 0 turns
233 // the check off and leaves rotation entirely to logrotate.
234 string strMaxDebugSize = argval2(argc, argv, "/maxdebugfilesize", "-maxdebugfilesize");
235 if (!strMaxDebugSize.empty())
236 {
237 // Parsed with atoi64() before, which is strtoll() with the endptr
238 // thrown away: it returns 0 for anything it cannot read, so
239 // /maxdebugfilesize=1G and =1024MB both came out as 0 -- "never
240 // roll", identical to asking for that on purpose, with nothing said
241 // to the operator. The old code also multiplied without a range
242 // check, so a big enough N wrapped to a negative cap, which the roll
243 // reads as "off" too. Every rejection is now reported and leaves the
244 // default in place; the value is a plain count of MB, no suffix.
245 const char* psz = strMaxDebugSize.c_str();
246 char* pszEnd = NULL;
247 errno = 0;
248 int64 nMB = strtoll(psz, &pszEnd, 10);
249 // INT64_MAX, without depending on which limits header this tree pulls in.
250 const int64 nMaxMB = (int64)((~(uint64)0) >> 1) / (1024 * 1024);
251 const char* pszWhy = NULL;
252 if (pszEnd == psz)
253 pszWhy = "not a number";
254 else if (*pszEnd != '\0')
255 pszWhy = "trailing characters -- the value is a count of MB, with no unit suffix";
256 else if (errno == ERANGE || nMB < 0)
257 pszWhy = "not a size in MB";
258 else if (nMB > nMaxMB)
259 pszWhy = "larger than a file size can be";
260 if (pszWhy)
261 fprintf(stderr, "Ignoring /maxdebugfilesize=%s: %s (keeping %lld MB)\n",
262 strMaxDebugSize.c_str(), pszWhy,
263 (long long)(nMaxDebugFileSize / 1024 / 1024));
264 else
265 nMaxDebugFileSize = nMB * 1024 * 1024;
266 }
267
194 if (arg(argc,argv,"/gen") || arg(argc,argv,"-gen")) 268 if (arg(argc,argv,"/gen") || arg(argc,argv,"-gen"))
195 { 269 {
196 fGenerateBitcoins = 1; 270 fGenerateBitcoins = 1;
@@ -530,10 +604,119 @@ int main(int argc, char* argv[])
530 604
531 ParseStartupArguments(argc, argv); 605 ParseStartupArguments(argc, argv);
532 606
607#ifdef _WIN32
608 // Set by the GUI "Restart Now" button: give the previous instance a moment
609 // to release the network port and the block-index files before this one
610 // opens them, so a relaunch does not race the process it is replacing.
611 if (arg(argc, argv, "/restartwait") || arg(argc, argv, "-restartwait"))
612 Sleep(2500);
613#endif
614
615 // Hidden self-test helper. It must run before LoadWallet(): several
616 // storage-sanity scenarios deliberately make wallet.dat unsafe to load.
617 string strSelfTestMutateWallet =
618 argval2(argc, argv, "/selftestmutatewallet", "-selftestmutatewallet");
619 if (!strSelfTestMutateWallet.empty())
620 return RunSelfTestMutateWallet(strSelfTestMutateWallet);
621
533 string strSelfTest = argval2(argc, argv, "/selftest", "-selftest"); 622 string strSelfTest = argval2(argc, argv, "/selftest", "-selftest");
534 if (!strSelfTest.empty()) 623 if (!strSelfTest.empty())
535 return RunSelfTest(strSelfTest); 624 return RunSelfTest(strSelfTest);
536 625
626 // Storage diagnostics must run before LoadWallet(). Their job is to
627 // explain wallet.dat states that the normal wallet loader may refuse.
628 string strWalletStorageAuditJson =
629 argval2(argc, argv, "/walletstorageauditjson", "-walletstorageauditjson");
630 bool fDiagWantsAudit = arg(argc,argv,"/walletstorageaudit") ||
631 arg(argc,argv,"-walletstorageaudit") ||
632 !strWalletStorageAuditJson.empty();
633 bool fDiagWantsCheck = arg(argc,argv,"/walletstoragecheck") ||
634 arg(argc,argv,"-walletstoragecheck");
635
636 // These diagnostics normally read wallet.dat. Under -walletbackend=sqlite
637 // the live wallet is wallet.sqlite, so open it read-only as the active
638 // store and let the same scan walk the right file. This runs before the
639 // normal backend resolution further down, so validate the value here too.
640 string strDiagBackend = argval2(argc, argv, "/walletbackend", "-walletbackend");
641 if ((fDiagWantsAudit || fDiagWantsCheck) && !strDiagBackend.empty() &&
642 strDiagBackend != "sqlite" && strDiagBackend != "bdb")
643 {
644 AttachTerminal();
645 fprintf(stderr, "Unknown wallet backend '%s'. Use -walletbackend=sqlite "
646 "or -walletbackend=bdb.\n", strDiagBackend.c_str());
647 return 1;
648 }
649 bool fDiagSQLite = (strDiagBackend == "sqlite") &&
650 (fDiagWantsAudit || fDiagWantsCheck);
651 if (fDiagSQLite)
652 {
653 AttachTerminal();
654 string strSQLitePath = GetAppDir() + "/wallet.sqlite";
655 string strOpenErr;
656 if (!WalletSQLiteRuntimeOpenReadOnly(strSQLitePath, strOpenErr))
657 {
658 fprintf(stderr, "Cannot open the SQLite wallet for diagnostics: %s\n",
659 strOpenErr.c_str());
660 return 1;
661 }
662 }
663
664 if (fDiagWantsAudit)
665 {
666 int nRet = CmdWalletStorageAudit(strWalletStorageAuditJson);
667 if (fDiagSQLite)
668 WalletSQLiteRuntimeClose();
669 else
670 DBFlush(true);
671 return nRet;
672 }
673
674 if (fDiagWantsCheck)
675 {
676 int nRet = CmdWalletStorageCheck();
677 if (fDiagSQLite)
678 WalletSQLiteRuntimeClose();
679 else
680 DBFlush(true);
681 return nRet;
682 }
683
684 string strWalletSQLiteExport =
685 argval2(argc, argv, "/walletsqliteexport", "-walletsqliteexport");
686 if (!strWalletSQLiteExport.empty())
687 {
688 int nRet = CmdWalletSQLiteExport(strWalletSQLiteExport);
689 DBFlush(true);
690 return nRet;
691 }
692
693 string strWalletSQLiteVerify =
694 argval2(argc, argv, "/walletsqliteverify", "-walletsqliteverify");
695 if (!strWalletSQLiteVerify.empty())
696 {
697 int nRet = CmdWalletSQLiteVerify(strWalletSQLiteVerify);
698 DBFlush(true);
699 return nRet;
700 }
701
702 string strWalletSQLiteRestore =
703 argval2(argc, argv, "/walletsqliterestore", "-walletsqliterestore");
704 if (!strWalletSQLiteRestore.empty())
705 {
706 int nRet = CmdWalletSQLiteRestore(strWalletSQLiteRestore);
707 DBFlush(true);
708 return nRet;
709 }
710
711 string strWalletSQLiteLoadCheck =
712 argval2(argc, argv, "/walletsqliteloadcheck", "-walletsqliteloadcheck");
713 if (!strWalletSQLiteLoadCheck.empty())
714 {
715 int nRet = CmdWalletSQLiteLoadCheck(strWalletSQLiteLoadCheck);
716 DBFlush(true);
717 return nRet;
718 }
719
537 // Berkeley DB reports failure by throwing, and CDB's constructor lets it 720 // Berkeley DB reports failure by throwing, and CDB's constructor lets it
538 // through. Nothing on this path caught anything, so an unreadable 721 // through. Nothing on this path caught anything, so an unreadable
539 // blkindex.dat or wallet.dat -- one written by another platform's Berkeley 722 // blkindex.dat or wallet.dat -- one written by another platform's Berkeley
@@ -578,18 +761,197 @@ int main(int argc, char* argv[])
578 printf("Loading wallet...\n"); 761 printf("Loading wallet...\n");
579 try 762 try
580 { 763 {
581 if (!LoadWallet()) 764 string strWalletBackend =
765 argval2(argc, argv, "/walletbackend", "-walletbackend");
766 string strWalletSQLite =
767 argval2(argc, argv, "/walletsqlite", "-walletsqlite");
768
769 // A misspelled backend must not fall through to Berkeley DB silently --
770 // someone who typed -walletbackend=sqlit meant to run on SQLite, and
771 // opening wallet.dat instead of telling them is the wrong surprise.
772 if (!strWalletBackend.empty() &&
773 strWalletBackend != "sqlite" && strWalletBackend != "bdb")
582 { 774 {
583 // LoadWallet() explains itself into strWalletLoadError when it 775 AttachTerminal();
584 // knows why -- an unsupported wallet format, for one, which is the 776 FatalStartupError(fHeadlessStartup,
585 // case this whole path exists to make visible. 777 strprintf("Unknown wallet backend '%s'.", strWalletBackend.c_str()),
586 FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.", 778 "Use -walletbackend=sqlite for the SQLite backend, or "
587 strWalletLoadError); 779 "-walletbackend=bdb for the Berkeley DB wallet.dat.");
588 return 1; 780 return 1;
589 } 781 }
590 782
783 if (strWalletBackend == "sqlite")
784 {
785 // Explicit opt-in via the flag. The node runs on
786 // <datadir>/wallet.sqlite and never opens wallet.dat, but the user
787 // has to have exported one first -- there is no silent migration,
788 // and wallet.dat is left
789 // exactly where it is so a plain restart goes back to it.
790 AttachTerminal();
791 string strSQLitePath = GetAppDir() + "/wallet.sqlite";
792 if (!FileExists(strSQLitePath.c_str()))
793 {
794 string strGuide = strprintf(
795 "No SQLite wallet was found at\n"
796 " %s\n\n"
797 "The SQLite wallet backend is opt-in and needs an exported\n"
798 "wallet first. Your Berkeley DB wallet.dat is not touched by\n"
799 "any of this.\n\n"
800 " Step 1 Export your existing wallet to SQLite:\n"
801 " bitflash -walletsqliteexport=\"%s\"\n"
802 " Step 2 Start again with the SQLite backend:\n"
803 " bitflash -walletbackend=sqlite\n\n"
804 "To go back to Berkeley DB at any time, just start without\n"
805 "-walletbackend=sqlite. wallet.dat is still your wallet.",
806 strSQLitePath.c_str(), strSQLitePath.c_str());
807 FatalStartupError(fHeadlessStartup,
808 "The SQLite wallet backend needs an exported wallet first.",
809 strGuide);
810 return 1;
811 }
812
813 if (!LoadWalletFromSQLiteRuntime(strSQLitePath))
814 {
815 FatalStartupError(fHeadlessStartup,
816 "Cannot open the SQLite wallet backend.",
817 strWalletLoadError);
818 return 1;
819 }
820
821 fprintf(stderr,
822 "Wallet backend: SQLite\n"
823 " %s\n"
824 "Your Berkeley DB wallet.dat is left untouched. Restart without\n"
825 "-walletbackend=sqlite to go back to it.\n",
826 strSQLitePath.c_str());
827 fflush(stderr);
828 // Fall through and run the node normally: every CWalletDB read,
829 // write, and erase now routes to this SQLite file.
830 }
831 else if (!strWalletSQLite.empty())
832 {
833 AttachTerminal();
834 if (!LoadWalletFromSQLite(strWalletSQLite))
835 {
836 FatalStartupError(fHeadlessStartup,
837 "Cannot open SQLite wallet export.",
838 strWalletLoadError);
839 return 1;
840 }
841
842 fprintf(stderr, "SQLite wallet loaded: read-only staging backend\n");
843 fprintf(stderr, "SQLite runtime writes are not enabled yet; exiting before "
844 "network, mining, GUI, or wallet mutation starts.\n");
845 fflush(stderr);
846 DBFlush(true);
847 return 0;
848 }
849 else if (strWalletBackend == "bdb")
850 {
851 // Explicit -walletbackend=bdb overrides the marker and the default:
852 // force Berkeley DB (LoadWallet creates wallet.dat if none exists).
853 if (!LoadWallet())
854 {
855 FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.",
856 strWalletLoadError);
857 return 1;
858 }
859 }
860 else
861 {
862 // No explicit -walletbackend on the command line. Resolve it from
863 // the persistent marker, then defaults: an existing wallet.sqlite
864 // stays on SQLite, an existing wallet.dat stays on Berkeley DB (the
865 // GUI offers to convert), and a fresh install starts on SQLite --
866 // the new default. wallet.dat is never touched by any of this.
867 string strSQLitePath = GetAppDir() + "/wallet.sqlite";
868 bool fSQLiteExists = FileExists(strSQLitePath.c_str());
869 bool fWalletDatExists = FileExists((GetAppDir() + "/wallet.dat").c_str());
870 string strMarker = ReadWalletBackendMarker();
871
872 bool fUseSQLite = false;
873 bool fCreateSQLite = false;
874 if (strMarker == "sqlite")
875 {
876 if (fSQLiteExists) fUseSQLite = true;
877 else if (fWalletDatExists) fUseSQLite = false; // marker stale; real wallet is BDB
878 else { fUseSQLite = true; fCreateSQLite = true; }
879 }
880 else if (strMarker == "bdb")
881 {
882 fUseSQLite = false;
883 }
884 else // no marker recorded yet
885 {
886 if (fSQLiteExists && !fWalletDatExists) fUseSQLite = true;
887 else if (fWalletDatExists) fUseSQLite = false;
888 else { fUseSQLite = true; fCreateSQLite = true; }
889 }
890
891 // A headless node whose datadir holds no wallet of either kind is
892 // far more often an operations fault -- datadir not mounted, or
893 // wallet.dat caught mid-rename by a backup -- than a first run.
894 // Creating one here is not the harmless default it looks like:
895 // success also writes the backend marker, and from then on
896 // restoring the real wallet.dat no longer takes effect, because the
897 // marker keeps the node on the empty wallet it just made. Under
898 // 1.2.19 the same fault created a wallet.dat that a restore
899 // overwrote, so operators could recover by putting the file back.
900 // Refuse instead, and name the flag that makes it deliberate.
901 if (fCreateSQLite && fHeadlessStartup &&
902 !arg(argc, argv, "/createwallet") && !arg(argc, argv, "-createwallet"))
903 {
904 AttachTerminal();
905 fprintf(stderr,
906 "No wallet found in %s\n"
907 "Refusing to create one. On a headless node an absent wallet is\n"
908 "usually a mount or backup fault, and creating one would record a\n"
909 "backend marker that makes restoring the real wallet a no-op.\n"
910 "Pass -createwallet to start a new one deliberately, or\n"
911 "-walletbackend=bdb to run on a Berkeley DB wallet.dat.\n",
912 GetAppDir().c_str());
913 fflush(stderr);
914 return 1;
915 }
916
917 if (fUseSQLite)
918 {
919 AttachTerminal();
920 bool fOk = fCreateSQLite
921 ? CreateNewSQLiteWallet(strSQLitePath)
922 : LoadWalletFromSQLiteRuntime(strSQLitePath);
923 if (!fOk)
924 {
925 FatalStartupError(fHeadlessStartup,
926 fCreateSQLite ? "Cannot create the SQLite wallet."
927 : "Cannot open the SQLite wallet.",
928 strWalletLoadError);
929 return 1;
930 }
931 // Record the choice so it is stable from here on.
932 if (ReadWalletBackendMarker() != "sqlite")
933 WriteWalletBackendMarker("sqlite");
934 fprintf(stderr, "Wallet backend: SQLite\n %s\n%s",
935 strSQLitePath.c_str(),
936 fCreateSQLite ? "A new wallet was created here.\n" : "");
937 fflush(stderr);
938 // Fall through and run the node; CWalletDB routes to this file.
939 }
940 else if (!LoadWallet())
941 {
942 // LoadWallet() explains itself into strWalletLoadError when it
943 // knows why -- an unsupported wallet format, for one, which is
944 // the case this whole path exists to make visible.
945 FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.",
946 strWalletLoadError);
947 return 1;
948 }
949 }
950
591 // After the block index, so there is a chain to compare the wallet 951 // After the block index, so there is a chain to compare the wallet
592 // against, and before anything reports a balance. 952 // against, and before anything reports a balance. Works on either
953 // backend: RescanSpentFlags writes corrected flags through CWalletDB,
954 // which routes to SQLite when that backend is active.
593 RescanSpentFlags(); 955 RescanSpentFlags();
594 } 956 }
595 catch (const std::exception& e) 957 catch (const std::exception& e)
@@ -858,6 +1220,10 @@ int main(int argc, char* argv[])
858 // called here, which is why a wallet.dat copied elsewhere would not 1220 // called here, which is why a wallet.dat copied elsewhere would not
859 // open -- issue #40. Not optional. 1221 // open -- issue #40. Not optional.
860 DBFlush(true); 1222 DBFlush(true);
1223 // Checkpoint and close the SQLite wallet backend if one is active. A
1224 // no-op on the Berkeley DB path. Safe to kill before this runs: WAL +
1225 // synchronous=FULL already made every committed write durable.
1226 WalletSQLiteRuntimeClose();
861#ifdef _WIN32 1227#ifdef _WIN32
862 // wallet.dat is safely flushed and closed now; let a pending console 1228 // wallet.dat is safely flushed and closed now; let a pending console
863 // control handler (CLOSE/LOGOFF/SHUTDOWN) return so the OS can finish. 1229 // control handler (CLOSE/LOGOFF/SHUTDOWN) return so the OS can finish.
@@ -874,6 +1240,7 @@ int main(int argc, char* argv[])
874 StopNode(); 1240 StopNode();
875 BtfStopManagedTor(); 1241 BtfStopManagedTor();
876 DBFlush(true); 1242 DBFlush(true);
1243 WalletSQLiteRuntimeClose();
877 return ret; 1244 return ret;
878#endif 1245#endif
879} 1246}
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 0ba1d76..740bba6 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -24,7 +24,7 @@ CXXFLAGS = -std=gnu++14 -O2 $(WARNFLAGS) -fpermissive -pthread \
24 24
25LIBS = -L$(DEPS)/lib -lsecp256k1 \ 25LIBS = -L$(DEPS)/lib -lsecp256k1 \
26 -L$(RANDOMX)/build -lrandomx \ 26 -L$(RANDOMX)/build -lrandomx \
27 -lsodium -lssl -lcrypto -ldb_cxx -lpthread \ 27 -lsodium -lssl -lcrypto -ldb_cxx -lsqlite3 -lpthread \
28 -LC:/msys64/ucrt64/lib \ 28 -LC:/msys64/ucrt64/lib \
29 -lglfw3 -lopengl32 -lgdi32 \ 29 -lglfw3 -lopengl32 -lgdi32 \
30 -lkernel32 -luser32 -lcomdlg32 -lwinspool -lwinmm \ 30 -lkernel32 -luser32 -lcomdlg32 -lwinspool -lwinmm \
@@ -37,7 +37,8 @@ IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \
37NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \ 37NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \
38 obj/sha.o obj/nostr.o obj/randomx_pow.o \ 38 obj/sha.o obj/nostr.o obj/randomx_pow.o \
39 obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \ 39 obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \
40 obj/selftest.o obj/bip32.o obj/walletcmd.o obj/proxy.o obj/tor.o 40 obj/selftest.o obj/bip32.o obj/walletcmd.o obj/wallet_sqlite.o \
41 obj/proxy.o obj/tor.o
41 42
42GUI_OBJS = obj/gui.o obj/main_gui.o obj/bitflash_res.o 43GUI_OBJS = obj/gui.o obj/main_gui.o obj/bitflash_res.o
43 44
@@ -51,7 +52,7 @@ GUI_OBJS = obj/gui.o obj/main_gui.o obj/bitflash_res.o
51HEADERS = headers_core.h headers.h util.h main.h serialize.h uint256.h \ 52HEADERS = headers_core.h headers.h util.h main.h serialize.h uint256.h \
52 key.h bignum.h script.h db.h base58.h net.h nostr.h \ 53 key.h bignum.h script.h db.h base58.h net.h nostr.h \
53 randomx_pow.h btfaddr.h btfchan.h btfrv.h btftunnel.h \ 54 randomx_pow.h btfaddr.h btfchan.h btfrv.h btftunnel.h \
54 selftest.h walletcmd.h sockcount.h proxy.h tor.h compat.h font_roboto.h 55 selftest.h walletcmd.h wallet_sqlite.h sockcount.h proxy.h tor.h compat.h font_roboto.h
55 56
56all: bitflash.exe 57all: bitflash.exe
57 58
@@ -107,8 +108,13 @@ tests: test_bip32.exe bitflash.exe
107 ./bitflash.exe -selftest=wallet-keypool 108 ./bitflash.exe -selftest=wallet-keypool
108 ./bitflash.exe -selftest=wallet-hd 109 ./bitflash.exe -selftest=wallet-hd
109 ./bitflash.exe -selftest=wallet-format 110 ./bitflash.exe -selftest=wallet-format
111 ./bitflash.exe -selftest=wallet-sqlite
112 ./bitflash.exe -selftest=wallet-sqlite-migration
110 ./bitflash.exe -selftest=wallet-crypto 113 ./bitflash.exe -selftest=wallet-crypto
111 ./bitflash.exe -selftest=wallet-encrypt 114 ./bitflash.exe -selftest=wallet-encrypt
115 ./bitflash.exe -selftest=wallet-sqlite-encrypt
116 ./bitflash.exe -selftest=wallet-backend-default
117 ./bitflash.exe -selftest=wallet-convert
112 ./bitflash.exe -selftest=net-message 118 ./bitflash.exe -selftest=net-message
113 ./bitflash.exe -selftest=consensus-limits 119 ./bitflash.exe -selftest=consensus-limits
114 ./bitflash.exe -selftest=pool-stratum 120 ./bitflash.exe -selftest=pool-stratum
diff --git a/src/net.cpp b/src/net.cpp
index fed6123..bb05259 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1560,12 +1560,41 @@ void ThreadSocketHandler2(void* parg)
1560 unsigned int nNodes = 0; 1560 unsigned int nNodes = 0;
1561 CRITICAL_BLOCK(cs_vNodes) 1561 CRITICAL_BLOCK(cs_vNodes)
1562 nNodes = (unsigned int)vNodes.size(); 1562 nNodes = (unsigned int)vNodes.size();
1563 // Also count how many connections this one address already
1564 // holds. A single IP that keeps opening sockets -- a broken
1565 // node or a deliberate connection-exhaustion flood -- must not
1566 // be able to take the whole table, which is what starved the
1567 // relays.
1568 //
1569 // Loopback is exempt. Onion peers reach a managed-Tor node
1570 // through its local hidden-service listener, so every one of
1571 // them accepts as 127.0.0.1; capping loopback would cap all
1572 // inbound onion peers as a single group, which is the opposite
1573 // of where the network is headed. The global MAX_CONNECTIONS
1574 // still bounds the total, and the direct-IP flood this defends
1575 // against never arrives over loopback. (Per-onion-identity
1576 // limiting, once a peer's identity is known past accept(), is
1577 // the right tool for onion and is left for later.)
1578 bool fLoopback = (addr.GetByte(3) == 127);
1579 unsigned int nFromThisIP = 0;
1580 if (!fLoopback)
1581 CRITICAL_BLOCK(cs_vNodes)
1582 for (vector<CNode*>::iterator it = vNodes.begin(); it != vNodes.end(); ++it)
1583 if ((*it)->addr.ip == addr.ip)
1584 nFromThisIP++;
1585
1563 if (nNodes >= MAX_CONNECTIONS) 1586 if (nNodes >= MAX_CONNECTIONS)
1564 { 1587 {
1565 LogPrint("net", "refusing connection from %s, already at %u\n", 1588 LogPrint("net", "refusing connection from %s, already at %u\n",
1566 addr.ToString().c_str(), nNodes); 1589 addr.ToString().c_str(), nNodes);
1567 BtfCloseSocket(hSocket); 1590 BtfCloseSocket(hSocket);
1568 } 1591 }
1592 else if (!fLoopback && nFromThisIP >= MAX_CONNECTIONS_PER_IP)
1593 {
1594 LogPrint("net", "refusing connection from %s, already %u from that address\n",
1595 addr.ToString().c_str(), nFromThisIP);
1596 BtfCloseSocket(hSocket);
1597 }
1569 else 1598 else
1570 { 1599 {
1571 LogPrint("net", "accepted connection from %s\n", addr.ToString().c_str()); 1600 LogPrint("net", "accepted connection from %s\n", addr.ToString().c_str());
@@ -2090,7 +2119,7 @@ bool StartNode(string& strError)
2090 if (addrIncoming.ip) 2119 if (addrIncoming.ip)
2091 addrLocalHost.ip = addrIncoming.ip; 2120 addrLocalHost.ip = addrIncoming.ip;
2092 2121
2093 _beginthread([](void*) { 2122 if (_beginthread([](void*) {
2094 unsigned int ip = addrLocalHost.ip; 2123 unsigned int ip = addrLocalHost.ip;
2095 if (GetMyExternalIP(ip)) { 2124 if (GetMyExternalIP(ip)) {
2096 addrLocalHost.ip = ip; 2125 addrLocalHost.ip = ip;
@@ -2098,7 +2127,8 @@ bool StartNode(string& strError)
2098 CWalletDB().WriteSetting("addrIncoming", addrIncoming); 2127 CWalletDB().WriteSetting("addrIncoming", addrIncoming);
2099 printf("External IP updated: %s\n", addrLocalHost.ToStringIP().c_str()); 2128 printf("External IP updated: %s\n", addrLocalHost.ToStringIP().c_str());
2100 } 2129 }
2101 }, 0, NULL); 2130 }, 0, NULL) == (uintptr_t)-1)
2131 printf("Error: _beginthread(GetMyExternalIP) failed\n");
2102 2132
2103 // Peers that answered last time, dialled straight away. The relays are the 2133 // Peers that answered last time, dialled straight away. The relays are the
2104 // only way to find anyone otherwise, and reaching them plus walking their 2134 // only way to find anyone otherwise, and reaching them plus walking their
diff --git a/src/net.h b/src/net.h
index 9f3c7e4..752b1fe 100644
--- a/src/net.h
+++ b/src/net.h
@@ -23,6 +23,12 @@ enum
23 23
24 24
25 25
26// Defined in main.cpp. True while this node is tracking the tip rather than
27// still catching up, measured off the age of our own best block -- see there
28// for why that and not a peer-reported height. Declared here because net.h is
29// parsed before main.h and CNode::AskFor needs it.
30bool IsBlockFetchNearTip();
31
26bool GetMyExternalIP(unsigned int& ipRet); 32bool GetMyExternalIP(unsigned int& ipRet);
27CNode* ConnectNodeBtf(const string& strBtfAddr); 33CNode* ConnectNodeBtf(const string& strBtfAddr);
28CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting, 34CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting,
@@ -89,6 +95,15 @@ static const int BTF_RECV_TIMEOUT_SECS = 30 * 60;
89static const int BTF_SEND_STALL_SECS = 10 * 60; 95static const int BTF_SEND_STALL_SECS = 10 * 60;
90static const int BTF_HANDSHAKE_GRACE_SECS = 60; 96static const int BTF_HANDSHAKE_GRACE_SECS = 60;
91 97
98// How long a requested block gets before it is asked for again, in seconds.
99// Used twice: CNode::AskFor spaces the queued per-announcer block retries this
100// far apart instead of the two minutes transactions get, and the failover
101// sweep in main.cpp re-issues a getdata to a different peer once a sent
102// request has gone unanswered this long. A block body is at most 1 MB and
103// somebody demonstrably has it -- they announced it -- so on this timescale
104// "not here yet" means "not coming from that peer", not "still in transit".
105static const int BLOCK_FETCH_RETRY_SECS = 15;
106
92// A serialized object cannot be larger than MAX_SIZE, so no valid network 107// A serialized object cannot be larger than MAX_SIZE, so no valid network
93// message needs the old 256 MB header allowance. Keep the wire cap aligned with 108// message needs the old 256 MB header allowance. Keep the wire cap aligned with
94// what the deserializer can accept. How long a peer may sit on a partial 109// what the deserializer can accept. How long a peer may sit on a partial
@@ -109,6 +124,23 @@ static const unsigned int MAX_PROTOCOL_MESSAGE_SIZE = MAX_SIZE;
109// 125 is Bitcoin's number and remains a conservative public-node default. 124// 125 is Bitcoin's number and remains a conservative public-node default.
110static const unsigned int MAX_CONNECTIONS = 125; 125static const unsigned int MAX_CONNECTIONS = 125;
111 126
127// Cap inbound connections from any single address. Without it one source can
128// hold every slot up to MAX_CONNECTIONS -- exactly what happened to the
129// rendezvous relays, where a single IP parked hundreds of connections and
130// starved real peers. A legitimate peer, even a relay talking to a relay,
131// needs only a handful; 32 leaves generous room while denying any one address
132// more than a quarter of the table. The firewall connlimit on the relays does
133// the same thing a layer lower; this makes every build carry the defense.
134//
135// Loopback (127.0.0.0/8) is exempt in the accept path. A managed-Tor node
136// receives every inbound onion peer over its local hidden-service listener, so
137// they all appear as 127.0.0.1 -- capping loopback would cap onion peers as a
138// group, which is exactly wrong for a network moving toward onion-only. The
139// global MAX_CONNECTIONS still bounds the total; a per-onion-identity limit,
140// applied once identity is known after accept(), is the right future tool for
141// onion floods.
142static const unsigned int MAX_CONNECTIONS_PER_IP = 32;
143
112// A node marked for disconnect is normally held until its buffers drain, so 144// A node marked for disconnect is normally held until its buffers drain, so
113// a last message still goes out. But vSend cannot drain through a socket 145// a last message still goes out. But vSend cannot drain through a socket
114// whose far end is gone, and that made such a node immortal: it stayed in 146// whose far end is gone, and that made such a node immortal: it stayed in
@@ -795,13 +827,51 @@ public:
795 static int64 nLastTime; 827 static int64 nLastTime;
796 nLastTime = nNow = max(nNow, ++nLastTime); 828 nLastTime = nNow = max(nNow, ++nLastTime);
797 829
798 // Each retry is 2 minutes after the last 830 // Each retry is 2 minutes after the last -- and because nRequestTime
799 nRequestTime = max(nRequestTime + 2 * 60 * 1000000, nNow); 831 // is shared across peers, the same spacing is what strings requests
832 // for one inv across its announcers. For transactions that is pacing;
833 // for a new block it was the stall of issue #33: nine peers announce
834 // the block, the first one asked does not serve it, and the ninth
835 // candidate is not even asked until sixteen minutes in, with the pool
836 // mining on the stale tip the whole time.
837 //
838 // Blocks retry on the fetch timescale, but only while we are at the
839 // tip. That is where the stall costs something -- the pool keeps
840 // handing out jobs against a dead tip -- and it is also where the
841 // spacing is nearly free: one block is in flight, and its body has
842 // almost always arrived before the second announcer's turn comes
843 // round, so AlreadyHave drops the duplicate before a getdata is sent.
844 //
845 // While catching up neither half holds. A peer working through a
846 // 500-deep queue can be minutes away from block N without anything
847 // being wrong, so at 15s the second announcer is asked at +15s, the
848 // third at +30s, and the bulk download arrives once per announcer --
849 // up to eight times over. Nothing caps that: the eight-entry cap is on
850 // mapBlockFetch, which is a different mechanism from this spacing.
851 // Two minutes is the right number there, and it is what this line did
852 // before. Failover at the tip does not depend on this being 15s in any
853 // case: mapBlockFetch re-issues a stalled getdata to the next peer on
854 // its own timer, which is what actually fixes #33.
855 bool fFast = (inv.type == MSG_BLOCK && IsBlockFetchNearTip());
856 int64 nRetryUsecs = (fFast ? BLOCK_FETCH_RETRY_SECS : 2 * 60) * 1000000LL;
857 nRequestTime = max(nRequestTime + nRetryUsecs, nNow);
800 mapAskFor.insert(make_pair(nRequestTime, inv)); 858 mapAskFor.insert(make_pair(nRequestTime, inv));
801 } 859 }
802 860
803 861
804 862
863 // "sending: tx (N bytes)" alone was ~71% of a 13-hour production
864 // debug.log (issue #35), so tx sends are demoted to the /debugrelay-gated
865 // "relay" category; every other command's send trace stays on "net".
866 // The line is emitted in pieces across BeginMessage/EndMessage/
867 // AbortMessage, so all three must agree on the category or orphaned
868 // "(N bytes)" fragments would leak into a plain -debug log.
869 // Valid only mid-message (nPushPos != -1).
870 const char* SendLogCategory() const
871 {
872 return strcmp(GetMessageCommand(), "tx") == 0 ? "relay" : "net";
873 }
874
805 void BeginMessage(const char* pszCommand) 875 void BeginMessage(const char* pszCommand)
806 { 876 {
807 EnterCriticalSection(&cs_vSend); 877 EnterCriticalSection(&cs_vSend);
@@ -809,17 +879,19 @@ public:
809 AbortMessage(); 879 AbortMessage();
810 nPushPos = vSend.size(); 880 nPushPos = vSend.size();
811 vSend << CMessageHeader(pszCommand, 0); 881 vSend << CMessageHeader(pszCommand, 0);
812 if (LogAcceptsCategory("net")) printf("sending: %-12s ", pszCommand); 882 if (LogAcceptsCategory(SendLogCategory())) printf("sending: %-12s ", pszCommand);
813 } 883 }
814 884
815 void AbortMessage() 885 void AbortMessage()
816 { 886 {
817 if (nPushPos == -1) 887 if (nPushPos == -1)
818 return; 888 return;
889 // Capture before nPushPos is cleared; GetMessageCommand() needs it.
890 const char* pszCategory = SendLogCategory();
819 vSend.resize(nPushPos); 891 vSend.resize(nPushPos);
820 nPushPos = -1; 892 nPushPos = -1;
821 LeaveCriticalSection(&cs_vSend); 893 LeaveCriticalSection(&cs_vSend);
822 if (LogAcceptsCategory("net")) printf("(aborted)\n"); 894 if (LogAcceptsCategory(pszCategory)) printf("(aborted)\n");
823 } 895 }
824 896
825 void EndMessage() 897 void EndMessage()
@@ -827,7 +899,11 @@ public:
827 extern int nDropMessagesTest; 899 extern int nDropMessagesTest;
828 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0) 900 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0)
829 { 901 {
830 if (LogAcceptsCategory("net")) printf("dropmessages DROPPING SEND MESSAGE\n"); 902 // Same category as the "sending: " this line interrupts, so a tx
903 // does not announce its own drop under "net" having been gated
904 // off under "relay". nPushPos can be -1 here, and then
905 // GetMessageCommand() returns "" and this is "net" as before.
906 if (LogAcceptsCategory(SendLogCategory())) printf("dropmessages DROPPING SEND MESSAGE\n");
831 AbortMessage(); 907 AbortMessage();
832 return; 908 return;
833 } 909 }
@@ -839,10 +915,10 @@ public:
839 unsigned int nSize = vSend.size() - nPushPos - sizeof(CMessageHeader); 915 unsigned int nSize = vSend.size() - nPushPos - sizeof(CMessageHeader);
840 memcpy((char*)&vSend[nPushPos] + offsetof(CMessageHeader, nMessageSize), &nSize, sizeof(nSize)); 916 memcpy((char*)&vSend[nPushPos] + offsetof(CMessageHeader, nMessageSize), &nSize, sizeof(nSize));
841 917
842 if (LogAcceptsCategory("net")) printf("(%d bytes) ", nSize); 918 if (LogAcceptsCategory(SendLogCategory())) printf("(%d bytes) ", nSize);
843 //for (int i = nPushPos+sizeof(CMessageHeader); i < min(vSend.size(), nPushPos+sizeof(CMessageHeader)+20U); i++) 919 //for (int i = nPushPos+sizeof(CMessageHeader); i < min(vSend.size(), nPushPos+sizeof(CMessageHeader)+20U); i++)
844 // printf("%02x ", vSend[i] & 0xff); 920 // printf("%02x ", vSend[i] & 0xff);
845 if (LogAcceptsCategory("net")) printf("\n"); 921 if (LogAcceptsCategory(SendLogCategory())) printf("\n");
846 922
847 nPushPos = -1; 923 nPushPos = -1;
848 LeaveCriticalSection(&cs_vSend); 924 LeaveCriticalSection(&cs_vSend);
diff --git a/src/nostr.cpp b/src/nostr.cpp
index e2d839c..f44dc08 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -151,6 +151,19 @@ string BtfActiveRelay()
151 151
152void BtfSetActiveRelay(const string& relay) 152void BtfSetActiveRelay(const string& relay)
153{ 153{
154 // With /rvrelay set, the descriptor must never name anything but the pin.
155 // No caller can reach here with another relay today -- both registration
156 // paths draw their list from BtfAllRelays(), which returns the pin alone --
157 // but the descriptor is the one thing a miner obeys, so the invariant is
158 // enforced at the sink instead of being retrusted to every future caller.
159 if (fBtfRelayPinned &&
160 find(vBtfMeetingRelays.begin(), vBtfMeetingRelays.end(), relay) == vBtfMeetingRelays.end())
161 {
162 LogPrint("pool", "refusing to advertise meeting relay %s: /rvrelay pins %s\n",
163 relay.c_str(),
164 vBtfMeetingRelays.empty() ? "an empty list" : vBtfMeetingRelays[0].c_str());
165 return;
166 }
154 CRITICAL_BLOCK(cs_activeRelay) 167 CRITICAL_BLOCK(cs_activeRelay)
155 strBtfActiveRelay = relay; 168 strBtfActiveRelay = relay;
156} 169}
@@ -223,6 +236,18 @@ vector<string> BtfAllRelays()
223 return all; 236 return all;
224} 237}
225 238
239// Relays learned from Nostr, whether or not a pin is in force. Reporting only:
240// it exists so the pool can name what /rvrelay is excluding. Anything choosing
241// a relay to register or dial at must keep using BtfAllRelays().
242vector<string> BtfDiscoveredRelays()
243{
244 vector<string> out;
245 CRITICAL_BLOCK(cs_discoveredRelays)
246 foreach(const string& r, g_discoveredRelays)
247 out.push_back(r);
248 return out;
249}
250
226 251
227// 252//
228// Utilities 253// Utilities
@@ -1440,7 +1465,7 @@ static bool SeedFromRelay(CNostrKey& key, const string& relay)
1440 else if (createdAt > it->second) it->second = createdAt; // refresh 1465 else if (createdAt > it->second) it->second = createdAt; // refresh
1441 } 1466 }
1442 if (fNew && fDebug) 1467 if (fNew && fDebug)
1443 LogPrint("nostr", "Nostr: discovered .btf peer %s\n", peerAddr.c_str()); 1468 LogPrint("nostrdiscovery", "Nostr: discovered .btf peer %s\n", peerAddr.c_str());
1444 if (++nPeers > 500) break; 1469 if (++nPeers > 500) break;
1445 } 1470 }
1446 else if (type == "EOSE") 1471 else if (type == "EOSE")
diff --git a/src/nostr.h b/src/nostr.h
index 6dbbe26..0dd919d 100644
--- a/src/nostr.h
+++ b/src/nostr.h
@@ -60,6 +60,9 @@ extern bool fBtfRelayPinned;
60bool LooksLikeRelay(const std::string& r); 60bool LooksLikeRelay(const std::string& r);
61// Every relay to try: curated seeds + relays discovered from Nostr announcements. 61// Every relay to try: curated seeds + relays discovered from Nostr announcements.
62std::vector<std::string> BtfAllRelays(); 62std::vector<std::string> BtfAllRelays();
63// Relays discovered from Nostr, ignoring the pin. For reporting what a pin
64// excludes -- never for choosing a relay to register or dial at.
65std::vector<std::string> BtfDiscoveredRelays();
63 66
64struct BtfPoolAnnouncement 67struct BtfPoolAnnouncement
65{ 68{
diff --git a/src/randomx_pow.cpp b/src/randomx_pow.cpp
index 1d058d4..9c59364 100644
--- a/src/randomx_pow.cpp
+++ b/src/randomx_pow.cpp
@@ -8,6 +8,7 @@
8#include <thread> 8#include <thread>
9#include <vector> 9#include <vector>
10#include <mutex> 10#include <mutex>
11#include <system_error>
11 12
12// Fixed key (seed) that determines the RandomX cache/dataset. Keeping it 13// Fixed key (seed) that determines the RandomX cache/dataset. Keeping it
13// constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal 14// constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal
@@ -101,13 +102,28 @@ bool RandomXInitDataset(int nThreads)
101 102
102 std::vector<std::thread> workers; 103 std::vector<std::thread> workers;
103 unsigned long per = total / nThreads; 104 unsigned long per = total / nThreads;
104 for (int i = 0; i < nThreads; i++) 105 try
105 { 106 {
106 unsigned long start = i * per; 107 for (int i = 0; i < nThreads; i++)
107 unsigned long count = (i == nThreads - 1) ? (total - start) : per; 108 {
108 workers.emplace_back([start, count]() { 109 unsigned long start = i * per;
109 randomx_init_dataset(g_dataset, g_cache, start, count); 110 unsigned long count = (i == nThreads - 1) ? (total - start) : per;
110 }); 111 workers.emplace_back([start, count]() {
112 randomx_init_dataset(g_dataset, g_cache, start, count);
113 });
114 }
115 }
116 catch (const std::system_error& e)
117 {
118 // Same refusal as the allocation failure above: light mode is slow
119 // but alive, std::terminate is neither. Join the workers that did
120 // start before releasing the dataset out from under them.
121 for (auto& w : workers) w.join();
122 randomx_release_dataset(g_dataset);
123 g_dataset = NULL;
124 printf("RandomX: cannot spawn dataset init threads (%s), staying in light mode\n",
125 e.what());
126 return false;
111 } 127 }
112 for (auto& w : workers) w.join(); 128 for (auto& w : workers) w.join();
113 129
diff --git a/src/readme.txt b/src/readme.txt
index 752be66..7490a9a 100644
--- a/src/readme.txt
+++ b/src/readme.txt
@@ -16,7 +16,7 @@ This product includes software developed by the OpenSSL Project.
16Differences from the original Bitcoin 16Differences from the original Bitcoin
17------------------------------------- 17-------------------------------------
18- Own genesis block (22/Jul/2026 headline, independent chain) 18- Own genesis block (22/Jul/2026 headline, independent chain)
19- Own magic bytes (0xbf 0x1a 0x5c 0xfd) and P2P port 8433 19- Own magic bytes (0xbf 0x20 0x5c 0xfd) and P2P port 8433
20- Addresses start with "B" (ADDRESSVERSION = 25) 20- Addresses start with "B" (ADDRESSVERSION = 25)
21- Consensus fixes applied: value overflow (CVE-2010-5139), duplicate inputs, 21- Consensus fixes applied: value overflow (CVE-2010-5139), duplicate inputs,
22 money range checks (MoneyRange) 22 money range checks (MoneyRange)
diff --git a/src/rpc.cpp b/src/rpc.cpp
index a4b53ca..3827cd5 100644
--- a/src/rpc.cpp
+++ b/src/rpc.cpp
@@ -115,15 +115,28 @@ static uint256 ShareTargetFromDifficulty(double difficulty)
115{ 115{
116 if (difficulty <= 0.0) difficulty = 1.0; 116 if (difficulty <= 0.0) difficulty = 1.0;
117 static const uint256 diff1 = CBigNum(bnProofOfWorkLimit).getuint256(); 117 static const uint256 diff1 = CBigNum(bnProofOfWorkLimit).getuint256();
118 // Representability floor. shareTarget = diff1 / difficulty, and diff1 is
119 // ~uint256(0) >> 12, i.e. a hair under 2^244 -- so the true target stops
120 // fitting in a uint256 once difficulty reaches 2^-12 (= 1/4096), and the
121 // fixed-point divide below breaks even earlier in a second way: the
122 // (uint64)(difficulty * 65536.0) divisor truncates to zero for any
123 // difficulty under 2^-16. Both thresholds sit far below VARDIFF_MIN
124 // (0.001), so live operation never comes near this, but a caller that
125 // bypasses vardiff (or a future VARDIFF_MIN) must not divide by zero or
126 // hand back getuint256()'s low-256-bit truncation of an oversized
127 // quotient -- a wrapped target is not merely wrong, it is arbitrarily
128 // HARDER than difficulty 1. At these difficulties every hash is meant to
129 // be a share, so the honest answer is the easiest representable target.
130 // (This replaces a dead branch that read `if (t < diff1 && difficulty <
131 // 1.0)`: t = diff1/difficulty, so difficulty < 1.0 implies t > diff1 and
132 // the two conditions could never both hold.)
133 if (difficulty < 1.0 / 4096.0) return ~uint256(0);
118 // shareTarget = diff1 / difficulty 134 // shareTarget = diff1 / difficulty
119 CBigNum bn; bn.setuint256(diff1); 135 CBigNum bn; bn.setuint256(diff1);
120 // Multiply by 2^16 to keep precision, divide, shift back 136 // Multiply by 2^16 to keep precision, divide, shift back
121 bn <<= 16; 137 bn <<= 16;
122 bn /= (uint64)(difficulty * 65536.0); 138 bn /= (uint64)(difficulty * 65536.0);
123 uint256 t = bn.getuint256(); 139 return bn.getuint256();
124 // Cap at all-ones (maximum, easiest possible)
125 if (t < diff1 && difficulty < 1.0) t = ~uint256(0);
126 return t;
127} 140}
128 141
129// Expected number of hashes to find one share at a given Stratum difficulty, 142// Expected number of hashes to find one share at a given Stratum difficulty,
@@ -223,6 +236,29 @@ static bool RebuildJob()
223 // From the pool, so the template's payout key is in wallet.dat before 236 // From the pool, so the template's payout key is in wallet.dat before
224 // any miner is handed work against it. 237 // any miner is handed work against it.
225 vector<unsigned char> vchPubKey = GetKeyFromPool(); 238 vector<unsigned char> vchPubKey = GetKeyFromPool();
239 if (vchPubKey.empty()) {
240 // Empty means the wallet could not produce a key: encrypted and
241 // locked, or key generation itself failed. Serialised, an empty
242 // vector is a zero-length push, so the coinbase would become
243 // `<empty> OP_CHECKSIG` -- a script nobody can satisfy. Every
244 // block mined against such a job burns its whole subsidy, while
245 // QueuePayouts still records the full obligation and the miners
246 // are then paid out of coin that block never contributed.
247 // BitcoinMiner refuses to mine in this state; refuse to hand the
248 // pool's miners work in it too. printf and not LogPrint for the
249 // reason given above the deaf-node refusal, and throttled the
250 // same way -- the no-job watchdog retries every few seconds for
251 // as long as the condition lasts.
252 static int64 nLastSaidNoKey = 0;
253 if (GetTime() - nLastSaidNoKey > 60) {
254 nLastSaidNoKey = GetTime();
255 printf("RebuildJob: no wallet key available for the coinbase "
256 "(wallet locked, or key generation failed -- the "
257 "GetKeyFromPool line above says which) -- not issuing "
258 "jobs\n");
259 }
260 return false;
261 }
226 static std::atomic<uint32_t> sExtra{0}; 262 static std::atomic<uint32_t> sExtra{0};
227 uint32_t extraNonce = ++sExtra; 263 uint32_t extraNonce = ++sExtra;
228 264
@@ -267,16 +303,29 @@ static bool RebuildJob()
267 map<uint256, CTxIndex> pool; 303 map<uint256, CTxIndex> pool;
268 vector<char> vfAlreadyAdded(mapTransactions.size()); 304 vector<char> vfAlreadyAdded(mapTransactions.size());
269 bool fFoundSomething = true; 305 bool fFoundSomething = true;
270 unsigned int sz = 0; 306 // The cap is MAX_BLOCK_SIZE_GEN, not MAX_SIZE/2. MAX_SIZE is
271 // The cap is MAX_BLOCK_SIZE/2, not MAX_SIZE/2. MAX_SIZE is the 307 // the 32 MB serialisation ceiling, so half of it is 16 MB --
272 // 32 MB serialisation ceiling, so half of it is 16 MB -- this 308 // this builder would happily assemble a template that its own
273 // builder would happily assemble a template that its own
274 // CheckBlock() rejects at the 1 MB consensus cap. This is the 309 // CheckBlock() rejects at the 1 MB consensus cap. This is the
275 // pool's job builder, so a block over the cap is not one 310 // pool's job builder, so a block over the cap is not one
276 // wasted solve, it is every miner on the pool working a 311 // wasted solve, it is every miner on the pool working a
277 // template the network will reject. The inner break bounds the 312 // template the network will reject.
278 // overshoot to a single transaction within a pass. 313 //
279 while (fFoundSomething && sz < MAX_BLOCK_SIZE / 2) 314 // The inner break used to bound the overshoot to one
315 // transaction per pass. One transaction is still too many:
316 // nothing caps how big a single transaction may be, so that
317 // bound was worth up to MAX_SIZE. Asking whether each one fits
318 // before appending it bounds the overshoot at zero instead.
319 //
320 // Seeded with the header, tx count and coinbase, because
321 // CheckBlock() weighs the whole serialized block.
322 unsigned int sz = ::GetSerializeSize(block, SER_DISK);
323 // And the sigop budget, which CheckBlock() enforces and this
324 // builder never counted against. Same counting function
325 // CheckBlock() sums, seeded from the coinbase because it sums
326 // over all of vtx.
327 unsigned int nSigOps = block.vtx[0].GetSigOpCount();
328 while (fFoundSomething && sz < MAX_BLOCK_SIZE_GEN)
280 { 329 {
281 fFoundSomething = false; 330 fFoundSomething = false;
282 unsigned int n = 0; 331 unsigned int n = 0;
@@ -287,16 +336,26 @@ static bool RebuildJob()
287 continue; 336 continue;
288 CTransaction& tx = (*mi).second; 337 CTransaction& tx = (*mi).second;
289 if (tx.IsCoinBase() || !tx.IsFinal()) continue; 338 if (tx.IsCoinBase() || !tx.IsFinal()) continue;
339 // Skip rather than stop: one transaction too big for
340 // the room left does not mean the next is. Ordered
341 // before ConnectInputs because ConnectInputs credits
342 // nFees by reference on success, and a transaction we
343 // then decline would leave the coinbase claiming a fee
344 // this block does not collect.
345 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
346 unsigned int nTxSigOps = tx.GetSigOpCount();
347 if (!BlockTemplateFits(sz, nSigOps, nTxSize, nTxSigOps))
348 continue;
290 map<uint256, CTxIndex> tmp(pool); 349 map<uint256, CTxIndex> tmp(pool);
291 if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees, 350 if (!tx.ConnectInputs(txdb, tmp, CDiskTxPos(1,1,1), 0, nFees,
292 false, true, tx.GetMinFee(block.vtx.size() < 100))) 351 false, true, tx.GetMinFee(block.vtx.size() < 100)))
293 continue; 352 continue;
294 swap(pool, tmp); 353 swap(pool, tmp);
295 block.vtx.push_back(tx); 354 block.vtx.push_back(tx);
296 sz += ::GetSerializeSize(tx, SER_NETWORK); 355 sz += nTxSize;
356 nSigOps += nTxSigOps;
297 vfAlreadyAdded[n] = true; 357 vfAlreadyAdded[n] = true;
298 fFoundSomething = true; 358 fFoundSomething = true;
299 if (sz > MAX_BLOCK_SIZE / 2) break;
300 } 359 }
301 } 360 }
302 } 361 }
@@ -776,6 +835,29 @@ static void PublishPendingPayouts(const VersionedBlob& v)
776 gPayoutLedgerPublishedSeq, "pending payout ledger"); 835 gPayoutLedgerPublishedSeq, "pending payout ledger");
777} 836}
778 837
838// Parse a persisted block-hash field, refusing to guess. SetHex() reads
839// whatever hex prefix it finds and stops at the first character it does not
840// recognise, so a corrupt value does not fail to parse -- it parses as a
841// DIFFERENT hash. Both shapes of that are wrong in a dangerous direction: a
842// value with a hex-looking prefix ("deadbeef...") becomes a hash no block
843// ever had, which PayoutBlockStillOnChain reads as an orphaned block and
844// FlushMaturePayouts then VOIDS the debt; a value with no hex prefix at all
845// becomes 0, which is grandfathered past the orphan check on the strength
846// of garbage. A field that is not the 64-digit hex string ToString() wrote
847// is corruption, and corruption is a load failure like any other parse
848// error -- the file is quarantined, not read around.
849static uint256 ParsePayoutBlockHash(const std::string& str)
850{
851 if (str.size() != 64 ||
852 str.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos)
853 throw std::runtime_error(
854 strprintf("blockHash \"%s\" is not a 64-digit hex string",
855 str.c_str()));
856 uint256 h;
857 h.SetHex(str);
858 return h;
859}
860
779static void LoadPendingPayouts() 861static void LoadPendingPayouts()
780{ 862{
781 std::string path = PayoutFilePath(); 863 std::string path = PayoutFilePath();
@@ -797,7 +879,8 @@ static void LoadPendingPayouts()
797 // field. Leave blockHash zero and they are grandfathered past the 879 // field. Leave blockHash zero and they are grandfathered past the
798 // orphan check rather than voided on a guess. 880 // orphan check rather than voided on a guess.
799 if (obj.contains("blockHash") && obj["blockHash"].is_string()) 881 if (obj.contains("blockHash") && obj["blockHash"].is_string())
800 pp.blockHash.SetHex(obj["blockHash"].get<std::string>()); 882 pp.blockHash =
883 ParsePayoutBlockHash(obj["blockHash"].get<std::string>());
801 if (obj.contains("blockHeight") && obj["blockHeight"].is_number()) 884 if (obj.contains("blockHeight") && obj["blockHeight"].is_number())
802 pp.blockHeight = obj["blockHeight"].get<int>(); 885 pp.blockHeight = obj["blockHeight"].get<int>();
803 // Upstream's tolerance for an entry with no usable height, kept 886 // Upstream's tolerance for an entry with no usable height, kept
@@ -976,7 +1059,8 @@ static void LoadPaying()
976 r.amount = obj["amount"].get<int64>(); 1059 r.amount = obj["amount"].get<int64>();
977 r.matureAtHeight = obj["matureAtHeight"].get<int>(); 1060 r.matureAtHeight = obj["matureAtHeight"].get<int>();
978 if (obj.contains("blockHash") && obj["blockHash"].is_string()) 1061 if (obj.contains("blockHash") && obj["blockHash"].is_string())
979 r.blockHash.SetHex(obj["blockHash"].get<std::string>()); 1062 r.blockHash =
1063 ParsePayoutBlockHash(obj["blockHash"].get<std::string>());
980 if (obj.contains("blockHeight") && obj["blockHeight"].is_number()) 1064 if (obj.contains("blockHeight") && obj["blockHeight"].is_number())
981 r.blockHeight = obj["blockHeight"].get<int>(); 1065 r.blockHeight = obj["blockHeight"].get<int>();
982 if (obj.contains("retries") && obj["retries"].is_number()) 1066 if (obj.contains("retries") && obj["retries"].is_number())
@@ -1186,11 +1270,35 @@ static bool PayoutBlockStillOnChain(const uint256& blockHash)
1186 return false; 1270 return false;
1187} 1271}
1188 1272
1189static std::string PayoutComment(int blockHeight) 1273// The comment every pre-upgrade payout transaction carries, and therefore the
1274// ONLY needle the legacy fallback in FindRecordedPoolPayout may search with.
1275// This format is frozen: change it and every payment those builds made stops
1276// matching, every one of those debts looks unpaid, and reconciliation pays
1277// them all a second time.
1278static std::string LegacyPayoutComment(int blockHeight)
1190{ 1279{
1191 return strprintf("Pool payout height %d", blockHeight); 1280 return strprintf("Pool payout height %d", blockHeight);
1192} 1281}
1193 1282
1283// The comment written on new sends. It names the funding block, because the
1284// height alone cannot: a height that is won, lost to a reorg, and won again
1285// yields two rounds whose height-only comments are identical -- and identical
1286// amounts too, whenever the same miner held both rounds. The structured
1287// pool_payout_* metadata already keys the payment by hash for the code; this
1288// puts the same fact where an operator reading the wallet can see it, and
1289// makes a new transaction unmatchable by the legacy needle even if the
1290// structured keys are ever lost.
1291//
1292// A zero hash keeps the legacy form: those records predate hash keying, and
1293// there is no hash to name.
1294static std::string PayoutComment(int blockHeight, const uint256& blockHash)
1295{
1296 if (blockHash == 0)
1297 return LegacyPayoutComment(blockHeight);
1298 return strprintf("Pool payout height %d block %s", blockHeight,
1299 blockHash.ToString().c_str());
1300}
1301
1194// Startup reconciliation. 1302// Startup reconciliation.
1195// 1303//
1196// This used to be the crash-recovery mechanism: it scanned the wallet with 1304// This used to be the crash-recovery mechanism: it scanned the wallet with
@@ -1345,14 +1453,37 @@ static void MarkPoolRoundForfeited(const std::string& id,
1345 PublishPoolRounds(snap); 1453 PublishPoolRounds(snap);
1346} 1454}
1347 1455
1456// Does this round have a published round proof?
1457//
1458// Existence answers a temporal question the legacy fallback below needs:
1459// which builds could have paid this round. RecordPoolRound writes the proof
1460// when the round is created, and structured payout metadata (upstream
1461// cbdd06d) is an ancestor of round proofs (upstream 83ba822) -- no build
1462// able to write a proof has ever sent a payment that only a comment can
1463// identify. A round with a proof therefore cannot have a comment-only
1464// payment; one that matches it is another round's, colliding on the height.
1465static bool PoolRoundHasProof(const std::string& id)
1466{
1467 std::lock_guard<std::mutex> lk(gPoolRoundsMutex);
1468 for (const PoolRoundProof& r : gPoolRounds)
1469 if (r.id == id)
1470 return true;
1471 return false;
1472}
1473
1348// Which wallet transaction, if any, already paid this? 1474// Which wallet transaction, if any, already paid this?
1349// 1475//
1350// Two kinds of evidence, in order of quality. 1476// Two kinds of evidence, in order of quality.
1351// 1477//
1352// Structured (pool_payout_id / pool_payout_address / pool_payout_amount) is 1478// Structured (pool_payout_id / pool_payout_address / pool_payout_amount,
1353// upstream's, and it is exact: the id is derived from the block hash, so it 1479// plus pool_payout_block since this series) is upstream's, extended. The id
1354// names one round and one round only. Every transaction this build sends 1480// is derived from the block hash, but only from the FIRST sixteen hex digits
1355// carries it. 1481// of it -- and proof-of-work concentrates zeros exactly there, so the id can
1482// name two same-height rounds identically. New sends therefore record the
1483// full hash as pool_payout_block, and a structured match must agree with it
1484// wherever both sides have one; a transaction without the key was sent
1485// before it existed, and for those the id is the best evidence there is.
1486// Every transaction this build sends carries all four keys.
1356// 1487//
1357// But NO transaction this pool has ever sent carries it. Every payout in the 1488// But NO transaction this pool has ever sent carries it. Every payout in the
1358// existing wallet has only a "comment". So on the first boot after this 1489// existing wallet has only a "comment". So on the first boot after this
@@ -1375,11 +1506,22 @@ static void MarkPoolRoundForfeited(const std::string& id,
1375// (round, address) pair -- from the ledger's paidTxids, which is durable, 1506// (round, address) pair -- from the ledger's paidTxids, which is durable,
1376// and from what this process has already claimed. One legacy transaction 1507// and from what this process has already claimed. One legacy transaction
1377// can therefore settle at most one debt, so N transactions can never 1508// can therefore settle at most one debt, so N transactions can never
1378// satisfy more than N obligations. 1509// satisfy more than N obligations;
1379// 1510//
1380// What is left unresolved by construction is two pre-upgrade rounds at the 1511// 4. it is not consulted at all for a round that has a round proof
1381// SAME height paying the same address the same amount, since the comment 1512// (fAllowLegacy, decided by the caller from PoolRoundHasProof). Proofs
1382// carries only the height. That needs a reorg. 1513// are written when a round is created, and only by builds whose every
1514// payment names its round exactly -- so a comment-only transaction
1515// cannot be a payment for such a round, only a same-height collision
1516// from an older one, and skipping the fallback cannot hide a real
1517// payment: the structured probe above already found any that exist.
1518//
1519// What is left unresolved by construction is a comment-only transaction and
1520// a PROOFLESS round at the same height paying the same address the same
1521// amount: two grandfathered zero-hash twins, or a twin pair from the builds
1522// that keyed the ledger by hash before any metadata reached the wallet. The
1523// comment carries only the height, so nothing ties the transaction to one
1524// twin rather than the other. That needs a reorg.
1383// 1525//
1384// This used to say the losing twin's ledger entry is voided by the orphan 1526// This used to say the losing twin's ledger entry is voided by the orphan
1385// check before it can be paid. That is wrong: PayoutBlockStillOnChain() 1527// check before it can be paid. That is wrong: PayoutBlockStillOnChain()
@@ -1413,8 +1555,10 @@ enum PayoutProbeResult {
1413 1555
1414static PayoutProbeResult FindRecordedPoolPayout(const std::string& id, 1556static PayoutProbeResult FindRecordedPoolPayout(const std::string& id,
1415 int blockHeight, 1557 int blockHeight,
1558 const uint256& blockHash,
1416 const std::string& address, 1559 const std::string& address,
1417 int64 amount, 1560 int64 amount,
1561 bool fAllowLegacy,
1418 const std::set<std::string>& excluded, 1562 const std::set<std::string>& excluded,
1419 std::string& txidRet) 1563 std::string& txidRet)
1420{ 1564{
@@ -1425,7 +1569,7 @@ static PayoutProbeResult FindRecordedPoolPayout(const std::string& id,
1425 CScript want; 1569 CScript want;
1426 if (fHaveScript) 1570 if (fHaveScript)
1427 want << OP_DUP << OP_HASH160 << h160 << OP_EQUALVERIFY << OP_CHECKSIG; 1571 want << OP_DUP << OP_HASH160 << h160 << OP_EQUALVERIFY << OP_CHECKSIG;
1428 const std::string comment = PayoutComment(blockHeight); 1572 const std::string comment = LegacyPayoutComment(blockHeight);
1429 1573
1430 // Candidate legacy matches, adjudicated after cs_mapWallet is released. 1574 // Candidate legacy matches, adjudicated after cs_mapWallet is released.
1431 // 1575 //
@@ -1458,10 +1602,25 @@ static PayoutProbeResult FindRecordedPoolPayout(const std::string& id,
1458 map<string, string>::const_iterator amtIt = wtx.mapValue.find("pool_payout_amount"); 1602 map<string, string>::const_iterator amtIt = wtx.mapValue.find("pool_payout_amount");
1459 if (amtIt == wtx.mapValue.end() || amtIt->second != strprintf("%lld", amount)) 1603 if (amtIt == wtx.mapValue.end() || amtIt->second != strprintf("%lld", amount))
1460 continue; 1604 continue;
1605 // The id alone cannot tell twin rounds at one height apart
1606 // (see above); the full hash can, where the transaction
1607 // recorded one. Where it did not, the id decides, as it
1608 // always has -- refusing those transactions would make every
1609 // payment sent before the key existed look unpaid and pay it
1610 // again.
1611 if (blockHash != 0) {
1612 map<string, string>::const_iterator blkIt =
1613 wtx.mapValue.find("pool_payout_block");
1614 if (blkIt != wtx.mapValue.end()
1615 && blkIt->second != blockHash.ToString())
1616 continue; // a twin round's payment, not this round's
1617 }
1461 txidRet = wtx.GetHash().GetHex(); 1618 txidRet = wtx.GetHash().GetHex();
1462 return PAYOUT_PROBE_STRUCTURED; 1619 return PAYOUT_PROBE_STRUCTURED;
1463 } 1620 }
1464 1621
1622 if (!fAllowLegacy)
1623 continue; // comment-only evidence is inadmissible: rule 4
1465 if (!fHaveScript) 1624 if (!fHaveScript)
1466 continue; 1625 continue;
1467 // The comment is written only by the payout path, so its presence 1626 // The comment is written only by the payout path, so its presence
@@ -1732,7 +1891,9 @@ static void FlushMaturePayouts()
1732 } 1891 }
1733 int64 total = 0; 1892 int64 total = 0;
1734 for (auto& kv : it->amounts) total += kv.second; 1893 for (auto& kv : it->amounts) total += kv.second;
1735 LogPrint("payout", "[payout] block %d (%s) was orphaned -- voiding %zu" 1894 // Always: one line per voided entry, never a collapsed summary.
1895 // A reorg voids these in a burst from this one source line.
1896 LogPrintAlways("payout", "[payout] block %d (%s) was orphaned -- voiding %zu"
1736 " payment(s) totalling %s\n", 1897 " payment(s) totalling %s\n",
1737 it->blockHeight, it->blockHash.ToString().substr(0, 14).c_str(), 1898 it->blockHeight, it->blockHash.ToString().substr(0, 14).c_str(),
1738 it->amounts.size(), FormatMoney(total).c_str()); 1899 it->amounts.size(), FormatMoney(total).c_str());
@@ -1931,7 +2092,7 @@ static void RecordPayoutTxid(const PayingRecord& r, const std::string& txid)
1931// disagreeing. 2092// disagreeing.
1932static void ForfeitPayoutRecipient(const PayingRecord& r, const std::string& reason) 2093static void ForfeitPayoutRecipient(const PayingRecord& r, const std::string& reason)
1933{ 2094{
1934 LogPrint("payout", "[payout] writing off %s owed to %s for block %d: %s\n", 2095 LogPrintAlways("payout", "[payout] writing off %s owed to %s for block %d: %s\n",
1935 FormatMoney(r.amount).c_str(), r.address.c_str(), r.blockHeight, 2096 FormatMoney(r.amount).c_str(), r.address.c_str(), r.blockHeight,
1936 reason.c_str()); 2097 reason.c_str());
1937 VersionedBlob blob; 2098 VersionedBlob blob;
@@ -2036,7 +2197,7 @@ static void PayoutThreadFn(void*)
2036 // funding block can land at any point in that window. Upstream has 2197 // funding block can land at any point in that window. Upstream has
2037 // no equivalent -- it decides once, when the entry matures. 2198 // no equivalent -- it decides once, when the entry matures.
2038 if (!PayoutBlockStillOnChain(r.blockHash)) { 2199 if (!PayoutBlockStillOnChain(r.blockHash)) {
2039 LogPrint("payout", "[payout] block %d (%s) orphaned while queued" 2200 LogPrintAlways("payout", "[payout] block %d (%s) orphaned while queued"
2040 " -- dropping %s to %s\n", 2201 " -- dropping %s to %s\n",
2041 r.blockHeight, r.blockHash.ToString().substr(0, 14).c_str(), 2202 r.blockHeight, r.blockHash.ToString().substr(0, 14).c_str(),
2042 FormatMoney(r.amount).c_str(), r.address.c_str()); 2203 FormatMoney(r.amount).c_str(), r.address.c_str());
@@ -2080,7 +2241,7 @@ static void PayoutThreadFn(void*)
2080 const std::string reason = 2241 const std::string reason =
2081 strprintf("'%s' is not a valid address, so no transaction" 2242 strprintf("'%s' is not a valid address, so no transaction"
2082 " can pay it", r.address.c_str()); 2243 " can pay it", r.address.c_str());
2083 LogPrint("payout", "[payout] SKIPPED bad address '%s' --" 2244 LogPrintAlways("payout", "[payout] SKIPPED bad address '%s' --"
2084 " %s owed for block %d cannot be paid\n", 2245 " %s owed for block %d cannot be paid\n",
2085 r.address.c_str(), FormatMoney(r.amount).c_str(), 2246 r.address.c_str(), FormatMoney(r.amount).c_str(),
2086 r.blockHeight); 2247 r.blockHeight);
@@ -2099,9 +2260,15 @@ static void PayoutThreadFn(void*)
2099 std::set<std::string> excluded; 2260 std::set<std::string> excluded;
2100 CollectForeignPayoutTxids(r.blockHeight, r.blockHash, r.address, 2261 CollectForeignPayoutTxids(r.blockHeight, r.blockHash, r.address,
2101 claimKey, excluded); 2262 claimKey, excluded);
2263 // A round with a published proof was created by a build whose
2264 // every payment names its round exactly, so only structured
2265 // evidence can settle it -- a comment match would be another
2266 // round's payment colliding on the height.
2267 const bool fAllowLegacy = !PoolRoundHasProof(id);
2102 r.fProbeWallet = false; 2268 r.fProbeWallet = false;
2103 PayoutProbeResult probe = FindRecordedPoolPayout( 2269 PayoutProbeResult probe = FindRecordedPoolPayout(
2104 id, r.blockHeight, r.address, r.amount, excluded, recovered); 2270 id, r.blockHeight, r.blockHash, r.address, r.amount,
2271 fAllowLegacy, excluded, recovered);
2105 if (probe != PAYOUT_PROBE_NONE) { 2272 if (probe != PAYOUT_PROBE_NONE) {
2106 if (probe == PAYOUT_PROBE_LEGACY) { 2273 if (probe == PAYOUT_PROBE_LEGACY) {
2107 // Loud on purpose: this is the upgrade path, where the 2274 // Loud on purpose: this is the upgrade path, where the
@@ -2186,7 +2353,7 @@ static void PayoutThreadFn(void*)
2186 } 2353 }
2187 2354
2188 CWalletTx wtx; 2355 CWalletTx wtx;
2189 wtx.mapValue["comment"] = PayoutComment(r.blockHeight); 2356 wtx.mapValue["comment"] = PayoutComment(r.blockHeight, r.blockHash);
2190 // Upstream's structured metadata. This is what makes the wallet 2357 // Upstream's structured metadata. This is what makes the wallet
2191 // probe above possible at all -- the old comment-only form could 2358 // probe above possible at all -- the old comment-only form could
2192 // not distinguish two rounds paying the same address the same 2359 // not distinguish two rounds paying the same address the same
@@ -2195,6 +2362,11 @@ static void PayoutThreadFn(void*)
2195 wtx.mapValue["pool_payout_id"] = id; 2362 wtx.mapValue["pool_payout_id"] = id;
2196 wtx.mapValue["pool_payout_address"] = r.address; 2363 wtx.mapValue["pool_payout_address"] = r.address;
2197 wtx.mapValue["pool_payout_amount"] = strprintf("%lld", r.amount); 2364 wtx.mapValue["pool_payout_amount"] = strprintf("%lld", r.amount);
2365 // The full hash, not only the id's sixteen-digit prefix: the
2366 // prefix of a proof-of-work hash is where the zeros live, and
2367 // the probe needs to tell twin rounds at one height apart.
2368 if (r.blockHash != 0)
2369 wtx.mapValue["pool_payout_block"] = r.blockHash.ToString();
2198 2370
2199 SendMoneyOutcome outcome; 2371 SendMoneyOutcome outcome;
2200 if (SendMoney(sc, r.amount, wtx, outcome)) { 2372 if (SendMoney(sc, r.amount, wtx, outcome)) {
@@ -2205,7 +2377,7 @@ static void PayoutThreadFn(void*)
2205 // described by at least one of them at every instant. 2377 // described by at least one of them at every instant.
2206 RecordPayoutTxid(r, wtx.GetHash().GetHex()); 2378 RecordPayoutTxid(r, wtx.GetHash().GetHex());
2207 ForgetPayment(r); 2379 ForgetPayment(r);
2208 LogPrint("payout", "[payout] paid %s to %s (tx %s)\n", 2380 LogPrintAlways("payout", "[payout] paid %s to %s (tx %s)\n",
2209 FormatMoney(r.amount).c_str(), r.address.c_str(), 2381 FormatMoney(r.amount).c_str(), r.address.c_str(),
2210 wtx.GetHash().ToString().substr(0, 12).c_str()); 2382 wtx.GetHash().ToString().substr(0, 12).c_str());
2211 } else { 2383 } else {
@@ -2263,7 +2435,7 @@ static void PayoutThreadFn(void*)
2263 // going away must not turn the log into one line a minute 2435 // going away must not turn the log into one line a minute
2264 // forever, and must not go quiet either. 2436 // forever, and must not go quiet either.
2265 if (r.retries <= 5 || r.retries % 60 == 0) 2437 if (r.retries <= 5 || r.retries % 60 == 0)
2266 LogPrint("payout", "[payout] FAILED to pay %s to %s" 2438 LogPrintAlways("payout", "[payout] FAILED to pay %s to %s"
2267 " (%s spendable, wallet unlocked, block %d on" 2439 " (%s spendable, wallet unlocked, block %d on"
2268 " the main chain) -- attempt %d, retrying in" 2440 " the main chain) -- attempt %d, retrying in"
2269 " 60s; this debt stands until it is paid\n", 2441 " 60s; this debt stands until it is paid\n",
@@ -2471,6 +2643,31 @@ static const double VARDIFF_MAX = 1e12;
2471static const int64 VARDIFF_STALL_SECS = 45; // 3x share target -- reacts faster without false-triggering on normal variance 2643static const int64 VARDIFF_STALL_SECS = 45; // 3x share target -- reacts faster without false-triggering on normal variance
2472static const double VARDIFF_STALL_CUT = 4.0; // divide difficulty by this much 2644static const double VARDIFF_STALL_CUT = 4.0; // divide difficulty by this much
2473 2645
2646// #7: m->hashRate is an EWMA sampled only when a share arrives, so a miner
2647// that stops hashing keeps its last estimate forever and RefreshStats sums
2648// it into the announced pool total for as long as the connection sits open
2649// -- the published pool hashrate ran several times over ground truth
2650// because of exactly this. MinerReportedHashRate() below applies the
2651// policy: a miner gets a grace of HASHRATE_IDLE_MISSES expected share
2652// intervals (expected interval = expected hashes per share at its
2653// difficulty, divided by the estimate itself) with no share, after which
2654// the figure we report falls off with a half-life of one more such
2655// interval. The interval is clamped to [HASHRATE_MIN_INTERVAL,
2656// HASHRATE_MAX_INTERVAL] so a fast miner at the vardiff floor (expected
2657// interval of milliseconds) is not punished for ordinary share-delivery
2658// jitter, and a very slow miner's grace does not stretch past the point
2659// where its estimate means anything. At the vardiff target of one share
2660// per 15s that is a minute of grace, half the rate reported 75s in, and
2661// nothing reported after about seventeen minutes of silence.
2662static const double HASHRATE_IDLE_MISSES = 4.0; // grace, in expected share intervals
2663static const double HASHRATE_MIN_INTERVAL = 1.0; // seconds; floor for the expected interval
2664static const double HASHRATE_MAX_INTERVAL = 3600.0; // seconds; ceiling for the expected interval
2665// Half-lives past the grace beyond which we report a flat zero. 2^-64 of the
2666// original estimate is not a number anyone needs, and stopping here keeps
2667// pow() from walking down into the denormals for a miner that has been quiet
2668// for hours.
2669static const double HASHRATE_DECAY_CUTOFF = 64.0;
2670
2474struct Miner { 2671struct Miner {
2475 btf_socket_t fd; 2672 btf_socket_t fd;
2476 bool authorised = false; 2673 bool authorised = false;
@@ -2498,11 +2695,80 @@ struct Miner {
2498 int64 lastShareTime = 0; // GetTime() of the previous accepted share 2695 int64 lastShareTime = 0; // GetTime() of the previous accepted share
2499 double hashRate = 0.0; // smoothed H/s estimate 2696 double hashRate = 0.0; // smoothed H/s estimate
2500 2697
2698 // Replay guard for mining.submit (#8): nonces already credited on this
2699 // connection for the job it is currently working. Keyed to the job id in
2700 // seenNoncesJob and cleared by SendJob whenever that id changes, so a
2701 // resubmission under a NEW job is never turned away, while a replayed
2702 // (job_id, nonce) pair under the same job is refused before it can be
2703 // credited twice into roundShareCount -- the figure the payout split
2704 // divides by.
2705 std::set<uint32_t> seenNonces;
2706 std::string seenNoncesJob;
2707
2501 explicit Miner(btf_socket_t f) 2708 explicit Miner(btf_socket_t f)
2502 : fd(f), connectTime(GetTime()), 2709 : fd(f), connectTime(GetTime()),
2503 extranonce2(gNextExtranonce2.fetch_add(1)) {} 2710 extranonce2(gNextExtranonce2.fetch_add(1)) {}
2504}; 2711};
2505 2712
2713// What we publish as a miner's H/s: the last EWMA sample, faded out if shares
2714// have stopped arriving (#7, policy at HASHRATE_IDLE_MISSES).
2715//
2716// This reads m->hashRate and never writes it. That is the whole design. The
2717// first attempt decayed the stored estimate in place from the event loop's
2718// sweep, which put a running accumulator behind a figure that is only ever
2719// displayed, and the accumulator misbehaved in two ways:
2720//
2721// - `expected` was recomputed from the already-decayed estimate on every
2722// pass, so each decay raised `expected`, which raised `grace`, which is
2723// the very thing gating the next decay. `grace` simply chased `silent`
2724// upward and the reported rate settled into a 1/t crawl instead of the
2725// half-life curve the comment above promises -- 291x too high at ten
2726// half-lives past the grace. Issue #7 is about the pool over-reporting;
2727// that under-corrected it for hours.
2728// - Once `expected` reached HASHRATE_MAX_INTERVAL it could not rise any
2729// further, the feedback that had been gating each pass was gone, and the
2730// decay began compounding once a second against the total elapsed
2731// silence -- taking a miner from 0.4% of its baseline to 1e-11 of it in
2732// the next hundred seconds, four hours in, for no reason a reader of
2733// either the code or the announced number could see.
2734//
2735// Computing the value instead of accumulating it removes the failure mode
2736// rather than tuning it: with no stored decay state there is nothing for a
2737// tick rate, a missed pass or a duplicated pass to perturb, and the answer
2738// depends only on how long the miner has actually been quiet. It also keeps
2739// the fade out of the estimator -- the EWMA on the share path still blends
2740// against the value it last computed, not against a faded copy of it, so a
2741// miner that resumes recovers on exactly the curve it would have without any
2742// of this.
2743//
2744// `now` is a parameter rather than a GetTime() call so that one refresh fades
2745// every miner to the same instant, and so the policy is directly testable.
2746static double MinerReportedHashRate(const Miner* m, int64 now)
2747{
2748 // Nothing sampled yet: a fresh connection, or one that has found a single
2749 // share and so has no interval to have been timed over. Nothing to fade,
2750 // and nothing to divide by below.
2751 if (m->hashRate <= 0.0 || m->lastShareTime <= 0) return m->hashRate;
2752
2753 double expected = ExpectedHashesForDifficulty(m->difficulty) / m->hashRate;
2754 // Written so that a NaN would land on the floor rather than sail through:
2755 // the clamp is what guarantees `expected` is finite and strictly positive
2756 // below, whatever the division produced.
2757 if (!(expected > HASHRATE_MIN_INTERVAL)) expected = HASHRATE_MIN_INTERVAL;
2758 if (expected > HASHRATE_MAX_INTERVAL) expected = HASHRATE_MAX_INTERVAL;
2759
2760 double silent = (double)(now - m->lastShareTime);
2761 double grace = HASHRATE_IDLE_MISSES * expected;
2762 // grace is at least HASHRATE_IDLE_MISSES seconds, so a clock that has
2763 // stepped backwards gives a negative `silent`, takes this branch, and
2764 // reports the sample unchanged. Nothing here can scale an estimate up.
2765 if (!(silent > grace)) return m->hashRate;
2766
2767 double halflives = (silent - grace) / expected;
2768 if (halflives >= HASHRATE_DECAY_CUTOFF) return 0.0;
2769 return m->hashRate * pow(0.5, halflives);
2770}
2771
2506static std::vector<Miner*> gMiners; 2772static std::vector<Miner*> gMiners;
2507 2773
2508struct FoundBlock { int height; int64 ts; std::string addr; }; 2774struct FoundBlock { int height; int64 ts; std::string addr; };
@@ -2553,6 +2819,18 @@ static void LocalAcceptFn(void*)
2553 break; 2819 break;
2554 continue; 2820 continue;
2555 } 2821 }
2822 // #6: let the OS probe a peer that has gone silent, so a half-open
2823 // session eventually errors out of recv and lands in the event
2824 // loop's existing reap path instead of sitting in gMiners forever.
2825 // This is a backstop, not the mechanism: the default cadence before
2826 // the first probe is hours on most stacks, so the idle timeout in
2827 // PoolEventLoop fires long before keepalive does. Miners arriving
2828 // via a relay ride a loopback socket pair whose remote transport is
2829 // the rendezvous socket owned by the tunnel pump threads; keepalive
2830 // cannot reach across that, which is part of why the idle timeout
2831 // exists.
2832 int keepalive = 1;
2833 setsockopt(c, SOL_SOCKET, SO_KEEPALIVE, (char*)&keepalive, sizeof(keepalive));
2556 LogPrint("pool", "[pool] miner arrived via loopback -- pushing to event loop\n"); 2834 LogPrint("pool", "[pool] miner arrived via loopback -- pushing to event loop\n");
2557 PushReady((btf_socket_t)c); 2835 PushReady((btf_socket_t)c);
2558 } 2836 }
@@ -2596,6 +2874,16 @@ static const int64 POOL_ACTIVE_WINDOW = 300; // a miner is "active" if it
2596static const int64 POOL_UNAUTH_GRACE_SECS = 60; // a connection still not 2874static const int64 POOL_UNAUTH_GRACE_SECS = 60; // a connection still not
2597 // authorized after this long is 2875 // authorized after this long is
2598 // idle, not merely starting up 2876 // idle, not merely starting up
2877// #6: a Miner used to leave gMiners only when RecvLine reported a clean
2878// disconnect. A half-open session (miner host died, NAT state expired,
2879// cable pulled) sends no FIN and reports no error, so it sat in the table
2880// forever -- authorize outpaced disconnect 2:1 on the live pool, and the
2881// table grew toward MAX_POOL_MINERS, past which real miners are refused.
2882// lastSeen is stamped on every complete line received; a miner that has
2883// said nothing for this long is gone in every way that matters. Ten
2884// minutes is far beyond the vardiff target (a share every 15s) and the
2885// stall watchdog (45s), so no miner that is actually hashing comes close.
2886static const int64 POOL_IDLE_TIMEOUT_SECS = 600;
2599 2887
2600// Non-blocking recv: pull whatever bytes are available now into buf, then 2888// Non-blocking recv: pull whatever bytes are available now into buf, then
2601// return one complete line if one is ready. Three outcomes: 2889// return one complete line if one is ready. Three outcomes:
@@ -2680,6 +2968,17 @@ static json MakeNotifyParams(const StratumJob& job, const CBlock& b,
2680static void SendJob(Miner* m, bool clean) 2968static void SendJob(Miner* m, bool clean)
2681{ 2969{
2682 if (!gHaveJob) return; 2970 if (!gHaveJob) return;
2971 // Every path that hands a miner a new job funnels through here, so this
2972 // is the one place the replay guard needs to follow the job id: nonces
2973 // credited under the previous job mean nothing under the new one. Keying
2974 // the clear to the id (rather than clearing unconditionally) keeps a
2975 // same-job re-notify -- the vardiff retarget and stall watchdog both
2976 // re-send the CURRENT job -- from reopening the duplicate window it
2977 // exists to close.
2978 if (m->seenNoncesJob != gCurrentJob.jobId) {
2979 m->seenNonces.clear();
2980 m->seenNoncesJob = gCurrentJob.jobId;
2981 }
2683 CBlock mb = BuildMinerBlock(gCurrentJob, m->extranonce2); 2982 CBlock mb = BuildMinerBlock(gCurrentJob, m->extranonce2);
2684 // Use this miner's own shareTarget (set by vardiff) in the notify params. 2983 // Use this miner's own shareTarget (set by vardiff) in the notify params.
2685 // The participant miner reads p[3] as its share target, and HandleLine 2984 // The participant miner reads p[3] as its share target, and HandleLine
@@ -2938,6 +3237,22 @@ static bool HandleLine(Miner* m, const std::string& rawLine,
2938 } 3237 }
2939 unsigned int nNonce; memcpy(&nNonce, nb.data(), 4); 3238 unsigned int nNonce; memcpy(&nNonce, nb.data(), 4);
2940 3239
3240 // Reject a replayed (job, nonce) before it costs a rebuild and a
3241 // RandomX hash: nothing about the work below distinguishes a second
3242 // submission of a valid share from the first, so without this check
3243 // it passes every test and is credited again -- and roundShareCount
3244 // is what the payout split divides by, so each replay multiplies the
3245 // address's cut of the round. The set is per-connection and cleared
3246 // by SendJob on every job change, so only a same-job repeat is
3247 // refused.
3248 if (m->seenNonces.count(nNonce)) {
3249 LogPrint("worker", "[worker->pool] duplicate share from %s"
3250 " nonce=%u job=%s -- already credited, not counting again\n",
3251 m->address.c_str(), nNonce, submitJobId.c_str());
3252 reply(false, "Duplicate share");
3253 return true;
3254 }
3255
2941 // Rebuild this miner's exact block (unique merkle via extranonce2) 3256 // Rebuild this miner's exact block (unique merkle via extranonce2)
2942 CBlock b = BuildMinerBlock(gCurrentJob, m->extranonce2); 3257 CBlock b = BuildMinerBlock(gCurrentJob, m->extranonce2);
2943 b.nNonce = nNonce; 3258 b.nNonce = nNonce;
@@ -2965,6 +3280,7 @@ static bool HandleLine(Miner* m, const std::string& rawLine,
2965 m->roundShares++; 3280 m->roundShares++;
2966 m->totalShares++; 3281 m->totalShares++;
2967 m->vardiffShares++; 3282 m->vardiffShares++;
3283 m->seenNonces.insert(nNonce); // replay guard: credit each nonce once per job
2968 int64 shareTime = GetTime(); 3284 int64 shareTime = GetTime();
2969 m->lastSeen = shareTime; 3285 m->lastSeen = shareTime;
2970 roundShareCount[m->address]++; 3286 roundShareCount[m->address]++;
@@ -3261,7 +3577,8 @@ static std::string PayoutTestAddWalletTx(const std::string& comment,
3261 int64 amount, 3577 int64 amount,
3262 int nonce, 3578 int nonce,
3263 const std::string& structuredId, 3579 const std::string& structuredId,
3264 bool fConfirmed = true) 3580 bool fConfirmed = true,
3581 const std::string& structuredBlock = "")
3265{ 3582{
3266 CWalletTx wtx; 3583 CWalletTx wtx;
3267 wtx.vin.resize(1); 3584 wtx.vin.resize(1);
@@ -3276,6 +3593,8 @@ static std::string PayoutTestAddWalletTx(const std::string& comment,
3276 wtx.mapValue["pool_payout_id"] = structuredId; 3593 wtx.mapValue["pool_payout_id"] = structuredId;
3277 wtx.mapValue["pool_payout_address"] = address; 3594 wtx.mapValue["pool_payout_address"] = address;
3278 wtx.mapValue["pool_payout_amount"] = strprintf("%lld", amount); 3595 wtx.mapValue["pool_payout_amount"] = strprintf("%lld", amount);
3596 if (!structuredBlock.empty())
3597 wtx.mapValue["pool_payout_block"] = structuredBlock;
3279 } 3598 }
3280 uint256 hash = wtx.GetHash(); 3599 uint256 hash = wtx.GetHash();
3281 if (fConfirmed) { 3600 if (fConfirmed) {
@@ -3825,34 +4144,37 @@ int RunPayoutLedgerSelfTest()
3825 const int height = 8080; 4144 const int height = 8080;
3826 const int64 amount = 7 * COIN; 4145 const int64 amount = 7 * COIN;
3827 const std::string id = PayoutIdForBlock(height, 0); 4146 const std::string id = PayoutIdForBlock(height, 0);
3828 const std::string comment = PayoutComment(height); 4147 const std::string comment = LegacyPayoutComment(height);
3829 std::set<std::string> none; 4148 std::set<std::string> none;
3830 std::string found; 4149 std::string found;
4150 // No round proof exists for these rounds, so legacy evidence is
4151 // admissible -- computed exactly as the payout thread computes it.
4152 const bool fAllowLegacy = !PoolRoundHasProof(id);
3831 4153
3832 check(FindRecordedPoolPayout(id, height, addrA, amount, none, found) 4154 check(FindRecordedPoolPayout(id, height, 0, addrA, amount, fAllowLegacy, none, found)
3833 == PAYOUT_PROBE_NONE, 4155 == PAYOUT_PROBE_NONE,
3834 "an empty wallet is not evidence that anything was paid"); 4156 "an empty wallet is not evidence that anything was paid");
3835 4157
3836 const std::string legacyTxid = 4158 const std::string legacyTxid =
3837 PayoutTestAddWalletTx(comment, addrA, amount, 1, ""); 4159 PayoutTestAddWalletTx(comment, addrA, amount, 1, "");
3838 check(FindRecordedPoolPayout(id, height, addrA, amount, none, found) 4160 check(FindRecordedPoolPayout(id, height, 0, addrA, amount, fAllowLegacy, none, found)
3839 == PAYOUT_PROBE_LEGACY && found == legacyTxid, 4161 == PAYOUT_PROBE_LEGACY && found == legacyTxid,
3840 "a payment carrying only a pre-upgrade comment is recognised as" 4162 "a payment carrying only a pre-upgrade comment is recognised as"
3841 " already paid"); 4163 " already paid");
3842 4164
3843 check(FindRecordedPoolPayout(id, height, addrB, amount, none, found) 4165 check(FindRecordedPoolPayout(id, height, 0, addrB, amount, fAllowLegacy, none, found)
3844 == PAYOUT_PROBE_NONE, 4166 == PAYOUT_PROBE_NONE,
3845 "the legacy match requires the output to pay THIS recipient"); 4167 "the legacy match requires the output to pay THIS recipient");
3846 check(FindRecordedPoolPayout(id, height, addrA, amount + COIN, none, found) 4168 check(FindRecordedPoolPayout(id, height, 0, addrA, amount + COIN, fAllowLegacy, none, found)
3847 == PAYOUT_PROBE_NONE, 4169 == PAYOUT_PROBE_NONE,
3848 "the legacy match requires the output to pay exactly this amount"); 4170 "the legacy match requires the output to pay exactly this amount");
3849 check(FindRecordedPoolPayout(id, height + 1, addrA, amount, none, found) 4171 check(FindRecordedPoolPayout(id, height + 1, 0, addrA, amount, fAllowLegacy, none, found)
3850 == PAYOUT_PROBE_NONE, 4172 == PAYOUT_PROBE_NONE,
3851 "the legacy match requires the comment for this block height"); 4173 "the legacy match requires the comment for this block height");
3852 4174
3853 std::set<std::string> spent; 4175 std::set<std::string> spent;
3854 spent.insert(legacyTxid); 4176 spent.insert(legacyTxid);
3855 check(FindRecordedPoolPayout(id, height, addrA, amount, spent, found) 4177 check(FindRecordedPoolPayout(id, height, 0, addrA, amount, fAllowLegacy, spent, found)
3856 == PAYOUT_PROBE_NONE, 4178 == PAYOUT_PROBE_NONE,
3857 "one legacy transaction cannot settle a second debt"); 4179 "one legacy transaction cannot settle a second debt");
3858 4180
@@ -3861,7 +4183,7 @@ int RunPayoutLedgerSelfTest()
3861 // exactly what the structured metadata exists to prevent. 4183 // exactly what the structured metadata exists to prevent.
3862 const std::string otherId = PayoutIdForBlock(height, 0) + "-other"; 4184 const std::string otherId = PayoutIdForBlock(height, 0) + "-other";
3863 PayoutTestAddWalletTx(comment, addrB, amount, 2, otherId); 4185 PayoutTestAddWalletTx(comment, addrB, amount, 2, otherId);
3864 check(FindRecordedPoolPayout(id, height, addrB, amount, none, found) 4186 check(FindRecordedPoolPayout(id, height, 0, addrB, amount, fAllowLegacy, none, found)
3865 == PAYOUT_PROBE_NONE, 4187 == PAYOUT_PROBE_NONE,
3866 "another round's structured payment is not legacy evidence for" 4188 "another round's structured payment is not legacy evidence for"
3867 " this one"); 4189 " this one");
@@ -3869,7 +4191,7 @@ int RunPayoutLedgerSelfTest()
3869 // The structured path still wins where it applies. 4191 // The structured path still wins where it applies.
3870 const std::string structuredTxid = 4192 const std::string structuredTxid =
3871 PayoutTestAddWalletTx(comment, addrB, amount, 3, id); 4193 PayoutTestAddWalletTx(comment, addrB, amount, 3, id);
3872 check(FindRecordedPoolPayout(id, height, addrB, amount, none, found) 4194 check(FindRecordedPoolPayout(id, height, 0, addrB, amount, fAllowLegacy, none, found)
3873 == PAYOUT_PROBE_STRUCTURED && found == structuredTxid, 4195 == PAYOUT_PROBE_STRUCTURED && found == structuredTxid,
3874 "structured payout metadata is matched in preference to a comment"); 4196 "structured payout metadata is matched in preference to a comment");
3875 4197
@@ -3883,7 +4205,7 @@ int RunPayoutLedgerSelfTest()
3883 PayoutTestClearBlockIndex(); 4205 PayoutTestClearBlockIndex();
3884 const std::string addrC = PayoutTestAddress(0x33); 4206 const std::string addrC = PayoutTestAddress(0x33);
3885 PayoutTestAddWalletTx(comment, addrC, amount, 4, "", false); 4207 PayoutTestAddWalletTx(comment, addrC, amount, 4, "", false);
3886 check(FindRecordedPoolPayout(id, height, addrC, amount, none, found) 4208 check(FindRecordedPoolPayout(id, height, 0, addrC, amount, fAllowLegacy, none, found)
3887 == PAYOUT_PROBE_NONE, 4209 == PAYOUT_PROBE_NONE,
3888 "a pre-upgrade payout that never confirmed is not read as" 4210 "a pre-upgrade payout that never confirmed is not read as"
3889 " payment"); 4211 " payment");
@@ -3893,7 +4215,7 @@ int RunPayoutLedgerSelfTest()
3893 mapWallet.clear(); 4215 mapWallet.clear();
3894 const std::string confirmedTxid = 4216 const std::string confirmedTxid =
3895 PayoutTestAddWalletTx(comment, addrC, amount, 4, "", true); 4217 PayoutTestAddWalletTx(comment, addrC, amount, 4, "", true);
3896 check(FindRecordedPoolPayout(id, height, addrC, amount, none, found) 4218 check(FindRecordedPoolPayout(id, height, 0, addrC, amount, fAllowLegacy, none, found)
3897 == PAYOUT_PROBE_LEGACY && found == confirmedTxid, 4219 == PAYOUT_PROBE_LEGACY && found == confirmedTxid,
3898 "the same payout, confirmed, is"); 4220 "the same payout, confirmed, is");
3899 4221
@@ -3904,7 +4226,7 @@ int RunPayoutLedgerSelfTest()
3904 PayoutTestAddWalletTx(comment, addrC, amount, 5, "", false); 4226 PayoutTestAddWalletTx(comment, addrC, amount, 5, "", false);
3905 std::set<std::string> spentFirst; 4227 std::set<std::string> spentFirst;
3906 spentFirst.insert(confirmedTxid); 4228 spentFirst.insert(confirmedTxid);
3907 check(FindRecordedPoolPayout(id, height, addrC, amount, spentFirst, found) 4229 check(FindRecordedPoolPayout(id, height, 0, addrC, amount, fAllowLegacy, spentFirst, found)
3908 == PAYOUT_PROBE_LEGACY && found == alsoConfirmed, 4230 == PAYOUT_PROBE_LEGACY && found == alsoConfirmed,
3909 "an unconfirmed transaction does not hide a confirmed one behind" 4231 "an unconfirmed transaction does not hide a confirmed one behind"
3910 " it"); 4232 " it");
@@ -4419,6 +4741,248 @@ int RunPayoutLedgerSelfTest()
4419 } 4741 }
4420#endif 4742#endif
4421 4743
4744 // -- 14. The payout comment names the funding block ----------------------
4745 //
4746 // Same height, two hashes: the shape a reorg leaves when the pool wins a
4747 // height, loses the block, and wins the height again. The legacy needle is
4748 // frozen at the pre-upgrade format, so pre-upgrade payments keep matching;
4749 // a new-format comment never matches it, so a new send can never be read
4750 // as a pre-upgrade payment for a twin round.
4751 {
4752 PayoutTestResetState();
4753 CRITICAL_BLOCK(cs_mapWallet)
4754 mapWallet.clear();
4755 PayoutTestClearBlockIndex();
4756
4757 const int height = 9090;
4758 const int64 amount = 3 * COIN;
4759 uint256 hashA = 0, hashB = 0;
4760 hashA.SetHex("00000000000000000000000000000000000000000000000000000000000000aa");
4761 hashB.SetHex("00000000000000000000000000000000000000000000000000000000000000bb");
4762
4763 check(LegacyPayoutComment(height) ==
4764 strprintf("Pool payout height %d", height),
4765 "the legacy needle is frozen at the format pre-upgrade builds"
4766 " wrote");
4767 check(PayoutComment(height, 0) == LegacyPayoutComment(height),
4768 "a grandfathered zero-hash record still writes the legacy form");
4769 check(PayoutComment(height, hashA) ==
4770 strprintf("Pool payout height %d block %s", height,
4771 hashA.ToString().c_str()),
4772 "a hash-keyed record's comment names its funding block");
4773 check(PayoutComment(height, hashA) != PayoutComment(height, hashB),
4774 "twin rounds at one height carry distinguishable comments");
4775
4776 // A transaction wearing the NEW comment format and -- hypothetically
4777 // -- no structured metadata must not be matched by the legacy needle:
4778 // the fallback exists for pre-upgrade transactions, and the new format
4779 // proves this is not one.
4780 const std::string idA = PayoutIdForBlock(height, hashA);
4781 const std::string idB = PayoutIdForBlock(height, hashB);
4782 std::set<std::string> none;
4783 std::string found;
4784 // Neither twin has a round proof here (section 15 covers the gate),
4785 // and their ids collide, so one answer serves both.
4786 const bool fAllowLegacy = !PoolRoundHasProof(idA);
4787 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount, 1, "");
4788 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, fAllowLegacy, none, found)
4789 == PAYOUT_PROBE_NONE,
4790 "a new-format comment is never legacy evidence for a twin round");
4791 check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, fAllowLegacy, none, found)
4792 == PAYOUT_PROBE_NONE,
4793 "nor a match channel for its own round -- structured metadata is");
4794
4795 // Round A's own payment, sent by this build, is found by its
4796 // structured id.
4797 CRITICAL_BLOCK(cs_mapWallet)
4798 mapWallet.clear();
4799 PayoutTestClearBlockIndex();
4800 const std::string paidA =
4801 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount,
4802 2, idA);
4803 check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, fAllowLegacy, none, found)
4804 == PAYOUT_PROBE_STRUCTURED && found == paidA,
4805 "the winning twin's own payment is found by its structured id");
4806
4807 // The id is derived from the FIRST sixteen hex digits of the hash --
4808 // the end proof-of-work fills with zeros -- so twin hashes that agree
4809 // there collide in id, and a payment carrying no pool_payout_block
4810 // answers for either twin. That is the accepted trade for payments
4811 // sent before the key existed, pinned here so it stays a decision.
4812 check(idA == idB,
4813 "twin hashes differing only past the sixteenth hex digit collide"
4814 " in id -- the reason the full hash must decide");
4815 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, fAllowLegacy, none, found)
4816 == PAYOUT_PROBE_STRUCTURED,
4817 "a payment without the block key answers for a colliding twin --"
4818 " the accepted residual for pre-key transactions");
4819
4820 // With the full hash recorded -- as every send now records it -- the
4821 // twins are told apart and the re-won height is paid.
4822 CRITICAL_BLOCK(cs_mapWallet)
4823 mapWallet.clear();
4824 PayoutTestClearBlockIndex();
4825 const std::string keyedA =
4826 PayoutTestAddWalletTx(PayoutComment(height, hashA), addrA, amount,
4827 3, idA, true, hashA.ToString());
4828 check(FindRecordedPoolPayout(idA, height, hashA, addrA, amount, fAllowLegacy, none, found)
4829 == PAYOUT_PROBE_STRUCTURED && found == keyedA,
4830 "a payment carrying the full block hash settles its own round");
4831 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount, fAllowLegacy, none, found)
4832 == PAYOUT_PROBE_NONE,
4833 "and is no evidence for the twin, so the re-won height is paid");
4834
4835 CRITICAL_BLOCK(cs_mapWallet)
4836 mapWallet.clear();
4837 PayoutTestClearBlockIndex();
4838 }
4839
4840 // -- 15. A proof-bearing round rejects comment-only evidence -------------
4841 //
4842 // The surviving half of the same-height collision: a pre-upgrade
4843 // transaction carries only "Pool payout height H", so if H is won again
4844 // after a reorg, it matches the new round's (height, address, amount)
4845 // exactly and would settle a debt it never paid. The new round has a
4846 // round proof -- written at creation, and only by builds whose payments
4847 // all name their round -- so for it, comment-only evidence is
4848 // inadmissible and the miner is paid. A round without a proof keeps
4849 // today's bias: the evidence is admissible and doubt resolves to
4850 // "already paid".
4851 {
4852 PayoutTestResetState();
4853 CRITICAL_BLOCK(cs_mapWallet)
4854 mapWallet.clear();
4855 PayoutTestClearBlockIndex();
4856 const std::string roundsPath = dir + "/pool_rounds_gate_selftest.json";
4857 const std::string savedRoundsFile = strPoolRoundsFile;
4858 strPoolRoundsFile = roundsPath;
4859 remove(roundsPath.c_str());
4860
4861 const int height = 7171;
4862 const int64 amount = 4 * COIN;
4863 uint256 hashB = 0;
4864 hashB.SetHex("00000000000000000000000000000000000000000000000000000000000000cc");
4865 const std::string idB = PayoutIdForBlock(height, hashB);
4866 std::set<std::string> none;
4867 std::string found;
4868
4869 // The colliding pre-upgrade payment: confirmed, comment-only, same
4870 // height, same address, same amount -- the strongest wrong evidence
4871 // there is.
4872 PayoutTestAddWalletTx(LegacyPayoutComment(height), addrA, amount, 1, "");
4873
4874 check(!PoolRoundHasProof(idB),
4875 "a round with no recorded proof does not claim one");
4876 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount,
4877 !PoolRoundHasProof(idB), none, found)
4878 == PAYOUT_PROBE_LEGACY,
4879 "without a proof the doubt is genuine and still resolves to"
4880 " already-paid");
4881
4882 PoolRoundProof round;
4883 round.id = idB;
4884 round.blockHeight = height;
4885 round.matureAtHeight = height + COINBASE_SPENDABLE_DEPTH;
4886 round.payouts[addrA] = amount;
4887 RecordPoolRound(round);
4888 check(PoolRoundHasProof(idB),
4889 "recording the round proof marks the round as this lineage's");
4890 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount,
4891 !PoolRoundHasProof(idB), none, found)
4892 == PAYOUT_PROBE_NONE,
4893 "with a proof, a same-height comment-only transaction is not"
4894 " evidence, and the re-won height is paid");
4895
4896 // Structured evidence still is: the round's own payment, recovered.
4897 const std::string paidB =
4898 PayoutTestAddWalletTx(PayoutComment(height, hashB), addrA, amount,
4899 2, idB, true, hashB.ToString());
4900 check(FindRecordedPoolPayout(idB, height, hashB, addrA, amount,
4901 !PoolRoundHasProof(idB), none, found)
4902 == PAYOUT_PROBE_STRUCTURED && found == paidB,
4903 "the gate does not blind the probe to the round's own structured"
4904 " payment");
4905
4906 CRITICAL_BLOCK(cs_mapWallet)
4907 mapWallet.clear();
4908 PayoutTestClearBlockIndex();
4909 remove(roundsPath.c_str());
4910 remove((roundsPath + ".tmp").c_str());
4911 strPoolRoundsFile = savedRoundsFile;
4912 }
4913
4914 // -- 16. A malformed block hash is corruption, not a hash ----------------
4915 //
4916 // SetHex() parses whatever hex prefix it finds and stops, so a corrupt
4917 // blockHash did not fail to load -- it loaded as a DIFFERENT hash.
4918 // "deadbeef..." became 0xdeadbeef, a hash no block ever had, which the
4919 // orphan check reads as an orphaned block and the next flush VOIDS the
4920 // debt; a value with no hex prefix became 0, which is grandfathered past
4921 // the orphan check instead. Either way a corrupt file silently changed
4922 // what the pool owed. It is a load failure now, handled like every other
4923 // parse error: quarantine, and never rewrite.
4924 {
4925 PayoutTestResetState();
4926 remove(ledgerPath.c_str());
4927 remove(payingPath.c_str());
4928
4929 const std::string badLedger = strprintf(
4930 "[{\"amounts\":{\"%s\":100000000},"
4931 "\"blockHash\":\"deadbeef oops, not a hash\","
4932 "\"blockHeight\":10,\"matureAtHeight\":10}]", addrA.c_str());
4933 check(PayoutTestWriteFile(ledgerPath, badLedger),
4934 "wrote a ledger whose blockHash is not a 64-digit hex string");
4935 LoadPendingPayouts();
4936 {
4937 std::lock_guard<std::mutex> lk(gPayoutLedgerMutex);
4938 check(gPendingPayouts.empty(),
4939 "no entry is loaded with a guessed hash");
4940 }
4941 std::string gone;
4942 check(!PayoutTestReadFile(ledgerPath, gone),
4943 "the ledger with the corrupt hash is quarantined");
4944
4945 const std::string badPaying = strprintf(
4946 "[{\"address\":\"%s\",\"amount\":100000000,"
4947 "\"blockHash\":\"00000000cafe\",\"blockHeight\":10,"
4948 "\"matureAtHeight\":10}]", addrA.c_str());
4949 check(PayoutTestWriteFile(payingPath, badPaying),
4950 "wrote an in-flight list whose blockHash is truncated");
4951 LoadPaying();
4952 {
4953 std::lock_guard<std::mutex> lk(gPayoutExecMutex);
4954 check(gPaying.empty(),
4955 "no in-flight record is loaded with a guessed hash");
4956 }
4957 check(!PayoutTestReadFile(payingPath, gone),
4958 "the in-flight list with the corrupt hash is quarantined");
4959
4960 // The two spellings every real file uses still load: the 64-digit
4961 // hash of a keyed round, and the 64 zeros this build writes for a
4962 // grandfathered entry.
4963 PayoutTestResetState();
4964 remove(ledgerPath.c_str());
4965 const std::string goodLedger = strprintf(
4966 "[{\"amounts\":{\"%s\":100000000},"
4967 "\"blockHash\":\"00000000000000000000000000000000000000000000000000000000000000cd\","
4968 "\"blockHeight\":10,\"matureAtHeight\":10},"
4969 "{\"amounts\":{\"%s\":200000000},"
4970 "\"blockHash\":\"%s\","
4971 "\"blockHeight\":20,\"matureAtHeight\":20}]",
4972 addrA.c_str(), addrB.c_str(), uint256(0).ToString().c_str());
4973 check(PayoutTestWriteFile(ledgerPath, goodLedger),
4974 "wrote a ledger in both real hash spellings");
4975 LoadPendingPayouts();
4976 {
4977 std::lock_guard<std::mutex> lk(gPayoutLedgerMutex);
4978 check(gPendingPayouts.size() == 2 &&
4979 gPendingPayouts[0].blockHash != 0 &&
4980 gPendingPayouts[1].blockHash == 0,
4981 "a real hash and a serialised zero hash both still load");
4982 }
4983 remove(ledgerPath.c_str());
4984 }
4985
4422 // -- clean up ------------------------------------------------------------ 4986 // -- clean up ------------------------------------------------------------
4423 PayoutTestResetState(); 4987 PayoutTestResetState();
4424 CRITICAL_BLOCK(cs_mapWallet) 4988 CRITICAL_BLOCK(cs_mapWallet)
@@ -4579,6 +5143,82 @@ int RunPoolStratumSelfTest()
4579 strPoolRoundsFile = savedRoundsFile; 5143 strPoolRoundsFile = savedRoundsFile;
4580 gPoolRounds = savedRounds; 5144 gPoolRounds = savedRounds;
4581 5145
5146 // -- hashrate decay (#7) --------------------------------------------------
5147 // The first version of this decayed m->hashRate in place from the event
5148 // loop's sweep, which made the reported figure a function of how the loop
5149 // happened to run rather than of how long the miner had been quiet. These
5150 // pin the properties that fixes: sampling changes nothing, and the answer
5151 // depends only on elapsed silence.
5152 {
5153 // A miner exactly on the vardiff target: 4096 hashes per share at
5154 // difficulty 1.0, one share per 15s, so 4096/15 H/s. Its expected
5155 // interval is 15s and its grace is 60s.
5156 const double base = 4096.0 / 15.0;
5157 const int64 t0 = 1766000000; // any plausible non-zero wall clock
5158 Miner decayed(INVALID_SOCKET);
5159 decayed.difficulty = 1.0;
5160 decayed.hashRate = base;
5161 decayed.lastShareTime = t0;
5162
5163 check(MinerReportedHashRate(&decayed, t0 + 60) == base,
5164 "hashrate decay: a miner inside its grace period reports its full estimate");
5165
5166 // Sampling must not consume the estimate. Twenty passes is one second
5167 // of the event loop's old sweep; a thousand is nearly a minute of it.
5168 double once = MinerReportedHashRate(&decayed, t0 + 61);
5169 bool idempotent = (decayed.hashRate == base);
5170 for (int i = 0; i < 1000 && idempotent; i++)
5171 idempotent = (MinerReportedHashRate(&decayed, t0 + 61) == once) &&
5172 (decayed.hashRate == base);
5173 check(idempotent,
5174 "hashrate decay: repeated sampling returns one value and leaves the estimate alone");
5175
5176 // One second past a 60s grace is 1/15th of a half-life: ~4.5% off, not
5177 // the estimate gone. A miner at the vardiff target overshoots its grace
5178 // roughly 1.8% of the time (e^-4) purely by share-timing variance, and
5179 // must not be zeroed for it.
5180 check(once > 0.94 * base && once < 0.96 * base,
5181 "hashrate decay: one second past grace costs a few percent, not the estimate");
5182 double three = MinerReportedHashRate(&decayed, t0 + 63);
5183 check(three > 0.86 * base && three < 0.88 * base,
5184 "hashrate decay: three seconds past grace costs ~13%");
5185
5186 // One half-life past the grace is exactly half.
5187 double half = MinerReportedHashRate(&decayed, t0 + 75);
5188 check(half > 0.499 * base && half < 0.501 * base,
5189 "hashrate decay: one expected interval past grace halves the estimate");
5190
5191 // A miner that has genuinely stopped ends up reported as nothing --
5192 // the whole point of #7. The old in-place version could not get here:
5193 // its decay fed back into its own grace and settled into a 1/t crawl.
5194 check(MinerReportedHashRate(&decayed, t0 + 3600) == 0.0,
5195 "hashrate decay: a miner silent for an hour is reported as zero");
5196
5197 // A clock that steps backwards must never inflate what we announce.
5198 check(MinerReportedHashRate(&decayed, t0 - 3600) == base,
5199 "hashrate decay: a backwards clock reports the estimate unchanged, never higher");
5200
5201 // Fresh connection, and a connection with one share and so no interval
5202 // to have timed: both must report nothing rather than divide by zero.
5203 Miner fresh(INVALID_SOCKET);
5204 check(MinerReportedHashRate(&fresh, t0) == 0.0,
5205 "hashrate decay: a miner that never submitted a share reports zero");
5206 Miner single(INVALID_SOCKET);
5207 single.lastShareTime = t0;
5208 check(MinerReportedHashRate(&single, t0 + 86400) == 0.0,
5209 "hashrate decay: a miner with no rate sample yet reports zero");
5210
5211 // Nothing in the swept range may produce a NaN, an infinity, a negative
5212 // rate, or a rate above the estimate it started from.
5213 bool finite = true;
5214 for (int64 s = -100; s < 200000 && finite; s += 37) {
5215 double v = MinerReportedHashRate(&decayed, t0 + s);
5216 finite = !std::isnan(v) && !std::isinf(v) && v >= 0.0 && v <= base;
5217 }
5218 check(finite,
5219 "hashrate decay: stays finite, non-negative and non-amplifying across a long silence");
5220 }
5221
4582 gCurrentJob = savedJob; 5222 gCurrentJob = savedJob;
4583 gHaveJob = savedHaveJob; 5223 gHaveJob = savedHaveJob;
4584 5224
@@ -4606,6 +5246,10 @@ static void RefreshStats(int blocksFoundThisSession, uint64 roundShareTotal)
4606 if (m->authorised) gStats.authorizedMiners++; 5246 if (m->authorised) gStats.authorizedMiners++;
4607 5247
4608 gWorkerStats.clear(); 5248 gWorkerStats.clear();
5249 // One timestamp for the whole sweep (#7): every miner is faded to the
5250 // same instant, so the per-worker rows and the total they are summed
5251 // into describe one moment rather than a smear across the loop.
5252 int64 nowStats = GetTime();
4609 for (Miner* m : gMiners) { 5253 for (Miner* m : gMiners) {
4610 if (!m->authorised || m->lastSeen <= 0) continue; 5254 if (!m->authorised || m->lastSeen <= 0) continue;
4611 PoolWorkerStatView v; 5255 PoolWorkerStatView v;
@@ -4614,9 +5258,11 @@ static void RefreshStats(int blocksFoundThisSession, uint64 roundShareTotal)
4614 v.roundShares = m->roundShares; 5258 v.roundShares = m->roundShares;
4615 v.totalShares = m->totalShares; 5259 v.totalShares = m->totalShares;
4616 v.lastSeen = m->lastSeen; 5260 v.lastSeen = m->lastSeen;
4617 v.hashRate = m->hashRate; 5261 // Not m->hashRate: a miner that has stopped submitting shares must
5262 // not keep contributing its last sample to the announced total.
5263 v.hashRate = MinerReportedHashRate(m, nowStats);
4618 gWorkerStats.push_back(v); 5264 gWorkerStats.push_back(v);
4619 gStats.totalHashRate += m->hashRate; 5265 gStats.totalHashRate += v.hashRate;
4620 } 5266 }
4621 std::sort(gWorkerStats.begin(), gWorkerStats.end(), 5267 std::sort(gWorkerStats.begin(), gWorkerStats.end(),
4622 [](const PoolWorkerStatView& a, const PoolWorkerStatView& b) { 5268 [](const PoolWorkerStatView& a, const PoolWorkerStatView& b) {
@@ -4849,6 +5495,35 @@ static void PoolEventLoop()
4849 } 5495 }
4850 } 5496 }
4851 5497
5498 // 3.6. Idle reap (#6). Mirror the clean-disconnect path exactly --
5499 // log, close, erase from gMiners via std::find, delete -- just with
5500 // a different reason. Shares the miner already earned stay in
5501 // roundShareCount, exactly as they do when a miner cleanly
5502 // disconnects mid-round. connectTime stands in for lastSeen on a
5503 // connection that never completed a line. Erasing inside the loop
5504 // above would invalidate the iteration, so collect then reap.
5505 {
5506 int64 now = GetTime();
5507 std::vector<Miner*> idle;
5508 for (Miner* m : gMiners) {
5509 int64 last = (m->lastSeen > 0) ? m->lastSeen : m->connectTime;
5510 if (now - last > POOL_IDLE_TIMEOUT_SECS)
5511 idle.push_back(m);
5512 }
5513 for (Miner* m : idle) {
5514 LogPrint("worker", "[pool] dropping idle miner %s -- nothing"
5515 " received in over %llds (session shares: %llu,"
5516 " round shares: %llu)\n",
5517 m->worker.empty() ? "(never authorized)" : m->worker.c_str(),
5518 (long long)POOL_IDLE_TIMEOUT_SECS,
5519 (unsigned long long)m->sessionShares,
5520 (unsigned long long)m->roundShares);
5521 sock_close(m->fd);
5522 gMiners.erase(std::find(gMiners.begin(), gMiners.end(), m));
5523 delete m;
5524 }
5525 }
5526
4852 // 4. Select over miner sockets 5527 // 4. Select over miner sockets
4853 if (!gMiners.empty()) { 5528 if (!gMiners.empty()) {
4854 fd_set fds; FD_ZERO(&fds); 5529 fd_set fds; FD_ZERO(&fds);
@@ -4871,6 +5546,11 @@ static void PoolEventLoop()
4871 // terminate the whole node -- not just the pool. Every 5546 // terminate the whole node -- not just the pool. Every
4872 // line arrives from an unauthenticated socket. 5547 // line arrives from an unauthenticated socket.
4873 while (RecvLine(m->fd, m->readBuf, line, disc)) { 5548 while (RecvLine(m->fd, m->readBuf, line, disc)) {
5549 // Any complete line is proof of life (#6): authorize
5550 // and valid shares already stamp lastSeen themselves,
5551 // but a miner that only ever subscribes (or sends
5552 // unrecognised methods) must count as alive too.
5553 m->lastSeen = GetTime();
4874 try { 5554 try {
4875 HandleLine(m, line, roundShareCount, 5555 HandleLine(m, line, roundShareCount,
4876 roundShareTotal, blocksFoundThisSession); 5556 roundShareTotal, blocksFoundThisSession);
@@ -4947,13 +5627,19 @@ static void AcceptOneFn(void* arg)
4947 std::string host = relay.substr(0, colon); 5627 std::string host = relay.substr(0, colon);
4948 int port = atoi(relay.substr(colon + 1).c_str()); 5628 int port = atoi(relay.substr(colon + 1).c_str());
4949 5629
4950 LogPrint("pool", "[pool] accept thread started for relay %s\n", relay.c_str()); 5630 LogPrint("pool", "[pool] accept thread started for relay %s%s\n", relay.c_str(),
5631 fBtfRelayPinned ? " (/rvrelay pin)" : "");
5632
5633 // Registration health for this relay. A pin has no fallback, so a pin that
5634 // never registers means the pool is simply unreachable -- and that has to
5635 // be loud. The 2026-07-30 incident went unnoticed because this loop logged
5636 // failures at one flat line and success not at all, so "which relay is this
5637 // pool actually registered at" could not be answered from the log.
5638 int nConsecFail = 0;
5639 int64 nFirstFail = 0;
5640 bool fEverRegistered = false;
4951 5641
4952 while (!fShutdown && gPoolRunning) { 5642 while (!fShutdown && gPoolRunning) {
4953 // Advertise this relay as the pool's meeting node BEFORE registering,
4954 // so that miners which resolve our descriptor find a relay where the
4955 // pool (not the node's peer-accept) holds the registration.
4956 BtfSetActiveRelay(relay);
4957 // Returns as soon as the relay lists us -- registered, not yet paired. 5643 // Returns as soon as the relay lists us -- registered, not yet paired.
4958 btf::RvSocket rv = btf::RvServiceRegister(host.c_str(), (unsigned short)port, pk); 5644 btf::RvSocket rv = btf::RvServiceRegister(host.c_str(), (unsigned short)port, pk);
4959 5645
@@ -4962,12 +5648,36 @@ static void AcceptOneFn(void* arg)
4962 break; 5648 break;
4963 } 5649 }
4964 if (rv == btf::RV_INVALID) { 5650 if (rv == btf::RV_INVALID) {
4965 LogPrint("pool", "[pool] relay %s: RvServiceRegister failed" 5651 nConsecFail++;
4966 " -- backing off 30s before retry\n", relay.c_str()); 5652 if (nFirstFail == 0) nFirstFail = GetTime();
5653 LogPrint("pool", "[pool] relay %s: registration failed (attempt %d,"
5654 " failing for %llds) -- backing off 30s before retry\n",
5655 relay.c_str(), nConsecFail, (long long)(GetTime() - nFirstFail));
5656 if (fBtfRelayPinned && (nConsecFail == 3 || nConsecFail % 10 == 0))
5657 LogPrint("pool", "[pool] WARNING: pinned relay %s refused registration"
5658 " %d times running (%llds); /rvrelay leaves no fallback, so"
5659 " miners cannot reach this pool%s\n",
5660 relay.c_str(), nConsecFail, (long long)(GetTime() - nFirstFail),
5661 fEverRegistered ? "" : " -- it has never registered once");
4967 for (int i = 0; i < 30 && !fShutdown && gPoolRunning; i++) Sleep(1000); 5662 for (int i = 0; i < 30 && !fShutdown && gPoolRunning; i++) Sleep(1000);
4968 continue; 5663 continue;
4969 } 5664 }
4970 5665
5666 // Advertise THIS relay only now. Setting it before the call published a
5667 // meeting node we were not yet listed at, so a miner resolving the
5668 // descriptor inside that window burned its dial on a relay holding no
5669 // registration for us. net.cpp's peer-accept has always ordered it this
5670 // way round; the pool had not.
5671 BtfSetActiveRelay(relay);
5672 if (!fEverRegistered || nConsecFail > 0)
5673 LogPrint("pool", "[pool] registered at relay %s%s%s -- advertising it as"
5674 " the pool's meeting node\n", relay.c_str(),
5675 fBtfRelayPinned ? " (/rvrelay pin)" : "",
5676 nConsecFail > 0 ? ", recovered" : "");
5677 fEverRegistered = true;
5678 nConsecFail = 0;
5679 nFirstFail = 0;
5680
4971 // Block for a miner to actually arrive. RvServiceRegister used to do 5681 // Block for a miner to actually arrive. RvServiceRegister used to do
4972 // this itself and returned with the relay's 0x01 pairing byte already 5682 // this itself and returned with the relay's 0x01 pairing byte already
4973 // consumed; it no longer does. Without waiting here, BtfServiceWrap 5683 // consumed; it no longer does. Without waiting here, BtfServiceWrap
@@ -5022,9 +5732,21 @@ static void AcceptLoopFn(void*)
5022 LogPrint("pool", "[pool] .btf identity ready after %ds\n", waitSecs * 2); 5732 LogPrint("pool", "[pool] .btf identity ready after %ds\n", waitSecs * 2);
5023 5733
5024 std::set<std::string> spawned; // relays we've already started a thread for 5734 std::set<std::string> spawned; // relays we've already started a thread for
5735 std::set<std::string> skipped; // discovered relays the pin excluded, logged once each
5025 5736
5026 while (!fShutdown && gPoolRunning) { 5737 while (!fShutdown && gPoolRunning) {
5738 // BtfAllRelays() already honours the pin: with /rvrelay set it returns
5739 // vBtfMeetingRelays alone, so no thread is ever spawned for a
5740 // discovered relay. Name the ones it drops -- a pool registering at a
5741 // stranger's relay is exactly the thing that should not be silent.
5027 vector<string> relays = BtfAllRelays(); 5742 vector<string> relays = BtfAllRelays();
5743 if (fBtfRelayPinned) {
5744 for (const string& r : BtfDiscoveredRelays())
5745 if (std::find(relays.begin(), relays.end(), r) == relays.end() &&
5746 skipped.insert(r).second)
5747 LogPrint("pool", "[pool] not registering at discovered relay %s"
5748 " -- /rvrelay pins the relay list\n", r.c_str());
5749 }
5028 if (relays.empty()) { 5750 if (relays.empty()) {
5029 LogPrint("pool", "[pool] no relays available -- miners cannot connect yet\n"); 5751 LogPrint("pool", "[pool] no relays available -- miners cannot connect yet\n");
5030 } else { 5752 } else {
@@ -5040,8 +5762,9 @@ static void AcceptLoopFn(void*)
5040 relay.c_str()); 5762 relay.c_str());
5041 } else { 5763 } else {
5042 spawned.insert(relay); 5764 spawned.insert(relay);
5043 LogPrint("pool", "[pool] accept thread spawned for relay %s" 5765 LogPrint("pool", "[pool] accept thread spawned for relay %s%s"
5044 " (%zu total)\n", relay.c_str(), spawned.size()); 5766 " (%zu total)\n", relay.c_str(),
5767 fBtfRelayPinned ? " (/rvrelay pin)" : "", spawned.size());
5045 } 5768 }
5046 } 5769 }
5047 } 5770 }
@@ -5077,10 +5800,17 @@ void ThreadRPCServer(void*)
5077 // armed for its one wallet probe before it is allowed to send. 5800 // armed for its one wallet probe before it is allowed to send.
5078 ReconcilePayouts(); 5801 ReconcilePayouts();
5079 5802
5080 _beginthread(AcceptLoopFn, 0, NULL); 5803 // _beginthread cannot throw (it reports -1), but an unchecked failure
5081 _beginthread(LocalAcceptFn, 0, NULL); 5804 // leaves the pool running deaf: nobody accepts, announces, or pays, and
5082 _beginthread(ThreadBtfPoolAnnouncer, 0, NULL); 5805 // nothing in the log says why.
5083 _beginthread(PayoutThreadFn, 0, NULL); 5806 if (_beginthread(AcceptLoopFn, 0, NULL) == (uintptr_t)-1)
5807 LogPrint("pool", "[pool] FATAL: accept loop thread failed to spawn -- miners cannot connect\n");
5808 if (_beginthread(LocalAcceptFn, 0, NULL) == (uintptr_t)-1)
5809 LogPrint("pool", "[pool] local accept thread failed to spawn\n");
5810 if (_beginthread(ThreadBtfPoolAnnouncer, 0, NULL) == (uintptr_t)-1)
5811 LogPrint("pool", "[pool] announcer thread failed to spawn -- pool will not be advertised\n");
5812 if (_beginthread(PayoutThreadFn, 0, NULL) == (uintptr_t)-1)
5813 LogPrint("pool", "[pool] FATAL: payout thread failed to spawn -- no one gets paid\n");
5084 5814
5085 PoolEventLoop(); // blocks until gPoolRunning = false or fShutdown 5815 PoolEventLoop(); // blocks until gPoolRunning = false or fShutdown
5086 5816
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 9f7106f..65f215b 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -10,9 +10,13 @@
10#include "bip32.h" 10#include "bip32.h"
11#include "proxy.h" 11#include "proxy.h"
12#include "selftest.h" 12#include "selftest.h"
13#ifndef _WIN32
14#include <sys/wait.h> // WIFEXITED/WEXITSTATUS for the child-process helper
15#endif
13#include "sockcount.h" 16#include "sockcount.h"
14#include "tor.h" 17#include "tor.h"
15#include "walletcmd.h" 18#include "walletcmd.h"
19#include "wallet_sqlite.h"
16 20
17extern int RunPoolStratumSelfTest(); 21extern int RunPoolStratumSelfTest();
18extern int RunPayoutLedgerSelfTest(); 22extern int RunPayoutLedgerSelfTest();
@@ -35,6 +39,7 @@ extern int RunPayoutLedgerSelfTest();
35#undef printf 39#undef printf
36 40
37#include <mutex> 41#include <mutex>
42#include <cerrno>
38#include <stdexcept> 43#include <stdexcept>
39#include <thread> 44#include <thread>
40 45
@@ -62,6 +67,11 @@ static std::string HexStrLocal(const std::vector<unsigned char>& v)
62 return out; 67 return out;
63} 68}
64 69
70static vector<unsigned char> DataStreamBytes(const CDataStream& ss)
71{
72 return vector<unsigned char>(ss.begin(), ss.end());
73}
74
65static bool MakeTempDir(std::string& out) 75static bool MakeTempDir(std::string& out)
66{ 76{
67#ifdef _WIN32 77#ifdef _WIN32
@@ -736,6 +746,1017 @@ static int RunWalletFormatSelfTest()
736 return nFail == 0 ? 0 : 1; 746 return nFail == 0 ? 0 : 1;
737} 747}
738 748
749static bool FileContainsText(const string& strPath, const string& strNeedle);
750static bool MakeDirLocal(const string& strPath);
751static int RunBitflashChild(const string& strExe,
752 const vector<string>& vArgs,
753 const string* pStdinFile,
754 const string* pOutputFile);
755
756class CWalletStorageSanityRawDB : public CWalletDB
757{
758public:
759 CWalletStorageSanityRawDB(const char* pszMode="r+")
760 : CWalletDB(pszMode) { }
761
762 bool WriteUnknownRecord()
763 {
764 return Write(string("storage-sanity-unknown"), 1);
765 }
766
767 bool WriteMalformedKeyRecord()
768 {
769 if (!pdb)
770 return false;
771 unsigned char chKey = 0xff;
772 unsigned char chValue = 0x01;
773 Dbt datKey(&chKey, 1);
774 Dbt datValue(&chValue, 1);
775 return pdb->put(GetTxn(), &datKey, &datValue, 0) == 0;
776 }
777
778 bool MakePlainHDSeedIncomplete()
779 {
780 return Erase(string("hdchaincode"));
781 }
782
783 bool AddEncryptedMarkerToPlainWallet()
784 {
785 CWalletMasterKey kMasterKey;
786 kMasterKey.vchCryptedKey.resize(48);
787 for (size_t i = 0; i < kMasterKey.vchCryptedKey.size(); i++)
788 kMasterKey.vchCryptedKey[i] = (unsigned char)(0x80 + (i & 0x3f));
789 for (size_t i = 0; i < kMasterKey.vchSalt.size(); i++)
790 kMasterKey.vchSalt[i] = (unsigned char)(0x40 + i);
791 return WriteWalletMinVersion(WALLET_FORMAT_ENCRYPTED) &&
792 WriteMasterKey(1, kMasterKey);
793 }
794};
795
796static const char* SELFTEST_MUTATE_WALLET_MARKER =
797 ".bitflash-selftest-wallet-mutate-ok";
798static const char* SELFTEST_MUTATE_WALLET_MARKER_TEXT =
799 "bitflash destructive wallet storage self-test\n";
800
801static string SelfTestMutationMarkerPath()
802{
803 return GetAppDir() + "/" + SELFTEST_MUTATE_WALLET_MARKER;
804}
805
806static bool WriteSelfTestMutationMarker(const string& strDir)
807{
808 string strPath = strDir + "/" + SELFTEST_MUTATE_WALLET_MARKER;
809 FILE* pf = fopen(strPath.c_str(), "wb");
810 if (!pf)
811 return false;
812 bool fOk = fwrite(SELFTEST_MUTATE_WALLET_MARKER_TEXT, 1,
813 strlen(SELFTEST_MUTATE_WALLET_MARKER_TEXT), pf) ==
814 strlen(SELFTEST_MUTATE_WALLET_MARKER_TEXT);
815 if (fclose(pf) != 0)
816 fOk = false;
817 return fOk;
818}
819
820int RunSelfTestMutateWallet(const std::string& name)
821{
822 if (!FileContainsText(SelfTestMutationMarkerPath(),
823 SELFTEST_MUTATE_WALLET_MARKER_TEXT))
824 {
825 fprintf(stderr, "Refusing destructive wallet storage mutation outside "
826 "a marked self-test datadir.\n");
827 return 1;
828 }
829
830 bool fOk = false;
831 try
832 {
833 CWalletStorageSanityRawDB walletdb;
834 if (name == "unknown-record")
835 fOk = walletdb.WriteUnknownRecord();
836 else if (name == "malformed-key")
837 fOk = walletdb.WriteMalformedKeyRecord();
838 else if (name == "incomplete-plain-hd")
839 fOk = walletdb.MakePlainHDSeedIncomplete();
840 else if (name == "encrypted-marker-with-plain-keys")
841 fOk = walletdb.AddEncryptedMarkerToPlainWallet();
842 else
843 {
844 fprintf(stderr, "Unknown wallet storage mutation '%s'\n", name.c_str());
845 return 1;
846 }
847 }
848 catch (const std::exception& e)
849 {
850 fprintf(stderr, "Cannot mutate wallet storage: %s\n", e.what());
851 return 1;
852 }
853 catch (...)
854 {
855 fprintf(stderr, "Cannot mutate wallet storage: unknown exception\n");
856 return 1;
857 }
858
859 DBFlush(true);
860 return fOk ? 0 : 1;
861}
862
863static int RunWalletStorageSanitySelfTest()
864{
865 fflush(stdout);
866 printf("wallet-storage-sanity self-test\n");
867
868 std::string tmp;
869 std::string cwd;
870 if (!MakeTempDir(tmp))
871 {
872 printf(" FAIL could not create a temporary data directory\n");
873 return 1;
874 }
875 if (!GetCurrentDir(cwd))
876 {
877 printf(" FAIL could not read current directory\n");
878 RemoveTree(tmp);
879 return 1;
880 }
881
882 int nFail = 0;
883 printf(" temp root: %s\n", tmp.c_str());
884
885 try
886 {
887#ifdef _WIN32
888 string strExe = cwd + "\\bitflash.exe";
889#else
890 string strExe = cwd + "/bitflash-node";
891#endif
892
893 struct StorageScenario
894 {
895 const char* pszName;
896 const char* pszExpectedFailure;
897 };
898
899 StorageScenario scenarios[] = {
900 { "unknown-record", "wallet.dat contains unknown records" },
901 { "malformed-key", "wallet.dat contains malformed records" },
902 { "incomplete-plain-hd", "plain HD seed is incomplete" },
903 { "encrypted-marker-with-plain-keys",
904 "encrypted wallet still contains plain private key records" },
905 };
906
907 for (size_t i = 0; i < sizeof(scenarios)/sizeof(scenarios[0]); i++)
908 {
909 string strScenarioDir = tmp + "/" + scenarios[i].pszName;
910 nFail += Check(MakeDirLocal(strScenarioDir),
911 "scenario datadir can be created") ? 0 : 1;
912
913 string strCreateOut = tmp + "/" + scenarios[i].pszName + "-create.txt";
914 vector<string> vCreateArgs;
915 vCreateArgs.push_back("-datadir=" + strScenarioDir);
916 vCreateArgs.push_back("-nomanagedtor");
917 vCreateArgs.push_back("-nogui");
918 vCreateArgs.push_back("-walletbackend=bdb"); // this suite is about wallet.dat
919 vCreateArgs.push_back("-newphrase");
920 int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut);
921 nFail += Check(nCreateRet == 0 &&
922 FileContainsText(strCreateOut, "Write these twelve words down"),
923 "scenario phrase wallet can be created") ? 0 : 1;
924
925 if (i == 0)
926 {
927 string strBlockedOut = tmp + "/" + scenarios[i].pszName + "-blocked-mutate.txt";
928 vector<string> vBlockedArgs;
929 vBlockedArgs.push_back("-datadir=" + strScenarioDir);
930 vBlockedArgs.push_back("-nomanagedtor");
931 vBlockedArgs.push_back("-nogui");
932 vBlockedArgs.push_back("-selftestmutatewallet=" + string(scenarios[i].pszName));
933 int nBlockedRet = RunBitflashChild(strExe, vBlockedArgs, NULL, &strBlockedOut);
934 nFail += Check(nBlockedRet != 0 &&
935 FileContainsText(strBlockedOut, "Refusing destructive wallet storage mutation"),
936 "wallet mutation helper is blocked without marker") ? 0 : 1;
937 }
938
939 nFail += Check(WriteSelfTestMutationMarker(strScenarioDir),
940 "scenario mutation marker can be written") ? 0 : 1;
941
942 string strMutateOut = tmp + "/" + scenarios[i].pszName + "-mutate.txt";
943 vector<string> vMutateArgs;
944 vMutateArgs.push_back("-datadir=" + strScenarioDir);
945 vMutateArgs.push_back("-nomanagedtor");
946 vMutateArgs.push_back("-nogui");
947 vMutateArgs.push_back("-selftestmutatewallet=" + string(scenarios[i].pszName));
948 int nMutateRet = RunBitflashChild(strExe, vMutateArgs, NULL, &strMutateOut);
949 nFail += Check(nMutateRet == 0, scenarios[i].pszName) ? 0 : 1;
950
951 string strCheckOut = tmp + "/" + scenarios[i].pszName + "-check.txt";
952 vector<string> vCheckArgs;
953 vCheckArgs.push_back("-datadir=" + strScenarioDir);
954 vCheckArgs.push_back("-nomanagedtor");
955 vCheckArgs.push_back("-nogui");
956 vCheckArgs.push_back("-walletstoragecheck");
957 int nCheckRet = RunBitflashChild(strExe, vCheckArgs, NULL, &strCheckOut);
958 nFail += Check(nCheckRet == 2 &&
959 FileContainsText(strCheckOut, "storage sanity: failed") &&
960 FileContainsText(strCheckOut, scenarios[i].pszExpectedFailure),
961 scenarios[i].pszExpectedFailure) ? 0 : 1;
962 }
963 }
964 catch (const std::exception& e)
965 {
966 printf(" FAIL exception: %s\n", e.what());
967 nFail++;
968 }
969 catch (...)
970 {
971 printf(" FAIL unknown exception\n");
972 nFail++;
973 }
974
975 RemoveTree(tmp);
976 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
977 nFail, nFail == 1 ? "" : "s");
978 fflush(stdout);
979 return nFail == 0 ? 0 : 1;
980}
981
982static int RunDbEnvReopenSelfTest()
983{
984 fflush(stdout);
985 printf("db-env-reopen self-test\n");
986
987 std::string tmp;
988 if (!MakeTempDir(tmp))
989 {
990 printf(" FAIL could not create a temporary data directory\n");
991 return 1;
992 }
993
994 int nFail = 0;
995 std::string strOldDataDir = strSetDataDir;
996 strSetDataDir = tmp;
997 printf(" temp datadir: %s\n", tmp.c_str());
998
999 try
1000 {
1001 for (int i = 0; i < 5; i++)
1002 {
1003 int64 nValue = 1000 + i;
1004 {
1005 CWalletDB walletdb("cr+");
1006 nFail += Check(walletdb.WriteSetting("db-env-reopen-cycle", nValue),
1007 "wallet setting can be written before shutdown") ? 0 : 1;
1008 }
1009
1010 DBFlush(true);
1011
1012 int64 nRead = 0;
1013 {
1014 CWalletDB walletdb("r");
1015 nFail += Check(walletdb.ReadSetting("db-env-reopen-cycle", nRead),
1016 "wallet setting can be read after env reopen") ? 0 : 1;
1017 }
1018 nFail += Check(nRead == nValue,
1019 "reopened environment reads the last committed value") ? 0 : 1;
1020
1021 DBFlush(true);
1022 }
1023 }
1024 catch (const std::exception& e)
1025 {
1026 printf(" FAIL exception: %s\n", e.what());
1027 nFail++;
1028 }
1029 catch (...)
1030 {
1031 printf(" FAIL unknown exception\n");
1032 nFail++;
1033 }
1034
1035 DBFlush(true);
1036 strSetDataDir = strOldDataDir;
1037 RemoveTree(tmp);
1038 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1039 nFail, nFail == 1 ? "" : "s");
1040 fflush(stdout);
1041 return nFail == 0 ? 0 : 1;
1042}
1043
1044static int RunWalletSQLiteSelfTest()
1045{
1046 fflush(stdout);
1047 printf("wallet-sqlite self-test\n");
1048
1049 std::string tmp;
1050 if (!MakeTempDir(tmp))
1051 {
1052 printf(" FAIL could not create a temporary data directory\n");
1053 return 1;
1054 }
1055
1056 int nFail = 0;
1057 string strPath = tmp + "/wallet.sqlite";
1058 string strError;
1059 string strOldRuntimeDataDir;
1060 bool fRestoreRuntimeDataDir = false;
1061
1062 try
1063 {
1064 CDataStream ssKeySetting(SER_DISK);
1065 ssKeySetting << make_pair(string("setting"), string("sqlite-roundtrip"));
1066 CDataStream ssValueSetting(SER_DISK);
1067 ssValueSetting << (int64)424242;
1068
1069 vector<unsigned char> vchPubKey;
1070 vchPubKey.push_back(0x04);
1071 for (int i = 0; i < 64; i++)
1072 vchPubKey.push_back((unsigned char)i);
1073 CDataStream ssKeyDefault(SER_DISK);
1074 ssKeyDefault << string("defaultkey");
1075 CDataStream ssValueDefault(SER_DISK);
1076 ssValueDefault << vchPubKey;
1077
1078 {
1079 CWalletDBSQLite db;
1080 nFail += Check(db.Open(strPath, strError),
1081 "SQLite wallet opens and creates schema") ? 0 : 1;
1082 nFail += Check(db.BeginTransaction(strError),
1083 "SQLite wallet begins a write transaction") ? 0 : 1;
1084 nFail += Check(db.WriteRecord(DataStreamBytes(ssKeySetting),
1085 DataStreamBytes(ssValueSetting),
1086 strError),
1087 "SQLite wallet writes a serialized setting record") ? 0 : 1;
1088 nFail += Check(db.WriteRecord(DataStreamBytes(ssKeyDefault),
1089 DataStreamBytes(ssValueDefault),
1090 strError),
1091 "SQLite wallet writes a serialized defaultkey record") ? 0 : 1;
1092 nFail += Check(db.CommitTransaction(strError),
1093 "SQLite wallet commits a write transaction") ? 0 : 1;
1094 int nCount = 0;
1095 nFail += Check(db.CountRecords(nCount, strError) && nCount == 2,
1096 "SQLite wallet counts raw records") ? 0 : 1;
1097 }
1098
1099 {
1100 CWalletDBSQLite db;
1101 nFail += Check(db.Open(strPath, strError),
1102 "SQLite wallet reopens") ? 0 : 1;
1103 vector<unsigned char> vchRead;
1104 nFail += Check(db.ReadRecord(DataStreamBytes(ssKeySetting),
1105 vchRead,
1106 strError),
1107 "SQLite wallet reads a serialized setting record") ? 0 : 1;
1108 int64 nReadValue = 0;
1109 CDataStream ssRead(vchRead, SER_DISK);
1110 ssRead >> nReadValue;
1111 nFail += Check(!ssRead.fail() && nReadValue == 424242,
1112 "SQLite wallet preserves serialized values byte-for-byte") ? 0 : 1;
1113
1114 vchRead.clear();
1115 nFail += Check(db.ReadRecord(DataStreamBytes(ssKeyDefault),
1116 vchRead,
1117 strError),
1118 "SQLite wallet reads a serialized defaultkey record") ? 0 : 1;
1119 vector<unsigned char> vchReadPubKey;
1120 CDataStream ssReadDefault(vchRead, SER_DISK);
1121 ssReadDefault >> vchReadPubKey;
1122 nFail += Check(!ssReadDefault.fail() && vchReadPubKey == vchPubKey,
1123 "SQLite wallet preserves public key blobs") ? 0 : 1;
1124 }
1125
1126 {
1127 CWalletDBSQLite db;
1128 nFail += Check(db.Open(strPath, strError),
1129 "SQLite wallet reopens for typed records") ? 0 : 1;
1130
1131 nFail += Check(db.WriteTypedRecord(make_pair(string("setting"),
1132 string("sqlite-typed")),
1133 (int64)777, strError),
1134 "SQLite wallet writes a typed setting record") ? 0 : 1;
1135
1136 int64 nTypedRead = 0;
1137 nFail += Check(db.ReadTypedRecord(make_pair(string("setting"),
1138 string("sqlite-typed")),
1139 nTypedRead, strError) &&
1140 nTypedRead == 777,
1141 "SQLite wallet reads a typed setting record") ? 0 : 1;
1142
1143 nFail += Check(!db.WriteTypedRecord(make_pair(string("setting"),
1144 string("sqlite-typed")),
1145 (int64)888, strError, false),
1146 "SQLite wallet refuses typed overwrite when asked") ? 0 : 1;
1147
1148 nTypedRead = 0;
1149 nFail += Check(db.ReadTypedRecord(make_pair(string("setting"),
1150 string("sqlite-typed")),
1151 nTypedRead, strError) &&
1152 nTypedRead == 777,
1153 "SQLite wallet preserves the old typed value after overwrite refusal") ? 0 : 1;
1154
1155 nFail += Check(db.EraseTypedRecord(make_pair(string("setting"),
1156 string("sqlite-typed")),
1157 strError),
1158 "SQLite wallet erases a typed record") ? 0 : 1;
1159
1160 nTypedRead = 0;
1161 nFail += Check(!db.ReadTypedRecord(make_pair(string("setting"),
1162 string("sqlite-typed")),
1163 nTypedRead, strError),
1164 "SQLite wallet typed erase removes the record") ? 0 : 1;
1165
1166 nFail += Check(db.WriteTypedRecord(string("defaultkey"), vchPubKey,
1167 strError),
1168 "SQLite wallet writes a typed defaultkey record") ? 0 : 1;
1169
1170 vector<unsigned char> vchTypedPubKey;
1171 nFail += Check(db.ReadTypedRecord(string("defaultkey"), vchTypedPubKey,
1172 strError) &&
1173 vchTypedPubKey == vchPubKey,
1174 "SQLite wallet reads a typed defaultkey record") ? 0 : 1;
1175 }
1176
1177 {
1178 string strRuntimePath = tmp + "/runtime-wallet.sqlite";
1179 string strRuntimeDir = tmp + "/runtime-datadir";
1180 nFail += Check(MakeDirLocal(strRuntimeDir),
1181 "SQLite runtime datadir can be created") ? 0 : 1;
1182
1183 {
1184 CWalletDBSQLite db;
1185 nFail += Check(db.Open(strRuntimePath, strError),
1186 "SQLite runtime wallet file can be created") ? 0 : 1;
1187 }
1188
1189 string strOldDataDir = strSetDataDir;
1190 strSetDataDir = strRuntimeDir;
1191 strOldRuntimeDataDir = strOldDataDir;
1192 fRestoreRuntimeDataDir = true;
1193 bool fRuntimeOpened = WalletSQLiteRuntimeOpen(strRuntimePath, strError);
1194 nFail += Check(fRuntimeOpened,
1195 "SQLite runtime wallet can be activated") ? 0 : 1;
1196
1197 if (fRuntimeOpened)
1198 {
1199 CKey key;
1200 key.MakeNewKey();
1201 vector<unsigned char> vchRuntimePubKey = key.GetPubKey();
1202 CPrivKey vchRuntimePrivKey = key.GetPrivKey();
1203 string strRuntimeAddress = PubKeyToAddress(vchRuntimePubKey);
1204
1205 nFail += Check(CWalletDB().WriteSetting("runtime-sqlite-setting",
1206 (int64)909090) &&
1207 CWalletDB().WriteDefaultKey(vchRuntimePubKey) &&
1208 CWalletDB().WriteKey(vchRuntimePubKey,
1209 vchRuntimePrivKey) &&
1210 CWalletDB().WriteName(strRuntimeAddress,
1211 "SQLite Runtime Address"),
1212 "CWalletDB writes route to active SQLite runtime") ? 0 : 1;
1213
1214 int64 nRuntimeSetting = 0;
1215 vector<unsigned char> vchRuntimeDefaultKey;
1216 CPrivKey vchRuntimeReadPrivKey;
1217 string strRuntimeName;
1218 nFail += Check(CWalletDB("r").ReadSetting("runtime-sqlite-setting",
1219 nRuntimeSetting) &&
1220 nRuntimeSetting == 909090 &&
1221 CWalletDB("r").ReadDefaultKey(vchRuntimeDefaultKey) &&
1222 vchRuntimeDefaultKey == vchRuntimePubKey &&
1223 CWalletDB("r").ReadKey(vchRuntimePubKey,
1224 vchRuntimeReadPrivKey) &&
1225 vchRuntimeReadPrivKey == vchRuntimePrivKey &&
1226 CWalletDB("r").ReadName(strRuntimeAddress,
1227 strRuntimeName) &&
1228 strRuntimeName == "SQLite Runtime Address",
1229 "CWalletDB reads route to active SQLite runtime") ? 0 : 1;
1230
1231 nFail += Check(CWalletDB().EraseName(strRuntimeAddress) &&
1232 !CWalletDB("r").ReadName(strRuntimeAddress,
1233 strRuntimeName),
1234 "CWalletDB erases route to active SQLite runtime") ? 0 : 1;
1235
1236 nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()),
1237 "active SQLite runtime does not create wallet.dat") ? 0 : 1;
1238
1239 // Transaction bracket: a rolled-back group leaves nothing, a
1240 // committed group persists. This is the all-or-nothing that
1241 // WriteHDMaster relies on so a half-written seed cannot survive
1242 // a failure between its two records.
1243 bool fTxnBegan = WalletSQLiteRuntimeBeginTxn();
1244 bool fTxnWrote = fTxnBegan &&
1245 CWalletDB().WriteSetting("runtime-txn-rollback", (int64)111);
1246 if (fTxnBegan)
1247 WalletSQLiteRuntimeRollbackTxn();
1248 nFail += Check(fTxnBegan && fTxnWrote,
1249 "a SQLite runtime transaction opens and rolls back") ? 0 : 1;
1250 int64 nRolledBack = 0;
1251 nFail += Check(!CWalletDB("r").ReadSetting("runtime-txn-rollback",
1252 nRolledBack),
1253 "a rolled-back SQLite runtime write leaves no record") ? 0 : 1;
1254
1255 bool fCommitOk = WalletSQLiteRuntimeBeginTxn() &&
1256 CWalletDB().WriteSetting("runtime-txn-commit", (int64)222) &&
1257 WalletSQLiteRuntimeCommitTxn();
1258 int64 nCommitted = 0;
1259 nFail += Check(fCommitOk &&
1260 CWalletDB("r").ReadSetting("runtime-txn-commit",
1261 nCommitted) &&
1262 nCommitted == 222,
1263 "a committed SQLite runtime write persists") ? 0 : 1;
1264
1265 // WriteHDMaster brackets its two records in that transaction;
1266 // both halves of the seed must land.
1267 vector<unsigned char> vchTxnMaster(32, 0x5a);
1268 vector<unsigned char> vchTxnChainCode(32, 0xa5);
1269 vector<unsigned char> vchReadMaster, vchReadChainCode;
1270 nFail += Check(CWalletDB().WriteHDMaster(vchTxnMaster, vchTxnChainCode) &&
1271 CWalletDB("r").ReadHDMaster(vchReadMaster, vchReadChainCode) &&
1272 vchReadMaster == vchTxnMaster &&
1273 vchReadChainCode == vchTxnChainCode,
1274 "WriteHDMaster commits both halves of the seed under SQLite") ? 0 : 1;
1275
1276 // Durability: close the backend (checkpoints the WAL), reopen
1277 // the same file, and confirm what CWalletDB wrote is still
1278 // there. This is the write-survives-a-restart property the real
1279 // -walletbackend=sqlite node depends on.
1280 WalletSQLiteRuntimeClose();
1281 bool fReopened =
1282 WalletSQLiteRuntimeOpen(strRuntimePath, strError);
1283 nFail += Check(fReopened,
1284 "SQLite runtime wallet reopens after close") ? 0 : 1;
1285 if (fReopened)
1286 {
1287 int64 nPersisted = 0;
1288 CPrivKey vchPersistedPrivKey;
1289 vector<unsigned char> vchPersistedDefaultKey;
1290 nFail += Check(CWalletDB("r").ReadSetting("runtime-sqlite-setting",
1291 nPersisted) &&
1292 nPersisted == 909090 &&
1293 CWalletDB("r").ReadDefaultKey(vchPersistedDefaultKey) &&
1294 vchPersistedDefaultKey == vchRuntimePubKey &&
1295 CWalletDB("r").ReadKey(vchRuntimePubKey,
1296 vchPersistedPrivKey) &&
1297 vchPersistedPrivKey == vchRuntimePrivKey,
1298 "SQLite runtime writes persist across a close and reopen") ? 0 : 1;
1299 }
1300 }
1301
1302 WalletSQLiteRuntimeClose();
1303 strSetDataDir = strOldDataDir;
1304 fRestoreRuntimeDataDir = false;
1305 }
1306 }
1307 catch (const std::exception& e)
1308 {
1309 WalletSQLiteRuntimeClose();
1310 if (fRestoreRuntimeDataDir)
1311 strSetDataDir = strOldRuntimeDataDir;
1312 printf(" FAIL exception: %s\n", e.what());
1313 nFail++;
1314 }
1315 catch (...)
1316 {
1317 WalletSQLiteRuntimeClose();
1318 if (fRestoreRuntimeDataDir)
1319 strSetDataDir = strOldRuntimeDataDir;
1320 printf(" FAIL unknown exception\n");
1321 nFail++;
1322 }
1323
1324 RemoveTree(tmp);
1325 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1326 nFail, nFail == 1 ? "" : "s");
1327 fflush(stdout);
1328 return nFail == 0 ? 0 : 1;
1329}
1330
1331namespace {
1332
1333class CWalletRecordMapVisitor : public CWalletRecordVisitor
1334{
1335public:
1336 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecords;
1337
1338 explicit CWalletRecordMapVisitor(
1339 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecordsIn)
1340 : mapRecords(mapRecordsIn) { }
1341
1342 bool VisitWalletRecord(const CDataStream& ssKey,
1343 const CDataStream& ssValue,
1344 string& strErrorRet)
1345 {
1346 vector<unsigned char> vchKey = DataStreamBytes(ssKey);
1347 if (mapRecords.count(vchKey))
1348 {
1349 strErrorRet = "duplicate serialized wallet key";
1350 return false;
1351 }
1352 mapRecords[vchKey] = DataStreamBytes(ssValue);
1353 return true;
1354 }
1355};
1356
1357} // namespace
1358
1359static bool CreateSQLiteWalletWithEmptyKey(const string& strPath)
1360{
1361 sqlite3* pdb = NULL;
1362 int ret = sqlite3_open_v2(strPath.c_str(), &pdb,
1363 SQLITE_OPEN_READWRITE |
1364 SQLITE_OPEN_CREATE |
1365 SQLITE_OPEN_FULLMUTEX,
1366 NULL);
1367 if (ret != SQLITE_OK || !pdb)
1368 {
1369 if (pdb)
1370 sqlite3_close(pdb);
1371 return false;
1372 }
1373
1374 char* pszErr = NULL;
1375 bool fOk =
1376 sqlite3_exec(pdb,
1377 "CREATE TABLE wallet_records ("
1378 "key BLOB PRIMARY KEY NOT NULL,"
1379 "value BLOB NOT NULL"
1380 ");",
1381 NULL, NULL, &pszErr) == SQLITE_OK &&
1382 sqlite3_exec(pdb,
1383 "INSERT INTO wallet_records(key, value) "
1384 "VALUES(x'', x'01');",
1385 NULL, NULL, &pszErr) == SQLITE_OK;
1386 if (pszErr)
1387 sqlite3_free(pszErr);
1388 sqlite3_close(pdb);
1389 return fOk;
1390}
1391
1392static bool CreateSQLiteWalletWithFutureMinVersion(const string& strPath)
1393{
1394 string strError;
1395 CWalletDBSQLite db;
1396 CDataStream ssKey(SER_DISK);
1397 ssKey << string("walletminversion");
1398 CDataStream ssValue(SER_DISK);
1399 ssValue << (WALLET_FORMAT_SUPPORTED + 1);
1400
1401 if (!db.Open(strPath, strError))
1402 return false;
1403 if (!db.BeginTransaction(strError))
1404 return false;
1405 if (!db.WriteRecord(DataStreamBytes(ssKey), DataStreamBytes(ssValue), strError))
1406 return false;
1407 if (!db.CommitTransaction(strError))
1408 return false;
1409 if (!db.Checkpoint(strError))
1410 return false;
1411 return true;
1412}
1413
1414static bool CreateTypedSQLiteWallet(const string& strPath)
1415{
1416 string strError;
1417 CWalletDBSQLite db;
1418 CKey key;
1419 key.MakeNewKey();
1420
1421 vector<unsigned char> vchPubKey = key.GetPubKey();
1422 CPrivKey vchPrivKey = key.GetPrivKey();
1423 string strAddress = PubKeyToAddress(vchPubKey);
1424
1425 if (!db.Open(strPath, strError))
1426 return false;
1427 if (!db.BeginTransaction(strError))
1428 return false;
1429 if (!db.WriteTypedRecord(make_pair(string("key"), vchPubKey),
1430 vchPrivKey, strError, false))
1431 return false;
1432 if (!db.WriteTypedRecord(string("defaultkey"), vchPubKey, strError))
1433 return false;
1434 if (!db.WriteTypedRecord(make_pair(string("name"), strAddress),
1435 string("Typed SQLite Address"), strError))
1436 return false;
1437 if (!db.CommitTransaction(strError))
1438 return false;
1439 if (!db.Checkpoint(strError))
1440 return false;
1441 return true;
1442}
1443
1444static int RunWalletSQLiteMigrationSelfTest()
1445{
1446 fflush(stdout);
1447 printf("wallet-sqlite-migration self-test\n");
1448
1449 std::string tmp;
1450 std::string cwd;
1451 if (!MakeTempDir(tmp))
1452 {
1453 printf(" FAIL could not create a temporary data directory\n");
1454 return 1;
1455 }
1456 if (!GetCurrentDir(cwd))
1457 {
1458 printf(" FAIL could not read current directory\n");
1459 RemoveTree(tmp);
1460 return 1;
1461 }
1462
1463 int nFail = 0;
1464 printf(" temp root: %s\n", tmp.c_str());
1465
1466 try
1467 {
1468#ifdef _WIN32
1469 string strExe = cwd + "\\bitflash.exe";
1470#else
1471 string strExe = cwd + "/bitflash-node";
1472#endif
1473 string strWalletDir = tmp + "/wallet";
1474 nFail += Check(MakeDirLocal(strWalletDir),
1475 "temporary wallet directory can be created") ? 0 : 1;
1476
1477 string strCreateOut = tmp + "/create.txt";
1478 vector<string> vCreateArgs;
1479 vCreateArgs.push_back("-datadir=" + strWalletDir);
1480 vCreateArgs.push_back("-nomanagedtor");
1481 vCreateArgs.push_back("-nogui");
1482 vCreateArgs.push_back("-walletbackend=bdb"); // migrate FROM a Berkeley DB wallet.dat
1483 vCreateArgs.push_back("-newphrase");
1484 int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut);
1485 nFail += Check(nCreateRet == 0 &&
1486 FileContainsText(strCreateOut, "Write these twelve words down"),
1487 "a phrase wallet can be created for SQLite export") ? 0 : 1;
1488
1489 string strSQLite = tmp + "/wallet.sqlite";
1490 string strExportOut = tmp + "/sqlite-export.txt";
1491 vector<string> vExportArgs;
1492 vExportArgs.push_back("-datadir=" + strWalletDir);
1493 vExportArgs.push_back("-nomanagedtor");
1494 vExportArgs.push_back("-nogui");
1495 vExportArgs.push_back("-walletsqliteexport=" + strSQLite);
1496 int nExportRet = RunBitflashChild(strExe, vExportArgs, NULL, &strExportOut);
1497 nFail += Check(nExportRet == 0 &&
1498 FileExists(strSQLite.c_str()) &&
1499 FileContainsText(strExportOut, "SQLite wallet export written"),
1500 "wallet.dat can be exported to SQLite") ? 0 : 1;
1501
1502 string strVerifyOut = tmp + "/sqlite-verify.txt";
1503 vector<string> vVerifyArgs;
1504 vVerifyArgs.push_back("-datadir=" + strWalletDir);
1505 vVerifyArgs.push_back("-nomanagedtor");
1506 vVerifyArgs.push_back("-nogui");
1507 vVerifyArgs.push_back("-walletsqliteverify=" + strSQLite);
1508 int nVerifyRet = RunBitflashChild(strExe, vVerifyArgs, NULL, &strVerifyOut);
1509 nFail += Check(nVerifyRet == 0 &&
1510 FileContainsText(strVerifyOut, "verification: ok"),
1511 "SQLite wallet export verifies against wallet.dat") ? 0 : 1;
1512
1513 string strLoadCheckOut = tmp + "/sqlite-loadcheck.txt";
1514 vector<string> vLoadCheckArgs;
1515 vLoadCheckArgs.push_back("-datadir=" + strWalletDir);
1516 vLoadCheckArgs.push_back("-nomanagedtor");
1517 vLoadCheckArgs.push_back("-nogui");
1518 vLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strSQLite);
1519 int nLoadCheckRet =
1520 RunBitflashChild(strExe, vLoadCheckArgs, NULL, &strLoadCheckOut);
1521 nFail += Check(nLoadCheckRet == 0 &&
1522 FileContainsText(strLoadCheckOut,
1523 "load check: ok"),
1524 "SQLite wallet export passes the loader compatibility check") ? 0 : 1;
1525
1526 string strRuntimeOut = tmp + "/sqlite-runtime.txt";
1527 vector<string> vRuntimeArgs;
1528 vRuntimeArgs.push_back("-datadir=" + strWalletDir);
1529 vRuntimeArgs.push_back("-nomanagedtor");
1530 vRuntimeArgs.push_back("-nogui");
1531 vRuntimeArgs.push_back("-walletsqlite=" + strSQLite);
1532 int nRuntimeRet =
1533 RunBitflashChild(strExe, vRuntimeArgs, NULL, &strRuntimeOut);
1534 nFail += Check(nRuntimeRet == 0 &&
1535 FileContainsText(strRuntimeOut,
1536 "SQLite wallet loaded: read-only staging backend"),
1537 "SQLite wallet export loads through the runtime startup path") ? 0 : 1;
1538
1539 string strTypedSQLite = tmp + "/typed-wallet.sqlite";
1540 nFail += Check(CreateTypedSQLiteWallet(strTypedSQLite),
1541 "typed SQLite wallet can be created without Berkeley DB") ? 0 : 1;
1542
1543 string strTypedLoadCheckOut = tmp + "/sqlite-typed-loadcheck.txt";
1544 vector<string> vTypedLoadCheckArgs;
1545 vTypedLoadCheckArgs.push_back("-datadir=" + strWalletDir);
1546 vTypedLoadCheckArgs.push_back("-nomanagedtor");
1547 vTypedLoadCheckArgs.push_back("-nogui");
1548 vTypedLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strTypedSQLite);
1549 int nTypedLoadCheckRet =
1550 RunBitflashChild(strExe, vTypedLoadCheckArgs, NULL,
1551 &strTypedLoadCheckOut);
1552 nFail += Check(nTypedLoadCheckRet == 0 &&
1553 FileContainsText(strTypedLoadCheckOut,
1554 "load check: ok"),
1555 "typed SQLite wallet passes the loader compatibility check") ? 0 : 1;
1556
1557 string strTypedRuntimeDir = tmp + "/typed-runtime-wallet";
1558 nFail += Check(MakeDirLocal(strTypedRuntimeDir),
1559 "typed SQLite runtime directory can be created") ? 0 : 1;
1560 string strTypedRuntimeOut = tmp + "/sqlite-typed-runtime.txt";
1561 vector<string> vTypedRuntimeArgs;
1562 vTypedRuntimeArgs.push_back("-datadir=" + strTypedRuntimeDir);
1563 vTypedRuntimeArgs.push_back("-nomanagedtor");
1564 vTypedRuntimeArgs.push_back("-nogui");
1565 vTypedRuntimeArgs.push_back("-walletsqlite=" + strTypedSQLite);
1566 int nTypedRuntimeRet =
1567 RunBitflashChild(strExe, vTypedRuntimeArgs, NULL,
1568 &strTypedRuntimeOut);
1569 nFail += Check(nTypedRuntimeRet == 0 &&
1570 FileContainsText(strTypedRuntimeOut,
1571 "SQLite wallet loaded: read-only staging backend") &&
1572 !FileExists((strTypedRuntimeDir + "/wallet.dat").c_str()),
1573 "typed SQLite wallet loads through runtime without creating wallet.dat") ? 0 : 1;
1574
1575 string strRestoredDir = tmp + "/restored-wallet";
1576 nFail += Check(MakeDirLocal(strRestoredDir),
1577 "restored wallet directory can be created") ? 0 : 1;
1578 string strRestoreOut = tmp + "/sqlite-restore.txt";
1579 vector<string> vRestoreArgs;
1580 vRestoreArgs.push_back("-datadir=" + strRestoredDir);
1581 vRestoreArgs.push_back("-nomanagedtor");
1582 vRestoreArgs.push_back("-nogui");
1583 vRestoreArgs.push_back("-walletsqliterestore=" + strSQLite);
1584 int nRestoreRet = RunBitflashChild(strExe, vRestoreArgs, NULL, &strRestoreOut);
1585 nFail += Check(nRestoreRet == 0 &&
1586 FileContainsText(strRestoreOut, "records restored:"),
1587 "SQLite wallet export restores into a fresh wallet.dat") ? 0 : 1;
1588
1589 string strVerifyRestoredOut = tmp + "/sqlite-verify-restored.txt";
1590 vector<string> vVerifyRestoredArgs;
1591 vVerifyRestoredArgs.push_back("-datadir=" + strRestoredDir);
1592 vVerifyRestoredArgs.push_back("-nomanagedtor");
1593 vVerifyRestoredArgs.push_back("-nogui");
1594 vVerifyRestoredArgs.push_back("-walletsqliteverify=" + strSQLite);
1595 int nVerifyRestoredRet =
1596 RunBitflashChild(strExe, vVerifyRestoredArgs, NULL, &strVerifyRestoredOut);
1597 nFail += Check(nVerifyRestoredRet == 0 &&
1598 FileContainsText(strVerifyRestoredOut,
1599 "verification: ok"),
1600 "restored wallet.dat verifies against the SQLite export") ? 0 : 1;
1601
1602 string strRestoreOverwriteOut = tmp + "/sqlite-restore-overwrite.txt";
1603 int nRestoreOverwriteRet =
1604 RunBitflashChild(strExe, vRestoreArgs, NULL, &strRestoreOverwriteOut);
1605 nFail += Check(nRestoreOverwriteRet == 1 &&
1606 FileContainsText(strRestoreOverwriteOut,
1607 "Refusing to overwrite existing wallet.dat"),
1608 "SQLite wallet restore refuses to overwrite wallet.dat") ? 0 : 1;
1609
1610 string strBadSQLite = tmp + "/bad-empty-key.sqlite";
1611 nFail += Check(CreateSQLiteWalletWithEmptyKey(strBadSQLite),
1612 "bad SQLite export with an empty key can be created") ? 0 : 1;
1613 string strBadRestoreDir = tmp + "/bad-restore-wallet";
1614 nFail += Check(MakeDirLocal(strBadRestoreDir),
1615 "bad restore wallet directory can be created") ? 0 : 1;
1616 string strBadRestoreOut = tmp + "/sqlite-restore-bad.txt";
1617 vector<string> vBadRestoreArgs;
1618 vBadRestoreArgs.push_back("-datadir=" + strBadRestoreDir);
1619 vBadRestoreArgs.push_back("-nomanagedtor");
1620 vBadRestoreArgs.push_back("-nogui");
1621 vBadRestoreArgs.push_back("-walletsqliterestore=" + strBadSQLite);
1622 int nBadRestoreRet =
1623 RunBitflashChild(strExe, vBadRestoreArgs, NULL, &strBadRestoreOut);
1624 nFail += Check(nBadRestoreRet == 1 &&
1625 FileContainsText(strBadRestoreOut,
1626 "Cannot restore SQLite wallet export") &&
1627 !FileExists((strBadRestoreDir + "/wallet.dat").c_str()),
1628 "failed SQLite wallet restore removes incomplete wallet.dat") ? 0 : 1;
1629
1630 string strFutureSQLite = tmp + "/future-wallet.sqlite";
1631 nFail += Check(CreateSQLiteWalletWithFutureMinVersion(strFutureSQLite),
1632 "SQLite export with a future wallet format can be created") ? 0 : 1;
1633 string strFutureLoadCheckOut = tmp + "/sqlite-loadcheck-future.txt";
1634 vector<string> vFutureLoadCheckArgs;
1635 vFutureLoadCheckArgs.push_back("-datadir=" + strWalletDir);
1636 vFutureLoadCheckArgs.push_back("-nomanagedtor");
1637 vFutureLoadCheckArgs.push_back("-nogui");
1638 vFutureLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strFutureSQLite);
1639 int nFutureLoadCheckRet =
1640 RunBitflashChild(strExe, vFutureLoadCheckArgs, NULL,
1641 &strFutureLoadCheckOut);
1642 nFail += Check(nFutureLoadCheckRet == 2 &&
1643 FileContainsText(strFutureLoadCheckOut,
1644 "unsupported future wallet format") &&
1645 FileContainsText(strFutureLoadCheckOut,
1646 "load check: failed"),
1647 "SQLite wallet load check rejects future wallet formats") ? 0 : 1;
1648
1649 string strFutureRuntimeOut = tmp + "/sqlite-runtime-future.txt";
1650 vector<string> vFutureRuntimeArgs;
1651 vFutureRuntimeArgs.push_back("-datadir=" + strWalletDir);
1652 vFutureRuntimeArgs.push_back("-nomanagedtor");
1653 vFutureRuntimeArgs.push_back("-nogui");
1654 vFutureRuntimeArgs.push_back("-walletsqlite=" + strFutureSQLite);
1655 int nFutureRuntimeRet =
1656 RunBitflashChild(strExe, vFutureRuntimeArgs, NULL,
1657 &strFutureRuntimeOut);
1658 nFail += Check(nFutureRuntimeRet == 1 &&
1659 FileContainsText(strFutureRuntimeOut,
1660 "Cannot open SQLite wallet export") &&
1661 FileContainsText(strFutureRuntimeOut,
1662 "newer version of Bitflash"),
1663 "SQLite runtime startup rejects future wallet formats") ? 0 : 1;
1664
1665 string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt";
1666 int nOverwriteRet =
1667 RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut);
1668 nFail += Check(nOverwriteRet != 0 &&
1669 FileContainsText(strOverwriteOut,
1670 "Refusing to overwrite existing SQLite wallet export"),
1671 "SQLite wallet export refuses to overwrite") ? 0 : 1;
1672 remove((strSQLite + "-wal").c_str());
1673 remove((strSQLite + "-shm").c_str());
1674
1675 std::map<vector<unsigned char>, vector<unsigned char> > mapBDB;
1676 string strOldDataDir = strSetDataDir;
1677 string strScanError;
1678 strSetDataDir = strWalletDir;
1679 CWalletRecordMapVisitor visitor(mapBDB);
1680 bool fScanOk = ScanWalletRecords(visitor, strScanError);
1681 DBFlush(true);
1682 strSetDataDir = strOldDataDir;
1683 nFail += Check(fScanOk && !mapBDB.empty(),
1684 "wallet.dat records can be scanned for comparison") ? 0 : 1;
1685
1686 CWalletDBSQLite db;
1687 string strError;
1688 nFail += Check(db.Open(strSQLite, strError),
1689 "exported SQLite wallet opens") ? 0 : 1;
1690 int nSQLiteRecords = 0;
1691 nFail += Check(db.CountRecords(nSQLiteRecords, strError) &&
1692 nSQLiteRecords == (int)mapBDB.size(),
1693 "SQLite export has the same record count as wallet.dat") ? 0 : 1;
1694
1695 bool fAllMatch = true;
1696 for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it =
1697 mapBDB.begin(); it != mapBDB.end(); ++it)
1698 {
1699 vector<unsigned char> vchSQLiteValue;
1700 if (!db.ReadRecord(it->first, vchSQLiteValue, strError) ||
1701 vchSQLiteValue != it->second)
1702 {
1703 fAllMatch = false;
1704 break;
1705 }
1706 }
1707 nFail += Check(fAllMatch,
1708 "SQLite export preserves every wallet.dat key/value byte-for-byte") ? 0 : 1;
1709
1710 std::map<vector<unsigned char>, vector<unsigned char> > mapSQLite;
1711 CWalletRecordMapVisitor sqliteVisitor(mapSQLite);
1712 nFail += Check(db.ScanRecords(sqliteVisitor, strError) &&
1713 mapSQLite == mapBDB,
1714 "SQLite wallet scanner streams every exported record byte-for-byte") ? 0 : 1;
1715
1716 db.Close();
1717 CWalletDBSQLite dbCorrupt;
1718 vector<unsigned char> vchBadValue;
1719 vchBadValue.push_back(0xba);
1720 vchBadValue.push_back(0xad);
1721 nFail += Check(!mapBDB.empty() &&
1722 dbCorrupt.Open(strSQLite, strError) &&
1723 dbCorrupt.BeginTransaction(strError) &&
1724 dbCorrupt.WriteRecord(mapBDB.begin()->first,
1725 vchBadValue,
1726 strError) &&
1727 dbCorrupt.CommitTransaction(strError) &&
1728 dbCorrupt.Checkpoint(strError),
1729 "SQLite export can be deliberately corrupted for verifier test") ? 0 : 1;
1730 dbCorrupt.Close();
1731
1732 string strVerifyBadOut = tmp + "/sqlite-verify-bad.txt";
1733 int nVerifyBadRet =
1734 RunBitflashChild(strExe, vVerifyArgs, NULL, &strVerifyBadOut);
1735 nFail += Check(nVerifyBadRet == 2 &&
1736 FileContainsText(strVerifyBadOut,
1737 "record value mismatches: 1") &&
1738 FileContainsText(strVerifyBadOut,
1739 "verification: failed"),
1740 "SQLite wallet verifier detects a mismatched record value") ? 0 : 1;
1741 }
1742 catch (const std::exception& e)
1743 {
1744 printf(" FAIL exception: %s\n", e.what());
1745 nFail++;
1746 }
1747 catch (...)
1748 {
1749 printf(" FAIL unknown exception\n");
1750 nFail++;
1751 }
1752
1753 RemoveTree(tmp);
1754 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1755 nFail, nFail == 1 ? "" : "s");
1756 fflush(stdout);
1757 return nFail == 0 ? 0 : 1;
1758}
1759
739static void ClearWalletRuntimeForTest() 1760static void ClearWalletRuntimeForTest()
740{ 1761{
741 CRITICAL_BLOCK(cs_mapKeys) 1762 CRITICAL_BLOCK(cs_mapKeys)
@@ -926,6 +1947,90 @@ static bool WriteTextFile(const string& strPath, const string& strText)
926 return fOk; 1947 return fOk;
927} 1948}
928 1949
1950static bool MakeDirLocal(const string& strPath)
1951{
1952#ifdef _WIN32
1953 return CreateDirectoryA(strPath.c_str(), NULL) || GetLastError() == ERROR_ALREADY_EXISTS;
1954#else
1955 return mkdir(strPath.c_str(), 0700) == 0 || errno == EEXIST;
1956#endif
1957}
1958
1959static bool CopyFileLocal(const string& strSrc, const string& strDst)
1960{
1961 FILE* in = fopen(strSrc.c_str(), "rb");
1962 if (!in)
1963 return false;
1964 FILE* out = fopen(strDst.c_str(), "wb");
1965 if (!out)
1966 {
1967 fclose(in);
1968 return false;
1969 }
1970
1971 bool fOk = true;
1972 char buf[65536];
1973 size_t n = 0;
1974 while ((n = fread(buf, 1, sizeof(buf), in)) > 0)
1975 {
1976 if (fwrite(buf, 1, n, out) != n)
1977 {
1978 fOk = false;
1979 break;
1980 }
1981 }
1982 if (ferror(in))
1983 fOk = false;
1984 if (fclose(out) != 0)
1985 fOk = false;
1986 fclose(in);
1987 return fOk;
1988}
1989
1990static bool DirectoryHasFileWithPrefix(const string& strDir, const string& strPrefix)
1991{
1992#ifdef _WIN32
1993 WIN32_FIND_DATAA findData;
1994 string pattern = strDir + "\\*";
1995 HANDLE hFind = FindFirstFileA(pattern.c_str(), &findData);
1996 if (hFind == INVALID_HANDLE_VALUE)
1997 return false;
1998 bool fFound = false;
1999 do
2000 {
2001 if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
2002 {
2003 string name = findData.cFileName;
2004 if (name.compare(0, strPrefix.size(), strPrefix) == 0)
2005 {
2006 fFound = true;
2007 break;
2008 }
2009 }
2010 }
2011 while (FindNextFileA(hFind, &findData));
2012 FindClose(hFind);
2013 return fFound;
2014#else
2015 DIR* dir = opendir(strDir.c_str());
2016 if (!dir)
2017 return false;
2018 bool fFound = false;
2019 struct dirent* ent;
2020 while ((ent = readdir(dir)) != NULL)
2021 {
2022 string name = ent->d_name;
2023 if (name.compare(0, strPrefix.size(), strPrefix) == 0)
2024 {
2025 fFound = true;
2026 break;
2027 }
2028 }
2029 closedir(dir);
2030 return fFound;
2031#endif
2032}
2033
929static string QuoteCommandArg(const string& str) 2034static string QuoteCommandArg(const string& str)
930{ 2035{
931 string out = "\""; 2036 string out = "\"";
@@ -942,7 +2047,8 @@ static string QuoteCommandArg(const string& str)
942 2047
943static int RunBitflashChild(const string& strExe, 2048static int RunBitflashChild(const string& strExe,
944 const vector<string>& vArgs, 2049 const vector<string>& vArgs,
945 const string* pStdinFile = NULL) 2050 const string* pStdinFile = NULL,
2051 const string* pOutputFile = NULL)
946{ 2052{
947#ifdef _WIN32 2053#ifdef _WIN32
948 vector<char*> argv; 2054 vector<char*> argv;
@@ -955,8 +2061,16 @@ static int RunBitflashChild(const string& strExe,
955 int nOldErr = _dup(2); 2061 int nOldErr = _dup(2);
956 int nOldIn = _dup(0); 2062 int nOldIn = _dup(0);
957 int nNull = _open("NUL", _O_WRONLY); 2063 int nNull = _open("NUL", _O_WRONLY);
2064 int nOut = -1;
958 int nIn = -1; 2065 int nIn = -1;
959 if (nNull >= 0) 2066 if (pOutputFile)
2067 nOut = _open(pOutputFile->c_str(), _O_WRONLY|_O_CREAT|_O_TRUNC|_O_BINARY, _S_IREAD|_S_IWRITE);
2068 if (nOut >= 0)
2069 {
2070 _dup2(nOut, 1);
2071 _dup2(nOut, 2);
2072 }
2073 else if (nNull >= 0)
960 { 2074 {
961 _dup2(nNull, 1); 2075 _dup2(nNull, 1);
962 _dup2(nNull, 2); 2076 _dup2(nNull, 2);
@@ -970,6 +2084,8 @@ static int RunBitflashChild(const string& strExe,
970 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]); 2084 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]);
971 if (nIn >= 0) 2085 if (nIn >= 0)
972 _close(nIn); 2086 _close(nIn);
2087 if (nOut >= 0)
2088 _close(nOut);
973 if (nNull >= 0) 2089 if (nNull >= 0)
974 _close(nNull); 2090 _close(nNull);
975 if (nOldIn >= 0) 2091 if (nOldIn >= 0)
@@ -994,8 +2110,17 @@ static int RunBitflashChild(const string& strExe,
994 strCmd += " " + QuoteCommandArg(vArgs[i]); 2110 strCmd += " " + QuoteCommandArg(vArgs[i]);
995 if (pStdinFile) 2111 if (pStdinFile)
996 strCmd += " < " + QuoteCommandArg(*pStdinFile); 2112 strCmd += " < " + QuoteCommandArg(*pStdinFile);
997 strCmd += " > /dev/null 2>&1"; 2113 if (pOutputFile)
998 return system(strCmd.c_str()); 2114 strCmd += " > " + QuoteCommandArg(*pOutputFile) + " 2>&1";
2115 else
2116 strCmd += " > /dev/null 2>&1";
2117 // system() returns a wait status, not the exit code; unwrap it so callers
2118 // see the same value the Windows _spawnv path returns (e.g. exit code 2 is
2119 // 2, not 512). Without this every "== 2" assertion fails only on Linux.
2120 int nSysRet = system(strCmd.c_str());
2121 if (nSysRet != -1 && WIFEXITED(nSysRet))
2122 return WEXITSTATUS(nSysRet);
2123 return nSysRet;
999#endif 2124#endif
1000} 2125}
1001 2126
@@ -1036,6 +2161,13 @@ static int RunWalletEncryptSelfTest()
1036 TopUpKeyPool(); 2161 TopUpKeyPool();
1037 2162
1038 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey(); 2163 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2164 CKey publicLabelKey;
2165 publicLabelKey.MakeNewKey();
2166 string strPublicLabelAddress = PubKeyToAddress(publicLabelKey.GetPubKey());
2167 nFail += Check(CWalletDB().WriteName(strPublicLabelAddress, "rewrite-public-name") &&
2168 CWalletDB().WriteSetting("rewrite-public-setting", (int64)424242),
2169 "public wallet records can be written before encryption") ? 0 : 1;
2170
1039 CPrivKey vchDefaultPrivKey; 2171 CPrivKey vchDefaultPrivKey;
1040 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError), 2172 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
1041 "the default private key is readable before encryption") ? 0 : 1; 2173 "the default private key is readable before encryption") ? 0 : 1;
@@ -1063,16 +2195,31 @@ static int RunWalletEncryptSelfTest()
1063#else 2195#else
1064 string strExe = cwd + "/bitflash-node"; 2196 string strExe = cwd + "/bitflash-node";
1065#endif 2197#endif
2198 string strStorageAudit = tmp + "/encrypted-storage-audit.txt";
1066 string strWrongDump = tmp + "/wrong-pass-dump.txt"; 2199 string strWrongDump = tmp + "/wrong-pass-dump.txt";
1067 string strRightDump = tmp + "/right-pass-dump.txt"; 2200 string strRightDump = tmp + "/right-pass-dump.txt";
1068 string strLiteralDump = tmp + "/literal-pass-dump.txt"; 2201 string strLiteralDump = tmp + "/literal-pass-dump.txt";
1069 string strStdinDump = tmp + "/stdin-pass-dump.txt"; 2202 string strStdinDump = tmp + "/stdin-pass-dump.txt";
2203 string strLockedAudit = tmp + "/locked-recovery-audit.txt";
2204 string strUnlockedAudit = tmp + "/unlocked-recovery-audit.txt";
1070 string strWrongPassFile = tmp + "/wrong-pass.txt"; 2205 string strWrongPassFile = tmp + "/wrong-pass.txt";
1071 string strRightPassFile = tmp + "/right-pass.txt"; 2206 string strRightPassFile = tmp + "/right-pass.txt";
1072 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") && 2207 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") &&
1073 WriteTextFile(strRightPassFile, "btf-test-passphrase\n"), 2208 WriteTextFile(strRightPassFile, "btf-test-passphrase\n"),
1074 "passphrase files can be written for restarted commands") ? 0 : 1; 2209 "passphrase files can be written for restarted commands") ? 0 : 1;
1075 2210
2211 vector<string> vStorageAuditArgs;
2212 vStorageAuditArgs.push_back("-datadir=" + tmp);
2213 vStorageAuditArgs.push_back("-walletstorageaudit");
2214 vStorageAuditArgs.push_back("-nogui");
2215 int nStorageAuditRet = RunBitflashChild(strExe, vStorageAuditArgs, NULL, &strStorageAudit);
2216 nFail += Check(nStorageAuditRet == 0 &&
2217 FileContainsText(strStorageAudit, "address book labels: 3"),
2218 "the encrypted rewrite keeps address-book records") ? 0 : 1;
2219 nFail += Check(nStorageAuditRet == 0 &&
2220 FileContainsText(strStorageAudit, "settings: 1"),
2221 "the encrypted rewrite keeps wallet setting records") ? 0 : 1;
2222
1076 vector<string> vWrongArgs; 2223 vector<string> vWrongArgs;
1077 vWrongArgs.push_back("-datadir=" + tmp); 2224 vWrongArgs.push_back("-datadir=" + tmp);
1078 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile); 2225 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
@@ -1082,6 +2229,17 @@ static int RunWalletEncryptSelfTest()
1082 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()), 2229 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
1083 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1; 2230 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1;
1084 2231
2232 vector<string> vLockedAuditArgs;
2233 vLockedAuditArgs.push_back("-datadir=" + tmp);
2234 vLockedAuditArgs.push_back("-recoveryaudit");
2235 vLockedAuditArgs.push_back("-nogui");
2236 int nLockedAuditRet = RunBitflashChild(strExe, vLockedAuditArgs, NULL, &strLockedAudit);
2237 nFail += Check(nLockedAuditRet == 2 &&
2238 FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit") &&
2239 FileContainsText(strLockedAudit, "recovery coverage: unavailable while wallet is locked") &&
2240 FileContainsText(strLockedAudit, "Unlock the wallet with /walletpassphrase"),
2241 "a locked encrypted wallet reports that phrase coverage needs unlock") ? 0 : 1;
2242
1085 vector<string> vLiteralArgs; 2243 vector<string> vLiteralArgs;
1086 vLiteralArgs.push_back("-datadir=" + tmp); 2244 vLiteralArgs.push_back("-datadir=" + tmp);
1087 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase"); 2245 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase");
@@ -1102,6 +2260,17 @@ static int RunWalletEncryptSelfTest()
1102 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)), 2260 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
1103 "the restarted wallet can decrypt and dump the original key") ? 0 : 1; 2261 "the restarted wallet can decrypt and dump the original key") ? 0 : 1;
1104 2262
2263 vector<string> vUnlockedAuditArgs;
2264 vUnlockedAuditArgs.push_back("-datadir=" + tmp);
2265 vUnlockedAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2266 vUnlockedAuditArgs.push_back("-recoveryaudit");
2267 vUnlockedAuditArgs.push_back("-nogui");
2268 int nUnlockedAuditRet = RunBitflashChild(strExe, vUnlockedAuditArgs, NULL, &strUnlockedAudit);
2269 nFail += Check(nUnlockedAuditRet == 0 &&
2270 FileContainsText(strUnlockedAudit, "recovery phrase: present") &&
2271 !FileContainsText(strUnlockedAudit, "recovery phrase: not installed"),
2272 "an unlocked encrypted wallet audits the installed recovery phrase") ? 0 : 1;
2273
1105 vector<string> vStdinArgs; 2274 vector<string> vStdinArgs;
1106 vStdinArgs.push_back("-datadir=" + tmp); 2275 vStdinArgs.push_back("-datadir=" + tmp);
1107 vStdinArgs.push_back("-walletpassphrase"); 2276 vStdinArgs.push_back("-walletpassphrase");
@@ -1132,6 +2301,737 @@ static int RunWalletEncryptSelfTest()
1132 return nFail == 0 ? 0 : 1; 2301 return nFail == 0 ? 0 : 1;
1133} 2302}
1134 2303
2304// Encrypting a wallet while it runs on the SQLite backend. Unlike the Berkeley
2305// DB encrypt test above, this drives the real command-line path end to end in
2306// child processes: build a Berkeley DB wallet, export it to SQLite, then run
2307// -walletbackend=sqlite -encryptwallet and prove the SQLite wallet came out
2308// encrypted, unlockable, and with wallet.dat left untouched.
2309static int RunWalletSQLiteEncryptSelfTest()
2310{
2311 fflush(stdout);
2312 printf("wallet-sqlite-encrypt self-test\n");
2313
2314 std::string tmp;
2315 std::string cwd;
2316 if (!MakeTempDir(tmp))
2317 {
2318 printf(" FAIL could not create a temporary data directory\n");
2319 return 1;
2320 }
2321 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2322 {
2323 printf(" FAIL could not move into the temporary data directory\n");
2324 RemoveTree(tmp);
2325 return 1;
2326 }
2327
2328 int nFail = 0;
2329 strSetDataDir = tmp;
2330 printf(" temp datadir: %s\n", tmp.c_str());
2331
2332 try
2333 {
2334 // Build a normal Berkeley DB wallet with a recovery phrase, a key pool,
2335 // and a couple of public records, then release it. Everything after this
2336 // runs the real command-line paths in child processes.
2337 if (!LoadWallet())
2338 throw std::runtime_error("LoadWallet failed");
2339
2340 string strError;
2341 const string strMnemonic =
2342 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
2343 nFail += Check(SetHDSeedFromMnemonic(strMnemonic, strError),
2344 "a phrase can be installed before encryption") ? 0 : 1;
2345 TopUpKeyPool();
2346
2347 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2348 CKey publicLabelKey;
2349 publicLabelKey.MakeNewKey();
2350 string strPublicLabelAddress = PubKeyToAddress(publicLabelKey.GetPubKey());
2351 nFail += Check(CWalletDB().WriteName(strPublicLabelAddress, "rewrite-public-name") &&
2352 CWalletDB().WriteSetting("rewrite-public-setting", (int64)424242),
2353 "public wallet records can be written before encryption") ? 0 : 1;
2354
2355 CPrivKey vchDefaultPrivKey;
2356 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
2357 "the default private key is readable before encryption") ? 0 : 1;
2358 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(), vchDefaultPrivKey.end());
2359 vector<unsigned char> vchPlainHDMaster = vchHDMaster;
2360 vector<unsigned char> vchPlainHDChainCode = vchHDChainCode;
2361
2362 // Release wallet.dat so a child process can read it for the export.
2363 DBFlush(true);
2364
2365#ifdef _WIN32
2366 string strExe = cwd + "\\bitflash.exe";
2367#else
2368 string strExe = cwd + "/bitflash-node";
2369#endif
2370 string strSQLitePath = tmp + "/wallet.sqlite";
2371 string strWalletDatPath = tmp + "/wallet.dat";
2372 string strRightPassFile = tmp + "/right-pass.txt";
2373 string strWrongPassFile = tmp + "/wrong-pass.txt";
2374 nFail += Check(WriteTextFile(strRightPassFile, "btf-test-passphrase\n") &&
2375 WriteTextFile(strWrongPassFile, "wrong-passphrase\n"),
2376 "passphrase files can be written for restarted commands") ? 0 : 1;
2377
2378 // Step 1: export the Berkeley DB wallet to SQLite.
2379 vector<string> vExportArgs;
2380 vExportArgs.push_back("-datadir=" + tmp);
2381 vExportArgs.push_back("-walletsqliteexport=" + strSQLitePath);
2382 vExportArgs.push_back("-nogui");
2383 int nExportRet = RunBitflashChild(strExe, vExportArgs);
2384 nFail += Check(nExportRet == 0 && FileExists(strSQLitePath.c_str()),
2385 "the wallet exports to a SQLite file") ? 0 : 1;
2386 nFail += Check(FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2387 "the exported SQLite wallet holds the plaintext key before encryption") ? 0 : 1;
2388
2389 // Step 2: encrypt through the real SQLite backend command path.
2390 string strEncryptOut = tmp + "/sqlite-encrypt-out.txt";
2391 vector<string> vEncryptArgs;
2392 vEncryptArgs.push_back("-datadir=" + tmp);
2393 vEncryptArgs.push_back("-walletbackend=sqlite");
2394 vEncryptArgs.push_back("-encryptwallet=@" + strRightPassFile);
2395 vEncryptArgs.push_back("-nogui");
2396 int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut);
2397 nFail += Check(nEncryptRet == 0,
2398 "the SQLite wallet can be encrypted through -walletbackend=sqlite") ? 0 : 1;
2399 nFail += Check(FileContainsText(strEncryptOut, "Wallet backend: SQLite"),
2400 "encryption took the SQLite persistence path") ? 0 : 1;
2401 nFail += Check(FileContainsText(strEncryptOut, "wallet.sqlite.before-encrypt"),
2402 "the plaintext SQLite wallet is preserved as an explicit backup") ? 0 : 1;
2403
2404 // The encrypted SQLite wallet must no longer hold the plaintext secrets.
2405 nFail += Check(!FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2406 "the encrypted SQLite wallet no longer contains the default private key bytes") ? 0 : 1;
2407 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDMaster),
2408 "the encrypted SQLite wallet no longer contains the HD master key bytes") ? 0 : 1;
2409 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDChainCode),
2410 "the encrypted SQLite wallet no longer contains the HD chain-code bytes") ? 0 : 1;
2411
2412 // The Berkeley DB wallet.dat is a separate file: the SQLite encrypt path
2413 // must never touch it, so its plaintext key is still exactly there.
2414 nFail += Check(FileContainsBytes(strWalletDatPath, vchDefaultPrivBytes),
2415 "the original Berkeley DB wallet.dat is left untouched by the SQLite encrypt") ? 0 : 1;
2416
2417 // Step 3: a locked encrypted SQLite wallet reports that phrase coverage
2418 // needs an unlock, exactly as the Berkeley DB path does (exit code 2).
2419 string strLockedAudit = tmp + "/locked-recovery-audit.txt";
2420 vector<string> vLockedArgs;
2421 vLockedArgs.push_back("-datadir=" + tmp);
2422 vLockedArgs.push_back("-walletbackend=sqlite");
2423 vLockedArgs.push_back("-recoveryaudit");
2424 vLockedArgs.push_back("-nogui");
2425 int nLockedRet = RunBitflashChild(strExe, vLockedArgs, NULL, &strLockedAudit);
2426 nFail += Check(nLockedRet == 2 &&
2427 FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit"),
2428 "a locked encrypted SQLite wallet reports that phrase coverage needs unlock") ? 0 : 1;
2429
2430 // The wrong passphrase is rejected and reveals nothing.
2431 string strWrongDump = tmp + "/wrong-dump.txt";
2432 vector<string> vWrongArgs;
2433 vWrongArgs.push_back("-datadir=" + tmp);
2434 vWrongArgs.push_back("-walletbackend=sqlite");
2435 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
2436 vWrongArgs.push_back("-dumpwallet=" + strWrongDump);
2437 vWrongArgs.push_back("-nogui");
2438 int nWrongRet = RunBitflashChild(strExe, vWrongArgs);
2439 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
2440 "a restarted SQLite wallet rejects the wrong passphrase") ? 0 : 1;
2441
2442 // The right passphrase unlocks and can dump the original key.
2443 string strRightDump = tmp + "/right-dump.txt";
2444 vector<string> vRightArgs;
2445 vRightArgs.push_back("-datadir=" + tmp);
2446 vRightArgs.push_back("-walletbackend=sqlite");
2447 vRightArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2448 vRightArgs.push_back("-dumpwallet=" + strRightDump);
2449 vRightArgs.push_back("-nogui");
2450 int nRightRet = RunBitflashChild(strExe, vRightArgs);
2451 nFail += Check(nRightRet == 0 && FileExists(strRightDump.c_str()),
2452 "a restarted SQLite wallet unlocks with the right passphrase") ? 0 : 1;
2453 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
2454 "the unlocked SQLite wallet decrypts and dumps the original key") ? 0 : 1;
2455
2456 // The recovery phrase survives the encryption.
2457 string strAudit = tmp + "/recovery-audit.txt";
2458 vector<string> vAuditArgs;
2459 vAuditArgs.push_back("-datadir=" + tmp);
2460 vAuditArgs.push_back("-walletbackend=sqlite");
2461 vAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2462 vAuditArgs.push_back("-recoveryaudit");
2463 vAuditArgs.push_back("-nogui");
2464 int nAuditRet = RunBitflashChild(strExe, vAuditArgs, NULL, &strAudit);
2465 nFail += Check(nAuditRet == 0 &&
2466 FileContainsText(strAudit, "recovery phrase: present"),
2467 "the encrypted SQLite wallet still has its recovery phrase") ? 0 : 1;
2468
2469 // One-shot storage commands honour -walletbackend. The SQLite wallet is
2470 // encrypted; the wallet.dat beside it is still plaintext. Same datadir,
2471 // opposite answers -> the flag routes the diagnostic to the right file.
2472 string strSqliteAuditJson = tmp + "/sqlite-audit.json";
2473 vector<string> vSqliteAuditArgs;
2474 vSqliteAuditArgs.push_back("-datadir=" + tmp);
2475 vSqliteAuditArgs.push_back("-walletbackend=sqlite");
2476 vSqliteAuditArgs.push_back("-walletstorageauditjson=" + strSqliteAuditJson);
2477 vSqliteAuditArgs.push_back("-nogui");
2478 int nSqliteAuditRet = RunBitflashChild(strExe, vSqliteAuditArgs);
2479 nFail += Check(nSqliteAuditRet == 0 &&
2480 FileContainsText(strSqliteAuditJson, "\"encrypted_hd_seed\": \"complete\"") &&
2481 FileContainsText(strSqliteAuditJson, "\"plain_hd_seed\": \"none\""),
2482 "-walletstorageaudit under SQLite reads the encrypted wallet.sqlite") ? 0 : 1;
2483
2484 string strBdbAuditJson = tmp + "/bdb-audit.json";
2485 vector<string> vBdbAuditArgs;
2486 vBdbAuditArgs.push_back("-datadir=" + tmp);
2487 vBdbAuditArgs.push_back("-walletstorageauditjson=" + strBdbAuditJson);
2488 vBdbAuditArgs.push_back("-nogui");
2489 int nBdbAuditRet = RunBitflashChild(strExe, vBdbAuditArgs);
2490 nFail += Check(nBdbAuditRet == 0 &&
2491 FileContainsText(strBdbAuditJson, "\"plain_hd_seed\": \"complete\"") &&
2492 FileContainsText(strBdbAuditJson, "\"encrypted_hd_seed\": \"none\""),
2493 "-walletstorageaudit without the flag still reads the plaintext wallet.dat") ? 0 : 1;
2494
2495 string strSqliteCheckOut = tmp + "/sqlite-check.txt";
2496 vector<string> vSqliteCheckArgs;
2497 vSqliteCheckArgs.push_back("-datadir=" + tmp);
2498 vSqliteCheckArgs.push_back("-walletbackend=sqlite");
2499 vSqliteCheckArgs.push_back("-walletstoragecheck");
2500 vSqliteCheckArgs.push_back("-nogui");
2501 int nSqliteCheckRet = RunBitflashChild(strExe, vSqliteCheckArgs, NULL, &strSqliteCheckOut);
2502 nFail += Check(nSqliteCheckRet == 0,
2503 "-walletstoragecheck under SQLite passes on the encrypted wallet.sqlite") ? 0 : 1;
2504
2505 // Backup under SQLite copies wallet.sqlite, not the plaintext wallet.dat.
2506 // The copy is a standalone SQLite wallet (loadcheck accepts it) and holds
2507 // none of the plaintext key that wallet.dat still contains.
2508 string strBackupDest = tmp + "/backup.sqlite";
2509 vector<string> vBackupArgs;
2510 vBackupArgs.push_back("-datadir=" + tmp);
2511 vBackupArgs.push_back("-walletbackend=sqlite");
2512 vBackupArgs.push_back("-backupwallet=" + strBackupDest);
2513 vBackupArgs.push_back("-nogui");
2514 int nBackupRet = RunBitflashChild(strExe, vBackupArgs);
2515 nFail += Check(nBackupRet == 0 && FileExists(strBackupDest.c_str()),
2516 "-backupwallet under SQLite writes a backup file") ? 0 : 1;
2517 nFail += Check(!FileContainsBytes(strBackupDest, vchDefaultPrivBytes),
2518 "the SQLite backup holds no plaintext key (it copied wallet.sqlite, not wallet.dat)") ? 0 : 1;
2519
2520 string strBackupLoadCheck = tmp + "/backup-loadcheck.txt";
2521 vector<string> vLoadCheckArgs;
2522 vLoadCheckArgs.push_back("-datadir=" + tmp);
2523 vLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strBackupDest);
2524 vLoadCheckArgs.push_back("-nogui");
2525 int nBackupLoadRet = RunBitflashChild(strExe, vLoadCheckArgs, NULL, &strBackupLoadCheck);
2526 nFail += Check(nBackupLoadRet == 0,
2527 "the SQLite backup is a valid standalone SQLite wallet") ? 0 : 1;
2528 }
2529 catch (const std::exception& e)
2530 {
2531 printf(" FAIL exception: %s\n", e.what());
2532 nFail++;
2533 }
2534 catch (...)
2535 {
2536 printf(" FAIL unknown exception\n");
2537 nFail++;
2538 }
2539
2540 DBFlush(true);
2541 SetCurrentDir(cwd);
2542 RemoveTree(tmp);
2543 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2544 nFail, nFail == 1 ? "" : "s");
2545 fflush(stdout);
2546 return nFail == 0 ? 0 : 1;
2547}
2548
2549// Backend resolution when no -walletbackend flag is given: a fresh install
2550// starts on SQLite (the new default), an existing wallet.dat stays on Berkeley
2551// DB, the persistent marker overrides the default, and an explicit flag
2552// overrides everything. Each scenario runs a real child process.
2553static int RunWalletBackendDefaultSelfTest()
2554{
2555 fflush(stdout);
2556 printf("wallet-backend-default self-test\n");
2557
2558 std::string tmp, cwd;
2559 if (!MakeTempDir(tmp))
2560 {
2561 printf(" FAIL could not create a temporary data directory\n");
2562 return 1;
2563 }
2564 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2565 {
2566 printf(" FAIL could not move into the temporary data directory\n");
2567 RemoveTree(tmp);
2568 return 1;
2569 }
2570
2571 int nFail = 0;
2572 strSetDataDir = tmp;
2573 printf(" temp datadir: %s\n", tmp.c_str());
2574
2575#ifdef _WIN32
2576 string strExe = cwd + "\\bitflash.exe";
2577#else
2578 string strExe = cwd + "/bitflash-node";
2579#endif
2580
2581 try
2582 {
2583 // 1. Fresh datadir, no flag -> this fork refuses, where upstream creates.
2584 // An empty datadir on a headless node is far more often a mount or
2585 // backup fault than a first run, and creating a wallet also pins the
2586 // backend marker, after which restoring the real wallet is a no-op.
2587 // Nothing may be left behind: no wallet of either kind, no marker.
2588 string d1 = tmp + "/fresh";
2589 nFail += Check(MakeDirLocal(d1), "fresh scenario dir can be created") ? 0 : 1;
2590 vector<string> a1;
2591 a1.push_back("-datadir=" + d1);
2592 a1.push_back("-nomanagedtor");
2593 a1.push_back("-nogui");
2594 a1.push_back("-newaddress");
2595 int r0 = RunBitflashChild(strExe, a1);
2596 nFail += Check(r0 != 0 &&
2597 !FileExists((d1 + "/wallet.sqlite").c_str()) &&
2598 !FileExists((d1 + "/wallet.dat").c_str()) &&
2599 !FileExists((d1 + "/wallet-backend").c_str()),
2600 "a flagless headless start refuses to create a wallet") ? 0 : 1;
2601
2602 // 1a. -createwallet says it was meant: new wallet.sqlite, marker
2603 // "sqlite", no wallet.dat.
2604 vector<string> a1c = a1;
2605 a1c.push_back("-createwallet");
2606 int r1 = RunBitflashChild(strExe, a1c);
2607 nFail += Check(r1 == 0 &&
2608 FileExists((d1 + "/wallet.sqlite").c_str()) &&
2609 !FileExists((d1 + "/wallet.dat").c_str()) &&
2610 FileContainsText(d1 + "/wallet-backend", "sqlite"),
2611 "-createwallet starts a fresh install on SQLite and records the marker") ? 0 : 1;
2612
2613 // 1b. Second run reuses it without the flag -- the refusal only guards
2614 // creation, so an existing wallet still starts flagless.
2615 int r1b = RunBitflashChild(strExe, a1);
2616 nFail += Check(r1b == 0 &&
2617 FileExists((d1 + "/wallet.sqlite").c_str()) &&
2618 !FileExists((d1 + "/wallet.dat").c_str()),
2619 "the recorded SQLite choice survives a flagless restart") ? 0 : 1;
2620
2621 // 2. Existing wallet.dat, no flag -> stays on Berkeley DB.
2622 string d2 = tmp + "/existing-bdb";
2623 nFail += Check(MakeDirLocal(d2), "existing-bdb scenario dir can be created") ? 0 : 1;
2624 vector<string> a2c;
2625 a2c.push_back("-datadir=" + d2);
2626 a2c.push_back("-nomanagedtor");
2627 a2c.push_back("-nogui");
2628 a2c.push_back("-walletbackend=bdb");
2629 a2c.push_back("-newphrase");
2630 RunBitflashChild(strExe, a2c);
2631 vector<string> a2;
2632 a2.push_back("-datadir=" + d2);
2633 a2.push_back("-nomanagedtor");
2634 a2.push_back("-nogui");
2635 a2.push_back("-newaddress");
2636 int r2 = RunBitflashChild(strExe, a2);
2637 nFail += Check(r2 == 0 &&
2638 FileExists((d2 + "/wallet.dat").c_str()) &&
2639 !FileExists((d2 + "/wallet.sqlite").c_str()),
2640 "an existing wallet.dat keeps a flagless start on Berkeley DB") ? 0 : 1;
2641
2642 // 3. Marker "bdb" forces Berkeley DB even on a fresh datadir.
2643 string d3 = tmp + "/marker-bdb";
2644 nFail += Check(MakeDirLocal(d3), "marker-bdb scenario dir can be created") ? 0 : 1;
2645 nFail += Check(WriteTextFile(d3 + "/wallet-backend", "bdb\n"),
2646 "a bdb marker can be written") ? 0 : 1;
2647 vector<string> a3;
2648 a3.push_back("-datadir=" + d3);
2649 a3.push_back("-nomanagedtor");
2650 a3.push_back("-nogui");
2651 a3.push_back("-newaddress");
2652 int r3 = RunBitflashChild(strExe, a3);
2653 nFail += Check(r3 == 0 &&
2654 FileExists((d3 + "/wallet.dat").c_str()) &&
2655 !FileExists((d3 + "/wallet.sqlite").c_str()),
2656 "a bdb marker forces Berkeley DB on a fresh datadir") ? 0 : 1;
2657
2658 // 4. Explicit -walletbackend=bdb on a fresh datadir -> wallet.dat.
2659 string d4 = tmp + "/explicit-bdb";
2660 nFail += Check(MakeDirLocal(d4), "explicit-bdb scenario dir can be created") ? 0 : 1;
2661 vector<string> a4;
2662 a4.push_back("-datadir=" + d4);
2663 a4.push_back("-nomanagedtor");
2664 a4.push_back("-nogui");
2665 a4.push_back("-walletbackend=bdb");
2666 a4.push_back("-newaddress");
2667 int r4 = RunBitflashChild(strExe, a4);
2668 nFail += Check(r4 == 0 &&
2669 FileExists((d4 + "/wallet.dat").c_str()) &&
2670 !FileExists((d4 + "/wallet.sqlite").c_str()),
2671 "an explicit -walletbackend=bdb creates wallet.dat on a fresh datadir") ? 0 : 1;
2672 }
2673 catch (const std::exception& e)
2674 {
2675 printf(" FAIL exception: %s\n", e.what());
2676 nFail++;
2677 }
2678 catch (...)
2679 {
2680 printf(" FAIL unknown exception\n");
2681 nFail++;
2682 }
2683
2684 DBFlush(true);
2685 SetCurrentDir(cwd);
2686 RemoveTree(tmp);
2687 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2688 nFail, nFail == 1 ? "" : "s");
2689 fflush(stdout);
2690 return nFail == 0 ? 0 : 1;
2691}
2692
2693// The wizard's conversion action, headless: build a Berkeley DB wallet, run
2694// DoConvertWalletToSQLite() (what the GUI button calls), and prove it produced a
2695// usable wallet.sqlite + backend marker while leaving wallet.dat untouched, then
2696// that a flagless restart opens the converted SQLite wallet.
2697static int RunWalletConvertSelfTest()
2698{
2699 fflush(stdout);
2700 printf("wallet-convert self-test\n");
2701
2702 std::string tmp, cwd;
2703 if (!MakeTempDir(tmp))
2704 {
2705 printf(" FAIL could not create a temporary data directory\n");
2706 return 1;
2707 }
2708 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2709 {
2710 printf(" FAIL could not move into the temporary data directory\n");
2711 RemoveTree(tmp);
2712 return 1;
2713 }
2714
2715 int nFail = 0;
2716 strSetDataDir = tmp;
2717 printf(" temp datadir: %s\n", tmp.c_str());
2718
2719#ifdef _WIN32
2720 string strExe = cwd + "\\bitflash.exe";
2721#else
2722 string strExe = cwd + "/bitflash-node";
2723#endif
2724
2725 try
2726 {
2727 if (!LoadWallet())
2728 throw std::runtime_error("LoadWallet failed");
2729
2730 string strError;
2731 nFail += Check(SetHDSeedFromMnemonic(
2732 "abandon abandon abandon abandon abandon abandon abandon "
2733 "abandon abandon abandon abandon about", strError),
2734 "a phrase can be installed before conversion") ? 0 : 1;
2735 TopUpKeyPool();
2736
2737 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2738 CPrivKey vchDefaultPrivKey;
2739 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
2740 "the default private key is readable before conversion") ? 0 : 1;
2741 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(),
2742 vchDefaultPrivKey.end());
2743
2744 // The conversion action the GUI button invokes.
2745 int nCopied = 0;
2746 string strConvertError;
2747 bool fConverted = DoConvertWalletToSQLite(nCopied, strConvertError);
2748 nFail += Check(fConverted && nCopied > 0,
2749 "DoConvertWalletToSQLite exports the wallet") ? 0 : 1;
2750 nFail += Check(FileExists((tmp + "/wallet.sqlite").c_str()) &&
2751 FileContainsText(tmp + "/wallet-backend", "sqlite"),
2752 "conversion writes wallet.sqlite and records the sqlite marker") ? 0 : 1;
2753 nFail += Check(FileContainsBytes(tmp + "/wallet.dat", vchDefaultPrivBytes),
2754 "wallet.dat is left untouched by the conversion") ? 0 : 1;
2755
2756 // Idempotence guard: a second conversion refuses rather than overwrite.
2757 int nCopied2 = 0;
2758 string strSecondError;
2759 nFail += Check(!DoConvertWalletToSQLite(nCopied2, strSecondError),
2760 "a second conversion refuses to overwrite wallet.sqlite") ? 0 : 1;
2761
2762 DBFlush(true);
2763
2764 // A flagless restart now opens the converted SQLite wallet (the marker
2765 // routes it) and finds the same recovery phrase.
2766 string strAudit = tmp + "/converted-recovery-audit.txt";
2767 vector<string> vAuditArgs;
2768 vAuditArgs.push_back("-datadir=" + tmp);
2769 vAuditArgs.push_back("-nomanagedtor");
2770 vAuditArgs.push_back("-nogui");
2771 vAuditArgs.push_back("-recoveryaudit");
2772 int nAuditRet = RunBitflashChild(strExe, vAuditArgs, NULL, &strAudit);
2773 nFail += Check(nAuditRet == 0 &&
2774 FileContainsText(strAudit, "recovery phrase: present"),
2775 "a flagless restart opens the converted SQLite wallet") ? 0 : 1;
2776 }
2777 catch (const std::exception& e)
2778 {
2779 printf(" FAIL exception: %s\n", e.what());
2780 nFail++;
2781 }
2782 catch (...)
2783 {
2784 printf(" FAIL unknown exception\n");
2785 nFail++;
2786 }
2787
2788 DBFlush(true);
2789 SetCurrentDir(cwd);
2790 RemoveTree(tmp);
2791 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2792 nFail, nFail == 1 ? "" : "s");
2793 fflush(stdout);
2794 return nFail == 0 ? 0 : 1;
2795}
2796
2797static int RunWalletPortabilitySelfTest()
2798{
2799 fflush(stdout);
2800 printf("wallet-portability self-test\n");
2801
2802 std::string tmp;
2803 std::string cwd;
2804 if (!MakeTempDir(tmp))
2805 {
2806 printf(" FAIL could not create a temporary data directory\n");
2807 return 1;
2808 }
2809 if (!GetCurrentDir(cwd))
2810 {
2811 printf(" FAIL could not read current directory\n");
2812 RemoveTree(tmp);
2813 return 1;
2814 }
2815
2816 int nFail = 0;
2817 printf(" temp root: %s\n", tmp.c_str());
2818
2819 try
2820 {
2821#ifdef _WIN32
2822 string strExe = cwd + "\\bitflash.exe";
2823#else
2824 string strExe = cwd + "/bitflash-node";
2825#endif
2826 string strOriginal = tmp + "/original";
2827 string strRawCopy = tmp + "/raw-copy";
2828 string strBackupCopy = tmp + "/backup-copy";
2829 nFail += Check(MakeDirLocal(strOriginal) &&
2830 MakeDirLocal(strRawCopy) &&
2831 MakeDirLocal(strBackupCopy),
2832 "temporary wallet directories can be created") ? 0 : 1;
2833
2834 string strCreateOut = tmp + "/create.txt";
2835 vector<string> vCreateArgs;
2836 vCreateArgs.push_back("-datadir=" + strOriginal);
2837 vCreateArgs.push_back("-nomanagedtor");
2838 vCreateArgs.push_back("-nogui");
2839 vCreateArgs.push_back("-walletbackend=bdb"); // this suite is about wallet.dat portability
2840 vCreateArgs.push_back("-newphrase");
2841 int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut);
2842 nFail += Check(nCreateRet == 0 &&
2843 FileContainsText(strCreateOut, "Write these twelve words down"),
2844 "a phrase wallet can be created in a fresh datadir") ? 0 : 1;
2845
2846 string strOriginalAudit = tmp + "/original-audit.txt";
2847 vector<string> vOriginalAuditArgs;
2848 vOriginalAuditArgs.push_back("-datadir=" + strOriginal);
2849 vOriginalAuditArgs.push_back("-nomanagedtor");
2850 vOriginalAuditArgs.push_back("-nogui");
2851 vOriginalAuditArgs.push_back("-recoveryaudit");
2852 int nOriginalAuditRet = RunBitflashChild(strExe, vOriginalAuditArgs, NULL, &strOriginalAudit);
2853 nFail += Check(nOriginalAuditRet == 0 &&
2854 FileContainsText(strOriginalAudit, "recovery phrase: present") &&
2855 FileContainsText(strOriginalAudit, "SLIP-0044 BITFLASH"),
2856 "the original wallet reports its recovery phrase") ? 0 : 1;
2857
2858 string strOriginalStorageAudit = tmp + "/original-storage-audit.txt";
2859 vector<string> vOriginalStorageAuditArgs;
2860 vOriginalStorageAuditArgs.push_back("-datadir=" + strOriginal);
2861 vOriginalStorageAuditArgs.push_back("-nomanagedtor");
2862 vOriginalStorageAuditArgs.push_back("-nogui");
2863 vOriginalStorageAuditArgs.push_back("-walletstorageaudit");
2864 int nOriginalStorageAuditRet =
2865 RunBitflashChild(strExe, vOriginalStorageAuditArgs, NULL, &strOriginalStorageAudit);
2866 nFail += Check(nOriginalStorageAuditRet == 0 &&
2867 FileContainsText(strOriginalStorageAudit, "Wallet storage audit") &&
2868 FileContainsText(strOriginalStorageAudit, "plain HD seed: complete") &&
2869 FileContainsText(strOriginalStorageAudit, "encrypted HD seed: none") &&
2870 FileContainsText(strOriginalStorageAudit, "malformed records: 0"),
2871 "the storage audit recognizes a plaintext phrase wallet") ? 0 : 1;
2872
2873 string strOriginalStorageCheck = tmp + "/original-storage-check.txt";
2874 vector<string> vOriginalStorageCheckArgs;
2875 vOriginalStorageCheckArgs.push_back("-datadir=" + strOriginal);
2876 vOriginalStorageCheckArgs.push_back("-nomanagedtor");
2877 vOriginalStorageCheckArgs.push_back("-nogui");
2878 vOriginalStorageCheckArgs.push_back("-walletstoragecheck");
2879 int nOriginalStorageCheckRet =
2880 RunBitflashChild(strExe, vOriginalStorageCheckArgs, NULL, &strOriginalStorageCheck);
2881 nFail += Check(nOriginalStorageCheckRet == 0 &&
2882 FileContainsText(strOriginalStorageCheck, "storage sanity: ok") &&
2883 FileContainsText(strOriginalStorageCheck, "plain HD seed: complete"),
2884 "the storage sanity check accepts a plaintext phrase wallet") ? 0 : 1;
2885
2886 string strOriginalStorageAuditJson = tmp + "/original-storage-audit.json";
2887 vector<string> vOriginalStorageAuditJsonArgs;
2888 vOriginalStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
2889 vOriginalStorageAuditJsonArgs.push_back("-nomanagedtor");
2890 vOriginalStorageAuditJsonArgs.push_back("-nogui");
2891 vOriginalStorageAuditJsonArgs.push_back("-walletstorageauditjson=" +
2892 strOriginalStorageAuditJson);
2893 int nOriginalStorageAuditJsonRet =
2894 RunBitflashChild(strExe, vOriginalStorageAuditJsonArgs);
2895 nFail += Check(nOriginalStorageAuditJsonRet == 0 &&
2896 FileContainsText(strOriginalStorageAuditJson,
2897 "\"format\": \"bitflash-wallet-storage-audit-v1\"") &&
2898 FileContainsText(strOriginalStorageAuditJson,
2899 "\"plain_hd_seed\": \"complete\"") &&
2900 FileContainsText(strOriginalStorageAuditJson,
2901 "\"encrypted_hd_seed\": \"none\""),
2902 "the storage audit can write plaintext-wallet JSON") ? 0 : 1;
2903
2904 string strPortableWallet = tmp + "/portable-wallet.dat";
2905 string strBackupOut = tmp + "/backupwallet.txt";
2906 vector<string> vBackupArgs;
2907 vBackupArgs.push_back("-datadir=" + strOriginal);
2908 vBackupArgs.push_back("-nomanagedtor");
2909 vBackupArgs.push_back("-nogui");
2910 vBackupArgs.push_back("-backupwallet=" + strPortableWallet);
2911 int nBackupRet = RunBitflashChild(strExe, vBackupArgs, NULL, &strBackupOut);
2912 nFail += Check(nBackupRet == 0 && FileExists(strPortableWallet.c_str()),
2913 "/backupwallet writes a portable wallet.dat") ? 0 : 1;
2914
2915 string strBackupOut2 = tmp + "/backupwallet-overwrite.txt";
2916 int nBackupRet2 = RunBitflashChild(strExe, vBackupArgs, NULL, &strBackupOut2);
2917 nFail += Check(nBackupRet2 == 0 && FileExists(strPortableWallet.c_str()) &&
2918 !DirectoryHasFileWithPrefix(tmp, "portable-wallet.dat.tmp.") &&
2919 !DirectoryHasFileWithPrefix(tmp, "portable-wallet.dat.old."),
2920 "/backupwallet replaces an existing backup without leaving temp files") ? 0 : 1;
2921
2922 nFail += Check(CopyFileLocal(strOriginal + "/wallet.dat",
2923 strRawCopy + "/wallet.dat"),
2924 "a raw wallet.dat can be copied without database logs") ? 0 : 1;
2925 string strRawAudit = tmp + "/raw-copy-audit.txt";
2926 vector<string> vRawAuditArgs;
2927 vRawAuditArgs.push_back("-datadir=" + strRawCopy);
2928 vRawAuditArgs.push_back("-nomanagedtor");
2929 vRawAuditArgs.push_back("-nogui");
2930 vRawAuditArgs.push_back("-recoveryaudit");
2931 int nRawAuditRet = RunBitflashChild(strExe, vRawAuditArgs, NULL, &strRawAudit);
2932 nFail += Check(nRawAuditRet == 0 &&
2933 FileContainsText(strRawAudit, "recovery phrase: present") &&
2934 !FileContainsText(strRawAudit, "Cannot open wallet.dat"),
2935 "a raw wallet.dat copy opens in a new datadir") ? 0 : 1;
2936
2937 nFail += Check(CopyFileLocal(strPortableWallet,
2938 strBackupCopy + "/wallet.dat"),
2939 "the portable backup can be installed as wallet.dat") ? 0 : 1;
2940 string strBackupAudit = tmp + "/backup-copy-audit.txt";
2941 vector<string> vBackupAuditArgs;
2942 vBackupAuditArgs.push_back("-datadir=" + strBackupCopy);
2943 vBackupAuditArgs.push_back("-nomanagedtor");
2944 vBackupAuditArgs.push_back("-nogui");
2945 vBackupAuditArgs.push_back("-recoveryaudit");
2946 int nBackupAuditRet = RunBitflashChild(strExe, vBackupAuditArgs, NULL, &strBackupAudit);
2947 nFail += Check(nBackupAuditRet == 0 &&
2948 FileContainsText(strBackupAudit, "recovery phrase: present") &&
2949 FileContainsText(strBackupAudit, "SLIP-0044 BITFLASH"),
2950 "a /backupwallet copy opens in a new datadir") ? 0 : 1;
2951
2952 string strPassFile = tmp + "/encrypt-pass.txt";
2953 string strEncryptOut = tmp + "/encryptwallet.txt";
2954 nFail += Check(WriteTextFile(strPassFile, "portable-test-passphrase\n"),
2955 "an encryption passphrase file can be written") ? 0 : 1;
2956 vector<string> vEncryptArgs;
2957 vEncryptArgs.push_back("-datadir=" + strOriginal);
2958 vEncryptArgs.push_back("-nomanagedtor");
2959 vEncryptArgs.push_back("-nogui");
2960 vEncryptArgs.push_back("-encryptwallet=@" + strPassFile);
2961 int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut);
2962 nFail += Check(nEncryptRet == 0 &&
2963 !DirectoryHasFileWithPrefix(strOriginal + "/database", "log."),
2964 "the encryptwallet command purges Berkeley DB environment logs") ? 0 : 1;
2965
2966 string strEncryptedStorageAudit = tmp + "/encrypted-storage-audit.txt";
2967 vector<string> vEncryptedStorageAuditArgs;
2968 vEncryptedStorageAuditArgs.push_back("-datadir=" + strOriginal);
2969 vEncryptedStorageAuditArgs.push_back("-nomanagedtor");
2970 vEncryptedStorageAuditArgs.push_back("-nogui");
2971 vEncryptedStorageAuditArgs.push_back("-walletstorageaudit");
2972 int nEncryptedStorageAuditRet =
2973 RunBitflashChild(strExe, vEncryptedStorageAuditArgs, NULL, &strEncryptedStorageAudit);
2974 nFail += Check(nEncryptedStorageAuditRet == 0 &&
2975 FileContainsText(strEncryptedStorageAudit, "plain private keys: 0") &&
2976 FileContainsText(strEncryptedStorageAudit, "encrypted private keys:") &&
2977 FileContainsText(strEncryptedStorageAudit, "plain HD seed: none") &&
2978 FileContainsText(strEncryptedStorageAudit, "encrypted HD seed: complete") &&
2979 FileContainsText(strEncryptedStorageAudit, "wallet minimum version: present") &&
2980 FileContainsText(strEncryptedStorageAudit, "malformed records: 0"),
2981 "the storage audit recognizes an encrypted wallet") ? 0 : 1;
2982
2983 string strEncryptedStorageCheck = tmp + "/encrypted-storage-check.txt";
2984 vector<string> vEncryptedStorageCheckArgs;
2985 vEncryptedStorageCheckArgs.push_back("-datadir=" + strOriginal);
2986 vEncryptedStorageCheckArgs.push_back("-nomanagedtor");
2987 vEncryptedStorageCheckArgs.push_back("-nogui");
2988 vEncryptedStorageCheckArgs.push_back("-walletstoragecheck");
2989 int nEncryptedStorageCheckRet =
2990 RunBitflashChild(strExe, vEncryptedStorageCheckArgs, NULL, &strEncryptedStorageCheck);
2991 nFail += Check(nEncryptedStorageCheckRet == 0 &&
2992 FileContainsText(strEncryptedStorageCheck, "storage sanity: ok") &&
2993 FileContainsText(strEncryptedStorageCheck, "plain private keys: 0") &&
2994 FileContainsText(strEncryptedStorageCheck, "encrypted HD seed: complete"),
2995 "the storage sanity check accepts an encrypted wallet") ? 0 : 1;
2996
2997 string strEncryptedStorageAuditJson = tmp + "/encrypted-storage-audit.json";
2998 vector<string> vEncryptedStorageAuditJsonArgs;
2999 vEncryptedStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
3000 vEncryptedStorageAuditJsonArgs.push_back("-nomanagedtor");
3001 vEncryptedStorageAuditJsonArgs.push_back("-nogui");
3002 vEncryptedStorageAuditJsonArgs.push_back("-walletstorageauditjson=" +
3003 strEncryptedStorageAuditJson);
3004 int nEncryptedStorageAuditJsonRet =
3005 RunBitflashChild(strExe, vEncryptedStorageAuditJsonArgs);
3006 nFail += Check(nEncryptedStorageAuditJsonRet == 0 &&
3007 FileContainsText(strEncryptedStorageAuditJson,
3008 "\"plain_private_keys\": 0") &&
3009 FileContainsText(strEncryptedStorageAuditJson,
3010 "\"plain_hd_seed\": \"none\"") &&
3011 FileContainsText(strEncryptedStorageAuditJson,
3012 "\"encrypted_hd_seed\": \"complete\"") &&
3013 FileContainsText(strEncryptedStorageAuditJson,
3014 "\"wallet_minimum_version\": \"present\""),
3015 "the storage audit can write encrypted-wallet JSON") ? 0 : 1;
3016 }
3017 catch (const std::exception& e)
3018 {
3019 printf(" FAIL exception: %s\n", e.what());
3020 nFail++;
3021 }
3022 catch (...)
3023 {
3024 printf(" FAIL unknown exception\n");
3025 nFail++;
3026 }
3027
3028 RemoveTree(tmp);
3029 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
3030 nFail, nFail == 1 ? "" : "s");
3031 fflush(stdout);
3032 return nFail == 0 ? 0 : 1;
3033}
3034
1135static int RunNetMessageSelfTest() 3035static int RunNetMessageSelfTest()
1136{ 3036{
1137 fflush(stdout); 3037 fflush(stdout);
@@ -1235,6 +3135,74 @@ static CBlock MakeSizedConsensusBlock(size_t nScriptBytes)
1235 return block; 3135 return block;
1236} 3136}
1237 3137
3138// A distinct transaction of a chosen weight and verification cost. nUnique
3139// only keeps the txids apart, so a mempool built from these is not a pile of
3140// duplicates a block could never carry anyway.
3141static CTransaction MakeSizedTx(size_t nPadBytes, unsigned int nSigOps, int nUnique)
3142{
3143 CTransaction tx;
3144 tx.vin.push_back(CTxIn(COutPoint(), CScript() << (int64)nUnique << 1));
3145 tx.vout.push_back(CTxOut(0, CScript()));
3146 for (unsigned int i = 0; i < nSigOps; i++)
3147 tx.vout[0].scriptPubKey << OP_CHECKSIG;
3148 tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), nPadBytes, 0);
3149 return tx;
3150}
3151
3152static CBlock MakeCoinbaseOnlyTemplate()
3153{
3154 CTransaction txNew;
3155 txNew.vin.resize(1);
3156 txNew.vin[0].prevout.SetNull();
3157 txNew.vin[0].scriptSig << 1 << 1 << 1;
3158 txNew.vout.resize(1);
3159 txNew.vout[0].scriptPubKey << OP_CHECKSIG;
3160
3161 CBlock block;
3162 block.vtx.push_back(txNew);
3163 block.nVersion = 1;
3164 block.nTime = GetAdjustedTime();
3165 block.nBits = bnProofOfWorkLimit.GetCompact();
3166 block.nNonce = 0;
3167 return block;
3168}
3169
3170// The sum CheckBlock() takes, spelled the same way.
3171static unsigned int BlockSigOpCount(const CBlock& block)
3172{
3173 unsigned int n = 0;
3174 for (int i = 0; i < (int)block.vtx.size(); i++)
3175 n += block.vtx[i].GetSigOpCount();
3176 return n;
3177}
3178
3179// Drive the selection rule the way both assemblers drive it: ask
3180// BlockTemplateFits before appending, skip what does not fit, never stop early
3181// on one that does not.
3182//
3183// This mirrors the loops in BitcoinMiner() and RebuildJob() rather than calling
3184// them -- one needs a CTxDB, a wallet key and cs_main, the other is static in
3185// rpc.cpp, so neither is reachable from here. What that leaves proven is the
3186// rule the two of them share and the property it is meant to guarantee, which
3187// is where the defect actually lived: the budget was right and the enforcement
3188// was not.
3189static void SelectIntoTemplate(CBlock& block, const vector<CTransaction>& vMempool)
3190{
3191 unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK);
3192 unsigned int nBlockSigOps = block.vtx[0].GetSigOpCount();
3193 for (int i = 0; i < (int)vMempool.size(); i++)
3194 {
3195 const CTransaction& tx = vMempool[i];
3196 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
3197 unsigned int nTxSigOps = tx.GetSigOpCount();
3198 if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps))
3199 continue;
3200 block.vtx.push_back(tx);
3201 nBlockSize += nTxSize;
3202 nBlockSigOps += nTxSigOps;
3203 }
3204}
3205
1238static int RunConsensusLimitsSelfTest() 3206static int RunConsensusLimitsSelfTest()
1239{ 3207{
1240 fflush(stdout); 3208 fflush(stdout);
@@ -1258,6 +3226,95 @@ static int RunConsensusLimitsSelfTest()
1258 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1; 3226 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1;
1259 nFail += Check(!oversized.CheckSizeLimits(), 3227 nFail += Check(!oversized.CheckSizeLimits(),
1260 "a block above 1 MB is outside the consensus size limit") ? 0 : 1; 3228 "a block above 1 MB is outside the consensus size limit") ? 0 : 1;
3229
3230 // The other half of the cap. Everything above asks what validation
3231 // refuses; nothing asked what assembly produces, which is why a miner
3232 // that could overshoot the cap by an entire mempool went unnoticed.
3233 nFail += Check(MAX_BLOCK_SIZE_GEN < MAX_BLOCK_SIZE,
3234 "the assembly budget stays under the consensus cap") ? 0 : 1;
3235
3236 // The rule itself, at its edges. The sigop comparison has to match
3237 // CheckBlock(), which fails on `> MAX_BLOCK_SIGOPS` and so accepts a
3238 // block sitting exactly on the limit.
3239 nFail += Check(BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 100, 0),
3240 "a transaction that exactly fills the size budget fits") ? 0 : 1;
3241 nFail += Check(!BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 101, 0),
3242 "one byte past the size budget does not fit") ? 0 : 1;
3243 nFail += Check(BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS),
3244 "sigops exactly on the limit fit, as CheckBlock() allows") ? 0 : 1;
3245 nFail += Check(!BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS + 1),
3246 "one sigop past the limit does not fit") ? 0 : 1;
3247
3248 // A mempool several times the consensus cap, in ordinary-sized
3249 // transactions. The old miner tested its budget only on the outer
3250 // loop, so a single pass appended all of this.
3251 {
3252 CBlock block = MakeCoinbaseOnlyTemplate();
3253 vector<CTransaction> vMempool;
3254 for (int i = 0; i < 300; i++)
3255 vMempool.push_back(MakeSizedTx(10000, 1, i));
3256 SelectIntoTemplate(block, vMempool);
3257 block.hashMerkleRoot = block.BuildMerkleTree();
3258
3259 nFail += Check(block.vtx.size() > 1,
3260 "a full mempool still fills a template") ? 0 : 1;
3261 nFail += Check(::GetSerializeSize(block, SER_DISK) <= MAX_BLOCK_SIZE_GEN,
3262 "a template built from a 3 MB mempool respects the budget") ? 0 : 1;
3263 nFail += Check(block.CheckSizeLimits(),
3264 "that template is one CheckBlock() would accept on size") ? 0 : 1;
3265 }
3266
3267 // One transaction too big for the budget. Nothing on the path into the
3268 // mempool caps transaction size, so this is a transaction a peer can
3269 // simply send. It must be skipped and the rest still taken -- skipped,
3270 // not stopped on.
3271 {
3272 CBlock block = MakeCoinbaseOnlyTemplate();
3273 vector<CTransaction> vMempool;
3274 vMempool.push_back(MakeSizedTx(1500000, 1, 9999));
3275 for (int i = 0; i < 10; i++)
3276 vMempool.push_back(MakeSizedTx(1000, 1, i));
3277
3278 unsigned int nHugeSize = ::GetSerializeSize(vMempool[0], SER_NETWORK);
3279 nFail += Check(nHugeSize > MAX_BLOCK_SIZE && nHugeSize <= MAX_SIZE,
3280 "the outsized transaction is one the mempool would hold") ? 0 : 1;
3281
3282 SelectIntoTemplate(block, vMempool);
3283 block.hashMerkleRoot = block.BuildMerkleTree();
3284
3285 bool fTookHuge = false;
3286 for (int i = 0; i < (int)block.vtx.size(); i++)
3287 if (block.vtx[i].GetHash() == vMempool[0].GetHash())
3288 fTookHuge = true;
3289
3290 nFail += Check(!fTookHuge,
3291 "a transaction larger than the budget is left out") ? 0 : 1;
3292 nFail += Check(block.vtx.size() == 11,
3293 "the ten that do fit are taken anyway") ? 0 : 1;
3294 nFail += Check(block.CheckSizeLimits(),
3295 "one outsized transaction cannot invalidate a template") ? 0 : 1;
3296 }
3297
3298 // Transactions that are cheap in bytes and expensive to verify. Size
3299 // alone does not bound what a block costs, and CheckBlock() refuses on
3300 // the count regardless of how small the block is.
3301 {
3302 CBlock block = MakeCoinbaseOnlyTemplate();
3303 vector<CTransaction> vMempool;
3304 for (int i = 0; i < 100; i++)
3305 vMempool.push_back(MakeSizedTx(0, 1000, i));
3306 SelectIntoTemplate(block, vMempool);
3307 block.hashMerkleRoot = block.BuildMerkleTree();
3308
3309 nFail += Check(block.vtx.size() > 1,
3310 "sigop-heavy transactions are still selected") ? 0 : 1;
3311 nFail += Check(BlockSigOpCount(block) <= MAX_BLOCK_SIGOPS,
3312 "selection stops at the sigop cap CheckBlock() enforces") ? 0 : 1;
3313 nFail += Check(::GetSerializeSize(block, SER_DISK) < MAX_BLOCK_SIZE_GEN,
3314 "and stops on sigops while still well inside the size budget") ? 0 : 1;
3315 nFail += Check(block.CheckSizeLimits(),
3316 "the sigop-bounded template is within the size limit too") ? 0 : 1;
3317 }
1261 } 3318 }
1262 catch (const std::exception& e) 3319 catch (const std::exception& e)
1263 { 3320 {
@@ -1637,10 +3694,26 @@ int RunSelfTest(const std::string& name)
1637 return RunWalletHDSelfTest(); 3694 return RunWalletHDSelfTest();
1638 if (name == "wallet-format") 3695 if (name == "wallet-format")
1639 return RunWalletFormatSelfTest(); 3696 return RunWalletFormatSelfTest();
3697 if (name == "wallet-storage-sanity")
3698 return RunWalletStorageSanitySelfTest();
3699 if (name == "db-env-reopen")
3700 return RunDbEnvReopenSelfTest();
3701 if (name == "wallet-sqlite")
3702 return RunWalletSQLiteSelfTest();
3703 if (name == "wallet-sqlite-migration")
3704 return RunWalletSQLiteMigrationSelfTest();
1640 if (name == "wallet-crypto") 3705 if (name == "wallet-crypto")
1641 return RunWalletCryptoSelfTest(); 3706 return RunWalletCryptoSelfTest();
1642 if (name == "wallet-encrypt") 3707 if (name == "wallet-encrypt")
1643 return RunWalletEncryptSelfTest(); 3708 return RunWalletEncryptSelfTest();
3709 if (name == "wallet-sqlite-encrypt")
3710 return RunWalletSQLiteEncryptSelfTest();
3711 if (name == "wallet-backend-default")
3712 return RunWalletBackendDefaultSelfTest();
3713 if (name == "wallet-convert")
3714 return RunWalletConvertSelfTest();
3715 if (name == "wallet-portability")
3716 return RunWalletPortabilitySelfTest();
1644 if (name == "net-message") 3717 if (name == "net-message")
1645 return RunNetMessageSelfTest(); 3718 return RunNetMessageSelfTest();
1646 if (name == "consensus-limits") 3719 if (name == "consensus-limits")
@@ -1657,6 +3730,6 @@ int RunSelfTest(const std::string& name)
1657 return RunManagedTorSelfTest(); 3730 return RunManagedTorSelfTest();
1658 3731
1659 printf("Unknown self-test '%s'\n", name.c_str()); 3732 printf("Unknown self-test '%s'\n", name.c_str());
1660 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt, net-message, consensus-limits, pool-stratum, payout-ledger, parse-money, socks5-proxy, managed-tor\n"); 3733 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-sqlite-encrypt, wallet-backend-default, wallet-convert, wallet-portability, net-message, consensus-limits, pool-stratum, payout-ledger, parse-money, socks5-proxy, managed-tor\n");
1661 return 1; 3734 return 1;
1662} 3735}
diff --git a/src/selftest.h b/src/selftest.h
index d3699c6..0ffe232 100644
--- a/src/selftest.h
+++ b/src/selftest.h
@@ -7,5 +7,6 @@
7#include <string> 7#include <string>
8 8
9int RunSelfTest(const std::string& name); 9int RunSelfTest(const std::string& name);
10int RunSelfTestMutateWallet(const std::string& name);
10 11
11#endif 12#endif
diff --git a/src/util.cpp b/src/util.cpp
index 0cd0133..ab29178 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -7,6 +7,12 @@
7 7
8 8
9bool fDebug = false; 9bool fDebug = false;
10bool fDebugRelay = false;
11
12// In-process cap for debug.log, in bytes. Default matches the 1G trigger of
13// the shipped logrotate policy so a long-running node stays bounded even
14// without it; -maxdebugfilesize=N (MB) tunes it, 0 disables.
15int64 nMaxDebugFileSize = 1024 * 1024 * (int64)1024;
10 16
11// --------------------------------------------------------------------------- 17// ---------------------------------------------------------------------------
12// Categorized, burst-collapsing logging -- see the LogPrint doc comment in 18// Categorized, burst-collapsing logging -- see the LogPrint doc comment in
@@ -26,10 +32,22 @@ static bool LogCategoryDefaultOn(const char* category)
26 strcmp(category, "payout") == 0; 32 strcmp(category, "payout") == 0;
27} 33}
28 34
35// Categories so noisy they drown out everything else even under -debug:
36// per-message send traces, wallet relay rebroadcasts, raw CBlock dumps and
37// per-address discovery lines made up ~90% of a 13-hour production capture
38// (issue #35). They need /debugrelay, not merely /debug.
39static bool LogCategoryVerboseOnly(const char* category)
40{
41 return strcmp(category, "relay") == 0 ||
42 strcmp(category, "nostrdiscovery") == 0;
43}
44
29bool LogAcceptsCategory(const char* category) 45bool LogAcceptsCategory(const char* category)
30{ 46{
31 if (LogCategoryDefaultOn(category)) 47 if (LogCategoryDefaultOn(category))
32 return true; 48 return true;
49 if (LogCategoryVerboseOnly(category))
50 return fDebugRelay;
33 return fDebug; 51 return fDebug;
34} 52}
35 53
@@ -47,6 +65,61 @@ struct LogDedupState {
47static std::map<std::string, LogDedupState> g_logDedup; 65static std::map<std::string, LogDedupState> g_logDedup;
48static CCriticalSection cs_logDedup; 66static CCriticalSection cs_logDedup;
49 67
68// A pending "(N similar)" summary, lifted out of the table while the lock is
69// held and written after it is released.
70struct LogDedupFlush {
71 std::string category;
72 int suppressed;
73 int64 elapsed; // how long the window that counted them ran
74 std::string lastMsg;
75};
76
77static int64 g_logDedupLastSweep = 0;
78
79static void EmitDedupSummary(const LogDedupFlush& f)
80{
81 OutputDebugStringF("[%s] (%d similar in %llds, last: %s)\n",
82 f.category.c_str(), f.suppressed,
83 (long long)f.elapsed, f.lastMsg.c_str());
84}
85
86// A window is otherwise only closed out when its own call site fires again, so
87// a burst that simply stops -- 100 ledger entries voided in one pass and then
88// silence -- leaves its count sitting in the table and the summary is never
89// written at all. That is how 99 of 100 voids went unreported, with no "(N
90// similar)" line to say anything was missing (issue #3). Sweep every expired
91// window instead, so a finished burst is summarised as soon as anything at all
92// is logged.
93//
94// Expired entries are erased rather than reopened: the window is over, so there
95// is no state left worth keeping, and the next call from that site then takes
96// the "first time" path and prints directly -- exactly what the rollover branch
97// below would have done. It also stops the table growing without bound.
98//
99// Caller holds cs_logDedup; the summaries are written after it is released.
100static void SweepExpiredWindowsLocked(int64 now, std::vector<LogDedupFlush>& out)
101{
102 for (std::map<std::string, LogDedupState>::iterator it = g_logDedup.begin();
103 it != g_logDedup.end(); )
104 {
105 if (now - it->second.windowStart < DEDUP_WINDOW_SECS)
106 {
107 ++it;
108 continue;
109 }
110 if (it->second.suppressed > 0)
111 {
112 LogDedupFlush f;
113 f.category = it->first.substr(0, it->first.find('|'));
114 f.suppressed = it->second.suppressed;
115 f.elapsed = now - it->second.windowStart;
116 f.lastMsg = it->second.lastMsg;
117 out.push_back(f);
118 }
119 g_logDedup.erase(it++);
120 }
121}
122
50void LogPrintDedup(const char* category, const char* file, int line, const std::string& msgIn) 123void LogPrintDedup(const char* category, const char* file, int line, const std::string& msgIn)
51{ 124{
52 // Call sites follow the existing printf() convention of ending their 125 // Call sites follow the existing printf() convention of ending their
@@ -61,11 +134,19 @@ void LogPrintDedup(const char* category, const char* file, int line, const std::
61 134
62 int64 now = GetTime(); 135 int64 now = GetTime();
63 bool fEmitNow = false; 136 bool fEmitNow = false;
64 int suppressedToFlush = 0; 137 std::vector<LogDedupFlush> flushes;
65 std::string flushedMsg;
66 138
67 CRITICAL_BLOCK(cs_logDedup) 139 CRITICAL_BLOCK(cs_logDedup)
68 { 140 {
141 // At most once a second. The table holds one entry per call site that
142 // has fired in the last few seconds, so the walk is short, but it must
143 // not run once per line of a burst.
144 if (now != g_logDedupLastSweep)
145 {
146 g_logDedupLastSweep = now;
147 SweepExpiredWindowsLocked(now, flushes);
148 }
149
69 std::map<std::string, LogDedupState>::iterator it = g_logDedup.find(key); 150 std::map<std::string, LogDedupState>::iterator it = g_logDedup.find(key);
70 if (it == g_logDedup.end()) 151 if (it == g_logDedup.end())
71 { 152 {
@@ -76,10 +157,18 @@ void LogPrintDedup(const char* category, const char* file, int line, const std::
76 } 157 }
77 else if (now - it->second.windowStart >= DEDUP_WINDOW_SECS) 158 else if (now - it->second.windowStart >= DEDUP_WINDOW_SECS)
78 { 159 {
79 // Window rolled over: flush what was suppressed during it (if 160 // Window rolled over within the same second as the last sweep:
80 // anything), then start a new window with this call printed. 161 // flush what was suppressed during it (if anything), then start a
81 suppressedToFlush = it->second.suppressed; 162 // new window with this call printed.
82 flushedMsg = it->second.lastMsg; 163 if (it->second.suppressed > 0)
164 {
165 LogDedupFlush f;
166 f.category = category;
167 f.suppressed = it->second.suppressed;
168 f.elapsed = now - it->second.windowStart;
169 f.lastMsg = it->second.lastMsg;
170 flushes.push_back(f);
171 }
83 it->second.windowStart = now; 172 it->second.windowStart = now;
84 it->second.suppressed = 0; 173 it->second.suppressed = 0;
85 it->second.lastMsg = msg; 174 it->second.lastMsg = msg;
@@ -94,13 +183,23 @@ void LogPrintDedup(const char* category, const char* file, int line, const std::
94 } 183 }
95 } 184 }
96 185
97 if (suppressedToFlush > 0) 186 for (size_t i = 0; i < flushes.size(); i++)
98 OutputDebugStringF("[%s] (%d similar in %llds, last: %s)\n", 187 EmitDedupSummary(flushes[i]);
99 category, suppressedToFlush, (long long)DEDUP_WINDOW_SECS, flushedMsg.c_str());
100 if (fEmitNow) 188 if (fEmitNow)
101 OutputDebugStringF("[%s] %s\n", category, msg.c_str()); 189 OutputDebugStringF("[%s] %s\n", category, msg.c_str());
102} 190}
103 191
192// LogPrintAlways() -- see the doc comment in util.h. No window, no counting,
193// no table entry: every call writes its own line.
194void LogPrintNoCollapse(const char* category, const std::string& msgIn)
195{
196 std::string msg = msgIn;
197 while (!msg.empty() && (msg.back() == '\n' || msg.back() == '\r'))
198 msg.pop_back();
199
200 OutputDebugStringF("[%s] %s\n", category, msg.c_str());
201}
202
104 203
105 204
106 205
@@ -397,13 +496,37 @@ bool FileExists(const char* psz)
397#endif 496#endif
398} 497}
399 498
400int GetFilesize(FILE* file) 499#ifndef _WIN32
500// If off_t is 32 bits wide, ftello() truncates exactly like the int this
501// function used to return. Fail the build rather than ship the silent
502// version of the bug (32-bit hosts need -D_FILE_OFFSET_BITS=64).
503static_assert(sizeof(off_t) >= 8, "GetFilesize needs a 64-bit off_t");
504#endif
505
506// Returns the file's size in bytes, or -1 if it cannot be determined.
507//
508// This used to hold ftell()'s long in an int, so a file of 2 GiB or more came
509// back negative. Its only caller is the debug.log roll in util.h, which skips
510// the roll on a negative size -- i.e. rotation switched itself off for good at
511// exactly the size the roll exists to prevent, and any cap of 2048 MB or more
512// could never be exceeded at all. Both failures were silent.
513int64 GetFilesize(FILE* file)
401{ 514{
402 int nSavePos = ftell(file); 515#ifdef _WIN32
403 int nFilesize = -1; 516 __int64 nSavePos = _ftelli64(file);
404 if (fseek(file, 0, SEEK_END) == 0) 517 int64 nFilesize = -1;
405 nFilesize = ftell(file); 518 if (nSavePos >= 0 && _fseeki64(file, 0, SEEK_END) == 0)
406 fseek(file, nSavePos, SEEK_SET); 519 nFilesize = _ftelli64(file);
520 if (nSavePos >= 0)
521 _fseeki64(file, nSavePos, SEEK_SET);
522#else
523 off_t nSavePos = ftello(file);
524 int64 nFilesize = -1;
525 if (nSavePos >= 0 && fseeko(file, 0, SEEK_END) == 0)
526 nFilesize = ftello(file);
527 if (nSavePos >= 0)
528 fseeko(file, nSavePos, SEEK_SET);
529#endif
407 return nFilesize; 530 return nFilesize;
408} 531}
409 532
diff --git a/src/util.h b/src/util.h
index d72518b..dfe7511 100644
--- a/src/util.h
+++ b/src/util.h
@@ -87,6 +87,15 @@ inline T& REF(const T& val)
87 87
88extern bool fDebug; 88extern bool fDebug;
89 89
90// /debugrelay: the per-message firehose ("relay" and "nostrdiscovery"
91// categories) that even -debug keeps off. See LogAcceptsCategory().
92extern bool fDebugRelay;
93
94// debug.log is rolled to debug.log.old once it grows past this many bytes
95// (set from -maxdebugfilesize, in MB; 0 disables the check). See the roll
96// site in OutputDebugStringF() below.
97extern int64 nMaxDebugFileSize;
98
90// --------------------------------------------------------------------------- 99// ---------------------------------------------------------------------------
91// Categorized, burst-collapsing logging 100// Categorized, burst-collapsing logging
92// 101//
@@ -106,15 +115,29 @@ extern bool fDebug;
106// 2. Burst collapse: repeated LogPrint() calls from the *same source line* 115// 2. Burst collapse: repeated LogPrint() calls from the *same source line*
107// only emit one line per DEDUP_WINDOW_SECS; if more calls land inside 116// only emit one line per DEDUP_WINDOW_SECS; if more calls land inside
108// that window they're counted instead of reprinted, and the count is 117// that window they're counted instead of reprinted, and the count is
109// flushed as a single "(xN similar in Ws)" line the moment the window 118// flushed as a single "(xN similar in Ws)" line once the window expires
110// rolls over. A loop discovering 500 peers by address now produces a 119// -- whether or not that call site ever fires again. A loop discovering
111// small, bounded number of lines instead of 500 -- log growth is capped 120// 500 peers by address now produces a small, bounded number of lines
112// by elapsed time, not by how many similar events happen to fire. 121// instead of 500 -- log growth is capped by elapsed time, not by how
122// many similar events happen to fire.
123//
124// Use LogPrintAlways() instead for anything that reports an irreversible
125// change to money: an obligation voided, a payment sent, a payment that
126// failed. One line standing in for an unknown number of identical events is
127// a tidy report of peer chatter and a false report of a ledger. A reorg
128// voiding 100 entries hits one source line 100 times inside one window;
129// under LogPrint() that read as a single block losing a single 49.49 BTF
130// payment, while 4,950 BTF of obligations were actually destroyed, and the
131// discrepancy was only found by counting entries in pending_payouts.json
132// (issue #3). Bounded log growth is not worth that ambiguity here.
113// --------------------------------------------------------------------------- 133// ---------------------------------------------------------------------------
114bool LogAcceptsCategory(const char* category); 134bool LogAcceptsCategory(const char* category);
115void LogPrintDedup(const char* category, const char* file, int line, const std::string& msg); 135void LogPrintDedup(const char* category, const char* file, int line, const std::string& msg);
136void LogPrintNoCollapse(const char* category, const std::string& msg);
116#define LogPrint(category, ...) \ 137#define LogPrint(category, ...) \
117 do { if (LogAcceptsCategory(category)) LogPrintDedup((category), __FILE__, __LINE__, strprintf(__VA_ARGS__)); } while (0) 138 do { if (LogAcceptsCategory(category)) LogPrintDedup((category), __FILE__, __LINE__, strprintf(__VA_ARGS__)); } while (0)
139#define LogPrintAlways(category, ...) \
140 do { if (LogAcceptsCategory(category)) LogPrintNoCollapse((category), strprintf(__VA_ARGS__)); } while (0)
118 141
119// Every format string in this project goes through one of the four functions 142// Every format string in this project goes through one of the four functions
120// below -- `printf` itself is remapped to OutputDebugStringF just above, and 143// below -- `printf` itself is remapped to OutputDebugStringF just above, and
@@ -145,7 +168,7 @@ void ParseString(const string& str, char c, vector<string>& v);
145string FormatMoney(int64 n, bool fPlus=false); 168string FormatMoney(int64 n, bool fPlus=false);
146bool ParseMoney(const char* pszIn, int64& nRet); 169bool ParseMoney(const char* pszIn, int64& nRet);
147bool FileExists(const char* psz); 170bool FileExists(const char* psz);
148int GetFilesize(FILE* file); 171int64 GetFilesize(FILE* file);
149uint64 GetRand(uint64 nMax); 172uint64 GetRand(uint64 nMax);
150int64 GetTime(); 173int64 GetTime();
151int64 GetAdjustedTime(); 174int64 GetAdjustedTime();
@@ -199,7 +222,7 @@ protected:
199public: 222public:
200 char* pszFile; 223 char* pszFile;
201 int nLine; 224 int nLine;
202 explicit CCriticalSection() { InitializeCriticalSection(&cs); } 225 explicit CCriticalSection() : pszFile(NULL), nLine(0) { InitializeCriticalSection(&cs); }
203 ~CCriticalSection() { DeleteCriticalSection(&cs); } 226 ~CCriticalSection() { DeleteCriticalSection(&cs); }
204 void Enter() { EnterCriticalSection(&cs); } 227 void Enter() { EnterCriticalSection(&cs); }
205 void Leave() { LeaveCriticalSection(&cs); } 228 void Leave() { LeaveCriticalSection(&cs); }
@@ -390,7 +413,65 @@ inline int OutputDebugStringF(const char* pszFormat, ...)
390 else 413 else
391 strDebugFile = "debug.log"; 414 strDebugFile = "debug.log";
392 } 415 }
416 // Latched by a failed rename in the roll below, which cannot be
417 // retried usefully and must not be retried endlessly.
418 static bool fRollDisabled = false;
393 FILE* fileout = fopen(strDebugFile.c_str(), "a"); 419 FILE* fileout = fopen(strDebugFile.c_str(), "a");
420 if (fileout && nMaxDebugFileSize > 0 && !fRollDisabled)
421 {
422 // Roll an overgrown debug.log before adding to it. Nothing in the
423 // tree shrinks this file once the process is running (there is no
424 // ShrinkDebugFile, and even Bitcoin's only ran at startup), so a
425 // node that stays up for months grows the log without bound --
426 // production hit ~2 GiB. The file is opened and closed per line,
427 // so a rename is safe here: the next line simply reopens the
428 // path. One generation is kept as debug.log.old; logrotate
429 // (contrib/logrotate/) handles anything longer-term.
430 int64 nSize = GetFilesize(fileout);
431 if (nSize >= 0 && nSize > nMaxDebugFileSize)
432 {
433 fclose(fileout);
434 string strOldFile = strDebugFile + ".old";
435 remove(strOldFile.c_str()); // rename() won't clobber on Windows
436 bool fRolled = (rename(strDebugFile.c_str(), strOldFile.c_str()) == 0);
437 int nRenameErrno = errno; // before fopen() can overwrite it
438 fileout = fopen(strDebugFile.c_str(), "a");
439 if (fRolled)
440 {
441 if (fileout)
442 fprintf(fileout,
443 "debug.log grew past %lld MB; previous contents moved to debug.log.old (-maxdebugfilesize)\n",
444 (long long)(nMaxDebugFileSize / 1024 / 1024));
445 }
446 else if (nRenameErrno == ENOENT)
447 {
448 // The file went away between the size check and the
449 // rename, so an external rotator (logrotate) got there
450 // first. Nothing left to roll and nothing wrong: the
451 // fopen above has already recreated it. Latching on this
452 // would let a benign race with logrotate switch the cap
453 // off for the rest of the run.
454 }
455 else
456 {
457 // The file is still over the cap, so this branch would be
458 // reached again on the next line and every line after it.
459 // Unlatched, the complaint about failing to roll becomes
460 // the runaway growth the roll exists to prevent. Say it
461 // once and stop trying -- on stderr too, because whatever
462 // blocked the rename (read-only mount, permissions, EXDEV
463 // across a bind mount) may equally block the log write.
464 fRollDisabled = true;
465 fprintf(stderr,
466 "cannot roll %s (rename to .old failed: %s); -maxdebugfilesize disabled for this run\n",
467 strDebugFile.c_str(), strerror(nRenameErrno));
468 if (fileout)
469 fprintf(fileout,
470 "cannot roll debug.log (rename to .old failed: %s); -maxdebugfilesize disabled for this run\n",
471 strerror(nRenameErrno));
472 }
473 }
474 }
394 if (fileout) 475 if (fileout)
395 { 476 {
396 va_list arg_ptr; 477 va_list arg_ptr;
diff --git a/src/wallet_sqlite.cpp b/src/wallet_sqlite.cpp
new file mode 100644
index 0000000..f4b4b37
--- /dev/null
+++ b/src/wallet_sqlite.cpp
@@ -0,0 +1,374 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3
4#include "headers.h"
5#include "wallet_sqlite.h"
6
7static void SetSQLiteError(sqlite3* pdb, const string& strContext,
8 string& strError)
9{
10 if (pdb)
11 strError = strContext + ": " + sqlite3_errmsg(pdb);
12 else
13 strError = strContext;
14}
15
16CWalletDBSQLite::CWalletDBSQLite() : pdb(NULL)
17{
18}
19
20CWalletDBSQLite::~CWalletDBSQLite()
21{
22 Close();
23}
24
25bool CWalletDBSQLite::Open(const string& strPath, string& strError)
26{
27 Close();
28
29 int ret = sqlite3_open_v2(strPath.c_str(), &pdb,
30 SQLITE_OPEN_READWRITE |
31 SQLITE_OPEN_CREATE |
32 SQLITE_OPEN_FULLMUTEX,
33 NULL);
34 if (ret != SQLITE_OK)
35 {
36 SetSQLiteError(pdb, "cannot open SQLite wallet", strError);
37 Close();
38 return false;
39 }
40
41 sqlite3_busy_timeout(pdb, 5000);
42
43 if (!Exec("PRAGMA journal_mode=WAL;", strError))
44 {
45 Close();
46 return false;
47 }
48 if (!Exec("PRAGMA synchronous=FULL;", strError))
49 {
50 Close();
51 return false;
52 }
53 if (!InitSchema(strError))
54 {
55 Close();
56 return false;
57 }
58 return true;
59}
60
61bool CWalletDBSQLite::OpenReadOnly(const string& strPath, string& strError)
62{
63 Close();
64
65 int ret = sqlite3_open_v2(strPath.c_str(), &pdb,
66 SQLITE_OPEN_READONLY |
67 SQLITE_OPEN_FULLMUTEX,
68 NULL);
69 if (ret != SQLITE_OK)
70 {
71 SetSQLiteError(pdb, "cannot open SQLite wallet read-only", strError);
72 Close();
73 return false;
74 }
75
76 sqlite3_busy_timeout(pdb, 5000);
77 return true;
78}
79
80void CWalletDBSQLite::Close()
81{
82 if (pdb)
83 {
84 sqlite3_close(pdb);
85 pdb = NULL;
86 }
87}
88
89bool CWalletDBSQLite::BeginTransaction(string& strError)
90{
91 if (!pdb)
92 {
93 strError = "SQLite wallet is not open";
94 return false;
95 }
96 return Exec("BEGIN IMMEDIATE;", strError);
97}
98
99bool CWalletDBSQLite::CommitTransaction(string& strError)
100{
101 if (!pdb)
102 {
103 strError = "SQLite wallet is not open";
104 return false;
105 }
106 return Exec("COMMIT;", strError);
107}
108
109bool CWalletDBSQLite::RollbackTransaction(string& strError)
110{
111 if (!pdb)
112 {
113 strError = "SQLite wallet is not open";
114 return false;
115 }
116 return Exec("ROLLBACK;", strError);
117}
118
119bool CWalletDBSQLite::Checkpoint(string& strError)
120{
121 if (!pdb)
122 {
123 strError = "SQLite wallet is not open";
124 return false;
125 }
126 return Exec("PRAGMA wal_checkpoint(TRUNCATE);", strError);
127}
128
129bool CWalletDBSQLite::Exec(const char* pszSql, string& strError)
130{
131 char* pszErr = NULL;
132 int ret = sqlite3_exec(pdb, pszSql, NULL, NULL, &pszErr);
133 if (ret != SQLITE_OK)
134 {
135 if (pszErr)
136 {
137 strError = pszErr;
138 sqlite3_free(pszErr);
139 }
140 else
141 SetSQLiteError(pdb, "SQLite exec failed", strError);
142 return false;
143 }
144 return true;
145}
146
147bool CWalletDBSQLite::InitSchema(string& strError)
148{
149 return Exec("CREATE TABLE IF NOT EXISTS wallet_records ("
150 "key BLOB PRIMARY KEY NOT NULL,"
151 "value BLOB NOT NULL"
152 ");",
153 strError) &&
154 Exec("PRAGMA user_version=1;", strError);
155}
156
157bool CWalletDBSQLite::WriteRecord(const vector<unsigned char>& vchKey,
158 const vector<unsigned char>& vchValue,
159 string& strError,
160 bool fOverwrite)
161{
162 if (!pdb)
163 {
164 strError = "SQLite wallet is not open";
165 return false;
166 }
167 if (vchKey.empty())
168 {
169 strError = "SQLite wallet record key is empty";
170 return false;
171 }
172
173 sqlite3_stmt* stmt = NULL;
174 const char* pszSql = fOverwrite
175 ? "INSERT OR REPLACE INTO wallet_records(key, value) VALUES(?, ?);"
176 : "INSERT INTO wallet_records(key, value) VALUES(?, ?);";
177 int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL);
178 if (ret != SQLITE_OK)
179 {
180 SetSQLiteError(pdb, "cannot prepare SQLite wallet write", strError);
181 return false;
182 }
183
184 ret = sqlite3_bind_blob(stmt, 1, &vchKey[0], (int)vchKey.size(),
185 SQLITE_TRANSIENT);
186 if (ret == SQLITE_OK)
187 ret = sqlite3_bind_blob(stmt, 2,
188 vchValue.empty() ? NULL : &vchValue[0],
189 (int)vchValue.size(),
190 SQLITE_TRANSIENT);
191 if (ret == SQLITE_OK)
192 ret = sqlite3_step(stmt);
193
194 bool fOk = (ret == SQLITE_DONE);
195 if (!fOk)
196 SetSQLiteError(pdb, "cannot write SQLite wallet record", strError);
197 sqlite3_finalize(stmt);
198 return fOk;
199}
200
201bool CWalletDBSQLite::ReadRecord(const vector<unsigned char>& vchKey,
202 vector<unsigned char>& vchValueRet,
203 string& strError)
204{
205 vchValueRet.clear();
206 if (!pdb)
207 {
208 strError = "SQLite wallet is not open";
209 return false;
210 }
211 if (vchKey.empty())
212 {
213 strError = "SQLite wallet record key is empty";
214 return false;
215 }
216
217 sqlite3_stmt* stmt = NULL;
218 const char* pszSql = "SELECT value FROM wallet_records WHERE key = ?;";
219 int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL);
220 if (ret != SQLITE_OK)
221 {
222 SetSQLiteError(pdb, "cannot prepare SQLite wallet read", strError);
223 return false;
224 }
225
226 ret = sqlite3_bind_blob(stmt, 1, &vchKey[0], (int)vchKey.size(),
227 SQLITE_TRANSIENT);
228 if (ret != SQLITE_OK)
229 {
230 SetSQLiteError(pdb, "cannot bind SQLite wallet key", strError);
231 sqlite3_finalize(stmt);
232 return false;
233 }
234
235 ret = sqlite3_step(stmt);
236 if (ret == SQLITE_ROW)
237 {
238 const unsigned char* pch =
239 (const unsigned char*)sqlite3_column_blob(stmt, 0);
240 int nBytes = sqlite3_column_bytes(stmt, 0);
241 if (nBytes > 0 && pch)
242 vchValueRet.assign(pch, pch + nBytes);
243 sqlite3_finalize(stmt);
244 return true;
245 }
246
247 if (ret == SQLITE_DONE)
248 strError = "SQLite wallet record not found";
249 else
250 SetSQLiteError(pdb, "cannot read SQLite wallet record", strError);
251 sqlite3_finalize(stmt);
252 return false;
253}
254
255bool CWalletDBSQLite::EraseRecord(const vector<unsigned char>& vchKey,
256 string& strError)
257{
258 if (!pdb)
259 {
260 strError = "SQLite wallet is not open";
261 return false;
262 }
263 if (vchKey.empty())
264 {
265 strError = "SQLite wallet record key is empty";
266 return false;
267 }
268
269 sqlite3_stmt* stmt = NULL;
270 const char* pszSql = "DELETE FROM wallet_records WHERE key = ?;";
271 int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL);
272 if (ret != SQLITE_OK)
273 {
274 SetSQLiteError(pdb, "cannot prepare SQLite wallet erase", strError);
275 return false;
276 }
277
278 ret = sqlite3_bind_blob(stmt, 1, &vchKey[0], (int)vchKey.size(),
279 SQLITE_TRANSIENT);
280 if (ret == SQLITE_OK)
281 ret = sqlite3_step(stmt);
282
283 bool fOk = (ret == SQLITE_DONE);
284 if (!fOk)
285 SetSQLiteError(pdb, "cannot erase SQLite wallet record", strError);
286 sqlite3_finalize(stmt);
287 return fOk;
288}
289
290bool CWalletDBSQLite::ScanRecords(CWalletRecordVisitor& visitor,
291 string& strError)
292{
293 if (!pdb)
294 {
295 strError = "SQLite wallet is not open";
296 return false;
297 }
298
299 sqlite3_stmt* stmt = NULL;
300 const char* pszSql = "SELECT key, value FROM wallet_records ORDER BY rowid;";
301 int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL);
302 if (ret != SQLITE_OK)
303 {
304 SetSQLiteError(pdb, "cannot prepare SQLite wallet scan", strError);
305 return false;
306 }
307
308 while ((ret = sqlite3_step(stmt)) == SQLITE_ROW)
309 {
310 const unsigned char* pchKey =
311 (const unsigned char*)sqlite3_column_blob(stmt, 0);
312 int nKeyBytes = sqlite3_column_bytes(stmt, 0);
313 const unsigned char* pchValue =
314 (const unsigned char*)sqlite3_column_blob(stmt, 1);
315 int nValueBytes = sqlite3_column_bytes(stmt, 1);
316
317 vector<unsigned char> vchKey;
318 vector<unsigned char> vchValue;
319 if (nKeyBytes > 0 && pchKey)
320 vchKey.assign(pchKey, pchKey + nKeyBytes);
321 if (nValueBytes > 0 && pchValue)
322 vchValue.assign(pchValue, pchValue + nValueBytes);
323
324 CDataStream ssKey(vchKey, SER_DISK);
325 CDataStream ssValue(vchValue, SER_DISK);
326 if (!visitor.VisitWalletRecord(ssKey, ssValue, strError))
327 {
328 sqlite3_finalize(stmt);
329 return false;
330 }
331 }
332
333 if (ret != SQLITE_DONE)
334 {
335 SetSQLiteError(pdb, "cannot scan SQLite wallet records", strError);
336 sqlite3_finalize(stmt);
337 return false;
338 }
339
340 sqlite3_finalize(stmt);
341 return true;
342}
343
344bool CWalletDBSQLite::CountRecords(int& nRecordsRet, string& strError)
345{
346 nRecordsRet = 0;
347 if (!pdb)
348 {
349 strError = "SQLite wallet is not open";
350 return false;
351 }
352
353 sqlite3_stmt* stmt = NULL;
354 int ret = sqlite3_prepare_v2(pdb,
355 "SELECT COUNT(*) FROM wallet_records;",
356 -1, &stmt, NULL);
357 if (ret != SQLITE_OK)
358 {
359 SetSQLiteError(pdb, "cannot prepare SQLite wallet count", strError);
360 return false;
361 }
362
363 ret = sqlite3_step(stmt);
364 if (ret != SQLITE_ROW)
365 {
366 SetSQLiteError(pdb, "cannot count SQLite wallet records", strError);
367 sqlite3_finalize(stmt);
368 return false;
369 }
370
371 nRecordsRet = sqlite3_column_int(stmt, 0);
372 sqlite3_finalize(stmt);
373 return true;
374}
diff --git a/src/wallet_sqlite.h b/src/wallet_sqlite.h
new file mode 100644
index 0000000..d27e13a
--- /dev/null
+++ b/src/wallet_sqlite.h
@@ -0,0 +1,120 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3
4#ifndef BITFLASH_WALLET_SQLITE_H
5#define BITFLASH_WALLET_SQLITE_H
6
7#include <sqlite3.h>
8
9class CWalletRecordVisitor;
10
11class CWalletDBSQLite
12{
13public:
14 CWalletDBSQLite();
15 ~CWalletDBSQLite();
16
17 bool Open(const string& strPath, string& strError);
18 bool OpenReadOnly(const string& strPath, string& strError);
19 void Close();
20
21 bool BeginTransaction(string& strError);
22 bool CommitTransaction(string& strError);
23 bool RollbackTransaction(string& strError);
24 bool Checkpoint(string& strError);
25
26 bool WriteRecord(const vector<unsigned char>& vchKey,
27 const vector<unsigned char>& vchValue,
28 string& strError,
29 bool fOverwrite=true);
30 bool ReadRecord(const vector<unsigned char>& vchKey,
31 vector<unsigned char>& vchValueRet,
32 string& strError);
33 bool EraseRecord(const vector<unsigned char>& vchKey,
34 string& strError);
35 bool ScanRecords(CWalletRecordVisitor& visitor, string& strError);
36 bool CountRecords(int& nRecordsRet, string& strError);
37
38 template<typename K, typename T>
39 bool WriteTypedRecord(const K& key, const T& value, string& strError,
40 bool fOverwrite=true)
41 {
42 CDataStream ssKey(SER_DISK);
43 ssKey.reserve(1000);
44 ssKey << key;
45
46 CDataStream ssValue(SER_DISK);
47 ssValue.reserve(10000);
48 ssValue << value;
49
50 vector<unsigned char> vchKey(ssKey.begin(), ssKey.end());
51 vector<unsigned char> vchValue(ssValue.begin(), ssValue.end());
52 bool fOk = WriteRecord(vchKey, vchValue, strError, fOverwrite);
53
54 if (!vchKey.empty())
55 memset(&vchKey[0], 0, vchKey.size());
56 if (!vchValue.empty())
57 memset(&vchValue[0], 0, vchValue.size());
58 if (!ssKey.empty())
59 memset(&ssKey[0], 0, ssKey.size());
60 if (!ssValue.empty())
61 memset(&ssValue[0], 0, ssValue.size());
62 return fOk;
63 }
64
65 template<typename K, typename T>
66 bool ReadTypedRecord(const K& key, T& valueRet, string& strError)
67 {
68 CDataStream ssKey(SER_DISK);
69 ssKey.reserve(1000);
70 ssKey << key;
71 vector<unsigned char> vchKey(ssKey.begin(), ssKey.end());
72
73 vector<unsigned char> vchValue;
74 bool fOk = ReadRecord(vchKey, vchValue, strError);
75 if (!vchKey.empty())
76 memset(&vchKey[0], 0, vchKey.size());
77 if (!ssKey.empty())
78 memset(&ssKey[0], 0, ssKey.size());
79 if (!fOk)
80 return false;
81
82 CDataStream ssValue(vchValue, SER_DISK);
83 ssValue >> valueRet;
84 bool fReadOk = !ssValue.fail();
85 if (!vchValue.empty())
86 memset(&vchValue[0], 0, vchValue.size());
87 if (!ssValue.empty())
88 memset(&ssValue[0], 0, ssValue.size());
89 if (!fReadOk)
90 strError = "SQLite wallet record could not be deserialized";
91 return fReadOk;
92 }
93
94 template<typename K>
95 bool EraseTypedRecord(const K& key, string& strError)
96 {
97 CDataStream ssKey(SER_DISK);
98 ssKey.reserve(1000);
99 ssKey << key;
100 vector<unsigned char> vchKey(ssKey.begin(), ssKey.end());
101
102 bool fOk = EraseRecord(vchKey, strError);
103 if (!vchKey.empty())
104 memset(&vchKey[0], 0, vchKey.size());
105 if (!ssKey.empty())
106 memset(&ssKey[0], 0, ssKey.size());
107 return fOk;
108 }
109
110private:
111 sqlite3* pdb;
112
113 bool Exec(const char* pszSql, string& strError);
114 bool InitSchema(string& strError);
115
116 CWalletDBSQLite(const CWalletDBSQLite&);
117 void operator=(const CWalletDBSQLite&);
118};
119
120#endif
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index e8ac4bf..f59fc0c 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -12,6 +12,7 @@
12#include "headers_core.h" 12#include "headers_core.h"
13#include "bip32.h" 13#include "bip32.h"
14#include "walletcmd.h" 14#include "walletcmd.h"
15#include "wallet_sqlite.h"
15 16
16#undef printf 17#undef printf
17 18
@@ -400,7 +401,12 @@ int CmdRecoveryAudit()
400 int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; 401 int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit;
401 402
402 printf("Wallet recovery audit\n"); 403 printf("Wallet recovery audit\n");
403 printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed"); 404 if (audit.fHaveSeed)
405 printf(" recovery phrase: present\n");
406 else if (audit.fSeedEncryptedLocked)
407 printf(" recovery phrase: encrypted, unlock wallet to audit\n");
408 else
409 printf(" recovery phrase: not installed\n");
404 if (audit.fHaveSeed) 410 if (audit.fHaveSeed)
405 { 411 {
406 printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); 412 printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str());
@@ -412,15 +418,24 @@ int CmdRecoveryAudit()
412 if (!audit.fDeriveComplete) 418 if (!audit.fDeriveComplete)
413 printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); 419 printf(" derivation warning: %s\n", audit.strDeriveError.c_str());
414 printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); 420 printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str());
421 if (audit.fSeedEncryptedLocked)
422 {
423 printf(" recovery coverage: unavailable while wallet is locked\n");
424 printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str());
425 printf("\n");
426 printf("Unlock the wallet with /walletpassphrase or /walletpassphrase=@FILE to audit phrase coverage.\n");
427 fflush(stdout);
428 return 2;
429 }
415 printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", 430 printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n",
416 FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); 431 FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx);
417 printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n", 432 printf(" not phrase-backed balance: %s BTF (%d transaction(s))\n",
418 FormatMoney(audit.nLegacyCredit).c_str(), audit.nLegacyTx); 433 FormatMoney(audit.nLegacyCredit).c_str(), audit.nLegacyTx);
419 printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); 434 printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str());
420 printf(" phrase-backed immature: %s BTF (%d transaction(s))\n", 435 printf(" phrase-backed immature: %s BTF (%d transaction(s))\n",
421 FormatMoney(audit.nRecoverableImmatureCredit).c_str(), 436 FormatMoney(audit.nRecoverableImmatureCredit).c_str(),
422 audit.nRecoverableImmatureTx); 437 audit.nRecoverableImmatureTx);
423 printf(" wallet.dat-only immature: %s BTF (%d transaction(s))\n", 438 printf(" not phrase-backed immature: %s BTF (%d transaction(s))\n",
424 FormatMoney(audit.nLegacyImmatureCredit).c_str(), 439 FormatMoney(audit.nLegacyImmatureCredit).c_str(),
425 audit.nLegacyImmatureTx); 440 audit.nLegacyImmatureTx);
426 441
@@ -442,7 +457,7 @@ int CmdRecoveryAudit()
442 { 457 {
443 printf("\n"); 458 printf("\n");
444 printf("Warning: some coins are on keys the phrase does not reproduce.\n"); 459 printf("Warning: some coins are on keys the phrase does not reproduce.\n");
445 printf("Keep wallet.dat backups until that balance has been moved to a phrase-backed address.\n"); 460 printf("Keep a file backup of this wallet until that balance has been moved to a phrase-backed address.\n");
446 fflush(stdout); 461 fflush(stdout);
447 return 2; 462 return 2;
448 } 463 }
@@ -456,10 +471,952 @@ int CmdRecoveryAudit()
456 return 0; 471 return 0;
457} 472}
458 473
474struct WalletStorageAuditCounts
475{
476 unsigned int nTotal = 0;
477 unsigned int nMalformed = 0;
478 unsigned int nUnknown = 0;
479 unsigned int nVersion = 0;
480 unsigned int nNames = 0;
481 unsigned int nTransactions = 0;
482 unsigned int nPlainKeys = 0;
483 unsigned int nEncryptedKeys = 0;
484 unsigned int nMasterKeys = 0;
485 unsigned int nDefaultKey = 0;
486 unsigned int nPlainHDMaster = 0;
487 unsigned int nPlainHDChainCode = 0;
488 unsigned int nCryptedHDMaster = 0;
489 unsigned int nCryptedHDChainCode = 0;
490 unsigned int nWalletMinVersion = 0;
491 unsigned int nPool = 0;
492 unsigned int nSettings = 0;
493 unsigned int nHDNext = 0;
494 unsigned int nHDSchema = 0;
495 unsigned int nHDCoinType = 0;
496 unsigned int nHDReceiveNext = 0;
497 unsigned int nHDChangeNext = 0;
498};
499
500static std::string HDSeedStorageState(unsigned int nMaster, unsigned int nChain)
501{
502 if (nMaster == 0 && nChain == 0)
503 return "none";
504 if (nMaster > 0 && nChain > 0)
505 return "complete";
506 return "incomplete";
507}
508
509static void CountWalletStorageType(const std::string& strType,
510 WalletStorageAuditCounts& c)
511{
512 if (strType == "version")
513 c.nVersion++;
514 else if (strType == "name")
515 c.nNames++;
516 else if (strType == "tx")
517 c.nTransactions++;
518 else if (strType == "key")
519 c.nPlainKeys++;
520 else if (strType == "ckey")
521 c.nEncryptedKeys++;
522 else if (strType == "mkey")
523 c.nMasterKeys++;
524 else if (strType == "defaultkey")
525 c.nDefaultKey++;
526 else if (strType == "hdmaster")
527 c.nPlainHDMaster++;
528 else if (strType == "hdchaincode")
529 c.nPlainHDChainCode++;
530 else if (strType == "cryptedhdmaster")
531 c.nCryptedHDMaster++;
532 else if (strType == "cryptedhdchaincode")
533 c.nCryptedHDChainCode++;
534 else if (strType == "walletminversion")
535 c.nWalletMinVersion++;
536 else if (strType == "pool")
537 c.nPool++;
538 else if (strType == "setting")
539 c.nSettings++;
540 else if (strType == "hdnext")
541 c.nHDNext++;
542 else if (strType == "hdschema")
543 c.nHDSchema++;
544 else if (strType == "hdcointype")
545 c.nHDCoinType++;
546 else if (strType == "hdreceivenext")
547 c.nHDReceiveNext++;
548 else if (strType == "hdchangenext")
549 c.nHDChangeNext++;
550 else
551 c.nUnknown++;
552}
553
554static bool ReadWalletStorageCounts(WalletStorageAuditCounts& counts,
555 std::string& strError)
556{
557 class CWalletStorageCountVisitor : public CWalletRecordVisitor
558 {
559 public:
560 WalletStorageAuditCounts& counts;
561
562 explicit CWalletStorageCountVisitor(WalletStorageAuditCounts& countsIn)
563 : counts(countsIn) { }
564
565 bool VisitWalletRecord(const CDataStream& ssKeyIn,
566 const CDataStream& ssValue,
567 std::string& strErrorRet)
568 {
569 (void)ssValue;
570 (void)strErrorRet;
571 counts.nTotal++;
572 try
573 {
574 CDataStream ssKey = ssKeyIn;
575 std::string strType;
576 ssKey >> strType;
577 if (ssKey.fail())
578 {
579 counts.nMalformed++;
580 return true;
581 }
582 CountWalletStorageType(strType, counts);
583 }
584 catch (...)
585 {
586 counts.nMalformed++;
587 }
588 return true;
589 }
590 };
591
592 CWalletStorageCountVisitor visitor(counts);
593 return ScanWalletRecords(visitor, strError);
594}
595
596static std::string WalletStorageAuditJson(const WalletStorageAuditCounts& counts)
597{
598 std::string strJson;
599 strJson += "{\n";
600 strJson += " \"format\": \"bitflash-wallet-storage-audit-v1\",\n";
601 strJson += strprintf(" \"records_total\": %u,\n", counts.nTotal);
602 strJson += strprintf(" \"malformed_records\": %u,\n", counts.nMalformed);
603 strJson += strprintf(" \"unknown_records\": %u,\n", counts.nUnknown);
604 strJson += strprintf(" \"plain_private_keys\": %u,\n", counts.nPlainKeys);
605 strJson += strprintf(" \"encrypted_private_keys\": %u,\n", counts.nEncryptedKeys);
606 strJson += strprintf(" \"encryption_master_keys\": %u,\n", counts.nMasterKeys);
607 strJson += strprintf(" \"default_public_key\": \"%s\",\n",
608 counts.nDefaultKey ? "present" : "none");
609 strJson += strprintf(" \"plain_hd_seed\": \"%s\",\n",
610 HDSeedStorageState(counts.nPlainHDMaster,
611 counts.nPlainHDChainCode).c_str());
612 strJson += strprintf(" \"encrypted_hd_seed\": \"%s\",\n",
613 HDSeedStorageState(counts.nCryptedHDMaster,
614 counts.nCryptedHDChainCode).c_str());
615 strJson += strprintf(" \"wallet_minimum_version\": \"%s\",\n",
616 counts.nWalletMinVersion ? "present" : "none");
617 strJson += strprintf(" \"keypool_entries\": %u,\n", counts.nPool);
618 strJson += strprintf(" \"wallet_transactions\": %u,\n", counts.nTransactions);
619 strJson += strprintf(" \"address_book_labels\": %u,\n", counts.nNames);
620 strJson += strprintf(" \"settings\": %u,\n", counts.nSettings);
621 strJson += strprintf(" \"hd_metadata_records\": %u,\n",
622 counts.nHDNext + counts.nHDSchema + counts.nHDCoinType +
623 counts.nHDReceiveNext + counts.nHDChangeNext);
624 strJson += strprintf(" \"database_version_records\": %u\n", counts.nVersion);
625 strJson += "}\n";
626 return strJson;
627}
628
629static bool WriteAuditTextFile(const std::string& strPath,
630 const std::string& strText,
631 std::string& strError)
632{
633 FILE* pf = fopen(strPath.c_str(), "wb");
634 if (!pf)
635 {
636 strError = "cannot open output file";
637 return false;
638 }
639 size_t nWritten = fwrite(strText.data(), 1, strText.size(), pf);
640 bool fCloseOk = fclose(pf) == 0;
641 if (nWritten != strText.size() || !fCloseOk)
642 {
643 strError = "could not write the complete output file";
644 return false;
645 }
646 return true;
647}
648
649static void AddWalletStorageFailure(std::vector<std::string>& vFailures,
650 const std::string& strFailure)
651{
652 vFailures.push_back(strFailure);
653}
654
655static void BuildWalletStorageSanityFailures(const WalletStorageAuditCounts& counts,
656 std::vector<std::string>& vFailures)
657{
658 bool fPlainHD = counts.nPlainHDMaster > 0 || counts.nPlainHDChainCode > 0;
659 bool fCryptedHD = counts.nCryptedHDMaster > 0 || counts.nCryptedHDChainCode > 0;
660 bool fEncryptedRecords = counts.nEncryptedKeys > 0 ||
661 counts.nMasterKeys > 0 ||
662 fCryptedHD;
663
664 if (counts.nMalformed > 0)
665 AddWalletStorageFailure(vFailures, "wallet.dat contains malformed records");
666 if (counts.nUnknown > 0)
667 AddWalletStorageFailure(vFailures, "wallet.dat contains unknown records");
668 if ((counts.nPlainHDMaster > 0) != (counts.nPlainHDChainCode > 0))
669 AddWalletStorageFailure(vFailures, "plain HD seed is incomplete");
670 if ((counts.nCryptedHDMaster > 0) != (counts.nCryptedHDChainCode > 0))
671 AddWalletStorageFailure(vFailures, "encrypted HD seed is incomplete");
672 if (fPlainHD && fCryptedHD)
673 AddWalletStorageFailure(vFailures, "wallet.dat contains both plain and encrypted HD seed records");
674 if (counts.nDefaultKey > 1)
675 AddWalletStorageFailure(vFailures, "wallet.dat contains more than one default public key record");
676 if (counts.nWalletMinVersion > 1)
677 AddWalletStorageFailure(vFailures, "wallet.dat contains more than one minimum-version record");
678
679 if (fEncryptedRecords)
680 {
681 if (counts.nPlainKeys > 0)
682 AddWalletStorageFailure(vFailures, "encrypted wallet still contains plain private key records");
683 if (fPlainHD)
684 AddWalletStorageFailure(vFailures, "encrypted wallet still contains plain HD seed records");
685 if (counts.nMasterKeys == 0)
686 AddWalletStorageFailure(vFailures, "encrypted wallet has no encryption master key record");
687 if (counts.nWalletMinVersion == 0)
688 AddWalletStorageFailure(vFailures, "encrypted wallet has no minimum-version record");
689 }
690}
691
692int CmdWalletStorageCheck()
693{
694 AttachTerminal();
695
696 WalletStorageAuditCounts counts;
697 std::string strError;
698 if (!ReadWalletStorageCounts(counts, strError))
699 {
700 fprintf(stderr, "Cannot check wallet storage: %s\n", strError.c_str());
701 return 1;
702 }
703
704 std::vector<std::string> vFailures;
705 BuildWalletStorageSanityFailures(counts, vFailures);
706
707 printf("Wallet storage sanity check\n");
708 printf(" records total: %u\n", counts.nTotal);
709 printf(" encrypted records: %s\n",
710 (counts.nEncryptedKeys || counts.nMasterKeys ||
711 counts.nCryptedHDMaster || counts.nCryptedHDChainCode) ? "yes" : "no");
712 printf(" plain private keys: %u\n", counts.nPlainKeys);
713 printf(" encrypted private keys: %u\n", counts.nEncryptedKeys);
714 printf(" plain HD seed: %s\n",
715 HDSeedStorageState(counts.nPlainHDMaster,
716 counts.nPlainHDChainCode).c_str());
717 printf(" encrypted HD seed: %s\n",
718 HDSeedStorageState(counts.nCryptedHDMaster,
719 counts.nCryptedHDChainCode).c_str());
720 if (vFailures.empty())
721 {
722 printf(" storage sanity: ok\n");
723 fflush(stdout);
724 return 0;
725 }
726
727 printf(" storage sanity: failed\n");
728 for (std::vector<std::string>::const_iterator it = vFailures.begin();
729 it != vFailures.end(); ++it)
730 printf(" failure: %s\n", it->c_str());
731 fflush(stdout);
732 return 2;
733}
734
735int CmdWalletStorageAudit(const std::string& strJsonOut)
736{
737 AttachTerminal();
738
739 WalletStorageAuditCounts counts;
740 std::string strError;
741 if (!ReadWalletStorageCounts(counts, strError))
742 {
743 fprintf(stderr, "Cannot audit wallet storage: %s\n", strError.c_str());
744 return 1;
745 }
746
747 if (!strJsonOut.empty())
748 {
749 std::string strWriteError;
750 if (!WriteAuditTextFile(strJsonOut,
751 WalletStorageAuditJson(counts),
752 strWriteError))
753 {
754 fprintf(stderr, "Cannot write wallet storage audit JSON: %s\n",
755 strWriteError.c_str());
756 return 1;
757 }
758 printf("Wallet storage audit written to %s\n", strJsonOut.c_str());
759 fflush(stdout);
760 return counts.nMalformed > 0 ? 2 : 0;
761 }
762
763 printf("Wallet storage audit\n");
764 printf(" records total: %u\n", counts.nTotal);
765 printf(" malformed records: %u\n", counts.nMalformed);
766 printf(" unknown records: %u\n", counts.nUnknown);
767 printf(" plain private keys: %u\n", counts.nPlainKeys);
768 printf(" encrypted private keys: %u\n", counts.nEncryptedKeys);
769 printf(" encryption master keys: %u\n", counts.nMasterKeys);
770 printf(" default public key: %s\n", counts.nDefaultKey ? "present" : "none");
771 printf(" plain HD seed: %s\n",
772 HDSeedStorageState(counts.nPlainHDMaster,
773 counts.nPlainHDChainCode).c_str());
774 printf(" encrypted HD seed: %s\n",
775 HDSeedStorageState(counts.nCryptedHDMaster,
776 counts.nCryptedHDChainCode).c_str());
777 printf(" wallet minimum version: %s\n",
778 counts.nWalletMinVersion ? "present" : "none");
779 printf(" keypool entries: %u\n", counts.nPool);
780 printf(" wallet transactions: %u\n", counts.nTransactions);
781 printf(" address book labels: %u\n", counts.nNames);
782 printf(" settings: %u\n", counts.nSettings);
783 printf(" HD metadata records: %u\n",
784 counts.nHDNext + counts.nHDSchema + counts.nHDCoinType +
785 counts.nHDReceiveNext + counts.nHDChangeNext);
786 printf(" database version records: %u\n", counts.nVersion);
787 fflush(stdout);
788
789 if (counts.nMalformed > 0)
790 return 2;
791 return 0;
792}
793
794static vector<unsigned char> DataStreamBytesLocal(const CDataStream& ss)
795{
796 return vector<unsigned char>(ss.begin(), ss.end());
797}
798
799namespace {
800
801static void RemoveSQLiteExportFiles(const string& strPath)
802{
803 remove(strPath.c_str());
804 remove((strPath + "-wal").c_str());
805 remove((strPath + "-shm").c_str());
806}
807
808static void RemoveFailedRestoreWallet(const string& strWalletPath)
809{
810 DBFlush(true);
811 remove(strWalletPath.c_str());
812}
813
814class CWalletSQLiteExportVisitor : public CWalletRecordVisitor
815{
816public:
817 CWalletDBSQLite& db;
818 int& nCopied;
819
820 CWalletSQLiteExportVisitor(CWalletDBSQLite& dbIn, int& nCopiedIn)
821 : db(dbIn), nCopied(nCopiedIn) { }
822
823 bool VisitWalletRecord(const CDataStream& ssKey,
824 const CDataStream& ssValue,
825 string& strErrorRet)
826 {
827 if (!db.WriteRecord(DataStreamBytesLocal(ssKey),
828 DataStreamBytesLocal(ssValue),
829 strErrorRet))
830 return false;
831 nCopied++;
832 return true;
833 }
834};
835
836class CWalletRecordMapVisitor : public CWalletRecordVisitor
837{
838public:
839 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecords;
840
841 explicit CWalletRecordMapVisitor(
842 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecordsIn)
843 : mapRecords(mapRecordsIn) { }
844
845 bool VisitWalletRecord(const CDataStream& ssKey,
846 const CDataStream& ssValue,
847 string& strErrorRet)
848 {
849 vector<unsigned char> vchKey = DataStreamBytesLocal(ssKey);
850 if (mapRecords.count(vchKey))
851 {
852 strErrorRet = "duplicate serialized wallet key";
853 return false;
854 }
855 mapRecords[vchKey] = DataStreamBytesLocal(ssValue);
856 return true;
857 }
858};
859
860class CWalletRawRestoreDB : public CWalletDB
861{
862public:
863 CWalletRawRestoreDB(const char* pszMode="cr+", bool fTxn=true)
864 : CWalletDB(pszMode, fTxn) { }
865
866 bool WriteRaw(CDataStream ssKey, CDataStream ssValue)
867 {
868 if (!pdb || ssKey.empty())
869 return false;
870
871 Dbt datKey((void*)&ssKey[0], ssKey.size());
872 Dbt datValue(ssValue.empty() ? NULL : (void*)&ssValue[0],
873 ssValue.size());
874 int ret = pdb->put(GetTxn(), &datKey, &datValue, 0);
875
876 memset(datKey.get_data(), 0, datKey.get_size());
877 if (datValue.get_data())
878 memset(datValue.get_data(), 0, datValue.get_size());
879 return ret == 0;
880 }
881};
882
883class CWalletSQLiteRestoreVisitor : public CWalletRecordVisitor
884{
885public:
886 CWalletRawRestoreDB& db;
887 int& nCopied;
888
889 CWalletSQLiteRestoreVisitor(CWalletRawRestoreDB& dbIn, int& nCopiedIn)
890 : db(dbIn), nCopied(nCopiedIn) { }
891
892 bool VisitWalletRecord(const CDataStream& ssKey,
893 const CDataStream& ssValue,
894 string& strErrorRet)
895 {
896 if (!db.WriteRaw(ssKey, ssValue))
897 {
898 strErrorRet = "could not write raw wallet record";
899 return false;
900 }
901 nCopied++;
902 return true;
903 }
904};
905
906class CWalletSQLiteLoadCheckVisitor : public CWalletRecordVisitor
907{
908public:
909 int nRecords;
910 int nUnknown;
911 string strFailure;
912
913 CWalletSQLiteLoadCheckVisitor() : nRecords(0), nUnknown(0) { }
914
915 bool Fail(const string& strMessage, string& strErrorRet)
916 {
917 strFailure = strMessage;
918 strErrorRet = strMessage;
919 return false;
920 }
921
922 bool VisitWalletRecord(const CDataStream& ssKeyIn,
923 const CDataStream& ssValueIn,
924 string& strErrorRet)
925 {
926 nRecords++;
927 try
928 {
929 CDataStream ssKey = ssKeyIn;
930 CDataStream ssValue = ssValueIn;
931 string strType;
932 ssKey >> strType;
933 if (ssKey.fail())
934 return Fail("malformed wallet record key", strErrorRet);
935
936 if (strType == "name")
937 {
938 string strAddress;
939 string strName;
940 ssKey >> strAddress;
941 ssValue >> strName;
942 }
943 else if (strType == "tx")
944 {
945 uint256 hash;
946 CWalletTx wtx;
947 ssKey >> hash;
948 ssValue >> wtx;
949 }
950 else if (strType == "key")
951 {
952 vector<unsigned char> vchPubKey;
953 CPrivKey vchPrivKey;
954 ssKey >> vchPubKey;
955 ssValue >> vchPrivKey;
956 }
957 else if (strType == "mkey")
958 {
959 unsigned int nID = 0;
960 CWalletMasterKey kMasterKey;
961 ssKey >> nID;
962 ssValue >> kMasterKey;
963 }
964 else if (strType == "ckey")
965 {
966 vector<unsigned char> vchPubKey;
967 vector<unsigned char> vchCryptedSecret;
968 ssKey >> vchPubKey;
969 ssValue >> vchCryptedSecret;
970 }
971 else if (strType == "defaultkey" ||
972 strType == "hdmaster" ||
973 strType == "hdchaincode" ||
974 strType == "cryptedhdmaster" ||
975 strType == "cryptedhdchaincode")
976 {
977 vector<unsigned char> vch;
978 ssValue >> vch;
979 }
980 else if (strType == "hdnext" ||
981 strType == "hdreceivenext" ||
982 strType == "hdchangenext" ||
983 strType == "hdcointype")
984 {
985 unsigned int nValue = 0;
986 ssValue >> nValue;
987 }
988 else if (strType == "hdschema")
989 {
990 int nSchema = 0;
991 ssValue >> nSchema;
992 if (!ssValue.fail() &&
993 nSchema != HD_SCHEMA_NONE &&
994 nSchema != HD_SCHEMA_LEGACY &&
995 nSchema != HD_SCHEMA_BIP44)
996 return Fail("unsupported deterministic wallet schema",
997 strErrorRet);
998 }
999 else if (strType == "walletminversion")
1000 {
1001 int nMinVersion = 0;
1002 ssValue >> nMinVersion;
1003 if (!ssValue.fail() && nMinVersion > WALLET_FORMAT_SUPPORTED)
1004 return Fail("unsupported future wallet format", strErrorRet);
1005 }
1006 else if (strType == "pool")
1007 {
1008 int64 nIndex = 0;
1009 vector<unsigned char> vchPubKey;
1010 ssKey >> nIndex;
1011 ssValue >> vchPubKey;
1012 }
1013 else if (strType == "setting")
1014 {
1015 string strKey;
1016 ssKey >> strKey;
1017 if (strKey == "nTransactionFee")
1018 {
1019 int64 nValue = 0;
1020 ssValue >> nValue;
1021 }
1022 else if (strKey == "addrIncoming")
1023 {
1024 CAddress addr;
1025 ssValue >> addr;
1026 }
1027 else if (strKey == "nMineMode")
1028 {
1029 int nValue = 0;
1030 ssValue >> nValue;
1031 }
1032 else if (strKey == "fGenerateBitcoins")
1033 {
1034 int fValue = 0;
1035 ssValue >> fValue;
1036 }
1037 else if (strKey == "strParticipantPool")
1038 {
1039 string strValue;
1040 ssValue >> strValue;
1041 }
1042 }
1043 else if (strType == "version")
1044 {
1045 int nVersion = 0;
1046 ssValue >> nVersion;
1047 }
1048 else
1049 {
1050 nUnknown++;
1051 }
1052
1053 if (ssKey.fail())
1054 return Fail("malformed wallet record key", strErrorRet);
1055 if (ssValue.fail())
1056 return Fail("malformed wallet record value", strErrorRet);
1057 }
1058 catch (const std::exception& e)
1059 {
1060 return Fail(strprintf("wallet record parse exception: %s", e.what()),
1061 strErrorRet);
1062 }
1063 catch (...)
1064 {
1065 return Fail("wallet record parse exception", strErrorRet);
1066 }
1067 return true;
1068 }
1069};
1070
1071} // namespace
1072
1073// Export the currently-loaded wallet to a SQLite file. Shared by the
1074// -walletsqliteexport command and the GUI conversion wizard: opens the
1075// destination, copies every record inside one transaction, checkpoints, and
1076// verifies the stored count matches. Returns false with strErrorRet set on any
1077// failure and leaves no partial file behind; the source wallet is only read.
1078bool ExportActiveWalletToSQLite(const std::string& strDest, int& nCopiedRet,
1079 std::string& strErrorRet)
1080{
1081 nCopiedRet = 0;
1082 if (strDest.empty())
1083 {
1084 strErrorRet = "no SQLite export path was given";
1085 return false;
1086 }
1087 if (FileExists(strDest.c_str()))
1088 {
1089 strErrorRet = strprintf("refusing to overwrite existing file: %s", strDest.c_str());
1090 return false;
1091 }
1092
1093 CWalletDBSQLite db;
1094 if (!db.Open(strDest, strErrorRet))
1095 {
1096 RemoveSQLiteExportFiles(strDest);
1097 return false;
1098 }
1099 if (!db.BeginTransaction(strErrorRet))
1100 {
1101 db.Close();
1102 RemoveSQLiteExportFiles(strDest);
1103 return false;
1104 }
1105
1106 CWalletSQLiteExportVisitor visitor(db, nCopiedRet);
1107 if (!ScanWalletRecords(visitor, strErrorRet))
1108 {
1109 string strRollbackError;
1110 db.RollbackTransaction(strRollbackError);
1111 db.Close();
1112 RemoveSQLiteExportFiles(strDest);
1113 return false;
1114 }
1115 if (!db.CommitTransaction(strErrorRet) || !db.Checkpoint(strErrorRet))
1116 {
1117 db.Close();
1118 RemoveSQLiteExportFiles(strDest);
1119 return false;
1120 }
1121
1122 int nCount = 0;
1123 if (!db.CountRecords(nCount, strErrorRet))
1124 {
1125 db.Close();
1126 RemoveSQLiteExportFiles(strDest);
1127 return false;
1128 }
1129 if (nCount != nCopiedRet)
1130 {
1131 strErrorRet = strprintf("export count mismatch: copied %d, stored %d",
1132 nCopiedRet, nCount);
1133 db.Close();
1134 RemoveSQLiteExportFiles(strDest);
1135 return false;
1136 }
1137 db.Close();
1138 return true;
1139}
1140
1141// Convert the running Berkeley DB wallet to SQLite for the GUI wizard: export
1142// wallet.dat to <datadir>/wallet.sqlite, then record the backend marker so the
1143// next start opens SQLite. wallet.dat is never modified -- it stays as the
1144// fallback. Refuses if already on SQLite or if a wallet.sqlite already exists.
1145bool DoConvertWalletToSQLite(int& nCopiedRet, std::string& strErrorRet)
1146{
1147 nCopiedRet = 0;
1148 if (WalletSQLiteRuntimeActive())
1149 {
1150 strErrorRet = "this wallet is already on the SQLite backend";
1151 return false;
1152 }
1153 string strDest = GetAppDir() + "/wallet.sqlite";
1154 if (FileExists(strDest.c_str()))
1155 {
1156 strErrorRet = strprintf("a SQLite wallet already exists at %s", strDest.c_str());
1157 return false;
1158 }
1159 if (!ExportActiveWalletToSQLite(strDest, nCopiedRet, strErrorRet))
1160 return false;
1161 if (!WriteWalletBackendMarker("sqlite"))
1162 {
1163 strErrorRet = "the wallet was exported but the backend choice could not be saved";
1164 return false;
1165 }
1166 return true;
1167}
1168
1169int CmdWalletSQLiteExport(const std::string& strDest)
1170{
1171 AttachTerminal();
1172
1173 // Keep the command's own specific messages for the two conditions the
1174 // shared helper folds into a generic error, so scripts and the tests that
1175 // read this output see the same wording as before.
1176 if (strDest.empty())
1177 {
1178 fprintf(stderr, "Missing SQLite wallet export path.\n");
1179 return 1;
1180 }
1181 if (FileExists(strDest.c_str()))
1182 {
1183 fprintf(stderr, "Refusing to overwrite existing SQLite wallet export: %s\n",
1184 strDest.c_str());
1185 return 1;
1186 }
1187
1188 int nCopied = 0;
1189 string strError;
1190 if (!ExportActiveWalletToSQLite(strDest, nCopied, strError))
1191 {
1192 fprintf(stderr, "Cannot export wallet to SQLite: %s\n", strError.c_str());
1193 return 1;
1194 }
1195
1196 printf("SQLite wallet export written to %s\n", strDest.c_str());
1197 printf(" records copied: %d\n", nCopied);
1198 fflush(stdout);
1199 return 0;
1200}
1201
1202int CmdWalletSQLiteVerify(const std::string& strPath)
1203{
1204 AttachTerminal();
1205
1206 if (strPath.empty())
1207 {
1208 fprintf(stderr, "Missing SQLite wallet export path.\n");
1209 return 1;
1210 }
1211 if (!FileExists(strPath.c_str()))
1212 {
1213 fprintf(stderr, "SQLite wallet export does not exist: %s\n",
1214 strPath.c_str());
1215 return 1;
1216 }
1217
1218 std::map<vector<unsigned char>, vector<unsigned char> > mapBDB;
1219 std::map<vector<unsigned char>, vector<unsigned char> > mapSQLite;
1220 string strError;
1221
1222 CWalletRecordMapVisitor bdbVisitor(mapBDB);
1223 if (!ScanWalletRecords(bdbVisitor, strError))
1224 {
1225 fprintf(stderr, "Cannot scan wallet.dat records: %s\n", strError.c_str());
1226 return 1;
1227 }
1228
1229 CWalletDBSQLite db;
1230 if (!db.Open(strPath, strError))
1231 {
1232 fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str());
1233 return 1;
1234 }
1235 CWalletRecordMapVisitor sqliteVisitor(mapSQLite);
1236 if (!db.ScanRecords(sqliteVisitor, strError))
1237 {
1238 fprintf(stderr, "Cannot scan SQLite wallet export: %s\n", strError.c_str());
1239 return 1;
1240 }
1241
1242 int nMissing = 0;
1243 int nMismatched = 0;
1244 for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it =
1245 mapBDB.begin(); it != mapBDB.end(); ++it)
1246 {
1247 std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator sit =
1248 mapSQLite.find(it->first);
1249 if (sit == mapSQLite.end())
1250 nMissing++;
1251 else if (sit->second != it->second)
1252 nMismatched++;
1253 }
1254
1255 int nExtra = 0;
1256 for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it =
1257 mapSQLite.begin(); it != mapSQLite.end(); ++it)
1258 {
1259 if (!mapBDB.count(it->first))
1260 nExtra++;
1261 }
1262
1263 printf("SQLite wallet export verification\n");
1264 printf(" wallet.dat records: %u\n", (unsigned int)mapBDB.size());
1265 printf(" SQLite records: %u\n", (unsigned int)mapSQLite.size());
1266 printf(" missing records: %d\n", nMissing);
1267 printf(" extra records: %d\n", nExtra);
1268 printf(" record value mismatches: %d\n", nMismatched);
1269 if (nMissing == 0 && nExtra == 0 && nMismatched == 0)
1270 {
1271 printf(" verification: ok\n");
1272 fflush(stdout);
1273 return 0;
1274 }
1275
1276 printf(" verification: failed\n");
1277 fflush(stdout);
1278 return 2;
1279}
1280
1281int CmdWalletSQLiteRestore(const std::string& strPath)
1282{
1283 AttachTerminal();
1284
1285 if (strPath.empty())
1286 {
1287 fprintf(stderr, "Missing SQLite wallet export path.\n");
1288 return 1;
1289 }
1290 if (!FileExists(strPath.c_str()))
1291 {
1292 fprintf(stderr, "SQLite wallet export does not exist: %s\n",
1293 strPath.c_str());
1294 return 1;
1295 }
1296
1297 string strWalletPath = GetAppDir() + "/wallet.dat";
1298 if (FileExists(strWalletPath.c_str()))
1299 {
1300 fprintf(stderr, "Refusing to overwrite existing wallet.dat: %s\n",
1301 strWalletPath.c_str());
1302 return 1;
1303 }
1304
1305 string strError;
1306 CWalletDBSQLite sqlite;
1307 if (!sqlite.Open(strPath, strError))
1308 {
1309 fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str());
1310 return 1;
1311 }
1312
1313 int nSQLiteRecords = 0;
1314 if (!sqlite.CountRecords(nSQLiteRecords, strError))
1315 {
1316 fprintf(stderr, "Cannot count SQLite wallet export records: %s\n",
1317 strError.c_str());
1318 return 1;
1319 }
1320 if (nSQLiteRecords <= 0)
1321 {
1322 fprintf(stderr, "SQLite wallet export contains no records.\n");
1323 return 2;
1324 }
1325
1326 CWalletRawRestoreDB bdb("cr+", true);
1327 if (!bdb.TxnBegin())
1328 {
1329 fprintf(stderr, "Cannot begin Berkeley DB restore transaction.\n");
1330 return 1;
1331 }
1332
1333 int nCopied = 0;
1334 CWalletSQLiteRestoreVisitor visitor(bdb, nCopied);
1335 if (!sqlite.ScanRecords(visitor, strError))
1336 {
1337 bdb.TxnAbort();
1338 bdb.Close();
1339 RemoveFailedRestoreWallet(strWalletPath);
1340 fprintf(stderr, "Cannot restore SQLite wallet export: %s\n",
1341 strError.c_str());
1342 return 1;
1343 }
1344 if (nCopied != nSQLiteRecords)
1345 {
1346 bdb.TxnAbort();
1347 bdb.Close();
1348 RemoveFailedRestoreWallet(strWalletPath);
1349 fprintf(stderr, "SQLite wallet restore copied %d records, expected %d.\n",
1350 nCopied, nSQLiteRecords);
1351 return 2;
1352 }
1353 if (!bdb.TxnCommit())
1354 {
1355 bdb.Close();
1356 RemoveFailedRestoreWallet(strWalletPath);
1357 fprintf(stderr, "Cannot commit Berkeley DB restore transaction.\n");
1358 return 1;
1359 }
1360
1361 printf("SQLite wallet export restored to wallet.dat\n");
1362 printf(" records restored: %d\n", nCopied);
1363 fflush(stdout);
1364 return 0;
1365}
1366
1367int CmdWalletSQLiteLoadCheck(const std::string& strPath)
1368{
1369 AttachTerminal();
1370
1371 if (strPath.empty())
1372 {
1373 fprintf(stderr, "Missing SQLite wallet export path.\n");
1374 return 1;
1375 }
1376 if (!FileExists(strPath.c_str()))
1377 {
1378 fprintf(stderr, "SQLite wallet export does not exist: %s\n",
1379 strPath.c_str());
1380 return 1;
1381 }
1382
1383 string strError;
1384 CWalletDBSQLite sqlite;
1385 if (!sqlite.Open(strPath, strError))
1386 {
1387 fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str());
1388 return 1;
1389 }
1390
1391 CWalletSQLiteLoadCheckVisitor visitor;
1392 if (!sqlite.ScanRecords(visitor, strError))
1393 {
1394 printf("SQLite wallet load check\n");
1395 printf(" records checked: %d\n", visitor.nRecords);
1396 printf(" unknown records: %d\n", visitor.nUnknown);
1397 printf(" load check: failed\n");
1398 printf(" failure: %s\n", strError.c_str());
1399 fflush(stdout);
1400 return 2;
1401 }
1402
1403 printf("SQLite wallet load check\n");
1404 printf(" records checked: %d\n", visitor.nRecords);
1405 printf(" unknown records: %d\n", visitor.nUnknown);
1406 printf(" load check: ok\n");
1407 fflush(stdout);
1408 return 0;
1409}
1410
459int CmdEncryptWallet(const std::string& strPassphrase) 1411int CmdEncryptWallet(const std::string& strPassphrase)
460{ 1412{
461 AttachTerminal(); 1413 AttachTerminal();
462 1414
1415 // Capture the backend now: under SQLite, EncryptWallet() closes the runtime
1416 // store to swap the file, so WalletSQLiteRuntimeActive() reads false by the
1417 // time we would decide whether to purge Berkeley DB logs below.
1418 bool fSQLiteBackend = WalletSQLiteRuntimeActive();
1419
463 string strBackup; 1420 string strBackup;
464 string strError; 1421 string strError;
465 if (!EncryptWallet(strPassphrase, strBackup, strError)) 1422 if (!EncryptWallet(strPassphrase, strBackup, strError))
@@ -470,6 +1427,8 @@ int CmdEncryptWallet(const std::string& strPassphrase)
470 } 1427 }
471 1428
472 printf("Wallet encrypted.\n"); 1429 printf("Wallet encrypted.\n");
1430 if (fSQLiteBackend)
1431 printf("Wallet backend: SQLite (wallet.sqlite).\n");
473 printf("The old unencrypted wallet was moved to:\n"); 1432 printf("The old unencrypted wallet was moved to:\n");
474 printf(" %s\n", strBackup.c_str()); 1433 printf(" %s\n", strBackup.c_str());
475 printf("That backup still contains private keys in plain text. Move it offline or\n"); 1434 printf("That backup still contains private keys in plain text. Move it offline or\n");
@@ -482,7 +1441,13 @@ int CmdEncryptWallet(const std::string& strPassphrase)
482 // them. Closing the environment and dropping the logs is the last step of 1441 // them. Closing the environment and dropping the logs is the last step of
483 // encrypting, not an optimisation -- our own backup advice is to copy the 1442 // encrypting, not an optimisation -- our own backup advice is to copy the
484 // whole directory, and following it otherwise carries the keys along. 1443 // whole directory, and following it otherwise carries the keys along.
485 PurgeDbEnvironmentLogs(); 1444 //
1445 // The SQLite backend has no Berkeley DB environment behind the wallet, and
1446 // EncryptWallet() already closed and swapped the SQLite store; there is
1447 // nothing to purge, and calling into the environment here would only open
1448 // one needlessly.
1449 if (!fSQLiteBackend)
1450 PurgeDbEnvironmentLogs();
486 return 0; 1451 return 0;
487} 1452}
488 1453
diff --git a/src/walletcmd.h b/src/walletcmd.h
index 007b8b5..7013d44 100644
--- a/src/walletcmd.h
+++ b/src/walletcmd.h
@@ -62,6 +62,40 @@ int CmdShowDerived(int nCount);
62// by the installed recovery phrase or still depends on wallet.dat-only keys. 62// by the installed recovery phrase or still depends on wallet.dat-only keys.
63int CmdRecoveryAudit(); 63int CmdRecoveryAudit();
64 64
65// Diagnostic: count wallet.dat record types without printing keys,
66// addresses, transaction ids, labels, or other wallet values.
67int CmdWalletStorageAudit(const std::string& strJsonOut = "");
68
69// Diagnostic: fail closed when wallet.dat contains an internally inconsistent
70// or migration-unsafe mix of storage records.
71int CmdWalletStorageCheck();
72
73// Copy every raw wallet.dat record into a SQLite key/value store, preserving
74// serialized bytes. A migration/scripting tool; it does not change which backend
75// the runtime wallet opens.
76int CmdWalletSQLiteExport(const std::string& strDest);
77
78// Export the currently-loaded wallet to a SQLite file (shared by the command
79// above and the GUI wizard). Returns false with an error on any failure.
80bool ExportActiveWalletToSQLite(const std::string& strDest, int& nCopiedRet,
81 std::string& strErrorRet);
82
83// Convert the running Berkeley DB wallet to SQLite and record the backend
84// marker so the next start opens it. wallet.dat is left untouched as fallback.
85bool DoConvertWalletToSQLite(int& nCopiedRet, std::string& strErrorRet);
86
87// Diagnostic/migration staging: compare a SQLite wallet export with the current
88// wallet.dat raw records. Prints counts only, never record keys or values.
89int CmdWalletSQLiteVerify(const std::string& strPath);
90
91// Diagnostic/migration staging: rebuild wallet.dat from a SQLite export in an
92// empty data directory. Refuses to overwrite an existing wallet.dat.
93int CmdWalletSQLiteRestore(const std::string& strPath);
94
95// Diagnostic/migration staging: parse a SQLite wallet export as the runtime
96// wallet loader would. Prints counts only, never record keys or values.
97int CmdWalletSQLiteLoadCheck(const std::string& strPath);
98
65// Rewrite wallet.dat so private keys and the HD seed are encrypted with the 99// Rewrite wallet.dat so private keys and the HD seed are encrypted with the
66// given passphrase. Returns 0 on success and exits without starting the node. 100// given passphrase. Returns 0 on success and exits without starting the node.
67int CmdEncryptWallet(const std::string& strPassphrase); 101int CmdEncryptWallet(const std::string& strPassphrase);