mirror of
https://github.com/dashpay/dash.git
synced 2024-12-27 04:52:59 +01:00
d9574c2f14
If you copied your wallet and used it on two different machines, the balance reported by getbalance and the sum(listaccounts) could disagree, because you might receive payments for an address that is in your wallet but not your address book. Now all such transactions are credited to the default empty-string account.
4040 lines
126 KiB
C++
4040 lines
126 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file license.txt or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include "headers.h"
|
|
#include "cryptopp/sha.h"
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Global state
|
|
//
|
|
|
|
CCriticalSection cs_main;
|
|
|
|
map<uint256, CTransaction> mapTransactions;
|
|
CCriticalSection cs_mapTransactions;
|
|
unsigned int nTransactionsUpdated = 0;
|
|
map<COutPoint, CInPoint> mapNextTx;
|
|
|
|
map<uint256, CBlockIndex*> mapBlockIndex;
|
|
uint256 hashGenesisBlock("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
|
|
CBigNum bnProofOfWorkLimit(~uint256(0) >> 32);
|
|
CBlockIndex* pindexGenesisBlock = NULL;
|
|
int nBestHeight = -1;
|
|
CBigNum bnBestChainWork = 0;
|
|
CBigNum bnBestInvalidWork = 0;
|
|
uint256 hashBestChain = 0;
|
|
CBlockIndex* pindexBest = NULL;
|
|
int64 nTimeBestReceived = 0;
|
|
|
|
map<uint256, CBlock*> mapOrphanBlocks;
|
|
multimap<uint256, CBlock*> mapOrphanBlocksByPrev;
|
|
|
|
map<uint256, CDataStream*> mapOrphanTransactions;
|
|
multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev;
|
|
|
|
map<uint256, CWalletTx> mapWallet;
|
|
vector<uint256> vWalletUpdated;
|
|
CCriticalSection cs_mapWallet;
|
|
|
|
map<vector<unsigned char>, CPrivKey> mapKeys;
|
|
map<uint160, vector<unsigned char> > mapPubKeys;
|
|
CCriticalSection cs_mapKeys;
|
|
CKey keyUser;
|
|
|
|
map<uint256, int> mapRequestCount;
|
|
CCriticalSection cs_mapRequestCount;
|
|
|
|
map<string, string> mapAddressBook;
|
|
CCriticalSection cs_mapAddressBook;
|
|
|
|
vector<unsigned char> vchDefaultKey;
|
|
|
|
double dHashesPerSec;
|
|
int64 nHPSTimerStart;
|
|
|
|
// Settings
|
|
int fGenerateBitcoins = false;
|
|
int64 nTransactionFee = 0;
|
|
CAddress addrIncoming;
|
|
int fLimitProcessors = false;
|
|
int nLimitProcessors = 1;
|
|
int fMinimizeToTray = true;
|
|
int fMinimizeOnClose = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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<unsigned char> GenerateNewKey()
|
|
{
|
|
RandAddSeedPerfmon();
|
|
CKey key;
|
|
key.MakeNewKey();
|
|
if (!AddKey(key))
|
|
throw runtime_error("GenerateNewKey() : AddKey failed");
|
|
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<map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn));
|
|
CWalletTx& wtx = (*ret.first).second;
|
|
bool fInsertedNew = ret.second;
|
|
if (fInsertedNew)
|
|
wtx.nTimeReceived = GetAdjustedTime();
|
|
|
|
bool fUpdated = false;
|
|
if (!fInsertedNew)
|
|
{
|
|
// Merge
|
|
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;
|
|
}
|
|
}
|
|
|
|
//// debug print
|
|
printf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString().substr(0,10).c_str(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
|
|
|
|
// Write to disk
|
|
if (fInsertedNew || fUpdated)
|
|
if (!wtx.WriteToDisk())
|
|
return false;
|
|
|
|
// If default receiving address gets used, replace it with a new one
|
|
CScript scriptDefaultKey;
|
|
scriptDefaultKey.SetBitcoinAddress(vchDefaultKey);
|
|
foreach(const CTxOut& txout, wtx.vout)
|
|
{
|
|
if (txout.scriptPubKey == scriptDefaultKey)
|
|
{
|
|
CWalletDB walletdb;
|
|
vchDefaultKey = GetKeyFromKeyPool();
|
|
walletdb.WriteDefaultKey(vchDefaultKey);
|
|
walletdb.WriteName(PubKeyToAddress(vchDefaultKey), "");
|
|
}
|
|
}
|
|
|
|
// Notify UI
|
|
vWalletUpdated.push_back(hash);
|
|
}
|
|
|
|
// 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 AddToWalletIfFromMe(const CTransaction& tx, const CBlock* pblock)
|
|
{
|
|
if (tx.IsFromMe() || 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;
|
|
}
|
|
|
|
void WalletUpdateSpent(const COutPoint& prevout)
|
|
{
|
|
// Anytime a signature is successfully verified, it's proof the outpoint is spent.
|
|
// Update the wallet spent flag if it doesn't know due to wallet.dat being
|
|
// restored from backup or the user making copies of wallet.dat.
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
map<uint256, CWalletTx>::iterator mi = mapWallet.find(prevout.hash);
|
|
if (mi != mapWallet.end())
|
|
{
|
|
CWalletTx& wtx = (*mi).second;
|
|
if (!wtx.fSpent && wtx.vout[prevout.n].IsMine())
|
|
{
|
|
printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
|
|
wtx.fSpent = true;
|
|
wtx.WriteToDisk();
|
|
vWalletUpdated.push_back(prevout.hash);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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));
|
|
}
|
|
|
|
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<uint256, CDataStream*>::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 CTransaction::ReadFromDisk(CTxDB& txdb, COutPoint prevout, CTxIndex& txindexRet)
|
|
{
|
|
SetNull();
|
|
if (!txdb.ReadTxIndex(prevout.hash, txindexRet))
|
|
return false;
|
|
if (!ReadFromDisk(txindexRet.pos))
|
|
return false;
|
|
if (prevout.n >= vout.size())
|
|
{
|
|
SetNull();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CTransaction::ReadFromDisk(CTxDB& txdb, COutPoint prevout)
|
|
{
|
|
CTxIndex txindex;
|
|
return ReadFromDisk(txdb, prevout, txindex);
|
|
}
|
|
|
|
bool CTransaction::ReadFromDisk(COutPoint prevout)
|
|
{
|
|
CTxDB txdb("r");
|
|
CTxIndex txindex;
|
|
return ReadFromDisk(txdb, prevout, txindex);
|
|
}
|
|
|
|
bool CTxIn::IsMine() const
|
|
{
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
map<uint256, CWalletTx>::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<uint256, CWalletTx>::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<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
CBlockIndex* pindex = (*mi).second;
|
|
if (pindex)
|
|
return pindex->GetMedianTime();
|
|
}
|
|
}
|
|
return nTimeReceived;
|
|
}
|
|
|
|
int CWalletTx::GetRequestCount() const
|
|
{
|
|
// Returns -1 if it wasn't being tracked
|
|
int nRequests = -1;
|
|
CRITICAL_BLOCK(cs_mapRequestCount)
|
|
{
|
|
if (IsCoinBase())
|
|
{
|
|
// Generated block
|
|
if (hashBlock != 0)
|
|
{
|
|
map<uint256, int>::iterator mi = mapRequestCount.find(hashBlock);
|
|
if (mi != mapRequestCount.end())
|
|
nRequests = (*mi).second;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Did anyone request this transaction?
|
|
map<uint256, int>::iterator mi = mapRequestCount.find(GetHash());
|
|
if (mi != mapRequestCount.end())
|
|
{
|
|
nRequests = (*mi).second;
|
|
|
|
// How about the block it's in?
|
|
if (nRequests == 0 && hashBlock != 0)
|
|
{
|
|
map<uint256, int>::iterator mi = mapRequestCount.find(hashBlock);
|
|
if (mi != mapRequestCount.end())
|
|
nRequests = (*mi).second;
|
|
else
|
|
nRequests = 1; // If it's in someone else's block it must have got out
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nRequests;
|
|
}
|
|
|
|
void CWalletTx::GetAmounts(int64& nGenerated, list<pair<string, int64> >& listReceived,
|
|
list<pair<string, int64> >& listSent, int64& nFee, string& strSentAccount) const
|
|
{
|
|
nGenerated = nFee = 0;
|
|
listReceived.clear();
|
|
listSent.clear();
|
|
strSentAccount = strFromAccount;
|
|
|
|
if (IsCoinBase())
|
|
{
|
|
if (GetDepthInMainChain() >= COINBASE_MATURITY)
|
|
nGenerated = GetCredit();
|
|
return;
|
|
}
|
|
|
|
// Compute fee:
|
|
int64 nDebit = GetDebit();
|
|
if (nDebit > 0) // debit>0 means we signed/sent this transaction
|
|
{
|
|
int64 nValueOut = GetValueOut();
|
|
nFee = nDebit - nValueOut;
|
|
}
|
|
|
|
// Sent/received. Standard client will never generate a send-to-multiple-recipients,
|
|
// but non-standard clients might (so return a list of address/amount pairs)
|
|
foreach(const CTxOut& txout, vout)
|
|
{
|
|
string address;
|
|
uint160 hash160;
|
|
vector<unsigned char> vchPubKey;
|
|
if (ExtractHash160(txout.scriptPubKey, hash160))
|
|
address = Hash160ToAddress(hash160);
|
|
else if (ExtractPubKey(txout.scriptPubKey, false, vchPubKey))
|
|
address = PubKeyToAddress(vchPubKey);
|
|
else
|
|
{
|
|
printf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
|
|
this->GetHash().ToString().c_str());
|
|
address = " unknown ";
|
|
}
|
|
|
|
// Don't report 'change' txouts
|
|
if (nDebit > 0 && txout.IsChange())
|
|
continue;
|
|
|
|
if (nDebit > 0)
|
|
listSent.push_back(make_pair(address, txout.nValue));
|
|
|
|
if (txout.IsMine())
|
|
listReceived.push_back(make_pair(address, txout.nValue));
|
|
}
|
|
|
|
}
|
|
|
|
void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived,
|
|
int64& nSent, int64& nFee) const
|
|
{
|
|
nGenerated = nReceived = nSent = nFee = 0;
|
|
|
|
int64 allGenerated, allFee;
|
|
allGenerated = allFee = 0;
|
|
string strSentAccount;
|
|
list<pair<string, int64> > listReceived;
|
|
list<pair<string, int64> > listSent;
|
|
GetAmounts(allGenerated, listReceived, listSent, allFee, strSentAccount);
|
|
|
|
if (strAccount == "")
|
|
nGenerated = allGenerated;
|
|
if (strAccount == strSentAccount)
|
|
{
|
|
foreach(const PAIRTYPE(string,int64)& s, listSent)
|
|
nSent += s.second;
|
|
nFee = allFee;
|
|
}
|
|
CRITICAL_BLOCK(cs_mapAddressBook)
|
|
{
|
|
foreach(const PAIRTYPE(string,int64)& r, listReceived)
|
|
{
|
|
if (mapAddressBook.count(r.first))
|
|
{
|
|
if (mapAddressBook[r.first] == strAccount)
|
|
{
|
|
nReceived += r.second;
|
|
}
|
|
}
|
|
else if (strAccount.empty())
|
|
{
|
|
nReceived += r.second;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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))
|
|
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;
|
|
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<uint256, CBlockIndex*>::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<uint256> 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<uint256, const CMerkleTx*> mapWalletPrev;
|
|
set<uint256> 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
|
|
{
|
|
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::CheckTransaction() const
|
|
{
|
|
// Basic checks that don't depend on any context
|
|
if (vin.empty() || vout.empty())
|
|
return error("CTransaction::CheckTransaction() : vin or vout empty");
|
|
|
|
// Size limits
|
|
if (::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE)
|
|
return error("CTransaction::CheckTransaction() : size limits failed");
|
|
|
|
// Check for negative or overflow output values
|
|
int64 nValueOut = 0;
|
|
foreach(const CTxOut& txout, vout)
|
|
{
|
|
if (txout.nValue < 0)
|
|
return error("CTransaction::CheckTransaction() : txout.nValue negative");
|
|
if (txout.nValue > MAX_MONEY)
|
|
return error("CTransaction::CheckTransaction() : txout.nValue too high");
|
|
nValueOut += txout.nValue;
|
|
if (!MoneyRange(nValueOut))
|
|
return error("CTransaction::CheckTransaction() : txout total out of range");
|
|
}
|
|
|
|
if (IsCoinBase())
|
|
{
|
|
if (vin[0].scriptSig.size() < 2 || vin[0].scriptSig.size() > 100)
|
|
return error("CTransaction::CheckTransaction() : coinbase script size");
|
|
}
|
|
else
|
|
{
|
|
foreach(const CTxIn& txin, vin)
|
|
if (txin.prevout.IsNull())
|
|
return error("CTransaction::CheckTransaction() : prevout is null");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMissingInputs)
|
|
{
|
|
if (pfMissingInputs)
|
|
*pfMissingInputs = false;
|
|
|
|
if (!CheckTransaction())
|
|
return error("AcceptToMemoryPool() : CheckTransaction failed");
|
|
|
|
// Coinbase is only valid in a block, not as a loose transaction
|
|
if (IsCoinBase())
|
|
return error("AcceptToMemoryPool() : coinbase as individual tx");
|
|
|
|
// To help v0.1.5 clients who would see it as a negative number
|
|
if ((int64)nLockTime > INT_MAX)
|
|
return error("AcceptToMemoryPool() : not accepting nLockTime beyond 2038 yet");
|
|
|
|
// Safety limits
|
|
unsigned int nSize = ::GetSerializeSize(*this, SER_NETWORK);
|
|
if (GetSigOpCount() > 2 || nSize < 100)
|
|
return error("AcceptToMemoryPool() : nonstandard transaction");
|
|
|
|
// Rather not work on nonstandard transactions
|
|
if (!IsStandard())
|
|
return error("AcceptToMemoryPool() : nonstandard transaction type");
|
|
|
|
// 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))
|
|
{
|
|
// Disable replacement feature for now
|
|
return false;
|
|
|
|
// Allow replacing with a newer version of the same transaction
|
|
if (i != 0)
|
|
return false;
|
|
ptxOld = mapNextTx[outpoint].ptx;
|
|
if (ptxOld->IsFinal())
|
|
return false;
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (fCheckInputs)
|
|
{
|
|
// Check against previous transactions
|
|
map<uint256, CTxIndex> mapUnused;
|
|
int64 nFees = 0;
|
|
if (!ConnectInputs(txdb, mapUnused, CDiskTxPos(1,1,1), pindexBest, nFees, false, false))
|
|
{
|
|
if (pfMissingInputs)
|
|
*pfMissingInputs = true;
|
|
return error("AcceptToMemoryPool() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str());
|
|
}
|
|
|
|
// Don't accept it if it can't get into a block
|
|
if (nFees < GetMinFee(1000))
|
|
return error("AcceptToMemoryPool() : not enough fees");
|
|
|
|
// Limit free transactions per 10 minutes
|
|
if (nFees < CENT && GetBoolArg("-limitfreerelay"))
|
|
{
|
|
static int64 nNextReset;
|
|
static int64 nFreeCount;
|
|
if (GetTime() > nNextReset)
|
|
{
|
|
nNextReset = GetTime() + 10 * 60;
|
|
nFreeCount = 0;
|
|
}
|
|
if (nFreeCount > 150000 && !IsFromMe())
|
|
return error("AcceptToMemoryPool() : free transaction rejected by rate limiter");
|
|
nFreeCount += nSize;
|
|
}
|
|
}
|
|
|
|
// Store transaction in memory
|
|
CRITICAL_BLOCK(cs_mapTransactions)
|
|
{
|
|
if (ptxOld)
|
|
{
|
|
printf("AcceptToMemoryPool() : replacing tx %s with new version\n", ptxOld->GetHash().ToString().c_str());
|
|
ptxOld->RemoveFromMemoryPool();
|
|
}
|
|
AddToMemoryPoolUnchecked();
|
|
}
|
|
|
|
///// 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());
|
|
|
|
printf("AcceptToMemoryPool(): accepted %s\n", hash.ToString().substr(0,10).c_str());
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CTransaction::AddToMemoryPoolUnchecked()
|
|
{
|
|
// Add to memory pool without checking anything. Don't call this directly,
|
|
// call AcceptToMemoryPool 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(int& nHeightRet) const
|
|
{
|
|
if (hashBlock == 0 || nIndex == -1)
|
|
return 0;
|
|
|
|
// Find the block it claims to be in
|
|
map<uint256, CBlockIndex*>::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;
|
|
}
|
|
|
|
nHeightRet = pindex->nHeight;
|
|
return pindexBest->nHeight - pindex->nHeight + 1;
|
|
}
|
|
|
|
|
|
int CMerkleTx::GetBlocksToMaturity() const
|
|
{
|
|
if (!IsCoinBase())
|
|
return 0;
|
|
return max(0, (COINBASE_MATURITY+20) - GetDepthInMainChain());
|
|
}
|
|
|
|
|
|
bool CMerkleTx::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs)
|
|
{
|
|
if (fClient)
|
|
{
|
|
if (!IsInMainChain() && !ClientConnectInputs())
|
|
return false;
|
|
return CTransaction::AcceptToMemoryPool(txdb, false);
|
|
}
|
|
else
|
|
{
|
|
return CTransaction::AcceptToMemoryPool(txdb, fCheckInputs);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs)
|
|
{
|
|
CRITICAL_BLOCK(cs_mapTransactions)
|
|
{
|
|
// Add previous supporting transactions first
|
|
foreach(CMerkleTx& tx, vtxPrev)
|
|
{
|
|
if (!tx.IsCoinBase())
|
|
{
|
|
uint256 hash = tx.GetHash();
|
|
if (!mapTransactions.count(hash) && !txdb.ContainsTx(hash))
|
|
tx.AcceptToMemoryPool(txdb, fCheckInputs);
|
|
}
|
|
}
|
|
return AcceptToMemoryPool(txdb, fCheckInputs);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int ScanForWalletTransactions(CBlockIndex* pindexStart)
|
|
{
|
|
int ret = 0;
|
|
|
|
CBlockIndex* pindex = pindexStart;
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
while (pindex)
|
|
{
|
|
CBlock block;
|
|
block.ReadFromDisk(pindex, true);
|
|
foreach(CTransaction& tx, block.vtx)
|
|
{
|
|
uint256 hash = tx.GetHash();
|
|
if (mapWallet.count(hash)) continue;
|
|
AddToWalletIfMine(tx, &block);
|
|
if (mapWallet.count(hash))
|
|
{
|
|
++ret;
|
|
printf("Added missing RECEIVE %s\n", hash.ToString().c_str());
|
|
continue;
|
|
}
|
|
AddToWalletIfFromMe(tx, &block);
|
|
if (mapWallet.count(hash))
|
|
{
|
|
++ret;
|
|
printf("Added missing SEND %s\n", hash.ToString().c_str());
|
|
continue;
|
|
}
|
|
}
|
|
pindex = pindex->pnext;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void ReacceptWalletTransactions()
|
|
{
|
|
CTxDB txdb("r");
|
|
bool fRepeat = true;
|
|
while (fRepeat) CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
fRepeat = false;
|
|
vector<CDiskTxPos> vMissingTx;
|
|
foreach(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
|
|
{
|
|
CWalletTx& wtx = item.second;
|
|
if (wtx.fSpent && wtx.IsCoinBase())
|
|
continue;
|
|
|
|
CTxIndex txindex;
|
|
if (txdb.ReadTxIndex(wtx.GetHash(), txindex))
|
|
{
|
|
// Update fSpent if a tx got spent somewhere else by a copy of wallet.dat
|
|
if (!wtx.fSpent)
|
|
{
|
|
if (txindex.vSpent.size() != wtx.vout.size())
|
|
{
|
|
printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %d != wtx.vout.size() %d\n", txindex.vSpent.size(), wtx.vout.size());
|
|
continue;
|
|
}
|
|
for (int i = 0; i < txindex.vSpent.size(); i++)
|
|
{
|
|
if (!txindex.vSpent[i].IsNull() && wtx.vout[i].IsMine())
|
|
{
|
|
wtx.fSpent = true;
|
|
vMissingTx.push_back(txindex.vSpent[i]);
|
|
}
|
|
}
|
|
if (wtx.fSpent)
|
|
{
|
|
printf("ReacceptWalletTransactions found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
|
|
wtx.WriteToDisk();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Reaccept any txes of ours that aren't already in a block
|
|
if (!wtx.IsCoinBase())
|
|
wtx.AcceptWalletTransaction(txdb, false);
|
|
}
|
|
}
|
|
if (!vMissingTx.empty())
|
|
{
|
|
// TODO: optimize this to scan just part of the block chain?
|
|
if (ScanForWalletTransactions(pindexGenesisBlock))
|
|
fRepeat = true; // Found missing transactions: re-do Reaccept.
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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))
|
|
{
|
|
printf("Relaying wtx %s\n", hash.ToString().substr(0,10).c_str());
|
|
RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResendWalletTransactions()
|
|
{
|
|
// Do this infrequently and randomly to avoid giving away
|
|
// that these are our transactions.
|
|
static int64 nNextTime;
|
|
if (GetTime() < nNextTime)
|
|
return;
|
|
bool fFirst = (nNextTime == 0);
|
|
nNextTime = GetTime() + GetRand(30 * 60);
|
|
if (fFirst)
|
|
return;
|
|
|
|
// Only do it if there's been a new block since last time
|
|
static int64 nLastTime;
|
|
if (nTimeBestReceived < nLastTime)
|
|
return;
|
|
nLastTime = GetTime();
|
|
|
|
// Rebroadcast any of our txes that aren't in a block yet
|
|
printf("ResendWalletTransactions()\n");
|
|
CTxDB txdb("r");
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
// Sort them in chronological order
|
|
multimap<unsigned int, CWalletTx*> mapSorted;
|
|
foreach(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
|
|
{
|
|
CWalletTx& wtx = item.second;
|
|
// Don't rebroadcast until it's had plenty of time that
|
|
// it should have gotten in already by now.
|
|
if (nTimeBestReceived - (int64)wtx.nTimeReceived > 5 * 60)
|
|
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* pindex, bool fReadTransactions)
|
|
{
|
|
if (!fReadTransactions)
|
|
{
|
|
*this = pindex->GetBlockHeader();
|
|
return true;
|
|
}
|
|
if (!ReadFromDisk(pindex->nFile, pindex->nBlockPos, fReadTransactions))
|
|
return false;
|
|
if (GetHash() != pindex->GetBlockHash())
|
|
return error("CBlock::ReadFromDisk() : GetHash() doesn't match index");
|
|
return true;
|
|
}
|
|
|
|
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 GetBlockValue(int nHeight, int64 nFees)
|
|
{
|
|
int64 nSubsidy = 50 * COIN;
|
|
|
|
// Subsidy is cut in half every 4 years
|
|
nSubsidy >>= (nHeight / 210000);
|
|
|
|
return nSubsidy + nFees;
|
|
}
|
|
|
|
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast)
|
|
{
|
|
const int64 nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
|
|
const int64 nTargetSpacing = 10 * 60;
|
|
const int64 nInterval = nTargetTimespan / nTargetSpacing;
|
|
|
|
// 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
|
|
int64 nActualTimespan = pindexLast->GetBlockTime() - pindexFirst->GetBlockTime();
|
|
printf(" nActualTimespan = %"PRI64d" 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
|
|
printf("GetNextWorkRequired RETARGET\n");
|
|
printf("nTargetTimespan = %"PRI64d" nActualTimespan = %"PRI64d"\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 CheckProofOfWork(uint256 hash, unsigned int nBits)
|
|
{
|
|
CBigNum bnTarget;
|
|
bnTarget.SetCompact(nBits);
|
|
|
|
// Check range
|
|
if (bnTarget <= 0 || bnTarget > bnProofOfWorkLimit)
|
|
return error("CheckProofOfWork() : nBits below minimum work");
|
|
|
|
// Check proof of work matches claimed amount
|
|
if (hash > bnTarget.getuint256())
|
|
return error("CheckProofOfWork() : hash doesn't match nBits");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsInitialBlockDownload()
|
|
{
|
|
if (pindexBest == NULL || (!fTestNet && nBestHeight < 74000))
|
|
return true;
|
|
static int64 nLastUpdate;
|
|
static CBlockIndex* pindexLastBest;
|
|
if (pindexBest != pindexLastBest)
|
|
{
|
|
pindexLastBest = pindexBest;
|
|
nLastUpdate = GetTime();
|
|
}
|
|
return (GetTime() - nLastUpdate < 10 &&
|
|
pindexBest->GetBlockTime() < GetTime() - 24 * 60 * 60);
|
|
}
|
|
|
|
void InvalidChainFound(CBlockIndex* pindexNew)
|
|
{
|
|
if (pindexNew->bnChainWork > bnBestInvalidWork)
|
|
{
|
|
bnBestInvalidWork = pindexNew->bnChainWork;
|
|
CTxDB().WriteBestInvalidWork(bnBestInvalidWork);
|
|
MainFrameRepaint();
|
|
}
|
|
printf("InvalidChainFound: invalid block=%s height=%d work=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, pindexNew->bnChainWork.ToString().c_str());
|
|
printf("InvalidChainFound: current best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str());
|
|
if (pindexBest && bnBestInvalidWork > bnBestChainWork + pindexBest->GetBlockWork() * 6)
|
|
printf("InvalidChainFound: WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade.\n");
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
if (!txdb.UpdateTxIndex(prevout.hash, txindex))
|
|
return error("DisconnectInputs() : UpdateTxIndex failed");
|
|
}
|
|
}
|
|
|
|
// Remove transaction from index
|
|
if (!txdb.EraseTxIndex(*this))
|
|
return error("DisconnectInputs() : EraseTxPos failed");
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPool, CDiskTxPos posThisTx,
|
|
CBlockIndex* pindexBlock, 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,10).c_str(), prevout.hash.ToString().substr(0,10).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,10).c_str(), prevout.hash.ToString().substr(0,10).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,10).c_str(), prevout.hash.ToString().substr(0,10).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 prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str());
|
|
|
|
// If prev is coinbase, check that it's matured
|
|
if (txPrev.IsCoinBase())
|
|
for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev)
|
|
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
|
|
return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight);
|
|
|
|
// Verify signature
|
|
if (!VerifySignature(txPrev, *this, i))
|
|
return error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).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,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str());
|
|
|
|
// Check for negative or overflow input values
|
|
nValueIn += txPrev.vout[prevout.n].nValue;
|
|
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
|
|
return error("ConnectInputs() : txin values out of range");
|
|
|
|
// Mark outpoints as spent
|
|
txindex.vSpent[prevout.n] = posThisTx;
|
|
|
|
// Write back
|
|
if (fBlock)
|
|
{
|
|
if (!txdb.UpdateTxIndex(prevout.hash, txindex))
|
|
return error("ConnectInputs() : UpdateTxIndex failed");
|
|
}
|
|
else if (fMiner)
|
|
{
|
|
mapTestPool[prevout.hash] = txindex;
|
|
}
|
|
}
|
|
|
|
if (nValueIn < GetValueOut())
|
|
return error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str());
|
|
|
|
// Tally transaction fees
|
|
int64 nTxFee = nValueIn - GetValueOut();
|
|
if (nTxFee < 0)
|
|
return error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).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, pindexBlock->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 (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
|
|
return error("ClientConnectInputs() : txin values out of range");
|
|
}
|
|
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;
|
|
if (!txdb.WriteBlockIndex(blockindexPrev))
|
|
return error("DisconnectBlock() : WriteBlockIndex failed");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex)
|
|
{
|
|
// Check it again in case a previous version let a bad block in
|
|
if (!CheckBlock())
|
|
return false;
|
|
|
|
//// issue here: it doesn't know the version
|
|
unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - 1 + GetSizeOfCompactSize(vtx.size());
|
|
|
|
map<uint256, CTxIndex> 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, 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();
|
|
if (!txdb.WriteBlockIndex(blockindexPrev))
|
|
return error("ConnectBlock() : WriteBlockIndex failed");
|
|
}
|
|
|
|
// Watch for transactions paying to me
|
|
foreach(CTransaction& tx, vtx)
|
|
AddToWalletIfMine(tx, this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
|
|
{
|
|
printf("REORGANIZE\n");
|
|
|
|
// Find the fork
|
|
CBlockIndex* pfork = pindexBest;
|
|
CBlockIndex* plonger = pindexNew;
|
|
while (pfork != plonger)
|
|
{
|
|
while (plonger->nHeight > pfork->nHeight)
|
|
if (!(plonger = plonger->pprev))
|
|
return error("Reorganize() : plonger->pprev is null");
|
|
if (pfork == plonger)
|
|
break;
|
|
if (!(pfork = pfork->pprev))
|
|
return error("Reorganize() : pfork->pprev is null");
|
|
}
|
|
|
|
// List of what to disconnect
|
|
vector<CBlockIndex*> vDisconnect;
|
|
for (CBlockIndex* pindex = pindexBest; pindex != pfork; pindex = pindex->pprev)
|
|
vDisconnect.push_back(pindex);
|
|
|
|
// List of what to connect
|
|
vector<CBlockIndex*> vConnect;
|
|
for (CBlockIndex* pindex = pindexNew; pindex != pfork; pindex = pindex->pprev)
|
|
vConnect.push_back(pindex);
|
|
reverse(vConnect.begin(), vConnect.end());
|
|
|
|
// Disconnect shorter branch
|
|
vector<CTransaction> vResurrect;
|
|
foreach(CBlockIndex* pindex, vDisconnect)
|
|
{
|
|
CBlock block;
|
|
if (!block.ReadFromDisk(pindex))
|
|
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<CTransaction> vDelete;
|
|
for (int i = 0; i < vConnect.size(); i++)
|
|
{
|
|
CBlockIndex* pindex = vConnect[i];
|
|
CBlock block;
|
|
if (!block.ReadFromDisk(pindex))
|
|
return error("Reorganize() : ReadFromDisk for connect failed");
|
|
if (!block.ConnectBlock(txdb, pindex))
|
|
{
|
|
// Invalid block
|
|
txdb.TxnAbort();
|
|
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");
|
|
|
|
// Make sure it's successfully written to disk before changing memory structure
|
|
if (!txdb.TxnCommit())
|
|
return error("Reorganize() : TxnCommit failed");
|
|
|
|
// 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.AcceptToMemoryPool(txdb, false);
|
|
|
|
// Delete redundant memory transactions that are in the connected branch
|
|
foreach(CTransaction& tx, vDelete)
|
|
tx.RemoveFromMemoryPool();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
|
|
{
|
|
uint256 hash = GetHash();
|
|
|
|
txdb.TxnBegin();
|
|
if (pindexGenesisBlock == NULL && hash == hashGenesisBlock)
|
|
{
|
|
txdb.WriteHashBestChain(hash);
|
|
if (!txdb.TxnCommit())
|
|
return error("SetBestChain() : TxnCommit failed");
|
|
pindexGenesisBlock = pindexNew;
|
|
}
|
|
else if (hashPrevBlock == hashBestChain)
|
|
{
|
|
// Adding to current best branch
|
|
if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash))
|
|
{
|
|
txdb.TxnAbort();
|
|
InvalidChainFound(pindexNew);
|
|
return error("SetBestChain() : ConnectBlock failed");
|
|
}
|
|
if (!txdb.TxnCommit())
|
|
return error("SetBestChain() : TxnCommit failed");
|
|
|
|
// Add to current best branch
|
|
pindexNew->pprev->pnext = pindexNew;
|
|
|
|
// Delete redundant memory transactions
|
|
foreach(CTransaction& tx, vtx)
|
|
tx.RemoveFromMemoryPool();
|
|
}
|
|
else
|
|
{
|
|
// New best branch
|
|
if (!Reorganize(txdb, pindexNew))
|
|
{
|
|
txdb.TxnAbort();
|
|
InvalidChainFound(pindexNew);
|
|
return error("SetBestChain() : Reorganize failed");
|
|
}
|
|
}
|
|
|
|
// New best block
|
|
hashBestChain = hash;
|
|
pindexBest = pindexNew;
|
|
nBestHeight = pindexBest->nHeight;
|
|
bnBestChainWork = pindexNew->bnChainWork;
|
|
nTimeBestReceived = GetTime();
|
|
nTransactionsUpdated++;
|
|
printf("SetBestChain: new best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str());
|
|
|
|
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,20).c_str());
|
|
|
|
// Construct new block index object
|
|
CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this);
|
|
if (!pindexNew)
|
|
return error("AddToBlockIndex() : new CBlockIndex failed");
|
|
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
|
|
pindexNew->phashBlock = &((*mi).first);
|
|
map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(hashPrevBlock);
|
|
if (miPrev != mapBlockIndex.end())
|
|
{
|
|
pindexNew->pprev = (*miPrev).second;
|
|
pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
|
|
}
|
|
pindexNew->bnChainWork = (pindexNew->pprev ? pindexNew->pprev->bnChainWork : 0) + pindexNew->GetBlockWork();
|
|
|
|
CTxDB txdb;
|
|
txdb.TxnBegin();
|
|
txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
|
|
if (!txdb.TxnCommit())
|
|
return false;
|
|
|
|
// New best
|
|
if (pindexNew->bnChainWork > bnBestChainWork)
|
|
if (!SetBestChain(txdb, pindexNew))
|
|
return false;
|
|
|
|
txdb.Close();
|
|
|
|
if (pindexNew == pindexBest)
|
|
{
|
|
// Notify UI to display prev block's coinbase if it was ours
|
|
static uint256 hashPrevBestCoinBase;
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
vWalletUpdated.push_back(hashPrevBestCoinBase);
|
|
hashPrevBestCoinBase = vtx[0].GetHash();
|
|
}
|
|
|
|
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_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK) > MAX_BLOCK_SIZE)
|
|
return error("CheckBlock() : size limits failed");
|
|
|
|
// Check proof of work matches claimed amount
|
|
if (!CheckProofOfWork(GetHash(), nBits))
|
|
return error("CheckBlock() : proof of work failed");
|
|
|
|
// Check timestamp
|
|
if (GetBlockTime() > 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");
|
|
|
|
// Check that it's not full of nonstandard transactions
|
|
if (GetSigOpCount() > MAX_BLOCK_SIGOPS)
|
|
return error("CheckBlock() : too many nonstandard transactions");
|
|
|
|
// 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<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrevBlock);
|
|
if (mi == mapBlockIndex.end())
|
|
return error("AcceptBlock() : prev block not found");
|
|
CBlockIndex* pindexPrev = (*mi).second;
|
|
int nHeight = pindexPrev->nHeight+1;
|
|
|
|
// Check proof of work
|
|
if (nBits != GetNextWorkRequired(pindexPrev))
|
|
return error("AcceptBlock() : incorrect proof of work");
|
|
|
|
// Check timestamp against prev
|
|
if (GetBlockTime() <= pindexPrev->GetMedianTimePast())
|
|
return error("AcceptBlock() : block's timestamp is too early");
|
|
|
|
// Check that all transactions are finalized
|
|
foreach(const CTransaction& tx, vtx)
|
|
if (!tx.IsFinal(nHeight, GetBlockTime()))
|
|
return error("AcceptBlock() : contains a non-final transaction");
|
|
|
|
// Check that the block chain matches the known block chain up to a checkpoint
|
|
if (!fTestNet)
|
|
if ((nHeight == 11111 && hash != uint256("0x0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d")) ||
|
|
(nHeight == 33333 && hash != uint256("0x000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6")) ||
|
|
(nHeight == 68555 && hash != uint256("0x00000000001e1b4903550a0b96e9a9405c8a95f387162e4944e8d9fbe501cd6a")) ||
|
|
(nHeight == 70567 && hash != uint256("0x00000000006a49b14bcf27462068f1264c961f11fa2e0eddd2be0791e1d4124a")) ||
|
|
(nHeight == 74000 && hash != uint256("0x0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20")))
|
|
return error("AcceptBlock() : rejected by checkpoint lockin at %d", nHeight);
|
|
|
|
// Write block to history file
|
|
if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK)))
|
|
return error("AcceptBlock() : out of disk space");
|
|
unsigned int nFile = -1;
|
|
unsigned int nBlockPos = 0;
|
|
if (!WriteToDisk(nFile, nBlockPos))
|
|
return error("AcceptBlock() : WriteToDisk failed");
|
|
if (!AddToBlockIndex(nFile, nBlockPos))
|
|
return error("AcceptBlock() : AddToBlockIndex failed");
|
|
|
|
// Relay inventory, but don't relay old inventory during initial block download
|
|
if (hashBestChain == hash)
|
|
CRITICAL_BLOCK(cs_vNodes)
|
|
foreach(CNode* pnode, vNodes)
|
|
if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : 55000))
|
|
pnode->PushInventory(CInv(MSG_BLOCK, hash));
|
|
|
|
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,20).c_str());
|
|
if (mapOrphanBlocks.count(hash))
|
|
return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str());
|
|
|
|
// Preliminary checks
|
|
if (!pblock->CheckBlock())
|
|
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,20).c_str());
|
|
CBlock* pblock2 = new CBlock(*pblock);
|
|
mapOrphanBlocks.insert(make_pair(hash, pblock2));
|
|
mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2));
|
|
|
|
// Ask this guy to fill in what we're missing
|
|
if (pfrom)
|
|
pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2));
|
|
return true;
|
|
}
|
|
|
|
// Store to disk
|
|
if (!pblock->AcceptBlock())
|
|
return error("ProcessBlock() : AcceptBlock FAILED");
|
|
|
|
// Recursively process any orphan blocks that depended on this one
|
|
vector<uint256> vWorkQueue;
|
|
vWorkQueue.push_back(hash);
|
|
for (int i = 0; i < vWorkQueue.size(); i++)
|
|
{
|
|
uint256 hashPrev = vWorkQueue[i];
|
|
for (multimap<uint256, CBlock*>::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<typename Stream>
|
|
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;
|
|
}
|
|
}
|
|
|
|
bool CheckDiskSpace(uint64 nAdditionalBytes)
|
|
{
|
|
uint64 nFreeBytesAvailable = filesystem::space(GetDataDir()).available;
|
|
|
|
// Check for 15MB because database could create another 10MB log file at any time
|
|
if (nFreeBytesAvailable < (uint64)15000000 + nAdditionalBytes)
|
|
{
|
|
fShutdown = true;
|
|
string strMessage = _("Warning: Disk space is low ");
|
|
strMiscWarning = strMessage;
|
|
printf("*** %s\n", strMessage.c_str());
|
|
ThreadSafeMessageBox(strMessage, "Bitcoin", wxOK | wxICON_EXCLAMATION);
|
|
CreateThread(Shutdown, NULL);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode)
|
|
{
|
|
if (nFile == -1)
|
|
return NULL;
|
|
FILE* file = fopen(strprintf("%s/blk%04d.dat", GetDataDir().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)
|
|
{
|
|
if (fTestNet)
|
|
{
|
|
hashGenesisBlock = uint256("0x0000000224b1593e3ff16a0e3b61285bbc393a39f78c8aa48c456142671f7110");
|
|
bnProofOfWorkLimit = CBigNum(~uint256(0) >> 28);
|
|
pchMessageStart[0] = 0xfa;
|
|
pchMessageStart[1] = 0xbf;
|
|
pchMessageStart[2] = 0xb5;
|
|
pchMessageStart[3] = 0xda;
|
|
}
|
|
|
|
//
|
|
// 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:
|
|
// 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
|
|
|
|
// Genesis block
|
|
const char* pszTimestamp = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks";
|
|
CTransaction txNew;
|
|
txNew.vin.resize(1);
|
|
txNew.vout.resize(1);
|
|
txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp));
|
|
txNew.vout[0].nValue = 50 * COIN;
|
|
txNew.vout[0].scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
|
|
CBlock block;
|
|
block.vtx.push_back(txNew);
|
|
block.hashPrevBlock = 0;
|
|
block.hashMerkleRoot = block.BuildMerkleTree();
|
|
block.nVersion = 1;
|
|
block.nTime = 1231006505;
|
|
block.nBits = 0x1d00ffff;
|
|
block.nNonce = 2083236893;
|
|
|
|
if (fTestNet)
|
|
{
|
|
block.nTime = 1279232055;
|
|
block.nBits = 0x1d07fff8;
|
|
block.nNonce = 81622180;
|
|
}
|
|
|
|
//// debug print
|
|
printf("%s\n", block.GetHash().ToString().c_str());
|
|
printf("%s\n", hashGenesisBlock.ToString().c_str());
|
|
printf("%s\n", block.hashMerkleRoot.ToString().c_str());
|
|
assert(block.hashMerkleRoot == uint256("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"));
|
|
block.print();
|
|
assert(block.GetHash() == hashGenesisBlock);
|
|
|
|
// Start new block file
|
|
unsigned int nFile;
|
|
unsigned int nBlockPos;
|
|
if (!block.WriteToDisk(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<CBlockIndex*, vector<CBlockIndex*> > mapNext;
|
|
for (map<uint256, CBlockIndex*>::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<pair<int, CBlockIndex*> > 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);
|
|
printf("%d (%u,%u) %s %s tx %d",
|
|
pindex->nHeight,
|
|
pindex->nFile,
|
|
pindex->nBlockPos,
|
|
block.GetHash().ToString().substr(0,20).c_str(),
|
|
DateTimeStrFormat("%x %H:%M:%S", block.GetBlockTime()).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<CBlockIndex*>& 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]));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CAlert
|
|
//
|
|
|
|
map<uint256, CAlert> mapAlerts;
|
|
CCriticalSection cs_mapAlerts;
|
|
|
|
string GetWarnings(string strFor)
|
|
{
|
|
int nPriority = 0;
|
|
string strStatusBar;
|
|
string strRPC;
|
|
if (GetBoolArg("-testsafemode"))
|
|
strRPC = "test";
|
|
|
|
// Misc warnings like out of disk space and clock is wrong
|
|
if (strMiscWarning != "")
|
|
{
|
|
nPriority = 1000;
|
|
strStatusBar = strMiscWarning;
|
|
}
|
|
|
|
// Longer invalid proof-of-work chain
|
|
if (pindexBest && bnBestInvalidWork > bnBestChainWork + pindexBest->GetBlockWork() * 6)
|
|
{
|
|
nPriority = 2000;
|
|
strStatusBar = strRPC = "WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade.";
|
|
}
|
|
|
|
// Alerts
|
|
CRITICAL_BLOCK(cs_mapAlerts)
|
|
{
|
|
foreach(PAIRTYPE(const uint256, CAlert)& item, mapAlerts)
|
|
{
|
|
const CAlert& alert = item.second;
|
|
if (alert.AppliesToMe() && alert.nPriority > nPriority)
|
|
{
|
|
nPriority = alert.nPriority;
|
|
strStatusBar = alert.strStatusBar;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strFor == "statusbar")
|
|
return strStatusBar;
|
|
else if (strFor == "rpc")
|
|
return strRPC;
|
|
assert(("GetWarnings() : invalid parameter", false));
|
|
return "error";
|
|
}
|
|
|
|
bool CAlert::ProcessAlert()
|
|
{
|
|
if (!CheckSignature())
|
|
return false;
|
|
if (!IsInEffect())
|
|
return false;
|
|
|
|
CRITICAL_BLOCK(cs_mapAlerts)
|
|
{
|
|
// Cancel previous alerts
|
|
for (map<uint256, CAlert>::iterator mi = mapAlerts.begin(); mi != mapAlerts.end();)
|
|
{
|
|
const CAlert& alert = (*mi).second;
|
|
if (Cancels(alert))
|
|
{
|
|
printf("cancelling alert %d\n", alert.nID);
|
|
mapAlerts.erase(mi++);
|
|
}
|
|
else if (!alert.IsInEffect())
|
|
{
|
|
printf("expiring alert %d\n", alert.nID);
|
|
mapAlerts.erase(mi++);
|
|
}
|
|
else
|
|
mi++;
|
|
}
|
|
|
|
// Check if this alert has been cancelled
|
|
foreach(PAIRTYPE(const uint256, CAlert)& item, mapAlerts)
|
|
{
|
|
const CAlert& alert = item.second;
|
|
if (alert.Cancels(*this))
|
|
{
|
|
printf("alert already cancelled by %d\n", alert.nID);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Add to mapAlerts
|
|
mapAlerts.insert(make_pair(GetHash(), *this));
|
|
}
|
|
|
|
printf("accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe());
|
|
MainFrameRepaint();
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Messages
|
|
//
|
|
|
|
|
|
bool AlreadyHave(CTxDB& txdb, const CInv& inv)
|
|
{
|
|
switch (inv.type)
|
|
{
|
|
case MSG_TX: return mapTransactions.count(inv.hash) || mapOrphanTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash);
|
|
case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash);
|
|
}
|
|
// Don't know what it is, just say we already got one
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
// The message start string is designed to be unlikely to occur in normal data.
|
|
// The characters are rarely used upper ascii, not valid as UTF-8, and produce
|
|
// a large 4-byte int at any alignment.
|
|
char pchMessageStart[4] = { 0xf9, 0xbe, 0xb4, 0xd9 };
|
|
|
|
|
|
bool ProcessMessages(CNode* pfrom)
|
|
{
|
|
CDataStream& vRecv = pfrom->vRecv;
|
|
if (vRecv.empty())
|
|
return true;
|
|
//if (fDebug)
|
|
// printf("ProcessMessages(%u bytes)\n", vRecv.size());
|
|
|
|
//
|
|
// Message format
|
|
// (4) message start
|
|
// (12) command
|
|
// (4) size
|
|
// (4) checksum
|
|
// (x) data
|
|
//
|
|
|
|
loop
|
|
{
|
|
// Scan for message start
|
|
CDataStream::iterator pstart = search(vRecv.begin(), vRecv.end(), BEGIN(pchMessageStart), END(pchMessageStart));
|
|
int nHeaderSize = vRecv.GetSerializeSize(CMessageHeader());
|
|
if (vRecv.end() - pstart < nHeaderSize)
|
|
{
|
|
if (vRecv.size() > nHeaderSize)
|
|
{
|
|
printf("\n\nPROCESSMESSAGE MESSAGESTART NOT FOUND\n\n");
|
|
vRecv.erase(vRecv.begin(), vRecv.end() - nHeaderSize);
|
|
}
|
|
break;
|
|
}
|
|
if (pstart - vRecv.begin() > 0)
|
|
printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", pstart - vRecv.begin());
|
|
vRecv.erase(vRecv.begin(), pstart);
|
|
|
|
// Read header
|
|
vector<char> vHeaderSave(vRecv.begin(), vRecv.begin() + nHeaderSize);
|
|
CMessageHeader hdr;
|
|
vRecv >> hdr;
|
|
if (!hdr.IsValid())
|
|
{
|
|
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 > MAX_SIZE)
|
|
{
|
|
printf("ProcessMessage(%s, %u bytes) : nMessageSize > MAX_SIZE\n", strCommand.c_str(), nMessageSize);
|
|
continue;
|
|
}
|
|
if (nMessageSize > vRecv.size())
|
|
{
|
|
// Rewind and wait for rest of message
|
|
vRecv.insert(vRecv.begin(), vHeaderSave.begin(), vHeaderSave.end());
|
|
break;
|
|
}
|
|
|
|
// Checksum
|
|
if (vRecv.GetVersion() >= 209)
|
|
{
|
|
uint256 hash = Hash(vRecv.begin(), vRecv.begin() + nMessageSize);
|
|
unsigned int nChecksum = 0;
|
|
memcpy(&nChecksum, &hash, sizeof(nChecksum));
|
|
if (nChecksum != hdr.nChecksum)
|
|
{
|
|
printf("ProcessMessage(%s, %u bytes) : CHECKSUM ERROR nChecksum=%08x hdr.nChecksum=%08x\n",
|
|
strCommand.c_str(), nMessageSize, nChecksum, hdr.nChecksum);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
{
|
|
CRITICAL_BLOCK(cs_main)
|
|
fRet = ProcessMessage(pfrom, strCommand, vMsg);
|
|
if (fShutdown)
|
|
return true;
|
|
}
|
|
catch (std::ios_base::failure& e)
|
|
{
|
|
if (strstr(e.what(), "end of data"))
|
|
{
|
|
// Allow exceptions from underlength message on vRecv
|
|
printf("ProcessMessage(%s, %u bytes) : Exception '%s' caught, normally caused by a message being shorter than its stated length\n", strCommand.c_str(), nMessageSize, e.what());
|
|
}
|
|
else if (strstr(e.what(), "size too large"))
|
|
{
|
|
// Allow exceptions from overlong size
|
|
printf("ProcessMessage(%s, %u bytes) : Exception '%s' caught\n", strCommand.c_str(), nMessageSize, e.what());
|
|
}
|
|
else
|
|
{
|
|
PrintExceptionContinue(&e, "ProcessMessage()");
|
|
}
|
|
}
|
|
catch (std::exception& e) {
|
|
PrintExceptionContinue(&e, "ProcessMessage()");
|
|
} catch (...) {
|
|
PrintExceptionContinue(NULL, "ProcessMessage()");
|
|
}
|
|
|
|
if (!fRet)
|
|
printf("ProcessMessage(%s, %u bytes) FAILED\n", strCommand.c_str(), nMessageSize);
|
|
}
|
|
|
|
vRecv.Compact();
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
|
{
|
|
static map<unsigned int, vector<unsigned char> > mapReuseKey;
|
|
RandAddSeedPerfmon();
|
|
if (fDebug)
|
|
printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str());
|
|
printf("received: %s (%d bytes)\n", strCommand.c_str(), vRecv.size());
|
|
if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0)
|
|
{
|
|
printf("dropmessagestest DROPPING RECV MESSAGE\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (strCommand == "version")
|
|
{
|
|
// Each connection can only send one version message
|
|
if (pfrom->nVersion != 0)
|
|
return false;
|
|
|
|
int64 nTime;
|
|
CAddress addrMe;
|
|
CAddress addrFrom;
|
|
uint64 nNonce = 1;
|
|
vRecv >> pfrom->nVersion >> pfrom->nServices >> nTime >> addrMe;
|
|
if (pfrom->nVersion == 10300)
|
|
pfrom->nVersion = 300;
|
|
if (pfrom->nVersion >= 106 && !vRecv.empty())
|
|
vRecv >> addrFrom >> nNonce;
|
|
if (pfrom->nVersion >= 106 && !vRecv.empty())
|
|
vRecv >> pfrom->strSubVer;
|
|
if (pfrom->nVersion >= 209 && !vRecv.empty())
|
|
vRecv >> pfrom->nStartingHeight;
|
|
|
|
if (pfrom->nVersion == 0)
|
|
return false;
|
|
|
|
// Disconnect if we connected to ourself
|
|
if (nNonce == nLocalHostNonce && nNonce > 1)
|
|
{
|
|
printf("connected to self at %s, disconnecting\n", pfrom->addr.ToString().c_str());
|
|
pfrom->fDisconnect = true;
|
|
return true;
|
|
}
|
|
|
|
pfrom->fClient = !(pfrom->nServices & NODE_NETWORK);
|
|
|
|
AddTimeData(pfrom->addr.ip, nTime);
|
|
|
|
// Change version
|
|
if (pfrom->nVersion >= 209)
|
|
pfrom->PushMessage("verack");
|
|
pfrom->vSend.SetVersion(min(pfrom->nVersion, VERSION));
|
|
if (pfrom->nVersion < 209)
|
|
pfrom->vRecv.SetVersion(min(pfrom->nVersion, VERSION));
|
|
|
|
if (!pfrom->fInbound)
|
|
{
|
|
// Advertise our address
|
|
if (addrLocalHost.IsRoutable() && !fUseProxy)
|
|
{
|
|
CAddress addr(addrLocalHost);
|
|
addr.nTime = GetAdjustedTime();
|
|
pfrom->PushAddress(addr);
|
|
}
|
|
|
|
// Get recent addresses
|
|
if (pfrom->nVersion >= 31402 || mapAddresses.size() < 1000)
|
|
{
|
|
pfrom->PushMessage("getaddr");
|
|
pfrom->fGetAddr = true;
|
|
}
|
|
}
|
|
|
|
// Ask the first connected node for block updates
|
|
static int nAskedForBlocks;
|
|
if (!pfrom->fClient && (nAskedForBlocks < 1 || vNodes.size() <= 1))
|
|
{
|
|
nAskedForBlocks++;
|
|
pfrom->PushGetBlocks(pindexBest, uint256(0));
|
|
}
|
|
|
|
// Relay alerts
|
|
CRITICAL_BLOCK(cs_mapAlerts)
|
|
foreach(PAIRTYPE(const uint256, CAlert)& item, mapAlerts)
|
|
item.second.RelayTo(pfrom);
|
|
|
|
pfrom->fSuccessfullyConnected = true;
|
|
|
|
printf("version message: version %d, blocks=%d\n", pfrom->nVersion, pfrom->nStartingHeight);
|
|
}
|
|
|
|
|
|
else if (pfrom->nVersion == 0)
|
|
{
|
|
// Must have a version message before anything else
|
|
return false;
|
|
}
|
|
|
|
|
|
else if (strCommand == "verack")
|
|
{
|
|
pfrom->vRecv.SetVersion(min(pfrom->nVersion, VERSION));
|
|
}
|
|
|
|
|
|
else if (strCommand == "addr")
|
|
{
|
|
vector<CAddress> vAddr;
|
|
vRecv >> vAddr;
|
|
|
|
// Don't want addr from older versions unless seeding
|
|
if (pfrom->nVersion < 209)
|
|
return true;
|
|
if (pfrom->nVersion < 31402 && mapAddresses.size() > 1000)
|
|
return true;
|
|
if (vAddr.size() > 1000)
|
|
return error("message addr size() = %d", vAddr.size());
|
|
|
|
// Store the new addresses
|
|
int64 nNow = GetAdjustedTime();
|
|
int64 nSince = nNow - 10 * 60;
|
|
foreach(CAddress& addr, vAddr)
|
|
{
|
|
if (fShutdown)
|
|
return true;
|
|
// ignore IPv6 for now, since it isn't implemented anyway
|
|
if (!addr.IsIPv4())
|
|
continue;
|
|
if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60)
|
|
addr.nTime = nNow - 5 * 24 * 60 * 60;
|
|
AddAddress(addr, 2 * 60 * 60);
|
|
pfrom->AddAddressKnown(addr);
|
|
if (addr.nTime > nSince && !pfrom->fGetAddr && vAddr.size() <= 10 && addr.IsRoutable())
|
|
{
|
|
// Relay to a limited number of other nodes
|
|
CRITICAL_BLOCK(cs_vNodes)
|
|
{
|
|
// Use deterministic randomness to send to the same nodes for 24 hours
|
|
// at a time so the setAddrKnowns of the chosen nodes prevent repeats
|
|
static uint256 hashSalt;
|
|
if (hashSalt == 0)
|
|
RAND_bytes((unsigned char*)&hashSalt, sizeof(hashSalt));
|
|
uint256 hashRand = hashSalt ^ (((int64)addr.ip)<<32) ^ ((GetTime()+addr.ip)/(24*60*60));
|
|
hashRand = Hash(BEGIN(hashRand), END(hashRand));
|
|
multimap<uint256, CNode*> mapMix;
|
|
foreach(CNode* pnode, vNodes)
|
|
{
|
|
if (pnode->nVersion < 31402)
|
|
continue;
|
|
unsigned int nPointer;
|
|
memcpy(&nPointer, &pnode, sizeof(nPointer));
|
|
uint256 hashKey = hashRand ^ nPointer;
|
|
hashKey = Hash(BEGIN(hashKey), END(hashKey));
|
|
mapMix.insert(make_pair(hashKey, pnode));
|
|
}
|
|
int nRelayNodes = 2;
|
|
for (multimap<uint256, CNode*>::iterator mi = mapMix.begin(); mi != mapMix.end() && nRelayNodes-- > 0; ++mi)
|
|
((*mi).second)->PushAddress(addr);
|
|
}
|
|
}
|
|
}
|
|
if (vAddr.size() < 1000)
|
|
pfrom->fGetAddr = false;
|
|
}
|
|
|
|
|
|
else if (strCommand == "inv")
|
|
{
|
|
vector<CInv> vInv;
|
|
vRecv >> vInv;
|
|
if (vInv.size() > 50000)
|
|
return error("message inv size() = %d", vInv.size());
|
|
|
|
CTxDB txdb("r");
|
|
foreach(const CInv& inv, vInv)
|
|
{
|
|
if (fShutdown)
|
|
return true;
|
|
pfrom->AddInventoryKnown(inv);
|
|
|
|
bool fAlreadyHave = AlreadyHave(txdb, inv);
|
|
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->PushGetBlocks(pindexBest, GetOrphanRoot(mapOrphanBlocks[inv.hash]));
|
|
|
|
// Track requests for our stuff
|
|
CRITICAL_BLOCK(cs_mapRequestCount)
|
|
{
|
|
map<uint256, int>::iterator mi = mapRequestCount.find(inv.hash);
|
|
if (mi != mapRequestCount.end())
|
|
(*mi).second++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "getdata")
|
|
{
|
|
vector<CInv> vInv;
|
|
vRecv >> vInv;
|
|
if (vInv.size() > 50000)
|
|
return error("message getdata size() = %d", vInv.size());
|
|
|
|
foreach(const CInv& inv, vInv)
|
|
{
|
|
if (fShutdown)
|
|
return true;
|
|
printf("received getdata for: %s\n", inv.ToString().c_str());
|
|
|
|
if (inv.type == MSG_BLOCK)
|
|
{
|
|
// Send block from disk
|
|
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(inv.hash);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
CBlock block;
|
|
block.ReadFromDisk((*mi).second);
|
|
pfrom->PushMessage("block", block);
|
|
|
|
// Trigger them to send a getblocks request for the next batch of inventory
|
|
if (inv.hash == pfrom->hashContinue)
|
|
{
|
|
// Bypass PushInventory, this must send even if redundant,
|
|
// and we want it right after the last block so they don't
|
|
// wait for other stuff first.
|
|
vector<CInv> vInv;
|
|
vInv.push_back(CInv(MSG_BLOCK, hashBestChain));
|
|
pfrom->PushMessage("inv", vInv);
|
|
pfrom->hashContinue = 0;
|
|
}
|
|
}
|
|
}
|
|
else if (inv.IsKnownType())
|
|
{
|
|
// Send stream from relay memory
|
|
CRITICAL_BLOCK(cs_mapRelay)
|
|
{
|
|
map<CInv, CDataStream>::iterator mi = mapRelay.find(inv);
|
|
if (mi != mapRelay.end())
|
|
pfrom->PushMessage(inv.GetCommand(), (*mi).second);
|
|
}
|
|
}
|
|
|
|
// Track requests for our stuff
|
|
CRITICAL_BLOCK(cs_mapRequestCount)
|
|
{
|
|
map<uint256, int>::iterator mi = mapRequestCount.find(inv.hash);
|
|
if (mi != mapRequestCount.end())
|
|
(*mi).second++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "getblocks")
|
|
{
|
|
CBlockLocator locator;
|
|
uint256 hashStop;
|
|
vRecv >> locator >> hashStop;
|
|
|
|
// Find the last block the caller has in the main chain
|
|
CBlockIndex* pindex = locator.GetBlockIndex();
|
|
|
|
// Send the rest of the chain
|
|
if (pindex)
|
|
pindex = pindex->pnext;
|
|
int nLimit = 500 + locator.GetDistanceBack();
|
|
printf("getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,20).c_str(), nLimit);
|
|
for (; pindex; pindex = pindex->pnext)
|
|
{
|
|
if (pindex->GetBlockHash() == hashStop)
|
|
{
|
|
printf(" getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str());
|
|
break;
|
|
}
|
|
pfrom->PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash()));
|
|
if (--nLimit <= 0)
|
|
{
|
|
// When this block is requested, we'll send an inv that'll make them
|
|
// getblocks the next batch of inventory.
|
|
printf(" getblocks stopping at limit %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str());
|
|
pfrom->hashContinue = pindex->GetBlockHash();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "getheaders")
|
|
{
|
|
CBlockLocator locator;
|
|
uint256 hashStop;
|
|
vRecv >> locator >> hashStop;
|
|
|
|
CBlockIndex* pindex = NULL;
|
|
if (locator.IsNull())
|
|
{
|
|
// If locator is null, return the hashStop block
|
|
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashStop);
|
|
if (mi == mapBlockIndex.end())
|
|
return true;
|
|
pindex = (*mi).second;
|
|
}
|
|
else
|
|
{
|
|
// Find the last block the caller has in the main chain
|
|
pindex = locator.GetBlockIndex();
|
|
if (pindex)
|
|
pindex = pindex->pnext;
|
|
}
|
|
|
|
vector<CBlock> vHeaders;
|
|
int nLimit = 2000 + locator.GetDistanceBack();
|
|
printf("getheaders %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,20).c_str(), nLimit);
|
|
for (; pindex; pindex = pindex->pnext)
|
|
{
|
|
vHeaders.push_back(pindex->GetBlockHeader());
|
|
if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop)
|
|
break;
|
|
}
|
|
pfrom->PushMessage("headers", vHeaders);
|
|
}
|
|
|
|
|
|
else if (strCommand == "tx")
|
|
{
|
|
vector<uint256> vWorkQueue;
|
|
CDataStream vMsg(vRecv);
|
|
CTransaction tx;
|
|
vRecv >> tx;
|
|
|
|
CInv inv(MSG_TX, tx.GetHash());
|
|
pfrom->AddInventoryKnown(inv);
|
|
|
|
bool fMissingInputs = false;
|
|
if (tx.AcceptToMemoryPool(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<uint256, CDataStream*>::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.AcceptToMemoryPool(true))
|
|
{
|
|
printf(" accepted orphan tx %s\n", inv.hash.ToString().substr(0,10).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)
|
|
{
|
|
printf("storing orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
|
|
AddOrphanTx(vMsg);
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "block")
|
|
{
|
|
CBlock block;
|
|
vRecv >> block;
|
|
|
|
printf("received block %s\n", block.GetHash().ToString().substr(0,20).c_str());
|
|
// block.print();
|
|
|
|
CInv inv(MSG_BLOCK, block.GetHash());
|
|
pfrom->AddInventoryKnown(inv);
|
|
|
|
if (ProcessBlock(pfrom, &block))
|
|
mapAlreadyAskedFor.erase(inv);
|
|
}
|
|
|
|
|
|
else if (strCommand == "getaddr")
|
|
{
|
|
// Nodes rebroadcast an addr every 24 hours
|
|
pfrom->vAddrToSend.clear();
|
|
int64 nSince = GetAdjustedTime() - 3 * 60 * 60; // in the last 3 hours
|
|
CRITICAL_BLOCK(cs_mapAddresses)
|
|
{
|
|
unsigned int nCount = 0;
|
|
foreach(const PAIRTYPE(vector<unsigned char>, CAddress)& item, mapAddresses)
|
|
{
|
|
const CAddress& addr = item.second;
|
|
if (addr.nTime > nSince)
|
|
nCount++;
|
|
}
|
|
foreach(const PAIRTYPE(vector<unsigned char>, CAddress)& item, mapAddresses)
|
|
{
|
|
const CAddress& addr = item.second;
|
|
if (addr.nTime > nSince && GetRand(nCount) < 2500)
|
|
pfrom->PushAddress(addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "checkorder")
|
|
{
|
|
uint256 hashReply;
|
|
vRecv >> hashReply;
|
|
|
|
if (!GetBoolArg("-allowreceivebyip"))
|
|
{
|
|
pfrom->PushMessage("reply", hashReply, (int)2, string(""));
|
|
return true;
|
|
}
|
|
|
|
CWalletTx order;
|
|
vRecv >> 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] = GetKeyFromKeyPool();
|
|
|
|
// 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;
|
|
vRecv >> hashReply;
|
|
|
|
if (!GetBoolArg("-allowreceivebyip"))
|
|
{
|
|
pfrom->PushMessage("reply", hashReply, (int)2);
|
|
return true;
|
|
}
|
|
|
|
CWalletTx wtxNew;
|
|
vRecv >> wtxNew;
|
|
wtxNew.fFromMe = false;
|
|
|
|
// 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<uint256, CRequestTracker>::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 if (strCommand == "ping")
|
|
{
|
|
}
|
|
|
|
|
|
else if (strCommand == "alert")
|
|
{
|
|
CAlert alert;
|
|
vRecv >> alert;
|
|
|
|
if (alert.ProcessAlert())
|
|
{
|
|
// Relay
|
|
pfrom->setKnown.insert(alert.GetHash());
|
|
CRITICAL_BLOCK(cs_vNodes)
|
|
foreach(CNode* pnode, vNodes)
|
|
alert.RelayTo(pnode);
|
|
}
|
|
}
|
|
|
|
|
|
else
|
|
{
|
|
// Ignore unknown commands for extensibility
|
|
}
|
|
|
|
|
|
// Update the last seen time for this node's address
|
|
if (pfrom->fNetworkNode)
|
|
if (strCommand == "version" || strCommand == "addr" || strCommand == "inv" || strCommand == "getdata" || strCommand == "ping")
|
|
AddressCurrentlyConnected(pfrom->addr);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool SendMessages(CNode* pto, bool fSendTrickle)
|
|
{
|
|
CRITICAL_BLOCK(cs_main)
|
|
{
|
|
// Don't send anything until we get their version message
|
|
if (pto->nVersion == 0)
|
|
return true;
|
|
|
|
// Keep-alive ping
|
|
if (pto->nLastSend && GetTime() - pto->nLastSend > 30 * 60 && pto->vSend.empty())
|
|
pto->PushMessage("ping");
|
|
|
|
// Resend wallet transactions that haven't gotten in a block yet
|
|
ResendWalletTransactions();
|
|
|
|
// Address refresh broadcast
|
|
static int64 nLastRebroadcast;
|
|
if (GetTime() - nLastRebroadcast > 24 * 60 * 60)
|
|
{
|
|
nLastRebroadcast = GetTime();
|
|
CRITICAL_BLOCK(cs_vNodes)
|
|
{
|
|
foreach(CNode* pnode, vNodes)
|
|
{
|
|
// Periodically clear setAddrKnown to allow refresh broadcasts
|
|
pnode->setAddrKnown.clear();
|
|
|
|
// Rebroadcast our address
|
|
if (addrLocalHost.IsRoutable() && !fUseProxy)
|
|
{
|
|
CAddress addr(addrLocalHost);
|
|
addr.nTime = GetAdjustedTime();
|
|
pnode->PushAddress(addr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear out old addresses periodically so it's not too much work at once
|
|
static int64 nLastClear;
|
|
if (nLastClear == 0)
|
|
nLastClear = GetTime();
|
|
if (GetTime() - nLastClear > 10 * 60 && vNodes.size() >= 3)
|
|
{
|
|
nLastClear = GetTime();
|
|
CRITICAL_BLOCK(cs_mapAddresses)
|
|
{
|
|
CAddrDB addrdb;
|
|
int64 nSince = GetAdjustedTime() - 14 * 24 * 60 * 60;
|
|
for (map<vector<unsigned char>, CAddress>::iterator mi = mapAddresses.begin();
|
|
mi != mapAddresses.end();)
|
|
{
|
|
const CAddress& addr = (*mi).second;
|
|
if (addr.nTime < nSince)
|
|
{
|
|
if (mapAddresses.size() < 1000 || GetTime() > nLastClear + 20)
|
|
break;
|
|
addrdb.EraseAddress(addr);
|
|
mapAddresses.erase(mi++);
|
|
}
|
|
else
|
|
mi++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Message: addr
|
|
//
|
|
if (fSendTrickle)
|
|
{
|
|
vector<CAddress> vAddr;
|
|
vAddr.reserve(pto->vAddrToSend.size());
|
|
foreach(const CAddress& addr, pto->vAddrToSend)
|
|
{
|
|
// returns true if wasn't already contained in the set
|
|
if (pto->setAddrKnown.insert(addr).second)
|
|
{
|
|
vAddr.push_back(addr);
|
|
// receiver rejects addr messages larger than 1000
|
|
if (vAddr.size() >= 1000)
|
|
{
|
|
pto->PushMessage("addr", vAddr);
|
|
vAddr.clear();
|
|
}
|
|
}
|
|
}
|
|
pto->vAddrToSend.clear();
|
|
if (!vAddr.empty())
|
|
pto->PushMessage("addr", vAddr);
|
|
}
|
|
|
|
|
|
//
|
|
// Message: inventory
|
|
//
|
|
vector<CInv> vInv;
|
|
vector<CInv> vInvWait;
|
|
CRITICAL_BLOCK(pto->cs_inventory)
|
|
{
|
|
vInv.reserve(pto->vInventoryToSend.size());
|
|
vInvWait.reserve(pto->vInventoryToSend.size());
|
|
foreach(const CInv& inv, pto->vInventoryToSend)
|
|
{
|
|
if (pto->setInventoryKnown.count(inv))
|
|
continue;
|
|
|
|
// trickle out tx inv to protect privacy
|
|
if (inv.type == MSG_TX && !fSendTrickle)
|
|
{
|
|
// 1/4 of tx invs blast to all immediately
|
|
static uint256 hashSalt;
|
|
if (hashSalt == 0)
|
|
RAND_bytes((unsigned char*)&hashSalt, sizeof(hashSalt));
|
|
uint256 hashRand = inv.hash ^ hashSalt;
|
|
hashRand = Hash(BEGIN(hashRand), END(hashRand));
|
|
bool fTrickleWait = ((hashRand & 3) != 0);
|
|
|
|
// always trickle our own transactions
|
|
if (!fTrickleWait)
|
|
{
|
|
TRY_CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
map<uint256, CWalletTx>::iterator mi = mapWallet.find(inv.hash);
|
|
if (mi != mapWallet.end())
|
|
{
|
|
CWalletTx& wtx = (*mi).second;
|
|
if (wtx.fFromMe)
|
|
fTrickleWait = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fTrickleWait)
|
|
{
|
|
vInvWait.push_back(inv);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// returns true if wasn't already contained in the set
|
|
if (pto->setInventoryKnown.insert(inv).second)
|
|
{
|
|
vInv.push_back(inv);
|
|
if (vInv.size() >= 1000)
|
|
{
|
|
pto->PushMessage("inv", vInv);
|
|
vInv.clear();
|
|
}
|
|
}
|
|
}
|
|
pto->vInventoryToSend = vInvWait;
|
|
}
|
|
if (!vInv.empty())
|
|
pto->PushMessage("inv", vInv);
|
|
|
|
|
|
//
|
|
// Message: getdata
|
|
//
|
|
vector<CInv> vGetData;
|
|
int64 nNow = GetTime() * 1000000;
|
|
CTxDB txdb("r");
|
|
while (!pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow)
|
|
{
|
|
const CInv& inv = (*pto->mapAskFor.begin()).second;
|
|
if (!AlreadyHave(txdb, inv))
|
|
{
|
|
printf("sending getdata: %s\n", inv.ToString().c_str());
|
|
vGetData.push_back(inv);
|
|
if (vGetData.size() >= 1000)
|
|
{
|
|
pto->PushMessage("getdata", vGetData);
|
|
vGetData.clear();
|
|
}
|
|
}
|
|
pto->mapAskFor.erase(pto->mapAskFor.begin());
|
|
}
|
|
if (!vGetData.empty())
|
|
pto->PushMessage("getdata", vGetData);
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// BitcoinMiner
|
|
//
|
|
|
|
void GenerateBitcoins(bool fGenerate)
|
|
{
|
|
if (fGenerateBitcoins != fGenerate)
|
|
{
|
|
fGenerateBitcoins = fGenerate;
|
|
CWalletDB().WriteSetting("fGenerateBitcoins", fGenerateBitcoins);
|
|
MainFrameRepaint();
|
|
}
|
|
if (fGenerateBitcoins)
|
|
{
|
|
int nProcessors = boost::thread::hardware_concurrency();
|
|
printf("%d processors\n", nProcessors);
|
|
if (nProcessors < 1)
|
|
nProcessors = 1;
|
|
if (fLimitProcessors && nProcessors > nLimitProcessors)
|
|
nProcessors = nLimitProcessors;
|
|
int nAddThreads = nProcessors - vnThreadsRunning[3];
|
|
printf("Starting %d BitcoinMiner threads\n", nAddThreads);
|
|
for (int i = 0; i < nAddThreads; i++)
|
|
{
|
|
if (!CreateThread(ThreadBitcoinMiner, NULL))
|
|
printf("Error: CreateThread(ThreadBitcoinMiner) failed\n");
|
|
Sleep(10);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ThreadBitcoinMiner(void* parg)
|
|
{
|
|
try
|
|
{
|
|
vnThreadsRunning[3]++;
|
|
BitcoinMiner();
|
|
vnThreadsRunning[3]--;
|
|
}
|
|
catch (std::exception& e) {
|
|
vnThreadsRunning[3]--;
|
|
PrintException(&e, "ThreadBitcoinMiner()");
|
|
} catch (...) {
|
|
vnThreadsRunning[3]--;
|
|
PrintException(NULL, "ThreadBitcoinMiner()");
|
|
}
|
|
UIThreadCall(boost::bind(CalledSetStatusBar, "", 0));
|
|
nHPSTimerStart = 0;
|
|
if (vnThreadsRunning[3] == 0)
|
|
dHashesPerSec = 0;
|
|
printf("ThreadBitcoinMiner exiting, %d threads remaining\n", vnThreadsRunning[3]);
|
|
}
|
|
|
|
#if defined(__GNUC__) && defined(CRYPTOPP_X86_ASM_AVAILABLE)
|
|
void CallCPUID(int in, int& aret, int& cret)
|
|
{
|
|
int a, c;
|
|
asm (
|
|
"mov %2, %%eax; " // in into eax
|
|
"cpuid;"
|
|
"mov %%eax, %0;" // eax into a
|
|
"mov %%ecx, %1;" // ecx into c
|
|
:"=r"(a),"=r"(c) /* output */
|
|
:"r"(in) /* input */
|
|
:"%eax","%ecx" /* clobbered register */
|
|
);
|
|
aret = a;
|
|
cret = c;
|
|
}
|
|
|
|
bool Detect128BitSSE2()
|
|
{
|
|
int a, c, nBrand;
|
|
CallCPUID(0, a, nBrand);
|
|
bool fIntel = (nBrand == 0x6c65746e); // ntel
|
|
bool fAMD = (nBrand == 0x444d4163); // cAMD
|
|
|
|
struct
|
|
{
|
|
unsigned int nStepping : 4;
|
|
unsigned int nModel : 4;
|
|
unsigned int nFamily : 4;
|
|
unsigned int nProcessorType : 2;
|
|
unsigned int nUnused : 2;
|
|
unsigned int nExtendedModel : 4;
|
|
unsigned int nExtendedFamily : 8;
|
|
}
|
|
cpu;
|
|
CallCPUID(1, a, c);
|
|
memcpy(&cpu, &a, sizeof(cpu));
|
|
int nFamily = cpu.nExtendedFamily + cpu.nFamily;
|
|
int nModel = cpu.nExtendedModel*16 + cpu.nModel;
|
|
|
|
// We need Intel Nehalem or AMD K10 or better for 128bit SSE2
|
|
// Nehalem = i3/i5/i7 and some Xeon
|
|
// K10 = Opterons with 4 or more cores, Phenom, Phenom II, Athlon II
|
|
// Intel Core i5 family 6, model 26 or 30
|
|
// Intel Core i7 family 6, model 26 or 30
|
|
// Intel Core i3 family 6, model 37
|
|
// AMD Phenom family 16, model 10
|
|
bool fUseSSE2 = ((fIntel && nFamily * 10000 + nModel >= 60026) ||
|
|
(fAMD && nFamily * 10000 + nModel >= 160010));
|
|
|
|
// AMD reports a lower model number in 64-bit mode
|
|
if (fAMD && sizeof(void*) > 4 && nFamily * 10000 + nModel >= 160000)
|
|
fUseSSE2 = true;
|
|
|
|
static bool fPrinted;
|
|
if (!fPrinted)
|
|
{
|
|
fPrinted = true;
|
|
printf("CPUID %08x family %d, model %d, stepping %d, fUseSSE2=%d\n", nBrand, nFamily, nModel, cpu.nStepping, fUseSSE2);
|
|
}
|
|
return fUseSSE2;
|
|
}
|
|
#else
|
|
bool Detect128BitSSE2() { return false; }
|
|
#endif
|
|
|
|
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 const unsigned int pSHA256InitState[8] =
|
|
{0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
|
|
|
|
inline void SHA256Transform(void* pstate, void* pinput, const void* pinit)
|
|
{
|
|
memcpy(pstate, pinit, 32);
|
|
CryptoPP::SHA256::Transform((CryptoPP::word32*)pstate, (CryptoPP::word32*)pinput);
|
|
}
|
|
|
|
//
|
|
// ScanHash scans nonces looking for a hash with at least some zero bits.
|
|
// It operates on big endian data. Caller does the byte reversing.
|
|
// All input buffers are 16-byte aligned. nNonce is usually preserved
|
|
// between calls, but periodically or if nNonce is 0xffff0000 or above,
|
|
// the block is rebuilt and nNonce starts over at zero.
|
|
//
|
|
unsigned int ScanHash_CryptoPP(char* pmidstate, char* pdata, char* phash1, char* phash, unsigned int& nHashesDone)
|
|
{
|
|
unsigned int& nNonce = *(unsigned int*)(pdata + 12);
|
|
for (;;)
|
|
{
|
|
// Crypto++ SHA-256
|
|
// Hash pdata using pmidstate as the starting state into
|
|
// preformatted buffer phash1, then hash phash1 into phash
|
|
nNonce++;
|
|
SHA256Transform(phash1, pdata, pmidstate);
|
|
SHA256Transform(phash, phash1, pSHA256InitState);
|
|
|
|
// Return the nonce if the hash has at least some zero bits,
|
|
// caller will check if it has enough to reach the target
|
|
if (((unsigned short*)phash)[14] == 0)
|
|
return nNonce;
|
|
|
|
// If nothing found after trying for a while, return -1
|
|
if ((nNonce & 0xffff) == 0)
|
|
{
|
|
nHashesDone = 0xffff+1;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern unsigned int ScanHash_4WaySSE2(char* pmidstate, char* pblock, char* phash1, char* phash, unsigned int& nHashesDone);
|
|
|
|
|
|
|
|
class COrphan
|
|
{
|
|
public:
|
|
CTransaction* ptx;
|
|
set<uint256> setDependsOn;
|
|
double dPriority;
|
|
|
|
COrphan(CTransaction* ptxIn)
|
|
{
|
|
ptx = ptxIn;
|
|
dPriority = 0;
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("COrphan(hash=%s, dPriority=%.1f)\n", ptx->GetHash().ToString().substr(0,10).c_str(), dPriority);
|
|
foreach(uint256 hash, setDependsOn)
|
|
printf(" setDependsOn %s\n", hash.ToString().substr(0,10).c_str());
|
|
}
|
|
};
|
|
|
|
|
|
CBlock* CreateNewBlock(CReserveKey& reservekey)
|
|
{
|
|
CBlockIndex* pindexPrev = pindexBest;
|
|
|
|
// Create new block
|
|
auto_ptr<CBlock> pblock(new CBlock());
|
|
if (!pblock.get())
|
|
return NULL;
|
|
|
|
// Create coinbase tx
|
|
CTransaction txNew;
|
|
txNew.vin.resize(1);
|
|
txNew.vin[0].prevout.SetNull();
|
|
txNew.vout.resize(1);
|
|
txNew.vout[0].scriptPubKey << reservekey.GetReservedKey() << OP_CHECKSIG;
|
|
|
|
// Add our coinbase tx as first transaction
|
|
pblock->vtx.push_back(txNew);
|
|
|
|
// Collect memory pool transactions into the block
|
|
int64 nFees = 0;
|
|
CRITICAL_BLOCK(cs_main)
|
|
CRITICAL_BLOCK(cs_mapTransactions)
|
|
{
|
|
CTxDB txdb("r");
|
|
|
|
// Priority order to process transactions
|
|
list<COrphan> vOrphan; // list memory doesn't move
|
|
map<uint256, vector<COrphan*> > mapDependers;
|
|
multimap<double, CTransaction*> mapPriority;
|
|
for (map<uint256, CTransaction>::iterator mi = mapTransactions.begin(); mi != mapTransactions.end(); ++mi)
|
|
{
|
|
CTransaction& tx = (*mi).second;
|
|
if (tx.IsCoinBase() || !tx.IsFinal())
|
|
continue;
|
|
|
|
COrphan* porphan = NULL;
|
|
double dPriority = 0;
|
|
foreach(const CTxIn& txin, tx.vin)
|
|
{
|
|
// Read prev transaction
|
|
CTransaction txPrev;
|
|
CTxIndex txindex;
|
|
if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
|
|
{
|
|
// Has to wait for dependencies
|
|
if (!porphan)
|
|
{
|
|
// Use list for automatic deletion
|
|
vOrphan.push_back(COrphan(&tx));
|
|
porphan = &vOrphan.back();
|
|
}
|
|
mapDependers[txin.prevout.hash].push_back(porphan);
|
|
porphan->setDependsOn.insert(txin.prevout.hash);
|
|
continue;
|
|
}
|
|
int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
|
|
|
|
// Read block header
|
|
int nConf = 0;
|
|
CBlock block;
|
|
if (block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
|
|
{
|
|
map<uint256, CBlockIndex*>::iterator it = mapBlockIndex.find(block.GetHash());
|
|
if (it != mapBlockIndex.end())
|
|
{
|
|
CBlockIndex* pindex = (*it).second;
|
|
if (pindex->IsInMainChain())
|
|
nConf = 1 + nBestHeight - pindex->nHeight;
|
|
}
|
|
}
|
|
|
|
dPriority += (double)nValueIn * nConf;
|
|
|
|
if (fDebug && GetBoolArg("-printpriority"))
|
|
printf("priority nValueIn=%-12I64d nConf=%-5d dPriority=%-20.1f\n", nValueIn, nConf, dPriority);
|
|
}
|
|
|
|
// Priority is sum(valuein * age) / txsize
|
|
dPriority /= ::GetSerializeSize(tx, SER_NETWORK);
|
|
|
|
if (porphan)
|
|
porphan->dPriority = dPriority;
|
|
else
|
|
mapPriority.insert(make_pair(-dPriority, &(*mi).second));
|
|
|
|
if (fDebug && GetBoolArg("-printpriority"))
|
|
{
|
|
printf("priority %-20.1f %s\n%s", dPriority, tx.GetHash().ToString().substr(0,10).c_str(), tx.ToString().c_str());
|
|
if (porphan)
|
|
porphan->print();
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
// Collect transactions into block
|
|
map<uint256, CTxIndex> mapTestPool;
|
|
uint64 nBlockSize = 1000;
|
|
int nBlockSigOps = 100;
|
|
while (!mapPriority.empty())
|
|
{
|
|
// Take highest priority transaction off priority queue
|
|
double dPriority = -(*mapPriority.begin()).first;
|
|
CTransaction& tx = *(*mapPriority.begin()).second;
|
|
mapPriority.erase(mapPriority.begin());
|
|
|
|
// Size limits
|
|
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
|
|
if (nBlockSize + nTxSize >= MAX_BLOCK_SIZE_GEN)
|
|
continue;
|
|
int nTxSigOps = tx.GetSigOpCount();
|
|
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
|
|
continue;
|
|
|
|
// Transaction fee required depends on block size
|
|
bool fAllowFree = (nBlockSize + nTxSize < 4000 || dPriority > COIN * 144 / 250);
|
|
int64 nMinFee = tx.GetMinFee(nBlockSize, fAllowFree);
|
|
|
|
// Connecting shouldn't fail due to dependency on other memory pool transactions
|
|
// because we're already processing them in order of dependency
|
|
map<uint256, CTxIndex> mapTestPoolTmp(mapTestPool);
|
|
if (!tx.ConnectInputs(txdb, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, nFees, false, true, nMinFee))
|
|
continue;
|
|
swap(mapTestPool, mapTestPoolTmp);
|
|
|
|
// Added
|
|
pblock->vtx.push_back(tx);
|
|
nBlockSize += nTxSize;
|
|
nBlockSigOps += nTxSigOps;
|
|
|
|
// Add transactions that depend on this one to the priority queue
|
|
uint256 hash = tx.GetHash();
|
|
if (mapDependers.count(hash))
|
|
{
|
|
foreach(COrphan* porphan, mapDependers[hash])
|
|
{
|
|
if (!porphan->setDependsOn.empty())
|
|
{
|
|
porphan->setDependsOn.erase(hash);
|
|
if (porphan->setDependsOn.empty())
|
|
mapPriority.insert(make_pair(-porphan->dPriority, porphan->ptx));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees);
|
|
|
|
// Fill in header
|
|
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
|
|
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
|
|
pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
|
|
pblock->nBits = GetNextWorkRequired(pindexPrev);
|
|
pblock->nNonce = 0;
|
|
|
|
return pblock.release();
|
|
}
|
|
|
|
|
|
void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce, int64& nPrevTime)
|
|
{
|
|
// Update nExtraNonce
|
|
int64 nNow = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
|
|
if (++nExtraNonce >= 0x7f && nNow > nPrevTime+1)
|
|
{
|
|
nExtraNonce = 1;
|
|
nPrevTime = nNow;
|
|
}
|
|
pblock->vtx[0].vin[0].scriptSig = CScript() << pblock->nBits << CBigNum(nExtraNonce);
|
|
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
|
|
}
|
|
|
|
|
|
void FormatHashBuffers(CBlock* pblock, char* pmidstate, char* pdata, char* phash1)
|
|
{
|
|
//
|
|
// Prebuild hash buffers
|
|
//
|
|
struct
|
|
{
|
|
struct unnamed2
|
|
{
|
|
int nVersion;
|
|
uint256 hashPrevBlock;
|
|
uint256 hashMerkleRoot;
|
|
unsigned int nTime;
|
|
unsigned int nBits;
|
|
unsigned int nNonce;
|
|
}
|
|
block;
|
|
unsigned char pchPadding0[64];
|
|
uint256 hash1;
|
|
unsigned char pchPadding1[64];
|
|
}
|
|
tmp;
|
|
memset(&tmp, 0, sizeof(tmp));
|
|
|
|
tmp.block.nVersion = pblock->nVersion;
|
|
tmp.block.hashPrevBlock = pblock->hashPrevBlock;
|
|
tmp.block.hashMerkleRoot = pblock->hashMerkleRoot;
|
|
tmp.block.nTime = pblock->nTime;
|
|
tmp.block.nBits = pblock->nBits;
|
|
tmp.block.nNonce = pblock->nNonce;
|
|
|
|
FormatHashBlocks(&tmp.block, sizeof(tmp.block));
|
|
FormatHashBlocks(&tmp.hash1, sizeof(tmp.hash1));
|
|
|
|
// Byte swap all the input buffer
|
|
for (int i = 0; i < sizeof(tmp)/4; i++)
|
|
((unsigned int*)&tmp)[i] = ByteReverse(((unsigned int*)&tmp)[i]);
|
|
|
|
// Precalc the first half of the first hash, which stays constant
|
|
SHA256Transform(pmidstate, &tmp.block, pSHA256InitState);
|
|
|
|
memcpy(pdata, &tmp.block, 128);
|
|
memcpy(phash1, &tmp.hash1, 64);
|
|
}
|
|
|
|
|
|
bool CheckWork(CBlock* pblock, CReserveKey& reservekey)
|
|
{
|
|
uint256 hash = pblock->GetHash();
|
|
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
|
|
|
|
if (hash > hashTarget)
|
|
return false;
|
|
|
|
//// debug print
|
|
printf("BitcoinMiner:\n");
|
|
printf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
|
|
pblock->print();
|
|
printf("%s ", DateTimeStrFormat("%x %H:%M", GetTime()).c_str());
|
|
printf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue).c_str());
|
|
|
|
// Found a solution
|
|
CRITICAL_BLOCK(cs_main)
|
|
{
|
|
if (pblock->hashPrevBlock != hashBestChain)
|
|
return error("BitcoinMiner : generated block is stale");
|
|
|
|
// Remove key from key pool
|
|
reservekey.KeepKey();
|
|
|
|
// Track how many getdata requests this block gets
|
|
CRITICAL_BLOCK(cs_mapRequestCount)
|
|
mapRequestCount[pblock->GetHash()] = 0;
|
|
|
|
// Process this block the same as if we had received it from another node
|
|
if (!ProcessBlock(NULL, pblock))
|
|
return error("BitcoinMiner : ProcessBlock, block not accepted");
|
|
}
|
|
|
|
Sleep(2000);
|
|
return true;
|
|
}
|
|
|
|
|
|
void BitcoinMiner()
|
|
{
|
|
printf("BitcoinMiner started\n");
|
|
SetThreadPriority(THREAD_PRIORITY_LOWEST);
|
|
bool f4WaySSE2 = Detect128BitSSE2();
|
|
if (mapArgs.count("-4way"))
|
|
f4WaySSE2 = GetBoolArg(mapArgs["-4way"]);
|
|
|
|
// Each thread has its own key and counter
|
|
CReserveKey reservekey;
|
|
unsigned int nExtraNonce = 0;
|
|
int64 nPrevTime = 0;
|
|
|
|
while (fGenerateBitcoins)
|
|
{
|
|
if (AffinityBugWorkaround(ThreadBitcoinMiner))
|
|
return;
|
|
if (fShutdown)
|
|
return;
|
|
while (vNodes.empty() || IsInitialBlockDownload())
|
|
{
|
|
Sleep(1000);
|
|
if (fShutdown)
|
|
return;
|
|
if (!fGenerateBitcoins)
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Create new block
|
|
//
|
|
unsigned int nTransactionsUpdatedLast = nTransactionsUpdated;
|
|
CBlockIndex* pindexPrev = pindexBest;
|
|
|
|
auto_ptr<CBlock> pblock(CreateNewBlock(reservekey));
|
|
if (!pblock.get())
|
|
return;
|
|
IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce, nPrevTime);
|
|
|
|
printf("Running BitcoinMiner with %d transactions in block\n", pblock->vtx.size());
|
|
|
|
|
|
//
|
|
// Prebuild hash buffers
|
|
//
|
|
char pmidstatebuf[32+16]; char* pmidstate = alignup<16>(pmidstatebuf);
|
|
char pdatabuf[128+16]; char* pdata = alignup<16>(pdatabuf);
|
|
char phash1buf[64+16]; char* phash1 = alignup<16>(phash1buf);
|
|
|
|
FormatHashBuffers(pblock.get(), pmidstate, pdata, phash1);
|
|
|
|
unsigned int& nBlockTime = *(unsigned int*)(pdata + 64 + 4);
|
|
unsigned int& nBlockNonce = *(unsigned int*)(pdata + 64 + 12);
|
|
|
|
|
|
//
|
|
// Search
|
|
//
|
|
int64 nStart = GetTime();
|
|
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
|
|
uint256 hashbuf[2];
|
|
uint256& hash = *alignup<16>(hashbuf);
|
|
loop
|
|
{
|
|
unsigned int nHashesDone = 0;
|
|
unsigned int nNonceFound;
|
|
|
|
#ifdef FOURWAYSSE2
|
|
if (f4WaySSE2)
|
|
// tcatm's 4-way 128-bit SSE2 SHA-256
|
|
nNonceFound = ScanHash_4WaySSE2(pmidstate, pdata + 64, phash1, (char*)&hash, nHashesDone);
|
|
else
|
|
#endif
|
|
// Crypto++ SHA-256
|
|
nNonceFound = ScanHash_CryptoPP(pmidstate, pdata + 64, phash1, (char*)&hash, nHashesDone);
|
|
|
|
// Check if something found
|
|
if (nNonceFound != -1)
|
|
{
|
|
for (int i = 0; i < sizeof(hash)/4; i++)
|
|
((unsigned int*)&hash)[i] = ByteReverse(((unsigned int*)&hash)[i]);
|
|
|
|
if (hash <= hashTarget)
|
|
{
|
|
// Found a solution
|
|
pblock->nNonce = ByteReverse(nNonceFound);
|
|
assert(hash == pblock->GetHash());
|
|
|
|
SetThreadPriority(THREAD_PRIORITY_NORMAL);
|
|
CheckWork(pblock.get(), reservekey);
|
|
SetThreadPriority(THREAD_PRIORITY_LOWEST);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Meter hashes/sec
|
|
static int64 nHashCounter;
|
|
if (nHPSTimerStart == 0)
|
|
{
|
|
nHPSTimerStart = GetTimeMillis();
|
|
nHashCounter = 0;
|
|
}
|
|
else
|
|
nHashCounter += nHashesDone;
|
|
if (GetTimeMillis() - nHPSTimerStart > 4000)
|
|
{
|
|
static CCriticalSection cs;
|
|
CRITICAL_BLOCK(cs)
|
|
{
|
|
if (GetTimeMillis() - nHPSTimerStart > 4000)
|
|
{
|
|
dHashesPerSec = 1000.0 * nHashCounter / (GetTimeMillis() - nHPSTimerStart);
|
|
nHPSTimerStart = GetTimeMillis();
|
|
nHashCounter = 0;
|
|
string strStatus = strprintf(" %.0f khash/s", dHashesPerSec/1000.0);
|
|
UIThreadCall(boost::bind(CalledSetStatusBar, strStatus, 0));
|
|
static int64 nLogTime;
|
|
if (GetTime() - nLogTime > 30 * 60)
|
|
{
|
|
nLogTime = GetTime();
|
|
printf("%s ", DateTimeStrFormat("%x %H:%M", GetTime()).c_str());
|
|
printf("hashmeter %3d CPUs %6.0f khash/s\n", vnThreadsRunning[3], dHashesPerSec/1000.0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for stop or if block needs to be rebuilt
|
|
if (fShutdown)
|
|
return;
|
|
if (!fGenerateBitcoins)
|
|
return;
|
|
if (fLimitProcessors && vnThreadsRunning[3] > nLimitProcessors)
|
|
return;
|
|
if (vNodes.empty())
|
|
break;
|
|
if (nBlockNonce >= 0xffff0000)
|
|
break;
|
|
if (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)
|
|
break;
|
|
if (pindexPrev != pindexBest)
|
|
break;
|
|
|
|
// Update nTime every few seconds
|
|
pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
|
|
nBlockTime = ByteReverse(pblock->nTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Actions
|
|
//
|
|
|
|
|
|
int64 GetBalance()
|
|
{
|
|
int64 nStart = GetTimeMillis();
|
|
|
|
int64 nTotal = 0;
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
|
|
{
|
|
CWalletTx* pcoin = &(*it).second;
|
|
if (!pcoin->IsFinal() || pcoin->fSpent || !pcoin->IsConfirmed())
|
|
continue;
|
|
nTotal += pcoin->GetCredit();
|
|
}
|
|
}
|
|
|
|
//printf("GetBalance() %"PRI64d"ms\n", GetTimeMillis() - nStart);
|
|
return nTotal;
|
|
}
|
|
|
|
|
|
bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, set<CWalletTx*>& setCoinsRet)
|
|
{
|
|
setCoinsRet.clear();
|
|
|
|
// List of values less than target
|
|
int64 nLowestLarger = INT64_MAX;
|
|
CWalletTx* pcoinLowestLarger = NULL;
|
|
vector<pair<int64, CWalletTx*> > vValue;
|
|
int64 nTotalLower = 0;
|
|
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
vector<CWalletTx*> vCoins;
|
|
vCoins.reserve(mapWallet.size());
|
|
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
|
|
vCoins.push_back(&(*it).second);
|
|
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
|
|
|
|
foreach(CWalletTx* pcoin, vCoins)
|
|
{
|
|
if (!pcoin->IsFinal() || pcoin->fSpent || !pcoin->IsConfirmed())
|
|
continue;
|
|
|
|
int nDepth = pcoin->GetDepthInMainChain();
|
|
if (nDepth < (pcoin->IsFromMe() ? nConfMine : nConfTheirs))
|
|
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<char> vfIncluded;
|
|
vector<char> 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
|
|
printf("SelectCoins() best subset: ");
|
|
for (int i = 0; i < vValue.size(); i++)
|
|
if (vfBest[i])
|
|
printf("%s ", FormatMoney(vValue[i].first).c_str());
|
|
printf("total %s\n", FormatMoney(nBest).c_str());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SelectCoins(int64 nTargetValue, set<CWalletTx*>& setCoinsRet)
|
|
{
|
|
return (SelectCoinsMinConf(nTargetValue, 1, 6, setCoinsRet) ||
|
|
SelectCoinsMinConf(nTargetValue, 1, 1, setCoinsRet) ||
|
|
SelectCoinsMinConf(nTargetValue, 0, 1, setCoinsRet));
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet)
|
|
{
|
|
CRITICAL_BLOCK(cs_main)
|
|
{
|
|
// txdb must be opened before the mapWallet lock
|
|
CTxDB txdb("r");
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
nFeeRet = nTransactionFee;
|
|
loop
|
|
{
|
|
wtxNew.vin.clear();
|
|
wtxNew.vout.clear();
|
|
wtxNew.fFromMe = true;
|
|
if (nValue < 0)
|
|
return false;
|
|
int64 nValueOut = nValue;
|
|
int64 nTotalValue = nValue + nFeeRet;
|
|
|
|
// Choose coins to use
|
|
set<CWalletTx*> setCoins;
|
|
if (!SelectCoins(nTotalValue, setCoins))
|
|
return false;
|
|
int64 nValueIn = 0;
|
|
foreach(CWalletTx* pcoin, setCoins)
|
|
nValueIn += pcoin->GetCredit();
|
|
|
|
// Fill a vout to the payee
|
|
bool fChangeFirst = GetRand(2);
|
|
if (!fChangeFirst)
|
|
wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey));
|
|
|
|
// Fill a vout back to self with any change
|
|
int64 nChange = nValueIn - nTotalValue;
|
|
if (nChange >= CENT)
|
|
{
|
|
// Note: We use a new key here to keep it from being obvious which side is the change.
|
|
// The drawback is that by not reusing a previous key, the change may be lost if a
|
|
// backup is restored, if the backup doesn't have the new private key for the change.
|
|
// If we reused the old key, it would be possible to add code to look for and
|
|
// rediscover unknown transactions that were written with keys of ours to recover
|
|
// post-backup change.
|
|
|
|
// Reserve a new key pair from key pool
|
|
vector<unsigned char> vchPubKey = reservekey.GetReservedKey();
|
|
assert(mapKeys.count(vchPubKey));
|
|
|
|
// Fill a vout to ourself, using same address type as the payment
|
|
CScript scriptChange;
|
|
if (scriptPubKey.GetBitcoinAddressHash160() != 0)
|
|
scriptChange.SetBitcoinAddress(vchPubKey);
|
|
else
|
|
scriptChange << vchPubKey << OP_CHECKSIG;
|
|
wtxNew.vout.push_back(CTxOut(nChange, scriptChange));
|
|
}
|
|
else
|
|
reservekey.ReturnKey();
|
|
|
|
// Fill a vout to the payee
|
|
if (fChangeFirst)
|
|
wtxNew.vout.push_back(CTxOut(nValueOut, 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())
|
|
if (!SignSignature(*pcoin, wtxNew, nIn++))
|
|
return false;
|
|
|
|
// Limit size
|
|
unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK);
|
|
if (nBytes >= MAX_BLOCK_SIZE_GEN/5)
|
|
return false;
|
|
|
|
// Check that enough fee is included
|
|
int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000);
|
|
int64 nMinFee = wtxNew.GetMinFee();
|
|
if (nFeeRet < max(nPayFee, nMinFee))
|
|
{
|
|
nFeeRet = max(nPayFee, nMinFee);
|
|
continue;
|
|
}
|
|
|
|
// Fill vtxPrev by copying from previous transactions vtxPrev
|
|
wtxNew.AddSupportingTransactions(txdb);
|
|
wtxNew.fTimeReceivedIsTxTime = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Call after CreateTransaction unless you want to abort
|
|
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
|
|
{
|
|
CRITICAL_BLOCK(cs_main)
|
|
{
|
|
printf("CommitTransaction:\n%s", wtxNew.ToString().c_str());
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
// This is only to keep the database open to defeat the auto-flush for the
|
|
// duration of this scope. This is the only place where this optimization
|
|
// maybe makes sense; please don't do it anywhere else.
|
|
CWalletDB walletdb("r");
|
|
|
|
// Take key pair from key pool so it won't be used again
|
|
reservekey.KeepKey();
|
|
|
|
// 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<CWalletTx*> 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(pcoin->GetHash());
|
|
}
|
|
}
|
|
|
|
// Track how many getdata requests our transaction gets
|
|
CRITICAL_BLOCK(cs_mapRequestCount)
|
|
mapRequestCount[wtxNew.GetHash()] = 0;
|
|
|
|
// Broadcast
|
|
if (!wtxNew.AcceptToMemoryPool())
|
|
{
|
|
// This must not fail. The transaction has already been signed and recorded.
|
|
printf("CommitTransaction() : Error: Transaction not valid");
|
|
return false;
|
|
}
|
|
wtxNew.RelayWalletTransaction();
|
|
}
|
|
MainFrameRepaint();
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee)
|
|
{
|
|
CRITICAL_BLOCK(cs_main)
|
|
{
|
|
CReserveKey reservekey;
|
|
int64 nFeeRequired;
|
|
if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, 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 ");
|
|
printf("SendMoney() : %s", strError.c_str());
|
|
return strError;
|
|
}
|
|
|
|
if (fAskFee && !ThreadSafeAskFee(nFeeRequired, _("Sending..."), NULL))
|
|
return "ABORTED";
|
|
|
|
if (!CommitTransaction(wtxNew, reservekey))
|
|
return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.");
|
|
}
|
|
MainFrameRepaint();
|
|
return "";
|
|
}
|
|
|
|
|
|
|
|
string SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWalletTx& wtxNew, bool fAskFee)
|
|
{
|
|
// Check amount
|
|
if (nValue <= 0)
|
|
return _("Invalid amount");
|
|
if (nValue + nTransactionFee > GetBalance())
|
|
return _("Insufficient funds");
|
|
|
|
// Parse bitcoin address
|
|
CScript scriptPubKey;
|
|
if (!scriptPubKey.SetBitcoinAddress(strAddress))
|
|
return _("Invalid bitcoin address");
|
|
|
|
return SendMoney(scriptPubKey, nValue, wtxNew, fAskFee);
|
|
}
|