diff options
| author | Bitflash <btf01@proton.me> | 2026-08-01 12:56:16 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-01 12:56:16 -0300 |
| commit | 7131c30c33c980f0ba1d37dfc6a3fbe8c319813e (patch) | |
| tree | 668de28f9aec4f8f350146f1d824b487752cf747 /src/main.cpp | |
| parent | b426948b966405b65f3ea6c1994164b462980694 (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.cpp | 75 |
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 | ||
| 178 | CCriticalSection cs_keyPool; | ||
| 179 | map<int64, vector<unsigned char> > mapKeyPool; | ||
| 180 | |||
| 181 | void 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 | |||
| 208 | vector<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())) |