diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
| commit | ceae3d076b5ad11ee84729ae43f14e9312896646 (patch) | |
| tree | 7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/main.cpp | |
| parent | be63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff) | |
| parent | 66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff) | |
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we
build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped
line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git
merge-base returns no result at all -- so anyone cloning this repository
landed on a default branch that was neither current nor related to the
running node.
The 28 commits on main were audited before this merge: every one is already
present in the tree by content, including the two that are hardest to see
because they were rewritten afterwards -- the payout reorg-safety work
(PayoutBlockStillOnChain, since evolved to re-check on every retry rather
than once at maturity) and the nostr relay certificate verification
(SSL_set1_host, now with a Windows root-store path). Nothing on main is lost
by this merge.
Recorded as a merge rather than a force-push: main only ever moves forward,
its old history stays reachable as the first parent, and this is revertible.
The tree is taken wholesale from the merged branch, so main's content is now
exactly what was built and tested.
What this brings onto main:
- compactblock: BIP152-style compact block relay ported from upstream 1.2.23,
primitives and unit test only, with the RAND_bytes failure path closed and
the reconstruction allocation bounded.
- upstream/main 17741d9: isolated testnet parameters and genesis, optional
2 MB large pages for RandomX. Mainnet genesis verified byte-identical
through upstream's BuildGenesisBlock() refactor.
- deploy: every image built on bookworm so binaries run on Debian 12, and the
container build now honours the RandomX and libsecp256k1 pins it had been
silently ignoring since before 1.2.15.
Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus
constants unchanged against e4f898c apart from the pre-existing
MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 1000 |
1 files changed, 936 insertions, 64 deletions
diff --git a/src/main.cpp b/src/main.cpp index 77aba89..0fa6b89 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" |
| @@ -41,12 +42,30 @@ unsigned int nTransactionsUpdated = 0; | |||
| 41 | map<COutPoint, CInPoint> mapNextTx; | 42 | map<COutPoint, CInPoint> mapNextTx; |
| 42 | 43 | ||
| 43 | map<uint256, CBlockIndex*> mapBlockIndex; | 44 | map<uint256, CBlockIndex*> mapBlockIndex; |
| 44 | // Bitflash genesis -- mined under RandomX PoW on 2026-07-22 | 45 | // Bitflash mainnet genesis -- mined under RandomX PoW for the 2026 stable |
| 45 | // (nTime 1753142400, nBits 0x1f0fffff, RandomX key "Bitflash/RandomX/v1/...") | 46 | // relaunch. Testnet gets its own resettable genesis below so block indexes, |
| 46 | // Bitflash 2026 stable relaunch genesis (2-min blocks, RandomX). Mined values. | 47 | // headers, and wallet scans cannot be accidentally mixed across networks. |
| 47 | unsigned int GENESIS_NONCE = 3141; | 48 | static const unsigned int MAINNET_GENESIS_TIME = 1753315200; // 2026-07-24 00:00:00 UTC |
| 48 | uint256 hashGenesisBlock("0x5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169"); | 49 | static const unsigned int MAINNET_GENESIS_NONCE = 3141; |
| 49 | uint256 hashGenesisMerkleRoot("0x1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325"); | 50 | static const char* MAINNET_GENESIS_TIMESTAMP = |
| 51 | "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; | ||
| 52 | static const char* MAINNET_GENESIS_HASH = | ||
| 53 | "0x5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169"; | ||
| 54 | static const char* MAINNET_GENESIS_MERKLE = | ||
| 55 | "0x1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325"; | ||
| 56 | |||
| 57 | static const unsigned int TESTNET_GENESIS_TIME = 1787184000; // 2026-08-20 00:00:00 UTC | ||
| 58 | static const unsigned int TESTNET_GENESIS_NONCE = 3199; | ||
| 59 | static const char* TESTNET_GENESIS_TIMESTAMP = | ||
| 60 | "Bitflash testnet 20/Aug/2026: resettable public test chain"; | ||
| 61 | static const char* TESTNET_GENESIS_HASH = | ||
| 62 | "0xa9d11c6d697bcb7aea0653bc088b12dcf9f44e732df205162cf085fd9d9963eb"; | ||
| 63 | static const char* TESTNET_GENESIS_MERKLE = | ||
| 64 | "0x510b293105e3ff85b8d4c3920e357f85d1a86bf199eb4e7d11f04f380030ec5c"; | ||
| 65 | |||
| 66 | unsigned int GENESIS_NONCE = MAINNET_GENESIS_NONCE; | ||
| 67 | uint256 hashGenesisBlock(MAINNET_GENESIS_HASH); | ||
| 68 | uint256 hashGenesisMerkleRoot(MAINNET_GENESIS_MERKLE); | ||
| 50 | CBlockIndex* pindexGenesisBlock = NULL; | 69 | CBlockIndex* pindexGenesisBlock = NULL; |
| 51 | int nBestHeight = -1; | 70 | int nBestHeight = -1; |
| 52 | uint256 hashBestChain = 0; | 71 | uint256 hashBestChain = 0; |
| @@ -64,6 +83,137 @@ CBlockIndex* pindexBest = NULL; | |||
| 64 | map<uint256, CBlock*> mapOrphanBlocks; | 83 | map<uint256, CBlock*> mapOrphanBlocks; |
| 65 | multimap<uint256, CBlock*> mapOrphanBlocksByPrev; | 84 | multimap<uint256, CBlock*> mapOrphanBlocksByPrev; |
| 66 | 85 | ||
| 86 | // Block bodies we have sent a getdata for and not yet received, so the | ||
| 87 | // failover pass in SendMessages can tell "requested and being served" from | ||
| 88 | // "requested and gone quiet" and re-issue the request to a different peer | ||
| 89 | // (#33). Keyed by block hash; setAsked is the peers already tried since the | ||
| 90 | // round last restarted, so the retry rotates instead of hammering the | ||
| 91 | // announcer that is not serving. Guarded by cs_main, which both the message | ||
| 92 | // handlers and SendMessages already hold. | ||
| 93 | // | ||
| 94 | // Capped small on purpose. At the tip there is one block in flight; during | ||
| 95 | // initial download there are legitimately hundreds outstanding to one peer, | ||
| 96 | // and rotating those would duplicate the whole download to every other peer. | ||
| 97 | // | ||
| 98 | // The cap is a shared resource and nothing on the path from inv to insertion | ||
| 99 | // can tell a real block hash from 32 random bytes -- AlreadyHave only asks | ||
| 100 | // whether we already hold the block, and there is no header to check work | ||
| 101 | // against. So the table does not refuse a new hash when it is full; it makes | ||
| 102 | // room. See MakeRoomInBlockFetch for the rule, which is what stops one peer | ||
| 103 | // filling all eight slots and switching failover off for everybody. | ||
| 104 | // | ||
| 105 | // The initialisers matter: the insertion path uses operator[] and then tests | ||
| 106 | // nTimeFirstAsked == 0 to detect a fresh entry. That worked on | ||
| 107 | // value-initialisation alone, but only for as long as nobody gave this struct | ||
| 108 | // a constructor. | ||
| 109 | struct CBlockFetch | ||
| 110 | { | ||
| 111 | int64 nTimeFirstAsked = 0; // when the first getdata for this block went out | ||
| 112 | int64 nTimeAsked = 0; // when the latest getdata went out | ||
| 113 | CAddress addrAnnounced; // peer whose announcement put this hash here | ||
| 114 | set<CAddress> setAsked; // peers tried since the round last restarted | ||
| 115 | }; | ||
| 116 | map<uint256, CBlockFetch> mapBlockFetch; | ||
| 117 | static const int BLOCK_FETCH_TRACK_MAX = 8; | ||
| 118 | // A block nobody has served for this long is not a fetch problem: the hash may | ||
| 119 | // have been announced and never existed. Drop the entry rather than getdata | ||
| 120 | // forever; a re-announcement recreates it. | ||
| 121 | static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60; | ||
| 122 | |||
| 123 | // Failover firing at all is worth saying out loud: it means peers are | ||
| 124 | // announcing blocks they then do not serve, which is the pathology of #33 and | ||
| 125 | // the thing nobody could otherwise see. Naming the peer on every re-request is | ||
| 126 | // a different matter. A peer can drive that line as often as it likes by | ||
| 127 | // announcing hashes it will not serve, and what it prints is a peer address, | ||
| 128 | // which is the one thing the .btf transport otherwise takes trouble not to | ||
| 129 | // spread around. So the per-peer detail sits behind the net category and the | ||
| 130 | // unconditional line is a count over a window no peer can shorten: the first | ||
| 131 | // failover after a quiet spell prints at once, because the change from "blocks | ||
| 132 | // arrive" to "blocks are announced and not served" is the event worth seeing, | ||
| 133 | // and everything after it is summarised once a minute. | ||
| 134 | static const int BLOCK_FETCH_REPORT_SECS = 60; | ||
| 135 | static int64 nBlockFetchWindowStart = 0; | ||
| 136 | static int nBlockFetchFailovers = 0; | ||
| 137 | |||
| 138 | // How stale our own best block may be before we stop calling this the tip. | ||
| 139 | // Blocks target two minutes, so this is thirty intervals: far longer than any | ||
| 140 | // stall worth failing over from -- the one in #33 ran sixteen minutes -- and | ||
| 141 | // far shorter than catching up, which starts hours or days back. | ||
| 142 | static const int BLOCK_FETCH_TIP_AGE_SECS = 60 * 60; | ||
| 143 | |||
| 144 | // Whether this node is tracking the tip rather than still catching up. | ||
| 145 | // | ||
| 146 | // Measured off the age of our own best block, not off any height a peer told | ||
| 147 | // us. nStartingHeight is optional in the version message and every node | ||
| 148 | // released so far sends the short form, so it is -1 for essentially every | ||
| 149 | // peer; and where it is filled in later it is filled in from the height of a | ||
| 150 | // block that peer served us, which during catch-up tracks our own height and | ||
| 151 | // would report "at the tip" for the whole download. Block timestamps are the | ||
| 152 | // one clock here that peers cannot move: CheckBlock refuses anything more than | ||
| 153 | // two hours ahead and AcceptBlock refuses anything at or before the previous | ||
| 154 | // median, so the only way to make this read "at the tip" falsely is to mine, | ||
| 155 | // and all that buys is the 15s spacing this code had unconditionally anyway. | ||
| 156 | bool IsBlockFetchNearTip() | ||
| 157 | { | ||
| 158 | return pindexBest && (int64)pindexBest->nTime > GetTime() - BLOCK_FETCH_TIP_AGE_SECS; | ||
| 159 | } | ||
| 160 | |||
| 161 | // Drop one entry so a hash announced by addrNew can be tracked. | ||
| 162 | // | ||
| 163 | // Refusing the insertion instead was a remote off-switch for the whole | ||
| 164 | // feature. An inv carrying eight hashes that never existed filled every slot | ||
| 165 | // -- nothing between inv and here can tell those from real blocks -- and no | ||
| 166 | // genuinely new block was tracked again until the give-up timer expired ten | ||
| 167 | // minutes later, re-armable for one message per ten minutes from a single | ||
| 168 | // connection. | ||
| 169 | // | ||
| 170 | // So evict instead, by occupancy: a peer may only take a slot from a peer | ||
| 171 | // holding strictly more slots than itself, and otherwise recycles one of its | ||
| 172 | // own. That is what keeps eviction from becoming the same attack in a new | ||
| 173 | // shape. A peer that has filled the table is by definition the heaviest | ||
| 174 | // holder, so its own entries are the only ones it can displace, while a peer | ||
| 175 | // announcing its first block holds nothing and therefore always evicts from | ||
| 176 | // the heaviest -- a genuine announcement is never refused a slot, whatever the | ||
| 177 | // attacker holds or however many connections it holds it from. The one case | ||
| 178 | // that costs a real fetch is eight distinct peers holding one entry each, | ||
| 179 | // where a ninth takes the oldest; that is a busy healthy node, not an attack, | ||
| 180 | // and losing the oldest of eight beats refusing the ninth. | ||
| 181 | static void MakeRoomInBlockFetch(const CAddress& addrNew) | ||
| 182 | { | ||
| 183 | if (mapBlockFetch.empty()) | ||
| 184 | return; | ||
| 185 | |||
| 186 | map<CAddress, int> mapHeld; | ||
| 187 | for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi) | ||
| 188 | mapHeld[(*mi).second.addrAnnounced]++; | ||
| 189 | |||
| 190 | int nMine = mapHeld.count(addrNew) ? mapHeld[addrNew] : 0; | ||
| 191 | |||
| 192 | int nMax = 0; | ||
| 193 | CAddress addrVictim; | ||
| 194 | for (map<CAddress, int>::iterator mi = mapHeld.begin(); mi != mapHeld.end(); ++mi) | ||
| 195 | if ((*mi).second > nMax) | ||
| 196 | { | ||
| 197 | nMax = (*mi).second; | ||
| 198 | addrVictim = (*mi).first; | ||
| 199 | } | ||
| 200 | |||
| 201 | // Nobody is holding more than this peer, so it recycles its own oldest | ||
| 202 | // rather than taking a slot off an equal. | ||
| 203 | if (nMax <= nMine) | ||
| 204 | addrVictim = addrNew; | ||
| 205 | |||
| 206 | map<uint256, CBlockFetch>::iterator miVictim = mapBlockFetch.end(); | ||
| 207 | for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi) | ||
| 208 | if ((*mi).second.addrAnnounced == addrVictim && | ||
| 209 | (miVictim == mapBlockFetch.end() || | ||
| 210 | (*mi).second.nTimeFirstAsked < (*miVictim).second.nTimeFirstAsked)) | ||
| 211 | miVictim = mi; | ||
| 212 | |||
| 213 | if (miVictim != mapBlockFetch.end()) | ||
| 214 | mapBlockFetch.erase(miVictim); | ||
| 215 | } | ||
| 216 | |||
| 67 | map<uint256, CDataStream*> mapOrphanTransactions; | 217 | map<uint256, CDataStream*> mapOrphanTransactions; |
| 68 | multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; | 218 | multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; |
| 69 | 219 | ||
| @@ -109,7 +259,11 @@ string strPayoutFile; // participant: /payoutfile with one addres | |||
| 109 | int nHashThreads = 0; // participant: /hashthreads override (0 = cores-1) | 259 | int nHashThreads = 0; // participant: /hashthreads override (0 = cores-1) |
| 110 | string strPoolName = "Bitflash Pool"; | 260 | string strPoolName = "Bitflash Pool"; |
| 111 | string strPoolDashboardUrl; | 261 | string strPoolDashboardUrl; |
| 262 | string strPoolStatusFile; | ||
| 263 | string strPoolRoundsFile; | ||
| 112 | double dPoolFeePercent = 0.0; | 264 | double dPoolFeePercent = 0.0; |
| 265 | bool fStratumBridge = false; | ||
| 266 | int nStratumBridgePort = 3333; | ||
| 113 | 267 | ||
| 114 | static std::atomic<uint64> gParticipantSharesSent{0}; | 268 | static std::atomic<uint64> gParticipantSharesSent{0}; |
| 115 | static std::atomic<uint64> gParticipantSharesAccepted{0}; | 269 | static std::atomic<uint64> gParticipantSharesAccepted{0}; |
| @@ -118,6 +272,63 @@ static std::atomic<uint64> gParticipantHashRateX1000{0}; | |||
| 118 | static std::mutex gParticipantStatusMutex; | 272 | static std::mutex gParticipantStatusMutex; |
| 119 | static std::string gParticipantStatus; | 273 | static std::string gParticipantStatus; |
| 120 | 274 | ||
| 275 | void SelectGenesisParams(bool fTestNetIn) | ||
| 276 | { | ||
| 277 | static bool fGenesisParamsSelected = false; | ||
| 278 | static bool fGenesisParamsTestNet = false; | ||
| 279 | if (fGenesisParamsSelected && fGenesisParamsTestNet == fTestNetIn) | ||
| 280 | return; | ||
| 281 | |||
| 282 | fGenesisParamsSelected = true; | ||
| 283 | fGenesisParamsTestNet = fTestNetIn; | ||
| 284 | |||
| 285 | if (fTestNetIn) | ||
| 286 | { | ||
| 287 | GENESIS_NONCE = TESTNET_GENESIS_NONCE; | ||
| 288 | hashGenesisBlock = uint256(TESTNET_GENESIS_HASH); | ||
| 289 | hashGenesisMerkleRoot = uint256(TESTNET_GENESIS_MERKLE); | ||
| 290 | } | ||
| 291 | else | ||
| 292 | { | ||
| 293 | GENESIS_NONCE = MAINNET_GENESIS_NONCE; | ||
| 294 | hashGenesisBlock = uint256(MAINNET_GENESIS_HASH); | ||
| 295 | hashGenesisMerkleRoot = uint256(MAINNET_GENESIS_MERKLE); | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | void SelectChainParams(bool fTestNetIn) | ||
| 300 | { | ||
| 301 | SelectNetworkParams(fTestNetIn); | ||
| 302 | SelectGenesisParams(fTestNetIn); | ||
| 303 | } | ||
| 304 | |||
| 305 | static CBlock BuildGenesisBlock() | ||
| 306 | { | ||
| 307 | const char* pszTimestamp = IsTestNet() ? | ||
| 308 | TESTNET_GENESIS_TIMESTAMP : MAINNET_GENESIS_TIMESTAMP; | ||
| 309 | |||
| 310 | CTransaction txNew; | ||
| 311 | txNew.vin.resize(1); | ||
| 312 | txNew.vout.resize(1); | ||
| 313 | txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << | ||
| 314 | vector<unsigned char>((unsigned char*)pszTimestamp, | ||
| 315 | (unsigned char*)pszTimestamp + strlen(pszTimestamp)); | ||
| 316 | txNew.vout[0].nValue = 50 * COIN; | ||
| 317 | txNew.vout[0].scriptPubKey = CScript() << | ||
| 318 | CBigNum("0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704") << | ||
| 319 | OP_CHECKSIG; | ||
| 320 | |||
| 321 | CBlock block; | ||
| 322 | block.vtx.push_back(txNew); | ||
| 323 | block.hashPrevBlock = 0; | ||
| 324 | block.hashMerkleRoot = block.BuildMerkleTree(); | ||
| 325 | block.nVersion = 1; | ||
| 326 | block.nTime = IsTestNet() ? TESTNET_GENESIS_TIME : MAINNET_GENESIS_TIME; | ||
| 327 | block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) | ||
| 328 | block.nNonce = GENESIS_NONCE; | ||
| 329 | return block; | ||
| 330 | } | ||
| 331 | |||
| 121 | void SetParticipantMiningStatus(const std::string& status) | 332 | void SetParticipantMiningStatus(const std::string& status) |
| 122 | { | 333 | { |
| 123 | // Updates the GUI status string only. Logging is done at each call site | 334 | // Updates the GUI status string only. Logging is done at each call site |
| @@ -190,6 +401,9 @@ int ParticipantHashThreadCount() | |||
| 190 | return nWorkers - 1; | 401 | return nWorkers - 1; |
| 191 | } | 402 | } |
| 192 | 403 | ||
| 404 | static void SecureClearBytes(vector<unsigned char>& vch); | ||
| 405 | static void SecureClearKeyingMaterial(CKeyingMaterial& vch); | ||
| 406 | |||
| 193 | bool AddKey(const CKey& key) | 407 | bool AddKey(const CKey& key) |
| 194 | { | 408 | { |
| 195 | if (IsWalletEncrypted()) | 409 | if (IsWalletEncrypted()) |
| @@ -199,9 +413,13 @@ bool AddKey(const CKey& key) | |||
| 199 | vector<unsigned char> vchCryptedSecret; | 413 | vector<unsigned char> vchCryptedSecret; |
| 200 | if (IsWalletLocked()) | 414 | if (IsWalletLocked()) |
| 201 | return error("AddKey() : wallet is locked\n"); | 415 | return error("AddKey() : wallet is locked\n"); |
| 202 | if (!EncryptSecret(vWalletMasterKey, vector<unsigned char>(vchPrivKey.begin(), vchPrivKey.end()), | 416 | vector<unsigned char> vchPlainSecret(vchPrivKey.begin(), vchPrivKey.end()); |
| 203 | WalletKeyIV(key.GetPubKey()), vchCryptedSecret)) | 417 | if (!EncryptSecret(vWalletMasterKey, vchPlainSecret, WalletKeyIV(key.GetPubKey()), vchCryptedSecret)) |
| 418 | { | ||
| 419 | SecureClearBytes(vchPlainSecret); | ||
| 204 | return error("AddKey() : encrypting key failed\n"); | 420 | return error("AddKey() : encrypting key failed\n"); |
| 421 | } | ||
| 422 | SecureClearBytes(vchPlainSecret); | ||
| 205 | if (!AddCryptedKey(key.GetPubKey(), vchCryptedSecret)) | 423 | if (!AddCryptedKey(key.GetPubKey(), vchCryptedSecret)) |
| 206 | return false; | 424 | return false; |
| 207 | return CWalletDB().WriteCryptedKey(key.GetPubKey(), vchCryptedSecret); | 425 | return CWalletDB().WriteCryptedKey(key.GetPubKey(), vchCryptedSecret); |
| @@ -225,17 +443,31 @@ bool IsWalletLocked() | |||
| 225 | return fWalletEncrypted && fWalletLocked; | 443 | return fWalletEncrypted && fWalletLocked; |
| 226 | } | 444 | } |
| 227 | 445 | ||
| 446 | static void SecureClearBytes(vector<unsigned char>& vch) | ||
| 447 | { | ||
| 448 | if (!vch.empty()) | ||
| 449 | OPENSSL_cleanse(&vch[0], vch.size()); | ||
| 450 | vch.clear(); | ||
| 451 | } | ||
| 452 | |||
| 453 | static void SecureClearKeyingMaterial(CKeyingMaterial& vch) | ||
| 454 | { | ||
| 455 | if (!vch.empty()) | ||
| 456 | OPENSSL_cleanse(&vch[0], vch.size()); | ||
| 457 | vch.clear(); | ||
| 458 | } | ||
| 459 | |||
| 228 | void LockWallet() | 460 | void LockWallet() |
| 229 | { | 461 | { |
| 230 | if (!fWalletEncrypted) | 462 | if (!fWalletEncrypted) |
| 231 | return; | 463 | return; |
| 232 | CRITICAL_BLOCK(cs_mapKeys) | 464 | CRITICAL_BLOCK(cs_mapKeys) |
| 233 | { | 465 | { |
| 234 | vWalletMasterKey.clear(); | 466 | SecureClearKeyingMaterial(vWalletMasterKey); |
| 235 | if (!vchCryptedHDMaster.empty()) | 467 | if (!vchCryptedHDMaster.empty()) |
| 236 | vchHDMaster.clear(); | 468 | SecureClearBytes(vchHDMaster); |
| 237 | if (!vchCryptedHDChainCode.empty()) | 469 | if (!vchCryptedHDChainCode.empty()) |
| 238 | vchHDChainCode.clear(); | 470 | SecureClearBytes(vchHDChainCode); |
| 239 | fWalletLocked = true; | 471 | fWalletLocked = true; |
| 240 | } | 472 | } |
| 241 | } | 473 | } |
| @@ -260,14 +492,14 @@ bool DeriveWalletPassphraseKey(const string& strPassphrase, | |||
| 260 | chKey, chIV); | 492 | chKey, chIV); |
| 261 | if (n != 32) | 493 | if (n != 32) |
| 262 | { | 494 | { |
| 263 | memset(chKey, 0, sizeof(chKey)); | 495 | OPENSSL_cleanse(chKey, sizeof(chKey)); |
| 264 | memset(chIV, 0, sizeof(chIV)); | 496 | OPENSSL_cleanse(chIV, sizeof(chIV)); |
| 265 | return false; | 497 | return false; |
| 266 | } | 498 | } |
| 267 | vchKeyRet.assign(chKey, chKey + sizeof(chKey)); | 499 | vchKeyRet.assign(chKey, chKey + sizeof(chKey)); |
| 268 | vchIVRet.assign(chIV, chIV + sizeof(chIV)); | 500 | vchIVRet.assign(chIV, chIV + sizeof(chIV)); |
| 269 | memset(chKey, 0, sizeof(chKey)); | 501 | OPENSSL_cleanse(chKey, sizeof(chKey)); |
| 270 | memset(chIV, 0, sizeof(chIV)); | 502 | OPENSSL_cleanse(chIV, sizeof(chIV)); |
| 271 | return true; | 503 | return true; |
| 272 | } | 504 | } |
| 273 | 505 | ||
| @@ -328,6 +560,8 @@ bool DecryptSecret(const CKeyingMaterial& vchKey, | |||
| 328 | vchPlaintextRet.swap(vchOut); | 560 | vchPlaintextRet.swap(vchOut); |
| 329 | fOk = true; | 561 | fOk = true; |
| 330 | } | 562 | } |
| 563 | if (!fOk) | ||
| 564 | SecureClearBytes(vchOut); | ||
| 331 | EVP_CIPHER_CTX_free(ctx); | 565 | EVP_CIPHER_CTX_free(ctx); |
| 332 | return fOk; | 566 | return fOk; |
| 333 | } | 567 | } |
| @@ -403,6 +637,7 @@ bool GetWalletPrivKey(const vector<unsigned char>& vchPubKey, | |||
| 403 | } | 637 | } |
| 404 | 638 | ||
| 405 | CPrivKey vchPrivKey(vchPlaintext.begin(), vchPlaintext.end()); | 639 | CPrivKey vchPrivKey(vchPlaintext.begin(), vchPlaintext.end()); |
| 640 | SecureClearBytes(vchPlaintext); | ||
| 406 | CKey key; | 641 | CKey key; |
| 407 | if (!key.SetPrivKey(vchPrivKey) || key.GetPubKey() != vchPubKey) | 642 | if (!key.SetPrivKey(vchPrivKey) || key.GetPubKey() != vchPubKey) |
| 408 | { | 643 | { |
| @@ -437,11 +672,20 @@ bool UnlockWallet(const string& strPassphrase, string& strErrorRet) | |||
| 437 | vector<unsigned char> vchMasterPlain; | 672 | vector<unsigned char> vchMasterPlain; |
| 438 | if (!DecryptSecret(vchPassKey, kMasterKey.vchCryptedKey, | 673 | if (!DecryptSecret(vchPassKey, kMasterKey.vchCryptedKey, |
| 439 | vchPassIV, vchMasterPlain)) | 674 | vchPassIV, vchMasterPlain)) |
| 675 | { | ||
| 676 | SecureClearBytes(vchPassIV); | ||
| 440 | continue; | 677 | continue; |
| 678 | } | ||
| 441 | if (vchMasterPlain.size() != 32) | 679 | if (vchMasterPlain.size() != 32) |
| 680 | { | ||
| 681 | SecureClearBytes(vchMasterPlain); | ||
| 682 | SecureClearBytes(vchPassIV); | ||
| 442 | continue; | 683 | continue; |
| 684 | } | ||
| 443 | 685 | ||
| 444 | CKeyingMaterial vchCandidate(vchMasterPlain.begin(), vchMasterPlain.end()); | 686 | CKeyingMaterial vchCandidate(vchMasterPlain.begin(), vchMasterPlain.end()); |
| 687 | SecureClearBytes(vchMasterPlain); | ||
| 688 | SecureClearBytes(vchPassIV); | ||
| 445 | bool fValid = mapCryptedKeys.empty(); | 689 | bool fValid = mapCryptedKeys.empty(); |
| 446 | for (map<vector<unsigned char>, vector<unsigned char> >::iterator mci = mapCryptedKeys.begin(); | 690 | for (map<vector<unsigned char>, vector<unsigned char> >::iterator mci = mapCryptedKeys.begin(); |
| 447 | mci != mapCryptedKeys.end(); ++mci) | 691 | mci != mapCryptedKeys.end(); ++mci) |
| @@ -450,6 +694,7 @@ bool UnlockWallet(const string& strPassphrase, string& strErrorRet) | |||
| 450 | if (!DecryptSecret(vchCandidate, (*mci).second, WalletKeyIV((*mci).first), vchPlaintext)) | 694 | if (!DecryptSecret(vchCandidate, (*mci).second, WalletKeyIV((*mci).first), vchPlaintext)) |
| 451 | break; | 695 | break; |
| 452 | CPrivKey vchPrivKey(vchPlaintext.begin(), vchPlaintext.end()); | 696 | CPrivKey vchPrivKey(vchPlaintext.begin(), vchPlaintext.end()); |
| 697 | SecureClearBytes(vchPlaintext); | ||
| 453 | CKey key; | 698 | CKey key; |
| 454 | if (key.SetPrivKey(vchPrivKey) && key.GetPubKey() == (*mci).first) | 699 | if (key.SetPrivKey(vchPrivKey) && key.GetPubKey() == (*mci).first) |
| 455 | fValid = true; | 700 | fValid = true; |
| @@ -465,10 +710,12 @@ bool UnlockWallet(const string& strPassphrase, string& strErrorRet) | |||
| 465 | WalletSecretIV("hdmaster"), vchPlainHDMaster) || | 710 | WalletSecretIV("hdmaster"), vchPlainHDMaster) || |
| 466 | vchPlainHDMaster.size() != 32) | 711 | vchPlainHDMaster.size() != 32) |
| 467 | { | 712 | { |
| 713 | SecureClearBytes(vchPlainHDMaster); | ||
| 468 | strErrorRet = "could not decrypt HD master key"; | 714 | strErrorRet = "could not decrypt HD master key"; |
| 469 | return false; | 715 | return false; |
| 470 | } | 716 | } |
| 471 | vchHDMaster = vchPlainHDMaster; | 717 | vchHDMaster = vchPlainHDMaster; |
| 718 | SecureClearBytes(vchPlainHDMaster); | ||
| 472 | } | 719 | } |
| 473 | if (!vchCryptedHDChainCode.empty()) | 720 | if (!vchCryptedHDChainCode.empty()) |
| 474 | { | 721 | { |
| @@ -477,10 +724,12 @@ bool UnlockWallet(const string& strPassphrase, string& strErrorRet) | |||
| 477 | WalletSecretIV("hdchaincode"), vchPlainHDChainCode) || | 724 | WalletSecretIV("hdchaincode"), vchPlainHDChainCode) || |
| 478 | vchPlainHDChainCode.size() != 32) | 725 | vchPlainHDChainCode.size() != 32) |
| 479 | { | 726 | { |
| 727 | SecureClearBytes(vchPlainHDChainCode); | ||
| 480 | strErrorRet = "could not decrypt HD chain code"; | 728 | strErrorRet = "could not decrypt HD chain code"; |
| 481 | return false; | 729 | return false; |
| 482 | } | 730 | } |
| 483 | vchHDChainCode = vchPlainHDChainCode; | 731 | vchHDChainCode = vchPlainHDChainCode; |
| 732 | SecureClearBytes(vchPlainHDChainCode); | ||
| 484 | } | 733 | } |
| 485 | fWalletLocked = false; | 734 | fWalletLocked = false; |
| 486 | return true; | 735 | return true; |
| @@ -625,7 +874,7 @@ unsigned int nHDNext = 0; | |||
| 625 | int nHDKeySchema = HD_SCHEMA_NONE; | 874 | int nHDKeySchema = HD_SCHEMA_NONE; |
| 626 | unsigned int nHDReceiveNext = 0; | 875 | unsigned int nHDReceiveNext = 0; |
| 627 | unsigned int nHDChangeNext = 0; | 876 | unsigned int nHDChangeNext = 0; |
| 628 | unsigned int nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL; | 877 | unsigned int nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH; |
| 629 | 878 | ||
| 630 | string HDKeySchemaName(int nSchema) | 879 | string HDKeySchemaName(int nSchema) |
| 631 | { | 880 | { |
| @@ -781,7 +1030,7 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet) | |||
| 781 | 1030 | ||
| 782 | if (!CWalletDB().WriteHDMaster(master.privateKey, master.chainCode) || | 1031 | if (!CWalletDB().WriteHDMaster(master.privateKey, master.chainCode) || |
| 783 | !CWalletDB().WriteHDSchema(HD_SCHEMA_BIP44) || | 1032 | !CWalletDB().WriteHDSchema(HD_SCHEMA_BIP44) || |
| 784 | !CWalletDB().WriteHDCoinType(HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL) || | 1033 | !CWalletDB().WriteHDCoinType(HD_BIP44_COIN_TYPE_BITFLASH) || |
| 785 | !CWalletDB().WriteHDNext(0) || | 1034 | !CWalletDB().WriteHDNext(0) || |
| 786 | !CWalletDB().WriteHDReceiveNext(1) || | 1035 | !CWalletDB().WriteHDReceiveNext(1) || |
| 787 | !CWalletDB().WriteHDChangeNext(0)) | 1036 | !CWalletDB().WriteHDChangeNext(0)) |
| @@ -794,7 +1043,7 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet) | |||
| 794 | nHDKeySchema = HD_SCHEMA_BIP44; | 1043 | nHDKeySchema = HD_SCHEMA_BIP44; |
| 795 | nHDReceiveNext = 1; | 1044 | nHDReceiveNext = 1; |
| 796 | nHDChangeNext = 0; | 1045 | nHDChangeNext = 0; |
| 797 | nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL; | 1046 | nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH; |
| 798 | 1047 | ||
| 799 | // Index 0 is spoken for before it is derived, so a failure below cannot | 1048 | // Index 0 is spoken for before it is derived, so a failure below cannot |
| 800 | // leave the pool free to hand it out as an ordinary key. The cost of | 1049 | // leave the pool free to hand it out as an ordinary key. The cost of |
| @@ -1670,7 +1919,7 @@ void CWalletTx::RelayWalletTransaction(CTxDB& txdb) | |||
| 1670 | uint256 hash = GetHash(); | 1919 | uint256 hash = GetHash(); |
| 1671 | if (!txdb.ContainsTx(hash)) | 1920 | if (!txdb.ContainsTx(hash)) |
| 1672 | { | 1921 | { |
| 1673 | if (LogAcceptsCategory("net")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str()); | 1922 | if (LogAcceptsCategory("relay")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str()); |
| 1674 | RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); | 1923 | RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); |
| 1675 | } | 1924 | } |
| 1676 | } | 1925 | } |
| @@ -2480,11 +2729,21 @@ string GetAppDir() | |||
| 2480 | { | 2729 | { |
| 2481 | return "."; | 2730 | return "."; |
| 2482 | } | 2731 | } |
| 2483 | static bool fMkdirDone; | 2732 | static string strCreatedBaseDir; |
| 2484 | if (!fMkdirDone) | 2733 | if (strCreatedBaseDir != strDir) |
| 2485 | { | 2734 | { |
| 2486 | fMkdirDone = true; | ||
| 2487 | _mkdir(strDir.c_str()); | 2735 | _mkdir(strDir.c_str()); |
| 2736 | strCreatedBaseDir = strDir; | ||
| 2737 | } | ||
| 2738 | if (IsTestNet()) | ||
| 2739 | { | ||
| 2740 | strDir += "/testnet"; | ||
| 2741 | static string strCreatedTestnetDir; | ||
| 2742 | if (strCreatedTestnetDir != strDir) | ||
| 2743 | { | ||
| 2744 | _mkdir(strDir.c_str()); | ||
| 2745 | strCreatedTestnetDir = strDir; | ||
| 2746 | } | ||
| 2488 | } | 2747 | } |
| 2489 | return strDir; | 2748 | return strDir; |
| 2490 | } | 2749 | } |
| @@ -2636,22 +2895,36 @@ bool LoadBlockIndex(bool fAllowNew) | |||
| 2636 | // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) | 2895 | // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) |
| 2637 | // vMerkleTree: 4a5e1e | 2896 | // vMerkleTree: 4a5e1e |
| 2638 | 2897 | ||
| 2639 | // Bitflash genesis block -- own header (fair launch, 2026 stable relaunch) | 2898 | // Bitflash genesis block -- own header (fair launch) |
| 2640 | char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; | 2899 | // |
| 2641 | CTransaction txNew; | 2900 | // Upstream moved the construction into BuildGenesisBlock() so mainnet |
| 2642 | txNew.vin.resize(1); | 2901 | // and the isolated testnet can share it. The mainnet inputs it uses are |
| 2643 | txNew.vout.resize(1); | 2902 | // byte-identical to what this block built inline before the merge -- |
| 2644 | txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector<unsigned char>((unsigned char*)pszTimestamp, (unsigned char*)pszTimestamp + strlen(pszTimestamp)); | 2903 | // timestamp string, nTime, nNonce, scriptPubKey, nBits and nVersion all |
| 2645 | txNew.vout[0].nValue = 50 * COIN; | 2904 | // verified equal against MAINNET_GENESIS_* -- so the mainnet genesis |
| 2646 | txNew.vout[0].scriptPubKey = CScript() << CBigNum("0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704") << OP_CHECKSIG; | 2905 | // hash is unchanged. The warning below is why that was checked rather |
| 2647 | CBlock block; | 2906 | // than assumed, and it still applies to those constants: |
| 2648 | block.vtx.push_back(txNew); | 2907 | // |
| 2649 | block.hashPrevBlock = 0; | 2908 | // Do not change MAINNET_GENESIS_TIMESTAMP, the scriptPubKey, |
| 2650 | block.hashMerkleRoot = block.BuildMerkleTree(); | 2909 | // MAINNET_GENESIS_TIME or MAINNET_GENESIS_NONCE. They are the inputs to |
| 2651 | block.nVersion = 1; | 2910 | // the genesis hash, and the genesis hash is consensus. Changing one on |
| 2652 | block.nTime = 1753315200; // 2026-07-24 00:00:00 UTC (stable relaunch) | 2911 | // its own trips the assert below, which is loud and reverts cleanly; |
| 2653 | block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) | 2912 | // changing one and regenerating GENESIS_NONCE and hashGenesisBlock to |
| 2654 | block.nNonce = GENESIS_NONCE; | 2913 | // match -- which is exactly what the re-mining branch below prints out |
| 2914 | // and invites -- gives a node that starts without complaint, builds its | ||
| 2915 | // own chain from height 0, and rejects every real peer as being on a | ||
| 2916 | // different chain. That one fails silently, and the damage scales with | ||
| 2917 | // how many people run the "fixed" build. | ||
| 2918 | // | ||
| 2919 | // MAINNET_GENESIS_TIME is 1753315200, which is 2025-07-24T00:00:00Z, | ||
| 2920 | // while the headline in MAINNET_GENESIS_TIMESTAMP reads 2026 -- as does | ||
| 2921 | // the comment upstream put on that constant, which is wrong about the | ||
| 2922 | // year. This is a known discrepancy and not a typo in the value: it is | ||
| 2923 | // only consensus-critical as an input to the genesis hash, the block | ||
| 2924 | // hashes to hashGenesisBlock with it, and every node on the network | ||
| 2925 | // agrees. The mismatch is in the launched chain and cannot be corrected | ||
| 2926 | // -- see the warning above before deciding otherwise. | ||
| 2927 | CBlock block = BuildGenesisBlock(); | ||
| 2655 | 2928 | ||
| 2656 | // If the nonce has not been found yet, mine the genesis once (RandomX) | 2929 | // If the nonce has not been found yet, mine the genesis once (RandomX) |
| 2657 | // and print the values to be hardcoded in the constants above. | 2930 | // and print the values to be hardcoded in the constants above. |
| @@ -2676,6 +2949,11 @@ bool LoadBlockIndex(bool fAllowNew) | |||
| 2676 | printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); | 2949 | printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); |
| 2677 | printf("hash = %s\n", block.GetHash().ToString().c_str()); | 2950 | printf("hash = %s\n", block.GetHash().ToString().c_str()); |
| 2678 | printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); | 2951 | printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); |
| 2952 | printf("----------------------------------------\n"); | ||
| 2953 | printf("These constants bring up a NEW chain. Do not paste them\n"); | ||
| 2954 | printf("into a build for this network to make an edited nTime or\n"); | ||
| 2955 | printf("pszTimestamp fit -- a node whose genesis differs starts\n"); | ||
| 2956 | printf("cleanly on a chain of its own and rejects every real peer.\n"); | ||
| 2679 | printf("========================================\n"); | 2957 | printf("========================================\n"); |
| 2680 | } | 2958 | } |
| 2681 | 2959 | ||
| @@ -3221,15 +3499,61 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) | |||
| 3221 | vRecv >> *pblock; | 3499 | vRecv >> *pblock; |
| 3222 | 3500 | ||
| 3223 | //// debug print | 3501 | //// debug print |
| 3224 | if (LogAcceptsCategory("net")) { printf("received block:\n"); pblock->print(); } | 3502 | if (LogAcceptsCategory("relay")) { printf("received block:\n"); pblock->print(); } |
| 3225 | 3503 | ||
| 3226 | CInv inv(MSG_BLOCK, pblock->GetHash()); | 3504 | CInv inv(MSG_BLOCK, pblock->GetHash()); |
| 3227 | pfrom->AddInventoryKnown(inv); | 3505 | pfrom->AddInventoryKnown(inv); |
| 3228 | 3506 | ||
| 3507 | // GetHash() covers the 80-byte header and nothing else, so the hash | ||
| 3508 | // coming out right does not make this the block we asked for. Every | ||
| 3509 | // peer that saw X announced holds X's header, and can answer our | ||
| 3510 | // getdata with that header bolted onto a transaction list of its own | ||
| 3511 | // invention -- the reply still hashes to X. hashMerkleRoot is the only | ||
| 3512 | // thing binding the header to the body, so check it here, before the | ||
| 3513 | // mapBlockFetch entry is touched and before CheckBlock spends a | ||
| 3514 | // RandomX hash on a body already known to be forged. | ||
| 3515 | // | ||
| 3516 | // There is no honest way to arrive here. Peer traffic rides the .btf | ||
| 3517 | // channel, which is XChaCha20-Poly1305, so a body that arrives at all | ||
| 3518 | // arrived intact: a mismatch is the peer's own construction, not line | ||
| 3519 | // noise. Drop the peer and leave the fetch outstanding so the failover | ||
| 3520 | // sweep asks somebody else for the real X, rather than recording X as | ||
| 3521 | // delivered on the strength of a header anyone could copy. | ||
| 3522 | if (pblock->hashMerkleRoot != pblock->BuildMerkleTree()) | ||
| 3523 | { | ||
| 3524 | if (LogAcceptsCategory("net")) | ||
| 3525 | printf("peer %s served a forged body for %s\n", | ||
| 3526 | pfrom->addr.ToString().c_str(), inv.ToString().c_str()); | ||
| 3527 | // One line per connection: ProcessMessages keeps draining the | ||
| 3528 | // receive buffer after fDisconnect is set, so a peer that packs | ||
| 3529 | // many forged bodies into one buffer must not get one log line | ||
| 3530 | // for each of them. | ||
| 3531 | if (!pfrom->fDisconnect) | ||
| 3532 | printf("block fetch: a peer answered the request for %s with a body that is not " | ||
| 3533 | "that block -- dropping it\n", inv.hash.ToString().substr(0,14).c_str()); | ||
| 3534 | pfrom->fDisconnect = true; | ||
| 3535 | return error("ProcessMessage() : forged block body for %s", | ||
| 3536 | inv.hash.ToString().substr(0,14).c_str()); | ||
| 3537 | } | ||
| 3538 | |||
| 3229 | if (ProcessBlock(pfrom, pblock.release())) | 3539 | if (ProcessBlock(pfrom, pblock.release())) |
| 3230 | { | 3540 | { |
| 3231 | mapAlreadyAskedFor.erase(inv); | 3541 | mapAlreadyAskedFor.erase(inv); |
| 3232 | 3542 | ||
| 3543 | // The body arrived and validated, so the fetch really is over: | ||
| 3544 | // the block is now in mapBlockIndex, or parked in mapOrphanBlocks, | ||
| 3545 | // and either way AlreadyHave answers for it from here on. | ||
| 3546 | // | ||
| 3547 | // This has to stay inside the success branch, next to the erase | ||
| 3548 | // above. When validation rejects a body nothing is stored, | ||
| 3549 | // AlreadyHave stays false, and the mapAskFor entry was already | ||
| 3550 | // consumed when the getdata went out -- so erasing the tracking | ||
| 3551 | // here would leave the block requested from nobody at all until | ||
| 3552 | // some peer happened to re-announce it. That is exactly the stall | ||
| 3553 | // of #33, and erasing before ProcessBlock made it reachable on | ||
| 3554 | // demand. | ||
| 3555 | mapBlockFetch.erase(inv.hash); | ||
| 3556 | |||
| 3233 | // A peer that just handed us a block is demonstrably at least at | 3557 | // A peer that just handed us a block is demonstrably at least at |
| 3234 | // that block's height. Without this, nStartingHeight is a snapshot | 3558 | // that block's height. Without this, nStartingHeight is a snapshot |
| 3235 | // taken once during the version handshake and never touched again, | 3559 | // taken once during the version handshake and never touched again, |
| @@ -3354,12 +3678,119 @@ bool SendMessages(CNode* pto) | |||
| 3354 | const CInv& inv = (*pto->mapAskFor.begin()).second; | 3678 | const CInv& inv = (*pto->mapAskFor.begin()).second; |
| 3355 | if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str()); | 3679 | if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str()); |
| 3356 | if (!AlreadyHave(txdb, inv)) | 3680 | if (!AlreadyHave(txdb, inv)) |
| 3681 | { | ||
| 3357 | vAskFor.push_back(inv); | 3682 | vAskFor.push_back(inv); |
| 3683 | // Blocks get their delivery tracked so the failover pass | ||
| 3684 | // below can chase the ones that go quiet. | ||
| 3685 | if (inv.type == MSG_BLOCK) | ||
| 3686 | { | ||
| 3687 | // Full, and this is a hash we are not already tracking: | ||
| 3688 | // make room rather than drop it on the floor, or one peer | ||
| 3689 | // announcing eight hashes turns failover off for everyone. | ||
| 3690 | if (!mapBlockFetch.count(inv.hash) && | ||
| 3691 | mapBlockFetch.size() >= BLOCK_FETCH_TRACK_MAX) | ||
| 3692 | MakeRoomInBlockFetch(pto->addr); | ||
| 3693 | |||
| 3694 | if (mapBlockFetch.count(inv.hash) || | ||
| 3695 | mapBlockFetch.size() < BLOCK_FETCH_TRACK_MAX) | ||
| 3696 | { | ||
| 3697 | CBlockFetch& fetch = mapBlockFetch[inv.hash]; | ||
| 3698 | if (fetch.nTimeFirstAsked == 0) | ||
| 3699 | { | ||
| 3700 | fetch.nTimeFirstAsked = GetTime(); | ||
| 3701 | // Who is answerable for this hash existing. Set | ||
| 3702 | // once, on creation: the failover sweep asks other | ||
| 3703 | // peers later, and none of them chose to announce | ||
| 3704 | // it, so none of them should be charged for it. | ||
| 3705 | fetch.addrAnnounced = pto->addr; | ||
| 3706 | } | ||
| 3707 | fetch.nTimeAsked = GetTime(); | ||
| 3708 | fetch.setAsked.insert(pto->addr); | ||
| 3709 | } | ||
| 3710 | } | ||
| 3711 | } | ||
| 3358 | pto->mapAskFor.erase(pto->mapAskFor.begin()); | 3712 | pto->mapAskFor.erase(pto->mapAskFor.begin()); |
| 3359 | } | 3713 | } |
| 3360 | if (!vAskFor.empty()) | 3714 | if (!vAskFor.empty()) |
| 3361 | pto->PushMessage("getdata", vAskFor); | 3715 | pto->PushMessage("getdata", vAskFor); |
| 3362 | 3716 | ||
| 3717 | |||
| 3718 | // | ||
| 3719 | // Message: getdata (block fetch failover) | ||
| 3720 | // | ||
| 3721 | // A requested block that has not arrived in BLOCK_FETCH_RETRY_SECS is | ||
| 3722 | // not still in transit -- a body is at most 1 MB and somebody | ||
| 3723 | // demonstrably has it -- so ask somebody else instead of waiting on | ||
| 3724 | // whoever was asked first (#33). Each peer the message handler visits | ||
| 3725 | // takes at most one turn per block per round; once every connected | ||
| 3726 | // peer has had its turn and a further interval passes with nobody | ||
| 3727 | // new to ask, the round restarts. A peer that declared itself a | ||
| 3728 | // client cannot serve block bodies and is no use as a target. | ||
| 3729 | if (!pto->fClient) | ||
| 3730 | { | ||
| 3731 | vector<CInv> vFailover; | ||
| 3732 | int64 nNowSecs = GetTime(); | ||
| 3733 | for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end();) | ||
| 3734 | { | ||
| 3735 | CBlockFetch& fetch = (*mi).second; | ||
| 3736 | CInv inv(MSG_BLOCK, (*mi).first); | ||
| 3737 | if (AlreadyHave(txdb, inv)) | ||
| 3738 | { | ||
| 3739 | // Arrived by some other road -- our own miner, an orphan | ||
| 3740 | // parked in mapOrphanBlocks. Nothing left to chase. | ||
| 3741 | mapBlockFetch.erase(mi++); | ||
| 3742 | continue; | ||
| 3743 | } | ||
| 3744 | if (nNowSecs - fetch.nTimeFirstAsked > BLOCK_FETCH_GIVEUP_SECS) | ||
| 3745 | { | ||
| 3746 | printf("block fetch: giving up on %s -- no peer served it in %d s\n", | ||
| 3747 | inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeFirstAsked)); | ||
| 3748 | mapBlockFetch.erase(mi++); | ||
| 3749 | continue; | ||
| 3750 | } | ||
| 3751 | if (nNowSecs - fetch.nTimeAsked >= BLOCK_FETCH_RETRY_SECS) | ||
| 3752 | { | ||
| 3753 | // The handler loop visits every peer within ~100ms, so a | ||
| 3754 | // request stale by two full intervals means nobody outside | ||
| 3755 | // setAsked was there to ask. Start the round over. | ||
| 3756 | if (nNowSecs - fetch.nTimeAsked >= 2 * BLOCK_FETCH_RETRY_SECS) | ||
| 3757 | fetch.setAsked.clear(); | ||
| 3758 | if (!fetch.setAsked.count(pto->addr)) | ||
| 3759 | { | ||
| 3760 | // Which block, how long, and which peer we are trying | ||
| 3761 | // next. Behind the category because it names a peer | ||
| 3762 | // and a peer decides how often it is written. | ||
| 3763 | if (LogAcceptsCategory("net")) | ||
| 3764 | printf("block fetch failover: %s unanswered for %d s, re-requesting from %s\n", | ||
| 3765 | inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeAsked), | ||
| 3766 | pto->addr.ToString().c_str()); | ||
| 3767 | |||
| 3768 | // The unconditional half: no addresses, and at most | ||
| 3769 | // one line per BLOCK_FETCH_REPORT_SECS however hard a | ||
| 3770 | // peer tries. See the counters for the reasoning. | ||
| 3771 | nBlockFetchFailovers++; | ||
| 3772 | if (nNowSecs - nBlockFetchWindowStart >= BLOCK_FETCH_REPORT_SECS) | ||
| 3773 | { | ||
| 3774 | printf("block fetch: %d block request%s went unanswered and %s been " | ||
| 3775 | "re-asked of other peers (%d tracked)\n", | ||
| 3776 | nBlockFetchFailovers, nBlockFetchFailovers == 1 ? "" : "s", | ||
| 3777 | nBlockFetchFailovers == 1 ? "has" : "have", | ||
| 3778 | (int)mapBlockFetch.size()); | ||
| 3779 | nBlockFetchWindowStart = nNowSecs; | ||
| 3780 | nBlockFetchFailovers = 0; | ||
| 3781 | } | ||
| 3782 | |||
| 3783 | vFailover.push_back(inv); | ||
| 3784 | fetch.nTimeAsked = nNowSecs; | ||
| 3785 | fetch.setAsked.insert(pto->addr); | ||
| 3786 | } | ||
| 3787 | } | ||
| 3788 | ++mi; | ||
| 3789 | } | ||
| 3790 | if (!vFailover.empty()) | ||
| 3791 | pto->PushMessage("getdata", vFailover); | ||
| 3792 | } | ||
| 3793 | |||
| 3363 | } | 3794 | } |
| 3364 | return true; | 3795 | return true; |
| 3365 | } | 3796 | } |
| @@ -3620,6 +4051,14 @@ static void ParticipantHashWorker(ParticipantJobShared* sh, int idx, int nWorker | |||
| 3620 | RandomXDestroyMinerVM(vm); | 4051 | RandomXDestroyMinerVM(vm); |
| 3621 | } | 4052 | } |
| 3622 | 4053 | ||
| 4054 | // Run one participant session: resolve the pool, open the tunnel, subscribe, | ||
| 4055 | // hash, submit shares. Returns true only if the pool actually handed out work | ||
| 4056 | // -- at least one well-formed mining.notify arrived. Everything else is false, | ||
| 4057 | // including a tunnel that opened and was dropped before any job. | ||
| 4058 | // | ||
| 4059 | // The caller's reconnect backoff keys off this return value, so any path added | ||
| 4060 | // here that leaves the session without work MUST return false. "The session | ||
| 4061 | // ended tidily" is not the question being asked. | ||
| 3623 | static bool PoolParticipantMiner() | 4062 | static bool PoolParticipantMiner() |
| 3624 | { | 4063 | { |
| 3625 | // ------------------------------------------------------------------ | 4064 | // ------------------------------------------------------------------ |
| @@ -3697,6 +4136,33 @@ static bool PoolParticipantMiner() | |||
| 3697 | } | 4136 | } |
| 3698 | LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); | 4137 | LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); |
| 3699 | 4138 | ||
| 4139 | // The descriptor names ONE meeting node, but the pool parks a | ||
| 4140 | // registration at every relay it knows (one accept thread per relay, | ||
| 4141 | // rpc.cpp:AcceptOneFn), so any seed relay can pair us. Insisting on the | ||
| 4142 | // advertised entry forever turns one bad path into a miner that idles | ||
| 4143 | // indefinitely while a working path exists (issue #17); after a few | ||
| 4144 | // consecutive tunnel failures, rotate the seed relays into the attempts | ||
| 4145 | // too. Only ONE participant session runs (StartMinerThreads), so these | ||
| 4146 | // statics are single-threaded. | ||
| 4147 | static const int TUNNEL_FAILOVER_AFTER = 3; | ||
| 4148 | static int nTunnelFails = 0; // consecutive tunnel failures, this pool | ||
| 4149 | static size_t iFailoverRelay = 0; // rotation cursor over vBtfMeetingRelays | ||
| 4150 | static std::string strFailPool; // pool the counters belong to | ||
| 4151 | if (strFailPool != strParticipantPool) { | ||
| 4152 | strFailPool = strParticipantPool; | ||
| 4153 | nTunnelFails = 0; | ||
| 4154 | iFailoverRelay = 0; | ||
| 4155 | } | ||
| 4156 | if (nTunnelFails >= TUNNEL_FAILOVER_AFTER && !vBtfMeetingRelays.empty()) { | ||
| 4157 | std::string alt = vBtfMeetingRelays[iFailoverRelay++ % vBtfMeetingRelays.size()]; | ||
| 4158 | if (alt != meetingHostPort) { | ||
| 4159 | LogPrint("worker", "[worker] advertised meeting node %s failed %d" | ||
| 4160 | " times in a row -- trying seed relay %s instead\n", | ||
| 4161 | meetingHostPort.c_str(), nTunnelFails, alt.c_str()); | ||
| 4162 | meetingHostPort = alt; | ||
| 4163 | } | ||
| 4164 | } | ||
| 4165 | |||
| 3700 | size_t colon = meetingHostPort.rfind(':'); | 4166 | size_t colon = meetingHostPort.rfind(':'); |
| 3701 | if (colon == std::string::npos) { | 4167 | if (colon == std::string::npos) { |
| 3702 | LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); | 4168 | LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); |
| @@ -3716,15 +4182,18 @@ static bool PoolParticipantMiner() | |||
| 3716 | // Step 2b: open encrypted tunnel to pool | 4182 | // Step 2b: open encrypted tunnel to pool |
| 3717 | // ------------------------------------------------------------------ | 4183 | // ------------------------------------------------------------------ |
| 3718 | SetParticipantMiningStatus("opening tunnel"); | 4184 | SetParticipantMiningStatus("opening tunnel"); |
| 4185 | btf::TunnelFail tunnelFail = btf::TUNNEL_FAIL_NONE; | ||
| 3719 | s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, | 4186 | s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, |
| 3720 | target_pubkey, service_enc_pub); | 4187 | target_pubkey, service_enc_pub, &tunnelFail); |
| 3721 | if (s == INVALID_SOCKET) { | 4188 | if (s == INVALID_SOCKET) { |
| 3722 | LogPrint("worker", "[worker] tunnel connect FAILED via %s" | 4189 | nTunnelFails++; |
| 3723 | " (relay unreachable or pool not listening)\n", | 4190 | LogPrint("worker", "[worker] tunnel connect FAILED via %s: %s\n", |
| 3724 | meetingHostPort.c_str()); | 4191 | meetingHostPort.c_str(), btf::TunnelFailText(tunnelFail)); |
| 3725 | SetParticipantMiningStatus("tunnel connect failed"); | 4192 | SetParticipantMiningStatus(std::string("tunnel connect failed: ") |
| 4193 | + btf::TunnelFailText(tunnelFail)); | ||
| 3726 | return false; | 4194 | return false; |
| 3727 | } | 4195 | } |
| 4196 | nTunnelFails = 0; | ||
| 3728 | LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); | 4197 | LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); |
| 3729 | SetParticipantMiningStatus("tunnel open"); | 4198 | SetParticipantMiningStatus("tunnel open"); |
| 3730 | } | 4199 | } |
| @@ -3857,9 +4326,32 @@ connected: | |||
| 3857 | std::vector<ParticipantShareHit> shareHits; | 4326 | std::vector<ParticipantShareHit> shareHits; |
| 3858 | std::mutex shareHitsMtx; | 4327 | std::mutex shareHitsMtx; |
| 3859 | std::vector<std::thread> hashThreads; | 4328 | std::vector<std::thread> hashThreads; |
| 3860 | for (int i = 0; i < nWorkers; i++) | 4329 | try |
| 3861 | hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers, | 4330 | { |
| 3862 | &shareHits, &shareHitsMtx); | 4331 | for (int i = 0; i < nWorkers; i++) |
| 4332 | hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers, | ||
| 4333 | &shareHits, &shareHitsMtx); | ||
| 4334 | } | ||
| 4335 | catch (const std::system_error& e) | ||
| 4336 | { | ||
| 4337 | // A refused pthread_create used to reach std::terminate here and | ||
| 4338 | // kill the worker mid-session -- observed twice on an otherwise | ||
| 4339 | // healthy participant. Stop the workers that did start the same way | ||
| 4340 | // the disconnect path below does, drop this pool connection, and let | ||
| 4341 | // the caller's reconnect loop try again once the pressure clears. | ||
| 4342 | LogPrint("worker", "[worker] cannot spawn hash threads (%s)" | ||
| 4343 | " -- dropping pool connection\n", e.what()); | ||
| 4344 | SetParticipantMiningStatus("hash thread spawn failed"); | ||
| 4345 | { | ||
| 4346 | std::lock_guard<std::mutex> lk(jobShared.mtx); | ||
| 4347 | jobShared.stop = true; | ||
| 4348 | jobShared.haveJob = false; | ||
| 4349 | } | ||
| 4350 | for (std::thread& t : hashThreads) | ||
| 4351 | t.join(); | ||
| 4352 | closesocket(s); | ||
| 4353 | return false; | ||
| 4354 | } | ||
| 3863 | LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n", | 4355 | LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n", |
| 3864 | nWorkers); | 4356 | nWorkers); |
| 3865 | const uint64 sessionStartHashes = gParticipantHashes.load(); | 4357 | const uint64 sessionStartHashes = gParticipantHashes.load(); |
| @@ -3870,8 +4362,17 @@ connected: | |||
| 3870 | std::string jobId; | 4362 | std::string jobId; |
| 3871 | unsigned char header[80] = {}; | 4363 | unsigned char header[80] = {}; |
| 3872 | uint256 target; | 4364 | uint256 target; |
| 4365 | // Cached answer to "which block is this job built on", for the | ||
| 4366 | // mining.notify handler below -- see the comment there. Session-local, so | ||
| 4367 | // a reconnect re-resolves rather than trusting an answer obtained before | ||
| 4368 | // a possible change of pool. | ||
| 4369 | uint256 hashPrevResolved; // prevhash the cached answer describes | ||
| 4370 | bool fPrevResolved = false; // whether that answer exists yet | ||
| 4371 | int nPrevHeight = -1; // its height, or -1 == unknown to us | ||
| 4372 | int nBestHeightResolved = -1; // nBestHeight when we last resolved | ||
| 3873 | double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool | 4373 | double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool |
| 3874 | bool haveJob = false; | 4374 | bool haveJob = false; |
| 4375 | bool gotAnyJob = false; // any job at all this session -- the return value | ||
| 3875 | bool gotSubscribeAck = false; | 4376 | bool gotSubscribeAck = false; |
| 3876 | bool gotAuthorizeAck = false; | 4377 | bool gotAuthorizeAck = false; |
| 3877 | int64 authWaitStart = 0; | 4378 | int64 authWaitStart = 0; |
| @@ -3988,8 +4489,9 @@ connected: | |||
| 3988 | jobId = newJobId; | 4489 | jobId = newJobId; |
| 3989 | memcpy(header, hdrBytes.data(), 80); | 4490 | memcpy(header, hdrBytes.data(), 80); |
| 3990 | memcpy(&target, tgtBytes.data(), 32); | 4491 | memcpy(&target, tgtBytes.data(), 32); |
| 3991 | haveJob = true; | 4492 | haveJob = true; |
| 3992 | nNonce = 1; | 4493 | gotAnyJob = true; |
| 4494 | nNonce = 1; | ||
| 3993 | { | 4495 | { |
| 3994 | std::lock_guard<std::mutex> lk(jobShared.mtx); | 4496 | std::lock_guard<std::mutex> lk(jobShared.mtx); |
| 3995 | memcpy(jobShared.header, header, 80); | 4497 | memcpy(jobShared.header, header, 80); |
| @@ -3998,8 +4500,64 @@ connected: | |||
| 3998 | jobShared.jobSeq++; | 4500 | jobShared.jobSeq++; |
| 3999 | jobShared.haveJob = true; | 4501 | jobShared.haveJob = true; |
| 4000 | } | 4502 | } |
| 4001 | LogPrint("worker", "[pool->worker] new job %s received\n", | 4503 | // Log the chain position the pool is |
| 4002 | jobId.c_str()); | 4504 | // building on, so a stale or private |
| 4505 | // fork is auditable from worker logs. | ||
| 4506 | // The prevhash comes from the header we | ||
| 4507 | // actually hash (== p[2] of the notify); | ||
| 4508 | // a brief cs_main lookup is safe here -- | ||
| 4509 | // this thread holds no other lock. | ||
| 4510 | // | ||
| 4511 | // Resolving it is the expensive half, and | ||
| 4512 | // this is the socket-reading thread: block | ||
| 4513 | // validation can hold cs_main for a long | ||
| 4514 | // time, and every moment spent waiting on | ||
| 4515 | // it here is a moment set_difficulty and | ||
| 4516 | // share responses are not being handled. | ||
| 4517 | // Most notifies in a round are a new job | ||
| 4518 | // on the SAME tip, so the answer is cached | ||
| 4519 | // and re-resolved only when it could come | ||
| 4520 | // out differently: a prevhash we have not | ||
| 4521 | // resolved, or one we could NOT resolve | ||
| 4522 | // last time while the chain has since | ||
| 4523 | // moved (unknown -> known). A block's | ||
| 4524 | // height is fixed once mapBlockIndex knows | ||
| 4525 | // it, so a cached "known" never goes | ||
| 4526 | // stale. nBestHeight is read unlocked on | ||
| 4527 | // purpose -- it only decides whether to | ||
| 4528 | // repeat a lookup, never what gets | ||
| 4529 | // printed, so a stale read costs at most | ||
| 4530 | // one deferred or one extra lookup. | ||
| 4531 | // The line still prints in full for every | ||
| 4532 | // job, cached or not. | ||
| 4533 | uint256 hashPrev; | ||
| 4534 | memcpy(&hashPrev, header + 4, 32); | ||
| 4535 | if (LogAcceptsCategory("worker")) | ||
| 4536 | { | ||
| 4537 | int nBestHeightNow = nBestHeight; | ||
| 4538 | if (!fPrevResolved | ||
| 4539 | || hashPrev != hashPrevResolved | ||
| 4540 | || (nPrevHeight < 0 && nBestHeightNow != nBestHeightResolved)) | ||
| 4541 | { | ||
| 4542 | nPrevHeight = -1; | ||
| 4543 | CRITICAL_BLOCK(cs_main) | ||
| 4544 | { | ||
| 4545 | map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrev); | ||
| 4546 | if (mi != mapBlockIndex.end()) | ||
| 4547 | nPrevHeight = (*mi).second->nHeight; | ||
| 4548 | } | ||
| 4549 | hashPrevResolved = hashPrev; | ||
| 4550 | nBestHeightResolved = nBestHeightNow; | ||
| 4551 | fPrevResolved = true; | ||
| 4552 | } | ||
| 4553 | if (nPrevHeight >= 0) | ||
| 4554 | LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (known, job height %d)\n", | ||
| 4555 | jobId.c_str(), hashPrev.GetHex().c_str(), | ||
| 4556 | nPrevHeight + 1); | ||
| 4557 | else | ||
| 4558 | LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (unknown to our chain)\n", | ||
| 4559 | jobId.c_str(), hashPrev.GetHex().c_str()); | ||
| 4560 | } | ||
| 4003 | SetParticipantMiningStatus("hashing job " + jobId); | 4561 | SetParticipantMiningStatus("hashing job " + jobId); |
| 4004 | } | 4562 | } |
| 4005 | } | 4563 | } |
| @@ -4125,9 +4683,197 @@ connected: | |||
| 4125 | (unsigned long long)hashesSinceSample, | 4683 | (unsigned long long)hashesSinceSample, |
| 4126 | (unsigned long long)sessionSharesSent, | 4684 | (unsigned long long)sessionSharesSent, |
| 4127 | (unsigned long long)sessionSharesAccepted); | 4685 | (unsigned long long)sessionSharesAccepted); |
| 4686 | // Not "we got here", which is true of a tunnel the pool accepted and then | ||
| 4687 | // dropped without ever sending a job -- exactly the case the caller's | ||
| 4688 | // backoff exists for. Reaching the end of the session is not evidence the | ||
| 4689 | // session was worth anything; a job is. | ||
| 4690 | return gotAnyJob; | ||
| 4691 | } | ||
| 4692 | |||
| 4693 | // --------------------------------------------------------------------------- | ||
| 4694 | // Local Stratum bridge -- lets external miners speak plain Stratum to | ||
| 4695 | // 127.0.0.1 while Bitflash opens the .btf tunnel to the selected pool. | ||
| 4696 | // --------------------------------------------------------------------------- | ||
| 4697 | |||
| 4698 | struct StratumBridgeClientCtx | ||
| 4699 | { | ||
| 4700 | SOCKET miner; | ||
| 4701 | std::string pool; | ||
| 4702 | }; | ||
| 4703 | |||
| 4704 | static bool BridgeSendAll(SOCKET s, const char* p, int n) | ||
| 4705 | { | ||
| 4706 | int off = 0; | ||
| 4707 | while (off < n) | ||
| 4708 | { | ||
| 4709 | int r = send(s, p + off, n - off, 0); | ||
| 4710 | if (r <= 0) | ||
| 4711 | return false; | ||
| 4712 | off += r; | ||
| 4713 | } | ||
| 4128 | return true; | 4714 | return true; |
| 4129 | } | 4715 | } |
| 4130 | 4716 | ||
| 4717 | static void BridgePump(SOCKET a, SOCKET b) | ||
| 4718 | { | ||
| 4719 | char buf[8192]; | ||
| 4720 | while (!fShutdown) | ||
| 4721 | { | ||
| 4722 | fd_set rd; | ||
| 4723 | FD_ZERO(&rd); | ||
| 4724 | FD_SET(a, &rd); | ||
| 4725 | FD_SET(b, &rd); | ||
| 4726 | SOCKET maxfd = a > b ? a : b; | ||
| 4727 | struct timeval tv = {1, 0}; | ||
| 4728 | int r = select((int)maxfd + 1, &rd, NULL, NULL, &tv); | ||
| 4729 | if (r <= 0) | ||
| 4730 | continue; | ||
| 4731 | |||
| 4732 | if (FD_ISSET(a, &rd)) | ||
| 4733 | { | ||
| 4734 | int n = recv(a, buf, sizeof(buf), 0); | ||
| 4735 | if (n <= 0 || !BridgeSendAll(b, buf, n)) | ||
| 4736 | break; | ||
| 4737 | } | ||
| 4738 | if (FD_ISSET(b, &rd)) | ||
| 4739 | { | ||
| 4740 | int n = recv(b, buf, sizeof(buf), 0); | ||
| 4741 | if (n <= 0 || !BridgeSendAll(a, buf, n)) | ||
| 4742 | break; | ||
| 4743 | } | ||
| 4744 | } | ||
| 4745 | } | ||
| 4746 | |||
| 4747 | static void ThreadStratumBridgeClient(void* arg) | ||
| 4748 | { | ||
| 4749 | StratumBridgeClientCtx* ctx = (StratumBridgeClientCtx*)arg; | ||
| 4750 | SOCKET miner = ctx->miner; | ||
| 4751 | std::string pool = ctx->pool; | ||
| 4752 | delete ctx; | ||
| 4753 | |||
| 4754 | unsigned char target_pubkey[32]; | ||
| 4755 | if (!btf::ParseAddress(pool, target_pubkey)) | ||
| 4756 | { | ||
| 4757 | LogPrint("worker", "[bridge] invalid pool .btf address: %s\n", pool.c_str()); | ||
| 4758 | BtfCloseSocket(miner); | ||
| 4759 | return; | ||
| 4760 | } | ||
| 4761 | |||
| 4762 | std::string meetingHostPort; | ||
| 4763 | unsigned char service_enc_pub[32]; | ||
| 4764 | if (!BtfResolve(pool, meetingHostPort, service_enc_pub)) | ||
| 4765 | { | ||
| 4766 | LogPrint("worker", "[bridge] failed to resolve pool %s\n", pool.c_str()); | ||
| 4767 | BtfCloseSocket(miner); | ||
| 4768 | return; | ||
| 4769 | } | ||
| 4770 | |||
| 4771 | size_t colon = meetingHostPort.rfind(':'); | ||
| 4772 | if (colon == std::string::npos) | ||
| 4773 | { | ||
| 4774 | LogPrint("worker", "[bridge] bad pool endpoint '%s'\n", meetingHostPort.c_str()); | ||
| 4775 | BtfCloseSocket(miner); | ||
| 4776 | return; | ||
| 4777 | } | ||
| 4778 | std::string host = meetingHostPort.substr(0, colon); | ||
| 4779 | int port = atoi(meetingHostPort.substr(colon + 1).c_str()); | ||
| 4780 | if (port <= 0 || port > 65535) | ||
| 4781 | { | ||
| 4782 | LogPrint("worker", "[bridge] bad pool port in '%s'\n", meetingHostPort.c_str()); | ||
| 4783 | BtfCloseSocket(miner); | ||
| 4784 | return; | ||
| 4785 | } | ||
| 4786 | |||
| 4787 | SOCKET poolSock = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, | ||
| 4788 | target_pubkey, service_enc_pub); | ||
| 4789 | if (poolSock == INVALID_SOCKET) | ||
| 4790 | { | ||
| 4791 | LogPrint("worker", "[bridge] tunnel connect failed for pool %s via %s\n", | ||
| 4792 | pool.c_str(), meetingHostPort.c_str()); | ||
| 4793 | BtfCloseSocket(miner); | ||
| 4794 | return; | ||
| 4795 | } | ||
| 4796 | |||
| 4797 | LogPrint("worker", "[bridge] miner connected to pool %s via %s\n", | ||
| 4798 | pool.c_str(), meetingHostPort.c_str()); | ||
| 4799 | BridgePump(miner, poolSock); | ||
| 4800 | BtfCloseSocket(poolSock); | ||
| 4801 | BtfCloseSocket(miner); | ||
| 4802 | LogPrint("worker", "[bridge] miner disconnected from pool %s\n", pool.c_str()); | ||
| 4803 | } | ||
| 4804 | |||
| 4805 | void ThreadStratumBridge(void*) | ||
| 4806 | { | ||
| 4807 | if (strParticipantPool.empty()) | ||
| 4808 | { | ||
| 4809 | printf("Stratum bridge: no pool selected. Use /stratumbridge=POOL_BTF_ADDRESS\n"); | ||
| 4810 | return; | ||
| 4811 | } | ||
| 4812 | |||
| 4813 | SOCKET listener = BtfSocketTag(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), | ||
| 4814 | SOCK_STRATUM_BRIDGE); | ||
| 4815 | if (listener == INVALID_SOCKET) | ||
| 4816 | { | ||
| 4817 | printf("Stratum bridge: could not create listen socket\n"); | ||
| 4818 | return; | ||
| 4819 | } | ||
| 4820 | |||
| 4821 | int yes = 1; | ||
| 4822 | setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)); | ||
| 4823 | |||
| 4824 | sockaddr_in addr; | ||
| 4825 | memset(&addr, 0, sizeof(addr)); | ||
| 4826 | addr.sin_family = AF_INET; | ||
| 4827 | addr.sin_addr.s_addr = htonl(0x7f000001); | ||
| 4828 | addr.sin_port = htons((unsigned short)nStratumBridgePort); | ||
| 4829 | |||
| 4830 | if (bind(listener, (sockaddr*)&addr, sizeof(addr)) != 0 || | ||
| 4831 | listen(listener, 16) != 0) | ||
| 4832 | { | ||
| 4833 | printf("Stratum bridge: could not listen on 127.0.0.1:%d\n", | ||
| 4834 | nStratumBridgePort); | ||
| 4835 | BtfCloseSocket(listener); | ||
| 4836 | return; | ||
| 4837 | } | ||
| 4838 | |||
| 4839 | printf("Stratum bridge: listening on 127.0.0.1:%d for pool %s\n", | ||
| 4840 | nStratumBridgePort, strParticipantPool.c_str()); | ||
| 4841 | |||
| 4842 | while (!fShutdown) | ||
| 4843 | { | ||
| 4844 | fd_set rd; | ||
| 4845 | FD_ZERO(&rd); | ||
| 4846 | FD_SET(listener, &rd); | ||
| 4847 | struct timeval tv = {1, 0}; | ||
| 4848 | if (select((int)listener + 1, &rd, NULL, NULL, &tv) <= 0) | ||
| 4849 | continue; | ||
| 4850 | |||
| 4851 | sockaddr_in cli; | ||
| 4852 | #ifdef _WIN32 | ||
| 4853 | int len = sizeof(cli); | ||
| 4854 | #else | ||
| 4855 | socklen_t len = sizeof(cli); | ||
| 4856 | #endif | ||
| 4857 | SOCKET miner = BtfSocketTag(accept(listener, (sockaddr*)&cli, &len), | ||
| 4858 | SOCK_STRATUM_BRIDGE); | ||
| 4859 | if (miner == INVALID_SOCKET) | ||
| 4860 | continue; | ||
| 4861 | |||
| 4862 | StratumBridgeClientCtx* ctx = new StratumBridgeClientCtx(); | ||
| 4863 | ctx->miner = miner; | ||
| 4864 | ctx->pool = strParticipantPool; | ||
| 4865 | if (_beginthread(ThreadStratumBridgeClient, 0, ctx) == (uintptr_t)-1) | ||
| 4866 | { | ||
| 4867 | delete ctx; | ||
| 4868 | BtfCloseSocket(miner); | ||
| 4869 | LogPrint("worker", "[bridge] failed to spawn client thread\n"); | ||
| 4870 | } | ||
| 4871 | } | ||
| 4872 | |||
| 4873 | BtfCloseSocket(listener); | ||
| 4874 | printf("Stratum bridge: stopped\n"); | ||
| 4875 | } | ||
| 4876 | |||
| 4131 | bool BitcoinMiner(int nThreadId) | 4877 | bool BitcoinMiner(int nThreadId) |
| 4132 | { | 4878 | { |
| 4133 | // Relay mode: never mines, just relays/syncs. (Shouldn't normally get here | 4879 | // Relay mode: never mines, just relays/syncs. (Shouldn't normally get here |
| @@ -4138,11 +4884,51 @@ bool BitcoinMiner(int nThreadId) | |||
| 4138 | // Participant mode: connect to external pool instead of mining solo/operator | 4884 | // Participant mode: connect to external pool instead of mining solo/operator |
| 4139 | if (nMineMode == MINE_PARTICIPANT) | 4885 | if (nMineMode == MINE_PARTICIPANT) |
| 4140 | { | 4886 | { |
| 4887 | // An attempt that never got work backs off (10s doubling to 120s) | ||
| 4888 | // instead of hammering the same path every flat 10s, and a streak | ||
| 4889 | // lasting minutes is said at printf level: the identical once-per-10s | ||
| 4890 | // LogPrint line is how a worker idled all night unnoticed (issue #17). | ||
| 4891 | // | ||
| 4892 | // Only a session the pool actually sent a job to resets either. The | ||
| 4893 | // weaker test -- "the session returned" -- was gameable: a pool that | ||
| 4894 | // completes the tunnel and drops without ever sending work leaves by | ||
| 4895 | // the same tidy cleanup path a healthy one does, which would pin the | ||
| 4896 | // retry at 10s forever and defeat the backoff in exactly the scenario | ||
| 4897 | // it exists for. | ||
| 4898 | int nRetrySecs = 10; | ||
| 4899 | int64 nFailingSince = 0; | ||
| 4141 | while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { | 4900 | while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { |
| 4142 | PoolParticipantMiner(); | 4901 | bool fGotWork = PoolParticipantMiner(); |
| 4143 | if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { | 4902 | if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { |
| 4144 | LogPrint("worker", "PoolParticipantMiner: reconnecting in 10s\n"); | 4903 | if (fGotWork) { |
| 4145 | for (int i = 0; i < 10 && !fShutdown; i++) Sleep(1000); | 4904 | nRetrySecs = 10; |
| 4905 | nFailingSince = 0; | ||
| 4906 | } else { | ||
| 4907 | if (nFailingSince == 0) | ||
| 4908 | nFailingSince = GetTime(); | ||
| 4909 | int64 nMins = (GetTime() - nFailingSince) / 60; | ||
| 4910 | if (nMins >= 1) | ||
| 4911 | printf("[worker] connection to pool %s has been failing" | ||
| 4912 | " for %lld minutes\n", | ||
| 4913 | strParticipantPool.c_str(), (long long)nMins); | ||
| 4914 | } | ||
| 4915 | LogPrint("worker", "PoolParticipantMiner: reconnecting in %ds\n", | ||
| 4916 | nRetrySecs); | ||
| 4917 | // Poll everything the enclosing while polls, not just | ||
| 4918 | // fShutdown. Raising the ceiling to 120s made a | ||
| 4919 | // shutdown-only predicate expensive: after the operator | ||
| 4920 | // clears Generate or switches to Relay, the session -- and | ||
| 4921 | // the vfThreadRunning[3] slot that gates restarting it -- | ||
| 4922 | // would stay held for up to two minutes. Measured 119s | ||
| 4923 | // before, 1s after. | ||
| 4924 | for (int i = 0; i < nRetrySecs && !fShutdown && fGenerateBitcoins | ||
| 4925 | && nMineMode == MINE_PARTICIPANT; i++) | ||
| 4926 | Sleep(1000); | ||
| 4927 | if (!fGotWork && nRetrySecs < 120) { | ||
| 4928 | nRetrySecs *= 2; | ||
| 4929 | if (nRetrySecs > 120) | ||
| 4930 | nRetrySecs = 120; | ||
| 4931 | } | ||
| 4146 | } | 4932 | } |
| 4147 | } | 4933 | } |
| 4148 | return true; | 4934 | return true; |
| @@ -4244,7 +5030,6 @@ bool BitcoinMiner(int nThreadId) | |||
| 4244 | map<uint256, CTxIndex> mapTestPool; | 5030 | map<uint256, CTxIndex> mapTestPool; |
| 4245 | vector<char> vfAlreadyAdded(mapTransactions.size()); | 5031 | vector<char> vfAlreadyAdded(mapTransactions.size()); |
| 4246 | bool fFoundSomething = true; | 5032 | bool fFoundSomething = true; |
| 4247 | unsigned int nBlockSize = 0; | ||
| 4248 | // Assembly must stop below the consensus cap that CheckBlock() | 5033 | // Assembly must stop below the consensus cap that CheckBlock() |
| 4249 | // enforces, or we mine a block our own node then rejects. | 5034 | // enforces, or we mine a block our own node then rejects. |
| 4250 | // | 5035 | // |
| @@ -4255,9 +5040,30 @@ bool BitcoinMiner(int nThreadId) | |||
| 4255 | // meant solving a block and throwing it away -- with this pool at | 5040 | // meant solving a block and throwing it away -- with this pool at |
| 4256 | // ~90% of hashrate, that is the network stalling, not just us. | 5041 | // ~90% of hashrate, that is the network stalling, not just us. |
| 4257 | // | 5042 | // |
| 4258 | // Half the cap, keeping the original margin: the test runs before | 5043 | // The budget test then sat only on this outer while, and the |
| 4259 | // a transaction is added, so the loop can overshoot by one tx. | 5044 | // comment claimed that bounded the overshoot to a single |
| 4260 | while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE/2) | 5045 | // transaction. It did not. The inner for walks all of |
| 5046 | // mapTransactions with no test of its own, so one pass could append | ||
| 5047 | // the entire mempool: the overshoot was bounded by mempool size, | ||
| 5048 | // which is to say not bounded at all. Ask per transaction, before | ||
| 5049 | // appending, and the overshoot is zero rather than small. | ||
| 5050 | // | ||
| 5051 | // Seeded with what the block already weighs -- header, tx count, | ||
| 5052 | // coinbase -- because CheckBlock() weighs the whole serialized | ||
| 5053 | // block and not just the transactions we chose to add to it. | ||
| 5054 | unsigned int nBlockSize = ::GetSerializeSize(*pblock, SER_DISK); | ||
| 5055 | // Sigops are a budget too, and CheckBlock() enforces one that no | ||
| 5056 | // assembler here counted against. Bytes do not bound validation | ||
| 5057 | // cost: every OP_CHECKSIG is an elliptic-curve verification, so a | ||
| 5058 | // template well inside the size limit can still be refused for | ||
| 5059 | // demanding more work than a block is allowed to. Counted with | ||
| 5060 | // CTransaction::GetSigOpCount, the function CheckBlock() itself | ||
| 5061 | // sums, so the two cannot disagree about what a transaction costs. | ||
| 5062 | // | ||
| 5063 | // Seeded from the coinbase because CheckBlock() sums over all of | ||
| 5064 | // vtx, coinbase included. | ||
| 5065 | unsigned int nBlockSigOps = pblock->vtx[0].GetSigOpCount(); | ||
| 5066 | while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE_GEN) | ||
| 4261 | { | 5067 | { |
| 4262 | fFoundSomething = false; | 5068 | fFoundSomething = false; |
| 4263 | unsigned int n = 0; | 5069 | unsigned int n = 0; |
| @@ -4269,6 +5075,29 @@ bool BitcoinMiner(int nThreadId) | |||
| 4269 | if (tx.IsCoinBase() || !tx.IsFinal()) | 5075 | if (tx.IsCoinBase() || !tx.IsFinal()) |
| 4270 | continue; | 5076 | continue; |
| 4271 | 5077 | ||
| 5078 | // Skip, not break: a transaction too big for the room left | ||
| 5079 | // says nothing about the next one, which may still fit. | ||
| 5080 | // | ||
| 5081 | // This is also the only ceiling on a single transaction's | ||
| 5082 | // size anywhere on the path into a block. CheckTransaction | ||
| 5083 | // does not cap it and neither does AcceptTransaction, so | ||
| 5084 | // the mempool will hold one of any size the wire allows; | ||
| 5085 | // without this test a lone 1.5 MB transaction offering a | ||
| 5086 | // fat fee is selected, and every template built from that | ||
| 5087 | // mempool is one CheckBlock() throws away. The fee is never | ||
| 5088 | // paid, because the block never confirms. | ||
| 5089 | // | ||
| 5090 | // Deliberately before ConnectInputs and not after: | ||
| 5091 | // ConnectInputs adds this transaction's fee to nFees by | ||
| 5092 | // reference on success. Passing it and then declining the | ||
| 5093 | // transaction would credit the coinbase for a fee no | ||
| 5094 | // transaction in the block pays, and the block would be | ||
| 5095 | // rejected for a wholly different reason. | ||
| 5096 | unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); | ||
| 5097 | unsigned int nTxSigOps = tx.GetSigOpCount(); | ||
| 5098 | if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps)) | ||
| 5099 | continue; | ||
| 5100 | |||
| 4272 | // Transaction fee requirements, mainly only needed for flood control | 5101 | // Transaction fee requirements, mainly only needed for flood control |
| 4273 | // Under 10K (about 80 inputs) is free for first 100 transactions | 5102 | // Under 10K (about 80 inputs) is free for first 100 transactions |
| 4274 | // Base rate is 0.01 per KB | 5103 | // Base rate is 0.01 per KB |
| @@ -4280,7 +5109,8 @@ bool BitcoinMiner(int nThreadId) | |||
| 4280 | swap(mapTestPool, mapTestPoolTmp); | 5109 | swap(mapTestPool, mapTestPoolTmp); |
| 4281 | 5110 | ||
| 4282 | pblock->vtx.push_back(tx); | 5111 | pblock->vtx.push_back(tx); |
| 4283 | nBlockSize += ::GetSerializeSize(tx, SER_NETWORK); | 5112 | nBlockSize += nTxSize; |
| 5113 | nBlockSigOps += nTxSigOps; | ||
| 4284 | vfAlreadyAdded[n] = true; | 5114 | vfAlreadyAdded[n] = true; |
| 4285 | fFoundSomething = true; | 5115 | fFoundSomething = true; |
| 4286 | } | 5116 | } |
| @@ -4329,7 +5159,7 @@ bool BitcoinMiner(int nThreadId) | |||
| 4329 | //// debug print | 5159 | //// debug print |
| 4330 | if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n"); | 5160 | if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n"); |
| 4331 | if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); | 5161 | if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); |
| 4332 | if (LogAcceptsCategory("net")) pblock->print(); | 5162 | if (LogAcceptsCategory("relay")) pblock->print(); |
| 4333 | 5163 | ||
| 4334 | SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); | 5164 | SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); |
| 4335 | CRITICAL_BLOCK(cs_main) | 5165 | CRITICAL_BLOCK(cs_main) |
| @@ -4435,6 +5265,9 @@ WalletRecoveryAudit GetWalletRecoveryAudit() | |||
| 4435 | CRITICAL_BLOCK(cs_keyPool) | 5265 | CRITICAL_BLOCK(cs_keyPool) |
| 4436 | { | 5266 | { |
| 4437 | audit.fHaveSeed = HaveHDSeed(); | 5267 | audit.fHaveSeed = HaveHDSeed(); |
| 5268 | audit.fSeedEncryptedLocked = !audit.fHaveSeed && | ||
| 5269 | (!vchCryptedHDMaster.empty() || | ||
| 5270 | !vchCryptedHDChainCode.empty()); | ||
| 4438 | audit.nSchema = nHDKeySchema; | 5271 | audit.nSchema = nHDKeySchema; |
| 4439 | audit.nDerivedKnown = nHDNext; | 5272 | audit.nDerivedKnown = nHDNext; |
| 4440 | audit.nReceiveNext = nHDReceiveNext; | 5273 | audit.nReceiveNext = nHDReceiveNext; |
| @@ -4681,9 +5514,17 @@ bool SelectCoins(int64 nTargetValue, set<CWalletTx*>& setCoinsRet) | |||
| 4681 | 5514 | ||
| 4682 | 5515 | ||
| 4683 | 5516 | ||
| 4684 | bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet) | 5517 | // presultRet, when given, says WHY this returned false: the caller cannot work |
| 5518 | // it out afterwards. "Coin selection came up short" and "the wallet could not | ||
| 5519 | // build the transaction" are indistinguishable from the outside -- both are | ||
| 5520 | // just `false` -- and the difference decides whether a pool defers a payment | ||
| 5521 | // or counts it against the payment's retry budget. See WalletSendResult. | ||
| 5522 | bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet, | ||
| 5523 | WalletSendResult* presultRet) | ||
| 4685 | { | 5524 | { |
| 4686 | nFeeRequiredRet = 0; | 5525 | nFeeRequiredRet = 0; |
| 5526 | if (presultRet) | ||
| 5527 | *presultRet = WALLET_SEND_FAILED; | ||
| 4687 | CRITICAL_BLOCK(cs_main) | 5528 | CRITICAL_BLOCK(cs_main) |
| 4688 | { | 5529 | { |
| 4689 | // txdb must be opened before the mapWallet lock | 5530 | // txdb must be opened before the mapWallet lock |
| @@ -4714,10 +5555,21 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in | |||
| 4714 | // fixed this in 107d9e288. | 5555 | // fixed this in 107d9e288. |
| 4715 | int64 nTotalValue = nValue + nFee; | 5556 | int64 nTotalValue = nValue + nFee; |
| 4716 | 5557 | ||
| 4717 | // Choose coins to use | 5558 | // Choose coins to use. |
| 5559 | // | ||
| 5560 | // Failing here is a funding shortfall and nothing else: there | ||
| 5561 | // is not enough spendable coin for nValue plus the fee this | ||
| 5562 | // pass is working with. On the second and later passes that | ||
| 5563 | // fee is GetMinFee() of the transaction the first pass built, | ||
| 5564 | // so this is also how a wallet that can afford the amount but | ||
| 5565 | // not the 11 * CENT charged at 10,000 bytes reports itself. | ||
| 4718 | set<CWalletTx*> setCoins; | 5566 | set<CWalletTx*> setCoins; |
| 4719 | if (!SelectCoins(nTotalValue, setCoins)) | 5567 | if (!SelectCoins(nTotalValue, setCoins)) |
| 5568 | { | ||
| 5569 | if (presultRet) | ||
| 5570 | *presultRet = WALLET_SEND_INSUFFICIENT_FUNDS; | ||
| 4720 | return false; | 5571 | return false; |
| 5572 | } | ||
| 4721 | int64 nValueIn = 0; | 5573 | int64 nValueIn = 0; |
| 4722 | foreach(CWalletTx* pcoin, setCoins) | 5574 | foreach(CWalletTx* pcoin, setCoins) |
| 4723 | nValueIn += pcoin->GetCredit(); | 5575 | nValueIn += pcoin->GetCredit(); |
| @@ -4787,6 +5639,8 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in | |||
| 4787 | } | 5639 | } |
| 4788 | } | 5640 | } |
| 4789 | } | 5641 | } |
| 5642 | if (presultRet) | ||
| 5643 | *presultRet = WALLET_SEND_OK; | ||
| 4790 | return true; | 5644 | return true; |
| 4791 | } | 5645 | } |
| 4792 | 5646 | ||
| @@ -4849,13 +5703,24 @@ bool CommitTransactionSpent(const CWalletTx& wtxNew) | |||
| 4849 | 5703 | ||
| 4850 | 5704 | ||
| 4851 | 5705 | ||
| 4852 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | 5706 | // outcomeRet reports what happened rather than only whether it worked. A |
| 5707 | // caller that has to decide what a failure MEANS -- the pool payout thread -- | ||
| 5708 | // cannot reconstruct it from a balance reading afterwards, because the fee | ||
| 5709 | // that was actually required depends on the size of a transaction that was | ||
| 5710 | // never built. See WalletSendResult. | ||
| 5711 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, | ||
| 5712 | SendMoneyOutcome& outcomeRet) | ||
| 4853 | { | 5713 | { |
| 5714 | outcomeRet.result = WALLET_SEND_FAILED; | ||
| 5715 | outcomeRet.nFeeRequired = 0; | ||
| 4854 | CRITICAL_BLOCK(cs_main) | 5716 | CRITICAL_BLOCK(cs_main) |
| 4855 | { | 5717 | { |
| 4856 | int64 nFeeRequired; | 5718 | int64 nFeeRequired; |
| 4857 | if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired)) | 5719 | WalletSendResult createResult = WALLET_SEND_FAILED; |
| 5720 | if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired, &createResult)) | ||
| 4858 | { | 5721 | { |
| 5722 | outcomeRet.result = createResult; | ||
| 5723 | outcomeRet.nFeeRequired = nFeeRequired; | ||
| 4859 | string strError; | 5724 | string strError; |
| 4860 | if (nValue + nFeeRequired > GetBalance()) | 5725 | if (nValue + nFeeRequired > GetBalance()) |
| 4861 | // Not an oversized transaction -- this branch is simply "the | 5726 | // Not an oversized transaction -- this branch is simply "the |
| @@ -4893,6 +5758,13 @@ bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | |||
| 4893 | } | 5758 | } |
| 4894 | wtxNew.RelayWalletTransaction(); | 5759 | wtxNew.RelayWalletTransaction(); |
| 4895 | } | 5760 | } |
| 5761 | outcomeRet.result = WALLET_SEND_OK; | ||
| 4896 | MainFrameRepaint(); | 5762 | MainFrameRepaint(); |
| 4897 | return true; | 5763 | return true; |
| 4898 | } | 5764 | } |
| 5765 | |||
| 5766 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | ||
| 5767 | { | ||
| 5768 | SendMoneyOutcome ignored; | ||
| 5769 | return SendMoney(scriptPubKey, nValue, wtxNew, ignored); | ||
| 5770 | } | ||