mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-19 18:09:47 +01:00
40cd036941
faster initial block download -- version 0.3.3
1438 lines
35 KiB
C++
1438 lines
35 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.
|
|
|
|
class COutPoint;
|
|
class CInPoint;
|
|
class CDiskTxPos;
|
|
class CCoinBase;
|
|
class CTxIn;
|
|
class CTxOut;
|
|
class CTransaction;
|
|
class CBlock;
|
|
class CBlockIndex;
|
|
class CWalletTx;
|
|
class CKeyItem;
|
|
|
|
static const unsigned int MAX_SIZE = 0x02000000;
|
|
static const unsigned int MAX_BLOCK_SIZE = 1000000;
|
|
static const int64 COIN = 100000000;
|
|
static const int64 CENT = 1000000;
|
|
static const int COINBASE_MATURITY = 100;
|
|
|
|
static const CBigNum bnProofOfWorkLimit(~uint256(0) >> 32);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extern CCriticalSection cs_main;
|
|
extern map<uint256, CBlockIndex*> mapBlockIndex;
|
|
extern const uint256 hashGenesisBlock;
|
|
extern CBlockIndex* pindexGenesisBlock;
|
|
extern int nBestHeight;
|
|
extern CBigNum bnBestChainWork;
|
|
extern uint256 hashBestChain;
|
|
extern CBlockIndex* pindexBest;
|
|
extern unsigned int nTransactionsUpdated;
|
|
extern map<uint256, int> mapRequestCount;
|
|
extern CCriticalSection cs_mapRequestCount;
|
|
extern map<string, string> mapAddressBook;
|
|
extern CCriticalSection cs_mapAddressBook;
|
|
extern vector<unsigned char> vchDefaultKey;
|
|
|
|
// Settings
|
|
extern int fGenerateBitcoins;
|
|
extern int64 nTransactionFee;
|
|
extern CAddress addrIncoming;
|
|
extern int fLimitProcessors;
|
|
extern int nLimitProcessors;
|
|
extern int fMinimizeToTray;
|
|
extern int fMinimizeOnClose;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CheckDiskSpace(int64 nAdditionalBytes=0);
|
|
FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode="rb");
|
|
FILE* AppendBlockFile(unsigned int& nFileRet);
|
|
bool AddKey(const CKey& key);
|
|
vector<unsigned char> GenerateNewKey();
|
|
bool AddToWallet(const CWalletTx& wtxIn);
|
|
void WalletUpdateSpent(const COutPoint& prevout);
|
|
void ReacceptWalletTransactions();
|
|
bool LoadBlockIndex(bool fAllowNew=true);
|
|
void PrintBlockTree();
|
|
bool ProcessMessages(CNode* pfrom);
|
|
bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv);
|
|
bool SendMessages(CNode* pto, bool fSendTrickle);
|
|
int64 GetBalance();
|
|
bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CKey& keyRet, int64& nFeeRequiredRet);
|
|
bool CommitTransaction(CWalletTx& wtxNew, const CKey& key);
|
|
bool BroadcastTransaction(CWalletTx& wtxNew);
|
|
string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false);
|
|
string SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false);
|
|
void GenerateBitcoins(bool fGenerate);
|
|
void ThreadBitcoinMiner(void* parg);
|
|
void BitcoinMiner();
|
|
bool IsInitialBlockDownload();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CDiskTxPos
|
|
{
|
|
public:
|
|
unsigned int nFile;
|
|
unsigned int nBlockPos;
|
|
unsigned int nTxPos;
|
|
|
|
CDiskTxPos()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
CDiskTxPos(unsigned int nFileIn, unsigned int nBlockPosIn, unsigned int nTxPosIn)
|
|
{
|
|
nFile = nFileIn;
|
|
nBlockPos = nBlockPosIn;
|
|
nTxPos = nTxPosIn;
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE( READWRITE(FLATDATA(*this)); )
|
|
void SetNull() { nFile = -1; nBlockPos = 0; nTxPos = 0; }
|
|
bool IsNull() const { return (nFile == -1); }
|
|
|
|
friend bool operator==(const CDiskTxPos& a, const CDiskTxPos& b)
|
|
{
|
|
return (a.nFile == b.nFile &&
|
|
a.nBlockPos == b.nBlockPos &&
|
|
a.nTxPos == b.nTxPos);
|
|
}
|
|
|
|
friend bool operator!=(const CDiskTxPos& a, const CDiskTxPos& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
string ToString() const
|
|
{
|
|
if (IsNull())
|
|
return strprintf("null");
|
|
else
|
|
return strprintf("(nFile=%d, nBlockPos=%d, nTxPos=%d)", nFile, nBlockPos, nTxPos);
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
class CInPoint
|
|
{
|
|
public:
|
|
CTransaction* ptx;
|
|
unsigned int n;
|
|
|
|
CInPoint() { SetNull(); }
|
|
CInPoint(CTransaction* ptxIn, unsigned int nIn) { ptx = ptxIn; n = nIn; }
|
|
void SetNull() { ptx = NULL; n = -1; }
|
|
bool IsNull() const { return (ptx == NULL && n == -1); }
|
|
};
|
|
|
|
|
|
|
|
|
|
class COutPoint
|
|
{
|
|
public:
|
|
uint256 hash;
|
|
unsigned int n;
|
|
|
|
COutPoint() { SetNull(); }
|
|
COutPoint(uint256 hashIn, unsigned int nIn) { hash = hashIn; n = nIn; }
|
|
IMPLEMENT_SERIALIZE( READWRITE(FLATDATA(*this)); )
|
|
void SetNull() { hash = 0; n = -1; }
|
|
bool IsNull() const { return (hash == 0 && n == -1); }
|
|
|
|
friend bool operator<(const COutPoint& a, const COutPoint& b)
|
|
{
|
|
return (a.hash < b.hash || (a.hash == b.hash && a.n < b.n));
|
|
}
|
|
|
|
friend bool operator==(const COutPoint& a, const COutPoint& b)
|
|
{
|
|
return (a.hash == b.hash && a.n == b.n);
|
|
}
|
|
|
|
friend bool operator!=(const COutPoint& a, const COutPoint& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
string ToString() const
|
|
{
|
|
return strprintf("COutPoint(%s, %d)", hash.ToString().substr(0,6).c_str(), n);
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
// An input of a transaction. It contains the location of the previous
|
|
// transaction's output that it claims and a signature that matches the
|
|
// output's public key.
|
|
//
|
|
class CTxIn
|
|
{
|
|
public:
|
|
COutPoint prevout;
|
|
CScript scriptSig;
|
|
unsigned int nSequence;
|
|
|
|
CTxIn()
|
|
{
|
|
nSequence = UINT_MAX;
|
|
}
|
|
|
|
explicit CTxIn(COutPoint prevoutIn, CScript scriptSigIn=CScript(), unsigned int nSequenceIn=UINT_MAX)
|
|
{
|
|
prevout = prevoutIn;
|
|
scriptSig = scriptSigIn;
|
|
nSequence = nSequenceIn;
|
|
}
|
|
|
|
CTxIn(uint256 hashPrevTx, unsigned int nOut, CScript scriptSigIn=CScript(), unsigned int nSequenceIn=UINT_MAX)
|
|
{
|
|
prevout = COutPoint(hashPrevTx, nOut);
|
|
scriptSig = scriptSigIn;
|
|
nSequence = nSequenceIn;
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(prevout);
|
|
READWRITE(scriptSig);
|
|
READWRITE(nSequence);
|
|
)
|
|
|
|
bool IsFinal() const
|
|
{
|
|
return (nSequence == UINT_MAX);
|
|
}
|
|
|
|
friend bool operator==(const CTxIn& a, const CTxIn& b)
|
|
{
|
|
return (a.prevout == b.prevout &&
|
|
a.scriptSig == b.scriptSig &&
|
|
a.nSequence == b.nSequence);
|
|
}
|
|
|
|
friend bool operator!=(const CTxIn& a, const CTxIn& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
string ToString() const
|
|
{
|
|
string str;
|
|
str += strprintf("CTxIn(");
|
|
str += prevout.ToString();
|
|
if (prevout.IsNull())
|
|
str += strprintf(", coinbase %s", HexStr(scriptSig.begin(), scriptSig.end(), false).c_str());
|
|
else
|
|
str += strprintf(", scriptSig=%s", scriptSig.ToString().substr(0,24).c_str());
|
|
if (nSequence != UINT_MAX)
|
|
str += strprintf(", nSequence=%u", nSequence);
|
|
str += ")";
|
|
return str;
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
|
|
bool IsMine() const;
|
|
int64 GetDebit() const;
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
// An output of a transaction. It contains the public key that the next input
|
|
// must be able to sign with to claim it.
|
|
//
|
|
class CTxOut
|
|
{
|
|
public:
|
|
int64 nValue;
|
|
CScript scriptPubKey;
|
|
|
|
public:
|
|
CTxOut()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
CTxOut(int64 nValueIn, CScript scriptPubKeyIn)
|
|
{
|
|
nValue = nValueIn;
|
|
scriptPubKey = scriptPubKeyIn;
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(nValue);
|
|
READWRITE(scriptPubKey);
|
|
)
|
|
|
|
void SetNull()
|
|
{
|
|
nValue = -1;
|
|
scriptPubKey.clear();
|
|
}
|
|
|
|
bool IsNull()
|
|
{
|
|
return (nValue == -1);
|
|
}
|
|
|
|
uint256 GetHash() const
|
|
{
|
|
return SerializeHash(*this);
|
|
}
|
|
|
|
bool IsMine() const
|
|
{
|
|
return ::IsMine(scriptPubKey);
|
|
}
|
|
|
|
int64 GetCredit() const
|
|
{
|
|
if (IsMine())
|
|
return nValue;
|
|
return 0;
|
|
}
|
|
|
|
friend bool operator==(const CTxOut& a, const CTxOut& b)
|
|
{
|
|
return (a.nValue == b.nValue &&
|
|
a.scriptPubKey == b.scriptPubKey);
|
|
}
|
|
|
|
friend bool operator!=(const CTxOut& a, const CTxOut& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
string ToString() const
|
|
{
|
|
if (scriptPubKey.size() < 6)
|
|
return "CTxOut(error)";
|
|
return strprintf("CTxOut(nValue=%"PRI64d".%08"PRI64d", scriptPubKey=%s)", nValue / COIN, nValue % COIN, scriptPubKey.ToString().substr(0,24).c_str());
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
// The basic transaction that is broadcasted on the network and contained in
|
|
// blocks. A transaction can contain multiple inputs and outputs.
|
|
//
|
|
class CTransaction
|
|
{
|
|
public:
|
|
int nVersion;
|
|
vector<CTxIn> vin;
|
|
vector<CTxOut> vout;
|
|
unsigned int nLockTime;
|
|
|
|
|
|
CTransaction()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(this->nVersion);
|
|
nVersion = this->nVersion;
|
|
READWRITE(vin);
|
|
READWRITE(vout);
|
|
READWRITE(nLockTime);
|
|
)
|
|
|
|
void SetNull()
|
|
{
|
|
nVersion = 1;
|
|
vin.clear();
|
|
vout.clear();
|
|
nLockTime = 0;
|
|
}
|
|
|
|
bool IsNull() const
|
|
{
|
|
return (vin.empty() && vout.empty());
|
|
}
|
|
|
|
uint256 GetHash() const
|
|
{
|
|
return SerializeHash(*this);
|
|
}
|
|
|
|
bool IsFinal(int64 nBlockTime=0) const
|
|
{
|
|
// Time based nLockTime implemented in 0.1.6,
|
|
// do not use time based until most 0.1.5 nodes have upgraded.
|
|
if (nLockTime == 0)
|
|
return true;
|
|
if (nBlockTime == 0)
|
|
nBlockTime = GetAdjustedTime();
|
|
if (nLockTime < (nLockTime < 500000000 ? nBestHeight : nBlockTime))
|
|
return true;
|
|
foreach(const CTxIn& txin, vin)
|
|
if (!txin.IsFinal())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool IsNewerThan(const CTransaction& old) const
|
|
{
|
|
if (vin.size() != old.vin.size())
|
|
return false;
|
|
for (int i = 0; i < vin.size(); i++)
|
|
if (vin[i].prevout != old.vin[i].prevout)
|
|
return false;
|
|
|
|
bool fNewer = false;
|
|
unsigned int nLowest = UINT_MAX;
|
|
for (int i = 0; i < vin.size(); i++)
|
|
{
|
|
if (vin[i].nSequence != old.vin[i].nSequence)
|
|
{
|
|
if (vin[i].nSequence <= nLowest)
|
|
{
|
|
fNewer = false;
|
|
nLowest = vin[i].nSequence;
|
|
}
|
|
if (old.vin[i].nSequence < nLowest)
|
|
{
|
|
fNewer = true;
|
|
nLowest = old.vin[i].nSequence;
|
|
}
|
|
}
|
|
}
|
|
return fNewer;
|
|
}
|
|
|
|
bool IsCoinBase() const
|
|
{
|
|
return (vin.size() == 1 && vin[0].prevout.IsNull());
|
|
}
|
|
|
|
bool CheckTransaction() const
|
|
{
|
|
// Basic checks that don't depend on any context
|
|
if (vin.empty() || vout.empty())
|
|
return error("CTransaction::CheckTransaction() : vin or vout empty");
|
|
|
|
// Check for negative values
|
|
foreach(const CTxOut& txout, vout)
|
|
if (txout.nValue < 0)
|
|
return error("CTransaction::CheckTransaction() : txout.nValue negative");
|
|
|
|
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 IsMine() const
|
|
{
|
|
foreach(const CTxOut& txout, vout)
|
|
if (txout.IsMine())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
int64 GetDebit() const
|
|
{
|
|
int64 nDebit = 0;
|
|
foreach(const CTxIn& txin, vin)
|
|
nDebit += txin.GetDebit();
|
|
return nDebit;
|
|
}
|
|
|
|
int64 GetCredit() const
|
|
{
|
|
int64 nCredit = 0;
|
|
foreach(const CTxOut& txout, vout)
|
|
nCredit += txout.GetCredit();
|
|
return nCredit;
|
|
}
|
|
|
|
int64 GetValueOut() const
|
|
{
|
|
int64 nValueOut = 0;
|
|
foreach(const CTxOut& txout, vout)
|
|
{
|
|
if (txout.nValue < 0)
|
|
throw runtime_error("CTransaction::GetValueOut() : negative value");
|
|
nValueOut += txout.nValue;
|
|
}
|
|
return nValueOut;
|
|
}
|
|
|
|
int64 GetMinFee(unsigned int nBlockSize=1) const
|
|
{
|
|
// Base fee is 1 cent per kilobyte
|
|
unsigned int nBytes = ::GetSerializeSize(*this, SER_NETWORK);
|
|
int64 nMinFee = (1 + (int64)nBytes / 1000) * CENT;
|
|
|
|
// Transactions under 60K are free as long as block size is under 80K
|
|
// (about 27,000bc if made of 50bc inputs)
|
|
if (nBytes < 60000 && nBlockSize < 80000)
|
|
nMinFee = 0;
|
|
|
|
// Transactions under 3K are free as long as block size is under 200K
|
|
if (nBytes < 3000 && nBlockSize < 200000)
|
|
nMinFee = 0;
|
|
|
|
// To limit dust spam, require a 0.01 fee if any output is less than 0.01
|
|
if (nMinFee < CENT)
|
|
foreach(const CTxOut& txout, vout)
|
|
if (txout.nValue < CENT)
|
|
nMinFee = CENT;
|
|
|
|
return nMinFee;
|
|
}
|
|
|
|
|
|
|
|
bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=NULL)
|
|
{
|
|
CAutoFile filein = OpenBlockFile(pos.nFile, 0, pfileRet ? "rb+" : "rb");
|
|
if (!filein)
|
|
return error("CTransaction::ReadFromDisk() : OpenBlockFile failed");
|
|
|
|
// Read transaction
|
|
if (fseek(filein, pos.nTxPos, SEEK_SET) != 0)
|
|
return error("CTransaction::ReadFromDisk() : fseek failed");
|
|
filein >> *this;
|
|
|
|
// Return file pointer
|
|
if (pfileRet)
|
|
{
|
|
if (fseek(filein, pos.nTxPos, SEEK_SET) != 0)
|
|
return error("CTransaction::ReadFromDisk() : second fseek failed");
|
|
*pfileRet = filein.release();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
friend bool operator==(const CTransaction& a, const CTransaction& b)
|
|
{
|
|
return (a.nVersion == b.nVersion &&
|
|
a.vin == b.vin &&
|
|
a.vout == b.vout &&
|
|
a.nLockTime == b.nLockTime);
|
|
}
|
|
|
|
friend bool operator!=(const CTransaction& a, const CTransaction& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
|
|
string ToString() const
|
|
{
|
|
string str;
|
|
str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%d, vout.size=%d, nLockTime=%d)\n",
|
|
GetHash().ToString().substr(0,6).c_str(),
|
|
nVersion,
|
|
vin.size(),
|
|
vout.size(),
|
|
nLockTime);
|
|
for (int i = 0; i < vin.size(); i++)
|
|
str += " " + vin[i].ToString() + "\n";
|
|
for (int i = 0; i < vout.size(); i++)
|
|
str += " " + vout[i].ToString() + "\n";
|
|
return str;
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s", ToString().c_str());
|
|
}
|
|
|
|
|
|
|
|
bool DisconnectInputs(CTxDB& txdb);
|
|
bool ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPool, CDiskTxPos posThisTx, int nHeight, int64& nFees, bool fBlock, bool fMiner, int64 nMinFee=0);
|
|
bool ClientConnectInputs();
|
|
|
|
bool AcceptTransaction(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL);
|
|
|
|
bool AcceptTransaction(bool fCheckInputs=true, bool* pfMissingInputs=NULL)
|
|
{
|
|
CTxDB txdb("r");
|
|
return AcceptTransaction(txdb, fCheckInputs, pfMissingInputs);
|
|
}
|
|
|
|
protected:
|
|
bool AddToMemoryPool();
|
|
public:
|
|
bool RemoveFromMemoryPool();
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// A transaction with a merkle branch linking it to the block chain
|
|
//
|
|
class CMerkleTx : public CTransaction
|
|
{
|
|
public:
|
|
uint256 hashBlock;
|
|
vector<uint256> vMerkleBranch;
|
|
int nIndex;
|
|
|
|
// memory only
|
|
mutable bool fMerkleVerified;
|
|
mutable bool fGetCreditCached;
|
|
mutable int64 nGetCreditCached;
|
|
|
|
|
|
CMerkleTx()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
CMerkleTx(const CTransaction& txIn) : CTransaction(txIn)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
hashBlock = 0;
|
|
nIndex = -1;
|
|
fMerkleVerified = false;
|
|
fGetCreditCached = false;
|
|
nGetCreditCached = 0;
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
nSerSize += SerReadWrite(s, *(CTransaction*)this, nType, nVersion, ser_action);
|
|
nVersion = this->nVersion;
|
|
READWRITE(hashBlock);
|
|
READWRITE(vMerkleBranch);
|
|
READWRITE(nIndex);
|
|
)
|
|
|
|
int64 GetCredit(bool fUseCache=false) const
|
|
{
|
|
// Must wait until coinbase is safely deep enough in the chain before valuing it
|
|
if (IsCoinBase() && GetBlocksToMaturity() > 0)
|
|
return 0;
|
|
|
|
// GetBalance can assume transactions in mapWallet won't change
|
|
if (fUseCache && fGetCreditCached)
|
|
return nGetCreditCached;
|
|
nGetCreditCached = CTransaction::GetCredit();
|
|
fGetCreditCached = true;
|
|
return nGetCreditCached;
|
|
}
|
|
|
|
|
|
int SetMerkleBranch(const CBlock* pblock=NULL);
|
|
int GetDepthInMainChain(int& nHeightRet) const;
|
|
int GetDepthInMainChain() const { int nHeight; return GetDepthInMainChain(nHeight); }
|
|
bool IsInMainChain() const { return GetDepthInMainChain() > 0; }
|
|
int GetBlocksToMaturity() const;
|
|
bool AcceptTransaction(CTxDB& txdb, bool fCheckInputs=true);
|
|
bool AcceptTransaction() { CTxDB txdb("r"); return AcceptTransaction(txdb); }
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
// A transaction with a bunch of additional info that only the owner cares
|
|
// about. It includes any unrecorded transactions needed to link it back
|
|
// to the block chain.
|
|
//
|
|
class CWalletTx : public CMerkleTx
|
|
{
|
|
public:
|
|
vector<CMerkleTx> vtxPrev;
|
|
map<string, string> mapValue;
|
|
vector<pair<string, string> > vOrderForm;
|
|
unsigned int fTimeReceivedIsTxTime;
|
|
unsigned int nTimeReceived; // time received by this node
|
|
char fFromMe;
|
|
char fSpent;
|
|
//// probably need to sign the order info so know it came from payer
|
|
|
|
// memory only UI hints
|
|
mutable unsigned int nTimeDisplayed;
|
|
mutable int nLinesDisplayed;
|
|
|
|
|
|
CWalletTx()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
CWalletTx(const CMerkleTx& txIn) : CMerkleTx(txIn)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
CWalletTx(const CTransaction& txIn) : CMerkleTx(txIn)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
fTimeReceivedIsTxTime = false;
|
|
nTimeReceived = 0;
|
|
fFromMe = false;
|
|
fSpent = false;
|
|
nTimeDisplayed = 0;
|
|
nLinesDisplayed = 0;
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
nSerSize += SerReadWrite(s, *(CMerkleTx*)this, nType, nVersion, ser_action);
|
|
nVersion = this->nVersion;
|
|
READWRITE(vtxPrev);
|
|
READWRITE(mapValue);
|
|
READWRITE(vOrderForm);
|
|
READWRITE(fTimeReceivedIsTxTime);
|
|
READWRITE(nTimeReceived);
|
|
READWRITE(fFromMe);
|
|
READWRITE(fSpent);
|
|
)
|
|
|
|
bool WriteToDisk()
|
|
{
|
|
return CWalletDB().WriteTx(GetHash(), *this);
|
|
}
|
|
|
|
|
|
int64 GetTxTime() const;
|
|
int GetRequestCount() const;
|
|
|
|
void AddSupportingTransactions(CTxDB& txdb);
|
|
|
|
bool AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs=true);
|
|
bool AcceptWalletTransaction() { CTxDB txdb("r"); return AcceptWalletTransaction(txdb); }
|
|
|
|
void RelayWalletTransaction(CTxDB& txdb);
|
|
void RelayWalletTransaction() { CTxDB txdb("r"); RelayWalletTransaction(txdb); }
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
// A txdb record that contains the disk location of a transaction and the
|
|
// locations of transactions that spend its outputs. vSpent is really only
|
|
// used as a flag, but having the location is very helpful for debugging.
|
|
//
|
|
class CTxIndex
|
|
{
|
|
public:
|
|
CDiskTxPos pos;
|
|
vector<CDiskTxPos> vSpent;
|
|
|
|
CTxIndex()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
CTxIndex(const CDiskTxPos& posIn, unsigned int nOutputs)
|
|
{
|
|
pos = posIn;
|
|
vSpent.resize(nOutputs);
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
if (!(nType & SER_GETHASH))
|
|
READWRITE(nVersion);
|
|
READWRITE(pos);
|
|
READWRITE(vSpent);
|
|
)
|
|
|
|
void SetNull()
|
|
{
|
|
pos.SetNull();
|
|
vSpent.clear();
|
|
}
|
|
|
|
bool IsNull()
|
|
{
|
|
return pos.IsNull();
|
|
}
|
|
|
|
friend bool operator==(const CTxIndex& a, const CTxIndex& b)
|
|
{
|
|
if (a.pos != b.pos || a.vSpent.size() != b.vSpent.size())
|
|
return false;
|
|
for (int i = 0; i < a.vSpent.size(); i++)
|
|
if (a.vSpent[i] != b.vSpent[i])
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
friend bool operator!=(const CTxIndex& a, const CTxIndex& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Nodes collect new transactions into a block, hash them into a hash tree,
|
|
// and scan through nonce values to make the block's hash satisfy proof-of-work
|
|
// requirements. When they solve the proof-of-work, they broadcast the block
|
|
// to everyone and the block is added to the block chain. The first transaction
|
|
// in the block is a special one that creates a new coin owned by the creator
|
|
// of the block.
|
|
//
|
|
// Blocks are appended to blk0001.dat files on disk. Their location on disk
|
|
// is indexed by CBlockIndex objects in memory.
|
|
//
|
|
class CBlock
|
|
{
|
|
public:
|
|
// header
|
|
int nVersion;
|
|
uint256 hashPrevBlock;
|
|
uint256 hashMerkleRoot;
|
|
unsigned int nTime;
|
|
unsigned int nBits;
|
|
unsigned int nNonce;
|
|
|
|
// network and disk
|
|
vector<CTransaction> vtx;
|
|
|
|
// memory only
|
|
mutable vector<uint256> vMerkleTree;
|
|
|
|
|
|
CBlock()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(this->nVersion);
|
|
nVersion = this->nVersion;
|
|
READWRITE(hashPrevBlock);
|
|
READWRITE(hashMerkleRoot);
|
|
READWRITE(nTime);
|
|
READWRITE(nBits);
|
|
READWRITE(nNonce);
|
|
|
|
// ConnectBlock depends on vtx being last so it can calculate offset
|
|
if (!(nType & (SER_GETHASH|SER_BLOCKHEADERONLY)))
|
|
READWRITE(vtx);
|
|
else if (fRead)
|
|
const_cast<CBlock*>(this)->vtx.clear();
|
|
)
|
|
|
|
void SetNull()
|
|
{
|
|
nVersion = 1;
|
|
hashPrevBlock = 0;
|
|
hashMerkleRoot = 0;
|
|
nTime = 0;
|
|
nBits = 0;
|
|
nNonce = 0;
|
|
vtx.clear();
|
|
vMerkleTree.clear();
|
|
}
|
|
|
|
bool IsNull() const
|
|
{
|
|
return (nBits == 0);
|
|
}
|
|
|
|
uint256 GetHash() const
|
|
{
|
|
return Hash(BEGIN(nVersion), END(nNonce));
|
|
}
|
|
|
|
|
|
uint256 BuildMerkleTree() const
|
|
{
|
|
vMerkleTree.clear();
|
|
foreach(const CTransaction& tx, vtx)
|
|
vMerkleTree.push_back(tx.GetHash());
|
|
int j = 0;
|
|
for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2)
|
|
{
|
|
for (int i = 0; i < nSize; i += 2)
|
|
{
|
|
int i2 = min(i+1, nSize-1);
|
|
vMerkleTree.push_back(Hash(BEGIN(vMerkleTree[j+i]), END(vMerkleTree[j+i]),
|
|
BEGIN(vMerkleTree[j+i2]), END(vMerkleTree[j+i2])));
|
|
}
|
|
j += nSize;
|
|
}
|
|
return (vMerkleTree.empty() ? 0 : vMerkleTree.back());
|
|
}
|
|
|
|
vector<uint256> GetMerkleBranch(int nIndex) const
|
|
{
|
|
if (vMerkleTree.empty())
|
|
BuildMerkleTree();
|
|
vector<uint256> vMerkleBranch;
|
|
int j = 0;
|
|
for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2)
|
|
{
|
|
int i = min(nIndex^1, nSize-1);
|
|
vMerkleBranch.push_back(vMerkleTree[j+i]);
|
|
nIndex >>= 1;
|
|
j += nSize;
|
|
}
|
|
return vMerkleBranch;
|
|
}
|
|
|
|
static uint256 CheckMerkleBranch(uint256 hash, const vector<uint256>& vMerkleBranch, int nIndex)
|
|
{
|
|
if (nIndex == -1)
|
|
return 0;
|
|
foreach(const uint256& otherside, vMerkleBranch)
|
|
{
|
|
if (nIndex & 1)
|
|
hash = Hash(BEGIN(otherside), END(otherside), BEGIN(hash), END(hash));
|
|
else
|
|
hash = Hash(BEGIN(hash), END(hash), BEGIN(otherside), END(otherside));
|
|
nIndex >>= 1;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
|
|
bool WriteToDisk(bool fWriteTransactions, unsigned int& nFileRet, unsigned int& nBlockPosRet)
|
|
{
|
|
// Open history file to append
|
|
CAutoFile fileout = AppendBlockFile(nFileRet);
|
|
if (!fileout)
|
|
return error("CBlock::WriteToDisk() : AppendBlockFile failed");
|
|
if (!fWriteTransactions)
|
|
fileout.nType |= SER_BLOCKHEADERONLY;
|
|
|
|
// Write index header
|
|
unsigned int nSize = fileout.GetSerializeSize(*this);
|
|
fileout << FLATDATA(pchMessageStart) << nSize;
|
|
|
|
// Write block
|
|
nBlockPosRet = ftell(fileout);
|
|
if (nBlockPosRet == -1)
|
|
return error("CBlock::WriteToDisk() : ftell failed");
|
|
fileout << *this;
|
|
|
|
// Flush stdio buffers and commit to disk before returning
|
|
fflush(fileout);
|
|
if (!IsInitialBlockDownload() || (nBestHeight+1) % 500 == 0)
|
|
{
|
|
#ifdef __WXMSW__
|
|
_commit(_fileno(fileout));
|
|
#else
|
|
fsync(fileno(fileout));
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ReadFromDisk(unsigned int nFile, unsigned int nBlockPos, bool fReadTransactions=true)
|
|
{
|
|
SetNull();
|
|
|
|
// Open history file to read
|
|
CAutoFile filein = OpenBlockFile(nFile, nBlockPos, "rb");
|
|
if (!filein)
|
|
return error("CBlock::ReadFromDisk() : OpenBlockFile failed");
|
|
if (!fReadTransactions)
|
|
filein.nType |= SER_BLOCKHEADERONLY;
|
|
|
|
// Read block
|
|
filein >> *this;
|
|
|
|
// Check the header
|
|
if (CBigNum().SetCompact(nBits) > bnProofOfWorkLimit)
|
|
return error("CBlock::ReadFromDisk() : nBits errors in block header");
|
|
if (GetHash() > CBigNum().SetCompact(nBits).getuint256())
|
|
return error("CBlock::ReadFromDisk() : GetHash() errors in block header");
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void print() const
|
|
{
|
|
printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%d)\n",
|
|
GetHash().ToString().substr(0,16).c_str(),
|
|
nVersion,
|
|
hashPrevBlock.ToString().substr(0,16).c_str(),
|
|
hashMerkleRoot.ToString().substr(0,6).c_str(),
|
|
nTime, nBits, nNonce,
|
|
vtx.size());
|
|
for (int i = 0; i < vtx.size(); i++)
|
|
{
|
|
printf(" ");
|
|
vtx[i].print();
|
|
}
|
|
printf(" vMerkleTree: ");
|
|
for (int i = 0; i < vMerkleTree.size(); i++)
|
|
printf("%s ", vMerkleTree[i].ToString().substr(0,6).c_str());
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
int64 GetBlockValue(int64 nFees) const;
|
|
bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex);
|
|
bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex);
|
|
bool ReadFromDisk(const CBlockIndex* blockindex, bool fReadTransactions=true);
|
|
bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos);
|
|
bool CheckBlock() const;
|
|
bool AcceptBlock();
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// The block chain is a tree shaped structure starting with the
|
|
// genesis block at the root, with each block potentially having multiple
|
|
// candidates to be the next block. pprev and pnext link a path through the
|
|
// main/longest chain. A blockindex may have multiple pprev pointing back
|
|
// to it, but pnext will only point forward to the longest branch, or will
|
|
// be null if the block is not part of the longest chain.
|
|
//
|
|
class CBlockIndex
|
|
{
|
|
public:
|
|
const uint256* phashBlock;
|
|
CBlockIndex* pprev;
|
|
CBlockIndex* pnext;
|
|
unsigned int nFile;
|
|
unsigned int nBlockPos;
|
|
int nHeight;
|
|
CBigNum bnChainWork;
|
|
|
|
// block header
|
|
int nVersion;
|
|
uint256 hashMerkleRoot;
|
|
unsigned int nTime;
|
|
unsigned int nBits;
|
|
unsigned int nNonce;
|
|
|
|
|
|
CBlockIndex()
|
|
{
|
|
phashBlock = NULL;
|
|
pprev = NULL;
|
|
pnext = NULL;
|
|
nFile = 0;
|
|
nBlockPos = 0;
|
|
nHeight = 0;
|
|
bnChainWork = 0;
|
|
|
|
nVersion = 0;
|
|
hashMerkleRoot = 0;
|
|
nTime = 0;
|
|
nBits = 0;
|
|
nNonce = 0;
|
|
}
|
|
|
|
CBlockIndex(unsigned int nFileIn, unsigned int nBlockPosIn, CBlock& block)
|
|
{
|
|
phashBlock = NULL;
|
|
pprev = NULL;
|
|
pnext = NULL;
|
|
nFile = nFileIn;
|
|
nBlockPos = nBlockPosIn;
|
|
nHeight = 0;
|
|
bnChainWork = 0;
|
|
|
|
nVersion = block.nVersion;
|
|
hashMerkleRoot = block.hashMerkleRoot;
|
|
nTime = block.nTime;
|
|
nBits = block.nBits;
|
|
nNonce = block.nNonce;
|
|
}
|
|
|
|
uint256 GetBlockHash() const
|
|
{
|
|
return *phashBlock;
|
|
}
|
|
|
|
CBigNum GetBlockWork() const
|
|
{
|
|
return (CBigNum(1)<<256) / (CBigNum().SetCompact(nBits)+1);
|
|
}
|
|
|
|
bool IsInMainChain() const
|
|
{
|
|
return (pnext || this == pindexBest);
|
|
}
|
|
|
|
bool EraseBlockFromDisk()
|
|
{
|
|
// Open history file
|
|
CAutoFile fileout = OpenBlockFile(nFile, nBlockPos, "rb+");
|
|
if (!fileout)
|
|
return false;
|
|
|
|
// Overwrite with empty null block
|
|
CBlock block;
|
|
block.SetNull();
|
|
fileout << block;
|
|
|
|
return true;
|
|
}
|
|
|
|
enum { nMedianTimeSpan=11 };
|
|
|
|
int64 GetMedianTimePast() const
|
|
{
|
|
unsigned int pmedian[nMedianTimeSpan];
|
|
unsigned int* pbegin = &pmedian[nMedianTimeSpan];
|
|
unsigned int* pend = &pmedian[nMedianTimeSpan];
|
|
|
|
const CBlockIndex* pindex = this;
|
|
for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev)
|
|
*(--pbegin) = pindex->nTime;
|
|
|
|
sort(pbegin, pend);
|
|
return pbegin[(pend - pbegin)/2];
|
|
}
|
|
|
|
int64 GetMedianTime() const
|
|
{
|
|
const CBlockIndex* pindex = this;
|
|
for (int i = 0; i < nMedianTimeSpan/2; i++)
|
|
{
|
|
if (!pindex->pnext)
|
|
return nTime;
|
|
pindex = pindex->pnext;
|
|
}
|
|
return pindex->GetMedianTimePast();
|
|
}
|
|
|
|
|
|
|
|
string ToString() const
|
|
{
|
|
return strprintf("CBlockIndex(nprev=%08x, pnext=%08x, nFile=%d, nBlockPos=%-6d nHeight=%d, merkle=%s, hashBlock=%s)",
|
|
pprev, pnext, nFile, nBlockPos, nHeight,
|
|
hashMerkleRoot.ToString().substr(0,6).c_str(),
|
|
GetBlockHash().ToString().substr(0,16).c_str());
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
//
|
|
// Used to marshal pointers into hashes for db storage.
|
|
//
|
|
class CDiskBlockIndex : public CBlockIndex
|
|
{
|
|
public:
|
|
uint256 hashPrev;
|
|
uint256 hashNext;
|
|
|
|
CDiskBlockIndex()
|
|
{
|
|
hashPrev = 0;
|
|
hashNext = 0;
|
|
}
|
|
|
|
explicit CDiskBlockIndex(CBlockIndex* pindex) : CBlockIndex(*pindex)
|
|
{
|
|
hashPrev = (pprev ? pprev->GetBlockHash() : 0);
|
|
hashNext = (pnext ? pnext->GetBlockHash() : 0);
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
if (!(nType & SER_GETHASH))
|
|
READWRITE(nVersion);
|
|
|
|
READWRITE(hashNext);
|
|
READWRITE(nFile);
|
|
READWRITE(nBlockPos);
|
|
READWRITE(nHeight);
|
|
|
|
// block header
|
|
READWRITE(this->nVersion);
|
|
READWRITE(hashPrev);
|
|
READWRITE(hashMerkleRoot);
|
|
READWRITE(nTime);
|
|
READWRITE(nBits);
|
|
READWRITE(nNonce);
|
|
)
|
|
|
|
uint256 GetBlockHash() const
|
|
{
|
|
CBlock block;
|
|
block.nVersion = nVersion;
|
|
block.hashPrevBlock = hashPrev;
|
|
block.hashMerkleRoot = hashMerkleRoot;
|
|
block.nTime = nTime;
|
|
block.nBits = nBits;
|
|
block.nNonce = nNonce;
|
|
return block.GetHash();
|
|
}
|
|
|
|
|
|
string ToString() const
|
|
{
|
|
string str = "CDiskBlockIndex(";
|
|
str += CBlockIndex::ToString();
|
|
str += strprintf("\n hashBlock=%s, hashPrev=%s, hashNext=%s)",
|
|
GetBlockHash().ToString().c_str(),
|
|
hashPrev.ToString().substr(0,16).c_str(),
|
|
hashNext.ToString().substr(0,16).c_str());
|
|
return str;
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Describes a place in the block chain to another node such that if the
|
|
// other node doesn't have the same branch, it can find a recent common trunk.
|
|
// The further back it is, the further before the fork it may be.
|
|
//
|
|
class CBlockLocator
|
|
{
|
|
protected:
|
|
vector<uint256> vHave;
|
|
public:
|
|
|
|
CBlockLocator()
|
|
{
|
|
}
|
|
|
|
explicit CBlockLocator(const CBlockIndex* pindex)
|
|
{
|
|
Set(pindex);
|
|
}
|
|
|
|
explicit CBlockLocator(uint256 hashBlock)
|
|
{
|
|
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock);
|
|
if (mi != mapBlockIndex.end())
|
|
Set((*mi).second);
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
if (!(nType & SER_GETHASH))
|
|
READWRITE(nVersion);
|
|
READWRITE(vHave);
|
|
)
|
|
|
|
void Set(const CBlockIndex* pindex)
|
|
{
|
|
vHave.clear();
|
|
int nStep = 1;
|
|
while (pindex)
|
|
{
|
|
vHave.push_back(pindex->GetBlockHash());
|
|
|
|
// Exponentially larger steps back
|
|
for (int i = 0; pindex && i < nStep; i++)
|
|
pindex = pindex->pprev;
|
|
if (vHave.size() > 10)
|
|
nStep *= 2;
|
|
}
|
|
vHave.push_back(hashGenesisBlock);
|
|
}
|
|
|
|
int GetDistanceBack()
|
|
{
|
|
// Retrace how far back it was in the sender's branch
|
|
int nDistance = 0;
|
|
int nStep = 1;
|
|
foreach(const uint256& hash, vHave)
|
|
{
|
|
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
CBlockIndex* pindex = (*mi).second;
|
|
if (pindex->IsInMainChain())
|
|
return nDistance;
|
|
}
|
|
nDistance += nStep;
|
|
if (nDistance > 10)
|
|
nStep *= 2;
|
|
}
|
|
return nDistance;
|
|
}
|
|
|
|
CBlockIndex* GetBlockIndex()
|
|
{
|
|
// Find the first block the caller has in the main chain
|
|
foreach(const uint256& hash, vHave)
|
|
{
|
|
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
CBlockIndex* pindex = (*mi).second;
|
|
if (pindex->IsInMainChain())
|
|
return pindex;
|
|
}
|
|
}
|
|
return pindexGenesisBlock;
|
|
}
|
|
|
|
uint256 GetBlockHash()
|
|
{
|
|
// Find the first block the caller has in the main chain
|
|
foreach(const uint256& hash, vHave)
|
|
{
|
|
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
CBlockIndex* pindex = (*mi).second;
|
|
if (pindex->IsInMainChain())
|
|
return hash;
|
|
}
|
|
}
|
|
return hashGenesisBlock;
|
|
}
|
|
|
|
int GetHeight()
|
|
{
|
|
CBlockIndex* pindex = GetBlockIndex();
|
|
if (!pindex)
|
|
return 0;
|
|
return pindex->nHeight;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Private key that includes an expiration date in case it never gets used.
|
|
//
|
|
class CWalletKey
|
|
{
|
|
public:
|
|
CPrivKey vchPrivKey;
|
|
int64 nTimeCreated;
|
|
int64 nTimeExpires;
|
|
string strComment;
|
|
//// todo: add something to note what created it (user, getnewaddress, change)
|
|
//// maybe should have a map<string, string> property map
|
|
|
|
CWalletKey(int64 nTimeExpiresIn=0)
|
|
{
|
|
nTimeCreated = (nTimeExpiresIn ? GetTime() : 0);
|
|
nTimeExpires = nTimeExpiresIn;
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
if (!(nType & SER_GETHASH))
|
|
READWRITE(nVersion);
|
|
READWRITE(vchPrivKey);
|
|
READWRITE(nTimeCreated);
|
|
READWRITE(nTimeExpires);
|
|
READWRITE(strComment);
|
|
)
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extern map<uint256, CTransaction> mapTransactions;
|
|
extern map<uint256, CWalletTx> mapWallet;
|
|
extern vector<uint256> vWalletUpdated;
|
|
extern CCriticalSection cs_mapWallet;
|
|
extern map<vector<unsigned char>, CPrivKey> mapKeys;
|
|
extern map<uint160, vector<unsigned char> > mapPubKeys;
|
|
extern CCriticalSection cs_mapKeys;
|
|
extern CKey keyUser;
|