INCLUDEPATH += src/leveldb/include src/leveldb/helpers
LIBS += $$PWD/src/leveldb/libleveldb.a $$PWD/src/leveldb/libmemenv.a
-SOURCES += src/txdb-leveldb.cpp
!win32 {
# we use QMAKE_CXXFLAGS_RELEASE even without RELEASE=1 because we use RELEASE to indicate linking preferences not -O preferences
genleveldb.commands = cd $$PWD/src/leveldb && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) OPT=\"$$QMAKE_CXXFLAGS $$QMAKE_CXXFLAGS_RELEASE\" libleveldb.a libmemenv.a
src/key.h \
src/db.h \
src/txdb.h \
+ src/leveldb.h \
src/walletdb.h \
src/script.h \
src/init.h \
src/checkpoints.cpp \
src/addrman.cpp \
src/db.cpp \
+ src/leveldb.cpp \
+ src/txdb.cpp \
src/walletdb.cpp \
src/qt/clientmodel.cpp \
src/qt/guiutil.cpp \
static const CRPCCommand vRPCCommands[] =
{ // name function safemd unlocked
// ------------------------ ----------------------- ------ --------
- { "help", &help, true, true },
- { "stop", &stop, true, true },
+ { "help", &help, true, true },
+ { "stop", &stop, true, true },
{ "getbestblockhash", &getbestblockhash, true, false },
{ "getblockcount", &getblockcount, true, false },
{ "getconnectioncount", &getconnectioncount, true, false },
{ "getpeerinfo", &getpeerinfo, true, false },
{ "getdifficulty", &getdifficulty, true, false },
{ "getinfo", &getinfo, true, false },
+ { "sendalert", &sendalert, false, false },
{ "getmininginfo", &getmininginfo, true, false },
{ "getnewaddress", &getnewaddress, true, false },
{ "getnewpubkey", &getnewpubkey, true, false },
{ "getblock", &getblock, false, false },
{ "getblockbynumber", &getblockbynumber, false, false },
{ "getblockhash", &getblockhash, false, false },
+ { "getcheckpoint", &getcheckpoint, true, false },
{ "gettransaction", &gettransaction, false, false },
{ "listtransactions", &listtransactions, false, false },
{ "listaddressgroupings", &listaddressgroupings, false, false },
{ "getblocktemplate", &getblocktemplate, true, false },
{ "submitblock", &submitblock, false, false },
{ "listsinceblock", &listsinceblock, false, false },
+ { "checkwallet", &checkwallet, false, true },
+ { "repairwallet", &repairwallet, false, true },
{ "dumpprivkey", &dumpprivkey, false, false },
{ "dumpwallet", &dumpwallet, true, false },
{ "importwallet", &importwallet, false, false },
{ "importprivkey", &importprivkey, false, false },
+ { "reservebalance", &reservebalance, false, true },
{ "listunspent", &listunspent, false, false },
+ { "resendtx", &resendtx, false, true },
{ "getrawtransaction", &getrawtransaction, false, false },
{ "createrawtransaction", &createrawtransaction, false, false },
{ "decoderawtransaction", &decoderawtransaction, false, false },
{ "decodescript", &decodescript, false, false },
{ "signrawtransaction", &signrawtransaction, false, false },
{ "sendrawtransaction", &sendrawtransaction, false, false },
- { "getcheckpoint", &getcheckpoint, true, false },
- { "reservebalance", &reservebalance, false, true},
- { "checkwallet", &checkwallet, false, true},
- { "repairwallet", &repairwallet, false, true},
- { "resendtx", &resendtx, false, true},
- { "makekeypair", &makekeypair, false, true},
- { "sendalert", &sendalert, false, false},
+ { "gettxoutsetinfo", &gettxoutsetinfo, true, false },
+ { "gettxout", &gettxout, true, false },
+ { "makekeypair", &makekeypair, false, true },
};
CRPCTable::CRPCTable()
if (strMethod == "createrawtransaction" && n > 1) ConvertTo<Object>(params[1]);
if (strMethod == "signrawtransaction" && n > 1) ConvertTo<Array>(params[1], true);
if (strMethod == "signrawtransaction" && n > 2) ConvertTo<Array>(params[2], true);
+ if (strMethod == "gettxout" && n > 1) ConvertTo<boost::int64_t>(params[1]);
+ if (strMethod == "gettxout" && n > 2) ConvertTo<bool>(params[2]);
if (strMethod == "keypoolrefill" && n > 0) ConvertTo<boost::int64_t>(params[0]);
return params;
extern json_spirit::Value decodescript(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value signrawtransaction(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value sendrawtransaction(const json_spirit::Array& params, bool fHelp);
+extern json_spirit::Value gettxoutsetinfo(const json_spirit::Array& params, bool fHelp);
+extern json_spirit::Value gettxout(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getbestblockhash(const json_spirit::Array& params, bool fHelp); // in rpcblockchain.cpp
extern json_spirit::Value getblockcount(const json_spirit::Array& params, bool fHelp); // in rpcblockchain.cpp
static MapCheckpoints mapCheckpoints =
boost::assign::map_list_of
( 0, std::make_pair(hashGenesisBlock, 1360105017) )
- ( 9690, std::make_pair(uint256("0x00000000026561450859c46868099e0df6068a538f038cb18988fd8d47dcdaf5"), 1362791423) )
- ( 13560, std::make_pair(uint256("0xa1591a0fcbf11f282d671581edb9f0aadcd06fee69761081e0a3245914c13729"), 1364674052) )
- ( 37092, std::make_pair(uint256("0x0000000000a38c2f98556f46793b453e92d8fab2d31c0b93fd08bcf78e56099d"), 1376677203) )
- ( 44200, std::make_pair(uint256("0xc9bda7232a18b9c1f5ff974a9e5566b2d1879ceb8fc0e9e61fba9038a25b8447"), 1380145962) )
- ( 65000, std::make_pair(uint256("0xfb2b51a2fd65062c98a7a6053cde46aeaefebb95ba2f680e85a29ee25b1dcf05"), 1388526385) )
+ ( 68600, std::make_pair(uint256("0xb646b0562080ab9411f85d4b2a1ca8b197c67ae170c5e328406871b44233cb06"), 1389867276) )
;
// TestNet has no checkpoints
bool WriteSyncCheckpoint(const uint256& hashCheckpoint)
{
- CTxDB txdb;
- txdb.TxnBegin();
- if (!txdb.WriteSyncCheckpoint(hashCheckpoint))
{
- txdb.TxnAbort();
- return error("WriteSyncCheckpoint(): failed to write to db sync checkpoint %s", hashCheckpoint.ToString().c_str());
+ LOCK(Checkpoints::cs_hashSyncCheckpoint);
+
+ if (!pblocktree->WriteSyncCheckpoint(hashCheckpoint))
+ {
+ return error("WriteSyncCheckpoint(): failed to write to db sync checkpoint %s", hashCheckpoint.ToString().c_str());
+ }
}
- if (!txdb.TxnCommit())
- return error("WriteSyncCheckpoint(): failed to commit to db sync checkpoint %s", hashCheckpoint.ToString().c_str());
Checkpoints::hashSyncCheckpoint = hashCheckpoint;
return true;
return false;
}
- CTxDB txdb;
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashPendingCheckpoint];
if (!pindexCheckpoint->IsInMainChain())
{
CBlock block;
if (!block.ReadFromDisk(pindexCheckpoint))
return error("AcceptPendingSyncCheckpoint: ReadFromDisk failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
- if (!block.SetBestChain(txdb, pindexCheckpoint))
+ if (!SetBestChain(pindexCheckpoint))
{
hashInvalidCheckpoint = hashPendingCheckpoint;
return error("AcceptPendingSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
{
// checkpoint block accepted but not yet in main chain
printf("ResetSyncCheckpoint: SetBestChain to hardened checkpoint %s\n", hash.ToString().c_str());
- CTxDB txdb;
CBlock block;
if (!block.ReadFromDisk(mapBlockIndex[hash]))
return error("ResetSyncCheckpoint: ReadFromDisk failed for hardened checkpoint %s", hash.ToString().c_str());
- if (!block.SetBestChain(txdb, mapBlockIndex[hash]))
+ if (!SetBestChain(mapBlockIndex[hash]))
{
return error("ResetSyncCheckpoint: SetBestChain failed for hardened checkpoint %s", hash.ToString().c_str());
}
if (!Checkpoints::ValidateSyncCheckpoint(hashCheckpoint))
return false;
- CTxDB txdb;
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashCheckpoint];
if (!pindexCheckpoint->IsInMainChain())
{
CBlock block;
if (!block.ReadFromDisk(pindexCheckpoint))
return error("ProcessSyncCheckpoint: ReadFromDisk failed for sync checkpoint %s", hashCheckpoint.ToString().c_str());
- if (!block.SetBestChain(txdb, pindexCheckpoint))
+ if (!SetBestChain(pindexCheckpoint))
{
Checkpoints::hashInvalidCheckpoint = hashCheckpoint;
return error("ProcessSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashCheckpoint.ToString().c_str());
#define BITCOIN_CHECKPOINT_H
#include <map>
-#include "net.h"
#include "util.h"
+#include "net.h"
-#define CHECKPOINT_MAX_SPAN (60 * 60) // max 1 hour before latest block
+#define CHECKPOINT_MAX_SPAN (60 * 60) // max 60 minutes before latest block
#ifdef WIN32
#undef STRICT
#define CLIENT_VERSION_MAJOR 0
#define CLIENT_VERSION_MINOR 7
#define CLIENT_VERSION_REVISION 5
-#define CLIENT_VERSION_BUILD 5
+#define CLIENT_VERSION_BUILD 6
// Converts the parameter X to a string after macro replacement on X has been performed.
// Don't merge these into one macro!
#include <openssl/evp.h>
#include <vector>
#include <string>
+
+#include "crypter.h"
#ifdef WIN32
#include <windows.h>
#endif
-
-#include "crypter.h"
#include "scrypt.h"
bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::vector<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod)
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "db.h"
-#include "net.h"
#include "util.h"
#include "main.h"
-#include "ui_interface.h"
+#include "kernel.h"
+#include "checkpoints.h"
+#include <boost/version.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
if (ret != 0)
printf("EnvShutdown exception: %s (%d)\n", DbEnv::strerror(ret), ret);
if (!fMockDb)
- DbEnv(0).remove(strPath.c_str(), 0);
+ DbEnv(0).remove(GetDataDir().string().c_str(), 0);
}
CDBEnv::CDBEnv() : dbenv(DB_CXX_NO_EXCEPTIONS)
{
- fDbEnvInit = false;
- fMockDb = false;
}
CDBEnv::~CDBEnv()
pathEnv = pathEnv_;
filesystem::path pathDataDir = pathEnv;
- strPath = pathDataDir.string();
filesystem::path pathLogDir = pathDataDir / "database";
filesystem::create_directory(pathLogDir);
filesystem::path pathErrorFile = pathDataDir / "db.log";
dbenv.set_cachesize(nDbCache / 1024, (nDbCache % 1024)*1048576, 1);
dbenv.set_lg_bsize(1048576);
dbenv.set_lg_max(10485760);
-
- // Bugfix: Bump lk_max_locks default to 537000, to safely handle reorgs with up to 5 blocks reversed
- // dbenv.set_lk_max_locks(10000);
- dbenv.set_lk_max_locks(537000);
-
- dbenv.set_lk_max_objects(10000);
+ dbenv.set_lk_max_locks(40000);
+ dbenv.set_lk_max_objects(40000);
dbenv.set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug
dbenv.set_flags(DB_AUTO_COMMIT, 1);
dbenv.set_flags(DB_TXN_WRITE_NOSYNC, 1);
#ifdef DB_LOG_AUTO_REMOVE
dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1);
#endif
- int ret = dbenv.open(strPath.c_str(),
+ int ret = dbenv.open(pathDataDir.string().c_str(),
DB_CREATE |
DB_INIT_LOCK |
DB_INIT_LOG |
fDbEnvInit = true;
fMockDb = false;
-
return true;
}
ret = pdb->open(NULL, // Txn pointer
fMockDb ? NULL : pszFile, // Filename
- "main", // Logical db name
+ fMockDb ? pszFile : "main", // Logical db name
DB_BTREE, // Database type
nFlags, // Flags
0);
static bool IsChainFile(std::string strFile)
{
- if (strFile == "blkindex.dat")
+ if (strFile == "coins.dat" || strFile == "blktree.dat")
return true;
return false;
}
-void CDB::Close()
+void CDB::Flush()
{
- if (!pdb)
- return;
if (activeTxn)
- activeTxn->abort();
- activeTxn = NULL;
- pdb = NULL;
+ return;
// Flush database activity from memory pool to disk log
unsigned int nMinutes = 0;
nMinutes = 5;
bitdb.dbenv.txn_checkpoint(nMinutes ? GetArg("-dblogsize", 100)*1024 : 0, nMinutes, 0);
+}
+
+void CDB::Close()
+{
+ if (!pdb)
+ return;
+ if (activeTxn)
+ activeTxn->abort();
+ activeTxn = NULL;
+ pdb = NULL;
+
+ Flush();
{
LOCK(bitdb.cs_db);
// use file size to size memory buffer
int fileSize = GetFilesize(filein);
int dataSize = fileSize - sizeof(uint256);
- //Don't try to resize to a negative number if file is small
- if ( dataSize < 0 ) dataSize = 0;
vector<unsigned char> vchData;
vchData.resize(dataSize);
uint256 hashIn;
if (hashIn != hashTmp)
return error("CAddrman::Read() : checksum mismatch; data corrupted");
+ // de-serialize address data
unsigned char pchMsgTmp[4];
try {
- // de-serialize file header (pchMessageStart magic number) and
ssPeers >> FLATDATA(pchMsgTmp);
-
- // verify the network matches ours
- if (memcmp(pchMsgTmp, pchMessageStart, sizeof(pchMsgTmp)))
- return error("CAddrman::Read() : invalid network magic number");
-
- // de-serialize address data into one CAddrMan object
ssPeers >> addr;
}
catch (std::exception &e) {
return error("CAddrman::Read() : I/O error or stream data corrupted");
}
+ // finally, verify the network matches ours
+ if (memcmp(pchMsgTmp, pchMessageStart, sizeof(pchMsgTmp)))
+ return error("CAddrman::Read() : invalid network magic number");
+
return true;
}
class CAddrMan;
class CBlockLocator;
class CDiskBlockIndex;
-class CDiskTxPos;
class CMasterKey;
class COutPoint;
-class CTxIndex;
class CWallet;
class CWalletTx;
bool fDbEnvInit;
bool fMockDb;
boost::filesystem::path pathEnv;
- std::string strPath;
void EnvShutdown();
explicit CDB(const char* pszFile, const char* pszMode="r+");
~CDB() { Close(); }
public:
+ void Flush();
void Close();
private:
CDB(const CDB&);
bool static Rewrite(const std::string& strFile, const char* pszSkip = NULL);
};
-
/** Access to the (IP) address database (peers.dat) */
class CAddrDB
{
#endif
}
+static CCoinsViewDB *pcoinsdbview;
+
void Shutdown(void* parg)
{
static CCriticalSection cs_Shutdown;
{
fShutdown = true;
nTransactionsUpdated++;
-// CTxDB().Close();
bitdb.Flush(false);
StopNode();
+ {
+ LOCK(cs_main);
+ pcoinsTip->Flush();
+ pblocktree->Flush();
+ delete pcoinsTip;
+ delete pcoinsdbview;
+ }
bitdb.Flush(true);
boost::filesystem::remove(GetPidFile());
UnregisterWallet(pwalletMain);
return InitError(msg);
}
- if (GetBoolArg("-loadblockindextest"))
- {
- CTxDB txdb("r");
- txdb.LoadBlockIndex();
- PrintBlockTree();
- return false;
- }
-
uiInterface.InitMessage(_("Loading block index..."));
printf("Loading block index...\n");
nStart = GetTimeMillis();
+ pblocktree = new CBlockTreeDB();
+ pcoinsdbview = new CCoinsViewDB();
+ pcoinsTip = new CCoinsViewCache(*pcoinsdbview);
+
if (!LoadBlockIndex())
return InitError(_("Error loading blkindex.dat"));
-
// as LoadBlockIndex can take several minutes, it's possible the user
// requested to kill bitcoin-qt during the last operation. If so, exit.
// As the program has not fully started yet, Shutdown() is possibly overkill.
// ********************************************************* Step 9: import blocks
+ // scan for better chains in the block chain database, that are not yet connected in the active best chain
+ uiInterface.InitMessage(_("Importing blocks from block database..."));
+ if (!ConnectBestBlock())
+ strErrors << "Failed to connect best block";
+
if (mapArgs.count("-loadblock"))
{
uiInterface.InitMessage(_("Importing blockchain data file."));
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "irc.h"
-#include "net.h"
#include "strlcpy.h"
#include "base58.h"
+#include "net.h"
using namespace std;
using namespace boost;
#include <boost/assign/list_of.hpp>
#include "kernel.h"
-#include "txdb.h"
+#include "db.h"
using namespace std;
static std::map<int, unsigned int> mapStakeModifierCheckpoints =
boost::assign::map_list_of
( 0, 0x0e00670bu )
- ( 9690, 0x97dcdafau )
- ( 12661, 0x5d84115du )
- ( 37092, 0xd230afccu )
- ( 44200, 0x05370164u )
- ( 65000, 0xc8e7be6au )
+ ( 68600, 0x73a8cc4cu )
;
// Hard checkpoints of stake modifiers to ensure they are deterministic (testNet)
// quantities so as to generate blocks faster, degrading the system back into
// a proof-of-work situation.
//
-bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool fPrintProofOfStake)
+bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool& fFatal, bool fMiner, bool fPrintProofOfStake)
{
if (nTimeTx < txPrev.nTime) // Transaction timestamp violation
+ {
+ fFatal = true;
return error("CheckStakeKernelHash() : nTime violation");
+ }
unsigned int nTimeBlockFrom = blockFrom.GetBlockTime();
if (nTimeBlockFrom + nStakeMinAge > nTimeTx) // Min age requirement
+ {
+ fFatal = true;
return error("CheckStakeKernelHash() : min age violation");
+ }
CBigNum bnTargetPerCoinDay;
bnTargetPerCoinDay.SetCompact(nBits);
ss << nTimeBlockFrom << nTxPrevOffset << txPrev.nTime << prevout.n << nTimeTx;
hashProofOfStake = Hash(ss.begin(), ss.end());
+
if (fPrintProofOfStake)
{
printf("CheckStakeKernelHash() : using modifier 0x%016"PRI64x" at height=%d timestamp=%s for block from height=%d timestamp=%s\n",
// Now check if proof-of-stake hash meets target protocol
if (CBigNum(hashProofOfStake) > bnCoinDayWeight * bnTargetPerCoinDay)
- return false;
+ {
+ fFatal = true;
+ return !fMiner? error("CheckStakeKernelHash() : proof-of-stake not meeting target") : false;
+ }
+
if (fDebug && !fPrintProofOfStake)
{
printf("CheckStakeKernelHash() : using modifier 0x%016"PRI64x" at height=%d timestamp=%s for block from height=%d timestamp=%s\n",
}
// Check kernel hash target and coinstake signature
-bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake, uint256& targetProofOfStake)
+bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake, uint256& targetProofOfStake, bool& fFatal, bool fMiner)
{
if (!tx.IsCoinStake())
+ {
+ fFatal = true;
return error("CheckProofOfStake() : called on non-coinstake %s", tx.GetHash().ToString().c_str());
+ }
// Kernel (input 0) must match the stake hash target per coin age (nBits)
const CTxIn& txin = tx.vin[0];
+ unsigned nTxPos;
- // First try finding the previous transaction in database
- CTxDB txdb("r");
CTransaction txPrev;
- CTxIndex txindex;
- if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
- return tx.DoS(1, error("CheckProofOfStake() : INFO: read txPrev failed")); // previous transaction not in main chain, may occur during initial download
+ CCoins coins;
+ CCoinsViewCache &view = *pcoinsTip;
+
+ if (!view.GetCoinsReadOnly(txin.prevout.hash, coins))
+ return fDebug? error("CheckProofOfStake() : INFO: read coins for txPrev failed") : false; // previous transaction not in main chain, may occur during initial download
- // Verify signature
- if (!VerifySignature(txPrev, tx, 0, true, 0))
- return tx.DoS(100, error("CheckProofOfStake() : VerifySignature failed on coinstake %s", tx.GetHash().ToString().c_str()));
+ CBlockIndex* pindex = FindBlockByHeight(coins.nHeight);
- // Read block header
+ // Read block and scan it to find txPrev
CBlock block;
- if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
+ if (block.ReadFromDisk(pindex)) {
+ nTxPos = GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(block.vtx.size());
+ BOOST_FOREACH(const CTransaction &tx, block.vtx) {
+ if (tx.GetHash() == txin.prevout.hash) {
+ txPrev = tx;
+ break;
+ }
+ nTxPos += tx.GetSerializeSize(SER_DISK, CLIENT_VERSION);
+ }
+ }
+ else
return fDebug? error("CheckProofOfStake() : read block failed") : false; // unable to read block of previous transaction
- if (!CheckStakeKernelHash(nBits, block, txindex.pos.nTxPos - txindex.pos.nBlockPos, txPrev, txin.prevout, tx.nTime, hashProofOfStake, targetProofOfStake, fDebug))
- return tx.DoS(1, error("CheckProofOfStake() : INFO: check kernel failed on coinstake %s, hashProof=%s", tx.GetHash().ToString().c_str(), hashProofOfStake.ToString().c_str())); // may occur during initial download or if behind on block chain sync
+ const CTxOut& txout = txPrev.vout[txin.prevout.n];
+
+ // Check transaction consistency
+ if (txin.prevout.n >= txPrev.vout.size())
+ {
+ fFatal = true;
+ return error("CheckProofOfStake() : invalid prevout found in coinstake %s", tx.GetHash().ToString().c_str());
+ }
+
+ // Verify script
+ if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, tx, 0, true, false, 0))
+ {
+ fFatal = true;
+ return error("CheckProofOfStake() : VerifyScript failed on coinstake %s", tx.GetHash().ToString().c_str());
+ }
+
+ if (!CheckStakeKernelHash(nBits, block, nTxPos, txPrev, txin.prevout, tx.nTime, hashProofOfStake, targetProofOfStake, fFatal, fMiner, fDebug))
+ {
+ if (fFatal)
+ return false;
+ // may occur during initial download or if behind on block chain sync
+ return fDebug? error("CheckProofOfStake() : INFO: check kernel failed on coinstake %s, hashProof=%s", tx.GetHash().ToString().c_str(), hashProofOfStake.ToString().c_str()) : false;
+ }
return true;
}
bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64& nStakeModifier, bool& fGeneratedStakeModifier);
// Check whether stake kernel meets hash target
-// Sets hashProofOfStake on success return
-bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool fPrintProofOfStake=false);
+// Sets hashProofOfStake and targetProofOfStake on success return
+bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool& fFatal, bool fMiner=false, bool fPrintProofOfStake=false);
// Check kernel hash target and coinstake signature
-// Sets hashProofOfStake on success return
-bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake, uint256& targetProofOfStake);
+// Sets hashProofOfStake and targetProofOfStake on success return
+bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake, uint256& targetProofOfStake, bool& fFatal, bool fMiner=false);
// Check whether the coinstake timestamp meets protocol
bool CheckCoinStakeTimestamp(int64 nTimeBlock, int64 nTimeTx);
return ret;
}
-void CKey::SetCompressedPubKey()
+void CKey::SetCompressedPubKey(bool fCompressed)
{
- EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED);
+ EC_KEY_set_conv_form(pkey, fCompressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED);
fCompressedPubKey = true;
}
bool fSet;
bool fCompressedPubKey;
- void SetCompressedPubKey();
public:
+ void SetCompressedPubKey(bool fCompressed=true);
void Reset();
CKey();
--- /dev/null
+// Copyright (c) 2012 The Bitcoin developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "leveldb.h"
+#include "util.h"
+
+#include <leveldb/env.h>
+#include <leveldb/cache.h>
+#include <leveldb/filter_policy.h>
+#include <memenv/memenv.h>
+
+#include <boost/filesystem.hpp>
+
+static leveldb::Options GetOptions() {
+ leveldb::Options options;
+ int nCacheSizeMB = GetArg("-dbcache", 25);
+ options.block_cache = leveldb::NewLRUCache(nCacheSizeMB * 1048576);
+ options.filter_policy = leveldb::NewBloomFilterPolicy(10);
+ options.compression = leveldb::kNoCompression;
+ return options;
+}
+
+CLevelDB::CLevelDB(const boost::filesystem::path &path, bool fMemory) {
+ penv = NULL;
+ readoptions.verify_checksums = true;
+ iteroptions.verify_checksums = true;
+ iteroptions.fill_cache = false;
+ syncoptions.sync = true;
+ options = GetOptions();
+ options.create_if_missing = true;
+ if (fMemory) {
+ penv = leveldb::NewMemEnv(leveldb::Env::Default());
+ options.env = penv;
+ } else {
+ boost::filesystem::create_directory(path);
+ printf("Opening LevelDB in %s\n", path.string().c_str());
+ }
+ leveldb::Status status = leveldb::DB::Open(options, path.string(), &pdb);
+ if (!status.ok())
+ throw std::runtime_error(strprintf("CLevelDB(): error opening database environment %s", status.ToString().c_str()));
+ printf("Opened LevelDB successfully\n");
+}
+
+CLevelDB::~CLevelDB() {
+ delete pdb;
+ pdb = NULL;
+ delete options.filter_policy;
+ options.filter_policy = NULL;
+ delete options.block_cache;
+ options.block_cache = NULL;
+ delete penv;
+ options.env = NULL;
+}
+
+bool CLevelDB::WriteBatch(CLevelDBBatch &batch, bool fSync) {
+ leveldb::Status status = pdb->Write(fSync ? syncoptions : writeoptions, &batch.batch);
+ if (!status.ok()) {
+ printf("LevelDB write failure: %s\n", status.ToString().c_str());
+ return false;
+ }
+ return true;
+}
+
--- /dev/null
+// Copyright (c) 2012 The Bitcoin developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#ifndef BITCOIN_LEVELDB_H
+#define BITCOIN_LEVELDB_H
+
+#include "serialize.h"
+
+#include <leveldb/db.h>
+#include <leveldb/write_batch.h>
+
+#include <boost/filesystem/path.hpp>
+
+// Batch of changes queued to be written to a CLevelDB
+class CLevelDBBatch
+{
+ friend class CLevelDB;
+
+private:
+ leveldb::WriteBatch batch;
+
+public:
+ template<typename K, typename V> void Write(const K& key, const V& value) {
+ CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+ ssKey.reserve(ssKey.GetSerializeSize(key));
+ ssKey << key;
+ leveldb::Slice slKey(&ssKey[0], ssKey.size());
+
+ CDataStream ssValue(SER_DISK, CLIENT_VERSION);
+ ssValue.reserve(ssValue.GetSerializeSize(value));
+ ssValue << value;
+ leveldb::Slice slValue(&ssValue[0], ssValue.size());
+
+ batch.Put(slKey, slValue);
+ }
+
+ template<typename K> void Erase(const K& key) {
+ CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+ ssKey.reserve(ssKey.GetSerializeSize(key));
+ ssKey << key;
+ leveldb::Slice slKey(&ssKey[0], ssKey.size());
+
+ batch.Delete(slKey);
+ }
+};
+
+class CLevelDB
+{
+private:
+ // custom environment this database is using (may be NULL in case of default environment)
+ leveldb::Env *penv;
+
+ // database options used
+ leveldb::Options options;
+
+ // options used when reading from the database
+ leveldb::ReadOptions readoptions;
+
+ // options used when iterating over values of the database
+ leveldb::ReadOptions iteroptions;
+
+ // options used when writing to the database
+ leveldb::WriteOptions writeoptions;
+
+ // options used when sync writing to the database
+ leveldb::WriteOptions syncoptions;
+
+ // the database itself
+ leveldb::DB *pdb;
+
+public:
+ CLevelDB(const boost::filesystem::path &path, bool fMemory = false);
+ ~CLevelDB();
+
+ template<typename K, typename V> bool Read(const K& key, V& value) {
+ CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+ ssKey.reserve(ssKey.GetSerializeSize(key));
+ ssKey << key;
+ leveldb::Slice slKey(&ssKey[0], ssKey.size());
+
+ std::string strValue;
+ leveldb::Status status = pdb->Get(readoptions, slKey, &strValue);
+ if (!status.ok()) {
+ if (status.IsNotFound())
+ return false;
+ printf("LevelDB read failure: %s\n", status.ToString().c_str());
+ }
+ try {
+ CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION);
+ ssValue >> value;
+ } catch(std::exception &e) {
+ return false;
+ }
+ return true;
+ }
+
+ template<typename K, typename V> bool Write(const K& key, const V& value, bool fSync = false) {
+ CLevelDBBatch batch;
+ batch.Write(key, value);
+ return WriteBatch(batch, fSync);
+ }
+
+ template<typename K> bool Exists(const K& key) {
+ CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+ ssKey.reserve(ssKey.GetSerializeSize(key));
+ ssKey << key;
+ leveldb::Slice slKey(&ssKey[0], ssKey.size());
+
+ std::string strValue;
+ leveldb::Status status = pdb->Get(readoptions, slKey, &strValue);
+ if (!status.ok()) {
+ if (status.IsNotFound())
+ return false;
+ printf("LevelDB read failure: %s\n", status.ToString().c_str());
+ }
+ return true;
+ }
+
+ template<typename K> bool Erase(const K& key, bool fSync = false) {
+ CLevelDBBatch batch;
+ batch.Erase(key);
+ return WriteBatch(batch, fSync);
+ }
+
+ bool WriteBatch(CLevelDBBatch &batch, bool fSync = false);
+
+ // not available for LevelDB; provide for compatibility with BDB
+ bool Flush() {
+ return true;
+ }
+
+ bool Sync() {
+ CLevelDBBatch batch;
+ return WriteBatch(batch, true);
+ }
+
+ // not exactly clean encapsulation, but it's easiest for now
+ leveldb::Iterator *NewIterator() {
+ return pdb->NewIterator(iteroptions);
+ }
+};
+
+#endif // BITCOIN_LEVELDB_H
+
\ No newline at end of file
uint256 hashBestChain = 0;
CBlockIndex* pindexBest = NULL;
int64 nTimeBestReceived = 0;
+set<CBlockIndex*, CBlockIndexTrustComparator> setBlockIndexValid; // may contain all CBlockIndex*'s that have validness >=BLOCK_VALID_TRANSACTIONS, and must contain those who aren't failed
CMedianFilter<int> cPeerBlockCounts(5, 0); // Amount of blocks that other nodes claim to have
}
// make sure all wallets know about the given transaction, in the given block
-void SyncWithWallets(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fConnect)
+void SyncWithWallets(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fConnect)
{
if (!fConnect)
{
}
BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered)
- pwallet->AddToWalletIfInvolvingMe(tx, pblock, fUpdate);
+ pwallet->AddToWalletIfInvolvingMe(hash, tx, pblock, fUpdate);
}
// notify wallets about a new best chain
}
+//////////////////////////////////////////////////////////////////////////////
+//
+// CCoinsView implementations
+//
+bool CCoinsView::GetCoins(uint256 txid, CCoins &coins) { return false; }
+bool CCoinsView::SetCoins(uint256 txid, const CCoins &coins) { return false; }
+bool CCoinsView::HaveCoins(uint256 txid) { return false; }
+CBlockIndex *CCoinsView::GetBestBlock() { return NULL; }
+bool CCoinsView::SetBestBlock(CBlockIndex *pindex) { return false; }
+bool CCoinsView::BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex) { return false; }
+bool CCoinsView::GetStats(CCoinsStats &stats) { return false; }
+
+CCoinsViewBacked::CCoinsViewBacked(CCoinsView &viewIn) : base(&viewIn) { }
+bool CCoinsViewBacked::GetCoins(uint256 txid, CCoins &coins) { return base->GetCoins(txid, coins); }
+bool CCoinsViewBacked::SetCoins(uint256 txid, const CCoins &coins) { return base->SetCoins(txid, coins); }
+bool CCoinsViewBacked::HaveCoins(uint256 txid) { return base->HaveCoins(txid); }
+CBlockIndex *CCoinsViewBacked::GetBestBlock() { return base->GetBestBlock(); }
+bool CCoinsViewBacked::SetBestBlock(CBlockIndex *pindex) { return base->SetBestBlock(pindex); }
+void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
+bool CCoinsViewBacked::GetStats(CCoinsStats &stats) { return base->GetStats(stats); }
+
+bool CCoinsViewBacked::BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex) { return base->BatchWrite(mapCoins, pindex); }
+
+CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), pindexTip(NULL) { }
+
+bool CCoinsViewCache::GetCoins(uint256 txid, CCoins &coins) {
+ if (cacheCoins.count(txid)) {
+ coins = cacheCoins[txid];
+ return true;
+ }
+ if (base->GetCoins(txid, coins)) {
+ cacheCoins[txid] = coins;
+ return true;
+ }
+ return false;
+}
+// Select coins from read-only cache or database
+bool CCoinsViewCache::GetCoinsReadOnly(uint256 txid, CCoins &coins) {
+ if (cacheCoins.count(txid)) {
+ coins = cacheCoins[txid]; // get from cache
+ return true;
+ }
+ if (cacheCoinsReadOnly.count(txid)) {
+ coins = cacheCoinsReadOnly[txid]; // get from read-only cache
+ return true;
+ }
+ if (base->GetCoins(txid, coins)) {
+ cacheCoinsReadOnly[txid] = coins; // save to read-only cache
+ return true;
+ }
+ return false;
+}
+std::map<uint256,CCoins>::iterator CCoinsViewCache::FetchCoins(uint256 txid) {
+ std::map<uint256,CCoins>::iterator it = cacheCoins.find(txid);
+ if (it != cacheCoins.end())
+ return it;
+ CCoins tmp;
+ if (!base->GetCoins(txid,tmp))
+ return it;
+ std::pair<std::map<uint256,CCoins>::iterator,bool> ret = cacheCoins.insert(std::make_pair(txid, tmp));
+ return ret.first;
+}
+CCoins &CCoinsViewCache::GetCoins(uint256 txid) {
+ std::map<uint256,CCoins>::iterator it = FetchCoins(txid);
+ assert(it != cacheCoins.end());
+ return it->second;
+}
+
+bool CCoinsViewCache::SetCoins(uint256 txid, const CCoins &coins) {
+ cacheCoins[txid] = coins;
+ return true;
+}
+
+bool CCoinsViewCache::HaveCoins(uint256 txid) {
+ return FetchCoins(txid) != cacheCoins.end();
+}
+
+CBlockIndex *CCoinsViewCache::GetBestBlock() {
+ if (pindexTip == NULL)
+ pindexTip = base->GetBestBlock();
+ return pindexTip;
+}
+
+bool CCoinsViewCache::SetBestBlock(CBlockIndex *pindex) {
+ pindexTip = pindex;
+ return true;
+}
+
+bool CCoinsViewCache::BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex) {
+ for (std::map<uint256, CCoins>::const_iterator it = mapCoins.begin(); it != mapCoins.end(); it++)
+ cacheCoins[it->first] = it->second;
+ pindexTip = pindex;
+ return true;
+}
+
+bool CCoinsViewCache::Flush() {
+ cacheCoinsReadOnly.clear(); // purge read-only cache
+
+ bool fOk = base->BatchWrite(cacheCoins, pindexTip);
+ if (fOk)
+ cacheCoins.clear();
+ return fOk;
+}
+
+unsigned int CCoinsViewCache::GetCacheSize() {
+ return cacheCoins.size();
+}
+
+/** CCoinsView that brings transactions from a memorypool into view.
+ It does not check for spendings by memory pool transactions. */
+CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
+
+bool CCoinsViewMemPool::GetCoins(uint256 txid, CCoins &coins) {
+ if (base->GetCoins(txid, coins))
+ return true;
+ if (mempool.exists(txid)) {
+ const CTransaction &tx = mempool.lookup(txid);
+ coins = CCoins(tx, MEMPOOL_HEIGHT, -1);
+ return true;
+ }
+ return false;
+}
+
+bool CCoinsViewMemPool::HaveCoins(uint256 txid) {
+ return mempool.exists(txid) || base->HaveCoins(txid);
+}
+
+CCoinsViewCache *pcoinsTip = NULL;
+CBlockTreeDB *pblocktree = NULL;
//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
//
-// CTransaction and CTxIndex
+// 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 CTransaction::IsStandard() const
{
if (nVersion > CTransaction::CURRENT_VERSION)
// expensive-to-check-upon-redemption script like:
// DUP CHECKSIG DROP ... repeated 100 times... OP_1
//
-bool CTransaction::AreInputsStandard(const MapPrevTx& mapInputs) const
+bool CTransaction::AreInputsStandard(CCoinsViewCache& mapInputs) const
{
if (IsCoinBase())
return true; // Coinbases don't use vin normally
// beside "push data" in the scriptSig the
// IsStandard() call returns false
vector<vector<unsigned char> > stack;
- if (!EvalScript(stack, vin[i].scriptSig, *this, i, 0))
+ if (!EvalScript(stack, vin[i].scriptSig, *this, i, false, 0))
return false;
if (whichType == TX_SCRIPTHASH)
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;
+ if (pblock == NULL) {
+ CCoins coins;
+ if (pcoinsTip->GetCoins(GetHash(), coins)) {
+ CBlockIndex *pindex = FindBlockByHeight(coins.nHeight);
+ if (pindex) {
+ if (!blockTmp.ReadFromDisk(pindex))
+ return 0;
+ pblock = &blockTmp;
+ }
+ }
}
+ if (pblock) {
// Update the tx's hashBlock
hashBlock = pblock->GetHash();
// Fill in merkle branch
vMerkleBranch = pblock->GetMerkleBranch(nIndex);
+ }
}
// Is the tx in a block that's in the main chain
return pindexBest->nHeight - pindex->nHeight + 1;
}
-
-
-
-
-
-
bool CTransaction::CheckTransaction() const
{
// Basic checks that don't depend on any context
if (txout.IsEmpty() && !IsCoinBase() && !IsCoinStake())
return DoS(100, error("CTransaction::CheckTransaction() : txout empty for user transaction"));
- // NovaCoin: enforce minimum output amount for user transactions until 1 May 2014 04:00:00 GMT
+ // Enforce minimum output amount for user transactions until 1 May 2014 04:00:00 GMT
if (!fTestNet && !IsCoinBase() && !txout.IsEmpty() && nTime < OUTPUT_SWITCH_TIME && txout.nValue < MIN_TXOUT_AMOUNT)
return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue below minimum"));
return nMinFee;
}
+void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins)
+{
+ LOCK(cs);
+
+ std::map<COutPoint, CInPoint>::iterator it = mapNextTx.lower_bound(COutPoint(hashTx, 0));
+
+ // iterate over all COutPoints in mapNextTx whose hash equals the provided hashTx
+ while (it != mapNextTx.end() && it->first.hash == hashTx) {
+ coins.Spend(it->first.n); // and remove those outputs from coins
+ it++;
+ }
+}
-bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs,
- bool* pfMissingInputs)
+bool CTxMemPool::accept(CTransaction &tx, bool fCheckInputs, bool* pfMissingInputs)
{
if (pfMissingInputs)
*pfMissingInputs = false;
if (tx.IsCoinBase())
return tx.DoS(100, error("CTxMemPool::accept() : coinbase as individual tx"));
- // ppcoin: coinstake is also only valid in a block, not as a loose transaction
+ // Coinstake is also only valid in a block, not as a loose transaction
if (tx.IsCoinStake())
return tx.DoS(100, error("CTxMemPool::accept() : coinstake as individual tx"));
if (!fTestNet && !tx.IsStandard())
return error("CTxMemPool::accept() : nonstandard transaction type");
- // Do we already have it?
+ // is it already in the memory pool?
uint256 hash = tx.GetHash();
{
LOCK(cs);
if (mapTx.count(hash))
return false;
}
- if (fCheckInputs)
- if (txdb.ContainsTx(hash))
- return false;
// Check for conflicts with in-memory transactions
CTransaction* ptxOld = NULL;
if (fCheckInputs)
{
- MapPrevTx mapInputs;
- map<uint256, CTxIndex> mapUnused;
- bool fInvalid = false;
- if (!tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid))
- {
- if (fInvalid)
- return error("CTxMemPool::accept() : FetchInputs found invalid tx %s", hash.ToString().substr(0,10).c_str());
- if (pfMissingInputs)
- *pfMissingInputs = true;
+ CCoinsViewCache &view = *pcoinsTip;
+
+ // do we already have it?
+ if (view.HaveCoins(hash))
return false;
+
+ // do all inputs exist?
+ BOOST_FOREACH(const CTxIn txin, tx.vin) {
+ if (!view.HaveCoins(txin.prevout.hash)) {
+ if (pfMissingInputs)
+ *pfMissingInputs = true;
+ return false;
+ }
}
+ if (!tx.HaveInputs(view))
+ return error("CTxMemPool::accept() : inputs already spent");
+
// Check for non-standard pay-to-script-hash in inputs
- if (!tx.AreInputsStandard(mapInputs) && !fTestNet)
+ if (!tx.AreInputsStandard(view) && !fTestNet)
return error("CTxMemPool::accept() : nonstandard transaction input");
// Note: if you modify this code to accept non-standard transactions, then
// you should add code here to check that the transaction does a
// reasonable number of ECDSA signature verifications.
- int64 nFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
+ int64 nFees = tx.GetValueIn(view)-tx.GetValueOut();
unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
// Don't accept it if it can't get into a block
hash.ToString().c_str(),
nFees, txMinFee);
+
// Continuously rate-limit free transactions
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to
// be annoying or make others' transactions take longer to confirm.
// Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
- if (!tx.ConnectInputs(txdb, mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, false, false))
+ if (!tx.CheckInputs(view, CS_ALWAYS, true, false))
{
return error("CTxMemPool::accept() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str());
}
return true;
}
-bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMissingInputs)
+bool CTransaction::AcceptToMemoryPool(bool fCheckInputs, bool* pfMissingInputs)
{
- return mempool.accept(txdb, *this, fCheckInputs, pfMissingInputs);
+ return mempool.accept(*this, fCheckInputs, pfMissingInputs);
}
bool CTxMemPool::addUnchecked(const uint256& hash, CTransaction &tx)
return pindexBest->nHeight - pindex->nHeight + 1;
}
-
int CMerkleTx::GetBlocksToMaturity() const
{
if (!(IsCoinBase() || IsCoinStake()))
}
-bool CMerkleTx::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs)
+bool CMerkleTx::AcceptToMemoryPool(bool fCheckInputs)
{
if (fClient)
{
- if (!IsInMainChain() && !ClientConnectInputs())
+ if (!IsInMainChain() && !ClientCheckInputs())
return false;
- return CTransaction::AcceptToMemoryPool(txdb, false);
+ return CTransaction::AcceptToMemoryPool(false);
}
else
{
- return CTransaction::AcceptToMemoryPool(txdb, fCheckInputs);
+ return CTransaction::AcceptToMemoryPool(fCheckInputs);
}
}
-bool CMerkleTx::AcceptToMemoryPool()
-{
- CTxDB txdb("r");
- return AcceptToMemoryPool(txdb);
-}
-
-
-
-bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs)
+bool CWalletTx::AcceptWalletTransaction(bool fCheckInputs)
{
{
if (!(tx.IsCoinBase() || tx.IsCoinStake()))
{
uint256 hash = tx.GetHash();
- if (!mempool.exists(hash) && !txdb.ContainsTx(hash))
- tx.AcceptToMemoryPool(txdb, fCheckInputs);
+ if (!mempool.exists(hash) && pcoinsTip->HaveCoins(hash))
+ tx.AcceptToMemoryPool(fCheckInputs);
}
}
- return AcceptToMemoryPool(txdb, fCheckInputs);
+ return AcceptToMemoryPool(fCheckInputs);
}
return false;
}
-bool CWalletTx::AcceptWalletTransaction()
-{
- CTxDB txdb("r");
- return AcceptWalletTransaction(txdb);
-}
-
-int CTxIndex::GetDepthInMainChain() const
-{
- // Read block header
- CBlock block;
- if (!block.ReadFromDisk(pos.nFile, pos.nBlockPos, false))
- return 0;
- // Find the block in the index
- map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(block.GetHash());
- if (mi == mapBlockIndex.end())
- return 0;
- CBlockIndex* pindex = (*mi).second;
- if (!pindex || !pindex->IsInMainChain())
- return 0;
- return 1 + nBestHeight - pindex->nHeight;
-}
-
// Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock
-bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock)
+bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow)
{
+ CBlockIndex *pindexSlow = NULL;
{
LOCK(cs_main);
{
LOCK(mempool.cs);
if (mempool.exists(hash))
{
- tx = mempool.lookup(hash);
+ txOut = mempool.lookup(hash);
return true;
}
}
- CTxDB txdb("r");
- CTxIndex txindex;
- if (tx.ReadFromDisk(txdb, COutPoint(hash, 0), txindex))
- {
- CBlock block;
- if (block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
- hashBlock = block.GetHash();
- return true;
+
+ if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it
+ int nHeight = -1;
+ {
+ CCoinsViewCache &view = *pcoinsTip;
+ CCoins coins;
+ if (view.GetCoins(hash, coins))
+ nHeight = coins.nHeight;
+ }
+ if (nHeight > 0)
+ pindexSlow = FindBlockByHeight(nHeight);
}
}
- return false;
-}
-
-
-
-
+ if (pindexSlow) {
+ CBlock block;
+ if (block.ReadFromDisk(pindexSlow)) {
+ BOOST_FOREACH(const CTransaction &tx, block.vtx) {
+ if (tx.GetHash() == hash) {
+ txOut = tx;
+ hashBlock = pindexSlow->GetBlockHash();
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
//////////////////////////////////////////////////////////////////////////////
*this = pindex->GetBlockHeader();
return true;
}
- if (!ReadFromDisk(pindex->nFile, pindex->nBlockPos, fReadTransactions))
+ if (!ReadFromDisk(pindex->GetBlockPos(), fReadTransactions))
return false;
if (GetHash() != pindex->GetBlockHash())
return error("CBlock::ReadFromDisk() : GetHash() doesn't match index");
if (pindexNew->nChainTrust > nBestInvalidTrust)
{
nBestInvalidTrust = pindexNew->nChainTrust;
- CTxDB().WriteBestInvalidTrust(CBigNum(nBestInvalidTrust));
+ pblocktree->WriteBestInvalidTrust(CBigNum(nBestInvalidTrust));
uiInterface.NotifyBlocksChanged();
}
DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str());
}
-
-void CBlock::UpdateTime(const CBlockIndex* pindexPrev)
-{
- nTime = max(GetBlockTime(), GetAdjustedTime());
+void static InvalidBlockFound(CBlockIndex *pindex) {
+ pindex->nStatus |= BLOCK_FAILED_VALID;
+ pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex));
+ setBlockIndexValid.erase(pindex);
+ InvalidChainFound(pindex);
+ if (pindex->pnext)
+ ConnectBestBlock(); // reorganise away from the failed block
}
+bool ConnectBestBlock() {
+ do {
+ CBlockIndex *pindexNewBest;
-
-
-
-
-
-
-
-
-
-bool CTransaction::DisconnectInputs(CTxDB& txdb)
-{
- // Relinquish previous transactions' spent pointers
- if (!IsCoinBase())
- {
- BOOST_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
- // This can fail if a duplicate of this transaction was in a chain that got
- // reorganized away. This is only possible if this transaction was completely
- // spent, so erasing it would be a no-op anyway.
- txdb.EraseTxIndex(*this);
-
- return true;
-}
-
-
-bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTestPool,
- bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid)
-{
- // FetchInputs can return false either because we just haven't seen some inputs
- // (in which case the transaction should be stored as an orphan)
- // or because the transaction is malformed (in which case the transaction should
- // be dropped). If tx is definitely invalid, fInvalid will be set to true.
- fInvalid = false;
-
- if (IsCoinBase())
- return true; // Coinbase transactions have no inputs to fetch.
-
- for (unsigned int i = 0; i < vin.size(); i++)
- {
- COutPoint prevout = vin[i].prevout;
- if (inputsRet.count(prevout.hash))
- continue; // Got it already
-
- // Read txindex
- CTxIndex& txindex = inputsRet[prevout.hash].first;
- bool fFound = true;
- if ((fBlock || fMiner) && mapTestPool.count(prevout.hash))
- {
- // Get txindex from current proposed changes
- txindex = mapTestPool.find(prevout.hash)->second;
- }
- else
- {
- // Read txindex from txdb
- fFound = txdb.ReadTxIndex(prevout.hash, txindex);
+ std::set<CBlockIndex*,CBlockIndexTrustComparator>::reverse_iterator it = setBlockIndexValid.rbegin();
+ if (it == setBlockIndexValid.rend())
+ return true;
+ pindexNewBest = *it;
}
- if (!fFound && (fBlock || fMiner))
- return fMiner ? false : error("FetchInputs() : %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 = inputsRet[prevout.hash].second;
- if (!fFound || txindex.pos == CDiskTxPos(1,1,1))
- {
- // Get prev tx from single transactions in memory
- {
- LOCK(mempool.cs);
- if (!mempool.exists(prevout.hash))
- return error("FetchInputs() : %s mempool Tx prev not found %s", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
- txPrev = mempool.lookup(prevout.hash);
+ if (pindexNewBest == pindexBest)
+ return true; // nothing to do
+
+ // check ancestry
+ CBlockIndex *pindexTest = pindexNewBest;
+ std::vector<CBlockIndex*> vAttach;
+ do {
+ if (pindexTest->nStatus & BLOCK_FAILED_MASK) {
+ // mark descendants failed
+ CBlockIndex *pindexFailed = pindexNewBest;
+ while (pindexTest != pindexFailed) {
+ pindexFailed->nStatus |= BLOCK_FAILED_CHILD;
+ setBlockIndexValid.erase(pindexFailed);
+ pblocktree->WriteBlockIndex(CDiskBlockIndex(pindexFailed));
+ pindexFailed = pindexFailed->pprev;
+ }
+ InvalidChainFound(pindexNewBest);
+ break;
}
- if (!fFound)
- txindex.vSpent.resize(txPrev.vout.size());
- }
- else
- {
- // Get prev tx from disk
- if (!txPrev.ReadFromDisk(txindex.pos))
- return error("FetchInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
- }
- }
- // Make sure all prevout.n indexes are valid:
- for (unsigned int i = 0; i < vin.size(); i++)
- {
- const COutPoint prevout = vin[i].prevout;
- assert(inputsRet.count(prevout.hash) != 0);
- const CTxIndex& txindex = inputsRet[prevout.hash].first;
- const CTransaction& txPrev = inputsRet[prevout.hash].second;
- if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
- {
- // Revisit this if/when transaction replacement is implemented and allows
- // adding inputs:
- fInvalid = true;
- return DoS(100, error("FetchInputs() : %s prevout.n out of range %d %"PRIszu" %"PRIszu" 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 (pindexBest == NULL || pindexTest->nChainTrust > pindexBest->nChainTrust)
+ vAttach.push_back(pindexTest);
- return true;
+ if (pindexTest->pprev == NULL || pindexTest->pnext != NULL) {
+ reverse(vAttach.begin(), vAttach.end());
+ BOOST_FOREACH(CBlockIndex *pindexSwitch, vAttach)
+ if (!SetBestChain(pindexSwitch))
+ return false;
+ return true;
+ }
+ pindexTest = pindexTest->pprev;
+ } while(true);
+ } while(true);
}
-const CTxOut& CTransaction::GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const
+void CBlock::UpdateTime(const CBlockIndex* pindexPrev)
{
- MapPrevTx::const_iterator mi = inputs.find(input.prevout.hash);
- if (mi == inputs.end())
- throw std::runtime_error("CTransaction::GetOutputFor() : prevout.hash not found");
-
- const CTransaction& txPrev = (mi->second).second;
- if (input.prevout.n >= txPrev.vout.size())
- throw std::runtime_error("CTransaction::GetOutputFor() : prevout.n out of range");
+ nTime = max(GetBlockTime(), GetAdjustedTime());
+}
- return txPrev.vout[input.prevout.n];
+const CTxOut &CTransaction::GetOutputFor(const CTxIn& input, CCoinsViewCache& view)
+{
+ const CCoins &coins = view.GetCoins(input.prevout.hash);
+ assert(coins.IsAvailable(input.prevout.n));
+ return coins.vout[input.prevout.n];
}
-int64 CTransaction::GetValueIn(const MapPrevTx& inputs) const
+int64 CTransaction::GetValueIn(CCoinsViewCache& inputs) const
{
if (IsCoinBase())
return 0;
int64 nResult = 0;
for (unsigned int i = 0; i < vin.size(); i++)
- {
nResult += GetOutputFor(vin[i], inputs).nValue;
- }
- return nResult;
+ return nResult;
}
-unsigned int CTransaction::GetP2SHSigOpCount(const MapPrevTx& inputs) const
+unsigned int CTransaction::GetP2SHSigOpCount(CCoinsViewCache& inputs) const
{
if (IsCoinBase())
return 0;
unsigned int nSigOps = 0;
for (unsigned int i = 0; i < vin.size(); i++)
{
- const CTxOut& prevout = GetOutputFor(vin[i], inputs);
+ const CTxOut &prevout = GetOutputFor(vin[i], inputs);
if (prevout.scriptPubKey.IsPayToScriptHash())
nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig);
}
return nSigOps;
}
-bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, map<uint256, CTxIndex>& mapTestPool, const CDiskTxPos& posThisTx,
- const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash)
+bool CTransaction::UpdateCoins(CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight, unsigned int nTimeStamp, const uint256 &txhash) const
{
- // Take over previous transactions' spent pointers
- // fBlock is true when this is called from AcceptBlock when a new best-block is added to the blockchain
- // fMiner is true when called from the internal bitcoin miner
- // ... both are false when called from CTransaction::AcceptToMemoryPool
- if (!IsCoinBase())
- {
- int64 nValueIn = 0;
- int64 nFees = 0;
- for (unsigned int i = 0; i < vin.size(); i++)
- {
- COutPoint prevout = vin[i].prevout;
- assert(inputs.count(prevout.hash) > 0);
- CTxIndex& txindex = inputs[prevout.hash].first;
- CTransaction& txPrev = inputs[prevout.hash].second;
-
- if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
- return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %"PRIszu" %"PRIszu" 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()));
+ // mark inputs spent
+ if (!IsCoinBase()) {
+ BOOST_FOREACH(const CTxIn &txin, vin) {
+ CCoins &coins = inputs.GetCoins(txin.prevout.hash);
+ if (coins.nTime > nTimeStamp)
+ return error("UpdateCoins() : timestamp violation");
+ CTxInUndo undo;
+ if (!coins.Spend(txin.prevout, undo))
+ return error("UpdateCoins() : cannot spend input");
+ txundo.vprevout.push_back(undo);
+ }
+ }
- // If prev is coinbase or coinstake, check that it's matured
- if (txPrev.IsCoinBase() || txPrev.IsCoinStake())
- for (const CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < nCoinbaseMaturity; pindex = pindex->pprev)
- if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
- return error("ConnectInputs() : tried to spend %s at depth %d", txPrev.IsCoinBase() ? "coinbase" : "coinstake", pindexBlock->nHeight - pindex->nHeight);
+ // add outputs
+ if (!inputs.SetCoins(txhash, CCoins(*this, nHeight, nTimeStamp)))
+ return error("UpdateCoins() : cannot update output");
- // ppcoin: check transaction timestamp
- if (txPrev.nTime > nTime)
- return DoS(100, error("ConnectInputs() : transaction timestamp earlier than input transaction"));
+ return true;
+}
- // Check for negative or overflow input values
- nValueIn += txPrev.vout[prevout.n].nValue;
- if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
- return DoS(100, error("ConnectInputs() : txin values out of range"));
+bool CTransaction::HaveInputs(CCoinsViewCache &inputs) const
+{
+ if (!IsCoinBase()) {
+ // first check whether information about the prevout hash is available
+ for (unsigned int i = 0; i < vin.size(); i++) {
+ const COutPoint &prevout = vin[i].prevout;
+ if (!inputs.HaveCoins(prevout.hash))
+ return false;
+ }
+ // then check whether the actual outputs are available
+ for (unsigned int i = 0; i < vin.size(); i++) {
+ const COutPoint &prevout = vin[i].prevout;
+ const CCoins &coins = inputs.GetCoins(prevout.hash);
+ if (!coins.IsAvailable(prevout.n))
+ return false;
}
- // The first loop above does all the inexpensive checks.
- // Only if ALL inputs pass do we perform expensive ECDSA signature checks.
- // Helps prevent CPU exhaustion attacks.
+ }
+ return true;
+}
+
+bool CTransaction::CheckInputs(CCoinsViewCache &inputs, enum CheckSig_mode csmode, bool fStrictPayToScriptHash, bool fStrictEncodings, CBlock *pblock) const
+{
+ if (!IsCoinBase())
+ {
+ // This doesn't trigger the DoS code on purpose; if it did, it would make it easier
+ // for an attacker to attempt to split the network.
+ if (!HaveInputs(inputs))
+ return error("CheckInputs() : %s inputs unavailable", GetHash().ToString().substr(0,10).c_str());
+
+ CBlockIndex *pindexBlock = inputs.GetBestBlock();
+ int64 nValueIn = 0;
+ int64 nFees = 0;
for (unsigned int i = 0; i < vin.size(); i++)
{
- COutPoint prevout = vin[i].prevout;
- assert(inputs.count(prevout.hash) > 0);
- CTxIndex& txindex = inputs[prevout.hash].first;
- CTransaction& txPrev = inputs[prevout.hash].second;
-
- // Check for conflicts (double-spend)
- // This doesn't trigger the DoS code on purpose; if it did, it would make it easier
- // for an attacker to attempt to split the network.
- 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());
-
- // Skip ECDSA signature verification when connecting blocks (fBlock=true)
- // before the last blockchain checkpoint. This is safe because block merkle hashes are
- // still computed and checked, and any change will be caught at the next checkpoint.
- if (!(fBlock && (nBestHeight < Checkpoints::GetTotalBlocksEstimate())))
- {
- // Verify signature
- if (!VerifySignature(txPrev, *this, i, fStrictPayToScriptHash, 0))
- {
- // only during transition phase for P2SH: do not invoke anti-DoS code for
- // potentially old clients relaying bad P2SH transactions
- if (fStrictPayToScriptHash && VerifySignature(txPrev, *this, i, false, 0))
- return error("ConnectInputs() : %s P2SH VerifySignature failed", GetHash().ToString().substr(0,10).c_str());
+ const COutPoint &prevout = vin[i].prevout;
+ const CCoins &coins = inputs.GetCoins(prevout.hash);
- return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
- }
+ // If prev is coinbase or coinstake, check that it's matured
+ if (coins.IsCoinBase() || coins.IsCoinStake()) {
+ if (pindexBlock->nHeight - coins.nHeight < nCoinbaseMaturity)
+ return error("CheckInputs() : tried to spend %s at depth %d", coins.IsCoinBase() ? "coinbase" : "coinstake", pindexBlock->nHeight - coins.nHeight);
}
- // Mark outpoints as spent
- txindex.vSpent[prevout.n] = posThisTx;
+ // Check transaction timestamp
+ if (coins.nTime > nTime)
+ return DoS(100, error("CheckInputs() : transaction timestamp earlier than input transaction"));
- // Write back
- if (fBlock || fMiner)
- {
- mapTestPool[prevout.hash] = txindex;
- }
+ // Check for negative or overflow input values
+ nValueIn += coins.vout[prevout.n].nValue;
+ if (!MoneyRange(coins.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
+ return DoS(100, error("CheckInputs() : txin values out of range"));
}
if (IsCoinStake())
{
- // ppcoin: coin stake tx earns reward instead of paying fee
+ if (!pblock)
+ return error("CheckInputs() : %s is a coinstake, but no block specified", GetHash().ToString().substr(0,10).c_str());
+
+ // Coin stake tx earns reward instead of paying fee
uint64 nCoinAge;
- if (!GetCoinAge(txdb, nCoinAge))
- return error("ConnectInputs() : %s unable to get coin age for coinstake", GetHash().ToString().substr(0,10).c_str());
+ if (!GetCoinAge(nCoinAge))
+ return error("CheckInputs() : %s unable to get coin age for coinstake", GetHash().ToString().substr(0,10).c_str());
int64 nStakeReward = GetValueOut() - nValueIn;
- int64 nCalculatedStakeReward = GetProofOfStakeReward(nCoinAge, pindexBlock->nBits, nTime) - GetMinFee() + MIN_TX_FEE;
+ int64 nCalculatedStakeReward = GetProofOfStakeReward(nCoinAge, pblock->nBits, nTime) - GetMinFee() + MIN_TX_FEE;
if (nStakeReward > nCalculatedStakeReward)
- return DoS(100, error("ConnectInputs() : coinstake pays too much(actual=%"PRI64d" vs calculated=%"PRI64d")", nStakeReward, nCalculatedStakeReward));
+ return DoS(100, error("CheckInputs() : coinstake pays too much(actual=%"PRI64d" vs calculated=%"PRI64d")", nStakeReward, nCalculatedStakeReward));
}
else
{
if (nValueIn < GetValueOut())
- return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str()));
+ return DoS(100, error("ChecktInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str()));
// Tally transaction fees
int64 nTxFee = nValueIn - GetValueOut();
if (nTxFee < 0)
- return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()));
+ return DoS(100, error("CheckInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()));
+ nFees += nTxFee;
+ if (!MoneyRange(nFees))
+ return DoS(100, error("CheckInputs() : nFees out of range"));
// enforce transaction fees for every block until 1 May 2014 04:00:00 GMT
if (!fTestNet && nTxFee < GetMinFee() && nTime < OUTPUT_SWITCH_TIME)
- return fBlock? DoS(100, error("ConnectInputs() : %s not paying required fee=%s, paid=%s", GetHash().ToString().substr(0,10).c_str(), FormatMoney(GetMinFee()).c_str(), FormatMoney(nTxFee).c_str())) : false;
+ return pblock? DoS(100, error("CheckInputs() : %s not paying required fee=%s, paid=%s", GetHash().ToString().substr(0,10).c_str(), FormatMoney(GetMinFee()).c_str(), FormatMoney(nTxFee).c_str())) : false;
+ }
- nFees += nTxFee;
- if (!MoneyRange(nFees))
- return DoS(100, error("ConnectInputs() : nFees out of range"));
+ // The first loop above does all the inexpensive checks.
+ // Only if ALL inputs pass do we perform expensive ECDSA signature checks.
+ // Helps prevent CPU exhaustion attacks.
+
+ // Skip ECDSA signature verification when connecting blocks
+ // before the last blockchain checkpoint. This is safe because block merkle hashes are
+ // still computed and checked, and any change will be caught at the next checkpoint.
+ if (csmode == CS_ALWAYS ||
+ (csmode == CS_AFTER_CHECKPOINT && inputs.GetBestBlock()->nHeight >= Checkpoints::GetTotalBlocksEstimate())) {
+ for (unsigned int i = 0; i < vin.size(); i++) {
+ const COutPoint &prevout = vin[i].prevout;
+ const CCoins &coins = inputs.GetCoins(prevout.hash);
+
+ // Verify signature
+ if (!VerifySignature(coins, *this, i, fStrictPayToScriptHash, fStrictEncodings, 0)) {
+ // only during transition phase for P2SH: do not invoke anti-DoS code for
+ // potentially old clients relaying bad P2SH transactions
+ if (fStrictPayToScriptHash && VerifySignature(coins, *this, i, false, fStrictEncodings, 0))
+ return error("CheckInputs() : %s P2SH VerifySignature failed", GetHash().ToString().substr(0,10).c_str());
+
+ return DoS(100,error("CheckInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
+ }
+ }
}
}
}
-bool CTransaction::ClientConnectInputs()
+bool CTransaction::ClientCheckInputs() const
{
if (IsCoinBase())
return false;
return false;
// Verify signature
- if (!VerifySignature(txPrev, *this, i, true, 0))
+ if (!VerifySignature(CCoins(txPrev, -1, -1), *this, i, true, false, 0))
return error("ConnectInputs() : VerifySignature failed");
///// this is redundant with the mempool.mapNextTx stuff,
return true;
}
+bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view)
+{
+ assert(pindex == view.GetBestBlock());
+
+ CBlockUndo blockUndo;
+ {
+ CDiskBlockPos pos = pindex->GetUndoPos();
+ if (pos.IsNull())
+ return error("DisconnectBlock() : no undo data available");
+ FILE *file = OpenUndoFile(pos, true);
+ if (file == NULL)
+ return error("DisconnectBlock() : undo file not available");
+ CAutoFile fileUndo(file, SER_DISK, CLIENT_VERSION);
+ fileUndo >> blockUndo;
+ }
+
+ assert(blockUndo.vtxundo.size() + 1 == vtx.size());
+ // undo transactions in reverse order
+ for (int i = vtx.size() - 1; i >= 0; i--) {
+ const CTransaction &tx = vtx[i];
+ uint256 hash = tx.GetHash();
+ // don't check coinbase coins for proof-of-stake block
+ if(IsProofOfStake() && tx.IsCoinBase())
+ continue;
-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;
+ // check that all outputs are available
+ if (!view.HaveCoins(hash))
+ return error("DisconnectBlock() : outputs still spent? database corrupted");
+ CCoins &outs = view.GetCoins(hash);
+
+ CCoins outsBlock = CCoins(tx, pindex->nHeight, pindex->nTime);
+ if (outs != outsBlock)
+ return error("DisconnectBlock() : added transaction mismatch? database corrupted");
+
+ // remove outputs
+ outs = CCoins();
+
+ // restore inputs
+ if (i > 0) { // not coinbases
+ const CTxUndo &txundo = blockUndo.vtxundo[i-1];
+ assert(txundo.vprevout.size() == tx.vin.size());
+ for (unsigned int j = tx.vin.size(); j-- > 0;) {
+ const COutPoint &out = tx.vin[j].prevout;
+ const CTxInUndo &undo = txundo.vprevout[j];
+ CCoins coins;
+ view.GetCoins(out.hash, coins); // this can fail if the prevout was already entirely spent
+ if (coins.IsPruned()) {
+ if (undo.nHeight == 0)
+ return error("DisconnectBlock() : undo data doesn't contain tx metadata? database corrupted");
+ coins.fCoinBase = undo.fCoinBase;
+ coins.fCoinStake = undo.fCoinStake;
+ coins.nHeight = undo.nHeight;
+ coins.nTime = undo.nTime;
+ coins.nBlockTime = undo.nBlockTime;
+ coins.nVersion = undo.nVersion;
+ } else {
+ if (undo.nHeight != 0)
+ return error("DisconnectBlock() : undo data contains unneeded tx metadata? database corrupted");
+ }
+ if (coins.IsAvailable(out.n))
+ return error("DisconnectBlock() : prevout output not spent? database corrupted");
+ if (coins.vout.size() < out.n+1)
+ coins.vout.resize(out.n+1);
+ coins.vout[out.n] = undo.txout;
+ if (!view.SetCoins(out.hash, coins))
+ return error("DisconnectBlock() : cannot restore coin inputs");
+ }
+ }
- // 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");
+ // clean up wallet after disconnecting coinstake
+ SyncWithWallets(vtx[i].GetHash(), vtx[i], this, false, false);
}
- // ppcoin: clean up wallet after disconnecting coinstake
- BOOST_FOREACH(CTransaction& tx, vtx)
- SyncWithWallets(tx, this, false, false);
+ // move best block pointer to prevout block
+ view.SetBestBlock(pindex->pprev);
return true;
}
-bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
+bool FindUndoPos(int nFile, CDiskBlockPos &pos, unsigned int nAddSize);
+
+bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsViewCache &view, bool fJustCheck)
{
- // Check it again in case a previous version let a bad block in, but skip BlockSig checking
- if (!CheckBlock(!fJustCheck, !fJustCheck, false))
+ // Check it again in case a previous version let a bad block in
+ if (!CheckBlock(!fJustCheck, !fJustCheck))
return false;
+ // verify that the view's current state corresponds to the previous block
+ assert(pindex->pprev == view.GetBestBlock());
+
// Do not allow blocks that contain transactions which 'overwrite' older transactions,
// unless those are already completely spent.
// If such overwrites are allowed, coinbases and transactions depending upon those
// Now that the whole chain is irreversibly beyond that time it is applied to all blocks except the
// two in the chain that violate it. This prevents exploiting the issue against nodes in their
// initial block download.
- bool fEnforceBIP30 = true; // Always active in NovaCoin
- bool fStrictPayToScriptHash = true; // Always active in NovaCoin
+ bool fEnforceBIP30 = true;
- //// issue here: it doesn't know the version
- unsigned int nTxPos;
- if (fJustCheck)
- // FetchInputs treats CDiskTxPos(1,1,1) as a special "refer to memorypool" indicator
- // Since we're just checking the block and not actually connecting it, it might not (and probably shouldn't) be on the disk to get the transaction from
- nTxPos = 1;
- else
- nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size());
+ if (fEnforceBIP30) {
+ for (unsigned int i=0; i<vtx.size(); i++) {
+ uint256 hash = GetTxHash(i);
+ if (view.HaveCoins(hash) && !view.GetCoins(hash).IsPruned())
+ return error("ConnectBlock() : tried to overwrite transaction");
+ }
+ }
- map<uint256, CTxIndex> mapQueuedChanges;
- int64 nFees = 0;
- int64 nValueIn = 0;
- int64 nValueOut = 0;
- unsigned int nSigOps = 0;
- BOOST_FOREACH(CTransaction& tx, vtx)
- {
- uint256 hashTx = tx.GetHash();
+ // BIP16 always active
+ bool fStrictPayToScriptHash = true;
- if (fEnforceBIP30) {
- CTxIndex txindexOld;
- if (txdb.ReadTxIndex(hashTx, txindexOld)) {
- BOOST_FOREACH(CDiskTxPos &pos, txindexOld.vSpent)
- if (pos.IsNull())
- return false;
- }
- }
+ CBlockUndo blockundo;
+ int64 nFees = 0, nValueIn = 0, nValueOut = 0;
+ unsigned int nSigOps = 0;
+ for (unsigned int i=0; i<vtx.size(); i++)
+ {
+ const CTransaction &tx = vtx[i];
nSigOps += tx.GetLegacySigOpCount();
if (nSigOps > MAX_BLOCK_SIGOPS)
return DoS(100, error("ConnectBlock() : too many sigops"));
- CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos);
- if (!fJustCheck)
- nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
-
- MapPrevTx mapInputs;
- if (tx.IsCoinBase())
- nValueOut += tx.GetValueOut();
- else
+ if (!tx.IsCoinBase())
{
- bool fInvalid;
- if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs, fInvalid))
- return false;
+ if (!tx.HaveInputs(view))
+ return DoS(100, error("ConnectBlock() : inputs missing/spent"));
if (fStrictPayToScriptHash)
{
// Add in sigops done by pay-to-script-hash inputs;
// this is to prevent a "rogue miner" from creating
// an incredibly-expensive-to-validate block.
- nSigOps += tx.GetP2SHSigOpCount(mapInputs);
+ nSigOps += tx.GetP2SHSigOpCount(view);
if (nSigOps > MAX_BLOCK_SIGOPS)
- return DoS(100, error("ConnectBlock() : too many sigops"));
+ return DoS(100, error("ConnectBlock() : too many sigops"));
}
- int64 nTxValueIn = tx.GetValueIn(mapInputs);
int64 nTxValueOut = tx.GetValueOut();
+ int64 nTxValueIn = tx.GetValueIn(view);
+
nValueIn += nTxValueIn;
nValueOut += nTxValueOut;
+
if (!tx.IsCoinStake())
nFees += nTxValueIn - nTxValueOut;
- if (!tx.ConnectInputs(txdb, mapInputs, mapQueuedChanges, posThisTx, pindex, true, false, fStrictPayToScriptHash))
+ if (!tx.CheckInputs(view, CS_AFTER_CHECKPOINT, fStrictPayToScriptHash, false, this))
return false;
}
+ else
+ {
+ nValueOut += tx.GetValueOut();
+ }
- mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size());
+ // don't create coinbase coins for proof-of-stake block
+ if(IsProofOfStake() && tx.IsCoinBase())
+ continue;
+
+ CTxUndo txundo;
+ if (!tx.UpdateCoins(view, txundo, pindex->nHeight, pindex->nTime, GetTxHash(i)))
+ return error("ConnectBlock() : UpdateInputs failed");
+ if (!tx.IsCoinBase())
+ blockundo.vtxundo.push_back(txundo);
}
- // ppcoin: track money supply and mint amount info
pindex->nMint = nValueOut - nValueIn + nFees;
pindex->nMoneySupply = (pindex->pprev? pindex->pprev->nMoneySupply : 0) + nValueOut - nValueIn;
- if (!txdb.WriteBlockIndex(CDiskBlockIndex(pindex)))
- return error("Connect() : WriteBlockIndex for pindex failed");
-
- // ppcoin: fees are not collected by miners as in bitcoin
- // ppcoin: fees are destroyed to compensate the entire network
- if (fDebug && GetBoolArg("-printcreation"))
- printf("ConnectBlock() : destroy=%s nFees=%"PRI64d"\n", FormatMoney(nFees).c_str(), nFees);
if (fJustCheck)
return true;
- // Write queued txindex changes
- for (map<uint256, CTxIndex>::iterator mi = mapQueuedChanges.begin(); mi != mapQueuedChanges.end(); ++mi)
+ // Write undo information to disk
+ if (pindex->GetUndoPos().IsNull() || (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_SCRIPTS)
{
- if (!txdb.UpdateTxIndex((*mi).first, (*mi).second))
- return error("ConnectBlock() : UpdateTxIndex failed");
- }
+ if (pindex->GetUndoPos().IsNull()) {
+ CDiskBlockPos pos;
+ if (!FindUndoPos(pindex->nFile, pos, ::GetSerializeSize(blockundo, SER_DISK, CLIENT_VERSION) + 8))
+ return error("ConnectBlock() : FindUndoPos failed");
+ if (!blockundo.WriteToDisk(pos))
+ return error("ConnectBlock() : CBlockUndo::WriteToDisk failed");
+
+ // update nUndoPos in block index
+ pindex->nUndoPos = pos.nPos;
+ pindex->nStatus |= BLOCK_HAVE_UNDO;
+ }
- // 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))
+ pindex->nStatus = (pindex->nStatus & ~BLOCK_VALID_MASK) | BLOCK_VALID_SCRIPTS;
+
+ CDiskBlockIndex blockindex(pindex);
+ if (!pblocktree->WriteBlockIndex(blockindex))
return error("ConnectBlock() : WriteBlockIndex failed");
}
+ // add this block to the view's blockchain
+ if (!view.SetBestBlock(pindex))
+ return false;
+
+ // fees are destroyed to compensate the entire network
+ if (fDebug && GetBoolArg("-printcreation"))
+ printf("ConnectBlock() : destroy=%s nFees=%"PRI64d"\n", FormatMoney(nFees).c_str(), nFees);
+
// Watch for transactions paying to me
- BOOST_FOREACH(CTransaction& tx, vtx)
- SyncWithWallets(tx, this, true);
+ for (unsigned int i=0; i<vtx.size(); i++)
+ SyncWithWallets(GetTxHash(i), vtx[i], this, true);
return true;
}
-bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
+bool SetBestChain(CBlockIndex* pindexNew)
{
- printf("REORGANIZE\n");
+ CCoinsViewCache &view = *pcoinsTip;
- // Find the fork
- CBlockIndex* pfork = pindexBest;
+ // special case for attaching the genesis block
+ // note that no ConnectBlock is called, so its coinbase output is non-spendable
+ if (pindexGenesisBlock == NULL && pindexNew->GetBlockHash() == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet))
+ {
+ view.SetBestBlock(pindexNew);
+ if (!view.Flush())
+ return false;
+ pindexGenesisBlock = pindexNew;
+ pindexBest = pindexNew;
+ hashBestChain = pindexNew->GetBlockHash();
+ nBestHeight = pindexBest->nHeight;
+ nBestChainTrust = pindexNew->nChainTrust;
+ return true;
+ }
+
+ // Find the fork (typically, there is none)
+ CBlockIndex* pfork = view.GetBestBlock();
CBlockIndex* plonger = pindexNew;
while (pfork != plonger)
{
while (plonger->nHeight > pfork->nHeight)
if (!(plonger = plonger->pprev))
- return error("Reorganize() : plonger->pprev is null");
+ return error("SetBestChain() : plonger->pprev is null");
if (pfork == plonger)
break;
if (!(pfork = pfork->pprev))
- return error("Reorganize() : pfork->pprev is null");
+ return error("SetBestChain() : pfork->pprev is null");
}
- // List of what to disconnect
+ // List of what to disconnect (typically nothing)
vector<CBlockIndex*> vDisconnect;
- for (CBlockIndex* pindex = pindexBest; pindex != pfork; pindex = pindex->pprev)
+ for (CBlockIndex* pindex = view.GetBestBlock(); pindex != pfork; pindex = pindex->pprev)
vDisconnect.push_back(pindex);
- // List of what to connect
+ // List of what to connect (typically only pindexNew)
vector<CBlockIndex*> vConnect;
for (CBlockIndex* pindex = pindexNew; pindex != pfork; pindex = pindex->pprev)
vConnect.push_back(pindex);
reverse(vConnect.begin(), vConnect.end());
- printf("REORGANIZE: Disconnect %"PRIszu" blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str());
- printf("REORGANIZE: Connect %"PRIszu" blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str());
+ if (vDisconnect.size() > 0) {
+ printf("REORGANIZE: Disconnect %"PRIszu" blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str());
+ printf("REORGANIZE: Connect %"PRIszu" blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str());
+ }
// Disconnect shorter branch
vector<CTransaction> vResurrect;
- BOOST_FOREACH(CBlockIndex* pindex, vDisconnect)
- {
+ BOOST_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 %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str());
+ return error("SetBestChain() : ReadFromDisk for disconnect failed");
+ CCoinsViewCache viewTemp(view, true);
+ if (!block.DisconnectBlock(pindex, viewTemp))
+ return error("SetBestChain() : DisconnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str());
+ if (!viewTemp.Flush())
+ return error("SetBestChain() : Cache flush failed after disconnect");
// Queue memory transactions to resurrect
BOOST_FOREACH(const CTransaction& tx, block.vtx)
- if (!(tx.IsCoinBase() || tx.IsCoinStake()))
+ if (!tx.IsCoinBase() && !tx.IsCoinStake())
vResurrect.push_back(tx);
}
// Connect longer branch
vector<CTransaction> vDelete;
- for (unsigned int i = 0; i < vConnect.size(); i++)
- {
- CBlockIndex* pindex = vConnect[i];
+ BOOST_FOREACH(CBlockIndex *pindex, vConnect) {
CBlock block;
if (!block.ReadFromDisk(pindex))
- return error("Reorganize() : ReadFromDisk for connect failed");
- if (!block.ConnectBlock(txdb, pindex))
- {
- // Invalid block
- return error("Reorganize() : ConnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str());
+ return error("SetBestChain() : ReadFromDisk for connect failed");
+ CCoinsViewCache viewTemp(view, true);
+ if (!block.ConnectBlock(pindex, viewTemp)) {
+ InvalidChainFound(pindexNew);
+ InvalidBlockFound(pindex);
+ return error("SetBestChain() : ConnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str());
}
+ if (!viewTemp.Flush())
+ return error("SetBestChain() : Cache flush failed after connect");
// Queue memory transactions to delete
BOOST_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");
+ bool fIsInitialDownload = IsInitialBlockDownload();
+ if (!fIsInitialDownload || view.GetCacheSize()>5000)
+ if (!view.Flush())
+ return false;
+
+ // At this point, all changes have been done to the database.
+ // Proceed by updating the memory structures.
// Disconnect shorter branch
BOOST_FOREACH(CBlockIndex* pindex, vDisconnect)
// Resurrect memory transactions that were in the disconnected branch
BOOST_FOREACH(CTransaction& tx, vResurrect)
- tx.AcceptToMemoryPool(txdb, false);
+ tx.AcceptToMemoryPool(false);
// Delete redundant memory transactions that are in the connected branch
BOOST_FOREACH(CTransaction& tx, vDelete)
mempool.remove(tx);
- printf("REORGANIZE: done\n");
-
- return true;
-}
-
-
-// Called from inside SetBestChain: attaches a block to the new best chain being built
-bool CBlock::SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew)
-{
- uint256 hash = GetHash();
-
- // Adding to current best branch
- if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash))
- {
- txdb.TxnAbort();
- InvalidChainFound(pindexNew);
- return false;
- }
- if (!txdb.TxnCommit())
- return error("SetBestChain() : TxnCommit failed");
-
- // Add to current best branch
- pindexNew->pprev->pnext = pindexNew;
-
- // Delete redundant memory transactions
- BOOST_FOREACH(CTransaction& tx, vtx)
- mempool.remove(tx);
-
- return true;
-}
-
-bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
-{
- uint256 hash = GetHash();
-
- if (!txdb.TxnBegin())
- return error("SetBestChain() : TxnBegin failed");
-
- if (pindexGenesisBlock == NULL && hash == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet))
- {
- txdb.WriteHashBestChain(hash);
- if (!txdb.TxnCommit())
- return error("SetBestChain() : TxnCommit failed");
- pindexGenesisBlock = pindexNew;
- }
- else if (hashPrevBlock == hashBestChain)
- {
- if (!SetBestChainInner(txdb, pindexNew))
- return error("SetBestChain() : SetBestChainInner failed");
- }
- else
- {
- // the first block in the new chain that will cause it to become the new best chain
- CBlockIndex *pindexIntermediate = pindexNew;
-
- // list of blocks that need to be connected afterwards
- std::vector<CBlockIndex*> vpindexSecondary;
-
- // Reorganize is costly in terms of db load, as it works in a single db transaction.
- // Try to limit how much needs to be done inside
- while (pindexIntermediate->pprev && pindexIntermediate->pprev->nChainTrust > pindexBest->nChainTrust)
- {
- vpindexSecondary.push_back(pindexIntermediate);
- pindexIntermediate = pindexIntermediate->pprev;
- }
-
- if (!vpindexSecondary.empty())
- printf("Postponing %"PRIszu" reconnects\n", vpindexSecondary.size());
-
- // Switch to new best branch
- if (!Reorganize(txdb, pindexIntermediate))
- {
- txdb.TxnAbort();
- InvalidChainFound(pindexNew);
- return error("SetBestChain() : Reorganize failed");
- }
-
- // Connect further blocks
- BOOST_REVERSE_FOREACH(CBlockIndex *pindex, vpindexSecondary)
- {
- CBlock block;
- if (!block.ReadFromDisk(pindex))
- {
- printf("SetBestChain() : ReadFromDisk failed\n");
- break;
- }
- if (!txdb.TxnBegin()) {
- printf("SetBestChain() : TxnBegin 2 failed\n");
- break;
- }
- // errors now are not fatal, we still did a reorganisation to a new chain in a valid way
- if (!block.SetBestChainInner(txdb, pindex))
- break;
- }
- }
-
// Update best block in wallet (so we can detect restored wallets)
- bool fIsInitialDownload = IsInitialBlockDownload();
if (!fIsInitialDownload)
{
const CBlockLocator locator(pindexNew);
}
// New best block
- hashBestChain = hash;
+ hashBestChain = pindexNew->GetBlockHash();
pindexBest = pindexNew;
pblockindexFBBHLast = NULL;
nBestHeight = pindexBest->nHeight;
uint256 nBestBlockTrust = pindexBest->nHeight != 0 ? (pindexBest->nChainTrust - pindexBest->pprev->nChainTrust) : pindexBest->nChainTrust;
- printf("SetBestChain: new best=%s height=%d trust=%s blocktrust=%"PRI64d" date=%s\n",
- hashBestChain.ToString().substr(0,20).c_str(), nBestHeight,
- CBigNum(nBestChainTrust).ToString().c_str(),
- nBestBlockTrust.Get64(),
+ printf("SetBestChain: new best=%s height=%d trust=%s blocktrust=%s tx=%lu date=%s\n",
+ hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str(), CBigNum(nBestBlockTrust).ToString().c_str(), (unsigned long)pindexNew->nChainTx,
DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str());
// Check the version of the last 100 blocks to see if we need to upgrade:
return true;
}
-// ppcoin: total coin age spent in transaction, in the unit of coin-days.
+// Total coin age spent in transaction, in the unit of coin-days.
// Only those coins meeting minimum age requirement counts. As those
// transactions not in main chain are not currently indexed so we
// might not find out about their coin age. Older transactions are
// guaranteed to be in main chain by sync-checkpoint. This rule is
// introduced to help nodes establish a consistent view of the coin
// age (trust score) of competing branches.
-bool CTransaction::GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const
+bool CTransaction::GetCoinAge(uint64& nCoinAge) const
{
+ CCoinsViewCache &inputs = *pcoinsTip;
+
CBigNum bnCentSecond = 0; // coin age in the unit of cent-seconds
nCoinAge = 0;
if (IsCoinBase())
return true;
- BOOST_FOREACH(const CTxIn& txin, vin)
+ for (unsigned int i = 0; i < vin.size(); i++)
{
- // First try finding the previous transaction in database
- CTransaction txPrev;
- CTxIndex txindex;
- if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
- continue; // previous transaction not in main chain
- if (nTime < txPrev.nTime)
- return false; // Transaction timestamp violation
+ const COutPoint &prevout = vin[i].prevout;
+ CCoins coins;
+ if (!inputs.GetCoins(prevout.hash, coins))
+ continue;
- // Read block header
- CBlock block;
- if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
- return false; // unable to read block of previous transaction
- if (block.GetBlockTime() + nStakeMinAge > nTime)
- continue; // only count coins meeting min age requirement
+ if (nTime < coins.nTime)
+ return false; // Transaction timestamp violation
- int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
- bnCentSecond += CBigNum(nValueIn) * (nTime-txPrev.nTime) / CENT;
+ // only count coins meeting min age requirement
+ if (coins.nBlockTime + nStakeMinAge > nTime)
+ continue;
- if (fDebug && GetBoolArg("-printcoinage"))
- printf("coin age nValueIn=%"PRI64d" nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString().c_str());
+ int64 nValueIn = coins.vout[vin[i].prevout.n].nValue;
+ bnCentSecond += CBigNum(nValueIn) * (nTime-coins.nTime) / CENT;
}
CBigNum bnCoinDay = bnCentSecond * CENT / COIN / (24 * 60 * 60);
return true;
}
-// ppcoin: total coin age spent in block, in the unit of coin-days.
+// Total coin age spent in block, in the unit of coin-days.
bool CBlock::GetCoinAge(uint64& nCoinAge) const
{
nCoinAge = 0;
- CTxDB txdb("r");
BOOST_FOREACH(const CTransaction& tx, vtx)
{
uint64 nTxCoinAge;
- if (tx.GetCoinAge(txdb, nTxCoinAge))
+ if (tx.GetCoinAge(nTxCoinAge))
nCoinAge += nTxCoinAge;
else
return false;
return true;
}
-bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos)
+bool CBlock::AddToBlockIndex(const CDiskBlockPos &pos)
{
// Check for duplicate
uint256 hash = GetHash();
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);
+ CBlockIndex* pindexNew = new CBlockIndex(*this);
if (!pindexNew)
return error("AddToBlockIndex() : new CBlockIndex failed");
- pindexNew->phashBlock = &hash;
+ 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;
}
-
- // ppcoin: compute chain trust score
+ pindexNew->nTx = vtx.size();
pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + pindexNew->GetBlockTrust();
+ pindexNew->nChainTx = (pindexNew->pprev ? pindexNew->pprev->nChainTx : 0) + pindexNew->nTx;
+ pindexNew->nFile = pos.nFile;
+ pindexNew->nDataPos = pos.nPos;
+ pindexNew->nUndoPos = 0;
+ pindexNew->nStatus = BLOCK_VALID_TRANSACTIONS | BLOCK_HAVE_DATA;
- // ppcoin: compute stake entropy bit for stake modifier
+ // Compute stake entropy bit for stake modifier
if (!pindexNew->SetStakeEntropyBit(GetStakeEntropyBit(pindexNew->nTime)))
return error("AddToBlockIndex() : SetStakeEntropyBit() failed");
- // ppcoin: record proof-of-stake hash value
+ // Record proof-of-stake hash value
if (pindexNew->IsProofOfStake())
{
if (!mapProofOfStake.count(hash))
pindexNew->hashProofOfStake = mapProofOfStake[hash];
}
- // ppcoin: compute stake modifier
+ // Compute stake modifier
uint64 nStakeModifier = 0;
bool fGeneratedStakeModifier = false;
if (!ComputeNextStakeModifier(pindexNew->pprev, nStakeModifier, fGeneratedStakeModifier))
if (!CheckStakeModifierCheckpoints(pindexNew->nHeight, pindexNew->nStakeModifierChecksum))
return error("AddToBlockIndex() : Rejected by stake modifier checkpoint height=%d, modifier=0x%016"PRI64x, pindexNew->nHeight, nStakeModifier);
- // Add to mapBlockIndex
- map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
- if (pindexNew->IsProofOfStake())
- setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime));
- pindexNew->phashBlock = &((*mi).first);
+ setBlockIndexValid.insert(pindexNew);
- // Write to disk block index
- CTxDB txdb;
- if (!txdb.TxnBegin())
- return false;
- txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
- if (!txdb.TxnCommit())
- return false;
+ pblocktree->WriteBlockIndex(CDiskBlockIndex(pindexNew));
- // New best
- if (pindexNew->nChainTrust > nBestChainTrust)
- if (!SetBestChain(txdb, pindexNew))
- return false;
+ // New best?
+ if (!ConnectBestBlock())
+ return false;
if (pindexNew == pindexBest)
{
// Notify UI to display prev block's coinbase if it was ours
static uint256 hashPrevBestCoinBase;
UpdatedTransaction(hashPrevBestCoinBase);
- hashPrevBestCoinBase = vtx[0].GetHash();
+ hashPrevBestCoinBase = GetTxHash(0);
}
+ pblocktree->Flush();
+
uiInterface.NotifyBlocksChanged();
return true;
}
+bool FindBlockPos(CDiskBlockPos &pos, unsigned int nAddSize, unsigned int nHeight, uint64 nTime)
+{
+ bool fUpdatedLast = false;
+
+ LOCK(cs_LastBlockFile);
+
+ while (infoLastBlockFile.nSize + nAddSize >= MAX_BLOCKFILE_SIZE) {
+ printf("Leaving block file %i: %s\n", nLastBlockFile, infoLastBlockFile.ToString().c_str());
+ FILE *file = OpenBlockFile(pos);
+ FileCommit(file);
+ fclose(file);
+ file = OpenUndoFile(pos);
+ FileCommit(file);
+ fclose(file);
+ nLastBlockFile++;
+ infoLastBlockFile.SetNull();
+ pblocktree->ReadBlockFileInfo(nLastBlockFile, infoLastBlockFile); // check whether data for the new file somehow already exist; can fail just fine
+ fUpdatedLast = true;
+ }
+ pos.nFile = nLastBlockFile;
+ pos.nPos = infoLastBlockFile.nSize;
+ infoLastBlockFile.nSize += nAddSize;
+ infoLastBlockFile.AddBlock(nHeight, nTime);
+
+ unsigned int nOldChunks = (pos.nPos + BLOCKFILE_CHUNK_SIZE - 1) / BLOCKFILE_CHUNK_SIZE;
+ unsigned int nNewChunks = (infoLastBlockFile.nSize + BLOCKFILE_CHUNK_SIZE - 1) / BLOCKFILE_CHUNK_SIZE;
+ if (nNewChunks > nOldChunks) {
+ FILE *file = OpenBlockFile(pos);
+ if (file) {
+ printf("Pre-allocating up to position 0x%x in blk%05u.dat\n", nNewChunks * BLOCKFILE_CHUNK_SIZE, pos.nFile);
+ AllocateFileRange(file, pos.nPos, nNewChunks * BLOCKFILE_CHUNK_SIZE - pos.nPos);
+ }
+ fclose(file);
+ }
+
+ if (!pblocktree->WriteBlockFileInfo(nLastBlockFile, infoLastBlockFile))
+ return error("FindBlockPos() : cannot write updated block info");
+ if (fUpdatedLast)
+ pblocktree->WriteLastBlockFile(nLastBlockFile);
+ return true;
+}
+
+bool FindUndoPos(int nFile, CDiskBlockPos &pos, unsigned int nAddSize)
+{
+ pos.nFile = nFile;
+
+ LOCK(cs_LastBlockFile);
+
+ unsigned int nNewSize;
+ if (nFile == nLastBlockFile) {
+ pos.nPos = infoLastBlockFile.nUndoSize;
+ nNewSize = (infoLastBlockFile.nUndoSize += nAddSize);
+ if (!pblocktree->WriteBlockFileInfo(nLastBlockFile, infoLastBlockFile))
+ return error("FindUndoPos() : cannot write updated block info");
+ } else {
+ CBlockFileInfo info;
+ if (!pblocktree->ReadBlockFileInfo(nFile, info))
+ return error("FindUndoPos() : cannot read block info");
+ pos.nPos = info.nUndoSize;
+ nNewSize = (info.nUndoSize += nAddSize);
+ if (!pblocktree->WriteBlockFileInfo(nFile, info))
+ return error("FindUndoPos() : cannot write updated block info");
+ }
+
+ unsigned int nOldChunks = (pos.nPos + UNDOFILE_CHUNK_SIZE - 1) / UNDOFILE_CHUNK_SIZE;
+ unsigned int nNewChunks = (nNewSize + UNDOFILE_CHUNK_SIZE - 1) / UNDOFILE_CHUNK_SIZE;
+ if (nNewChunks > nOldChunks) {
+ FILE *file = OpenUndoFile(pos);
+ if (file) {
+ printf("Pre-allocating up to position 0x%x in rev%05u.dat\n", nNewChunks * UNDOFILE_CHUNK_SIZE, pos.nFile);
+ AllocateFileRange(file, pos.nPos, nNewChunks * UNDOFILE_CHUNK_SIZE - pos.nPos);
+ }
+ fclose(file);
+ }
+
+ return true;
+}
bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot, bool fCheckSig) const
{
return DoS(50, error("CheckBlock() : proof of work failed"));
// Check timestamp
- if (GetBlockTime() > FutureDrift(GetAdjustedTime()))
+ 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
// Check coinstake timestamp
if (!CheckCoinStakeTimestamp(GetBlockTime(), (int64)vtx[1].nTime))
return DoS(50, error("CheckBlock() : coinstake timestamp violation nTimeBlock=%"PRI64d" nTimeTx=%u", GetBlockTime(), vtx[1].nTime));
-
- // NovaCoin: check proof-of-stake block signature
- if (fCheckSig && !CheckBlockSignature(true))
- return DoS(100, error("CheckBlock() : bad proof-of-stake block signature"));
}
else
{
{
bool checkEntropySig = (GetBlockTime() < ENTROPY_SWITCH_TIME);
- // NovaCoin: check proof-of-work block signature
- if (checkEntropySig && !CheckBlockSignature(false))
+ // check legacy proof-of-work block signature
+ if (checkEntropySig && !CheckLegacySignature())
return DoS(100, error("CheckBlock() : bad proof-of-work block signature"));
}
}
+
// Check transactions
BOOST_FOREACH(const CTransaction& tx, vtx)
{
if (!tx.CheckTransaction())
return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed"));
- // ppcoin: check transaction timestamp
+ // check transaction timestamp
if (GetBlockTime() < (int64)tx.nTime)
return DoS(50, error("CheckBlock() : block timestamp earlier than transaction timestamp"));
}
// Check for duplicate txids. This is caught by ConnectInputs(),
// but catching it earlier avoids a potential DoS attack:
+ BuildMerkleTree();
set<uint256> uniqueTx;
- BOOST_FOREACH(const CTransaction& tx, vtx)
- {
- uniqueTx.insert(tx.GetHash());
+ for (unsigned int i=0; i<vtx.size(); i++) {
+ uniqueTx.insert(GetTxHash(i));
}
if (uniqueTx.size() != vtx.size())
return DoS(100, error("CheckBlock() : duplicate transaction"));
if (fCheckMerkleRoot && hashMerkleRoot != BuildMerkleTree())
return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"));
-
return true;
}
+
bool CBlock::AcceptBlock()
{
// Check for duplicate
// Check proof-of-work or proof-of-stake
if (nBits != GetNextTargetRequired(pindexPrev, IsProofOfStake()))
- return DoS(100, error("AcceptBlock() : incorrect %s", IsProofOfWork() ? "proof-of-work" : "proof-of-stake"));
+ return DoS(100, error("AcceptBlock() : incorrect proof-of-%s amount", IsProofOfWork() ? "work" : "stake"));
// Check timestamp against prev
if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || FutureDrift(GetBlockTime()) < pindexPrev->GetBlockTime())
return DoS(100, error("AcceptBlock() : block height mismatch in coinbase"));
// Write block to history file
+ unsigned int nBlockSize = ::GetSerializeSize(*this, SER_DISK, CLIENT_VERSION);
if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK, CLIENT_VERSION)))
return error("AcceptBlock() : out of disk space");
- unsigned int nFile = -1;
- unsigned int nBlockPos = 0;
- if (!WriteToDisk(nFile, nBlockPos))
+ CDiskBlockPos blockPos;
+ {
+ if (!FindBlockPos(blockPos, nBlockSize+8, nHeight, nTime))
+ return error("AcceptBlock() : FindBlockPos failed");
+ }
+ if (!WriteToDisk(blockPos))
return error("AcceptBlock() : WriteToDisk failed");
- if (!AddToBlockIndex(nFile, nBlockPos))
+ if (!AddToBlockIndex(blockPos))
return error("AcceptBlock() : AddToBlockIndex failed");
// Relay inventory, but don't relay old inventory during initial block download
pnode->PushInventory(CInv(MSG_BLOCK, hash));
}
- // ppcoin: check pending sync-checkpoint
+ // Check pending sync-checkpoint
Checkpoints::AcceptPendingSyncCheckpoint();
return true;
if (mapOrphanBlocks.count(hash))
return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str());
- // ppcoin: check proof-of-stake
- // Limited duplicity on stake: prevents block flood attack
- // Duplicate stake allowed only when there is orphan child block
- if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
- return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());
-
// Preliminary checks
if (!pblock->CheckBlock())
return error("ProcessBlock() : CheckBlock FAILED");
- // ppcoin: verify hash target and signature of coinstake tx
if (pblock->IsProofOfStake())
{
- uint256 hashProofOfStake = 0, targetProofOfStake = 0;
- if (!CheckProofOfStake(pblock->vtx[1], pblock->nBits, hashProofOfStake, targetProofOfStake))
+ // Limited duplicity on stake: prevents block flood attack
+ // Duplicate stake allowed only when there is orphan child block
+ if (setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
+ return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());
+
+ bool fFatal = false;
+ uint256 hashProofOfStake;
+
+ // Verify proof-of-stake script, hash target and signature
+ if (!pblock->CheckSignature(fFatal, hashProofOfStake))
{
- printf("WARNING: ProcessBlock(): check proof-of-stake failed for block %s\n", hash.ToString().c_str());
- return false; // do not error here as we expect this during initial block download
+ if (fFatal)
+ {
+ // Invalid coinstake script, blockhash signature or no generator defined, nothing to do here
+ // This also may occur when supplied proof-of-stake doesn't satisfy required target
+ if (pfrom)
+ pfrom->Misbehaving(100);
+ return error("ProcessBlock() : invalid signatures found in proof-of-stake block %s", hash.ToString().c_str());
+ }
+ else
+ {
+ // Blockhash and coinstake signatures are OK but target checkings failed
+ // This may occur during initial block download
+
+ if (pfrom)
+ pfrom->Misbehaving(1); // Small DoS penalty
+
+ printf("WARNING: ProcessBlock(): proof-of-stake target checkings failed for block %s, we'll try again later\n", hash.ToString().c_str());
+ return false;
+ }
}
+
if (!mapProofOfStake.count(hash)) // add to mapProofOfStake
mapProofOfStake.insert(make_pair(hash, hashProofOfStake));
}
{
if (pfrom)
pfrom->Misbehaving(100);
- return error("ProcessBlock() : block with too little %s", pblock->IsProofOfStake()? "proof-of-stake" : "proof-of-work");
+ return error("ProcessBlock() : block with too little proof-of-%s", pblock->IsProofOfStake() ? "stake" : "work");
}
}
- // ppcoin: ask for pending sync-checkpoint if any
+ // Ask for pending sync-checkpoint if any
if (!IsInitialBlockDownload())
Checkpoints::AskForPendingSyncCheckpoint(pfrom);
{
printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,20).c_str());
CBlock* pblock2 = new CBlock(*pblock);
- // ppcoin: check proof-of-stake
+
if (pblock2->IsProofOfStake())
{
// Limited duplicity on stake: prevents block flood attack
else
setStakeSeenOrphan.insert(pblock2->GetProofOfStake());
}
+
mapOrphanBlocks.insert(make_pair(hash, pblock2));
mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2));
return true;
}
-// novacoin: attempt to generate suitable proof-of-stake
+// attempt to generate suitable proof-of-stake
bool CBlock::SignBlock(CWallet& wallet)
{
// if we are trying to sign
return false;
}
-// ppcoin: check block signature
-bool CBlock::CheckBlockSignature(bool fProofOfStake) const
+// get generation key
+bool CBlock::GetGenerator(CKey& GeneratorKey) const
{
- if (GetHash() == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet))
- return vchBlockSig.empty();
+ if(!IsProofOfStake())
+ return false;
vector<valtype> vSolutions;
txnouttype whichType;
- if(fProofOfStake)
+ const CTxOut& txout = vtx[1].vout[1];
+
+ if (!Solver(txout.scriptPubKey, whichType, vSolutions))
+ return false;
+ if (whichType == TX_PUBKEY)
{
- const CTxOut& txout = vtx[1].vout[1];
+ valtype& vchPubKey = vSolutions[0];
+ CKey key;
+ return GeneratorKey.SetPubKey(vchPubKey);
+ }
+
+ return false;
+}
+
+// verify proof-of-stake signatures
+bool CBlock::CheckSignature(bool& fFatal, uint256& hashProofOfStake) const
+{
+ CKey key;
+
+ // no generator or invalid hash signature means fatal error
+ fFatal = !GetGenerator(key) || !key.Verify(GetHash(), vchBlockSig);
+
+ if (fFatal)
+ return false;
+
+ uint256 hashTarget = 0;
+ if (!CheckProofOfStake(vtx[1], nBits, hashProofOfStake, hashTarget, fFatal))
+ return false; // hash target mismatch or invalid coinstake signature
+
+ return true;
+}
+
+// verify legacy proof-of-work signature
+bool CBlock::CheckLegacySignature() const
+{
+ if (IsProofOfStake())
+ return false;
+
+ vector<valtype> vSolutions;
+ txnouttype whichType;
+
+ for(unsigned int i = 0; i < vtx[0].vout.size(); i++)
+ {
+ const CTxOut& txout = vtx[0].vout[i];
if (!Solver(txout.scriptPubKey, whichType, vSolutions))
return false;
+
if (whichType == TX_PUBKEY)
{
+ // Verify
valtype& vchPubKey = vSolutions[0];
CKey key;
if (!key.SetPubKey(vchPubKey))
- return false;
+ continue;
if (vchBlockSig.empty())
- return false;
- return key.Verify(GetHash(), vchBlockSig);
+ continue;
+ if(!key.Verify(GetHash(), vchBlockSig))
+ continue;
+ return true;
}
}
- else
- {
- for(unsigned int i = 0; i < vtx[0].vout.size(); i++)
- {
- const CTxOut& txout = vtx[0].vout[i];
- if (!Solver(txout.scriptPubKey, whichType, vSolutions))
- return false;
-
- if (whichType == TX_PUBKEY)
- {
- // Verify
- valtype& vchPubKey = vSolutions[0];
- CKey key;
- if (!key.SetPubKey(vchPubKey))
- continue;
- if (vchBlockSig.empty())
- continue;
- if(!key.Verify(GetHash(), vchBlockSig))
- continue;
-
- return true;
- }
- }
- }
return false;
}
return true;
}
-static filesystem::path BlockFilePath(unsigned int nFile)
-{
- string strBlockFn = strprintf("blk%04u.dat", nFile);
- return GetDataDir() / strBlockFn;
-}
-FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode)
+CCriticalSection cs_LastBlockFile;
+CBlockFileInfo infoLastBlockFile;
+int nLastBlockFile = 0;
+
+FILE* OpenDiskFile(const CDiskBlockPos &pos, const char *prefix, bool fReadOnly)
{
- if ((nFile < 1) || (nFile == (unsigned int) -1))
+ if (pos.IsNull())
return NULL;
- FILE* file = fopen(BlockFilePath(nFile).string().c_str(), pszMode);
- if (!file)
+ boost::filesystem::path path = GetDataDir() / "blocks" / strprintf("%s%05u.dat", prefix, pos.nFile);
+ boost::filesystem::create_directories(path.parent_path());
+ FILE* file = fopen(path.string().c_str(), "rb+");
+ if (!file && !fReadOnly)
+ file = fopen(path.string().c_str(), "wb+");
+ if (!file) {
+ printf("Unable to open file %s\n", path.string().c_str());
return NULL;
- if (nBlockPos != 0 && !strchr(pszMode, 'a') && !strchr(pszMode, 'w'))
- {
- if (fseek(file, nBlockPos, SEEK_SET) != 0)
- {
+ }
+ if (pos.nPos) {
+ if (fseek(file, pos.nPos, SEEK_SET)) {
+ printf("Unable to seek to position %u of %s\n", pos.nPos, path.string().c_str());
fclose(file);
return NULL;
}
return file;
}
-static unsigned int nCurrentBlockFile = 1;
+FILE* OpenBlockFile(const CDiskBlockPos &pos, bool fReadOnly) {
+ return OpenDiskFile(pos, "blk", fReadOnly);
+}
+
+FILE *OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly) {
+ return OpenDiskFile(pos, "rev", fReadOnly);
+}
-FILE* AppendBlockFile(unsigned int& nFileRet)
+CBlockIndex * InsertBlockIndex(uint256 hash)
{
- nFileRet = 0;
- while (true)
+ if (hash == 0)
+ return NULL;
+
+ // Return existing
+ map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
+ if (mi != mapBlockIndex.end())
+ return (*mi).second;
+
+ // Create new
+ CBlockIndex* pindexNew = new CBlockIndex();
+ if (!pindexNew)
+ throw runtime_error("InsertBlockIndex() : new CBlockIndex failed");
+ mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
+ pindexNew->phashBlock = &((*mi).first);
+
+ return pindexNew;
+}
+
+bool static LoadBlockIndexDB()
+{
+ if (!pblocktree->LoadBlockIndexGuts())
+ return false;
+
+ if (fRequestShutdown)
+ return true;
+
+ // Calculate nChainTrust
+ vector<pair<int, CBlockIndex*> > vSortedByHeight;
+ vSortedByHeight.reserve(mapBlockIndex.size());
+ BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex)
{
- FILE* file = OpenBlockFile(nCurrentBlockFile, 0, "ab");
- if (!file)
- return NULL;
- if (fseek(file, 0, SEEK_END) != 0)
- return NULL;
- // FAT32 file size max 4GB, fseek and ftell max 2GB, so we must stay under 2GB
- if (ftell(file) < (long)(0x7F000000 - MAX_SIZE))
+ CBlockIndex* pindex = item.second;
+ vSortedByHeight.push_back(make_pair(pindex->nHeight, pindex));
+ }
+ sort(vSortedByHeight.begin(), vSortedByHeight.end());
+ BOOST_FOREACH(const PAIRTYPE(int, CBlockIndex*)& item, vSortedByHeight)
+ {
+ CBlockIndex* pindex = item.second;
+ pindex->nChainTrust = (pindex->pprev ? pindex->pprev->nChainTrust : 0) + pindex->GetBlockTrust();
+ pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
+ if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TRANSACTIONS && !(pindex->nStatus & BLOCK_FAILED_MASK))
+ setBlockIndexValid.insert(pindex);
+
+ // Calculate stake modifier checksum
+ pindex->nStakeModifierChecksum = GetStakeModifierChecksum(pindex);
+ if (!CheckStakeModifierCheckpoints(pindex->nHeight, pindex->nStakeModifierChecksum))
+ return error("LoadBlockIndexDB() : Failed stake modifier checkpoint height=%d, modifier=0x%016"PRI64x, pindex->nHeight, pindex->nStakeModifier);
+ }
+
+ // Load block file info
+ pblocktree->ReadLastBlockFile(nLastBlockFile);
+ printf("LoadBlockIndexDB(): last block file = %i\n", nLastBlockFile);
+ if (pblocktree->ReadBlockFileInfo(nLastBlockFile, infoLastBlockFile))
+ printf("LoadBlockIndexDB(): last block file: %s\n", infoLastBlockFile.ToString().c_str());
+
+ // Load hashBestChain pointer to end of best chain
+ pindexBest = pcoinsTip->GetBestBlock();
+ if (pindexBest == NULL)
+ {
+ if (pindexGenesisBlock == NULL)
+ return true;
+ return error("LoadBlockIndexDB() : hashBestChain not loaded");
+ }
+ hashBestChain = pindexBest->GetBlockHash();
+ nBestHeight = pindexBest->nHeight;
+ nBestChainTrust = pindexBest->nChainTrust;
+
+ // set 'next' pointers in best chain
+ CBlockIndex *pindex = pindexBest;
+ while(pindex != NULL && pindex->pprev != NULL) {
+ CBlockIndex *pindexPrev = pindex->pprev;
+ pindexPrev->pnext = pindex;
+ pindex = pindexPrev;
+ }
+ printf("LoadBlockIndexDB(): hashBestChain=%s height=%d date=%s\n",
+ hashBestChain.ToString().substr(0,20).c_str(), nBestHeight,
+ DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str());
+
+ // Load sync-checkpoint
+ if (!pblocktree->ReadSyncCheckpoint(Checkpoints::hashSyncCheckpoint))
+ return error("LoadBlockIndexDB() : hashSyncCheckpoint not loaded");
+ printf("LoadBlockIndexDB(): synchronized checkpoint %s\n", Checkpoints::hashSyncCheckpoint.ToString().c_str());
+
+ // Load bnBestInvalidTrust, OK if it doesn't exist
+ CBigNum bnBestInvalidTrust;
+ pblocktree->ReadBestInvalidTrust(bnBestInvalidTrust);
+ nBestInvalidTrust = bnBestInvalidTrust.getuint256();
+
+ // Verify blocks in the best chain
+ int nCheckLevel = GetArg("-checklevel", 1);
+ int nCheckDepth = GetArg( "-checkblocks", 2500);
+ if (nCheckDepth == 0)
+ nCheckDepth = 1000000000; // suffices until the year 19000
+ if (nCheckDepth > nBestHeight)
+ nCheckDepth = nBestHeight;
+ printf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel);
+ CBlockIndex* pindexFork = NULL;
+ for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev)
+ {
+ if (fRequestShutdown || pindex->nHeight < nBestHeight-nCheckDepth)
+ break;
+ CBlock block;
+ if (!block.ReadFromDisk(pindex))
+ return error("LoadBlockIndexDB() : block.ReadFromDisk failed");
+ // check level 1: verify block validity
+ if (nCheckLevel>0 && !block.CheckBlock())
{
- nFileRet = nCurrentBlockFile;
- return file;
+ printf("LoadBlockIndexDB() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str());
+ pindexFork = pindex->pprev;
}
- fclose(file);
- nCurrentBlockFile++;
+ // TODO: stronger verifications
}
+ if (pindexFork && !fRequestShutdown)
+ {
+ // TODO: reorg back
+ return error("LoadBlockIndexDB(): chain database corrupted");
+ }
+
+ return true;
}
+
bool LoadBlockIndex(bool fAllowNew)
{
CBigNum bnTrustedModulus;
ZCParams = new libzerocoin::Params(bnTrustedModulus);
//
- // Load block index
+ // Load block index from databases
//
- CTxDB txdb("cr+");
- if (!txdb.LoadBlockIndex())
+ if (!LoadBlockIndexDB())
return false;
//
block.nNonce = !fTestNet ? 1575379 : 46534;
//// debug print
+ uint256 hash = block.GetHash();
+ printf("%s\n", hash.ToString().c_str());
assert(block.hashMerkleRoot == uint256("0x4cb33b3b6a861dcbc685d3e614a9cafb945738d6833f182855679f2fad02057b"));
block.print();
- assert(block.GetHash() == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet));
+ assert(hash == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet));
assert(block.CheckBlock());
// Start new block file
- unsigned int nFile;
- unsigned int nBlockPos;
- if (!block.WriteToDisk(nFile, nBlockPos))
+ unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK, CLIENT_VERSION);
+ CDiskBlockPos blockPos;
+ if (!FindBlockPos(blockPos, nBlockSize+8, 0, block.nTime))
+ return error("AcceptBlock() : FindBlockPos failed");
+ if (!block.WriteToDisk(blockPos))
return error("LoadBlockIndex() : writing genesis block to disk failed");
- if (!block.AddToBlockIndex(nFile, nBlockPos))
+ if (!block.AddToBlockIndex(blockPos))
return error("LoadBlockIndex() : genesis block not accepted");
- // ppcoin: initialize synchronized checkpoint
+ // initialize synchronized checkpoint
if (!Checkpoints::WriteSyncCheckpoint((!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet)))
return error("LoadBlockIndex() : failed to init sync checkpoint");
}
string strPubKey = "";
-
// if checkpoint master key changed must reset sync-checkpoint
- if (!txdb.ReadCheckpointPubKey(strPubKey) || strPubKey != CSyncCheckpoint::strMasterPubKey)
- {
- // write checkpoint master key to db
- txdb.TxnBegin();
- if (!txdb.WriteCheckpointPubKey(CSyncCheckpoint::strMasterPubKey))
- return error("LoadBlockIndex() : failed to write new checkpoint master key to db");
- if (!txdb.TxnCommit())
- return error("LoadBlockIndex() : failed to commit new checkpoint master key to db");
+ if (!pblocktree->ReadCheckpointPubKey(strPubKey) || strPubKey != CSyncCheckpoint::strMasterPubKey)
+ {
+ {
+ LOCK(Checkpoints::cs_hashSyncCheckpoint);
+ // write checkpoint master key to db
+ if (!pblocktree->WriteCheckpointPubKey(CSyncCheckpoint::strMasterPubKey))
+ return error("LoadBlockIndex() : failed to write new checkpoint master key to db");
+ }
+
if ((!fTestNet) && !Checkpoints::ResetSyncCheckpoint())
return error("LoadBlockIndex() : failed to reset sync-checkpoint");
}
return true;
}
-
-
void PrintBlockTree()
{
// pre-compute tree structure
// print item
CBlock block;
block.ReadFromDisk(pindex);
- printf("%d (%u,%u) %s %08x %s mint %7s tx %"PRIszu"",
+ printf("%d (blk%05u.dat:0x%x) %s tx %"PRIszu"",
pindex->nHeight,
- pindex->nFile,
- pindex->nBlockPos,
- block.GetHash().ToString().c_str(),
- block.nBits,
+ pindex->GetBlockPos().nFile, pindex->GetBlockPos().nPos,
DateTimeStrFormat("%x %H:%M:%S", block.GetBlockTime()).c_str(),
- FormatMoney(pindex->nMint).c_str(),
block.vtx.size());
PrintWallets(block);
//
-bool static AlreadyHave(CTxDB& txdb, const CInv& inv)
+bool static AlreadyHave(const CInv& inv)
{
switch (inv.type)
{
case MSG_TX:
{
- bool txInMap = false;
+ bool txInMap = false;
{
- LOCK(mempool.cs);
- txInMap = (mempool.exists(inv.hash));
+ LOCK(mempool.cs);
+ txInMap = mempool.exists(inv.hash);
}
- return txInMap ||
- mapOrphanTransactions.count(inv.hash) ||
- txdb.ContainsTx(inv.hash);
+ return txInMap || mapOrphanTransactions.count(inv.hash) ||
+ pcoinsTip->HaveCoins(inv.hash);
}
-
case MSG_BLOCK:
return mapBlockIndex.count(inv.hash) ||
mapOrphanBlocks.count(inv.hash);
break;
}
}
- CTxDB txdb("r");
for (unsigned int nInv = 0; nInv < vInv.size(); nInv++)
{
const CInv &inv = vInv[nInv];
return true;
pfrom->AddInventoryKnown(inv);
- bool fAlreadyHave = AlreadyHave(txdb, inv);
+ bool fAlreadyHave = AlreadyHave(inv);
if (fDebug)
printf(" got inventory: %s %s\n", inv.ToString().c_str(), fAlreadyHave ? "have" : "new");
vector<uint256> vWorkQueue;
vector<uint256> vEraseQueue;
CDataStream vMsg(vRecv);
- CTxDB txdb("r");
CTransaction tx;
vRecv >> tx;
pfrom->AddInventoryKnown(inv);
bool fMissingInputs = false;
- if (tx.AcceptToMemoryPool(txdb, true, &fMissingInputs))
+ if (tx.AcceptToMemoryPool(true, &fMissingInputs))
{
- SyncWithWallets(tx, NULL, true);
+ SyncWithWallets(inv.hash, tx, NULL, true);
RelayTransaction(tx, inv.hash);
mapAlreadyAskedFor.erase(inv);
vWorkQueue.push_back(inv.hash);
CTransaction& orphanTx = mapOrphanTransactions[orphanTxHash];
bool fMissingInputs2 = false;
- if (orphanTx.AcceptToMemoryPool(txdb, true, &fMissingInputs2))
+ if (orphanTx.AcceptToMemoryPool(true, &fMissingInputs2))
{
printf(" accepted orphan tx %s\n", orphanTxHash.ToString().substr(0,10).c_str());
- SyncWithWallets(tx, NULL, true);
+ SyncWithWallets(inv.hash, tx, NULL, true);
RelayTransaction(orphanTx, orphanTxHash);
mapAlreadyAskedFor.erase(CInv(MSG_TX, orphanTxHash));
vWorkQueue.push_back(orphanTxHash);
//
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))
+ if (!AlreadyHave(inv))
{
if (fDebugNet)
printf("sending getdata: %s\n", inv.ToString().c_str());
}
return true;
}
+
+// Amount compression:
+// * If the amount is 0, output 0
+// * first, divide the amount (in base units) by the largest power of 10 possible; call the exponent e (e is max 9)
+// * if e<9, the last digit of the resulting number cannot be 0; store it as d, and drop it (divide by 10)
+// * call the result n
+// * output 1 + 10*(9*n + d - 1) + e
+// * if e==9, we only know the resulting number is not zero, so output 1 + 10*(n - 1) + 9
+// (this is decodable, as d is in [1-9] and e is in [0-9])
+
+uint64 CTxOutCompressor::CompressAmount(uint64 n)
+{
+ if (n == 0)
+ return 0;
+ int e = 0;
+ while (((n % 10) == 0) && e < 9) {
+ n /= 10;
+ e++;
+ }
+ if (e < 9) {
+ int d = (n % 10);
+ assert(d >= 1 && d <= 9);
+ n /= 10;
+ return 1 + (n*9 + d - 1)*10 + e;
+ } else {
+ return 1 + (n - 1)*10 + 9;
+ }
+}
+
+uint64 CTxOutCompressor::DecompressAmount(uint64 x)
+{
+ // x = 0 OR x = 1+10*(9*n + d - 1) + e OR x = 1+10*(n - 1) + 9
+ if (x == 0)
+ return 0;
+ x--;
+ // x = 10*(9*n + d - 1) + e
+ int e = x % 10;
+ x /= 10;
+ uint64 n = 0;
+ if (e < 9) {
+ // x = 9*n + d - 1
+ int d = (x % 9) + 1;
+ x /= 9;
+ // x = n
+ n = x*10 + d;
+ } else {
+ n = x+1;
+ }
+ while (e) {
+ n *= 10;
+ e--;
+ }
+ return n;
+}
class CInv;
class CRequestTracker;
class CNode;
+class CBlockIndexTrustComparator;
static const unsigned int MAX_BLOCK_SIZE = 1000000;
static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
static const unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50;
static const unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100;
static const unsigned int MAX_INV_SZ = 50000;
+
+static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB
+static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB
+static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB
+static const unsigned int MEMPOOL_HEIGHT = 0x7FFFFFFF;
+
static const int64 MIN_TX_FEE = CENT;
static const int64 MIN_RELAY_TX_FEE = CENT;
static const int64 MAX_MONEY = 2000000000 * COIN;
extern CScript COINBASE_FLAGS;
extern CCriticalSection cs_main;
extern std::map<uint256, CBlockIndex*> mapBlockIndex;
+extern std::set<CBlockIndex*, CBlockIndexTrustComparator> setBlockIndexValid;
extern std::set<std::pair<COutPoint, unsigned int> > setStakeSeen;
extern CBlockIndex* pindexGenesisBlock;
extern unsigned int nStakeMinAge;
static const uint64 nMinDiskSpace = 52428800;
class CReserveKey;
-class CTxDB;
-class CTxIndex;
+class CCoinsDB;
+class CBlockTreeDB;
+class CDiskBlockPos;
+class CCoins;
+class CTxUndo;
+class CCoinsView;
+class CCoinsViewCache;
void RegisterWallet(CWallet* pwalletIn);
void UnregisterWallet(CWallet* pwalletIn);
-void SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false, bool fConnect = true);
+void SyncWithWallets(const uint256 &hash, const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false, bool fConnect = true);
bool ProcessBlock(CNode* pfrom, CBlock* pblock);
bool CheckDiskSpace(uint64 nAdditionalBytes=0);
-FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode="rb");
-FILE* AppendBlockFile(unsigned int& nFileRet);
+FILE* OpenBlockFile(const CDiskBlockPos &pos, bool fReadOnly = false);
+FILE* OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly = false);
bool LoadBlockIndex(bool fAllowNew=true);
void PrintBlockTree();
CBlockIndex* FindBlockByHeight(int nHeight);
int GetNumBlocksOfPeers();
bool IsInitialBlockDownload();
std::string GetWarnings(std::string strFor);
-bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock);
+bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock, bool fAllowSlow);
+bool SetBestChain(CBlockIndex* pindexNew);
+bool ConnectBestBlock();
+CBlockIndex * InsertBlockIndex(uint256 hash);
uint256 WantedByOrphan(const CBlock* pblockOrphan);
const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake);
void StakeMiner(CWallet *pwallet);
void ResendWalletTransactions();
-
-
-
-
-
-
-
-
-
bool GetWalletFile(CWallet* pwallet, std::string &strWalletFileOut);
-/** Position on disk for a particular transaction. */
-class CDiskTxPos
+class CDiskBlockPos
{
public:
- unsigned int nFile;
- unsigned int nBlockPos;
- unsigned int nTxPos;
-
- CDiskTxPos()
- {
- SetNull();
- }
+ int nFile;
+ unsigned int nPos;
- CDiskTxPos(unsigned int nFileIn, unsigned int nBlockPosIn, unsigned int nTxPosIn)
- {
- nFile = nFileIn;
- nBlockPos = nBlockPosIn;
- nTxPos = nTxPosIn;
- }
-
- IMPLEMENT_SERIALIZE( READWRITE(FLATDATA(*this)); )
- void SetNull() { nFile = (unsigned int) -1; nBlockPos = 0; nTxPos = 0; }
- bool IsNull() const { return (nFile == (unsigned int) -1); }
+ IMPLEMENT_SERIALIZE(
+ READWRITE(VARINT(nFile));
+ READWRITE(VARINT(nPos));
+ )
- 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 CDiskBlockPos &a, const CDiskBlockPos &b) {
+ return (a.nFile == b.nFile && a.nPos == b.nPos);
}
- friend bool operator!=(const CDiskTxPos& a, const CDiskTxPos& b)
- {
+ friend bool operator!=(const CDiskBlockPos &a, const CDiskBlockPos &b) {
return !(a == b);
}
-
- std::string ToString() const
- {
- if (IsNull())
- return "null";
- else
- return strprintf("(nFile=%u, nBlockPos=%u, nTxPos=%u)", nFile, nBlockPos, nTxPos);
- }
-
- void print() const
- {
- printf("%s", ToString().c_str());
- }
+ void SetNull() { nFile = -1; nPos = 0; }
+ bool IsNull() const { return (nFile == -1); }
};
-
/** An inpoint - a combination of a transaction and an index n into its vin */
class CInPoint
{
scriptPubKey.clear();
}
- bool IsNull()
+ bool IsNull() const
{
return (nValue == -1);
}
std::string ToStringShort() const
{
- return strprintf(" out %s %s", FormatMoney(nValue).c_str(), scriptPubKey.ToString(true).c_str());
+ return strprintf(" out %s %s", FormatMoney(nValue).c_str(), scriptPubKey.ToString().substr(0, 10).c_str());
}
std::string ToString() const
GMF_SEND,
};
-typedef std::map<uint256, std::pair<CTxIndex, CTransaction> > MapPrevTx;
+// Modes for script/signature checking
+enum CheckSig_mode
+{
+ CS_NEVER, // never validate scripts
+ CS_AFTER_CHECKPOINT, // validate scripts after the last checkpoint
+ CS_ALWAYS // always validate scripts
+};
/** The basic transaction that is broadcasted on the network and contained in
* blocks. A transaction can contain multiple inputs and outputs.
@return True if all inputs (scriptSigs) use only standard transaction forms
@see CTransaction::FetchInputs
*/
- bool AreInputsStandard(const MapPrevTx& mapInputs) const;
+ bool AreInputsStandard(CCoinsViewCache& mapInputs) const;
/** Count ECDSA signature operations the old-fashioned (pre-0.6) way
@return number of sigops this transaction's outputs will produce when spent
@return maximum number of sigops required to validate this transaction's inputs
@see CTransaction::FetchInputs
*/
- unsigned int GetP2SHSigOpCount(const MapPrevTx& mapInputs) const;
+ unsigned int GetP2SHSigOpCount(CCoinsViewCache& mapInputs) const;
/** Amount of bitcoins spent by this transaction.
@return sum of all outputs (note: does not include fees)
@return Sum of value of all inputs (scriptSigs)
@see CTransaction::FetchInputs
*/
- int64 GetValueIn(const MapPrevTx& mapInputs) const;
+ int64 GetValueIn(CCoinsViewCache& mapInputs) const;
static bool AllowFree(double dPriority)
{
int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=false, enum GetMinFee_mode mode=GMF_BLOCK, unsigned int nBytes = 0) const;
- bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=NULL)
- {
- CAutoFile filein = CAutoFile(OpenBlockFile(pos.nFile, 0, pfileRet ? "rb+" : "rb"), SER_DISK, CLIENT_VERSION);
- if (!filein)
- return error("CTransaction::ReadFromDisk() : OpenBlockFile failed");
-
- // Read transaction
- if (fseek(filein, pos.nTxPos, SEEK_SET) != 0)
- return error("CTransaction::ReadFromDisk() : fseek failed");
-
- try {
- filein >> *this;
- }
- catch (std::exception &e) {
- return error("%s() : deserialize or I/O error", __PRETTY_FUNCTION__);
- }
-
- // 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 &&
}
- bool ReadFromDisk(CTxDB& txdb, COutPoint prevout, CTxIndex& txindexRet);
- bool ReadFromDisk(CTxDB& txdb, COutPoint prevout);
- bool ReadFromDisk(COutPoint prevout);
- bool DisconnectInputs(CTxDB& txdb);
+ // Do all possible client-mode checks
+ bool ClientCheckInputs() const;
- /** Fetch from memory and/or disk. inputsRet keys are transaction hashes.
+ // Check whether all prevouts of this transaction are present in the UTXO set represented by view
+ bool HaveInputs(CCoinsViewCache &view) const;
- @param[in] txdb Transaction database
- @param[in] mapTestPool List of pending changes to the transaction index database
- @param[in] fBlock True if being called to add a new best-block to the chain
- @param[in] fMiner True if being called by CreateNewBlock
- @param[out] inputsRet Pointers to this transaction's inputs
- @param[out] fInvalid returns true if transaction is invalid
- @return Returns true if all inputs are in txdb or mapTestPool
- */
- bool FetchInputs(CTxDB& txdb, const std::map<uint256, CTxIndex>& mapTestPool,
- bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid);
-
- /** Sanity check previous transactions, then, if all checks succeed,
- mark them as spent by this transaction.
-
- @param[in] inputs Previous transactions (from FetchInputs)
- @param[out] mapTestPool Keeps track of inputs that need to be updated on disk
- @param[in] posThisTx Position of this transaction on disk
- @param[in] pindexBlock
- @param[in] fBlock true if called from ConnectBlock
- @param[in] fMiner true if called from CreateNewBlock
- @param[in] fStrictPayToScriptHash true if fully validating p2sh transactions
- @return Returns true if all checks succeed
- */
- bool ConnectInputs(CTxDB& txdb, MapPrevTx inputs,
- std::map<uint256, CTxIndex>& mapTestPool, const CDiskTxPos& posThisTx,
- const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash=true);
- bool ClientConnectInputs();
+ // Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)
+ // This does not modify the UTXO set
+ bool CheckInputs(CCoinsViewCache &view, enum CheckSig_mode csmode, bool fStrictPayToScriptHash=true, bool fStrictEncodings=true, CBlock *pblock=NULL) const;
+
+ // Apply the effects of this transaction on the UTXO set represented by view
+ bool UpdateCoins(CCoinsViewCache &view, CTxUndo &txundo, int nHeight, unsigned int nBlockTime, const uint256 &txhash) const;
+
+ // Context-independent validity checks
bool CheckTransaction() const;
- bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL);
- bool GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const; // ppcoin: get transaction coin age
+ // Try to accept this transaction into the memory pool
+ bool AcceptToMemoryPool(bool fCheckInputs=true, bool* pfMissingInputs=NULL);
+ bool GetCoinAge(uint64& nCoinAge) const; // Get transaction coin age
protected:
- const CTxOut& GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const;
+ static const CTxOut &GetOutputFor(const CTxIn& input, CCoinsViewCache& mapInputs);
+};
+
+
+/** wrapper for CTxOut that provides a more compact serialization */
+class CTxOutCompressor
+{
+private:
+ CTxOut &txout;
+
+public:
+ static uint64 CompressAmount(uint64 nAmount);
+ static uint64 DecompressAmount(uint64 nAmount);
+
+ CTxOutCompressor(CTxOut &txoutIn) : txout(txoutIn) { }
+
+ IMPLEMENT_SERIALIZE(({
+ if (!fRead) {
+ uint64 nVal = CompressAmount(txout.nValue);
+ READWRITE(VARINT(nVal));
+ } else {
+ uint64 nVal = 0;
+ READWRITE(VARINT(nVal));
+ txout.nValue = DecompressAmount(nVal);
+ }
+ CScriptCompressor cscript(REF(txout.scriptPubKey));
+ READWRITE(cscript);
+ });)
+};
+
+/** Undo information for a CTxIn
+ *
+ * Contains the prevout's CTxOut being spent, and if this was the
+ * last output of the affected transaction, its metadata as well
+ * (coinbase or not, height, transaction version)
+ */
+class CTxInUndo
+{
+public:
+ CTxOut txout; // the txout data before being spent
+ bool fCoinBase; // if the outpoint was the last unspent: whether it belonged to a coinbase
+ bool fCoinStake; // if the outpoint was the last unspent: whether it belonged to a coinstake
+ unsigned int nHeight; // if the outpoint was the last unspent: its height
+ int nVersion; // if the outpoint was the last unspent: its version
+ unsigned int nTime; // if the outpoint was the last unspent: its timestamp
+ unsigned int nBlockTime; // if the outpoint was the last unspent: its block timestamp
+
+ CTxInUndo() : txout(), fCoinBase(false), fCoinStake(false), nHeight(0), nVersion(0), nTime(0), nBlockTime(0) {}
+ CTxInUndo(const CTxOut &txoutIn, bool fCoinBaseIn = false, bool fCoinStakeIn = false, unsigned int nHeightIn = 0, int nVersionIn = 0, int nTimeIn = 0, int nBlockTimeIn = 0) : txout(txoutIn), fCoinBase(fCoinBaseIn), fCoinStake(fCoinStakeIn), nHeight(nHeightIn), nVersion(nVersionIn), nTime(nTimeIn), nBlockTime(nBlockTimeIn) { }
+
+ unsigned int GetSerializeSize(int nType, int nVersion) const {
+ return ::GetSerializeSize(VARINT(nHeight*2+(fCoinBase ? 1 : 0)), nType, nVersion) +
+ ::GetSerializeSize(VARINT(nTime*2+(fCoinStake ? 1 : 0)), nType, nVersion) +
+ ::GetSerializeSize(VARINT(nBlockTime), nType, nVersion) +
+ (nHeight > 0 ? ::GetSerializeSize(VARINT(this->nVersion), nType, nVersion) : 0) +
+ ::GetSerializeSize(CTxOutCompressor(REF(txout)), nType, nVersion);
+ }
+
+ template<typename Stream>
+ void Serialize(Stream &s, int nType, int nVersion) const {
+ ::Serialize(s, VARINT(nHeight*2+(fCoinBase ? 1 : 0)), nType, nVersion);
+ ::Serialize(s, VARINT(nTime*2+(fCoinStake ? 1 : 0)), nType, nVersion);
+ ::Serialize(s, VARINT(nBlockTime), nType, nVersion);
+ if (nHeight > 0)
+ ::Serialize(s, VARINT(this->nVersion), nType, nVersion);
+ ::Serialize(s, CTxOutCompressor(REF(txout)), nType, nVersion);
+ }
+
+ template<typename Stream>
+ void Unserialize(Stream &s, int nType, int nVersion) {
+ unsigned int nCodeHeight = 0, nCodeTime = 0;
+ ::Unserialize(s, VARINT(nCodeHeight), nType, nVersion);
+ nHeight = nCodeHeight / 2;
+ fCoinBase = nCodeHeight & 1;
+ ::Unserialize(s, VARINT(nCodeTime), nType, nVersion);
+ nTime = nCodeTime / 2;
+ fCoinStake = nCodeTime & 1;
+ ::Unserialize(s, VARINT(nBlockTime), nType, nVersion);
+ if (nHeight > 0)
+ ::Unserialize(s, VARINT(this->nVersion), nType, nVersion);
+ ::Unserialize(s, REF(CTxOutCompressor(REF(txout))), nType, nVersion);
+ }
+};
+
+/** Undo information for a CTransaction */
+class CTxUndo
+{
+public:
+ // undo information for all txins
+ std::vector<CTxInUndo> vprevout;
+
+ IMPLEMENT_SERIALIZE(
+ READWRITE(vprevout);
+ )
+};
+
+/** Undo information for a CBlock */
+class CBlockUndo
+{
+public:
+ std::vector<CTxUndo> vtxundo; // for all but the coinbase
+
+ IMPLEMENT_SERIALIZE(
+ READWRITE(vtxundo);
+ )
+
+ bool WriteToDisk(CDiskBlockPos &pos)
+ {
+ // Open history file to append
+ CAutoFile fileout = CAutoFile(OpenUndoFile(pos), SER_DISK, CLIENT_VERSION);
+ if (!fileout)
+ return error("CBlockUndo::WriteToDisk() : OpenUndoFile failed");
+
+ // Write index header
+ unsigned int nSize = fileout.GetSerializeSize(*this);
+ fileout << FLATDATA(pchMessageStart) << nSize;
+
+ // Write undo data
+ long fileOutPos = ftell(fileout);
+ if (fileOutPos < 0)
+ return error("CBlockUndo::WriteToDisk() : ftell failed");
+ pos.nPos = (unsigned int)fileOutPos;
+ fileout << *this;
+
+ // Flush stdio buffers and commit to disk before returning
+ fflush(fileout);
+ if (!IsInitialBlockDownload())
+ FileCommit(fileout);
+
+ return true;
+ }
+
};
+/** pruned version of CTransaction: only retains metadata and unspent transaction outputs
+ *
+ * Serialized format:
+ * - VARINT(nVersion)
+ * - VARINT(nCode)
+ * - unspentness bitvector, for vout[2] and further; least significant byte first
+ * - the non-spent CTxOuts (via CTxOutCompressor)
+ * - VARINT(nHeight)
+ * - VARINT(nTime + is_coinstake)
+ * - VARINT(nBlockTime)
+ *
+ * The nCode value consists of:
+ * - bit 1: IsCoinBase()
+ * - bit 2: vout[0] is not spent
+ * - bit 4: vout[1] is not spent
+ * - The higher bits encode N, the number of non-zero bytes in the following bitvector.
+ * - In case both bit 2 and bit 4 are unset, they encode N-1, as there must be at
+ * least one non-spent output).
+ *
+ * Example: 0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e40f1d75240f1d752
+ * <><><--------------------------------------------><----><------><------>
+ * | \ | / / /
+ * version code vout[1] height timestamp block timestamp
+ *
+ * - version = 1
+ * - code = 4 (vout[1] is not spent, and 0 non-zero bytes of bitvector follow)
+ * - unspentness bitvector: as 0 non-zero bytes follow, it has length 0
+ * - vout[1]: 835800816115944e077fe7c803cfa57f29b36bf87c1d35
+ * * 8358: compact amount representation for 60000000000 (600 BTC)
+ * * 00: special txout type pay-to-pubkey-hash
+ * * 816115944e077fe7c803cfa57f29b36bf87c1d35: address uint160
+ * - height = 203998
+ * - time = 1389883712
+ * - is_coinstake = 0
+ * - block time = 1389883712
+ *
+ *
+ * Example: 0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b40f1d75240f1d752
+ * <><><--><--------------------------------------------------><----------------------------------------------><----><------><------>
+ * / \ \ | | / / /
+ * version code unspentness vout[4] vout[16] height timestamp block timestamp
+ *
+ * - version = 1
+ * - code = 9 (coinbase, neither vout[0] or vout[1] are unspent,
+ * 2 (1, +1 because both bit 2 and bit 4 are unset) non-zero bitvector bytes follow)
+ * - unspentness bitvector: bits 2 (0x04) and 14 (0x4000) are set, so vout[2+2] and vout[14+2] are unspent
+ * - vout[4]: 86ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4ee
+ * * 86ef97d579: compact amount representation for 234925952 (2.35 BTC)
+ * * 00: special txout type pay-to-pubkey-hash
+ * * 61b01caab50f1b8e9c50a5057eb43c2d9563a4ee: address uint160
+ * - vout[16]: bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4
+ * * bbd123: compact amount representation for 110397 (0.001 BTC)
+ * * 00: special txout type pay-to-pubkey-hash
+ * * 8c988f1a4a4de2161e0f50aac7f17e7f9555caa4: address uint160
+ * - height = 120891
+ * - time = 1389883712
+ * - is_coinstake = 0
+ * - block time = 1389883712
+ */
+class CCoins
+{
+public:
+ // whether transaction is a coinbase
+ bool fCoinBase;
+
+ // whether transaction is a coinstake
+ bool fCoinStake;
+
+ // unspent transaction outputs; spent outputs are .IsNull(); spent outputs at the end of the array are dropped
+ std::vector<CTxOut> vout;
+
+ // at which height this transaction was included in the active blockchain
+ int nHeight;
+
+ // version of the CTransaction; accesses to this value should probably check for nHeight as well,
+ // as new tx version will probably only be introduced at certain heights
+ int nVersion;
+
+ // transaction timestamp + coinstake flag
+ unsigned int nTime;
+
+ // block timestamp
+ unsigned int nBlockTime;
+
+ // construct a CCoins from a CTransaction, at a given height/timestamp
+ CCoins(const CTransaction &tx, int nHeightIn, int nBlockTimeIn) : fCoinBase(tx.IsCoinBase()), fCoinStake(tx.IsCoinStake()), vout(tx.vout), nHeight(nHeightIn), nVersion(tx.nVersion), nTime(tx.nTime), nBlockTime(nBlockTimeIn) { }
+
+ // empty constructor
+ CCoins() : fCoinBase(false), fCoinStake(false), vout(0), nHeight(0), nVersion(0), nTime(0), nBlockTime(0) { }
+
+ // remove spent outputs at the end of vout
+ void Cleanup() {
+ while (vout.size() > 0 && vout.back().IsNull())
+ vout.pop_back();
+ }
+
+ // equality test
+ friend bool operator==(const CCoins &a, const CCoins &b) {
+ return a.fCoinBase == b.fCoinBase &&
+ a.fCoinStake == b.fCoinStake &&
+ a.nHeight == b.nHeight &&
+ a.nVersion == b.nVersion &&
+ a.nTime == b.nTime &&
+ a.nBlockTime == b.nBlockTime &&
+ a.vout == b.vout;
+ }
+ friend bool operator!=(const CCoins &a, const CCoins &b) {
+ return !(a == b);
+ }
+
+ // calculate number of bytes for the bitmask, and its number of non-zero bytes
+ // each bit in the bitmask represents the availability of one output, but the
+ // availabilities of the first two outputs are encoded separately
+ void CalcMaskSize(unsigned int &nBytes, unsigned int &nNonzeroBytes) const {
+ unsigned int nLastUsedByte = 0;
+ for (unsigned int b = 0; 2+b*8 < vout.size(); b++) {
+ bool fZero = true;
+ for (unsigned int i = 0; i < 8 && 2+b*8+i < vout.size(); i++) {
+ if (!vout[2+b*8+i].IsNull()) {
+ fZero = false;
+ continue;
+ }
+ }
+ if (!fZero) {
+ nLastUsedByte = b + 1;
+ nNonzeroBytes++;
+ }
+ }
+ nBytes += nLastUsedByte;
+ }
+
+ bool IsCoinBase() const {
+ return fCoinBase;
+ }
+
+ bool IsCoinStake() const {
+ return fCoinStake;
+ }
+
+ unsigned int GetSerializeSize(int nType, int nVersion) const {
+ unsigned int nSize = 0;
+ unsigned int nMaskSize = 0, nMaskCode = 0;
+ CalcMaskSize(nMaskSize, nMaskCode);
+ bool fFirst = vout.size() > 0 && !vout[0].IsNull();
+ bool fSecond = vout.size() > 1 && !vout[1].IsNull();
+ assert(fFirst || fSecond || nMaskCode);
+ unsigned int nCode = 8*(nMaskCode - (fFirst || fSecond ? 0 : 1)) + (fCoinBase ? 1 : 0) + (fFirst ? 2 : 0) + (fCoinStake ? 1 : 0) + (fSecond ? 4 : 0);
+ // version
+ nSize += ::GetSerializeSize(VARINT(this->nVersion), nType, nVersion);
+ // size of header code
+ nSize += ::GetSerializeSize(VARINT(nCode), nType, nVersion);
+ // spentness bitmask
+ nSize += nMaskSize;
+ // txouts themself
+ for (unsigned int i = 0; i < vout.size(); i++)
+ if (!vout[i].IsNull())
+ nSize += ::GetSerializeSize(CTxOutCompressor(REF(vout[i])), nType, nVersion);
+ // height
+ nSize += ::GetSerializeSize(VARINT(nHeight), nType, nVersion);
+ // timestamp and coinstake flag
+ nSize += ::GetSerializeSize(VARINT(nTime*2+(fCoinStake ? 1 : 0)), nType, nVersion);
+ // block timestamp
+ nSize += ::GetSerializeSize(VARINT(nBlockTime), nType, nVersion);
+ return nSize;
+ }
+
+ template<typename Stream>
+ void Serialize(Stream &s, int nType, int nVersion) const {
+ unsigned int nMaskSize = 0, nMaskCode = 0;
+ CalcMaskSize(nMaskSize, nMaskCode);
+ bool fFirst = vout.size() > 0 && !vout[0].IsNull();
+ bool fSecond = vout.size() > 1 && !vout[1].IsNull();
+ assert(fFirst || fSecond || nMaskCode);
+ unsigned int nCode = 8*(nMaskCode - (fFirst || fSecond ? 0 : 1)) + (fCoinBase ? 1 : 0) + (fFirst ? 2 : 0) + (fSecond ? 4 : 0);
+ // version
+ ::Serialize(s, VARINT(this->nVersion), nType, nVersion);
+ // header code
+ ::Serialize(s, VARINT(nCode), nType, nVersion);
+ // spentness bitmask
+ for (unsigned int b = 0; b<nMaskSize; b++) {
+ unsigned char chAvail = 0;
+ for (unsigned int i = 0; i < 8 && 2+b*8+i < vout.size(); i++)
+ if (!vout[2+b*8+i].IsNull())
+ chAvail |= (1 << i);
+ ::Serialize(s, chAvail, nType, nVersion);
+ }
+ // txouts themself
+ for (unsigned int i = 0; i < vout.size(); i++) {
+ if (!vout[i].IsNull())
+ ::Serialize(s, CTxOutCompressor(REF(vout[i])), nType, nVersion);
+ }
+ // coinbase height
+ ::Serialize(s, VARINT(nHeight), nType, nVersion);
+ // transaction timestamp and coinstake flag
+ ::Serialize(s, VARINT(nTime*2+(fCoinStake ? 1 : 0)), nType, nVersion);
+ // block time
+ ::Serialize(s, VARINT(nBlockTime), nType, nVersion);
+ }
+
+ template<typename Stream>
+ void Unserialize(Stream &s, int nType, int nVersion) {
+ unsigned int nCode = 0, nCodeTime = 0;
+ // version
+ ::Unserialize(s, VARINT(this->nVersion), nType, nVersion);
+ // header code
+ ::Unserialize(s, VARINT(nCode), nType, nVersion);
+ fCoinBase = nCode & 1;
+ std::vector<bool> vAvail(2, false);
+ vAvail[0] = nCode & 2;
+ vAvail[1] = nCode & 4;
+ unsigned int nMaskCode = (nCode / 8) + ((nCode & 6) != 0 ? 0 : 1);
+ // spentness bitmask
+ while (nMaskCode > 0) {
+ unsigned char chAvail = 0;
+ ::Unserialize(s, chAvail, nType, nVersion);
+ for (unsigned int p = 0; p < 8; p++) {
+ bool f = (chAvail & (1 << p)) != 0;
+ vAvail.push_back(f);
+ }
+ if (chAvail != 0)
+ nMaskCode--;
+ }
+ // txouts themself
+ vout.assign(vAvail.size(), CTxOut());
+ for (unsigned int i = 0; i < vAvail.size(); i++) {
+ if (vAvail[i])
+ ::Unserialize(s, REF(CTxOutCompressor(vout[i])), nType, nVersion);
+ }
+ // coinbase height
+ ::Unserialize(s, VARINT(nHeight), nType, nVersion);
+ // transaction timestamp
+ ::Unserialize(s, VARINT(nCodeTime), nType, nVersion);
+ nTime = nCodeTime / 2;
+ fCoinStake = nCodeTime & 1;
+ // block timestamp
+ ::Unserialize(s, VARINT(nBlockTime), nType, nVersion);
+ Cleanup();
+ }
+
+ // mark an outpoint spent, and construct undo information
+ bool Spend(const COutPoint &out, CTxInUndo &undo) {
+ if (out.n >= vout.size())
+ return false;
+ if (vout[out.n].IsNull())
+ return false;
+ undo = CTxInUndo(vout[out.n]);
+ vout[out.n].SetNull();
+ Cleanup();
+ if (vout.size() == 0) {
+ undo.nHeight = nHeight;
+ undo.nTime = nTime;
+ undo.nBlockTime = nBlockTime;
+ undo.fCoinBase = fCoinBase;
+ undo.fCoinStake = fCoinStake;
+ undo.nVersion = this->nVersion;
+ }
+ return true;
+ }
+
+ // mark a vout spent
+ bool Spend(int nPos) {
+ CTxInUndo undo;
+ COutPoint out(0, nPos);
+ return Spend(out, undo);
+ }
+
+ // check whether a particular output is still available
+ bool IsAvailable(unsigned int nPos) const {
+ return (nPos < vout.size() && !vout[nPos].IsNull());
+ }
+
+ // check whether the entire CCoins is spent
+ // note that only !IsPruned() CCoins can be serialized
+ bool IsPruned() const {
+ BOOST_FOREACH(const CTxOut &out, vout)
+ if (!out.IsNull())
+ return false;
+ return true;
+ }
+};
int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
bool IsInMainChain() const { return GetDepthInMainChain() > 0; }
int GetBlocksToMaturity() const;
- bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true);
- bool AcceptToMemoryPool();
+ bool AcceptToMemoryPool(bool fCheckInputs=true);
};
-
-/** 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;
- std::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)
- {
- return (a.pos == b.pos &&
- a.vSpent == b.vSpent);
- }
-
- friend bool operator!=(const CTxIndex& a, const CTxIndex& b)
- {
- return !(a == b);
- }
- int GetDepthInMainChain() const;
-
-};
-
-
-
-
-
/** 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
{
return (vMerkleTree.empty() ? 0 : vMerkleTree.back());
}
+ const uint256 &GetTxHash(unsigned int nIndex) const {
+ assert(vMerkleTree.size() > 0); // BuildMerkleTree must have been called first
+ assert(nIndex < vtx.size());
+ return vMerkleTree[nIndex];
+ }
+
std::vector<uint256> GetMerkleBranch(int nIndex) const
{
if (vMerkleTree.empty())
return hash;
}
-
- bool WriteToDisk(unsigned int& nFileRet, unsigned int& nBlockPosRet)
+ bool WriteToDisk(CDiskBlockPos &pos)
{
// Open history file to append
- CAutoFile fileout = CAutoFile(AppendBlockFile(nFileRet), SER_DISK, CLIENT_VERSION);
+ CAutoFile fileout = CAutoFile(OpenBlockFile(pos), SER_DISK, CLIENT_VERSION);
if (!fileout)
- return error("CBlock::WriteToDisk() : AppendBlockFile failed");
+ return error("CBlock::WriteToDisk() : OpenBlockFile failed");
// Write index header
unsigned int nSize = fileout.GetSerializeSize(*this);
long fileOutPos = ftell(fileout);
if (fileOutPos < 0)
return error("CBlock::WriteToDisk() : ftell failed");
- nBlockPosRet = fileOutPos;
+ pos.nPos = (unsigned int)fileOutPos;
fileout << *this;
// Flush stdio buffers and commit to disk before returning
fflush(fileout);
- if (!IsInitialBlockDownload() || (nBestHeight+1) % 500 == 0)
+ if (!IsInitialBlockDownload())
FileCommit(fileout);
return true;
}
- bool ReadFromDisk(unsigned int nFile, unsigned int nBlockPos, bool fReadTransactions=true)
+ bool ReadFromDisk(const CDiskBlockPos &pos, bool fReadTransactions = true)
{
SetNull();
// Open history file to read
- CAutoFile filein = CAutoFile(OpenBlockFile(nFile, nBlockPos, "rb"), SER_DISK, CLIENT_VERSION);
+ CAutoFile filein = CAutoFile(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION);
if (!filein)
return error("CBlock::ReadFromDisk() : OpenBlockFile failed");
if (!fReadTransactions)
return true;
}
-
-
void print() const
{
printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%"PRIszu", vchBlockSig=%s)\n",
printf("\n");
}
+ // Undo the effects of this block (with given index) on the UTXO set represented by coins
+ bool DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &coins);
+
+ // Apply the effects of this block (with given index) on the UTXO set represented by coins
+ bool ConnectBlock(CBlockIndex *pindex, CCoinsViewCache &coins, bool fJustCheck=false);
- bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex);
- bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck=false);
+ // Read a block from disk
bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true);
- bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew);
- bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos);
- bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true, bool fCheckSig=true) const;
+
+ // Add this block to the block index, and if necessary, switch the active block chain to this
+ bool AddToBlockIndex(const CDiskBlockPos &pos);
+
+ // Context-independent validity checks
+ bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true, bool fCheckSig=false) const;
+
+ // Store block on disk
bool AcceptBlock();
- bool GetCoinAge(uint64& nCoinAge) const; // ppcoin: calculate total coin age spent in block
+
+ // Get total coinage consumed
+ bool GetCoinAge(uint64& nCoinAge) const;
+
+ // Generate proof-of-stake block signature
bool SignBlock(CWallet& keystore);
- bool CheckBlockSignature(bool fProofOfStake) const;
-private:
- bool SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew);
+ // Get generator key
+ bool GetGenerator(CKey& GeneratorKey) const;
+
+ // Validate proof-of-stake block signature
+ bool CheckSignature(bool& fFatal, uint256& hashProofOfStake) const;
+
+ // Legacy proof-of-work signature
+ bool CheckLegacySignature() const;
};
+class CBlockFileInfo
+{
+public:
+ unsigned int nBlocks; // number of blocks stored in file
+ unsigned int nSize; // number of used bytes of block file
+ unsigned int nUndoSize; // number of used bytes in the undo file
+ unsigned int nHeightFirst; // lowest height of block in file
+ unsigned int nHeightLast; // highest height of block in file
+ uint64 nTimeFirst; // earliest time of block in file
+ uint64 nTimeLast; // latest time of block in file
+
+ IMPLEMENT_SERIALIZE(
+ READWRITE(VARINT(nBlocks));
+ READWRITE(VARINT(nSize));
+ READWRITE(VARINT(nUndoSize));
+ READWRITE(VARINT(nHeightFirst));
+ READWRITE(VARINT(nHeightLast));
+ READWRITE(VARINT(nTimeFirst));
+ READWRITE(VARINT(nTimeLast));
+ )
+
+ void SetNull() {
+ nBlocks = 0;
+ nSize = 0;
+ nUndoSize = 0;
+ nHeightFirst = 0;
+ nHeightLast = 0;
+ nTimeFirst = 0;
+ nTimeLast = 0;
+ }
+
+ CBlockFileInfo() {
+ SetNull();
+ }
+
+ std::string ToString() const {
+ return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u..%u, time=%s..%s)", nBlocks, nSize, nHeightFirst, nHeightLast, DateTimeStrFormat("%Y-%m-%d", nTimeFirst).c_str(), DateTimeStrFormat("%Y-%m-%d", nTimeLast).c_str());
+ }
+
+ // update statistics (does not update nSize)
+ void AddBlock(unsigned int nHeightIn, uint64 nTimeIn) {
+ if (nBlocks==0 || nHeightFirst > nHeightIn)
+ nHeightFirst = nHeightIn;
+ if (nBlocks==0 || nTimeFirst > nTimeIn)
+ nTimeFirst = nTimeIn;
+ nBlocks++;
+ if (nHeightIn > nHeightFirst)
+ nHeightLast = nHeightIn;
+ if (nTimeIn > nTimeLast)
+ nTimeLast = nTimeIn;
+ }
+};
+
+extern CCriticalSection cs_LastBlockFile;
+extern CBlockFileInfo infoLastBlockFile;
+extern int nLastBlockFile;
+enum BlockStatus {
+ BLOCK_VALID_UNKNOWN = 0,
+ BLOCK_VALID_HEADER = 1, // parsed, version ok, hash satisfies claimed PoW, 1 <= vtx count <= max, timestamp not in future
+ BLOCK_VALID_TREE = 2, // parent found, difficulty matches, timestamp >= median previous, checkpoint
+ BLOCK_VALID_TRANSACTIONS = 3, // only first tx is coinbase, 2 <= coinbase input script length <= 100, transactions valid, no duplicate txids, sigops, size, merkle root
+ BLOCK_VALID_CHAIN = 4, // outputs do not overspend inputs, no double spends, coinbase output ok, immature coinbase spends, BIP30
+ BLOCK_VALID_SCRIPTS = 5, // scripts/signatures ok
+ BLOCK_VALID_MASK = 7,
+ BLOCK_HAVE_DATA = 8, // full block available in blk*.dat
+ BLOCK_HAVE_UNDO = 16, // undo data available in rev*.dat
+ BLOCK_HAVE_MASK = 24,
+
+ BLOCK_FAILED_VALID = 32, // stage after last reached validness failed
+ BLOCK_FAILED_CHILD = 64, // descends from failed block
+ BLOCK_FAILED_MASK = 96
+};
/** The block chain is a tree shaped structure starting with the
* genesis block at the root, with each block potentially having multiple
class CBlockIndex
{
public:
+ // pointer to the hash of the block, if any. memory is owned by this CBlockIndex
const uint256* phashBlock;
+
+ // pointer to the index of the predecessor of this block
CBlockIndex* pprev;
+
+ // (memory only) pointer to the index of the *active* successor of this block
CBlockIndex* pnext;
- unsigned int nFile;
- unsigned int nBlockPos;
- uint256 nChainTrust; // ppcoin: trust score of block chain
+
+ // height of the entry in the chain. The genesis block has height 0
int nHeight;
+ // Which # file this block is stored in (blk?????.dat)
+ int nFile;
+
+ // Byte offset within blk?????.dat where this block's data is stored
+ unsigned int nDataPos;
+
+ // Byte offset within rev?????.dat where this block's undo data is stored
+ unsigned int nUndoPos;
+
+ // (memory only) Trust score of block chain up to and including this block
+ uint256 nChainTrust;
+
+ // Number of transactions in this block.
+ unsigned int nTx;
+
+ // (memory only) Number of transactions in the chain up to and including this block
+ unsigned int nChainTx;
+
+ // Verification status of this block. See enum BlockStatus for detailed info
+ unsigned int nStatus;
+
+ // Coins amount created by this block
int64 nMint;
+
+ // Total coins created in this block chain up to and including this block
int64 nMoneySupply;
- unsigned int nFlags; // ppcoin: block index flags
- enum
+ // Block flags
+ unsigned int nFlags;
+ enum
{
- BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block
- BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier
- BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier
+ // is proof-of-stake block
+ BLOCK_PROOF_OF_STAKE = (1 << 0),
+ // entropy bit for stake modifier
+ BLOCK_STAKE_ENTROPY = (1 << 1),
+ // regenerated stake modifier
+ BLOCK_STAKE_MODIFIER = (1 << 2),
};
- uint64 nStakeModifier; // hash modifier for proof-of-stake
- unsigned int nStakeModifierChecksum; // checksum of index; in-memeory only
+ // Hash modifier for proof-of-stake kernel
+ uint64 nStakeModifier;
+
+ // Checksum of index in-memory only
+ unsigned int nStakeModifierChecksum;
- // proof-of-stake specific fields
+ // Predecessor of coinstake transaction
COutPoint prevoutStake;
+
+ // Timestamp of coinstake transaction
unsigned int nStakeTime;
+
+ // Kernel hash
uint256 hashProofOfStake;
- // block header
+ // Block header
int nVersion;
uint256 hashMerkleRoot;
unsigned int nTime;
phashBlock = NULL;
pprev = NULL;
pnext = NULL;
- nFile = 0;
- nBlockPos = 0;
nHeight = 0;
+ nFile = 0;
+ nDataPos = 0;
+ nUndoPos = 0;
nChainTrust = 0;
+ nTx = 0;
+ nChainTx = 0;
+ nStatus = 0;
nMint = 0;
nMoneySupply = 0;
nFlags = 0;
nNonce = 0;
}
- CBlockIndex(unsigned int nFileIn, unsigned int nBlockPosIn, CBlock& block)
+ CBlockIndex(CBlock& block)
{
phashBlock = NULL;
pprev = NULL;
pnext = NULL;
- nFile = nFileIn;
- nBlockPos = nBlockPosIn;
nHeight = 0;
+ nFile = 0;
+ nDataPos = 0;
+ nUndoPos = 0;
nChainTrust = 0;
+ nTx = 0;
+ nChainTx = 0;
+ nStatus = 0;
nMint = 0;
nMoneySupply = 0;
nFlags = 0;
nNonce = block.nNonce;
}
+ CDiskBlockPos GetBlockPos() const {
+ CDiskBlockPos ret;
+ if (nStatus & BLOCK_HAVE_DATA) {
+ ret.nFile = nFile;
+ ret.nPos = nDataPos;
+ } else
+ ret.SetNull();
+ return ret;
+ }
+
+ CDiskBlockPos GetUndoPos() const {
+ CDiskBlockPos ret;
+ if (nStatus & BLOCK_HAVE_UNDO) {
+ ret.nFile = nFile;
+ ret.nPos = nUndoPos;
+ } else
+ ret.SetNull();
+ return ret;
+ }
+
CBlock GetBlockHeader() const
{
CBlock block;
static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart,
unsigned int nRequired, unsigned int nToCheck);
-
bool IsProofOfWork() const
{
return !(nFlags & BLOCK_PROOF_OF_STAKE);
std::string ToString() const
{
- return strprintf("CBlockIndex(nprev=%p, pnext=%p, nFile=%u, nBlockPos=%-6d nHeight=%d, nMint=%s, nMoneySupply=%s, nFlags=(%s)(%d)(%s), nStakeModifier=%016"PRI64x", nStakeModifierChecksum=%08x, hashProofOfStake=%s, prevoutStake=(%s), nStakeTime=%d merkle=%s, hashBlock=%s)",
- pprev, pnext, nFile, nBlockPos, nHeight,
+ return strprintf("CBlockIndex(nprev=%p, pnext=%p nHeight=%d, nMint=%s, nMoneySupply=%s, nFlags=(%s)(%d)(%s), nStakeModifier=%016"PRI64x", nStakeModifierChecksum=%08x, hashProofOfStake=%s, prevoutStake=(%s), nStakeTime=%d merkle=%s, hashBlock=%s)",
+ pprev, pnext, nHeight,
FormatMoney(nMint).c_str(), FormatMoney(nMoneySupply).c_str(),
GeneratedStakeModifier() ? "MOD" : "-", GetStakeEntropyBit(), IsProofOfStake()? "PoS" : "PoW",
nStakeModifier, nStakeModifierChecksum,
GetBlockHash().ToString().c_str());
}
+
void print() const
{
printf("%s\n", ToString().c_str());
}
};
+struct CBlockIndexTrustComparator
+{
+ bool operator()(CBlockIndex *pa, CBlockIndex *pb) {
+ if (pa->nChainTrust > pb->nChainTrust) return false;
+ if (pa->nChainTrust < pb->nChainTrust) return true;
+ return false; // identical blocks
+ }
+};
/** Used to marshal pointers into hashes for db storage. */
class CDiskBlockIndex : public CBlockIndex
{
private:
uint256 blockHash;
-
public:
uint256 hashPrev;
- uint256 hashNext;
- CDiskBlockIndex()
- {
+ CDiskBlockIndex() {
hashPrev = 0;
- hashNext = 0;
blockHash = 0;
}
- explicit CDiskBlockIndex(CBlockIndex* pindex) : CBlockIndex(*pindex)
- {
+ 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);
+ READWRITE(VARINT(nVersion));
+
+ READWRITE(VARINT(nHeight));
+ READWRITE(VARINT(nStatus));
+ READWRITE(VARINT(nTx));
+ if (nStatus & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO))
+ READWRITE(VARINT(nFile));
+ if (nStatus & BLOCK_HAVE_DATA)
+ READWRITE(VARINT(nDataPos));
+ if (nStatus & BLOCK_HAVE_UNDO)
+ READWRITE(VARINT(nUndoPos));
READWRITE(nMint);
READWRITE(nMoneySupply);
READWRITE(nFlags);
const_cast<CDiskBlockIndex*>(this)->nStakeTime = 0;
const_cast<CDiskBlockIndex*>(this)->hashProofOfStake = 0;
}
+ READWRITE(blockHash);
// block header
READWRITE(this->nVersion);
READWRITE(nTime);
READWRITE(nBits);
READWRITE(nNonce);
- READWRITE(blockHash);
)
uint256 GetBlockHash() const
return blockHash;
}
+
std::string ToString() const
{
std::string str = "CDiskBlockIndex(";
str += CBlockIndex::ToString();
- str += strprintf("\n hashBlock=%s, hashPrev=%s, hashNext=%s)",
+ str += strprintf("\n hashBlock=%s, hashPrev=%s)",
GetBlockHash().ToString().c_str(),
- hashPrev.ToString().c_str(),
- hashNext.ToString().c_str());
+ hashPrev.ToString().substr(0,20).c_str());
return 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 CTxMemPool
{
public:
std::map<uint256, CTransaction> mapTx;
std::map<COutPoint, CInPoint> mapNextTx;
- bool accept(CTxDB& txdb, CTransaction &tx,
- bool fCheckInputs, bool* pfMissingInputs);
+ bool accept(CTransaction &tx, bool fCheckInputs, bool* pfMissingInputs);
bool addUnchecked(const uint256& hash, CTransaction &tx);
bool remove(CTransaction &tx);
void clear();
void queryHashes(std::vector<uint256>& vtxid);
+ void pruneSpent(const uint256& hash, CCoins &coins);
unsigned long size()
{
extern CTxMemPool mempool;
+struct CCoinsStats
+{
+ int nHeight;
+ uint64 nTransactions;
+ uint64 nTransactionOutputs;
+ uint64 nSerializedSize;
+
+ CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nSerializedSize(0) {}
+};
+
+/** Abstract view on the open txout dataset. */
+class CCoinsView
+{
+public:
+ // Retrieve the CCoins (unspent transaction outputs) for a given txid
+ virtual bool GetCoins(uint256 txid, CCoins &coins);
+
+ // Modify the CCoins for a given txid
+ virtual bool SetCoins(uint256 txid, const CCoins &coins);
+
+ // Just check whether we have data for a given txid.
+ // This may (but cannot always) return true for fully spent transactions
+ virtual bool HaveCoins(uint256 txid);
+
+ // Retrieve the block index whose state this CCoinsView currently represents
+ virtual CBlockIndex *GetBestBlock();
+
+ // Modify the currently active block index
+ virtual bool SetBestBlock(CBlockIndex *pindex);
+ virtual bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex);
+ virtual bool GetStats(CCoinsStats &stats);
+};
+
+/** CCoinsView backed by another CCoinsView */
+class CCoinsViewBacked : public CCoinsView
+{
+protected:
+ CCoinsView *base;
+
+public:
+ CCoinsViewBacked(CCoinsView &viewIn);
+ bool GetCoins(uint256 txid, CCoins &coins);
+ bool SetCoins(uint256 txid, const CCoins &coins);
+ bool HaveCoins(uint256 txid);
+ CBlockIndex *GetBestBlock();
+ bool SetBestBlock(CBlockIndex *pindex);
+ void SetBackend(CCoinsView &viewIn);
+ bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex);
+ bool GetStats(CCoinsStats &stats);
+};
+
+/** CCoinsView that adds a memory cache for transactions to another CCoinsView */
+class CCoinsViewCache : public CCoinsViewBacked
+{
+protected:
+ CBlockIndex *pindexTip;
+ std::map<uint256,CCoins> cacheCoins;
+ std::map<uint256,CCoins> cacheCoinsReadOnly;
+
+public:
+ CCoinsViewCache(CCoinsView &baseIn, bool fDummy = false);
+ bool GetCoins(uint256 txid, CCoins &coins);
+ bool GetCoinsReadOnly(uint256 txid, CCoins &coins);
+ bool SetCoins(uint256 txid, const CCoins &coins);
+ bool HaveCoins(uint256 txid);
+ CCoins &GetCoins(uint256 txid);
+ CBlockIndex *GetBestBlock();
+ bool SetBestBlock(CBlockIndex *pindex);
+ bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex);
+ bool Flush();
+ unsigned int GetCacheSize();
+private:
+ std::map<uint256,CCoins>::iterator FetchCoins(uint256 txid);
+};
+
+/** CCoinsView that brings transactions from a memorypool into view.
+ It does not check for spendings by memory pool transactions. */
+class CCoinsViewMemPool : public CCoinsViewBacked
+{
+protected:
+ CTxMemPool &mempool;
+
+public:
+ CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn);
+ bool GetCoins(uint256 txid, CCoins &coins);
+ bool HaveCoins(uint256 txid);
+};
+
+/** Global variable that points to the active CCoinsView (protected by cs_main) */
+extern CCoinsViewCache *pcoinsTip;
+
+/** Global variable that points to the active block tree (protected by cs_main) */
+extern CBlockTreeDB *pblocktree;
+
#endif
obj/crypter.o \
obj/key.o \
obj/db.o \
+ obj/leveldb.o \
+ obj/txdb.o \
obj/init.o \
obj/irc.o \
obj/keystore.o \
LIBS += $(CURDIR)/leveldb/libleveldb.a $(CURDIR)/leveldb/libmemenv.a
DEFS += $(addprefix -I,$(CURDIR)/leveldb/include)
DEFS += $(addprefix -I,$(CURDIR)/leveldb/helpers)
-OBJS += obj/txdb-leveldb.o
leveldb/libleveldb.a:
@echo "Building LevelDB ..."; cd leveldb; make libleveldb.a libmemenv.a; cd ..;
obj/txdb-leveldb.o: leveldb/libleveldb.a
obj/crypter.o \
obj/key.o \
obj/db.o \
+ obj/leveldb.o \
+ obj/txdb.o \
obj/init.o \
obj/irc.o \
obj/keystore.o \
LIBS += $(CURDIR)/leveldb/libleveldb.a $(CURDIR)/leveldb/libmemenv.a
DEFS += -I"$(CURDIR)/leveldb/include"
DEFS += -I"$(CURDIR)/leveldb/helpers"
-OBJS += obj/txdb-leveldb.o
leveldb/libleveldb.a:
@echo "Building LevelDB ..." && cd leveldb && CC=$(CC) CXX=$(CXX) TARGET_OS=OS_WINDOWS_CROSSCOMPILE CXXFLAGS="-I$(INCLUDEPATHS)" LDFLAGS="-L$(LIBPATHS)" $(MAKE) libleveldb.a libmemenv.a && $(RANLIB) libleveldb.a && $(RANLIB) libmemenv.a && cd ..
obj/txdb-leveldb.o: leveldb/libleveldb.a
obj/crypter.o \
obj/key.o \
obj/db.o \
+ obj/leveldb.o \
+ obj/txdb.o \
obj/init.o \
obj/irc.o \
obj/keystore.o \
LIBS += $(CURDIR)/leveldb/libleveldb.a $(CURDIR)/leveldb/libmemenv.a
DEFS += $(addprefix -I,$(CURDIR)/leveldb/include)
DEFS += $(addprefix -I,$(CURDIR)/leveldb/helpers)
-OBJS += obj/txdb-leveldb.o
leveldb/libleveldb.a:
cd leveldb; make; cd ..
obj/txdb-leveldb.o: leveldb/libleveldb.lib
obj/crypter.o \
obj/key.o \
obj/db.o \
+ obj/leveldb.o \
+ obj/txdb.o \
obj/init.o \
obj/irc.o \
obj/keystore.o \
LIBS += $(CURDIR)/leveldb/libleveldb.a $(CURDIR)/leveldb/libmemenv.a
DEFS += $(addprefix -I,$(CURDIR)/leveldb/include)
DEFS += $(addprefix -I,$(CURDIR)/leveldb/helpers)
-OBJS += obj/txdb-leveldb.o
leveldb/libleveldb.a:
@echo "Building LevelDB ..."; cd leveldb; make; cd ..
obj/txdb-leveldb.o: leveldb/libleveldb.a
obj/crypter.o \
obj/key.o \
obj/db.o \
+ obj/leveldb.o \
+ obj/txdb.o \
obj/init.o \
obj/irc.o \
obj/keystore.o \
LIBS += $(CURDIR)/leveldb/libleveldb.a $(CURDIR)/leveldb/libmemenv.a
DEFS += $(addprefix -I,$(CURDIR)/leveldb/include)
DEFS += $(addprefix -I,$(CURDIR)/leveldb/helpers)
-OBJS += obj/txdb-leveldb.o
leveldb/libleveldb.a:
@echo "Building LevelDB ..."; cd leveldb; make libleveldb.a libmemenv.a; cd ..;
-obj/txdb-leveldb.o: leveldb/libleveldb.a
+obj/leveldb.o: leveldb/libleveldb.a
# auto-generated dependencies:
-include obj/*.P
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "txdb.h"
+#include "db.h"
#include "miner.h"
#include "kernel.h"
int64 nFees = 0;
{
LOCK2(cs_main, mempool.cs);
- CBlockIndex* pindexPrev = pindexBest;
- CTxDB txdb("r");
+ CCoinsViewCache view(*pcoinsTip, true);
// Priority order to process transactions
list<COrphan> vOrphan; // list memory doesn't move
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
// Read prev transaction
- CTransaction txPrev;
- CTxIndex txindex;
- if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
+ CCoins coins;
+ if (!view.GetCoins(txin.prevout.hash, coins))
{
// This should never happen; all transactions in the memory
// pool should connect to either transactions in the chain
nTotalIn += mempool.mapTx[txin.prevout.hash].vout[txin.prevout.n].nValue;
continue;
}
- int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
+ int64 nValueIn = coins.vout[txin.prevout.n].nValue;
nTotalIn += nValueIn;
- int nConf = txindex.GetDepthInMainChain();
+ int nConf = pindexPrev->nHeight - coins.nHeight;
dPriority += (double)nValueIn * nConf;
}
if (fMissingInputs) continue;
}
// Collect transactions into block
- map<uint256, CTxIndex> mapTestPool;
uint64 nBlockSize = 1000;
uint64 nBlockTx = 0;
int nBlockSigOps = 100;
std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer);
vecPriority.pop_back();
+ // second layer cached modifications just for this transaction
+ CCoinsViewCache viewTemp(view, true);
+
// Size limits
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
if (nBlockSize + nTxSize >= nBlockMaxSize)
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
}
- // 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);
- MapPrevTx mapInputs;
- bool fInvalid;
- if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid))
+ if (!tx.CheckInputs(viewTemp, CS_ALWAYS, true, false))
continue;
- int64 nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
+ int64 nTxFees = tx.GetValueIn(viewTemp)-tx.GetValueOut();
if (nTxFees < nMinFee)
continue;
- nTxSigOps += tx.GetP2SHSigOpCount(mapInputs);
+ nTxSigOps += tx.GetP2SHSigOpCount(viewTemp);
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
continue;
- if (!tx.ConnectInputs(txdb, mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true))
+/*
+ * We need to call UpdateCoins using actual block timestamp, so don't perform this here.
+ *
+ CTxUndo txundo;
+ if (!tx.UpdateCoins(viewTemp, txundo, pindexPrev->nHeight+1, pblock->nTime))
continue;
- mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(1,1,1), tx.vout.size());
- swap(mapTestPool, mapTestPoolTmp);
+
+*/
+
+ // push changes from the second layer cache to the first one
+ viewTemp.Flush();
+ uint256 hash = tx.GetHash();
// Added
pblock->vtx.push_back(tx);
}
// Add transactions that depend on this one to the priority queue
- uint256 hash = tx.GetHash();
if (mapDependers.count(hash))
{
BOOST_FOREACH(COrphan* porphan, mapDependers[hash])
}
++nExtraNonce;
- unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required for block.version=2
+ unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required
pblock->vtx[0].vin[0].scriptSig = (CScript() << nHeight << CBigNum(nExtraNonce)) + COINBASE_FLAGS;
assert(pblock->vtx[0].vin[0].scriptSig.size() <= 100);
{
uint256 proofHash = 0, hashTarget = 0;
uint256 hashBlock = pblock->GetHash();
+ bool fFatal = false;
if(!pblock->IsProofOfStake())
return error("CheckStake() : %s is not a proof-of-stake block", hashBlock.GetHex().c_str());
// verify hash target and signature of coinstake tx
- if (!CheckProofOfStake(pblock->vtx[1], pblock->nBits, proofHash, hashTarget))
+ if (!CheckProofOfStake(pblock->vtx[1], pblock->nBits, proofHash, hashTarget, fFatal, true))
return error("CheckStake() : proof-of-stake checking failed");
//// debug print
}
else
Sleep(nMinerSleep);
-
- continue;
}
}
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "netbase.h"
#include "util.h"
+#include "netbase.h"
#include "sync.h"
#ifndef WIN32
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="text">
- <string>0.00 BTC</string>
+ <string>0.00 NVC</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="text">
- <string>0.00 BTC</string>
+ <string>0.00 NVC</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="text">
- <string>0.00 BTC</string>
+ <string>0.00 NVC</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="text">
- <string>0.00 BTC</string>
+ <string>0.00 NVC</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
- <string>123.456 BTC</string>
+ <string>123.456 NVC</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
<message>
<location line="+6"/>
<source>Up to date</source>
- <translation>Синхронизированно</translation>
+ <translation>Синхронизировано</translation>
</message>
<message>
<location line="+7"/>
filter->setSourceModel(model->getTransactionTableModel());
filter->setLimit(NUM_ITEMS);
filter->setDynamicSortFilter(true);
- filter->setSortRole(Qt::EditRole);
+// filter->setSortRole(Qt::EditRole);
+ filter->setSortRole(TransactionTableModel::DateRole);
filter->sort(TransactionTableModel::Status, Qt::DescendingOrder);
ui->listTransactions->setModel(filter);
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <boost/version.hpp>
-#if defined(WIN32) && BOOST_VERSION == 104900
+#if defined(WIN32)
#define BOOST_INTERPROCESS_HAS_WINDOWS_KERNEL_BOOTTIME
#define BOOST_INTERPROCESS_HAS_KERNEL_BOOTTIME
#endif
#include "main.h"
#include "wallet.h"
-#include "txdb.h"
+#include "db.h"
#include "ui_interface.h"
#include "base58.h"
strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true);
- CTxDB txdb("r"); // To fetch source txouts
-
strHTML += "<br><b>" + tr("Inputs") + ":</b>";
strHTML += "<ul>";
{
COutPoint prevout = txin.prevout;
- CTransaction prev;
- if(txdb.ReadDiskTx(prevout.hash, prev))
+ CCoins prev;
+ if(pcoinsTip->GetCoins(prevout.hash, prev))
{
if (prevout.n < prev.vout.size())
{
transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
- transactionProxyModel->setSortRole(Qt::EditRole);
+// transactionProxyModel->setSortRole(Qt::EditRole);
+ transactionProxyModel->setSortRole(TransactionTableModel::DateRole);
transactionView->setModel(transactionProxyModel);
transactionView->setAlternatingRowColors(true);
result.push_back(Pair("tx", txinfo));
- if ( block.IsProofOfStake() || (!fTestNet && block.GetBlockTime() < ENTROPY_SWITCH_TIME) )
- result.push_back(Pair("signature", HexStr(block.vchBlockSig.begin(), block.vchBlockSig.end())));
+ if ( block.IsProofOfStake() )
+ {
+ CKey key;
+ block.GetGenerator(key);
+ result.push_back(Pair("generator", HexStr(key.GetPubKey().Raw())));
+ result.push_back(Pair("signature", HexStr(block.vchBlockSig)));
+ }
return result;
}
return result;
}
+
+Value gettxoutsetinfo(const Array& params, bool fHelp)
+{
+ if (fHelp || params.size() != 0)
+ throw runtime_error(
+ "gettxoutsetinfo\n"
+ "Returns statistics about the unspent transaction output set.");
+
+ Object ret;
+
+ CCoinsStats stats;
+ if (pcoinsTip->GetStats(stats)) {
+ ret.push_back(Pair("bestblock", pcoinsTip->GetBestBlock()->GetBlockHash().GetHex()));
+ ret.push_back(Pair("transactions", (boost::int64_t)stats.nTransactions));
+ ret.push_back(Pair("txouts", (boost::int64_t)stats.nTransactionOutputs));
+ ret.push_back(Pair("bytes_serialized", (boost::int64_t)stats.nSerializedSize));
+ }
+ return ret;
+}
+
+Value gettxout(const Array& params, bool fHelp)
+{
+ if (fHelp || params.size() < 2 || params.size() > 3)
+ throw runtime_error(
+ "gettxout <txid> <n> [includemempool=true]\n"
+ "Returns details about an unspent transaction output.");
+
+ Object ret;
+
+ std::string strHash = params[0].get_str();
+ uint256 hash(strHash);
+ int n = params[1].get_int();
+ bool fMempool = true;
+ if (params.size() > 2)
+ fMempool = params[2].get_bool();
+
+ CCoins coins;
+ if (fMempool) {
+ LOCK(mempool.cs);
+ CCoinsViewMemPool view(*pcoinsTip, mempool);
+ if (!view.GetCoins(hash, coins))
+ return Value::null;
+ mempool.pruneSpent(hash, coins); // TODO: this should be done by the CCoinsViewMemPool
+ } else {
+ if (!pcoinsTip->GetCoins(hash, coins))
+ return Value::null;
+ }
+ if (n<0 || (unsigned int)n>=coins.vout.size() || coins.vout[n].IsNull())
+ return Value::null;
+
+ ret.push_back(Pair("bestblock", pcoinsTip->GetBestBlock()->GetBlockHash().GetHex()));
+ if ((unsigned int)coins.nHeight == MEMPOOL_HEIGHT)
+ ret.push_back(Pair("confirmations", 0));
+ else
+ ret.push_back(Pair("confirmations", pcoinsTip->GetBestBlock()->nHeight - coins.nHeight + 1));
+ ret.push_back(Pair("amount", (boost::int64_t)coins.vout[n].nValue));
+ Object o;
+ o.push_back(Pair("asm", coins.vout[n].scriptPubKey.ToString()));
+ o.push_back(Pair("hex", HexStr(coins.vout[n].scriptPubKey.begin(), coins.vout[n].scriptPubKey.end())));
+ ret.push_back(Pair("scriptPubKey", o));
+ ret.push_back(Pair("version", coins.nVersion));
+ ret.push_back(Pair("coinbase", coins.fCoinBase));
+ ret.push_back(Pair("coinstake", coins.fCoinStake));
+ ret.push_back(Pair("time", (boost::int64_t)coins.nTime));
+ ret.push_back(Pair("blocktime", (boost::int64_t)coins.nBlockTime));
+
+ return ret;
+}
#include "main.h"
#include "db.h"
-#include "txdb.h"
#include "init.h"
#include "miner.h"
#include "bitcoinrpc.h"
}
}
-
Value getblocktemplate(const Array& params, bool fHelp)
{
- if (fHelp || params.size() > 1)
+ if (fHelp || params.size() != 1)
throw runtime_error(
"getblocktemplate [params]\n"
"Returns data needed to construct a block to work on:\n"
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
if (vNodes.empty())
- throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "NovaCoin is not connected!");
+ throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Bitcoin is not connected!");
if (IsInitialBlockDownload())
- throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "NovaCoin is downloading blocks...");
+ throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Bitcoin is downloading blocks...");
static CReserveKey reservekey(pwalletMain);
Array transactions;
map<uint256, int64_t> setTxIndex;
int i = 0;
- CTxDB txdb("r");
+ CCoinsViewCache &view = *pcoinsTip;
BOOST_FOREACH (CTransaction& tx, pblock->vtx)
{
uint256 txHash = tx.GetHash();
entry.push_back(Pair("hash", txHash.GetHex()));
- MapPrevTx mapInputs;
- map<uint256, CTxIndex> mapUnused;
- bool fInvalid = false;
- if (tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid))
+ Array deps;
+ BOOST_FOREACH (const CTxIn &in, tx.vin)
{
- entry.push_back(Pair("fee", (int64_t)(tx.GetValueIn(mapInputs) - tx.GetValueOut())));
-
- Array deps;
- BOOST_FOREACH (MapPrevTx::value_type& inp, mapInputs)
- {
- if (setTxIndex.count(inp.first))
- deps.push_back(setTxIndex[inp.first]);
- }
- entry.push_back(Pair("depends", deps));
+ if (setTxIndex.count(in.prevout.hash))
+ deps.push_back(setTxIndex[in.prevout.hash]);
+ }
+ entry.push_back(Pair("depends", deps));
- int64_t nSigOps = tx.GetLegacySigOpCount();
- nSigOps += tx.GetP2SHSigOpCount(mapInputs);
- entry.push_back(Pair("sigops", nSigOps));
+ int64_t nSigOps = tx.GetLegacySigOpCount();
+ if (tx.HaveInputs(view))
+ {
+ entry.push_back(Pair("fee", (int64_t)(tx.GetValueIn(view) - tx.GetValueOut())));
+ nSigOps += tx.GetP2SHSigOpCount(view);
}
+ entry.push_back(Pair("sigops", nSigOps));
transactions.push_back(entry);
}
vector<unsigned char> blockData(ParseHex(params[0].get_str()));
CDataStream ssBlock(blockData, SER_NETWORK, PROTOCOL_VERSION);
- CBlock block;
+ CBlock pblock;
try {
- ssBlock >> block;
+ ssBlock >> pblock;
}
catch (std::exception &e) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");
}
- bool fAccepted = ProcessBlock(NULL, &block);
+ bool fAccepted = ProcessBlock(NULL, &pblock);
if (!fAccepted)
return "rejected";
return Value::null;
}
-
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "net.h"
#include "bitcoinrpc.h"
+#include "net.h"
#include "alert.h"
#include "wallet.h"
#include "db.h"
#include "base58.h"
#include "bitcoinrpc.h"
-#include "txdb.h"
+#include "db.h"
#include "init.h"
#include "main.h"
#include "net.h"
CTransaction tx;
uint256 hashBlock = 0;
- if (!GetTransaction(hash, tx, hashBlock))
+ if (!GetTransaction(hash, tx, hashBlock, true))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction");
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
return results;
}
+Value decodescript(const Array& params, bool fHelp)
+{
+ if (fHelp || params.size() != 1)
+ throw runtime_error(
+ "decodescript <hex string>\n"
+ "Decode a hex-encoded script.");
+
+ RPCTypeCheck(params, list_of(str_type));
+
+ Object r;
+ CScript script;
+ if (params[0].get_str().size() > 0){
+ vector<unsigned char> scriptData(ParseHexV(params[0], "argument"));
+ script = CScript(scriptData.begin(), scriptData.end());
+ } else {
+ // Empty scripts are valid
+ }
+ ScriptPubKeyToJSON(script, r, false);
+
+ r.push_back(Pair("p2sh", CBitcoinAddress(script.GetID()).ToString()));
+ return r;
+}
+
Value createrawtransaction(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 2)
{
CBitcoinAddress address(s.name_);
if (!address.IsValid())
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid NovaCoin address: ")+s.name_);
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+s.name_);
if (setAddress.count(address))
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+s.name_);
return result;
}
-Value decodescript(const Array& params, bool fHelp)
-{
- if (fHelp || params.size() != 1)
- throw runtime_error(
- "decodescript <hex string>\n"
- "Decode a hex-encoded script.");
-
- RPCTypeCheck(params, list_of(str_type));
-
- Object r;
- CScript script;
- if (params[0].get_str().size() > 0){
- vector<unsigned char> scriptData(ParseHexV(params[0], "argument"));
- script = CScript(scriptData.begin(), scriptData.end());
- } else {
- // Empty scripts are valid
- }
- ScriptPubKeyToJSON(script, r, false);
-
- r.push_back(Pair("p2sh", CBitcoinAddress(script.GetID()).ToString()));
- return r;
-}
-
Value signrawtransaction(const Array& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 4)
bool fComplete = true;
// Fetch previous transactions (inputs):
- map<COutPoint, CScript> mapPrevOut;
- for (unsigned int i = 0; i < mergedTx.vin.size(); i++)
+ CCoinsView viewDummy;
+ CCoinsViewCache view(viewDummy);
{
- CTransaction tempTx;
- MapPrevTx mapPrevTx;
- CTxDB txdb("r");
- map<uint256, CTxIndex> unused;
- bool fInvalid;
-
- // FetchInputs aborts on failure, so we go one at a time.
- tempTx.vin.push_back(mergedTx.vin[i]);
- tempTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid);
-
- // Copy results into mapPrevOut:
- BOOST_FOREACH(const CTxIn& txin, tempTx.vin)
- {
+ LOCK(mempool.cs);
+ CCoinsViewCache &viewChain = *pcoinsTip;
+ CCoinsViewMemPool viewMempool(viewChain, mempool);
+ view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
+
+ BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) {
const uint256& prevHash = txin.prevout.hash;
- if (mapPrevTx.count(prevHash) && mapPrevTx[prevHash].second.vout.size()>txin.prevout.n)
- mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey;
+ CCoins coins;
+ view.GetCoins(prevHash, coins); // this is certainly allowed to fail
}
+
+ view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
}
// Add previous txouts given in the RPC call:
vector<unsigned char> pkData(ParseHex(pkHex));
CScript scriptPubKey(pkData.begin(), pkData.end());
- COutPoint outpoint(txid, nOut);
- if (mapPrevOut.count(outpoint))
- {
- // Complain if scriptPubKey doesn't match
- if (mapPrevOut[outpoint] != scriptPubKey)
- {
+ CCoins coins;
+ if (view.GetCoins(txid, coins)) {
+ if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) {
string err("Previous output scriptPubKey mismatch:\n");
- err = err + mapPrevOut[outpoint].ToString() + "\nvs:\n"+
+ err = err + coins.vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+
scriptPubKey.ToString();
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
}
+ // what todo if txid is known, but the actual output isn't?
}
- else
- mapPrevOut[outpoint] = scriptPubKey;
+ coins.vout[nOut].scriptPubKey = scriptPubKey;
+ coins.vout[nOut].nValue = 0; // we don't know the actual output value
+ view.SetCoins(txid, coins);
}
}
for (unsigned int i = 0; i < mergedTx.vin.size(); i++)
{
CTxIn& txin = mergedTx.vin[i];
- if (mapPrevOut.count(txin.prevout) == 0)
+ CCoins coins;
+ if (!view.GetCoins(txin.prevout.hash, coins) || !coins.IsAvailable(txin.prevout.n))
{
fComplete = false;
continue;
}
- const CScript& prevPubKey = mapPrevOut[txin.prevout];
+ const CScript& prevPubKey = coins.vout[txin.prevout.n].scriptPubKey;
txin.scriptSig.clear();
// Only sign SIGHASH_SINGLE if there's a corresponding output:
{
txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig);
}
- if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, true, 0))
+ if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, true, true, 0))
fComplete = false;
}
}
uint256 hashTx = tx.GetHash();
- // See if the transaction is already in a block
- // or in the memory pool:
- CTransaction existingTx;
- uint256 hashBlock = 0;
- if (GetTransaction(hashTx, existingTx, hashBlock))
+ bool fHave = false;
+ CCoinsViewCache &view = *pcoinsTip;
+ CCoins existingCoins;
{
- if (hashBlock != 0)
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("transaction already in block ")+hashBlock.GetHex());
+ fHave = view.GetCoins(hashTx, existingCoins);
+ if (!fHave) {
+ // push to local node
+ if (!tx.AcceptToMemoryPool())
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX rejected");
+ }
+ }
+ if (fHave) {
+ if (existingCoins.nHeight < 1000000000)
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "transaction already in block chain");
// Not in block, but already in the memory pool; will drop
// through to re-relay it.
- }
- else
- {
- // push to local node
- CTxDB txdb("r");
- if (!tx.AcceptToMemoryPool(txdb))
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX rejected");
-
- SyncWithWallets(tx, NULL, true);
+ } else {
+ SyncWithWallets(hashTx, tx, NULL, true);
}
RelayTransaction(tx, hashTx);
{
CTransaction tx;
uint256 hashBlock = 0;
- if (GetTransaction(hash, tx, hashBlock))
+ if (GetTransaction(hash, tx, hashBlock, true))
{
TxToJSON(tx, 0, entry);
if (hashBlock == 0)
return ret;
}
-// ppcoin: reserve balance from being staked for network protection
+// reserve balance from being staked for network protection
Value reservebalance(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 2)
}
-// ppcoin: check wallet integrity
+// check wallet integrity
Value checkwallet(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 0)
}
-// ppcoin: repair wallet
+// repair wallet
Value repairwallet(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 0)
return result;
}
-// NovaCoin: resend unconfirmed wallet transactions
+// resend unconfirmed wallet transactions
Value resendtx(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 1)
return Value::null;
}
-// ppcoin: make a public-private key pair
+// make a public-private key pair
Value makekeypair(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 1)
string strPrefix = "";
if (params.size() > 0)
strPrefix = params[0].get_str();
-
+
CKey key;
key.MakeNewKey(false);
bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType);
+
+
+typedef vector<unsigned char> valtype;
static const valtype vchFalse(0);
static const valtype vchZero(0);
static const valtype vchTrue(1, 1);
}
}
-bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType)
+bool IsCanonicalPubKey(const valtype &vchPubKey) {
+ if (vchPubKey.size() < 33)
+ return error("Non-canonical public key: too short");
+ if (vchPubKey[0] == 0x04) {
+ if (vchPubKey.size() != 65)
+ return error("Non-canonical public key: invalid length for uncompressed key");
+ } else if (vchPubKey[0] == 0x02 || vchPubKey[0] == 0x03) {
+ if (vchPubKey.size() != 33)
+ return error("Non-canonical public key: invalid length for compressed key");
+ } else {
+ return error("Non-canonical public key: compressed nor uncompressed");
+ }
+ return true;
+}
+
+bool IsCanonicalSignature(const valtype &vchSig) {
+ // See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
+ // A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
+ // Where R and S are not negative (their first byte has its highest bit not set), and not
+ // excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
+ // in which case a single 0 byte is necessary and even required).
+ if (vchSig.size() < 9)
+ return error("Non-canonical signature: too short");
+ if (vchSig.size() > 73)
+ return error("Non-canonical signature: too long");
+ if (vchSig[vchSig.size() - 1] & 0x7C)
+ return error("Non-canonical signature: unknown hashtype byte");
+ if (vchSig[0] != 0x30)
+ return error("Non-canonical signature: wrong type");
+ if (vchSig[1] != vchSig.size()-3)
+ return error("Non-canonical signature: wrong length marker");
+ unsigned int nLenR = vchSig[3];
+ if (5 + nLenR >= vchSig.size())
+ return error("Non-canonical signature: S length misplaced");
+ unsigned int nLenS = vchSig[5+nLenR];
+ if ((unsigned long)(nLenR+nLenS+7) != vchSig.size())
+ return error("Non-canonical signature: R+S length mismatch");
+
+ const unsigned char *R = &vchSig[4];
+ if (R[-2] != 0x02)
+ return error("Non-canonical signature: R value type mismatch");
+ if (nLenR == 0)
+ return error("Non-canonical signature: R length is zero");
+ if (R[0] & 0x80)
+ return error("Non-canonical signature: R value negative");
+ if (nLenR > 1 && (R[0] == 0x00) && !(R[1] & 0x80))
+ return error("Non-canonical signature: R value excessively padded");
+
+ const unsigned char *S = &vchSig[6+nLenR];
+ if (S[-2] != 0x02)
+ return error("Non-canonical signature: S value type mismatch");
+ if (nLenS == 0)
+ return error("Non-canonical signature: S length is zero");
+ if (S[0] & 0x80)
+ return error("Non-canonical signature: S value negative");
+ if (nLenS > 1 && (S[0] == 0x00) && !(S[1] & 0x80))
+ return error("Non-canonical signature: S value excessively padded");
+
+ return true;
+}
+
+bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, bool fStrictEncodings, int nHashType)
{
CAutoBN_CTX pctx;
CScript::const_iterator pc = script.begin();
// Drop the signature, since there's no way for a signature to sign itself
scriptCode.FindAndDelete(CScript(vchSig));
- bool fSuccess = CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType);
+ bool fSuccess = (!fStrictEncodings || (IsCanonicalSignature(vchSig) && IsCanonicalPubKey(vchPubKey)));
+ if (fSuccess)
+ fSuccess = CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType);
popstack(stack);
popstack(stack);
valtype& vchPubKey = stacktop(-ikey);
// Check signature
- if (CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType))
- {
+ bool fOk = (!fStrictEncodings || (IsCanonicalSignature(vchSig) && IsCanonicalPubKey(vchPubKey)));
+ if (fOk)
+ fOk = CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType);
+
+ if (fOk) {
isig++;
nSigsCount--;
}
return false;
}
-bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
-{
- vector<valtype> vSolutions;
- txnouttype whichType;
- if (!Solver(scriptPubKey, whichType, vSolutions))
- return false;
-
- if (whichType == TX_PUBKEY)
- {
- addressRet = CPubKey(vSolutions[0]).GetID();
- return true;
- }
- else if (whichType == TX_PUBKEYHASH)
- {
- addressRet = CKeyID(uint160(vSolutions[0]));
- return true;
- }
- else if (whichType == TX_SCRIPTHASH)
- {
- addressRet = CScriptID(uint160(vSolutions[0]));
- return true;
- }
- // Multisig txns have more than one address...
- return false;
-}
-
class CAffectedKeysVisitor : public boost::static_visitor<void> {
private:
const CKeyStore &keystore;
CAffectedKeysVisitor(keystore, vKeys).Process(scriptPubKey);
}
+bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
+{
+ vector<valtype> vSolutions;
+ txnouttype whichType;
+ if (!Solver(scriptPubKey, whichType, vSolutions))
+ return false;
+
+ if (whichType == TX_PUBKEY)
+ {
+ addressRet = CPubKey(vSolutions[0]).GetID();
+ return true;
+ }
+ else if (whichType == TX_PUBKEYHASH)
+ {
+ addressRet = CKeyID(uint160(vSolutions[0]));
+ return true;
+ }
+ else if (whichType == TX_SCRIPTHASH)
+ {
+ addressRet = CScriptID(uint160(vSolutions[0]));
+ return true;
+ }
+ // Multisig txns have more than one address...
+ return false;
+}
+
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, vector<CTxDestination>& addressRet, int& nRequiredRet)
{
addressRet.clear();
}
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn,
- bool fValidatePayToScriptHash, int nHashType)
+ bool fValidatePayToScriptHash, bool fStrictEncodings, int nHashType)
{
vector<vector<unsigned char> > stack, stackCopy;
- if (!EvalScript(stack, scriptSig, txTo, nIn, nHashType))
+ if (!EvalScript(stack, scriptSig, txTo, nIn, fStrictEncodings, nHashType))
return false;
if (fValidatePayToScriptHash)
stackCopy = stack;
- if (!EvalScript(stack, scriptPubKey, txTo, nIn, nHashType))
+ if (!EvalScript(stack, scriptPubKey, txTo, nIn, fStrictEncodings, nHashType))
return false;
if (stack.empty())
return false;
CScript pubKey2(pubKeySerialized.begin(), pubKeySerialized.end());
popstack(stackCopy);
- if (!EvalScript(stackCopy, pubKey2, txTo, nIn, nHashType))
+ if (!EvalScript(stackCopy, pubKey2, txTo, nIn, fStrictEncodings, nHashType))
return false;
if (stackCopy.empty())
return false;
}
// Test solution
- return VerifyScript(txin.scriptSig, fromPubKey, txTo, nIn, true, 0);
+ return VerifyScript(txin.scriptSig, fromPubKey, txTo, nIn, true, true, 0);
}
bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType)
assert(nIn < txTo.vin.size());
CTxIn& txin = txTo.vin[nIn];
assert(txin.prevout.n < txFrom.vout.size());
- assert(txin.prevout.hash == txFrom.GetHash());
const CTxOut& txout = txFrom.vout[txin.prevout.n];
return SignSignature(keystore, txout.scriptPubKey, txTo, nIn, nHashType);
}
-bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, int nHashType)
+bool VerifySignature(const CCoins& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, bool fStrictEncodings, int nHashType)
{
assert(nIn < txTo.vin.size());
const CTxIn& txin = txTo.vin[nIn];
if (txin.prevout.n >= txFrom.vout.size())
return false;
- const CTxOut& txout = txFrom.vout[txin.prevout.n];
- if (txin.prevout.hash != txFrom.GetHash())
- return false;
+ const CTxOut& txout = txFrom.vout[txin.prevout.n];
- return VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, fValidatePayToScriptHash, nHashType);
+ return VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, fValidatePayToScriptHash, fStrictEncodings, nHashType);
}
static CScript PushAll(const vector<valtype>& values)
Solver(scriptPubKey, txType, vSolutions);
vector<valtype> stack1;
- EvalScript(stack1, scriptSig1, CTransaction(), 0, 0);
+ EvalScript(stack1, scriptSig1, CTransaction(), 0, true, 0);
vector<valtype> stack2;
- EvalScript(stack2, scriptSig2, CTransaction(), 0, 0);
+ EvalScript(stack2, scriptSig2, CTransaction(), 0, true, 0);
return CombineSignatures(scriptPubKey, txTo, nIn, txType, vSolutions, stack1, stack2);
}
*this << key.GetPubKey();
*this << EncodeOP_N(keys.size()) << OP_CHECKMULTISIG;
}
+
+bool CScriptCompressor::IsToKeyID(CKeyID &hash) const
+{
+ if (script.size() == 25 && script[0] == OP_DUP && script[1] == OP_HASH160
+ && script[2] == 20 && script[23] == OP_EQUALVERIFY
+ && script[24] == OP_CHECKSIG) {
+ memcpy(&hash, &script[3], 20);
+ return true;
+ }
+ return false;
+}
+
+bool CScriptCompressor::IsToScriptID(CScriptID &hash) const
+{
+ if (script.size() == 23 && script[0] == OP_HASH160 && script[1] == 20
+ && script[22] == OP_EQUAL) {
+ memcpy(&hash, &script[2], 20);
+ return true;
+ }
+ return false;
+}
+
+bool CScriptCompressor::IsToPubKey(std::vector<unsigned char> &pubkey) const
+{
+ if (script.size() == 35 && script[0] == 33 && script[34] == OP_CHECKSIG
+ && (script[1] == 0x02 || script[1] == 0x03)) {
+ pubkey.resize(33);
+ memcpy(&pubkey[0], &script[1], 33);
+ return true;
+ }
+ if (script.size() == 67 && script[0] == 65 && script[66] == OP_CHECKSIG
+ && script[1] == 0x04) {
+ pubkey.resize(65);
+ memcpy(&pubkey[0], &script[1], 65);
+ CKey key;
+ return (key.SetPubKey(CPubKey(pubkey))); // SetPubKey fails if this is not a valid public key, a case that would not be compressible
+ }
+ return false;
+}
+
+bool CScriptCompressor::Compress(std::vector<unsigned char> &out) const
+{
+ CKeyID keyID;
+ if (IsToKeyID(keyID)) {
+ out.resize(21);
+ out[0] = 0x00;
+ memcpy(&out[1], &keyID, 20);
+ return true;
+ }
+ CScriptID scriptID;
+ if (IsToScriptID(scriptID)) {
+ out.resize(21);
+ out[0] = 0x01;
+ memcpy(&out[1], &scriptID, 20);
+ return true;
+ }
+ std::vector<unsigned char> pubkey;
+ if (IsToPubKey(pubkey)) {
+ out.resize(33);
+ memcpy(&out[1], &pubkey[1], 32);
+ if (pubkey[0] == 0x02 || pubkey[0] == 0x03) {
+ out[0] = pubkey[0];
+ return true;
+ } else if (pubkey[0] == 0x04) {
+ out[0] = 0x04 | (pubkey[64] & 0x01);
+ return true;
+ }
+ }
+ return false;
+}
+
+unsigned int CScriptCompressor::GetSpecialSize(unsigned int nSize) const
+{
+ if (nSize == 0 || nSize == 1)
+ return 20;
+ if (nSize == 2 || nSize == 3 || nSize == 4 || nSize == 5)
+ return 32;
+ return 0;
+}
+
+bool CScriptCompressor::Decompress(unsigned int nSize, const std::vector<unsigned char> &in)
+{
+ switch(nSize) {
+ case 0x00:
+ script.resize(25);
+ script[0] = OP_DUP;
+ script[1] = OP_HASH160;
+ script[2] = 20;
+ memcpy(&script[3], &in[0], 20);
+ script[23] = OP_EQUALVERIFY;
+ script[24] = OP_CHECKSIG;
+ return true;
+ case 0x01:
+ script.resize(23);
+ script[0] = OP_HASH160;
+ script[1] = 20;
+ memcpy(&script[2], &in[0], 20);
+ script[22] = OP_EQUAL;
+ return true;
+ case 0x02:
+ case 0x03:
+ script.resize(35);
+ script[0] = 33;
+ script[1] = nSize;
+ memcpy(&script[2], &in[0], 32);
+ script[34] = OP_CHECKSIG;
+ return true;
+ case 0x04:
+ case 0x05:
+ std::vector<unsigned char> vch(33, 0x00);
+ vch[0] = nSize - 2;
+ memcpy(&vch[1], &in[0], 32);
+ CKey key;
+ if (!key.SetPubKey(CPubKey(vch)))
+ return false;
+ key.SetCompressedPubKey(false); // Decompress public key
+ CPubKey pubkey = key.GetPubKey();
+ script.resize(67);
+ script[0] = 65;
+ memcpy(&script[1], &pubkey.Raw()[0], 65);
+ script[66] = OP_CHECKSIG;
+ return true;
+ }
+ return false;
+}
#include "keystore.h"
#include "bignum.h"
-typedef std::vector<unsigned char> valtype;
-
+class CCoins;
class CTransaction;
+typedef std::vector<unsigned char> valtype;
+
/** Signature hash types/flags */
enum
{
printf("CScript(%s)\n", HexStr(begin(), end(), true).c_str());
}
- std::string ToString(bool fShort=false) const
+ std::string ToString() const
{
std::string str;
opcodetype opcode;
return str;
}
if (0 <= opcode && opcode <= OP_PUSHDATA4)
- str += fShort? ValueString(vch).substr(0, 10) : ValueString(vch);
+ str += ValueString(vch);
else
str += GetOpName(opcode);
}
}
};
+/** Compact serializer for scripts.
+ *
+ * It detects common cases and encodes them much more efficiently.
+ * 3 special cases are defined:
+ * * Pay to pubkey hash (encoded as 21 bytes)
+ * * Pay to script hash (encoded as 21 bytes)
+ * * Pay to pubkey starting with 0x02, 0x03 or 0x04 (encoded as 33 bytes)
+ *
+ * Other scripts up to 121 bytes require 1 byte + script length. Above
+ * that, scripts up to 16505 bytes require 2 bytes + script length.
+ */
+class CScriptCompressor
+{
+private:
+ // make this static for now (there are only 6 special scripts defined)
+ // this can potentially be extended together with a new nVersion for
+ // transactions, in which case this value becomes dependent on nVersion
+ // and nHeight of the enclosing transaction.
+ static const unsigned int nSpecialScripts = 6;
+
+ CScript &script;
+protected:
+ // These check for scripts for which a special case with a shorter encoding is defined.
+ // They are implemented separately from the CScript test, as these test for exact byte
+ // sequence correspondences, and are more strict. For example, IsToPubKey also verifies
+ // whether the public key is valid (as invalid ones cannot be represented in compressed
+ // form).
+ bool IsToKeyID(CKeyID &hash) const;
+ bool IsToScriptID(CScriptID &hash) const;
+ bool IsToPubKey(std::vector<unsigned char> &pubkey) const;
+
+ bool Compress(std::vector<unsigned char> &out) const;
+ unsigned int GetSpecialSize(unsigned int nSize) const;
+ bool Decompress(unsigned int nSize, const std::vector<unsigned char> &out);
+public:
+ CScriptCompressor(CScript &scriptIn) : script(scriptIn) { }
+
+ unsigned int GetSerializeSize(int nType, int nVersion) const {
+ std::vector<unsigned char> compr;
+ if (Compress(compr))
+ return compr.size();
+ unsigned int nSize = script.size() + nSpecialScripts;
+ return script.size() + VARINT(nSize).GetSerializeSize(nType, nVersion);
+ }
+ template<typename Stream>
+ void Serialize(Stream &s, int nType, int nVersion) const {
+ std::vector<unsigned char> compr;
+ if (Compress(compr)) {
+ s << CFlatData(&compr[0], &compr[compr.size()]);
+ return;
+ }
+ unsigned int nSize = script.size() + nSpecialScripts;
+ s << VARINT(nSize);
+ s << CFlatData(&script[0], &script[script.size()]);
+ }
+ template<typename Stream>
+ void Unserialize(Stream &s, int nType, int nVersion) {
+ unsigned int nSize;
+ s >> VARINT(nSize);
+ if (nSize < nSpecialScripts) {
+ std::vector<unsigned char> vch(GetSpecialSize(nSize), 0x00);
+ s >> REF(CFlatData(&vch[0], &vch[vch.size()]));
+ Decompress(nSize, vch);
+ return;
+ }
+ nSize -= nSpecialScripts;
+ script.resize(nSize);
+ s >> REF(CFlatData(&script[0], &script[script.size()]));
+ }
+};
+bool IsCanonicalPubKey(const std::vector<unsigned char> &vchPubKey);
+bool IsCanonicalSignature(const std::vector<unsigned char> &vchSig);
-bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType);
+bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, bool fStrictEncodings, int nHashType);
bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet);
int ScriptSigArgsExpected(txnouttype t, const std::vector<std::vector<unsigned char> >& vSolutions);
bool IsStandard(const CScript& scriptPubKey);
bool IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
bool IsMine(const CKeyStore& keystore, const CTxDestination &dest);
+
void ExtractAffectedKeys(const CKeyStore &keystore, const CScript& scriptPubKey, std::vector<CKeyID> &vKeys);
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);
bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn,
- bool fValidatePayToScriptHash, int nHashType);
-bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, int nHashType);
+ bool fValidatePayToScriptHash, bool fStrictEncodings, int nHashType);
+bool VerifySignature(const CCoins& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, bool fStrictEncodings, int nHashType);
// Given two sets of signatures for scriptPubKey, possibly with OP_0 placeholders,
// combine them intelligently and return the result.
}
+// Variable-length integers: bytes are a MSB base-128 encoding of the number.
+// The high bit in each byte signifies whether another digit follows. To make
+// the encoding is one-to-one, one is subtracted from all but the last digit.
+// Thus, the byte sequence a[] with length len, where all but the last byte
+// has bit 128 set, encodes the number:
+//
+// (a[len-1] & 0x7F) + sum(i=1..len-1, 128^i*((a[len-i-1] & 0x7F)+1))
+//
+// Properties:
+// * Very small (0-127: 1 byte, 128-16511: 2 bytes, 16512-2113663: 3 bytes)
+// * Every integer has exactly one encoding
+// * Encoding does not depend on size of original integer type
+// * No redundancy: every (infinite) byte sequence corresponds to a list
+// of encoded integers.
+//
+// 0: [0x00] 256: [0x81 0x00]
+// 1: [0x01] 16383: [0xFE 0x7F]
+// 127: [0x7F] 16384: [0xFF 0x00]
+// 128: [0x80 0x00] 16511: [0x80 0xFF 0x7F]
+// 255: [0x80 0x7F] 65535: [0x82 0xFD 0x7F]
+// 2^32: [0x8E 0xFE 0xFE 0xFF 0x00]
+
+template<typename I>
+inline unsigned int GetSizeOfVarInt(I n)
+{
+ int nRet = 0;
+ while(true) {
+ nRet++;
+ if (n <= 0x7F)
+ break;
+ n = (n >> 7) - 1;
+ }
+ return nRet;
+}
+
+template<typename Stream, typename I>
+void WriteVarInt(Stream& os, I n)
+{
+ unsigned char tmp[(sizeof(n)*8+6)/7];
+ int len=0;
+ while(true) {
+ tmp[len] = (n & 0x7F) | (len ? 0x80 : 0x00);
+ if (n <= 0x7F)
+ break;
+ n = (n >> 7) - 1;
+ len++;
+ }
+ do {
+ WRITEDATA(os, tmp[len]);
+ } while(len--);
+}
+
+template<typename Stream, typename I>
+I ReadVarInt(Stream& is)
+{
+ I n = 0;
+ while(true) {
+ unsigned char chData;
+ READDATA(is, chData);
+ n = (n << 7) | (chData & 0x7F);
+ if (chData & 0x80)
+ n++;
+ else
+ return n;
+ }
+}
+
#define FLATDATA(obj) REF(CFlatData((char*)&(obj), (char*)&(obj) + sizeof(obj)))
+#define VARINT(obj) REF(WrapVarInt(REF(obj)))
/** Wrapper for serializing arrays and POD.
*/
}
};
+template<typename I>
+class CVarInt
+{
+protected:
+ I &n;
+public:
+ CVarInt(I& nIn) : n(nIn) { }
+
+ unsigned int GetSerializeSize(int, int) const {
+ return GetSizeOfVarInt<I>(n);
+ }
+
+ template<typename Stream>
+ void Serialize(Stream &s, int, int) const {
+ WriteVarInt<Stream,I>(s, n);
+ }
+
+ template<typename Stream>
+ void Unserialize(Stream& s, int, int) {
+ n = ReadVarInt<Stream,I>(s);
+ }
+};
+
+template<typename I>
+CVarInt<I> WrapVarInt(I& n) { return CVarInt<I>(n); }
+
//
// Forward declarations
//
+++ /dev/null
-// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2012 The Bitcoin developers
-// Distributed under the MIT/X11 software license, see the accompanying
-// file license.txt or http://www.opensource.org/licenses/mit-license.php.
-
-#include <map>
-
-#include <boost/version.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/filesystem/fstream.hpp>
-
-#include <leveldb/env.h>
-#include <leveldb/cache.h>
-#include <leveldb/filter_policy.h>
-#include <memenv/memenv.h>
-
-#include "kernel.h"
-#include "checkpoints.h"
-#include "txdb.h"
-#include "util.h"
-#include "main.h"
-
-using namespace std;
-using namespace boost;
-
-leveldb::DB *txdb; // global pointer for LevelDB object instance
-
-static leveldb::Options GetOptions() {
- leveldb::Options options;
- int nCacheSizeMB = GetArg("-dbcache", 25);
- options.block_cache = leveldb::NewLRUCache(nCacheSizeMB * 1048576);
- options.filter_policy = leveldb::NewBloomFilterPolicy(10);
- return options;
-}
-
-void init_blockindex(leveldb::Options& options, bool fRemoveOld = false) {
- // First time init.
- filesystem::path directory = GetDataDir() / "txleveldb";
-
- if (fRemoveOld) {
- filesystem::remove_all(directory); // remove directory
- unsigned int nFile = 1;
-
- while (true)
- {
- filesystem::path strBlockFile = GetDataDir() / strprintf("blk%04u.dat", nFile);
-
- // Break if no such file
- if( !filesystem::exists( strBlockFile ) )
- break;
-
- filesystem::remove(strBlockFile);
-
- nFile++;
- }
- }
-
- filesystem::create_directory(directory);
- printf("Opening LevelDB in %s\n", directory.string().c_str());
- leveldb::Status status = leveldb::DB::Open(options, directory.string(), &txdb);
- if (!status.ok()) {
- throw runtime_error(strprintf("init_blockindex(): error opening database environment %s", status.ToString().c_str()));
- }
-}
-
-// CDB subclasses are created and destroyed VERY OFTEN. That's why
-// we shouldn't treat this as a free operations.
-CTxDB::CTxDB(const char* pszMode)
-{
- assert(pszMode);
- activeBatch = NULL;
- fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
-
- if (txdb) {
- pdb = txdb;
- return;
- }
-
- bool fCreate = strchr(pszMode, 'c');
-
- options = GetOptions();
- options.create_if_missing = fCreate;
- options.filter_policy = leveldb::NewBloomFilterPolicy(10);
-
- init_blockindex(options); // Init directory
- pdb = txdb;
-
- if (Exists(string("version")))
- {
- ReadVersion(nVersion);
- printf("Transaction index version is %d\n", nVersion);
-
- if (nVersion < DATABASE_VERSION)
- {
- printf("Required index version is %d, removing old database\n", DATABASE_VERSION);
-
- // Leveldb instance destruction
- delete txdb;
- txdb = pdb = NULL;
- delete activeBatch;
- activeBatch = NULL;
-
- init_blockindex(options, true); // Remove directory and create new database
- pdb = txdb;
-
- bool fTmp = fReadOnly;
- fReadOnly = false;
- WriteVersion(DATABASE_VERSION); // Save transaction index version
- fReadOnly = fTmp;
- }
- }
- else if (fCreate)
- {
- bool fTmp = fReadOnly;
- fReadOnly = false;
- WriteVersion(DATABASE_VERSION);
- fReadOnly = fTmp;
- }
-
- printf("Opened LevelDB successfully\n");
-}
-
-void CTxDB::Close()
-{
- delete txdb;
- txdb = pdb = NULL;
- delete options.filter_policy;
- options.filter_policy = NULL;
- delete options.block_cache;
- options.block_cache = NULL;
- delete activeBatch;
- activeBatch = NULL;
-}
-
-bool CTxDB::TxnBegin()
-{
- assert(!activeBatch);
- activeBatch = new leveldb::WriteBatch();
- return true;
-}
-
-bool CTxDB::TxnCommit()
-{
- assert(activeBatch);
- leveldb::Status status = pdb->Write(leveldb::WriteOptions(), activeBatch);
- delete activeBatch;
- activeBatch = NULL;
- if (!status.ok()) {
- printf("LevelDB batch commit failure: %s\n", status.ToString().c_str());
- return false;
- }
- return true;
-}
-
-class CBatchScanner : public leveldb::WriteBatch::Handler {
-public:
- std::string needle;
- bool *deleted;
- std::string *foundValue;
- bool foundEntry;
-
- CBatchScanner() : foundEntry(false) {}
-
- virtual void Put(const leveldb::Slice& key, const leveldb::Slice& value) {
- if (key.ToString() == needle) {
- foundEntry = true;
- *deleted = false;
- *foundValue = value.ToString();
- }
- }
-
- virtual void Delete(const leveldb::Slice& key) {
- if (key.ToString() == needle) {
- foundEntry = true;
- *deleted = true;
- }
- }
-};
-
-// When performing a read, if we have an active batch we need to check it first
-// before reading from the database, as the rest of the code assumes that once
-// a database transaction begins reads are consistent with it. It would be good
-// to change that assumption in future and avoid the performance hit, though in
-// practice it does not appear to be large.
-bool CTxDB::ScanBatch(const CDataStream &key, string *value, bool *deleted) const {
- assert(activeBatch);
- *deleted = false;
- CBatchScanner scanner;
- scanner.needle = key.str();
- scanner.deleted = deleted;
- scanner.foundValue = value;
- leveldb::Status status = activeBatch->Iterate(&scanner);
- if (!status.ok()) {
- throw runtime_error(status.ToString());
- }
- return scanner.foundEntry;
-}
-
-bool CTxDB::ReadTxIndex(uint256 hash, CTxIndex& txindex)
-{
- assert(!fClient);
- txindex.SetNull();
- return Read(make_pair(string("tx"), hash), txindex);
-}
-
-bool CTxDB::UpdateTxIndex(uint256 hash, const CTxIndex& txindex)
-{
- assert(!fClient);
- return Write(make_pair(string("tx"), hash), txindex);
-}
-
-bool CTxDB::AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight)
-{
- assert(!fClient);
-
- // Add to tx index
- uint256 hash = tx.GetHash();
- CTxIndex txindex(pos, tx.vout.size());
- return Write(make_pair(string("tx"), hash), txindex);
-}
-
-bool CTxDB::EraseTxIndex(const CTransaction& tx)
-{
- assert(!fClient);
- uint256 hash = tx.GetHash();
-
- return Erase(make_pair(string("tx"), hash));
-}
-
-bool CTxDB::ContainsTx(uint256 hash)
-{
- assert(!fClient);
- return Exists(make_pair(string("tx"), hash));
-}
-
-bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex)
-{
- assert(!fClient);
- tx.SetNull();
- if (!ReadTxIndex(hash, txindex))
- return false;
- return (tx.ReadFromDisk(txindex.pos));
-}
-
-bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx)
-{
- CTxIndex txindex;
- return ReadDiskTx(hash, tx, txindex);
-}
-
-bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex)
-{
- return ReadDiskTx(outpoint.hash, tx, txindex);
-}
-
-bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx)
-{
- CTxIndex txindex;
- return ReadDiskTx(outpoint.hash, tx, txindex);
-}
-
-bool CTxDB::WriteBlockIndex(const CDiskBlockIndex& blockindex)
-{
- return Write(make_pair(string("blockindex"), blockindex.GetBlockHash()), blockindex);
-}
-
-bool CTxDB::ReadHashBestChain(uint256& hashBestChain)
-{
- return Read(string("hashBestChain"), hashBestChain);
-}
-
-bool CTxDB::WriteHashBestChain(uint256 hashBestChain)
-{
- return Write(string("hashBestChain"), hashBestChain);
-}
-
-bool CTxDB::ReadBestInvalidTrust(CBigNum& bnBestInvalidTrust)
-{
- return Read(string("bnBestInvalidTrust"), bnBestInvalidTrust);
-}
-
-bool CTxDB::WriteBestInvalidTrust(CBigNum bnBestInvalidTrust)
-{
- return Write(string("bnBestInvalidTrust"), bnBestInvalidTrust);
-}
-
-bool CTxDB::ReadSyncCheckpoint(uint256& hashCheckpoint)
-{
- return Read(string("hashSyncCheckpoint"), hashCheckpoint);
-}
-
-bool CTxDB::WriteSyncCheckpoint(uint256 hashCheckpoint)
-{
- return Write(string("hashSyncCheckpoint"), hashCheckpoint);
-}
-
-bool CTxDB::ReadCheckpointPubKey(string& strPubKey)
-{
- return Read(string("strCheckpointPubKey"), strPubKey);
-}
-
-bool CTxDB::WriteCheckpointPubKey(const string& strPubKey)
-{
- return Write(string("strCheckpointPubKey"), strPubKey);
-}
-
-static CBlockIndex *InsertBlockIndex(uint256 hash)
-{
- if (hash == 0)
- return NULL;
-
- // Return existing
- map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
- if (mi != mapBlockIndex.end())
- return (*mi).second;
-
- // Create new
- CBlockIndex* pindexNew = new CBlockIndex();
- if (!pindexNew)
- throw runtime_error("LoadBlockIndex() : new CBlockIndex failed");
- mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
- pindexNew->phashBlock = &((*mi).first);
-
- return pindexNew;
-}
-
-bool CTxDB::LoadBlockIndex()
-{
- if (mapBlockIndex.size() > 0) {
- // Already loaded once in this session. It can happen during migration
- // from BDB.
- return true;
- }
- // The block index is an in-memory structure that maps hashes to on-disk
- // locations where the contents of the block can be found. Here, we scan it
- // out of the DB and into mapBlockIndex.
- leveldb::Iterator *iterator = pdb->NewIterator(leveldb::ReadOptions());
- // Seek to start key.
- CDataStream ssStartKey(SER_DISK, CLIENT_VERSION);
- ssStartKey << make_pair(string("blockindex"), uint256(0));
- iterator->Seek(ssStartKey.str());
- // Now read each entry.
- while (iterator->Valid())
- {
- // Unpack keys and values.
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- ssKey.write(iterator->key().data(), iterator->key().size());
- CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- ssValue.write(iterator->value().data(), iterator->value().size());
- string strType;
- ssKey >> strType;
- // Did we reach the end of the data to read?
- if (fRequestShutdown || strType != "blockindex")
- break;
- CDiskBlockIndex diskindex;
- ssValue >> diskindex;
-
- uint256 blockHash = diskindex.GetBlockHash();
-
- // Construct block index object
- CBlockIndex* pindexNew = InsertBlockIndex(blockHash);
- pindexNew->pprev = InsertBlockIndex(diskindex.hashPrev);
- pindexNew->pnext = InsertBlockIndex(diskindex.hashNext);
- pindexNew->nFile = diskindex.nFile;
- pindexNew->nBlockPos = diskindex.nBlockPos;
- pindexNew->nHeight = diskindex.nHeight;
- pindexNew->nMint = diskindex.nMint;
- pindexNew->nMoneySupply = diskindex.nMoneySupply;
- pindexNew->nFlags = diskindex.nFlags;
- pindexNew->nStakeModifier = diskindex.nStakeModifier;
- pindexNew->prevoutStake = diskindex.prevoutStake;
- pindexNew->nStakeTime = diskindex.nStakeTime;
- pindexNew->hashProofOfStake = diskindex.hashProofOfStake;
- pindexNew->nVersion = diskindex.nVersion;
- pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;
- pindexNew->nTime = diskindex.nTime;
- pindexNew->nBits = diskindex.nBits;
- pindexNew->nNonce = diskindex.nNonce;
-
- // Watch for genesis block
- if (pindexGenesisBlock == NULL && blockHash == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet))
- pindexGenesisBlock = pindexNew;
-
- if (!pindexNew->CheckIndex()) {
- delete iterator;
- return error("LoadBlockIndex() : CheckIndex failed at %d", pindexNew->nHeight);
- }
-
- // NovaCoin: build setStakeSeen
- if (pindexNew->IsProofOfStake())
- setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime));
-
- iterator->Next();
- }
- delete iterator;
-
- if (fRequestShutdown)
- return true;
-
- // Calculate nChainTrust
- vector<pair<int, CBlockIndex*> > vSortedByHeight;
- vSortedByHeight.reserve(mapBlockIndex.size());
- BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex)
- {
- CBlockIndex* pindex = item.second;
- vSortedByHeight.push_back(make_pair(pindex->nHeight, pindex));
- }
- sort(vSortedByHeight.begin(), vSortedByHeight.end());
- BOOST_FOREACH(const PAIRTYPE(int, CBlockIndex*)& item, vSortedByHeight)
- {
- CBlockIndex* pindex = item.second;
- pindex->nChainTrust = (pindex->pprev ? pindex->pprev->nChainTrust : 0) + pindex->GetBlockTrust();
- // NovaCoin: calculate stake modifier checksum
- pindex->nStakeModifierChecksum = GetStakeModifierChecksum(pindex);
- if (!CheckStakeModifierCheckpoints(pindex->nHeight, pindex->nStakeModifierChecksum))
- return error("CTxDB::LoadBlockIndex() : Failed stake modifier checkpoint height=%d, modifier=0x%016"PRI64x, pindex->nHeight, pindex->nStakeModifier);
- }
-
- // Load hashBestChain pointer to end of best chain
- if (!ReadHashBestChain(hashBestChain))
- {
- if (pindexGenesisBlock == NULL)
- return true;
- return error("CTxDB::LoadBlockIndex() : hashBestChain not loaded");
- }
- if (!mapBlockIndex.count(hashBestChain))
- return error("CTxDB::LoadBlockIndex() : hashBestChain not found in the block index");
- pindexBest = mapBlockIndex[hashBestChain];
- nBestHeight = pindexBest->nHeight;
- nBestChainTrust = pindexBest->nChainTrust;
-
- printf("LoadBlockIndex(): hashBestChain=%s height=%d trust=%s date=%s\n",
- hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str(),
- DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str());
-
- // NovaCoin: load hashSyncCheckpoint
- if (!ReadSyncCheckpoint(Checkpoints::hashSyncCheckpoint))
- return error("CTxDB::LoadBlockIndex() : hashSyncCheckpoint not loaded");
- printf("LoadBlockIndex(): synchronized checkpoint %s\n", Checkpoints::hashSyncCheckpoint.ToString().c_str());
-
- // Load bnBestInvalidTrust, OK if it doesn't exist
- CBigNum bnBestInvalidTrust;
- ReadBestInvalidTrust(bnBestInvalidTrust);
- nBestInvalidTrust = bnBestInvalidTrust.getuint256();
-
- // Verify blocks in the best chain
- int nCheckLevel = GetArg("-checklevel", 1);
- int nCheckDepth = GetArg( "-checkblocks", 2500);
- if (nCheckDepth == 0)
- nCheckDepth = 1000000000; // suffices until the year 19000
- if (nCheckDepth > nBestHeight)
- nCheckDepth = nBestHeight;
- printf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel);
- CBlockIndex* pindexFork = NULL;
- map<pair<unsigned int, unsigned int>, CBlockIndex*> mapBlockPos;
- for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev)
- {
- if (fRequestShutdown || pindex->nHeight < nBestHeight-nCheckDepth)
- break;
- CBlock block;
- if (!block.ReadFromDisk(pindex))
- return error("LoadBlockIndex() : block.ReadFromDisk failed");
- // check level 1: verify block validity
- // check level 7: verify block signature too
- if (nCheckLevel>0 && !block.CheckBlock(true, true, (nCheckLevel>6)))
- {
- printf("LoadBlockIndex() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str());
- pindexFork = pindex->pprev;
- }
- // check level 2: verify transaction index validity
- if (nCheckLevel>1)
- {
- pair<unsigned int, unsigned int> pos = make_pair(pindex->nFile, pindex->nBlockPos);
- mapBlockPos[pos] = pindex;
- BOOST_FOREACH(const CTransaction &tx, block.vtx)
- {
- uint256 hashTx = tx.GetHash();
- CTxIndex txindex;
- if (ReadTxIndex(hashTx, txindex))
- {
- // check level 3: checker transaction hashes
- if (nCheckLevel>2 || pindex->nFile != txindex.pos.nFile || pindex->nBlockPos != txindex.pos.nBlockPos)
- {
- // either an error or a duplicate transaction
- CTransaction txFound;
- if (!txFound.ReadFromDisk(txindex.pos))
- {
- printf("LoadBlockIndex() : *** cannot read mislocated transaction %s\n", hashTx.ToString().c_str());
- pindexFork = pindex->pprev;
- }
- else
- if (txFound.GetHash() != hashTx) // not a duplicate tx
- {
- printf("LoadBlockIndex(): *** invalid tx position for %s\n", hashTx.ToString().c_str());
- pindexFork = pindex->pprev;
- }
- }
- // check level 4: check whether spent txouts were spent within the main chain
- unsigned int nOutput = 0;
- if (nCheckLevel>3)
- {
- BOOST_FOREACH(const CDiskTxPos &txpos, txindex.vSpent)
- {
- if (!txpos.IsNull())
- {
- pair<unsigned int, unsigned int> posFind = make_pair(txpos.nFile, txpos.nBlockPos);
- if (!mapBlockPos.count(posFind))
- {
- printf("LoadBlockIndex(): *** found bad spend at %d, hashBlock=%s, hashTx=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str(), hashTx.ToString().c_str());
- pindexFork = pindex->pprev;
- }
- // check level 6: check whether spent txouts were spent by a valid transaction that consume them
- if (nCheckLevel>5)
- {
- CTransaction txSpend;
- if (!txSpend.ReadFromDisk(txpos))
- {
- printf("LoadBlockIndex(): *** cannot read spending transaction of %s:%i from disk\n", hashTx.ToString().c_str(), nOutput);
- pindexFork = pindex->pprev;
- }
- else if (!txSpend.CheckTransaction())
- {
- printf("LoadBlockIndex(): *** spending transaction of %s:%i is invalid\n", hashTx.ToString().c_str(), nOutput);
- pindexFork = pindex->pprev;
- }
- else
- {
- bool fFound = false;
- BOOST_FOREACH(const CTxIn &txin, txSpend.vin)
- if (txin.prevout.hash == hashTx && txin.prevout.n == nOutput)
- fFound = true;
- if (!fFound)
- {
- printf("LoadBlockIndex(): *** spending transaction of %s:%i does not spend it\n", hashTx.ToString().c_str(), nOutput);
- pindexFork = pindex->pprev;
- }
- }
- }
- }
- nOutput++;
- }
- }
- }
- // check level 5: check whether all prevouts are marked spent
- if (nCheckLevel>4)
- {
- BOOST_FOREACH(const CTxIn &txin, tx.vin)
- {
- CTxIndex txindex;
- if (ReadTxIndex(txin.prevout.hash, txindex))
- if (txindex.vSpent.size()-1 < txin.prevout.n || txindex.vSpent[txin.prevout.n].IsNull())
- {
- printf("LoadBlockIndex(): *** found unspent prevout %s:%i in %s\n", txin.prevout.hash.ToString().c_str(), txin.prevout.n, hashTx.ToString().c_str());
- pindexFork = pindex->pprev;
- }
- }
- }
- }
- }
- }
- if (pindexFork && !fRequestShutdown)
- {
- // Reorg back to the fork
- printf("LoadBlockIndex() : *** moving best chain pointer back to block %d\n", pindexFork->nHeight);
- CBlock block;
- if (!block.ReadFromDisk(pindexFork))
- return error("LoadBlockIndex() : block.ReadFromDisk failed");
- CTxDB txdb;
- block.SetBestChain(txdb, pindexFork);
- }
-
- return true;
-}
+++ /dev/null
-// Copyright (c) 2009-2012 The Bitcoin Developers.
-// Authored by Google, Inc.
-// Distributed under the MIT/X11 software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#ifndef BITCOIN_LEVELDB_H
-#define BITCOIN_LEVELDB_H
-
-#include "main.h"
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include <leveldb/db.h>
-#include <leveldb/write_batch.h>
-
-// Class that provides access to a LevelDB. Note that this class is frequently
-// instantiated on the stack and then destroyed again, so instantiation has to
-// be very cheap. Unfortunately that means, a CTxDB instance is actually just a
-// wrapper around some global state.
-//
-// A LevelDB is a key/value store that is optimized for fast usage on hard
-// disks. It prefers long read/writes to seeks and is based on a series of
-// sorted key/value mapping files that are stacked on top of each other, with
-// newer files overriding older files. A background thread compacts them
-// together when too many files stack up.
-//
-// Learn more: http://code.google.com/p/leveldb/
-class CTxDB
-{
-public:
- CTxDB(const char* pszMode="r+");
- ~CTxDB() {
- // Note that this is not the same as Close() because it deletes only
- // data scoped to this TxDB object.
- delete activeBatch;
- }
-
- // Destroys the underlying shared global state accessed by this TxDB.
- void Close();
-
-private:
- leveldb::DB *pdb; // Points to the global instance.
-
- // A batch stores up writes and deletes for atomic application. When this
- // field is non-NULL, writes/deletes go there instead of directly to disk.
- leveldb::WriteBatch *activeBatch;
- leveldb::Options options;
- bool fReadOnly;
- int nVersion;
-
-protected:
- // Returns true and sets (value,false) if activeBatch contains the given key
- // or leaves value alone and sets deleted = true if activeBatch contains a
- // delete for it.
- bool ScanBatch(const CDataStream &key, std::string *value, bool *deleted) const;
-
- template<typename K, typename T>
- bool Read(const K& key, T& value)
- {
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- ssKey.reserve(1000);
- ssKey << key;
- std::string strValue;
-
- bool readFromDb = true;
- if (activeBatch) {
- // First we must search for it in the currently pending set of
- // changes to the db. If not found in the batch, go on to read disk.
- bool deleted = false;
- readFromDb = ScanBatch(ssKey, &strValue, &deleted) == false;
- if (deleted) {
- return false;
- }
- }
- if (readFromDb) {
- leveldb::Status status = pdb->Get(leveldb::ReadOptions(),
- ssKey.str(), &strValue);
- if (!status.ok()) {
- if (status.IsNotFound())
- return false;
- // Some unexpected error.
- printf("LevelDB read failure: %s\n", status.ToString().c_str());
- return false;
- }
- }
- // Unserialize value
- try {
- CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(),
- SER_DISK, CLIENT_VERSION);
- ssValue >> value;
- }
- catch (std::exception &e) {
- return false;
- }
- return true;
- }
-
- template<typename K, typename T>
- bool Write(const K& key, const T& value)
- {
- if (fReadOnly)
- assert(!"Write called on database in read-only mode");
-
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- ssKey.reserve(1000);
- ssKey << key;
- CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- ssValue.reserve(10000);
- ssValue << value;
-
- if (activeBatch) {
- activeBatch->Put(ssKey.str(), ssValue.str());
- return true;
- }
- leveldb::Status status = pdb->Put(leveldb::WriteOptions(), ssKey.str(), ssValue.str());
- if (!status.ok()) {
- printf("LevelDB write failure: %s\n", status.ToString().c_str());
- return false;
- }
- return true;
- }
-
- template<typename K>
- bool Erase(const K& key)
- {
- if (!pdb)
- return false;
- if (fReadOnly)
- assert(!"Erase called on database in read-only mode");
-
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- ssKey.reserve(1000);
- ssKey << key;
- if (activeBatch) {
- activeBatch->Delete(ssKey.str());
- return true;
- }
- leveldb::Status status = pdb->Delete(leveldb::WriteOptions(), ssKey.str());
- return (status.ok() || status.IsNotFound());
- }
-
- template<typename K>
- bool Exists(const K& key)
- {
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- ssKey.reserve(1000);
- ssKey << key;
- std::string unused;
-
- if (activeBatch) {
- bool deleted;
- if (ScanBatch(ssKey, &unused, &deleted) && !deleted) {
- return true;
- }
- }
-
-
- leveldb::Status status = pdb->Get(leveldb::ReadOptions(), ssKey.str(), &unused);
- return status.IsNotFound() == false;
- }
-
-
-public:
- bool TxnBegin();
- bool TxnCommit();
- bool TxnAbort()
- {
- delete activeBatch;
- activeBatch = NULL;
- return true;
- }
-
- bool ReadVersion(int& nVersion)
- {
- nVersion = 0;
- return Read(std::string("version"), nVersion);
- }
-
- bool WriteVersion(int nVersion)
- {
- return Write(std::string("version"), nVersion);
- }
-
- bool ReadTxIndex(uint256 hash, CTxIndex& txindex);
- bool UpdateTxIndex(uint256 hash, const CTxIndex& txindex);
- bool AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight);
- bool EraseTxIndex(const CTransaction& tx);
- bool ContainsTx(uint256 hash);
- bool ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex);
- bool ReadDiskTx(uint256 hash, CTransaction& tx);
- bool ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex);
- bool ReadDiskTx(COutPoint outpoint, CTransaction& tx);
- bool WriteBlockIndex(const CDiskBlockIndex& blockindex);
- bool ReadHashBestChain(uint256& hashBestChain);
- bool WriteHashBestChain(uint256 hashBestChain);
- bool ReadBestInvalidTrust(CBigNum& bnBestInvalidTrust);
- bool WriteBestInvalidTrust(CBigNum bnBestInvalidTrust);
- bool ReadSyncCheckpoint(uint256& hashCheckpoint);
- bool WriteSyncCheckpoint(uint256 hashCheckpoint);
- bool ReadCheckpointPubKey(std::string& strPubKey);
- bool WriteCheckpointPubKey(const std::string& strPubKey);
- bool LoadBlockIndex();
-private:
- bool LoadBlockIndexGuts();
-};
-
-
-#endif // BITCOIN_DB_H
--- /dev/null
+// Copyright (c) 2009-2010 Satoshi Nakamoto
+// Copyright (c) 2009-2012 The Bitcoin developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "txdb.h"
+#include "main.h"
+
+using namespace std;
+
+void static BatchWriteCoins(CLevelDBBatch &batch, const uint256 &hash, const CCoins &coins) {
+ if (coins.IsPruned())
+ batch.Erase(make_pair('c', hash));
+ else
+ batch.Write(make_pair('c', hash), coins);
+}
+
+void static BatchWriteHashBestChain(CLevelDBBatch &batch, const uint256 &hash) {
+ batch.Write('B', hash);
+}
+
+CCoinsViewDB::CCoinsViewDB(bool fMemory) : db(GetDataDir() / "coins", fMemory) {
+}
+
+bool CCoinsViewDB::GetCoins(uint256 txid, CCoins &coins) {
+ return db.Read(make_pair('c', txid), coins);
+}
+
+bool CCoinsViewDB::SetCoins(uint256 txid, const CCoins &coins) {
+ CLevelDBBatch batch;
+ BatchWriteCoins(batch, txid, coins);
+ return db.WriteBatch(batch);
+}
+
+bool CCoinsViewDB::HaveCoins(uint256 txid) {
+ return db.Exists(make_pair('c', txid));
+}
+
+CBlockIndex *CCoinsViewDB::GetBestBlock() {
+ uint256 hashBestChain;
+ if (!db.Read('B', hashBestChain))
+ return NULL;
+ std::map<uint256, CBlockIndex*>::iterator it = mapBlockIndex.find(hashBestChain);
+ if (it == mapBlockIndex.end())
+ return NULL;
+ return it->second;
+}
+
+bool CCoinsViewDB::SetBestBlock(CBlockIndex *pindex) {
+ CLevelDBBatch batch;
+ BatchWriteHashBestChain(batch, pindex->GetBlockHash());
+ return db.WriteBatch(batch);
+}
+
+bool CCoinsViewDB::BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex) {
+ printf("Committing %u changed transactions to coin database...\n", (unsigned int)mapCoins.size());
+
+ CLevelDBBatch batch;
+ for (std::map<uint256, CCoins>::const_iterator it = mapCoins.begin(); it != mapCoins.end(); it++)
+ BatchWriteCoins(batch, it->first, it->second);
+ BatchWriteHashBestChain(batch, pindex->GetBlockHash());
+
+ return db.WriteBatch(batch);
+}
+
+CBlockTreeDB::CBlockTreeDB(bool fMemory) : CLevelDB(GetDataDir() / "blktree", fMemory) {
+}
+
+bool CBlockTreeDB::WriteBlockIndex(const CDiskBlockIndex& blockindex)
+{
+ return Write(make_pair('b', blockindex.GetBlockHash()), blockindex);
+}
+
+bool CBlockTreeDB::ReadBestInvalidTrust(CBigNum& bnBestInvalidTrust)
+{
+ return Read('I', bnBestInvalidTrust);
+}
+
+bool CBlockTreeDB::WriteBestInvalidTrust(CBigNum bnBestInvalidTrust)
+{
+ return Write('I', bnBestInvalidTrust);
+}
+
+bool CBlockTreeDB::ReadSyncCheckpoint(uint256& hashCheckpoint)
+{
+ return Read('H', hashCheckpoint);
+}
+
+bool CBlockTreeDB::WriteSyncCheckpoint(uint256 hashCheckpoint)
+{
+ return Write('H', hashCheckpoint);
+}
+
+bool CBlockTreeDB::ReadCheckpointPubKey(string& strPubKey)
+{
+ return Read('K', strPubKey);
+}
+
+bool CBlockTreeDB::WriteCheckpointPubKey(const string& strPubKey)
+{
+ return Write('K', strPubKey);
+}
+
+bool CBlockTreeDB::WriteBlockFileInfo(int nFile, const CBlockFileInfo &info) {
+ return Write(make_pair('f', nFile), info);
+}
+
+bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) {
+ return Read(make_pair('f', nFile), info);
+}
+
+bool CBlockTreeDB::WriteLastBlockFile(int nFile) {
+ return Write('l', nFile);
+}
+
+bool CBlockTreeDB::ReadLastBlockFile(int &nFile) {
+ return Read('l', nFile);
+}
+
+bool CCoinsViewDB::GetStats(CCoinsStats &stats) {
+ leveldb::Iterator *pcursor = db.NewIterator();
+ pcursor->SeekToFirst();
+
+ while (pcursor->Valid()) {
+ try {
+ leveldb::Slice slKey = pcursor->key();
+ CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION);
+ char chType;
+ ssKey >> chType;
+ if (chType == 'c' && !fRequestShutdown) {
+ leveldb::Slice slValue = pcursor->value();
+ CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION);
+ CCoins coins;
+ ssValue >> coins;
+ uint256 txhash;
+ ssKey >> txhash;
+
+ stats.nTransactions++;
+ BOOST_FOREACH(const CTxOut &out, coins.vout) {
+ if (!out.IsNull())
+ stats.nTransactionOutputs++;
+ }
+ stats.nSerializedSize += 32 + slValue.size();
+ }
+ pcursor->Next();
+ } catch (std::exception &e) {
+ return error("%s() : deserialize error", __PRETTY_FUNCTION__);
+ }
+ }
+ delete pcursor;
+ stats.nHeight = GetBestBlock()->nHeight;
+ return true;
+}
+
+bool CBlockTreeDB::LoadBlockIndexGuts()
+{
+ leveldb::Iterator *pcursor = NewIterator();
+
+ CDataStream ssKeySet(SER_DISK, CLIENT_VERSION);
+ ssKeySet << make_pair('b', uint256(0));
+ pcursor->Seek(ssKeySet.str());
+
+ // Load mapBlockIndex
+ while (pcursor->Valid()) {
+ try {
+ leveldb::Slice slKey = pcursor->key();
+ CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION);
+ char chType;
+ ssKey >> chType;
+ if (chType == 'b' && !fRequestShutdown) {
+ leveldb::Slice slValue = pcursor->value();
+ CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION);
+ CDiskBlockIndex diskindex;
+ ssValue >> diskindex;
+
+ uint256 blockHash = diskindex.GetBlockHash();
+
+ // Construct block index object
+ CBlockIndex* pindexNew = InsertBlockIndex(blockHash);
+ pindexNew->pprev = InsertBlockIndex(diskindex.hashPrev);
+ pindexNew->nHeight = diskindex.nHeight;
+ pindexNew->nFile = diskindex.nFile;
+ pindexNew->nDataPos = diskindex.nDataPos;
+ pindexNew->nUndoPos = diskindex.nUndoPos;
+ pindexNew->nMint = diskindex.nMint;
+ pindexNew->nMoneySupply = diskindex.nMoneySupply;
+ pindexNew->nFlags = diskindex.nFlags;
+ pindexNew->nStakeModifier = diskindex.nStakeModifier;
+ pindexNew->prevoutStake = diskindex.prevoutStake;
+ pindexNew->nStakeTime = diskindex.nStakeTime;
+ pindexNew->hashProofOfStake = diskindex.hashProofOfStake;
+ pindexNew->nVersion = diskindex.nVersion;
+ pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;
+ pindexNew->nTime = diskindex.nTime;
+ pindexNew->nBits = diskindex.nBits;
+ pindexNew->nNonce = diskindex.nNonce;
+ pindexNew->nStatus = diskindex.nStatus;
+ pindexNew->nTx = diskindex.nTx;
+
+ // Watch for genesis block
+ if (pindexGenesisBlock == NULL && blockHash == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet))
+ pindexGenesisBlock = pindexNew;
+
+ if (!pindexNew->CheckIndex())
+ return error("LoadBlockIndex() : CheckIndex failed: %s", pindexNew->ToString().c_str());
+
+ // Build setStakeSeen
+ if (pindexNew->IsProofOfStake())
+ setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime));
+
+ pcursor->Next();
+ } else {
+ break; // if shutdown requested or finished loading block index
+ }
+ } catch (std::exception &e) {
+ return error("%s() : deserialize error", __PRETTY_FUNCTION__);
+ }
+ }
+ delete pcursor;
+
+ return true;
+}
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2012 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
-// file license.txt or http://www.opensource.org/licenses/mit-license.php.
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#ifndef BITCOIN_TXDB_LEVELDB_H
+#define BITCOIN_TXDB_LEVELDB_H
-#ifndef BITCOIN_TXDB_H
-#define BITCOIN_TXDB_H
+#include "main.h"
+#include "leveldb.h"
-#include "txdb-leveldb.h"
+/** CCoinsView backed by the LevelDB coin database (coins/) */
+class CCoinsViewDB : public CCoinsView
+{
+protected:
+ CLevelDB db;
+public:
+ CCoinsViewDB(bool fMemory = false);
-#endif // BITCOIN_TXDB_H
+ bool GetCoins(uint256 txid, CCoins &coins);
+ bool SetCoins(uint256 txid, const CCoins &coins);
+ bool HaveCoins(uint256 txid);
+ CBlockIndex *GetBestBlock();
+ bool SetBestBlock(CBlockIndex *pindex);
+ bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, CBlockIndex *pindex);
+ bool GetStats(CCoinsStats &stats);
+};
+
+/** Access to the block database (blktree/) */
+class CBlockTreeDB : public CLevelDB
+{
+public:
+ CBlockTreeDB(bool fMemory = false);
+private:
+ CBlockTreeDB(const CBlockTreeDB&);
+ void operator=(const CBlockTreeDB&);
+public:
+ bool WriteBlockIndex(const CDiskBlockIndex& blockindex);
+ bool ReadBestInvalidTrust(CBigNum& bnBestInvalidTrust);
+ bool WriteBestInvalidTrust(CBigNum bnBestInvalidTrust);
+ bool ReadBlockFileInfo(int nFile, CBlockFileInfo &fileinfo);
+ bool WriteBlockFileInfo(int nFile, const CBlockFileInfo &fileinfo);
+ bool ReadLastBlockFile(int &nFile);
+ bool WriteLastBlockFile(int nFile);
+ bool ReadSyncCheckpoint(uint256& hashCheckpoint);
+ bool WriteSyncCheckpoint(uint256 hashCheckpoint);
+ bool ReadCheckpointPubKey(std::string& strPubKey);
+ bool WriteCheckpointPubKey(const std::string& strPubKey);
+ bool LoadBlockIndexGuts();
+};
+
+#endif // BITCOIN_TXDB_LEVELDB_H
return nFilesize;
}
+// this function tries to make a particular range of a file allocated (corresponding to disk space)
+// it is advisory, and the range specified in the arguments will never contain live data
+void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) {
+ static const char buf[65536] = {};
+ fseek(file, offset, SEEK_SET);
+ while (length > 0) {
+ unsigned int now = 65536;
+ if (length < now)
+ now = length;
+ fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway
+ length -= now;
+ }
+}
+
+
void ShrinkDebugFile()
{
// Scroll debug.log if it's getting too big
bool WildcardMatch(const std::string& str, const std::string& mask);
void FileCommit(FILE *fileout);
int GetFilesize(FILE* file);
+void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length);
bool RenameOver(boost::filesystem::path src, boost::filesystem::path dest);
boost::filesystem::path GetDefaultDataDir();
const boost::filesystem::path &GetDataDir(bool fNetSpecific = true);
#define DISPLAY_VERSION_MAJOR 0
#define DISPLAY_VERSION_MINOR 4
#define DISPLAY_VERSION_REVISION 4
-#define DISPLAY_VERSION_BUILD 6
+#define DISPLAY_VERSION_BUILD 7
#endif
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "txdb.h"
+#include "db.h"
#include "wallet.h"
#include "walletdb.h"
#include "crypter.h"
using namespace std;
extern int nStakeMaxAge;
-
//////////////////////////////////////////////////////////////////////////////
//
// mapWallet
return txOrdered;
}
-void CWallet::WalletUpdateSpent(const CTransaction &tx)
+void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
{
// 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
}
}
}
+
+ if (fBlock)
+ {
+ uint256 hash = tx.GetHash();
+ map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
+ CWalletTx& wtx = (*mi).second;
+
+ BOOST_FOREACH(const CTxOut& txout, tx.vout)
+ {
+ if (IsMine(txout))
+ {
+ wtx.MarkUnspent(&txout - &tx.vout[0]);
+ wtx.WriteToDisk();
+ NotifyTransactionChanged(this, hash, CT_UPDATED);
+ }
+ }
+ }
}
}
}
else
printf("AddToWallet() : found %s in block %s not in index\n",
- wtxIn.GetHash().ToString().substr(0,10).c_str(),
+ hash.ToString().substr(0,10).c_str(),
wtxIn.hashBlock.ToString().c_str());
}
}
}
//// debug print
- printf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString().substr(0,10).c_str(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
+ printf("AddToWallet %s %s%s\n", hash.ToString().substr(0,10).c_str(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if (fInsertedNew || fUpdated)
}
#endif
// since AddToWallet is called directly for self-originating transactions, check for consumption of own coins
- WalletUpdateSpent(wtx);
+ WalletUpdateSpent(wtx, (wtxIn.hashBlock != 0));
// Notify UI of new or updated transaction
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
if ( !strCmd.empty())
{
- boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
+ boost::replace_all(strCmd, "%s", hash.GetHex());
boost::thread t(runCommand, strCmd); // thread runs free
}
// Add a transaction to the wallet, or update it.
// pblock is optional, but should be provided if the transaction is known to be in a block.
// If fUpdate is true, existing transactions will be updated.
-bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fFindBlock)
+bool CWallet::AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fFindBlock)
{
- uint256 hash = tx.GetHash();
{
LOCK(cs_wallet);
bool fExisted = mapWallet.count(hash);
}
}
-void CWalletTx::AddSupportingTransactions(CTxDB& txdb)
+void CWalletTx::AddSupportingTransactions()
{
vtxPrev.clear();
BOOST_FOREACH(const CTxIn& txin, vin)
vWorkQueue.push_back(txin.prevout.hash);
- // This critsect is OK because txdb is already open
{
LOCK(pwallet->cs_wallet);
map<uint256, const CMerkleTx*> mapWalletPrev;
{
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);
block.ReadFromDisk(pindex, true);
BOOST_FOREACH(CTransaction& tx, block.vtx)
{
- if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
+ if (AddToWalletIfInvolvingMe(tx.GetHash(), tx, &block, fUpdate))
ret++;
}
pindex = pindex->pnext;
return ret;
}
-int CWallet::ScanForWalletTransaction(const uint256& hashTx)
-{
- CTransaction tx;
- tx.ReadFromDisk(COutPoint(hashTx, 0));
- if (AddToWalletIfInvolvingMe(tx, NULL, true, true))
- return 1;
- return 0;
-}
-
void CWallet::ReacceptWalletTransactions()
{
- CTxDB txdb("r");
bool fRepeat = true;
while (fRepeat)
{
LOCK(cs_wallet);
fRepeat = false;
- vector<CDiskTxPos> vMissingTx;
+ bool fMissing = false;
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
{
CWalletTx& wtx = item.second;
if ((wtx.IsCoinBase() && wtx.IsSpent(0)) || (wtx.IsCoinStake() && wtx.IsSpent(1)))
continue;
- CTxIndex txindex;
+ CCoins coins;
bool fUpdated = false;
- if (txdb.ReadTxIndex(wtx.GetHash(), txindex))
+ bool fNotFound = pcoinsTip->GetCoins(wtx.GetHash(), coins);
+ if (!fNotFound || wtx.GetDepthInMainChain() > 0)
{
// Update fSpent if a tx got spent somewhere else by a copy of wallet.dat
- if (txindex.vSpent.size() != wtx.vout.size())
- {
- printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %"PRIszu" != wtx.vout.size() %"PRIszu"\n", txindex.vSpent.size(), wtx.vout.size());
- continue;
- }
- for (unsigned int i = 0; i < txindex.vSpent.size(); i++)
+ for (unsigned int i = 0; i < wtx.vout.size(); i++)
{
if (wtx.IsSpent(i))
continue;
- if (!txindex.vSpent[i].IsNull() && IsMine(wtx.vout[i]))
+ if ((i >= coins.vout.size() || coins.vout[i].IsNull()) && IsMine(wtx.vout[i]))
{
wtx.MarkSpent(i);
fUpdated = true;
- vMissingTx.push_back(txindex.vSpent[i]);
+ fMissing = true;
}
}
if (fUpdated)
{
// Re-accept any txes of ours that aren't already in a block
if (!(wtx.IsCoinBase() || wtx.IsCoinStake()))
- wtx.AcceptWalletTransaction(txdb, false);
+ wtx.AcceptWalletTransaction(false);
}
}
- if (!vMissingTx.empty())
+ if (fMissing)
{
// TODO: optimize this to scan just part of the block chain?
if (ScanForWalletTransactions(pindexGenesisBlock))
}
}
-void CWalletTx::RelayWalletTransaction(CTxDB& txdb)
+void CWalletTx::RelayWalletTransaction()
{
+ CCoinsViewCache& coins = *pcoinsTip;
BOOST_FOREACH(const CMerkleTx& tx, vtxPrev)
{
if (!(tx.IsCoinBase() || tx.IsCoinStake()))
{
uint256 hash = tx.GetHash();
- if (!txdb.ContainsTx(hash))
+ if (!coins.HaveCoins(hash))
RelayTransaction((CTransaction)tx, hash);
}
}
if (!(IsCoinBase() || IsCoinStake()))
{
uint256 hash = GetHash();
- if (!txdb.ContainsTx(hash))
+ if (!coins.HaveCoins(hash))
{
printf("Relaying wtx %s\n", hash.ToString().substr(0,10).c_str());
RelayTransaction((CTransaction)*this, hash);
}
}
-void CWalletTx::RelayWalletTransaction()
-{
- CTxDB txdb("r");
- RelayWalletTransaction(txdb);
-}
-
void CWallet::ResendWalletTransactions()
{
// Do this infrequently and randomly to avoid giving away
// Rebroadcast any of our txes that aren't in a block yet
printf("ResendWalletTransactions()\n");
- CTxDB txdb("r");
{
LOCK(cs_wallet);
// Sort them in chronological order
BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted)
{
CWalletTx& wtx = *item.second;
- if (wtx.CheckTransaction())
- wtx.RelayWalletTransaction(txdb);
- else
- printf("ResendWalletTransactions() : CheckTransaction failed for transaction %s\n", wtx.GetHash().ToString().c_str());
+ wtx.RelayWalletTransaction();
}
}
}
{
LOCK2(cs_main, cs_wallet);
- // txdb must be opened before the mapWallet lock
- CTxDB txdb("r");
{
nFeeRet = nTransactionFee;
while (true)
nFeeRet += nMoveToFee;
}
- // ppcoin: sub-cent change is moved to fee
+ // sub-cent change is moved to fee
if (nChange > 0 && nChange < MIN_TXOUT_AMOUNT)
{
nFeeRet += nChange;
// Check that enough fee is included
int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000);
- int64 nMinFee = wtxNew.GetMinFee(1, false, GMF_SEND, nBytes);
-
+ bool fAllowFree = CTransaction::AllowFree(dPriority);
+ int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND);
if (nFeeRet < max(nPayFee, nMinFee))
{
nFeeRet = max(nPayFee, nMinFee);
}
// Fill vtxPrev by copying from previous transactions vtxPrev
- wtxNew.AddSupportingTransactions(txdb);
+ wtxNew.AddSupportingTransactions();
wtxNew.fTimeReceivedIsTxTime = true;
break;
return false;
vector<const CWalletTx*> vwtxPrev;
-
-/*
- * TODO: performance comparison
-
- static set<pair<const CWalletTx*,unsigned int> > setCoins;
- static uint256 hashPrevBlock;
- static int64 nValueIn = 0;
-
- // Cache outputs unless best block changed
- if (hashPrevBlock != pindexBest->GetBlockHash())
- {
- if (!SelectCoinsSimple(nBalance - nReserveBalance, GetAdjustedTime(), nCoinbaseMaturity * 10, setCoins, nValueIn))
- return false;
-
- if (setCoins.empty())
- return false;
-
- hashPrevBlock == pindexBest->GetBlockHash();
- }
-*/
-
set<pair<const CWalletTx*,unsigned int> > setCoins;
int64 nValueIn = 0;
if (setCoins.empty())
return false;
- CTxDB txdb("r");
+ CCoinsViewCache &view = *pcoinsTip;
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
- CTxIndex txindex;
+ CCoins coins;
{
LOCK2(cs_main, cs_wallet);
- if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex))
+ if (!view.GetCoinsReadOnly(pcoin.first->GetHash(), coins))
continue;
}
// The following combine threshold is important to security
// Should not be adjusted if you don't understand the consequences
int64 nCombineThreshold = GetProofOfWorkReward(GetLastBlockIndex(pindexBest, false)->nBits) / 3;
-
+ CBlockIndex* pindexPrev = pindexBest;
CBigNum bnTargetPerCoinDay;
bnTargetPerCoinDay.SetCompact(nBits);
return false;
vector<const CWalletTx*> vwtxPrev;
-
-/*
- * TODO: performance comparison
-
- static set<pair<const CWalletTx*,unsigned int> > setCoins;
- static uint256 hashPrevBlock;
- static int64 nValueIn = 0;
-
- // Cache outputs unless best block changed
- if (hashPrevBlock != pindexBest->GetBlockHash())
- {
- if (!SelectCoinsSimple(nBalance - nReserveBalance, txNew.nTime, nCoinbaseMaturity * 10, setCoins, nValueIn))
- return false;
-
- if (setCoins.empty())
- return false;
-
- hashPrevBlock == pindexBest->GetBlockHash();
- }
-*/
-
set<pair<const CWalletTx*,unsigned int> > setCoins;
int64 nValueIn = 0;
int64 nCredit = 0;
CScript scriptPubKeyKernel;
- CTxDB txdb("r");
+
+ CCoinsViewCache &view = *pcoinsTip;
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
- CTxIndex txindex;
+ CCoins coins;
{
LOCK2(cs_main, cs_wallet);
- if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex))
+ if (!view.GetCoinsReadOnly(pcoin.first->GetHash(), coins))
continue;
}
+ static int nMaxStakeSearchInterval = 60;
+ if (coins.nBlockTime + nStakeMinAge > txNew.nTime - nMaxStakeSearchInterval)
+ continue; // only count coins meeting min age requirement
+
// Read block header
CBlock block;
+ unsigned int nTxPos = 0;
{
LOCK2(cs_main, cs_wallet);
- if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
+ CBlockIndex *pindex = FindBlockByHeight(coins.nHeight);
+
+ if (!block.ReadFromDisk(pindex))
continue;
+
+ BOOST_FOREACH(const CTransaction &tx, block.vtx) {
+ if (tx.GetHash() == pcoin.first->GetHash()) {
+ break;
+ }
+ nTxPos += tx.GetSerializeSize(SER_DISK, CLIENT_VERSION);
+ }
+ nTxPos += GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(block.vtx.size());
}
- static int nMaxStakeSearchInterval = 60;
- if (block.GetBlockTime() + nStakeMinAge > txNew.nTime - nMaxStakeSearchInterval)
- continue; // only count coins meeting min age requirement
+ bool fFatal = false;
bool fKernelFound = false;
- for (unsigned int n=0; n<min(nSearchInterval,(int64)nMaxStakeSearchInterval) && !fKernelFound && !fShutdown; n++)
+ for (unsigned int n=0; n<min(nSearchInterval,(int64)nMaxStakeSearchInterval) && !fKernelFound && !fShutdown && pindexPrev == pindexBest; n++)
{
// Search backward in time from the given txNew timestamp
// Search nSearchInterval seconds back up to nMaxStakeSearchInterval
uint256 hashProofOfStake = 0, targetProofOfStake = 0;
COutPoint prevoutStake = COutPoint(pcoin.first->GetHash(), pcoin.second);
- if (CheckStakeKernelHash(nBits, block, txindex.pos.nTxPos - txindex.pos.nBlockPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake, targetProofOfStake))
+ if (CheckStakeKernelHash(nBits, block, nTxPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake, targetProofOfStake, fFatal, true))
{
// Found a kernel
if (fDebug && GetBoolArg("-printcoinstake"))
// Calculate coin age reward
{
uint64 nCoinAge;
- CTxDB txdb("r");
- if (!txNew.GetCoinAge(txdb, nCoinAge))
+
+ if (!txNew.GetCoinAge(nCoinAge))
return error("CreateCoinStake : failed to calculate coin age");
nCredit += GetProofOfStakeReward(nCoinAge, nBits, txNew.nTime);
}
return ret;
}
-// ppcoin: check 'spent' consistency between wallet and txindex
-// ppcoin: fix wallet spent state according to txindex
+// 1. check 'spent' consistency between wallet and coins database
+// 2. fix wallet spent state according to coins database
+// 3. remove orphaned coinstakes and coinbases from wallet
void CWallet::FixSpentCoins(int& nMismatchFound, int64& nBalanceInQuestion, bool fCheckOnly)
{
nMismatchFound = 0;
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
vCoins.push_back(&(*it).second);
- CTxDB txdb("r");
+ CCoinsViewCache &view = *pcoinsTip;
BOOST_FOREACH(CWalletTx* pcoin, vCoins)
{
- // Find the corresponding transaction index
- CTxIndex txindex;
- if (!txdb.ReadTxIndex(pcoin->GetHash(), txindex))
+ uint256 hash = pcoin->GetHash();
+ if(!view.HaveCoins(hash))
continue;
+
+ // Find the corresponding transaction index
+ CCoins &coins = view.GetCoins(hash);
+
for (unsigned int n=0; n < pcoin->vout.size(); n++)
{
- if (IsMine(pcoin->vout[n]) && pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull()))
+ bool fUpdated = false;
+ if (IsMine(pcoin->vout[n]))
{
- printf("FixSpentCoins found lost coin %sppc %s[%d], %s\n",
- FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
- nMismatchFound++;
- nBalanceInQuestion += pcoin->vout[n].nValue;
- if (!fCheckOnly)
+ if (pcoin->IsSpent(n) && coins.IsAvailable(n))
{
- pcoin->MarkUnspent(n);
- pcoin->WriteToDisk();
+ printf("FixSpentCoins found lost coin %snvc %s[%d], %s\n",
+ FormatMoney(pcoin->vout[n].nValue).c_str(), hash.ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
+ nMismatchFound++;
+ nBalanceInQuestion += pcoin->vout[n].nValue;
+ if (!fCheckOnly)
+ {
+ fUpdated = true;
+ pcoin->MarkUnspent(n);
+ pcoin->WriteToDisk();
+ }
}
- }
- else if (IsMine(pcoin->vout[n]) && !pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull()))
- {
- printf("FixSpentCoins found spent coin %sppc %s[%d], %s\n",
- FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
- nMismatchFound++;
- nBalanceInQuestion += pcoin->vout[n].nValue;
- if (!fCheckOnly)
+ else if (!pcoin->IsSpent(n) && !coins.IsAvailable(n))
{
- pcoin->MarkSpent(n);
- pcoin->WriteToDisk();
+ printf("FixSpentCoins found spent coin %snvc %s[%d], %s\n",
+ FormatMoney(pcoin->vout[n].nValue).c_str(), hash.ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
+ nMismatchFound++;
+ nBalanceInQuestion += pcoin->vout[n].nValue;
+ if (!fCheckOnly)
+ {
+ fUpdated = true;
+ pcoin->MarkSpent(n);
+ pcoin->WriteToDisk();
+ }
}
+
+ if (fUpdated)
+ NotifyTransactionChanged(this, hash, CT_UPDATED);
+ }
+ }
+
+ if((pcoin->IsCoinBase() || pcoin->IsCoinStake()) && pcoin->GetDepthInMainChain() == 0)
+ {
+ if (!fCheckOnly)
+ {
+ EraseFromWallet(hash);
+ NotifyTransactionChanged(this, hash, CT_DELETED);
}
+
+ printf("FixSpentCoins %s orphaned generation tx %s\n", fCheckOnly ? "found" : "removed", hash.ToString().c_str());
}
}
}
void MarkDirty();
bool AddToWallet(const CWalletTx& wtxIn);
- bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate = false, bool fFindBlock = false);
+ bool AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate = false, bool fFindBlock = false);
bool EraseFromWallet(uint256 hash);
- void WalletUpdateSpent(const CTransaction& prevout);
+ void WalletUpdateSpent(const CTransaction& prevout, bool fBlock = false);
int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
- int ScanForWalletTransaction(const uint256& hashTx);
void ReacceptWalletTransactions();
void ResendWalletTransactions();
int64 GetBalance() const;
int64 GetTxTime() const;
int GetRequestCount() const;
- void AddSupportingTransactions(CTxDB& txdb);
+ void AddSupportingTransactions();
- bool AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs=true);
- bool AcceptWalletTransaction();
-
- void RelayWalletTransaction(CTxDB& txdb);
+ bool AcceptWalletTransaction(bool fCheckInputs=true);
void RelayWalletTransaction();
};