// Copyright (c) 2009 Satoshi Nakamoto // Distributed under the MIT/X11 software license, see the accompanying // file license.txt or http://www.opensource.org/licenses/mit-license.php. #pragma push_macro("snprintf") #undef snprintf #include #pragma pop_macro("snprintf") #ifdef snprintf #undef snprintf #endif #include "headers.h" #include "sha.h" #include #include #include #include "btfaddr.h" #include "btftunnel.h" #ifndef _WIN32 #include #include #include #include #include #endif // // Global state // CCriticalSection cs_main; map mapTransactions; CCriticalSection cs_mapTransactions; unsigned int nTransactionsUpdated = 0; map mapNextTx; map mapBlockIndex; // Bitflash genesis -- mined under RandomX PoW on 2026-07-22 // (nTime 1753142400, nBits 0x1f0fffff, RandomX key "Bitflash/RandomX/v1/...") // Bitflash 2026 stable relaunch genesis (2-min blocks, RandomX). Mined values. unsigned int GENESIS_NONCE = 3141; uint256 hashGenesisBlock("0x5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169"); uint256 hashGenesisMerkleRoot("0x1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325"); CBlockIndex* pindexGenesisBlock = NULL; int nBestHeight = -1; uint256 hashBestChain = 0; CBlockIndex* pindexBest = NULL; map mapOrphanBlocks; multimap mapOrphanBlocksByPrev; map mapOrphanTransactions; multimap mapOrphanTransactionsByPrev; map mapWallet; vector > vWalletUpdated; CCriticalSection cs_mapWallet; map, CPrivKey> mapKeys; map > mapPubKeys; CCriticalSection cs_mapKeys; CKey keyUser; string strSetDataDir; int nDropMessagesTest = 0; bool fSoloMineTest = false; // /solomine: mine without requiring a peer (local test) // Settings int fGenerateBitcoins; int64 nTransactionFee = 0; CAddress addrIncoming; int nMineMode = MINE_RELAY; string strParticipantPool; // participant mode: pool .btf address string strPoolName = "Bitflash Pool"; string strPoolDashboardUrl; double dPoolFeePercent = 0.0; static std::atomic gParticipantSharesSent{0}; static std::atomic gParticipantSharesAccepted{0}; static std::atomic gParticipantHashes{0}; static std::atomic gParticipantHashRateX1000{0}; static std::mutex gParticipantStatusMutex; static std::string gParticipantStatus; void SetParticipantMiningStatus(const std::string& status) { // Updates the GUI status string only. Logging is done at each call site // with full context; duplicating it here just adds noise to the log. std::lock_guard lk(gParticipantStatusMutex); gParticipantStatus = status; } std::string GetParticipantMiningStatus() { std::lock_guard lk(gParticipantStatusMutex); return gParticipantStatus; } void GetParticipantMiningStats(uint64& sharesSent, uint64& sharesAccepted, double& hashRate) { sharesSent = gParticipantSharesSent.load(); sharesAccepted = gParticipantSharesAccepted.load(); hashRate = (double)gParticipantHashRateX1000.load() / 1000.0; } ////////////////////////////////////////////////////////////////////////////// // // mapKeys // bool AddKey(const CKey& key) { CRITICAL_BLOCK(cs_mapKeys) { mapKeys[key.GetPubKey()] = key.GetPrivKey(); mapPubKeys[Hash160(key.GetPubKey())] = key.GetPubKey(); } return CWalletDB().WriteKey(key.GetPubKey(), key.GetPrivKey()); } vector GenerateNewKey() { CKey key; key.MakeNewKey(); if (!AddKey(key)) throw runtime_error("GenerateNewKey() : AddKey failed\n"); return key.GetPubKey(); } ////////////////////////////////////////////////////////////////////////////// // // mapWallet // bool AddToWallet(const CWalletTx& wtxIn) { uint256 hash = wtxIn.GetHash(); CRITICAL_BLOCK(cs_mapWallet) { // Inserts only if not already there, returns tx inserted or tx found pair::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn)); CWalletTx& wtx = (*ret.first).second; bool fInsertedNew = ret.second; if (fInsertedNew) wtx.nTimeReceived = GetAdjustedTime(); //// debug print if (LogAcceptsCategory("net")) printf("AddToWallet %s %s\n", wtxIn.GetHash().ToString().substr(0,6).c_str(), fInsertedNew ? "new" : "update"); if (!fInsertedNew) { // Merge bool fUpdated = false; if (wtxIn.hashBlock != 0 && wtxIn.hashBlock != wtx.hashBlock) { wtx.hashBlock = wtxIn.hashBlock; fUpdated = true; } if (wtxIn.nIndex != -1 && (wtxIn.vMerkleBranch != wtx.vMerkleBranch || wtxIn.nIndex != wtx.nIndex)) { wtx.vMerkleBranch = wtxIn.vMerkleBranch; wtx.nIndex = wtxIn.nIndex; fUpdated = true; } if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) { wtx.fFromMe = wtxIn.fFromMe; fUpdated = true; } if (wtxIn.fSpent && wtxIn.fSpent != wtx.fSpent) { wtx.fSpent = wtxIn.fSpent; fUpdated = true; } if (!fUpdated) return true; } // Write to disk if (!wtx.WriteToDisk()) return false; // Notify UI vWalletUpdated.push_back(make_pair(hash, fInsertedNew)); } // Refresh UI MainFrameRepaint(); return true; } bool AddToWalletIfMine(const CTransaction& tx, const CBlock* pblock) { if (tx.IsMine() || mapWallet.count(tx.GetHash())) { CWalletTx wtx(tx); // Get merkle branch if transaction was found in a block if (pblock) wtx.SetMerkleBranch(pblock); return AddToWallet(wtx); } return true; } bool EraseFromWallet(uint256 hash) { CRITICAL_BLOCK(cs_mapWallet) { if (mapWallet.erase(hash)) CWalletDB().EraseTx(hash); } return true; } ////////////////////////////////////////////////////////////////////////////// // // mapOrphanTransactions // void AddOrphanTx(const CDataStream& vMsg) { CTransaction tx; CDataStream(vMsg) >> tx; uint256 hash = tx.GetHash(); if (mapOrphanTransactions.count(hash)) return; CDataStream* pvMsg = mapOrphanTransactions[hash] = new CDataStream(vMsg); foreach(const CTxIn& txin, tx.vin) mapOrphanTransactionsByPrev.insert(make_pair(txin.prevout.hash, pvMsg)); LimitOrphanTx(MAX_ORPHAN_TRANSACTIONS); } // Orphans cost nothing to create -- a transaction naming a parent that does // not exist is never validated, just held -- so without a ceiling any peer can // grow this map until the node runs out of memory. Evict at random rather than // in map order, so an attacker cannot keep its own orphans resident by // choosing hashes that sort low. void LimitOrphanTx(unsigned int nMaxOrphans) { while (mapOrphanTransactions.size() > nMaxOrphans) { uint256 randomhash; for (int i = 0; i < 4; i++) ((uint64*)&randomhash)[i] = GetRand(_UI64_MAX); map::iterator it = mapOrphanTransactions.lower_bound(randomhash); if (it == mapOrphanTransactions.end()) it = mapOrphanTransactions.begin(); EraseOrphanTx(it->first); } } void EraseOrphanTx(uint256 hash) { if (!mapOrphanTransactions.count(hash)) return; const CDataStream* pvMsg = mapOrphanTransactions[hash]; CTransaction tx; CDataStream(*pvMsg) >> tx; foreach(const CTxIn& txin, tx.vin) { for (multimap::iterator mi = mapOrphanTransactionsByPrev.lower_bound(txin.prevout.hash); mi != mapOrphanTransactionsByPrev.upper_bound(txin.prevout.hash);) { if ((*mi).second == pvMsg) mapOrphanTransactionsByPrev.erase(mi++); else mi++; } } delete pvMsg; mapOrphanTransactions.erase(hash); } ////////////////////////////////////////////////////////////////////////////// // // CTransaction // bool CTxIn::IsMine() const { CRITICAL_BLOCK(cs_mapWallet) { map::iterator mi = mapWallet.find(prevout.hash); if (mi != mapWallet.end()) { const CWalletTx& prev = (*mi).second; if (prevout.n < prev.vout.size()) if (prev.vout[prevout.n].IsMine()) return true; } } return false; } int64 CTxIn::GetDebit() const { CRITICAL_BLOCK(cs_mapWallet) { map::iterator mi = mapWallet.find(prevout.hash); if (mi != mapWallet.end()) { const CWalletTx& prev = (*mi).second; if (prevout.n < prev.vout.size()) if (prev.vout[prevout.n].IsMine()) return prev.vout[prevout.n].nValue; } } return 0; } int64 CWalletTx::GetTxTime() const { if (!fTimeReceivedIsTxTime && hashBlock != 0) { // If we did not receive the transaction directly, we rely on the block's // time to figure out when it happened. We use the median over a range // of blocks to try to filter out inaccurate block times. map::iterator mi = mapBlockIndex.find(hashBlock); if (mi != mapBlockIndex.end()) { CBlockIndex* pindex = (*mi).second; if (pindex) return pindex->GetMedianTime(); } } return nTimeReceived; } int CMerkleTx::SetMerkleBranch(const CBlock* pblock) { if (fClient) { if (hashBlock == 0) return 0; } else { CBlock blockTmp; if (pblock == NULL) { // Load the block this tx is in CTxIndex txindex; if (!CTxDB("r").ReadTxIndex(GetHash(), txindex)) return 0; if (!blockTmp.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, true)) return 0; pblock = &blockTmp; } // Update the tx's hashBlock hashBlock = pblock->GetHash(); // Locate the transaction for (nIndex = 0; nIndex < pblock->vtx.size(); nIndex++) if (pblock->vtx[nIndex] == *(CTransaction*)this) break; if (nIndex == pblock->vtx.size()) { vMerkleBranch.clear(); nIndex = -1; if (LogAcceptsCategory("net")) printf("ERROR: SetMerkleBranch() : couldn't find tx in block\n"); return 0; } // Fill in merkle branch vMerkleBranch = pblock->GetMerkleBranch(nIndex); } // Is the tx in a block that's in the main chain map::iterator mi = mapBlockIndex.find(hashBlock); if (mi == mapBlockIndex.end()) return 0; CBlockIndex* pindex = (*mi).second; if (!pindex || !pindex->IsInMainChain()) return 0; return pindexBest->nHeight - pindex->nHeight + 1; } void CWalletTx::AddSupportingTransactions(CTxDB& txdb) { vtxPrev.clear(); const int COPY_DEPTH = 3; if (SetMerkleBranch() < COPY_DEPTH) { vector vWorkQueue; foreach(const CTxIn& txin, vin) vWorkQueue.push_back(txin.prevout.hash); // This critsect is OK because txdb is already open CRITICAL_BLOCK(cs_mapWallet) { map mapWalletPrev; set setAlreadyDone; for (int i = 0; i < vWorkQueue.size(); i++) { uint256 hash = vWorkQueue[i]; if (setAlreadyDone.count(hash)) continue; setAlreadyDone.insert(hash); CMerkleTx tx; if (mapWallet.count(hash)) { tx = mapWallet[hash]; foreach(const CMerkleTx& txWalletPrev, mapWallet[hash].vtxPrev) mapWalletPrev[txWalletPrev.GetHash()] = &txWalletPrev; } else if (mapWalletPrev.count(hash)) { tx = *mapWalletPrev[hash]; } else if (!fClient && txdb.ReadDiskTx(hash, tx)) { ; } else { if (LogAcceptsCategory("net")) printf("ERROR: AddSupportingTransactions() : unsupported transaction\n"); continue; } int nDepth = tx.SetMerkleBranch(); vtxPrev.push_back(tx); if (nDepth < COPY_DEPTH) foreach(const CTxIn& txin, tx.vin) vWorkQueue.push_back(txin.prevout.hash); } } } reverse(vtxPrev.begin(), vtxPrev.end()); } bool CTransaction::AcceptTransaction(CTxDB& txdb, bool fCheckInputs, bool* pfMissingInputs) { if (pfMissingInputs) *pfMissingInputs = false; // Coinbase is only valid in a block, not as a loose transaction if (IsCoinBase()) return error("AcceptTransaction() : coinbase as individual tx"); if (!CheckTransaction()) return error("AcceptTransaction() : CheckTransaction failed"); // Do we already have it? uint256 hash = GetHash(); CRITICAL_BLOCK(cs_mapTransactions) if (mapTransactions.count(hash)) return false; if (fCheckInputs) if (txdb.ContainsTx(hash)) return false; // Check for conflicts with in-memory transactions CTransaction* ptxOld = NULL; for (int i = 0; i < vin.size(); i++) { COutPoint outpoint = vin[i].prevout; if (mapNextTx.count(outpoint)) { // Allow replacing with a newer version of the same transaction if (i != 0) return false; ptxOld = mapNextTx[outpoint].ptx; if (!IsNewerThan(*ptxOld)) return false; for (int i = 0; i < vin.size(); i++) { COutPoint outpoint = vin[i].prevout; if (!mapNextTx.count(outpoint) || mapNextTx[outpoint].ptx != ptxOld) return false; } break; } } // Check against previous transactions map mapUnused; int64 nFees = 0; if (fCheckInputs && !ConnectInputs(txdb, mapUnused, CDiskTxPos(1,1,1), 0, nFees, false, false)) { if (pfMissingInputs) *pfMissingInputs = true; return error("AcceptTransaction() : ConnectInputs failed %s", hash.ToString().substr(0,6).c_str()); } // Store transaction in memory CRITICAL_BLOCK(cs_mapTransactions) { if (ptxOld) { if (LogAcceptsCategory("net")) printf("mapTransaction.erase(%s) replacing with new version\n", ptxOld->GetHash().ToString().c_str()); mapTransactions.erase(ptxOld->GetHash()); } AddToMemoryPool(); } ///// are we sure this is ok when loading transactions or restoring block txes // If updated, erase old tx from wallet if (ptxOld) EraseFromWallet(ptxOld->GetHash()); if (LogAcceptsCategory("net")) printf("AcceptTransaction(): accepted %s\n", hash.ToString().substr(0,6).c_str()); return true; } bool CTransaction::AddToMemoryPool() { // Add to memory pool without checking anything. Don't call this directly, // call AcceptTransaction to properly check the transaction first. CRITICAL_BLOCK(cs_mapTransactions) { uint256 hash = GetHash(); mapTransactions[hash] = *this; for (int i = 0; i < vin.size(); i++) mapNextTx[vin[i].prevout] = CInPoint(&mapTransactions[hash], i); nTransactionsUpdated++; } return true; } bool CTransaction::RemoveFromMemoryPool() { // Remove transaction from memory pool CRITICAL_BLOCK(cs_mapTransactions) { foreach(const CTxIn& txin, vin) mapNextTx.erase(txin.prevout); mapTransactions.erase(GetHash()); nTransactionsUpdated++; } return true; } int CMerkleTx::GetDepthInMainChain() const { if (hashBlock == 0 || nIndex == -1) return 0; // Find the block it claims to be in map::iterator mi = mapBlockIndex.find(hashBlock); if (mi == mapBlockIndex.end()) return 0; CBlockIndex* pindex = (*mi).second; if (!pindex || !pindex->IsInMainChain()) return 0; // Make sure the merkle branch connects to this block if (!fMerkleVerified) { if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != pindex->hashMerkleRoot) return 0; fMerkleVerified = true; } return pindexBest->nHeight - pindex->nHeight + 1; } int CMerkleTx::GetBlocksToMaturity() const { if (!IsCoinBase()) return 0; return max(0, (COINBASE_MATURITY+20) - GetDepthInMainChain()); } bool CMerkleTx::AcceptTransaction(CTxDB& txdb, bool fCheckInputs) { if (fClient) { if (!IsInMainChain() && !ClientConnectInputs()) return false; return CTransaction::AcceptTransaction(txdb, false); } else { return CTransaction::AcceptTransaction(txdb, fCheckInputs); } } bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs) { CRITICAL_BLOCK(cs_mapTransactions) { foreach(CMerkleTx& tx, vtxPrev) { if (!tx.IsCoinBase()) { uint256 hash = tx.GetHash(); if (!mapTransactions.count(hash) && !txdb.ContainsTx(hash)) tx.AcceptTransaction(txdb, fCheckInputs); } } if (!IsCoinBase()) return AcceptTransaction(txdb, fCheckInputs); } return true; } void ReacceptWalletTransactions() { // Reaccept any txes of ours that aren't already in a block CTxDB txdb("r"); CRITICAL_BLOCK(cs_mapWallet) { foreach(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) { CWalletTx& wtx = item.second; if (!wtx.IsCoinBase() && !txdb.ContainsTx(wtx.GetHash())) wtx.AcceptWalletTransaction(txdb, false); } } } void CWalletTx::RelayWalletTransaction(CTxDB& txdb) { foreach(const CMerkleTx& tx, vtxPrev) { if (!tx.IsCoinBase()) { uint256 hash = tx.GetHash(); if (!txdb.ContainsTx(hash)) RelayMessage(CInv(MSG_TX, hash), (CTransaction)tx); } } if (!IsCoinBase()) { uint256 hash = GetHash(); if (!txdb.ContainsTx(hash)) { if (LogAcceptsCategory("net")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str()); RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); } } } void RelayWalletTransactions() { static int64 nLastTime; if (GetTime() - nLastTime < 10 * 60) return; nLastTime = GetTime(); // Rebroadcast any of our txes that aren't in a block yet if (LogAcceptsCategory("net")) printf("RelayWalletTransactions()\n"); CTxDB txdb("r"); CRITICAL_BLOCK(cs_mapWallet) { // Sort them in chronological order multimap mapSorted; foreach(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) { CWalletTx& wtx = item.second; mapSorted.insert(make_pair(wtx.nTimeReceived, &wtx)); } foreach(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted) { CWalletTx& wtx = *item.second; wtx.RelayWalletTransaction(txdb); } } } ////////////////////////////////////////////////////////////////////////////// // // CBlock and CBlockIndex // bool CBlock::ReadFromDisk(const CBlockIndex* pblockindex, bool fReadTransactions) { return ReadFromDisk(pblockindex->nFile, pblockindex->nBlockPos, fReadTransactions); } uint256 GetOrphanRoot(const CBlock* pblock) { // Work back to the first block in the orphan chain while (mapOrphanBlocks.count(pblock->hashPrevBlock)) pblock = mapOrphanBlocks[pblock->hashPrevBlock]; return pblock->GetHash(); } int64 CBlock::GetBlockValue(int nHeight, int64 nFees) const { int64 nSubsidy = 50 * COIN; // Halve on the height of this block, never on nBestHeight. The subsidy a // block is allowed to pay is a property of that block; deriving it from // the validating node's own tip makes the answer depend on how far that // node happens to have synced, so two nodes at different heights would // disagree about the same block across a halving boundary. nSubsidy >>= (nHeight / 210000); return nSubsidy + nFees; } unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast) { // Bitflash: 2-minute target block time with a fast retarget (every 30 // blocks, ~1h) so difficulty tracks the small, volatile CPU hashrate and // brief disconnections no longer spawn divergent forks. const unsigned int nTargetSpacing = 2 * 60; // 2 minutes const unsigned int nInterval = 30; // retarget window const unsigned int nTargetTimespan = nInterval * nTargetSpacing; // 3600s // Genesis block if (pindexLast == NULL) return bnProofOfWorkLimit.GetCompact(); // Only change once per interval if ((pindexLast->nHeight+1) % nInterval != 0) return pindexLast->nBits; // Go back by what we want to be 14 days worth of blocks const CBlockIndex* pindexFirst = pindexLast; for (int i = 0; pindexFirst && i < nInterval-1; i++) pindexFirst = pindexFirst->pprev; assert(pindexFirst); // Limit adjustment step unsigned int nActualTimespan = pindexLast->nTime - pindexFirst->nTime; if (LogAcceptsCategory("net")) printf(" nActualTimespan = %d before bounds\n", nActualTimespan); if (nActualTimespan < nTargetTimespan/4) nActualTimespan = nTargetTimespan/4; if (nActualTimespan > nTargetTimespan*4) nActualTimespan = nTargetTimespan*4; // Retarget CBigNum bnNew; bnNew.SetCompact(pindexLast->nBits); bnNew *= nActualTimespan; bnNew /= nTargetTimespan; if (bnNew > bnProofOfWorkLimit) bnNew = bnProofOfWorkLimit; /// debug print if (LogAcceptsCategory("net")) { printf("\n\n\nGetNextWorkRequired RETARGET *****\n"); printf("nTargetTimespan = %d nActualTimespan = %d\n", nTargetTimespan, nActualTimespan); printf("Before: %08x %s\n", pindexLast->nBits, CBigNum().SetCompact(pindexLast->nBits).getuint256().ToString().c_str()); printf("After: %08x %s\n", bnNew.GetCompact(), bnNew.getuint256().ToString().c_str()); } return bnNew.GetCompact(); } bool CTransaction::DisconnectInputs(CTxDB& txdb) { // Relinquish previous transactions' spent pointers if (!IsCoinBase()) { foreach(const CTxIn& txin, vin) { COutPoint prevout = txin.prevout; // Get prev txindex from disk CTxIndex txindex; if (!txdb.ReadTxIndex(prevout.hash, txindex)) return error("DisconnectInputs() : ReadTxIndex failed"); if (prevout.n >= txindex.vSpent.size()) return error("DisconnectInputs() : prevout.n out of range"); // Mark outpoint as not spent txindex.vSpent[prevout.n].SetNull(); // Write back txdb.UpdateTxIndex(prevout.hash, txindex); } } // Remove transaction from index if (!txdb.EraseTxIndex(*this)) return error("DisconnectInputs() : EraseTxPos failed"); return true; } bool CTransaction::ConnectInputs(CTxDB& txdb, map& mapTestPool, CDiskTxPos posThisTx, int nHeight, int64& nFees, bool fBlock, bool fMiner, int64 nMinFee) { // Take over previous transactions' spent pointers if (!IsCoinBase()) { int64 nValueIn = 0; for (int i = 0; i < vin.size(); i++) { COutPoint prevout = vin[i].prevout; // Read txindex CTxIndex txindex; bool fFound = true; if (fMiner && mapTestPool.count(prevout.hash)) { // Get txindex from current proposed changes txindex = mapTestPool[prevout.hash]; } else { // Read txindex from txdb fFound = txdb.ReadTxIndex(prevout.hash, txindex); } if (!fFound && (fBlock || fMiner)) return fMiner ? false : error("ConnectInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,6).c_str(), prevout.hash.ToString().substr(0,6).c_str()); // Read txPrev CTransaction txPrev; if (!fFound || txindex.pos == CDiskTxPos(1,1,1)) { // Get prev tx from single transactions in memory CRITICAL_BLOCK(cs_mapTransactions) { if (!mapTransactions.count(prevout.hash)) return error("ConnectInputs() : %s mapTransactions prev not found %s", GetHash().ToString().substr(0,6).c_str(), prevout.hash.ToString().substr(0,6).c_str()); txPrev = mapTransactions[prevout.hash]; } if (!fFound) txindex.vSpent.resize(txPrev.vout.size()); } else { // Get prev tx from disk if (!txPrev.ReadFromDisk(txindex.pos)) return error("ConnectInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,6).c_str(), prevout.hash.ToString().substr(0,6).c_str()); } if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) return error("ConnectInputs() : %s prevout.n out of range %d %d %d", GetHash().ToString().substr(0,6).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size()); // If prev is coinbase, check that it's matured if (txPrev.IsCoinBase()) for (CBlockIndex* pindex = pindexBest; pindex && nBestHeight - pindex->nHeight < COINBASE_MATURITY-1; pindex = pindex->pprev) if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile) return error("ConnectInputs() : tried to spend coinbase at depth %d", nBestHeight - pindex->nHeight); // Verify signature if (!VerifySignature(txPrev, *this, i)) return error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,6).c_str()); // Check for conflicts if (!txindex.vSpent[prevout.n].IsNull()) return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,6).c_str(), txindex.vSpent[prevout.n].ToString().c_str()); // Mark outpoints as spent txindex.vSpent[prevout.n] = posThisTx; // Write back if (fBlock) txdb.UpdateTxIndex(prevout.hash, txindex); else if (fMiner) mapTestPool[prevout.hash] = txindex; nValueIn += txPrev.vout[prevout.n].nValue; if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn)) return error("ConnectInputs() : txin values out of range"); } // Tally transaction fees int64 nTxFee = nValueIn - GetValueOut(); if (nTxFee < 0) return error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,6).c_str()); if (nTxFee < nMinFee) return false; nFees += nTxFee; if (!MoneyRange(nFees)) return error("ConnectInputs() : nFees out of range"); } if (fBlock) { // Add transaction to disk index if (!txdb.AddTxIndex(*this, posThisTx, nHeight)) return error("ConnectInputs() : AddTxPos failed"); } else if (fMiner) { // Add transaction to test pool mapTestPool[GetHash()] = CTxIndex(CDiskTxPos(1,1,1), vout.size()); } return true; } bool CTransaction::ClientConnectInputs() { if (IsCoinBase()) return false; // Take over previous transactions' spent pointers CRITICAL_BLOCK(cs_mapTransactions) { int64 nValueIn = 0; for (int i = 0; i < vin.size(); i++) { // Get prev tx from single transactions in memory COutPoint prevout = vin[i].prevout; if (!mapTransactions.count(prevout.hash)) return false; CTransaction& txPrev = mapTransactions[prevout.hash]; if (prevout.n >= txPrev.vout.size()) return false; // Verify signature if (!VerifySignature(txPrev, *this, i)) return error("ConnectInputs() : VerifySignature failed"); ///// this is redundant with the mapNextTx stuff, not sure which I want to get rid of ///// this has to go away now that posNext is gone // // Check for conflicts // if (!txPrev.vout[prevout.n].posNext.IsNull()) // return error("ConnectInputs() : prev tx already used"); // // // Flag outpoints as used // txPrev.vout[prevout.n].posNext = posThisTx; nValueIn += txPrev.vout[prevout.n].nValue; } if (GetValueOut() > nValueIn) return false; } return true; } bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) { // Disconnect in reverse order for (int i = vtx.size()-1; i >= 0; i--) if (!vtx[i].DisconnectInputs(txdb)) return false; // Update block index on disk without changing it in memory. // The memory index structure will be changed after the db commits. if (pindex->pprev) { CDiskBlockIndex blockindexPrev(pindex->pprev); blockindexPrev.hashNext = 0; txdb.WriteBlockIndex(blockindexPrev); } return true; } bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) { //// issue here: it doesn't know the version unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - 1 + GetSizeOfCompactSize(vtx.size()); map mapUnused; int64 nFees = 0; foreach(CTransaction& tx, vtx) { CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); nTxPos += ::GetSerializeSize(tx, SER_DISK); if (!tx.ConnectInputs(txdb, mapUnused, posThisTx, pindex->nHeight, nFees, true, false)) return false; } if (vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees)) return false; // Update block index on disk without changing it in memory. // The memory index structure will be changed after the db commits. if (pindex->pprev) { CDiskBlockIndex blockindexPrev(pindex->pprev); blockindexPrev.hashNext = pindex->GetBlockHash(); txdb.WriteBlockIndex(blockindexPrev); } // Watch for transactions paying to me foreach(CTransaction& tx, vtx) AddToWalletIfMine(tx, this); return true; } bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) { LogPrint("net", "*** REORGANIZE ***\n"); // Find the fork CBlockIndex* pfork = pindexBest; CBlockIndex* plonger = pindexNew; while (pfork != plonger) { if (!(pfork = pfork->pprev)) return error("Reorganize() : pfork->pprev is null"); while (plonger->nHeight > pfork->nHeight) if (!(plonger = plonger->pprev)) return error("Reorganize() : plonger->pprev is null"); } // List of what to disconnect vector vDisconnect; for (CBlockIndex* pindex = pindexBest; pindex != pfork; pindex = pindex->pprev) vDisconnect.push_back(pindex); // List of what to connect vector vConnect; for (CBlockIndex* pindex = pindexNew; pindex != pfork; pindex = pindex->pprev) vConnect.push_back(pindex); reverse(vConnect.begin(), vConnect.end()); // Disconnect shorter branch vector vResurrect; foreach(CBlockIndex* pindex, vDisconnect) { CBlock block; if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos, true)) return error("Reorganize() : ReadFromDisk for disconnect failed"); if (!block.DisconnectBlock(txdb, pindex)) return error("Reorganize() : DisconnectBlock failed"); // Queue memory transactions to resurrect foreach(const CTransaction& tx, block.vtx) if (!tx.IsCoinBase()) vResurrect.push_back(tx); } // Connect longer branch vector vDelete; for (int i = 0; i < vConnect.size(); i++) { CBlockIndex* pindex = vConnect[i]; CBlock block; if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos, true)) return error("Reorganize() : ReadFromDisk for connect failed"); if (!block.ConnectBlock(txdb, pindex)) { // Invalid block, delete the rest of this branch txdb.TxnAbort(); for (int j = i; j < vConnect.size(); j++) { CBlockIndex* pindex = vConnect[j]; pindex->EraseBlockFromDisk(); txdb.EraseBlockIndex(pindex->GetBlockHash()); mapBlockIndex.erase(pindex->GetBlockHash()); delete pindex; } return error("Reorganize() : ConnectBlock failed"); } // Queue memory transactions to delete foreach(const CTransaction& tx, block.vtx) vDelete.push_back(tx); } if (!txdb.WriteHashBestChain(pindexNew->GetBlockHash())) return error("Reorganize() : WriteHashBestChain failed"); // Commit now because resurrecting could take some time txdb.TxnCommit(); // Disconnect shorter branch foreach(CBlockIndex* pindex, vDisconnect) if (pindex->pprev) pindex->pprev->pnext = NULL; // Connect longer branch foreach(CBlockIndex* pindex, vConnect) if (pindex->pprev) pindex->pprev->pnext = pindex; // Resurrect memory transactions that were in the disconnected branch foreach(CTransaction& tx, vResurrect) tx.AcceptTransaction(txdb, false); // Delete redundant memory transactions that are in the connected branch foreach(CTransaction& tx, vDelete) tx.RemoveFromMemoryPool(); return true; } bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) { // Check for duplicate uint256 hash = GetHash(); if (mapBlockIndex.count(hash)) return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,14).c_str()); // Construct new block index object CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this); if (!pindexNew) return error("AddToBlockIndex() : new CBlockIndex failed"); map::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; pindexNew->phashBlock = &((*mi).first); map::iterator miPrev = mapBlockIndex.find(hashPrevBlock); if (miPrev != mapBlockIndex.end()) { pindexNew->pprev = (*miPrev).second; pindexNew->nHeight = pindexNew->pprev->nHeight + 1; } CTxDB txdb; txdb.TxnBegin(); txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew)); // New best if (pindexNew->nHeight > nBestHeight) { if (pindexGenesisBlock == NULL && hash == hashGenesisBlock) { pindexGenesisBlock = pindexNew; txdb.WriteHashBestChain(hash); } else if (hashPrevBlock == hashBestChain) { // Adding to current best branch if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash)) { txdb.TxnAbort(); pindexNew->EraseBlockFromDisk(); mapBlockIndex.erase(pindexNew->GetBlockHash()); delete pindexNew; return error("AddToBlockIndex() : ConnectBlock failed"); } txdb.TxnCommit(); pindexNew->pprev->pnext = pindexNew; // Delete redundant memory transactions foreach(CTransaction& tx, vtx) tx.RemoveFromMemoryPool(); } else { // New best branch if (!Reorganize(txdb, pindexNew)) { txdb.TxnAbort(); return error("AddToBlockIndex() : Reorganize failed"); } } // New best link hashBestChain = hash; pindexBest = pindexNew; nBestHeight = pindexBest->nHeight; nTransactionsUpdated++; printf("AddToBlockIndex: new best=%s height=%d\n", hashBestChain.ToString().substr(0,14).c_str(), nBestHeight); } txdb.TxnCommit(); txdb.Close(); // Relay wallet transactions that haven't gotten in yet if (pindexNew == pindexBest) RelayWalletTransactions(); MainFrameRepaint(); return true; } bool CBlock::CheckBlock() const { // These are checks that are independent of context // that can be verified before saving an orphan block. // Size limits if (vtx.empty() || vtx.size() > MAX_SIZE || ::GetSerializeSize(*this, SER_DISK) > MAX_SIZE) return error("CheckBlock() : size limits failed"); // Check timestamp if (nTime > GetAdjustedTime() + 2 * 60 * 60) return error("CheckBlock() : block timestamp too far in the future"); // First transaction must be coinbase, the rest must not be if (vtx.empty() || !vtx[0].IsCoinBase()) return error("CheckBlock() : first tx is not coinbase"); for (int i = 1; i < vtx.size(); i++) if (vtx[i].IsCoinBase()) return error("CheckBlock() : more than one coinbase"); // Check transactions foreach(const CTransaction& tx, vtx) if (!tx.CheckTransaction()) return error("CheckBlock() : CheckTransaction failed"); // Cap the work a block can demand. MAX_SIZE bounds the bytes, not the cost: // every signature operation is an elliptic-curve verification, and at this // chain's difficulty a block packed with them is affordable to mine and // expensive for everyone else to reject. unsigned int nSigOps = 0; foreach(const CTransaction& tx, vtx) nSigOps += tx.GetSigOpCount(); if (nSigOps > MAX_BLOCK_SIGOPS) return error("CheckBlock() : out-of-bounds signature operation count"); // Reject blocks carrying the same transaction twice (CVE-2012-2459). // // BuildMerkleTree duplicates the last hash when a level has an odd width, // so appending a copy of the trailing transaction(s) yields a block with // an identical merkle root -- and therefore an identical block hash -- to // a legitimate one. The forgery fails validation, the hash gets recorded // as bad, and the real block is then refused because it hashes the same. set setTxHashes; foreach(const CTransaction& tx, vtx) { uint256 hashTx = tx.GetHash(); if (setTxHashes.count(hashTx)) return error("CheckBlock() : duplicate transaction in block"); setTxHashes.insert(hashTx); } // Check proof of work matches claimed amount (memory-hard RandomX PoW) if (CBigNum().SetCompact(nBits) > bnProofOfWorkLimit) return error("CheckBlock() : nBits below minimum work"); if (GetPoWHash() > CBigNum().SetCompact(nBits).getuint256()) return error("CheckBlock() : RandomX proof-of-work does not match nBits"); // Check merkleroot if (hashMerkleRoot != BuildMerkleTree()) return error("CheckBlock() : hashMerkleRoot mismatch"); return true; } bool CBlock::AcceptBlock() { // Check for duplicate uint256 hash = GetHash(); if (mapBlockIndex.count(hash)) return error("AcceptBlock() : block already in mapBlockIndex"); // Get prev block index map::iterator mi = mapBlockIndex.find(hashPrevBlock); if (mi == mapBlockIndex.end()) return error("AcceptBlock() : prev block not found"); CBlockIndex* pindexPrev = (*mi).second; // Check timestamp against prev if (nTime <= pindexPrev->GetMedianTimePast()) return error("AcceptBlock() : block's timestamp is too early"); // Check proof of work if (nBits != GetNextWorkRequired(pindexPrev)) return error("AcceptBlock() : incorrect proof of work"); // Write block to history file unsigned int nFile; unsigned int nBlockPos; if (!WriteToDisk(!fClient, nFile, nBlockPos)) return error("AcceptBlock() : WriteToDisk failed"); if (!AddToBlockIndex(nFile, nBlockPos)) return error("AcceptBlock() : AddToBlockIndex failed"); if (hashBestChain == hash) RelayInventory(CInv(MSG_BLOCK, hash)); // // Add atoms to user reviews for coins created // vector vchPubKey; // if (ExtractPubKey(vtx[0].vout[0].scriptPubKey, false, vchPubKey)) // { // unsigned short nAtom = GetRand(USHRT_MAX - 100) + 100; // vector vAtoms(1, nAtom); // AddAtomsAndPropagate(Hash(vchPubKey.begin(), vchPubKey.end()), vAtoms, true); // } return true; } bool ProcessBlock(CNode* pfrom, CBlock* pblock) { // Check for duplicate uint256 hash = pblock->GetHash(); if (mapBlockIndex.count(hash)) return error("ProcessBlock() : already have block %d %s", mapBlockIndex[hash]->nHeight, hash.ToString().substr(0,14).c_str()); if (mapOrphanBlocks.count(hash)) return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,14).c_str()); // Preliminary checks if (!pblock->CheckBlock()) { delete pblock; return error("ProcessBlock() : CheckBlock FAILED"); } // If don't already have its previous block, shunt it off to holding area until we get it if (!mapBlockIndex.count(pblock->hashPrevBlock)) { printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,14).c_str()); mapOrphanBlocks.insert(make_pair(hash, pblock)); mapOrphanBlocksByPrev.insert(make_pair(pblock->hashPrevBlock, pblock)); // Ask this guy to fill in what we're missing if (pfrom) pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), GetOrphanRoot(pblock)); return true; } // Store to disk if (!pblock->AcceptBlock()) { delete pblock; return error("ProcessBlock() : AcceptBlock FAILED"); } delete pblock; // Recursively process any orphan blocks that depended on this one vector vWorkQueue; vWorkQueue.push_back(hash); for (int i = 0; i < vWorkQueue.size(); i++) { uint256 hashPrev = vWorkQueue[i]; for (multimap::iterator mi = mapOrphanBlocksByPrev.lower_bound(hashPrev); mi != mapOrphanBlocksByPrev.upper_bound(hashPrev); ++mi) { CBlock* pblockOrphan = (*mi).second; if (pblockOrphan->AcceptBlock()) vWorkQueue.push_back(pblockOrphan->GetHash()); mapOrphanBlocks.erase(pblockOrphan->GetHash()); delete pblockOrphan; } mapOrphanBlocksByPrev.erase(hashPrev); } printf("ProcessBlock: ACCEPTED\n"); return true; } template bool ScanMessageStart(Stream& s) { // Scan ahead to the next pchMessageStart, which should normally be immediately // at the file pointer. Leaves file pointer at end of pchMessageStart. s.clear(0); short prevmask = s.exceptions(0); const char* p = BEGIN(pchMessageStart); try { loop { char c; s.read(&c, 1); if (s.fail()) { s.clear(0); s.exceptions(prevmask); return false; } if (*p != c) p = BEGIN(pchMessageStart); if (*p == c) { if (++p == END(pchMessageStart)) { s.clear(0); s.exceptions(prevmask); return true; } } } } catch (...) { s.clear(0); s.exceptions(prevmask); return false; } } string GetAppDir() { string strDir; if (!strSetDataDir.empty()) { strDir = strSetDataDir; } #ifdef _WIN32 else if (getenv("APPDATA")) { strDir = strprintf("%s\\Bitflash", getenv("APPDATA")); } else if (getenv("USERPROFILE")) { string strAppData = strprintf("%s\\Application Data", getenv("USERPROFILE")); static bool fMkdirDone; if (!fMkdirDone) { fMkdirDone = true; _mkdir(strAppData.c_str()); } strDir = strprintf("%s\\Bitflash", strAppData.c_str()); } #else else if (getenv("HOME")) { strDir = strprintf("%s/.bitflash", getenv("HOME")); } #endif else { return "."; } static bool fMkdirDone; if (!fMkdirDone) { fMkdirDone = true; _mkdir(strDir.c_str()); } return strDir; } FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode) { if (nFile == -1) return NULL; FILE* file = fopen(strprintf("%s/blk%04d.dat", GetAppDir().c_str(), nFile).c_str(), pszMode); if (!file) return NULL; if (nBlockPos != 0 && !strchr(pszMode, 'a') && !strchr(pszMode, 'w')) { if (fseek(file, nBlockPos, SEEK_SET) != 0) { fclose(file); return NULL; } } return file; } static unsigned int nCurrentBlockFile = 1; FILE* AppendBlockFile(unsigned int& nFileRet) { nFileRet = 0; loop { FILE* file = OpenBlockFile(nCurrentBlockFile, 0, "ab"); if (!file) return NULL; if (fseek(file, 0, SEEK_END) != 0) return NULL; // FAT32 filesize max 4GB, fseek and ftell max 2GB, so we must stay under 2GB if (ftell(file) < 0x7F000000 - MAX_SIZE) { nFileRet = nCurrentBlockFile; return file; } fclose(file); nCurrentBlockFile++; } } bool LoadBlockIndex(bool fAllowNew) { // Initialize the RandomX PoW before touching any block (both the genesis // and verification depend on it). if (!RandomXInit()) return error("LoadBlockIndex() : RandomXInit failed"); // // Load block index // CTxDB txdb("cr"); if (!txdb.LoadBlockIndex()) return false; txdb.Close(); // // Init with genesis block // if (mapBlockIndex.empty()) { if (!fAllowNew) return false; // Genesis Block: // GetHash() = 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f // hashMerkleRoot = 0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b // txNew.vin[0].scriptSig = 486604799 4 0x736B6E616220726F662074756F6C69616220646E6F63657320666F206B6E697262206E6F20726F6C6C65636E61684320393030322F6E614A2F33302073656D695420656854 // txNew.vout[0].nValue = 5000000000 // txNew.vout[0].scriptPubKey = 0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704 OP_CHECKSIG // block.nVersion = 1 // block.nTime = 1231006505 // block.nBits = 0x1d00ffff // block.nNonce = 2083236893 // CBlock(hash=000000000019d6, ver=1, hashPrevBlock=00000000000000, hashMerkleRoot=4a5e1e, nTime=1231006505, nBits=1d00ffff, nNonce=2083236893, vtx=1) // CTransaction(hash=4a5e1e, ver=1, vin.size=1, vout.size=1, nLockTime=0) // CTxIn(COutPoint(000000, -1), coinbase 04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73) // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) // vMerkleTree: 4a5e1e // Bitflash genesis block -- own header (fair launch, 2026 stable relaunch) char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; CTransaction txNew; txNew.vin.resize(1); txNew.vout.resize(1); txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector((unsigned char*)pszTimestamp, (unsigned char*)pszTimestamp + strlen(pszTimestamp)); txNew.vout[0].nValue = 50 * COIN; txNew.vout[0].scriptPubKey = CScript() << CBigNum("0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704") << OP_CHECKSIG; CBlock block; block.vtx.push_back(txNew); block.hashPrevBlock = 0; block.hashMerkleRoot = block.BuildMerkleTree(); block.nVersion = 1; block.nTime = 1753315200; // 2026-07-24 00:00:00 UTC (stable relaunch) block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) block.nNonce = GENESIS_NONCE; // If the nonce has not been found yet, mine the genesis once (RandomX) // and print the values to be hardcoded in the constants above. if (block.GetHash() != hashGenesisBlock) { printf("Mining Bitflash genesis block (RandomX)...\n"); uint256 hashTarget = CBigNum().SetCompact(block.nBits).getuint256(); while (block.GetPoWHash() > hashTarget) { if ((block.nNonce & 0x3ff) == 0) printf("nonce %u\r", block.nNonce); block.nNonce++; if (block.nNonce == 0) { printf("nonce overflow, incrementing nTime\n"); block.nTime++; } } printf("\n=== BITFLASH GENESIS (RandomX) FOUND ===\n"); printf("nTime = %u\n", block.nTime); printf("nNonce = %u (0x%08x)\n", block.nNonce, block.nNonce); printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); printf("hash = %s\n", block.GetHash().ToString().c_str()); printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); printf("========================================\n"); } // Asserts only apply once the genesis values are hardcoded if (hashGenesisBlock != uint256(0)) { assert(block.hashMerkleRoot == hashGenesisMerkleRoot); assert(block.GetHash() == hashGenesisBlock); } // Start new block file unsigned int nFile; unsigned int nBlockPos; if (!block.WriteToDisk(!fClient, nFile, nBlockPos)) return error("LoadBlockIndex() : writing genesis block to disk failed"); if (!block.AddToBlockIndex(nFile, nBlockPos)) return error("LoadBlockIndex() : genesis block not accepted"); } return true; } void PrintBlockTree() { // precompute tree structure map > mapNext; for (map::iterator mi = mapBlockIndex.begin(); mi != mapBlockIndex.end(); ++mi) { CBlockIndex* pindex = (*mi).second; mapNext[pindex->pprev].push_back(pindex); // test //while (rand() % 3 == 0) // mapNext[pindex->pprev].push_back(pindex); } vector > vStack; vStack.push_back(make_pair(0, pindexGenesisBlock)); int nPrevCol = 0; while (!vStack.empty()) { int nCol = vStack.back().first; CBlockIndex* pindex = vStack.back().second; vStack.pop_back(); // print split or gap if (nCol > nPrevCol) { for (int i = 0; i < nCol-1; i++) printf("| "); printf("|\\\n"); } else if (nCol < nPrevCol) { for (int i = 0; i < nCol; i++) printf("| "); printf("|\n"); } nPrevCol = nCol; // print columns for (int i = 0; i < nCol; i++) printf("| "); // print item CBlock block; block.ReadFromDisk(pindex, true); printf("%d (%u,%u) %s %s tx %d", pindex->nHeight, pindex->nFile, pindex->nBlockPos, block.GetHash().ToString().substr(0,14).c_str(), DateTimeStr(block.nTime).c_str(), block.vtx.size()); CRITICAL_BLOCK(cs_mapWallet) { if (mapWallet.count(block.vtx[0].GetHash())) { CWalletTx& wtx = mapWallet[block.vtx[0].GetHash()]; printf(" mine: %d %d %d", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), wtx.GetCredit()); } } printf("\n"); // put the main timechain first vector& vNext = mapNext[pindex]; for (int i = 0; i < vNext.size(); i++) { if (vNext[i]->pnext) { swap(vNext[0], vNext[i]); break; } } // iterate children for (int i = 0; i < vNext.size(); i++) vStack.push_back(make_pair(nCol+i, vNext[i])); } } ////////////////////////////////////////////////////////////////////////////// // // Messages // bool AlreadyHave(CTxDB& txdb, const CInv& inv) { switch (inv.type) { case MSG_TX: return mapTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash); case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash); case MSG_REVIEW: return true; case MSG_PRODUCT: return mapProducts.count(inv.hash); } // Don't know what it is, just say we already got one return true; } bool ProcessMessages(CNode* pfrom) { CDataStream& vRecv = pfrom->vRecv; if (vRecv.empty()) return true; LogPrint("net", "ProcessMessages(%d bytes)\n", vRecv.size()); // // Message format // (4) message start // (12) command // (4) size // (x) data // loop { // Scan for message start CDataStream::iterator pstart = search(vRecv.begin(), vRecv.end(), BEGIN(pchMessageStart), END(pchMessageStart)); if (vRecv.end() - pstart < sizeof(CMessageHeader)) { if (vRecv.size() > sizeof(CMessageHeader)) { if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE MESSAGESTART NOT FOUND\n\n"); vRecv.erase(vRecv.begin(), vRecv.end() - sizeof(CMessageHeader)); } break; } if (pstart - vRecv.begin() > 0) if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", pstart - vRecv.begin()); vRecv.erase(vRecv.begin(), pstart); // Read header CMessageHeader hdr; vRecv >> hdr; if (!hdr.IsValid()) { if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str()); continue; } string strCommand = hdr.GetCommand(); // Message size unsigned int nMessageSize = hdr.nMessageSize; if (nMessageSize > vRecv.size()) { // Rewind and wait for rest of message ///// need a mechanism to give up waiting for overlong message size error if (LogAcceptsCategory("net")) printf("MESSAGE-BREAK 2\n"); vRecv.insert(vRecv.begin(), BEGIN(hdr), END(hdr)); Sleep(100); break; } // Copy message to its own buffer CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion); vRecv.ignore(nMessageSize); // Process message bool fRet = false; try { CheckForShutdown(2); CRITICAL_BLOCK(cs_main) fRet = ProcessMessage(pfrom, strCommand, vMsg); CheckForShutdown(2); } CATCH_PRINT_EXCEPTION("ProcessMessage()") if (!fRet) if (LogAcceptsCategory("net")) printf("ProcessMessage(%s, %d bytes) from %s to %s FAILED\n", strCommand.c_str(), nMessageSize, pfrom->addr.ToString().c_str(), addrLocalHost.ToString().c_str()); } vRecv.Compact(); return true; } bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { static map > mapReuseKey; if (LogAcceptsCategory("net")) { printf("received: %-12s (%d bytes) ", strCommand.c_str(), vRecv.size()); for (int i = 0; i < min(vRecv.size(), (unsigned int)25); i++) printf("%02x ", vRecv[i] & 0xff); printf("\n"); } if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0) { if (LogAcceptsCategory("net")) printf("dropmessages DROPPING RECV MESSAGE\n"); return true; } if (strCommand == "version") { // Can only do this once if (pfrom->nVersion != 0) return false; int64 nTime; CAddress addrMe; vRecv >> pfrom->nVersion >> pfrom->nServices >> nTime >> addrMe; if (pfrom->nVersion == 0) return false; pfrom->vSend.SetVersion(min(pfrom->nVersion, VERSION)); pfrom->vRecv.SetVersion(min(pfrom->nVersion, VERSION)); pfrom->fClient = !(pfrom->nServices & NODE_NETWORK); if (pfrom->fClient) { pfrom->vSend.nType |= SER_BLOCKHEADERONLY; pfrom->vRecv.nType |= SER_BLOCKHEADERONLY; } AddTimeData(pfrom->addr.ip, nTime); // Ask the first connected node for block updates static bool fAskedForBlocks; if (!fAskedForBlocks && !pfrom->fClient) { fAskedForBlocks = true; pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), uint256(0)); } // Introduce ourselves and hand over the .btf peers that answered us. // Everything in here is self-certifying, so this is safe to send to a // node we know nothing about, and safe for it to act on. vector vPexOut; BtfPexCollect(vPexOut); if (!vPexOut.empty()) pfrom->PushMessage("btfpeers", vPexOut); if (LogAcceptsCategory("net")) printf("version addrMe = %s\n", addrMe.ToString().c_str()); } else if (pfrom->nVersion == 0) { // Must have a version message before anything else return false; } else if (strCommand == "btfpeers") { // Peer exchange. Nothing here is taken on trust: BtfPexAccept checks // every descriptor's signature against the key its .btf address // decodes to, and drops the rest. int64 nNow = GetTime(); if (nNow - pfrom->nLastPexRecv < PEX_MIN_INTERVAL) { if (LogAcceptsCategory("net")) printf("btfpeers: ignoring, %s is sending them too fast\n", pfrom->addr.ToString().c_str()); return true; } pfrom->nLastPexRecv = nNow; vector vDesc; vRecv >> vDesc; int nKept = BtfPexAccept(vDesc); LogPrint("net", "btfpeers: kept %d of %zu descriptor(s) from %s\n", nKept, vDesc.size(), pfrom->addr.ToString().c_str()); } else if (strCommand == "inv") { vector vInv; vRecv >> vInv; CTxDB txdb("r"); foreach(const CInv& inv, vInv) { if (fShutdown) return true; pfrom->AddInventoryKnown(inv); bool fAlreadyHave = AlreadyHave(txdb, inv); if (LogAcceptsCategory("net")) printf(" got inventory: %s %s\n", inv.ToString().c_str(), fAlreadyHave ? "have" : "new"); if (!fAlreadyHave) pfrom->AskFor(inv); else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash)) pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), GetOrphanRoot(mapOrphanBlocks[inv.hash])); } } else if (strCommand == "getdata") { vector vInv; vRecv >> vInv; foreach(const CInv& inv, vInv) { if (fShutdown) return true; if (LogAcceptsCategory("net")) printf("received getdata for: %s\n", inv.ToString().c_str()); if (inv.type == MSG_BLOCK) { // Send block from disk map::iterator mi = mapBlockIndex.find(inv.hash); if (mi != mapBlockIndex.end()) { //// could optimize this to send header straight from blockindex for client CBlock block; block.ReadFromDisk((*mi).second, !pfrom->fClient); pfrom->PushMessage("block", block); } } else if (inv.IsKnownType()) { // Send stream from relay memory CRITICAL_BLOCK(cs_mapRelay) { map::iterator mi = mapRelay.find(inv); if (mi != mapRelay.end()) pfrom->PushMessage(inv.GetCommand(), (*mi).second); } } } } else if (strCommand == "getblocks") { CBlockLocator locator; uint256 hashStop; vRecv >> locator >> hashStop; // Find the first block the caller has in the main chain CBlockIndex* pindex = locator.GetBlockIndex(); // Send the rest of the chain if (pindex) pindex = pindex->pnext; if (LogAcceptsCategory("net")) printf("getblocks %d to %s\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,14).c_str()); for (; pindex; pindex = pindex->pnext) { if (pindex->GetBlockHash() == hashStop) { if (LogAcceptsCategory("net")) printf(" getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,14).c_str()); break; } // Bypass setInventoryKnown in case an inventory message got lost CRITICAL_BLOCK(pfrom->cs_inventory) { CInv inv(MSG_BLOCK, pindex->GetBlockHash()); // returns true if wasn't already contained in the set if (pfrom->setInventoryKnown2.insert(inv).second) { pfrom->setInventoryKnown.erase(inv); pfrom->vInventoryToSend.push_back(inv); } } } } else if (strCommand == "tx") { vector vWorkQueue; CDataStream vMsg(vRecv); CTransaction tx; vRecv >> tx; CInv inv(MSG_TX, tx.GetHash()); pfrom->AddInventoryKnown(inv); bool fMissingInputs = false; if (tx.AcceptTransaction(true, &fMissingInputs)) { AddToWalletIfMine(tx, NULL); RelayMessage(inv, vMsg); mapAlreadyAskedFor.erase(inv); vWorkQueue.push_back(inv.hash); // Recursively process any orphan transactions that depended on this one for (int i = 0; i < vWorkQueue.size(); i++) { uint256 hashPrev = vWorkQueue[i]; for (multimap::iterator mi = mapOrphanTransactionsByPrev.lower_bound(hashPrev); mi != mapOrphanTransactionsByPrev.upper_bound(hashPrev); ++mi) { const CDataStream& vMsg = *((*mi).second); CTransaction tx; CDataStream(vMsg) >> tx; CInv inv(MSG_TX, tx.GetHash()); if (tx.AcceptTransaction(true)) { if (LogAcceptsCategory("net")) printf(" accepted orphan tx %s\n", inv.hash.ToString().substr(0,6).c_str()); AddToWalletIfMine(tx, NULL); RelayMessage(inv, vMsg); mapAlreadyAskedFor.erase(inv); vWorkQueue.push_back(inv.hash); } } } foreach(uint256 hash, vWorkQueue) EraseOrphanTx(hash); } else if (fMissingInputs) { if (LogAcceptsCategory("net")) printf("storing orphan tx %s\n", inv.hash.ToString().substr(0,6).c_str()); AddOrphanTx(vMsg); } } else if (strCommand == "review") { CDataStream vMsg(vRecv); CReview review; vRecv >> review; CInv inv(MSG_REVIEW, review.GetHash()); pfrom->AddInventoryKnown(inv); if (review.AcceptReview()) { // Relay the original message as-is in case it's a higher version than we know how to parse RelayMessage(inv, vMsg); mapAlreadyAskedFor.erase(inv); } } else if (strCommand == "block") { auto_ptr pblock(new CBlock); vRecv >> *pblock; //// debug print if (LogAcceptsCategory("net")) { printf("received block:\n"); pblock->print(); } CInv inv(MSG_BLOCK, pblock->GetHash()); pfrom->AddInventoryKnown(inv); if (ProcessBlock(pfrom, pblock.release())) mapAlreadyAskedFor.erase(inv); } else if (strCommand == "checkorder") { uint256 hashReply; CWalletTx order; vRecv >> hashReply >> order; /// we have a chance to check the order here // Keep giving the same key to the same ip until they use it if (!mapReuseKey.count(pfrom->addr.ip)) mapReuseKey[pfrom->addr.ip] = GenerateNewKey(); // Send back approval of order and pubkey to use CScript scriptPubKey; scriptPubKey << mapReuseKey[pfrom->addr.ip] << OP_CHECKSIG; pfrom->PushMessage("reply", hashReply, (int)0, scriptPubKey); } else if (strCommand == "submitorder") { uint256 hashReply; CWalletTx wtxNew; vRecv >> hashReply >> wtxNew; // Broadcast if (!wtxNew.AcceptWalletTransaction()) { pfrom->PushMessage("reply", hashReply, (int)1); return error("submitorder AcceptWalletTransaction() failed, returning error 1"); } wtxNew.fTimeReceivedIsTxTime = true; AddToWallet(wtxNew); wtxNew.RelayWalletTransaction(); mapReuseKey.erase(pfrom->addr.ip); // Send back confirmation pfrom->PushMessage("reply", hashReply, (int)0); } else if (strCommand == "reply") { uint256 hashReply; vRecv >> hashReply; CRequestTracker tracker; CRITICAL_BLOCK(pfrom->cs_mapRequests) { map::iterator mi = pfrom->mapRequests.find(hashReply); if (mi != pfrom->mapRequests.end()) { tracker = (*mi).second; pfrom->mapRequests.erase(mi); } } if (!tracker.IsNull()) tracker.fn(tracker.param1, vRecv); } else { // Ignore unknown commands for extensibility if (LogAcceptsCategory("net")) printf("ProcessMessage(%s) : Ignored unknown message\n", strCommand.c_str()); } if (!vRecv.empty()) if (LogAcceptsCategory("net")) printf("ProcessMessage(%s) : %d extra bytes\n", strCommand.c_str(), vRecv.size()); return true; } bool SendMessages(CNode* pto) { CheckForShutdown(2); CRITICAL_BLOCK(cs_main) { // Don't send anything until we get their version message if (pto->nVersion == 0) return true; // // Message: inventory // vector vInventoryToSend; CRITICAL_BLOCK(pto->cs_inventory) { vInventoryToSend.reserve(pto->vInventoryToSend.size()); foreach(const CInv& inv, pto->vInventoryToSend) { // returns true if wasn't already contained in the set if (pto->setInventoryKnown.insert(inv).second) vInventoryToSend.push_back(inv); } pto->vInventoryToSend.clear(); pto->setInventoryKnown2.clear(); } if (!vInventoryToSend.empty()) pto->PushMessage("inv", vInventoryToSend); // // Message: getdata // vector vAskFor; int64 nNow = GetTime() * 1000000; CTxDB txdb("r"); while (!pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow) { const CInv& inv = (*pto->mapAskFor.begin()).second; if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str()); if (!AlreadyHave(txdb, inv)) vAskFor.push_back(inv); pto->mapAskFor.erase(pto->mapAskFor.begin()); } if (!vAskFor.empty()) pto->PushMessage("getdata", vAskFor); } return true; } ////////////////////////////////////////////////////////////////////////////// // // BitcoinMiner // int FormatHashBlocks(void* pbuffer, unsigned int len) { unsigned char* pdata = (unsigned char*)pbuffer; unsigned int blocks = 1 + ((len + 8) / 64); unsigned char* pend = pdata + 64 * blocks; memset(pdata + len, 0, 64 * blocks - len); pdata[len] = 0x80; unsigned int bits = len * 8; pend[-1] = (bits >> 0) & 0xff; pend[-2] = (bits >> 8) & 0xff; pend[-3] = (bits >> 16) & 0xff; pend[-4] = (bits >> 24) & 0xff; return blocks; } using CryptoPP::ByteReverse; static int detectlittleendian = 1; void BlockSHA256(const void* pin, unsigned int nBlocks, void* pout) { unsigned int* pinput = (unsigned int*)pin; unsigned int* pstate = (unsigned int*)pout; CryptoPP::SHA256::InitState(pstate); if (*(char*)&detectlittleendian != 0) { for (int n = 0; n < nBlocks; n++) { unsigned int pbuf[16]; for (int i = 0; i < 16; i++) pbuf[i] = ByteReverse(pinput[n * 16 + i]); CryptoPP::SHA256::Transform(pstate, pbuf); } for (int i = 0; i < 8; i++) pstate[i] = ByteReverse(pstate[i]); } else { for (int n = 0; n < nBlocks; n++) CryptoPP::SHA256::Transform(pstate, pinput + n * 16); } } // --------------------------------------------------------------------------- // Participant pool miner -- connects to an external Stratum server, receives // RandomX jobs, hashes with the local CPU, and submits shares. // Called from BitcoinMiner() when nMineMode == MINE_PARTICIPANT. // --------------------------------------------------------------------------- using json = nlohmann::json; // --------------------------------------------------------------------------- // Participant miner -- connects to a pool over a .btf tunnel, runs Stratum, // hashes with RandomX, submits shares. Called from BitcoinMiner(). // --------------------------------------------------------------------------- using json = nlohmann::json; static std::string ToHexStr(const void* p, size_t n) { const unsigned char* b = (const unsigned char*)p; std::string s; s.reserve(n*2); static const char* H = "0123456789abcdef"; for (size_t i = 0; i < n; i++) { s += H[b[i]>>4]; s += H[b[i]&0xf]; } return s; } static std::vector FromHexStr(const std::string& s) { std::vector v; for (size_t i = 0; i+1 < s.size(); i += 2) { auto h = [](char c)->int{ if(c>='0'&&c<='9') return c-'0'; if(c>='a'&&c<='f') return c-'a'+10; if(c>='A'&&c<='F') return c-'A'+10; return 0; }; v.push_back((unsigned char)((h(s[i])<<4)|h(s[i+1]))); } return v; } static void UpdateParticipantHashRate(uint64 hashesSinceSample, int64 sampleStart) { int64 elapsed = GetTime() - sampleStart; if (elapsed <= 0) elapsed = 1; gParticipantHashRateX1000.store( (uint64)((double)hashesSinceSample / (double)elapsed * 1000.0)); } // Blocking send of a JSON line to the pool. static bool StratumSendLine(SOCKET s, const json& j) { std::string line = j.dump() + "\n"; int sent = 0, total = (int)line.size(); while (sent < total) { int r = send(s, line.c_str()+sent, total-sent, 0); if (r <= 0) return false; sent += r; } return true; } // Non-blocking recv into carry buffer. Returns true + fills line when a // complete newline-terminated message is ready. Returns false in two cases: // disconnected=true -- socket closed by peer; caller must stop // disconnected=false -- no complete line yet; caller should select() and retry static bool StratumRecvLine(SOCKET s, std::string& buf, std::string& line, bool& disconnected) { disconnected = false; char tmp[4096]; #ifdef _WIN32 u_long avail = 0; ioctlsocket(s, FIONREAD, &avail); if (avail > 0) { int r = recv(s, tmp, (int)std::min((u_long)(sizeof(tmp)-1), avail), 0); if (r > 0) { tmp[r] = 0; buf += tmp; } else if (r == 0) { disconnected = true; return false; } } #else { int r = recv(s, tmp, sizeof(tmp)-1, MSG_DONTWAIT); if (r > 0) { tmp[r] = 0; buf += tmp; } else if (r == 0) { disconnected = true; return false; } // r < 0: EAGAIN -- no data right now } #endif size_t pos = buf.find('\n'); if (pos == std::string::npos) return false; line = buf.substr(0, pos); if (!line.empty() && line.back() == '\r') line.pop_back(); buf = buf.substr(pos + 1); return true; } // How long to wait for a mining.notify after authorize before giving up static const int64 STUCK_NO_JOB_TIMEOUT_SECS = 120; static bool PoolParticipantMiner() { // ------------------------------------------------------------------ // Step 1: resolve .btf address -> meeting node // ------------------------------------------------------------------ if (strParticipantPool.empty()) { LogPrint("worker", "[worker] no pool selected\n"); SetParticipantMiningStatus("no pool selected"); return false; } LogPrint("worker", "[worker] connecting to pool %s\n", strParticipantPool.c_str()); SetParticipantMiningStatus("resolving pool address"); unsigned char target_pubkey[32]; if (!btf::ParseAddress(strParticipantPool, target_pubkey)) { LogPrint("worker", "[worker] invalid .btf address: %s\n", strParticipantPool.c_str()); SetParticipantMiningStatus("invalid pool address"); return false; } std::string meetingHostPort; unsigned char service_enc_pub[32]; if (!BtfResolve(strParticipantPool, meetingHostPort, service_enc_pub)) { LogPrint("worker", "[worker] failed to resolve pool %s" " (no Nostr descriptor found -- pool may be offline or relays down)\n", strParticipantPool.c_str()); SetParticipantMiningStatus("failed to resolve pool"); return false; } LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); size_t colon = meetingHostPort.rfind(':'); if (colon == std::string::npos) { LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); SetParticipantMiningStatus("bad pool endpoint"); return false; } std::string host = meetingHostPort.substr(0, colon); int port = atoi(meetingHostPort.substr(colon + 1).c_str()); if (port <= 0 || port > 65535) { LogPrint("worker", "[worker] bad meeting port %d in '%s'\n", port, meetingHostPort.c_str()); SetParticipantMiningStatus("bad pool port"); return false; } // ------------------------------------------------------------------ // Step 2: open encrypted tunnel to pool // ------------------------------------------------------------------ SetParticipantMiningStatus("opening tunnel"); btf_socket_t s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, target_pubkey, service_enc_pub); if (s == INVALID_SOCKET) { LogPrint("worker", "[worker] tunnel connect FAILED via %s" " (relay unreachable or pool not listening)\n", meetingHostPort.c_str()); SetParticipantMiningStatus("tunnel connect failed"); return false; } LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); SetParticipantMiningStatus("tunnel open"); // ------------------------------------------------------------------ // Step 3: determine our payout address (mining username) // ------------------------------------------------------------------ std::string username; { std::vector vchPubKey; if (CWalletDB("r").ReadDefaultKey(vchPubKey)) username = PubKeyToAddress(vchPubKey); } if (username.empty()) username = BtfLocalAddress(); if (username.empty()) { LogPrint("worker", "[worker] WARNING: could not determine wallet address" " -- shares will not pay out\n"); username = "unknown"; } LogPrint("worker", "[worker] payout address: %s\n", username.c_str()); // ------------------------------------------------------------------ // Step 4: build the fast (~2 GB dataset) RandomX mode if not already // built, then start our VM. Without this, RandomXCreateMinerVM() falls // back to "light" mode (cache only) forever, which is dramatically // slower per hash than fast mode -- it recomputes dataset items on the // fly for every single hash instead of a direct memory lookup. That // alone can turn a "few seconds per share" pool into "many minutes per // share" regardless of how easy the share target is. // // This MUST happen before we send mining.subscribe/authorize, not // after: it's a synchronous, single-threaded-per-call build that can // take anywhere from several seconds to a couple of minutes depending // on core count. If it runs after the handshake messages are sent, // the pool's replies (subscribe ack, authorize ack, set_difficulty, // and the first job) all land in the OS socket buffer while we're // not reading -- nothing is lost, but we sit there looking (and // logging) like we're "stuck waiting for pool response" for the // entire build duration, when really we're just not listening yet. // Building the dataset first means that by the time we say // "subscribing", we're genuinely ready to read the reply the instant // it arrives. // ------------------------------------------------------------------ if (!RandomXFastReady()) { SetParticipantMiningStatus("building RandomX dataset (~2GB, one-time)"); unsigned int nThreads = std::thread::hardware_concurrency(); RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); } void* rxvm = RandomXCreateMinerVM(); if (!rxvm) { LogPrint("worker", "[worker] failed to create RandomX VM\n"); SetParticipantMiningStatus("RandomX init failed"); closesocket(s); return false; } LogPrint("worker", "[worker] RandomX VM ready (%s)\n", RandomXFastReady() ? "fast 2GB" : "light 256MB"); // ------------------------------------------------------------------ // Step 5: Stratum subscribe + authorize. The socket is read from // immediately after this, in the main loop below, so nothing sits // unread behind a blocking dataset build anymore. // ------------------------------------------------------------------ std::string buf; int msgId = 1; std::set pendingSubmitIds; SetParticipantMiningStatus("subscribing"); LogPrint("worker", "[worker->pool] sending mining.subscribe\n"); if (!StratumSendLine(s, json{{"id",msgId++},{"method","mining.subscribe"}, {"params",json::array()}})) { LogPrint("worker", "[worker] subscribe send FAILED -- pool dropped connection\n"); SetParticipantMiningStatus("subscribe send failed"); RandomXDestroyMinerVM(rxvm); closesocket(s); return false; } SetParticipantMiningStatus("authorizing"); LogPrint("worker", "[worker->pool] sending mining.authorize as %s\n", username.c_str()); if (!StratumSendLine(s, json{{"id",msgId++},{"method","mining.authorize"}, {"params",json::array({username,"x"})}})) { LogPrint("worker", "[worker] authorize send FAILED -- pool dropped connection\n"); SetParticipantMiningStatus("authorize send failed"); RandomXDestroyMinerVM(rxvm); closesocket(s); return false; } // ------------------------------------------------------------------ // Step 6: main loop -- receive messages, hash, submit shares // ------------------------------------------------------------------ std::string jobId; unsigned char header[80] = {}; uint256 target; double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool bool haveJob = false; bool gotSubscribeAck = false; bool gotAuthorizeAck = false; int64 authWaitStart = 0; int64 lastStuckLog = 0; unsigned int nNonce = 1; uint64 hashesSinceSample = 0; uint64 sessionSharesSent = 0; // this connection only uint64 sessionSharesAccepted = 0; int64 sampleStart = GetTime(); SetParticipantMiningStatus("waiting for pool response"); while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { // -- Receive: check buffer first, then select -- { bool haveBuffered = buf.find('\n') != std::string::npos; bool readable = false; if (!haveBuffered) { fd_set fds; FD_ZERO(&fds); FD_SET(s, &fds); struct timeval tv = {0, 50000}; // 50ms readable = select((int)s+1, &fds, NULL, NULL, &tv) > 0; } if (haveBuffered || readable) { std::string line; bool disc = false; while (StratumRecvLine(s, buf, line, disc)) { json msg; try { msg = json::parse(line); } catch (...) { LogPrint("worker", "[pool->worker] JSON parse error: %.80s\n", line.c_str()); continue; } // -- Response to our subscribe (id=1) -- if (msg.contains("id") && msg["id"].is_number_integer() && msg["id"].get() == 1 && msg.contains("result") && !msg["result"].is_null()) { gotSubscribeAck = true; LogPrint("worker", "[pool->worker] subscribe ack received\n"); SetParticipantMiningStatus("subscribed; waiting for authorize ack"); } // -- Response to our authorize (id=2) -- else if (msg.contains("id") && msg["id"].is_number_integer() && msg["id"].get() == 2 && msg.contains("result")) { bool ok = msg["result"].is_boolean() && msg["result"].get(); authWaitStart = GetTime(); if (ok) { gotAuthorizeAck = true; LogPrint("worker", "[pool->worker] authorize OK -- waiting for job\n"); SetParticipantMiningStatus("authorized; waiting for job"); } else { std::string reason = msg.value("error", json("unspecified")).is_string() ? msg["error"].get() : "unspecified"; LogPrint("worker", "[pool->worker] authorize REJECTED: %s\n", reason.c_str()); SetParticipantMiningStatus("authorization rejected"); // Don't break -- pool might still send a job (some pools // reject bad addresses but still send work) } } // -- Share submission response -- else if (msg.contains("id") && msg["id"].is_number_integer()) { int rid = msg["id"].get(); if (pendingSubmitIds.erase(rid) > 0) { bool accepted = msg.contains("result") && msg["result"].is_boolean() && msg["result"].get(); if (accepted) { gParticipantSharesAccepted.fetch_add(1); sessionSharesAccepted++; LogPrint("worker", "[pool->worker] share id=%d ACCEPTED\n", rid); SetParticipantMiningStatus("share accepted"); } else { std::string reason = (msg.contains("error") && msg["error"].is_string()) ? msg["error"].get() : "unspecified"; LogPrint("worker", "[pool->worker] share id=%d REJECTED: %s\n", rid, reason.c_str()); SetParticipantMiningStatus("share rejected: " + reason); } } } // -- Server notification -- else { std::string method = msg.value("method", ""); if (method == "mining.notify") { auto& p = msg["params"]; if (!p.is_array() || p.size() < 4) { LogPrint("worker", "[pool->worker] malformed mining.notify" " (params size=%zu)\n", p.is_array() ? p.size() : 0); } else { std::string newJobId = p[0].get(); auto hdrBytes = FromHexStr(p[1].get()); auto tgtBytes = FromHexStr(p[3].get()); if (hdrBytes.size() < 80) { LogPrint("worker", "[pool->worker] mining.notify:" " header too short (%zu bytes)\n", hdrBytes.size()); } else if (tgtBytes.size() < 32) { LogPrint("worker", "[pool->worker] mining.notify:" " target too short (%zu bytes)\n", tgtBytes.size()); } else { jobId = newJobId; memcpy(header, hdrBytes.data(), 80); memcpy(&target, tgtBytes.data(), 32); haveJob = true; nNonce = 1; LogPrint("worker", "[pool->worker] new job %s received\n", jobId.c_str()); SetParticipantMiningStatus("hashing job " + jobId); } } } else if (method == "mining.set_difficulty") { // Pool adjusts our share difficulty via vardiff. // Apply it immediately so we start hashing at the new target. if (msg.contains("params") && msg["params"].is_array() && msg["params"].size() > 0 && msg["params"][0].is_number()) { double newDiff = msg["params"][0].get(); if (newDiff > 0.0) { bool changed = (newDiff != currentDifficulty); currentDifficulty = newDiff; LogPrint("worker", "[pool->worker] set_difficulty %.6f%s\n", newDiff, changed ? " (updated)" : " (initial)"); } } else { LogPrint("worker", "[pool->worker] set_difficulty" " received (no valid params)\n"); } } else if (!method.empty()) { LogPrint("worker", "[pool->worker] unknown server method: %s\n", method.c_str()); } } } if (disc) { LogPrint("worker", "[worker] pool closed the connection" " (gotSubscribeAck=%d gotAuthorizeAck=%d haveJob=%d)\n", gotSubscribeAck, gotAuthorizeAck, haveJob); break; } } } // -- Stuck watchdog: authorized but no job arriving -- if (gotAuthorizeAck && !haveJob && authWaitStart > 0) { int64 waited = GetTime() - authWaitStart; if (waited > 10 && GetTime() - lastStuckLog >= 15) { LogPrint("worker", "[worker] authorized but no job after %llds" " -- pool tunnel is live, waiting on mining.notify\n", (long long)waited); lastStuckLog = GetTime(); } if (waited > STUCK_NO_JOB_TIMEOUT_SECS) { LogPrint("worker", "[worker] no job received after %llds -- disconnecting" " and reconnecting\n", (long long)STUCK_NO_JOB_TIMEOUT_SECS); SetParticipantMiningStatus("timeout waiting for job; reconnecting"); break; } } if (!haveJob) { Sleep(100); continue; } // -- Hash one nonce -- memcpy(header+76, &nNonce, 4); uint256 hash = RandomXHashWithVM(rxvm, header, 80); hashesSinceSample++; gParticipantHashes.fetch_add(1); if ((hashesSinceSample & 0x3ff) == 0) UpdateParticipantHashRate(hashesSinceSample, sampleStart); if (hash <= target) { LogPrint("worker", "[worker] share found! nonce=%u job=%s difficulty=%.4f\n", nNonce, jobId.c_str(), currentDifficulty); SetParticipantMiningStatus("share found; submitting"); int submitId = msgId++; pendingSubmitIds.insert(submitId); gParticipantSharesSent.fetch_add(1); sessionSharesSent++; json submit = {{"id",submitId},{"method","mining.submit"}, {"params",json::array({username, jobId, ToHexStr(&nNonce,4)})}}; if (!StratumSendLine(s, submit)) { LogPrint("worker", "[worker] share submit send FAILED" " -- pool connection lost\n"); break; } } nNonce++; if (nNonce == 0) { LogPrint("worker", "[worker] nonce space exhausted for job %s" " -- waiting for new job\n", jobId.c_str()); haveJob = false; } } // ------------------------------------------------------------------ // Cleanup // ------------------------------------------------------------------ RandomXDestroyMinerVM(rxvm); closesocket(s); UpdateParticipantHashRate(hashesSinceSample, sampleStart); SetParticipantMiningStatus("disconnected"); LogPrint("worker", "[worker] disconnected from pool" " (hashes this session: %llu, shares sent: %llu, accepted: %llu)\n", (unsigned long long)hashesSinceSample, (unsigned long long)sessionSharesSent, (unsigned long long)sessionSharesAccepted); return true; } bool BitcoinMiner() { // Relay mode: never mines, just relays/syncs. (Shouldn't normally get here // since the GUI hides Start Mining for Relay, but guard it regardless.) if (nMineMode == MINE_RELAY) return true; // Participant mode: connect to external pool instead of mining solo/operator if (nMineMode == MINE_PARTICIPANT) { while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { PoolParticipantMiner(); if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { LogPrint("worker", "PoolParticipantMiner: reconnecting in 10s\n"); for (int i = 0; i < 10 && !fShutdown; i++) Sleep(1000); } } return true; } // Operator mode only serves the pool; it does not mine solo. if (nMineMode == MINE_OPERATOR) return true; LogPrint("net", "BitcoinMiner started\n"); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); // Build the fast (~2 GB dataset) RandomX mode once, up front, so we're // not stuck mining in the much slower light/cache-only mode forever. if (!RandomXFastReady()) { unsigned int nThreads = std::thread::hardware_concurrency(); RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); } CKey key; key.MakeNewKey(); CBigNum bnExtraNonce = 0; while (fGenerateBitcoins) { Sleep(50); CheckForShutdown(3); while (vNodes.empty() && !fSoloMineTest) { Sleep(1000); CheckForShutdown(3); } unsigned int nTransactionsUpdatedLast = nTransactionsUpdated; CBlockIndex* pindexPrev = pindexBest; unsigned int nBits = GetNextWorkRequired(pindexPrev); // // Create coinbase tx // CTransaction txNew; txNew.vin.resize(1); txNew.vin[0].prevout.SetNull(); txNew.vin[0].scriptSig << nBits << ++bnExtraNonce; txNew.vout.resize(1); // Always pay coinbase to own wallet key. In operator mode, the pool // server distributes shares to miners via SendMoney() after each block. txNew.vout[0].scriptPubKey << key.GetPubKey() << OP_CHECKSIG; // // Create new block // auto_ptr pblock(new CBlock()); if (!pblock.get()) return false; // Add our coinbase tx as first transaction pblock->vtx.push_back(txNew); // Collect the latest transactions into the block int64 nFees = 0; CRITICAL_BLOCK(cs_main) CRITICAL_BLOCK(cs_mapTransactions) { CTxDB txdb("r"); map mapTestPool; vector vfAlreadyAdded(mapTransactions.size()); bool fFoundSomething = true; unsigned int nBlockSize = 0; while (fFoundSomething && nBlockSize < MAX_SIZE/2) { fFoundSomething = false; unsigned int n = 0; for (map::iterator mi = mapTransactions.begin(); mi != mapTransactions.end(); ++mi, ++n) { if (vfAlreadyAdded[n]) continue; CTransaction& tx = (*mi).second; if (tx.IsCoinBase() || !tx.IsFinal()) continue; // Transaction fee requirements, mainly only needed for flood control // Under 10K (about 80 inputs) is free for first 100 transactions // Base rate is 0.01 per KB int64 nMinFee = tx.GetMinFee(pblock->vtx.size() < 100); map mapTestPoolTmp(mapTestPool); if (!tx.ConnectInputs(txdb, mapTestPoolTmp, CDiskTxPos(1,1,1), 0, nFees, false, true, nMinFee)) continue; swap(mapTestPool, mapTestPoolTmp); pblock->vtx.push_back(tx); nBlockSize += ::GetSerializeSize(tx, SER_NETWORK); vfAlreadyAdded[n] = true; fFoundSomething = true; } } } pblock->nBits = nBits; pblock->vtx[0].vout[0].nValue = pblock->GetBlockValue(nBestHeight + 1, nFees); if (LogAcceptsCategory("net")) printf("\n\nRunning BitcoinMiner with %d transactions in block\n", pblock->vtx.size()); // // Prepare the block header // pblock->hashPrevBlock = (pindexPrev ? pindexPrev->GetBlockHash() : 0); pblock->hashMerkleRoot = pblock->BuildMerkleTree(); pblock->nTime = max((pindexPrev ? pindexPrev->GetMedianTimePast()+1 : 0), GetAdjustedTime()); pblock->nBits = nBits; pblock->nNonce = 1; // // Search (memory-hard RandomX PoW) -- each thread uses its own VM // void* rxvm = RandomXCreateMinerVM(); if (!rxvm) { LogPrint("net", "BitcoinMiner: failed to create RandomX VM\n"); Sleep(1000); continue; } LogPrint("net", "BitcoinMiner: mining with RandomX (%s)\n", RandomXFastReady() ? "fast 2GB" : "light 256MB"); unsigned int nStart = GetTime(); uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); loop { uint256 hash = RandomXHashWithVM(rxvm, (const void*)BEGIN(pblock->nVersion), END(pblock->nNonce) - BEGIN(pblock->nVersion)); if (hash <= hashTarget) { //// debug print if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n"); if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); if (LogAcceptsCategory("net")) pblock->print(); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); CRITICAL_BLOCK(cs_main) { // Save key if (!AddKey(key)) { RandomXDestroyMinerVM(rxvm); return false; } key.MakeNewKey(); // Process this block the same as if we had received it from another node if (!ProcessBlock(NULL, pblock.release())) LogPrint("net", "ERROR in BitcoinMiner, ProcessBlock, block not accepted\n"); } SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); Sleep(500); break; } // Check stop conditions periodically (RandomX is slow, so every // ~256 hashes is already plenty of time) if ((++pblock->nNonce & 0xff) == 0) { CheckForShutdown(3); if (pblock->nNonce == 0) break; if (pindexPrev != pindexBest) break; if (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60) break; if (!fGenerateBitcoins) break; pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); } } RandomXDestroyMinerVM(rxvm); } return true; } ////////////////////////////////////////////////////////////////////////////// // // Actions // int64 GetBalance() { int64 nStart, nEnd; QueryPerformanceCounter((LARGE_INTEGER*)&nStart); int64 nTotal = 0; CRITICAL_BLOCK(cs_mapWallet) { for (map::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { CWalletTx* pcoin = &(*it).second; if (!pcoin->IsFinal() || pcoin->fSpent) continue; nTotal += pcoin->GetCredit(); } } QueryPerformanceCounter((LARGE_INTEGER*)&nEnd); ///printf(" GetBalance() time = %16lld\n", nEnd - nStart); return nTotal; } bool SelectCoins(int64 nTargetValue, set& setCoinsRet) { setCoinsRet.clear(); // List of values less than target int64 nLowestLarger = _I64_MAX; CWalletTx* pcoinLowestLarger = NULL; vector > vValue; int64 nTotalLower = 0; CRITICAL_BLOCK(cs_mapWallet) { for (map::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { CWalletTx* pcoin = &(*it).second; if (!pcoin->IsFinal() || pcoin->fSpent) continue; int64 n = pcoin->GetCredit(); if (n <= 0) continue; if (n < nTargetValue) { vValue.push_back(make_pair(n, pcoin)); nTotalLower += n; } else if (n == nTargetValue) { setCoinsRet.insert(pcoin); return true; } else if (n < nLowestLarger) { nLowestLarger = n; pcoinLowestLarger = pcoin; } } } if (nTotalLower < nTargetValue) { if (pcoinLowestLarger == NULL) return false; setCoinsRet.insert(pcoinLowestLarger); return true; } // Solve subset sum by stochastic approximation sort(vValue.rbegin(), vValue.rend()); vector vfIncluded; vector vfBest(vValue.size(), true); int64 nBest = nTotalLower; for (int nRep = 0; nRep < 1000 && nBest != nTargetValue; nRep++) { vfIncluded.assign(vValue.size(), false); int64 nTotal = 0; bool fReachedTarget = false; for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) { for (int i = 0; i < vValue.size(); i++) { if (nPass == 0 ? rand() % 2 : !vfIncluded[i]) { nTotal += vValue[i].first; vfIncluded[i] = true; if (nTotal >= nTargetValue) { fReachedTarget = true; if (nTotal < nBest) { nBest = nTotal; vfBest = vfIncluded; } nTotal -= vValue[i].first; vfIncluded[i] = false; } } } } } // If the next larger is still closer, return it if (pcoinLowestLarger && nLowestLarger - nTargetValue <= nBest - nTargetValue) setCoinsRet.insert(pcoinLowestLarger); else { for (int i = 0; i < vValue.size(); i++) if (vfBest[i]) setCoinsRet.insert(vValue[i].second); //// debug print if (LogAcceptsCategory("net")) printf("SelectCoins() best subset: "); for (int i = 0; i < vValue.size(); i++) if (vfBest[i]) if (LogAcceptsCategory("net")) printf("%s ", FormatMoney(vValue[i].first).c_str()); if (LogAcceptsCategory("net")) printf("total %s\n", FormatMoney(nBest).c_str()); } return true; } bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet) { nFeeRequiredRet = 0; CRITICAL_BLOCK(cs_main) { // txdb must be opened before the mapWallet lock CTxDB txdb("r"); CRITICAL_BLOCK(cs_mapWallet) { int64 nFee = nTransactionFee; loop { wtxNew.vin.clear(); wtxNew.vout.clear(); if (nValue < 0) return false; int64 nValueOut = nValue; nValue += nFee; // Choose coins to use set setCoins; if (!SelectCoins(nValue, setCoins)) return false; int64 nValueIn = 0; foreach(CWalletTx* pcoin, setCoins) nValueIn += pcoin->GetCredit(); // Fill vout[0] to the payee wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey)); // Fill vout[1] back to self with any change if (nValueIn > nValue) { // Use the same key as one of the coins vector vchPubKey; CTransaction& txFirst = *(*setCoins.begin()); foreach(const CTxOut& txout, txFirst.vout) if (txout.IsMine()) if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey)) break; if (vchPubKey.empty()) return false; // Fill vout[1] to ourself CScript scriptPubKey; scriptPubKey << vchPubKey << OP_CHECKSIG; wtxNew.vout.push_back(CTxOut(nValueIn - nValue, scriptPubKey)); } // Fill vin foreach(CWalletTx* pcoin, setCoins) for (int nOut = 0; nOut < pcoin->vout.size(); nOut++) if (pcoin->vout[nOut].IsMine()) wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut)); // Sign int nIn = 0; foreach(CWalletTx* pcoin, setCoins) for (int nOut = 0; nOut < pcoin->vout.size(); nOut++) if (pcoin->vout[nOut].IsMine()) SignSignature(*pcoin, wtxNew, nIn++); // Check that enough fee is included if (nFee < wtxNew.GetMinFee(true)) { nFee = nFeeRequiredRet = wtxNew.GetMinFee(true); continue; } // Fill vtxPrev by copying from previous transactions vtxPrev wtxNew.AddSupportingTransactions(txdb); wtxNew.fTimeReceivedIsTxTime = true; break; } } } return true; } // Undo what CommitTransactionSpent did: unmark the inputs it marked spent // and erase the half-committed wallet entry. Used when a transaction is // accepted into the wallet but then rejected by the mempool/chain, so we // don't end up with a wallet that thinks money is spent that never left. void RollbackTransactionSpent(const CWalletTx& wtxNew) { CRITICAL_BLOCK(cs_main) CRITICAL_BLOCK(cs_mapWallet) { set setCoins; foreach(const CTxIn& txin, wtxNew.vin) { map::iterator mi = mapWallet.find(txin.prevout.hash); if (mi != mapWallet.end()) setCoins.insert(&mi->second); } foreach(CWalletTx* pcoin, setCoins) { pcoin->fSpent = false; pcoin->WriteToDisk(); vWalletUpdated.push_back(make_pair(pcoin->GetHash(), false)); } EraseFromWallet(wtxNew.GetHash()); } MainFrameRepaint(); } // Call after CreateTransaction unless you want to abort bool CommitTransactionSpent(const CWalletTx& wtxNew) { CRITICAL_BLOCK(cs_main) CRITICAL_BLOCK(cs_mapWallet) { //// todo: make this transactional, never want to add a transaction //// without marking spent transactions // Add tx to wallet, because if it has change it's also ours, // otherwise just for transaction history. AddToWallet(wtxNew); // Mark old coins as spent set setCoins; foreach(const CTxIn& txin, wtxNew.vin) setCoins.insert(&mapWallet[txin.prevout.hash]); foreach(CWalletTx* pcoin, setCoins) { pcoin->fSpent = true; pcoin->WriteToDisk(); vWalletUpdated.push_back(make_pair(pcoin->GetHash(), false)); } } MainFrameRepaint(); return true; } bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) { CRITICAL_BLOCK(cs_main) { int64 nFeeRequired; if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired)) { string strError; if (nValue + nFeeRequired > GetBalance()) strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str()); else strError = "Error: Transaction creation failed "; return error("SendMoney() : %s\n", strError.c_str()); } if (!CommitTransactionSpent(wtxNew)) { return error("SendMoney() : Error finalizing transaction"); } if (LogAcceptsCategory("net")) printf("SendMoney: %s\n", wtxNew.GetHash().ToString().substr(0,6).c_str()); // Broadcast if (!wtxNew.AcceptTransaction()) { // The transaction was already signed and committed to the wallet // above, but the chain/mempool just rejected it (e.g. a raced // input already spent by another transaction). Unwind the // wallet state we just wrote instead of crashing the app. RollbackTransactionSpent(wtxNew); return error("SendMoney() : wtxNew.AcceptTransaction() failed"); } wtxNew.RelayWalletTransaction(); } MainFrameRepaint(); return true; }