upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-08-01 12:56:16 -0300
committerGitHub <noreply@github.com>2026-08-01 12:56:16 -0300
commit7131c30c33c980f0ba1d37dfc6a3fbe8c319813e (patch)
tree668de28f9aec4f8f350146f1d824b487752cf747 /src/main.cpp
parentb426948b966405b65f3ea6c1994164b462980694 (diff)
wallet: generate keys ahead of use so a backup outlives the next block (#85)
The wallet made a key per address and per block mined, so a backup only ever contained the keys that existed the moment it was taken -- mine one block and the coin lands on a key the backup has never heard of. The README warned about it, which is an admission rather than a fix. Keys are now generated a hundred at a time and handed out one at a time, each written to wallet.dat before it can be pointed at. A backup covers the next hundred addresses. Deterministic derivation (#47) stays open; this buys back the distance between a backup and the next mistake.
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp75
1 files changed, 65 insertions, 10 deletions
diff --git a/src/main.cpp b/src/main.cpp
index f63f575..b027cef 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -175,6 +175,58 @@ vector<unsigned char> GenerateNewKey()
175 return key.GetPubKey(); 175 return key.GetPubKey();
176} 176}
177 177
178CCriticalSection cs_keyPool;
179map<int64, vector<unsigned char> > mapKeyPool;
180
181void TopUpKeyPool()
182{
183 CRITICAL_BLOCK(cs_keyPool)
184 {
185 while ((int)mapKeyPool.size() < KEYPOOL_SIZE)
186 {
187 // Indices only ever go up. Reusing one would mean two pool records
188 // racing for the same slot after an erase.
189 int64 nIndex = 1;
190 if (!mapKeyPool.empty())
191 nIndex = (--mapKeyPool.end())->first + 1;
192
193 CKey key;
194 key.MakeNewKey();
195 // AddKey writes the private key. Order matters: the key is in
196 // wallet.dat before anything can point at it, so a crash between
197 // the two writes costs an unused key, never a usable one.
198 if (!AddKey(key))
199 return;
200 vector<unsigned char> vchPubKey = key.GetPubKey();
201 if (!CWalletDB().WritePool(nIndex, vchPubKey))
202 return;
203 mapKeyPool[nIndex] = vchPubKey;
204 }
205 }
206}
207
208vector<unsigned char> GetKeyFromPool()
209{
210 CRITICAL_BLOCK(cs_keyPool)
211 {
212 TopUpKeyPool();
213 if (!mapKeyPool.empty())
214 {
215 map<int64, vector<unsigned char> >::iterator mi = mapKeyPool.begin();
216 int64 nIndex = mi->first;
217 vector<unsigned char> vchPubKey = mi->second;
218 mapKeyPool.erase(mi);
219 CWalletDB().ErasePool(nIndex);
220 return vchPubKey;
221 }
222 }
223 // Nothing in the pool and nothing could be added -- disk full, wallet
224 // locked open elsewhere. Generating on demand is what this code did for
225 // its whole life, so fall back to it rather than refuse to mine.
226 printf("GetKeyFromPool() : pool empty, generating a key on demand\n");
227 return GenerateNewKey();
228}
229
178 230
179 231
180 232
@@ -3061,8 +3113,11 @@ bool BitcoinMiner(int nThreadId)
3061 RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); 3113 RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1);
3062 } 3114 }
3063 3115
3064 CKey key; 3116 // Drawn from the pool, so it is already in wallet.dat before a single hash
3065 key.MakeNewKey(); 3117 // is computed. The old code generated a key here and only saved it if this
3118 // thread happened to win a block, which meant a backup taken while mining
3119 // could not spend what the very next block paid.
3120 vector<unsigned char> vchPubKey = GetKeyFromPool();
3066 CBigNum bnExtraNonce = 0; 3121 CBigNum bnExtraNonce = 0;
3067 while (fGenerateBitcoins) 3122 while (fGenerateBitcoins)
3068 { 3123 {
@@ -3113,7 +3168,7 @@ bool BitcoinMiner(int nThreadId)
3113 txNew.vout.resize(1); 3168 txNew.vout.resize(1);
3114 // Always pay coinbase to own wallet key. In operator mode, the pool 3169 // Always pay coinbase to own wallet key. In operator mode, the pool
3115 // server distributes shares to miners via SendMoney() after each block. 3170 // server distributes shares to miners via SendMoney() after each block.
3116 txNew.vout[0].scriptPubKey << key.GetPubKey() << OP_CHECKSIG; 3171 txNew.vout[0].scriptPubKey << vchPubKey << OP_CHECKSIG;
3117 3172
3118 3173
3119 // 3174 //
@@ -3234,13 +3289,13 @@ bool BitcoinMiner(int nThreadId)
3234 } 3289 }
3235 else 3290 else
3236 { 3291 {
3237 // Save key 3292 // The key that just got paid was written to wallet.dat
3238 if (!AddKey(key)) 3293 // when it left the pool, so there is nothing to save
3239 { 3294 // here -- only a fresh one to take for the next block.
3240 RandomXDestroyMinerVM(rxvm); 3295 // Two threads never draw the same key: the pool hands
3241 return false; 3296 // them out one at a time under cs_keyPool, which is
3242 } 3297 // what keeps concurrent coinbases distinct (#58).
3243 key.MakeNewKey(); 3298 vchPubKey = GetKeyFromPool();
3244 3299
3245 // Process this block the same as if we had received it from another node 3300 // Process this block the same as if we had received it from another node
3246 if (!ProcessBlock(NULL, pblock.release())) 3301 if (!ProcessBlock(NULL, pblock.release()))