diff options
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 | } | ||