upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-03 21:49:35 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-03 21:49:35 +0000
commit424da766b76ba572cc9a93c4dd03736dace04d80 (patch)
tree899f6ceec730bd5e1305f1c8e59ce13e21cfb048
parent3c18a66ef601d90f08ea622021cb02bafab1601b (diff)
parente63fcfff1ae24dfff8aca6581badb51a06ddae28 (diff)
merge: !10 fix/small-fixes into deploy/all-2026-08-24
-rw-r--r--README.md18
-rwxr-xr-xscripts/check-money-log-lines.sh38
-rw-r--r--src/main.cpp27
-rw-r--r--src/readme.txt2
-rw-r--r--src/rpc.cpp14
-rw-r--r--src/util.cpp99
-rw-r--r--src/util.h22
7 files changed, 194 insertions, 26 deletions
diff --git a/README.md b/README.md
index 1782bc3..77a0ad8 100644
--- a/README.md
+++ b/README.md
@@ -289,12 +289,22 @@ See [public pool directory](docs/pool-directory.md) for the website/API shape.
289**Participant** — mine to someone else's pool. Enter or select the pool's `.btf` address and enable Start Mining. 289**Participant** — mine to someone else's pool. Enter or select the pool's `.btf` address and enable Start Mining.
290 290
291The built-in participant miner connects to operator pools through the `.btf` 291The built-in participant miner connects to operator pools through the `.btf`
292path. External miners can use Bitflash as a local Stratum bridge: 292path. Use it — **third-party RandomX miners such as XMRig and SRBMiner cannot
293mine Bitflash pools**, and no amount of configuration changes that. In RandomX
294mode they speak the Monero-style JSON-RPC protocol (`login`/`job`/`submit`) and
295assume a Monero-style job: the nonce at byte 39 of the blob, and a 32-byte seed
296hash given as 64 hex characters. Bitflash pools speak Bitcoin-style Stratum
297(`mining.subscribe`/`mining.notify`/`mining.submit`) over an 80-byte header
298whose nonce is at byte 76 — byte 39 falls inside the merkle root — and key
299RandomX from a fixed 36-byte string that is not a hash at all.
300
301`-stratumbridge` carries that same Bitcoin-style Stratum over the `.btf` path,
302so a miner on this machine can reach a pool it otherwise could not address. It
303is a byte relay, not a protocol translator, so it helps only a miner that
304already speaks the pool's dialect, and it does not make the miners above work:
293 305
294```bash 306```bash
295./bitflash -nogui -stratumbridge=POOL_BTF_ADDRESS -stratumbridgeport=3333 307./bitflash -nogui -stratumbridge=POOL_BTF_ADDRESS -stratumbridgeport=3333
296SRBMiner-MULTI --algorithm randomx --pool 127.0.0.1:3333 --wallet YOUR_BTF_ADDRESS --password x
297xmrig -a rx/0 -o 127.0.0.1:3333 -u YOUR_BTF_ADDRESS -p x
298``` 308```
299 309
300Estimate rewards and electricity cost locally: 310Estimate rewards and electricity cost locally:
@@ -322,7 +332,7 @@ Bitflash has a reliable public market.
322 -poolstatusfile=/var/www/pool_status.json \ 332 -poolstatusfile=/var/www/pool_status.json \
323 -poolroundsfile=/var/www/pool_rounds.json 333 -poolroundsfile=/var/www/pool_rounds.json
324./bitflash -nogui -gen -participant=POOL_BTF_ADDRESS # mine to pool 334./bitflash -nogui -gen -participant=POOL_BTF_ADDRESS # mine to pool
325./bitflash -nogui -stratumbridge=POOL_BTF_ADDRESS # local bridge for XMRig/SRBMiner 335./bitflash -nogui -stratumbridge=POOL_BTF_ADDRESS # Stratum over .btf (see Mining)
326``` 336```
327 337
328Every option takes `-` or `/`. **Under MSYS2 use the `-` form** — the shell 338Every option takes `-` or `/`. **Under MSYS2 use the `-` form** — the shell
diff --git a/scripts/check-money-log-lines.sh b/scripts/check-money-log-lines.sh
new file mode 100755
index 0000000..c9f348b
--- /dev/null
+++ b/scripts/check-money-log-lines.sh
@@ -0,0 +1,38 @@
1#!/usr/bin/env bash
2# Money-moving payout lines must print every time they fire: they are the
3# audit trail for obligations created, moved and destroyed, and LogPrint's
4# burst collapse can stand one line in for any number of events inside one
5# 3-second window (issue #3: 100 voids totalling 4,950 BTF read as one
6# 49.49 BTF line). Every line carrying one of the phrases below must use
7# LogPrintAlways, which keeps the category gate but skips the collapse.
8#
9# Adding a payout line that reports money moving? Use LogPrintAlways and
10# add its phrase here. A phrase found in a plain LogPrint call fails this
11# check; anything else passes, so a non-money line is never held up by it.
12set -euo pipefail
13cd "$(dirname "$0")/.."
14
15phrases='was orphaned -- voiding|orphaned while queued|\[payout\] paid %s to %s|FAILED to pay|writing off|SKIPPED bad address'
16
17# Call sites span several lines; flatten each call to a single line first so
18# the phrase and the macro it belongs to are matched together. Only a line
19# whose first token is the macro starts a call, so comments mentioning
20# LogPrint are never mistaken for one.
21bad=$(awk '
22 /^[ \t]*LogPrint(Always)?[(]/ {
23 if (collecting) print buf
24 buf = $0; collecting = 1; next
25 }
26 collecting {
27 buf = buf $0
28 if (/;[ \t]*$/) { print buf; collecting = 0 }
29 }
30 END { if (collecting) print buf }
31' src/rpc.cpp | grep -E '^[[:space:]]*LogPrint[(]' | grep -E "$phrases" || true)
32
33if [ -n "$bad" ]; then
34 echo "error: money-moving payout line(s) on collapsing LogPrint:" >&2
35 echo "$bad" >&2
36 exit 1
37fi
38echo "ok: every money-moving payout line in src/rpc.cpp uses LogPrintAlways"
diff --git a/src/main.cpp b/src/main.cpp
index 981509c..dded83a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -2679,7 +2679,18 @@ bool LoadBlockIndex(bool fAllowNew)
2679 // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) 2679 // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B)
2680 // vMerkleTree: 4a5e1e 2680 // vMerkleTree: 4a5e1e
2681 2681
2682 // Bitflash genesis block -- own header (fair launch, 2026 stable relaunch) 2682 // Bitflash genesis block -- own header (fair launch)
2683 //
2684 // Do not change pszTimestamp, the scriptPubKey, nTime or nNonce. They
2685 // are the inputs to the genesis hash, and the genesis hash is
2686 // consensus. Changing one on its own trips the assert below, which is
2687 // loud and reverts cleanly; changing one and regenerating
2688 // GENESIS_NONCE and hashGenesisBlock to match -- which is exactly what
2689 // the re-mining branch below prints out and invites -- gives a node
2690 // that starts without complaint, builds its own chain from height 0,
2691 // and rejects every real peer as being on a different chain. That one
2692 // fails silently, and the damage scales with how many people run the
2693 // "fixed" build.
2683 char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; 2694 char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine";
2684 CTransaction txNew; 2695 CTransaction txNew;
2685 txNew.vin.resize(1); 2696 txNew.vin.resize(1);
@@ -2692,7 +2703,14 @@ bool LoadBlockIndex(bool fAllowNew)
2692 block.hashPrevBlock = 0; 2703 block.hashPrevBlock = 0;
2693 block.hashMerkleRoot = block.BuildMerkleTree(); 2704 block.hashMerkleRoot = block.BuildMerkleTree();
2694 block.nVersion = 1; 2705 block.nVersion = 1;
2695 block.nTime = 1753315200; // 2026-07-24 00:00:00 UTC (stable relaunch) 2706 // 1753315200 is 2025-07-24T00:00:00Z. The headline in pszTimestamp
2707 // above reads 2026 and does not match it. This is a known discrepancy
2708 // and not a typo in the constant: the value is only consensus-critical
2709 // as an input to the genesis hash, the block hashes to
2710 // hashGenesisBlock with it, and every node on the network agrees. The
2711 // mismatch is in the launched chain and cannot be corrected -- see the
2712 // warning above before deciding otherwise.
2713 block.nTime = 1753315200;
2696 block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) 2714 block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor)
2697 block.nNonce = GENESIS_NONCE; 2715 block.nNonce = GENESIS_NONCE;
2698 2716
@@ -2719,6 +2737,11 @@ bool LoadBlockIndex(bool fAllowNew)
2719 printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); 2737 printf("powhash = %s\n", block.GetPoWHash().ToString().c_str());
2720 printf("hash = %s\n", block.GetHash().ToString().c_str()); 2738 printf("hash = %s\n", block.GetHash().ToString().c_str());
2721 printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); 2739 printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str());
2740 printf("----------------------------------------\n");
2741 printf("These constants bring up a NEW chain. Do not paste them\n");
2742 printf("into a build for this network to make an edited nTime or\n");
2743 printf("pszTimestamp fit -- a node whose genesis differs starts\n");
2744 printf("cleanly on a chain of its own and rejects every real peer.\n");
2722 printf("========================================\n"); 2745 printf("========================================\n");
2723 } 2746 }
2724 2747
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 3cee687..bd5fd8b 100644
--- a/src/rpc.cpp
+++ b/src/rpc.cpp
@@ -1732,7 +1732,9 @@ static void FlushMaturePayouts()
1732 } 1732 }
1733 int64 total = 0; 1733 int64 total = 0;
1734 for (auto& kv : it->amounts) total += kv.second; 1734 for (auto& kv : it->amounts) total += kv.second;
1735 LogPrint("payout", "[payout] block %d (%s) was orphaned -- voiding %zu" 1735 // Always: one line per voided entry, never a collapsed summary.
1736 // A reorg voids these in a burst from this one source line.
1737 LogPrintAlways("payout", "[payout] block %d (%s) was orphaned -- voiding %zu"
1736 " payment(s) totalling %s\n", 1738 " payment(s) totalling %s\n",
1737 it->blockHeight, it->blockHash.ToString().substr(0, 14).c_str(), 1739 it->blockHeight, it->blockHash.ToString().substr(0, 14).c_str(),
1738 it->amounts.size(), FormatMoney(total).c_str()); 1740 it->amounts.size(), FormatMoney(total).c_str());
@@ -1931,7 +1933,7 @@ static void RecordPayoutTxid(const PayingRecord& r, const std::string& txid)
1931// disagreeing. 1933// disagreeing.
1932static void ForfeitPayoutRecipient(const PayingRecord& r, const std::string& reason) 1934static void ForfeitPayoutRecipient(const PayingRecord& r, const std::string& reason)
1933{ 1935{
1934 LogPrint("payout", "[payout] writing off %s owed to %s for block %d: %s\n", 1936 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, 1937 FormatMoney(r.amount).c_str(), r.address.c_str(), r.blockHeight,
1936 reason.c_str()); 1938 reason.c_str());
1937 VersionedBlob blob; 1939 VersionedBlob blob;
@@ -2036,7 +2038,7 @@ static void PayoutThreadFn(void*)
2036 // funding block can land at any point in that window. Upstream has 2038 // funding block can land at any point in that window. Upstream has
2037 // no equivalent -- it decides once, when the entry matures. 2039 // no equivalent -- it decides once, when the entry matures.
2038 if (!PayoutBlockStillOnChain(r.blockHash)) { 2040 if (!PayoutBlockStillOnChain(r.blockHash)) {
2039 LogPrint("payout", "[payout] block %d (%s) orphaned while queued" 2041 LogPrintAlways("payout", "[payout] block %d (%s) orphaned while queued"
2040 " -- dropping %s to %s\n", 2042 " -- dropping %s to %s\n",
2041 r.blockHeight, r.blockHash.ToString().substr(0, 14).c_str(), 2043 r.blockHeight, r.blockHash.ToString().substr(0, 14).c_str(),
2042 FormatMoney(r.amount).c_str(), r.address.c_str()); 2044 FormatMoney(r.amount).c_str(), r.address.c_str());
@@ -2080,7 +2082,7 @@ static void PayoutThreadFn(void*)
2080 const std::string reason = 2082 const std::string reason =
2081 strprintf("'%s' is not a valid address, so no transaction" 2083 strprintf("'%s' is not a valid address, so no transaction"
2082 " can pay it", r.address.c_str()); 2084 " can pay it", r.address.c_str());
2083 LogPrint("payout", "[payout] SKIPPED bad address '%s' --" 2085 LogPrintAlways("payout", "[payout] SKIPPED bad address '%s' --"
2084 " %s owed for block %d cannot be paid\n", 2086 " %s owed for block %d cannot be paid\n",
2085 r.address.c_str(), FormatMoney(r.amount).c_str(), 2087 r.address.c_str(), FormatMoney(r.amount).c_str(),
2086 r.blockHeight); 2088 r.blockHeight);
@@ -2205,7 +2207,7 @@ static void PayoutThreadFn(void*)
2205 // described by at least one of them at every instant. 2207 // described by at least one of them at every instant.
2206 RecordPayoutTxid(r, wtx.GetHash().GetHex()); 2208 RecordPayoutTxid(r, wtx.GetHash().GetHex());
2207 ForgetPayment(r); 2209 ForgetPayment(r);
2208 LogPrint("payout", "[payout] paid %s to %s (tx %s)\n", 2210 LogPrintAlways("payout", "[payout] paid %s to %s (tx %s)\n",
2209 FormatMoney(r.amount).c_str(), r.address.c_str(), 2211 FormatMoney(r.amount).c_str(), r.address.c_str(),
2210 wtx.GetHash().ToString().substr(0, 12).c_str()); 2212 wtx.GetHash().ToString().substr(0, 12).c_str());
2211 } else { 2213 } else {
@@ -2263,7 +2265,7 @@ static void PayoutThreadFn(void*)
2263 // going away must not turn the log into one line a minute 2265 // going away must not turn the log into one line a minute
2264 // forever, and must not go quiet either. 2266 // forever, and must not go quiet either.
2265 if (r.retries <= 5 || r.retries % 60 == 0) 2267 if (r.retries <= 5 || r.retries % 60 == 0)
2266 LogPrint("payout", "[payout] FAILED to pay %s to %s" 2268 LogPrintAlways("payout", "[payout] FAILED to pay %s to %s"
2267 " (%s spendable, wallet unlocked, block %d on" 2269 " (%s spendable, wallet unlocked, block %d on"
2268 " the main chain) -- attempt %d, retrying in" 2270 " the main chain) -- attempt %d, retrying in"
2269 " 60s; this debt stands until it is paid\n", 2271 " 60s; this debt stands until it is paid\n",
diff --git a/src/util.cpp b/src/util.cpp
index 60142be..ab29178 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -65,6 +65,61 @@ struct LogDedupState {
65static std::map<std::string, LogDedupState> g_logDedup; 65static std::map<std::string, LogDedupState> g_logDedup;
66static CCriticalSection cs_logDedup; 66static CCriticalSection cs_logDedup;
67 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
68void 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)
69{ 124{
70 // Call sites follow the existing printf() convention of ending their 125 // Call sites follow the existing printf() convention of ending their
@@ -79,11 +134,19 @@ void LogPrintDedup(const char* category, const char* file, int line, const std::
79 134
80 int64 now = GetTime(); 135 int64 now = GetTime();
81 bool fEmitNow = false; 136 bool fEmitNow = false;
82 int suppressedToFlush = 0; 137 std::vector<LogDedupFlush> flushes;
83 std::string flushedMsg;
84 138
85 CRITICAL_BLOCK(cs_logDedup) 139 CRITICAL_BLOCK(cs_logDedup)
86 { 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
87 std::map<std::string, LogDedupState>::iterator it = g_logDedup.find(key); 150 std::map<std::string, LogDedupState>::iterator it = g_logDedup.find(key);
88 if (it == g_logDedup.end()) 151 if (it == g_logDedup.end())
89 { 152 {
@@ -94,10 +157,18 @@ void LogPrintDedup(const char* category, const char* file, int line, const std::
94 } 157 }
95 else if (now - it->second.windowStart >= DEDUP_WINDOW_SECS) 158 else if (now - it->second.windowStart >= DEDUP_WINDOW_SECS)
96 { 159 {
97 // Window rolled over: flush what was suppressed during it (if 160 // Window rolled over within the same second as the last sweep:
98 // anything), then start a new window with this call printed. 161 // flush what was suppressed during it (if anything), then start a
99 suppressedToFlush = it->second.suppressed; 162 // new window with this call printed.
100 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 }
101 it->second.windowStart = now; 172 it->second.windowStart = now;
102 it->second.suppressed = 0; 173 it->second.suppressed = 0;
103 it->second.lastMsg = msg; 174 it->second.lastMsg = msg;
@@ -112,13 +183,23 @@ void LogPrintDedup(const char* category, const char* file, int line, const std::
112 } 183 }
113 } 184 }
114 185
115 if (suppressedToFlush > 0) 186 for (size_t i = 0; i < flushes.size(); i++)
116 OutputDebugStringF("[%s] (%d similar in %llds, last: %s)\n", 187 EmitDedupSummary(flushes[i]);
117 category, suppressedToFlush, (long long)DEDUP_WINDOW_SECS, flushedMsg.c_str());
118 if (fEmitNow) 188 if (fEmitNow)
119 OutputDebugStringF("[%s] %s\n", category, msg.c_str()); 189 OutputDebugStringF("[%s] %s\n", category, msg.c_str());
120} 190}
121 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
122 203
123 204
124 205
diff --git a/src/util.h b/src/util.h
index dc4536d..dfe7511 100644
--- a/src/util.h
+++ b/src/util.h
@@ -115,15 +115,29 @@ extern int64 nMaxDebugFileSize;
115// 2. Burst collapse: repeated LogPrint() calls from the *same source line* 115// 2. Burst collapse: repeated LogPrint() calls from the *same source line*
116// 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
117// 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
118// 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
119// 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
120// 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
121// 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.
122// --------------------------------------------------------------------------- 133// ---------------------------------------------------------------------------
123bool LogAcceptsCategory(const char* category); 134bool LogAcceptsCategory(const char* category);
124void 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);
125#define LogPrint(category, ...) \ 137#define LogPrint(category, ...) \
126 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)
127 141
128// 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
129// below -- `printf` itself is remapped to OutputDebugStringF just above, and 143// below -- `printf` itself is remapped to OutputDebugStringF just above, and