bdb7253399
FastImport was the legacy path for rebuilding the block index from a
local blk0001.dat. With v2 UTXO snapshots now containing embedded
blocks, FastImport is redundant and dangerous (could silently index
a forked chain from a stale blk0001.dat).
Changes:
- src/main.cpp: delete FastImportBlockFile() function (~270 lines)
- src/main.h: delete FastImportBlockFile() declaration
- src/init.cpp: delete -allowfastimport flag handler block
remove from help text
clean up stale comments referencing FastImportBlockFile
- src/bootstrap.cpp: update stale comments
v2 snapshot loading (auto-download from bootstrap or local placement
of utxo-snapshot.bin + manifest) is now the only supported sync start.
Tested: daemon builds, runs, chain state preserved across restart.
Binary SHA: 3f26f6202947a8dc0f7933314829702aafa1e42c968368ab7ec043d57baa9519
DNS2 + DNS3 running this build, both on correct chain.
Not bumped to v5.9.21 per Sami's preference. Next formal release
will inherit this change.
1823 lines
52 KiB
C++
1823 lines
52 KiB
C++
// 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.
|
|
#ifndef TRIANGLES_MAIN_H
|
|
#define TRIANGLES_MAIN_H
|
|
|
|
#include "bignum.h"
|
|
#include "sync.h"
|
|
#include "net.h"
|
|
#include "script.h"
|
|
#include "scrypt.h"
|
|
#include "hashblock.h"
|
|
#include "checkqueue.h"
|
|
#include "sigcache.h"
|
|
|
|
#include <list>
|
|
#include <array>
|
|
#include <memory>
|
|
|
|
class CWallet;
|
|
class CBlock;
|
|
class CBlockIndex;
|
|
class CKeyItem;
|
|
class CReserveKey;
|
|
class COutPoint;
|
|
|
|
class CAddress;
|
|
class CInv;
|
|
class CRequestTracker;
|
|
class CNode;
|
|
class CScriptCheck;
|
|
|
|
constexpr int CUTOFF_POW_BLOCK = 9000;
|
|
constexpr int CRAPCHAIN_CUTOFF_BLOCK = 17691; // pre-Pharao (version 4) blockchain until block 17691
|
|
constexpr int FORK_HEIGHT_V5 = 17651; // v5 hard fork: decentralization + Tor v3 (next block after last checkpoint)
|
|
constexpr int FORK_HEIGHT_V5_4 = 2186941; // v5.4: tighter timestamps, deterministic fork resolution
|
|
|
|
constexpr unsigned int MAX_BLOCK_SIZE = 1000000;
|
|
constexpr unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
|
|
constexpr unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50;
|
|
constexpr unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100;
|
|
constexpr unsigned int MAX_ORPHAN_BLOCKS = 750;
|
|
constexpr unsigned int MAX_ORPHAN_BLOCKS_IBD = 1500;
|
|
constexpr unsigned int MAX_REORG_DEPTH = 100; // reject reorgs deeper than this (finality)
|
|
constexpr unsigned int MAX_INV_SZ = 50000;
|
|
constexpr int64_t MIN_TX_FEE = (1 * CENT) / 100;
|
|
constexpr int64_t MIN_RELAY_TX_FEE = (1 * CENT) / 100;
|
|
constexpr int64_t MAX_MONEY = 2222222 * COIN;
|
|
constexpr int64_t COIN_YEAR_REWARD = 33 * CENT; // 33% per year
|
|
constexpr int64_t MAX_TRI_PROOF_OF_STAKE = 0.33 * COIN;
|
|
constexpr int MODIFIER_INTERVAL_SWITCH = 1;
|
|
|
|
inline bool MoneyRange(int64_t nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); }
|
|
// Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp.
|
|
constexpr unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC
|
|
|
|
#ifdef USE_UPNP
|
|
static const int fHaveUPnP = true;
|
|
#else
|
|
static const int fHaveUPnP = false;
|
|
#endif
|
|
|
|
static const uint256 hashGenesisBlockOfficial("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
|
static const uint256 hashGenesisBlockTestNet ("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
|
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 90 : 10 * 60; }
|
|
inline int64_t PastDrift(int64_t nTime, int nHeight) { return nTime - GetMaxTimeDrift(nHeight); }
|
|
inline int64_t FutureDrift(int64_t nTime, int nHeight) { return nTime + GetMaxTimeDrift(nHeight); }
|
|
// Height-less overloads always use post-V5.4 rules (90-second drift).
|
|
// All nodes are well past FORK_HEIGHT_V5_4; using the global nBestHeight
|
|
// here previously caused nodes at different heights to disagree on block
|
|
// validity during the fork transition — a consensus-splitting bug.
|
|
inline int64_t PastDrift(int64_t nTime) { return PastDrift(nTime, FORK_HEIGHT_V5_4); }
|
|
inline int64_t FutureDrift(int64_t nTime) { return FutureDrift(nTime, FORK_HEIGHT_V5_4); }
|
|
|
|
|
|
extern CScript COINBASE_FLAGS;
|
|
extern CCriticalSection cs_main;
|
|
extern std::map<uint256, CBlockIndex*> mapBlockIndex;
|
|
extern std::set<std::pair<COutPoint, unsigned int> > setStakeSeen;
|
|
extern CBlockIndex* pindexGenesisBlock;
|
|
extern unsigned int nStakeMinAge;
|
|
extern unsigned int nNodeLifespan;
|
|
extern int nCoinbaseMaturity;
|
|
extern int nBestHeight;
|
|
extern bool fLoadedFromSnapshot; // true after successful UtxoSnapshot::LoadSnapshot
|
|
extern uint256 nBestChainTrust;
|
|
extern uint256 nBestInvalidTrust;
|
|
extern uint256 hashBestChain;
|
|
extern CBlockIndex* pindexBest;
|
|
extern CBlockIndex* pindexFinalized; // auto-checkpoint: deepest finalized block
|
|
extern unsigned int nTransactionsUpdated;
|
|
extern uint64_t nLastBlockTx;
|
|
extern uint64_t nLastBlockSize;
|
|
extern int64_t nLastCoinStakeSearchInterval;
|
|
extern const std::string strMessageMagic;
|
|
extern int64_t nTimeBestReceived;
|
|
extern CCriticalSection cs_setpwalletRegistered;
|
|
extern std::set<CWallet*> setpwalletRegistered;
|
|
extern unsigned char pchMessageStart[4];
|
|
extern std::map<uint256, std::unique_ptr<CBlock>> mapOrphanBlocks;
|
|
|
|
// Settings
|
|
extern int64_t nTransactionFee;
|
|
extern int64_t nReserveBalance;
|
|
extern int64_t nMinimumInputValue;
|
|
extern bool fUseFastIndex;
|
|
extern unsigned int nDerivationMethodIndex;
|
|
|
|
extern bool fEnforceCanonical;
|
|
|
|
// Minimum disk space required - used in CheckDiskSpace()
|
|
constexpr uint64_t nMinDiskSpace = 52428800;
|
|
|
|
class CReserveKey;
|
|
class CTxDBBase;
|
|
class CTxIndex;
|
|
|
|
void RegisterWallet(CWallet* pwalletIn);
|
|
void UnregisterWallet(CWallet* pwalletIn);
|
|
void SyncWithWallets(const CTransaction& tx, const CBlock* pblock = nullptr, bool fUpdate = false, bool fConnect = true);
|
|
bool ProcessBlock(CNode* pfrom, CBlock* pblock);
|
|
bool CheckDiskSpace(uint64_t nAdditionalBytes=0);
|
|
FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode="rb");
|
|
FILE* AppendBlockFile(unsigned int& nFileRet);
|
|
bool LoadBlockIndex(bool fAllowNew=true);
|
|
void PrintBlockTree();
|
|
CBlockIndex* FindBlockByHeight(int nHeight);
|
|
bool ProcessMessages(CNode* pfrom);
|
|
bool SendMessages(CNode* pto, bool fSendTrickle);
|
|
bool LoadExternalBlockFile(FILE* fileIn);
|
|
|
|
bool CheckProofOfWork(uint256 hash, unsigned int nBits);
|
|
unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake);
|
|
int64_t GetProofOfWorkReward(int64_t nFees);
|
|
int64_t GetProofOfStakeReward(int64_t nCoinAge, int64_t nFees);
|
|
unsigned int ComputeMinWork(unsigned int nBase, int64_t nTime);
|
|
unsigned int ComputeMinStake(unsigned int nBase, int64_t nTime, unsigned int nBlockTime);
|
|
int GetNumBlocksOfPeers();
|
|
[[nodiscard]] bool IsInitialBlockDownload();
|
|
std::string GetWarnings(std::string strFor);
|
|
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock);
|
|
uint256 WantedByOrphan(const CBlock* pblockOrphan);
|
|
unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans);
|
|
const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake);
|
|
void StakeMiner(CWallet *pwallet);
|
|
void ResendWalletTransactions(bool fForce = false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool GetWalletFile(CWallet* pwallet, std::string &strWalletFileOut);
|
|
|
|
/** Position on disk for a particular transaction. */
|
|
class CDiskTxPos
|
|
{
|
|
public:
|
|
unsigned int nFile;
|
|
unsigned int nBlockPos;
|
|
unsigned int nTxPos;
|
|
|
|
CDiskTxPos()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
CDiskTxPos(unsigned int nFileIn, unsigned int nBlockPosIn, unsigned int nTxPosIn)
|
|
{
|
|
nFile = nFileIn;
|
|
nBlockPos = nBlockPosIn;
|
|
nTxPos = nTxPosIn;
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE( READWRITE(FLATDATA(*this)); )
|
|
void SetNull() { nFile = (unsigned int) -1; nBlockPos = 0; nTxPos = 0; }
|
|
bool IsNull() const { return (nFile == (unsigned int) -1); }
|
|
|
|
friend bool operator==(const CDiskTxPos& a, const CDiskTxPos& b)
|
|
{
|
|
return (a.nFile == b.nFile &&
|
|
a.nBlockPos == b.nBlockPos &&
|
|
a.nTxPos == b.nTxPos);
|
|
}
|
|
|
|
friend bool operator!=(const CDiskTxPos& a, const CDiskTxPos& b) = default;
|
|
|
|
|
|
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());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/** An inpoint - a combination of a transaction and an index n into its vin */
|
|
class CInPoint
|
|
{
|
|
public:
|
|
CTransaction* ptx;
|
|
unsigned int n;
|
|
|
|
CInPoint() { SetNull(); }
|
|
CInPoint(CTransaction* ptxIn, unsigned int nIn) { ptx = ptxIn; n = nIn; }
|
|
void SetNull() { ptx = nullptr; n = (unsigned int) -1; }
|
|
bool IsNull() const { return (ptx == nullptr && n == (unsigned int) -1); }
|
|
};
|
|
|
|
|
|
|
|
/** An outpoint - a combination of a transaction hash and an index n into its vout */
|
|
class COutPoint
|
|
{
|
|
public:
|
|
uint256 hash;
|
|
unsigned int n;
|
|
|
|
COutPoint() { SetNull(); }
|
|
COutPoint(uint256 hashIn, unsigned int nIn) { hash = hashIn; n = nIn; }
|
|
IMPLEMENT_SERIALIZE( READWRITE(FLATDATA(*this)); )
|
|
void SetNull() { hash = 0; n = (unsigned int) -1; }
|
|
bool IsNull() const { return (hash == 0 && n == (unsigned int) -1); }
|
|
|
|
friend bool operator<(const COutPoint& a, const COutPoint& b)
|
|
{
|
|
return (a.hash < b.hash || (a.hash == b.hash && a.n < b.n));
|
|
}
|
|
|
|
friend bool operator==(const COutPoint& a, const COutPoint& b)
|
|
{
|
|
return (a.hash == b.hash && a.n == b.n);
|
|
}
|
|
|
|
friend bool operator!=(const COutPoint& a, const COutPoint& b) = default;
|
|
|
|
std::string ToString() const
|
|
{
|
|
return strprintf("COutPoint(%s, %u)", hash.ToString().substr(0,10).c_str(), n);
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/** An input of a transaction. It contains the location of the previous
|
|
* transaction's output that it claims and a signature that matches the
|
|
* output's public key.
|
|
*/
|
|
class CTxIn
|
|
{
|
|
public:
|
|
COutPoint prevout;
|
|
CScript scriptSig;
|
|
unsigned int nSequence;
|
|
|
|
CTxIn()
|
|
{
|
|
nSequence = std::numeric_limits<unsigned int>::max();
|
|
}
|
|
|
|
explicit CTxIn(COutPoint prevoutIn, CScript scriptSigIn=CScript(), unsigned int nSequenceIn=std::numeric_limits<unsigned int>::max())
|
|
{
|
|
prevout = prevoutIn;
|
|
scriptSig = scriptSigIn;
|
|
nSequence = nSequenceIn;
|
|
}
|
|
|
|
CTxIn(uint256 hashPrevTx, unsigned int nOut, CScript scriptSigIn=CScript(), unsigned int nSequenceIn=std::numeric_limits<unsigned int>::max())
|
|
{
|
|
prevout = COutPoint(hashPrevTx, nOut);
|
|
scriptSig = scriptSigIn;
|
|
nSequence = nSequenceIn;
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(prevout);
|
|
READWRITE(scriptSig);
|
|
READWRITE(nSequence);
|
|
)
|
|
|
|
bool IsFinal() const
|
|
{
|
|
return (nSequence == std::numeric_limits<unsigned int>::max());
|
|
}
|
|
|
|
friend bool operator==(const CTxIn& a, const CTxIn& b)
|
|
{
|
|
return (a.prevout == b.prevout &&
|
|
a.scriptSig == b.scriptSig &&
|
|
a.nSequence == b.nSequence);
|
|
}
|
|
|
|
friend bool operator!=(const CTxIn& a, const CTxIn& b) = default;
|
|
|
|
std::string ToStringShort() const
|
|
{
|
|
return strprintf(" %s %d", prevout.hash.ToString().c_str(), prevout.n);
|
|
}
|
|
|
|
std::string ToString() const
|
|
{
|
|
std::string str;
|
|
str += "CTxIn(";
|
|
str += prevout.ToString();
|
|
if (prevout.IsNull())
|
|
str += strprintf(", coinbase %s", HexStr(scriptSig).c_str());
|
|
else
|
|
str += strprintf(", scriptSig=%s", scriptSig.ToString().substr(0,24).c_str());
|
|
if (nSequence != std::numeric_limits<unsigned int>::max())
|
|
str += strprintf(", nSequence=%u", nSequence);
|
|
str += ")";
|
|
return str;
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/** An output of a transaction. It contains the public key that the next input
|
|
* must be able to sign with to claim it.
|
|
*/
|
|
class CTxOut
|
|
{
|
|
public:
|
|
int64_t nValue;
|
|
CScript scriptPubKey;
|
|
|
|
CTxOut()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
CTxOut(int64_t nValueIn, CScript scriptPubKeyIn)
|
|
{
|
|
nValue = nValueIn;
|
|
scriptPubKey = scriptPubKeyIn;
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(nValue);
|
|
READWRITE(scriptPubKey);
|
|
)
|
|
|
|
void SetNull()
|
|
{
|
|
nValue = -1;
|
|
scriptPubKey.clear();
|
|
}
|
|
|
|
bool IsNull()
|
|
{
|
|
return (nValue == -1);
|
|
}
|
|
|
|
void SetEmpty()
|
|
{
|
|
nValue = 0;
|
|
scriptPubKey.clear();
|
|
}
|
|
|
|
bool IsEmpty() const
|
|
{
|
|
return (nValue == 0 && scriptPubKey.empty());
|
|
}
|
|
|
|
uint256 GetHash() const
|
|
{
|
|
return SerializeHash(*this);
|
|
}
|
|
|
|
friend bool operator==(const CTxOut& a, const CTxOut& b)
|
|
{
|
|
return (a.nValue == b.nValue &&
|
|
a.scriptPubKey == b.scriptPubKey);
|
|
}
|
|
|
|
friend bool operator!=(const CTxOut& a, const CTxOut& b) = default;
|
|
|
|
std::string ToStringShort() const
|
|
{
|
|
return strprintf(" out %s %s", FormatMoney(nValue).c_str(), scriptPubKey.ToString(true).c_str());
|
|
}
|
|
|
|
std::string ToString() const
|
|
{
|
|
if (IsEmpty()) return "CTxOut(empty)";
|
|
if (scriptPubKey.size() < 6)
|
|
return "CTxOut(error)";
|
|
return strprintf("CTxOut(nValue=%s, scriptPubKey=%s)", FormatMoney(nValue).c_str(), scriptPubKey.ToString().c_str());
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
enum class GetMinFeeMode : int
|
|
{
|
|
Block,
|
|
Relay,
|
|
Send,
|
|
};
|
|
|
|
/** A single unspent transaction output entry in the UTXO database.
|
|
* Keyed by (txhash, output_index). Erased when spent.
|
|
*/
|
|
class CUtxoEntry
|
|
{
|
|
public:
|
|
int64_t nValue; // output value in satoshis
|
|
int nHeight; // block height where output was created
|
|
CScript scriptPubKey; // output script (needed for sig verification)
|
|
bool fCoinBase; // from a coinbase transaction
|
|
bool fCoinStake; // from a coinstake transaction
|
|
unsigned int nTxTime; // transaction timestamp (needed for PoS coin age)
|
|
|
|
CUtxoEntry()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(nValue);
|
|
READWRITE(nHeight);
|
|
READWRITE(scriptPubKey);
|
|
READWRITE(fCoinBase);
|
|
READWRITE(fCoinStake);
|
|
READWRITE(nTxTime);
|
|
)
|
|
|
|
void SetNull()
|
|
{
|
|
nValue = -1;
|
|
nHeight = 0;
|
|
scriptPubKey.clear();
|
|
fCoinBase = false;
|
|
fCoinStake = false;
|
|
nTxTime = 0;
|
|
}
|
|
|
|
bool IsNull() const
|
|
{
|
|
return (nValue == -1);
|
|
}
|
|
};
|
|
|
|
using MapPrevTx = std::map<COutPoint, CUtxoEntry>;
|
|
|
|
/** The basic transaction that is broadcasted on the network and contained in
|
|
* blocks. A transaction can contain multiple inputs and outputs.
|
|
*/
|
|
class CTransaction
|
|
{
|
|
public:
|
|
static const int CURRENT_VERSION=1;
|
|
int nVersion;
|
|
unsigned int nTime;
|
|
std::vector<CTxIn> vin;
|
|
std::vector<CTxOut> vout;
|
|
unsigned int nLockTime;
|
|
|
|
// Denial-of-service detection:
|
|
mutable int nDoS;
|
|
bool DoS(int nDoSIn, bool fIn) const { nDoS += nDoSIn; return fIn; }
|
|
|
|
CTransaction()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(this->nVersion);
|
|
nVersion = this->nVersion;
|
|
READWRITE(nTime);
|
|
READWRITE(vin);
|
|
READWRITE(vout);
|
|
READWRITE(nLockTime);
|
|
)
|
|
|
|
void SetNull()
|
|
{
|
|
nVersion = CTransaction::CURRENT_VERSION;
|
|
nTime = GetAdjustedTime();
|
|
vin.clear();
|
|
vout.clear();
|
|
nLockTime = 0;
|
|
nDoS = 0; // Denial-of-service prevention
|
|
}
|
|
|
|
bool IsNull() const
|
|
{
|
|
return (vin.empty() && vout.empty());
|
|
}
|
|
|
|
uint256 GetHash() const
|
|
{
|
|
return SerializeHash(*this);
|
|
}
|
|
|
|
bool IsFinal(int nBlockHeight=0, int64_t nBlockTime=0) const
|
|
{
|
|
// Time based nLockTime implemented in 0.1.6
|
|
if (nLockTime == 0)
|
|
return true;
|
|
if (nBlockHeight == 0)
|
|
nBlockHeight = nBestHeight;
|
|
if (nBlockTime == 0)
|
|
nBlockTime = GetAdjustedTime();
|
|
if ((int64_t)nLockTime < ((int64_t)nLockTime < LOCKTIME_THRESHOLD ? (int64_t)nBlockHeight : nBlockTime))
|
|
return true;
|
|
for (const CTxIn& txin : vin)
|
|
if (!txin.IsFinal())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool IsNewerThan(const CTransaction& old) const
|
|
{
|
|
if (vin.size() != old.vin.size())
|
|
return false;
|
|
for (unsigned int i = 0; i < vin.size(); i++)
|
|
if (vin[i].prevout != old.vin[i].prevout)
|
|
return false;
|
|
|
|
bool fNewer = false;
|
|
unsigned int nLowest = std::numeric_limits<unsigned int>::max();
|
|
for (unsigned int i = 0; i < vin.size(); i++)
|
|
{
|
|
if (vin[i].nSequence != old.vin[i].nSequence)
|
|
{
|
|
if (vin[i].nSequence <= nLowest)
|
|
{
|
|
fNewer = false;
|
|
nLowest = vin[i].nSequence;
|
|
}
|
|
if (old.vin[i].nSequence < nLowest)
|
|
{
|
|
fNewer = true;
|
|
nLowest = old.vin[i].nSequence;
|
|
}
|
|
}
|
|
}
|
|
return fNewer;
|
|
}
|
|
|
|
bool IsCoinBase() const
|
|
{
|
|
return (vin.size() == 1 && vin[0].prevout.IsNull() && vout.size() >= 1);
|
|
}
|
|
|
|
bool IsCoinStake() const
|
|
{
|
|
// triangles: the coin stake transaction is marked with the first output empty
|
|
return (vin.size() > 0 && (!vin[0].prevout.IsNull()) && vout.size() >= 2 && vout[0].IsEmpty());
|
|
}
|
|
|
|
/** Check for standard transaction types
|
|
@return True if all outputs (scriptPubKeys) use only standard transaction forms
|
|
*/
|
|
[[nodiscard]] bool IsStandard() const;
|
|
|
|
/** Check for standard transaction types
|
|
@param[in] mapInputs Map of previous transactions that have outputs we're spending
|
|
@return True if all inputs (scriptSigs) use only standard transaction forms
|
|
@see CTransaction::FetchInputs
|
|
*/
|
|
bool AreInputsStandard(const MapPrevTx& 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
|
|
@see CTransaction::FetchInputs
|
|
*/
|
|
unsigned int GetLegacySigOpCount() const;
|
|
|
|
/** Count ECDSA signature operations in pay-to-script-hash inputs.
|
|
|
|
@param[in] mapInputs Map of previous transactions that have outputs we're spending
|
|
@return maximum number of sigops required to validate this transaction's inputs
|
|
@see CTransaction::FetchInputs
|
|
*/
|
|
unsigned int GetP2SHSigOpCount(const MapPrevTx& mapInputs) const;
|
|
|
|
/** Amount of Triangles spent by this transaction.
|
|
@return sum of all outputs (note: does not include fees)
|
|
*/
|
|
int64_t GetValueOut() const
|
|
{
|
|
int64_t nValueOut = 0;
|
|
for (const CTxOut& txout : vout)
|
|
{
|
|
nValueOut += txout.nValue;
|
|
if (!MoneyRange(txout.nValue) || !MoneyRange(nValueOut))
|
|
throw std::runtime_error("CTransaction::GetValueOut() : value out of range");
|
|
}
|
|
return nValueOut;
|
|
}
|
|
|
|
/** Amount of Triangles coming in to this transaction
|
|
Note that lightweight clients may not know anything besides the hash of previous transactions,
|
|
so may not be able to calculate this.
|
|
|
|
@param[in] mapInputs Map of previous transactions that have outputs we're spending
|
|
@return Sum of value of all inputs (scriptSigs)
|
|
@see CTransaction::FetchInputs
|
|
*/
|
|
int64_t GetValueIn(const MapPrevTx& mapInputs) const;
|
|
|
|
int64_t GetMinFee(unsigned int nBlockSize=1, GetMinFeeMode mode=GetMinFeeMode::Block, unsigned int nBytes = 0) const;
|
|
|
|
bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=nullptr)
|
|
{
|
|
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 &&
|
|
a.nTime == b.nTime &&
|
|
a.vin == b.vin &&
|
|
a.vout == b.vout &&
|
|
a.nLockTime == b.nLockTime);
|
|
}
|
|
|
|
friend bool operator!=(const CTransaction& a, const CTransaction& b) = default;
|
|
|
|
std::string ToStringShort() const
|
|
{
|
|
std::string str;
|
|
str += strprintf("%s %s", GetHash().ToString().c_str(), IsCoinBase()? "base" : (IsCoinStake()? "stake" : "user"));
|
|
return str;
|
|
}
|
|
|
|
std::string ToString() const
|
|
{
|
|
std::string str;
|
|
str += IsCoinBase()? "Coinbase" : (IsCoinStake()? "Coinstake" : "CTransaction");
|
|
str += strprintf("(hash=%s, nTime=%d, ver=%d, vin.size=%" PRIszu ", vout.size=%" PRIszu ", nLockTime=%d)\n",
|
|
GetHash().ToString().substr(0,10).c_str(),
|
|
nTime,
|
|
nVersion,
|
|
vin.size(),
|
|
vout.size(),
|
|
nLockTime);
|
|
for (const CTxIn& txin : vin)
|
|
str += " " + txin.ToString() + "\n";
|
|
for (const CTxOut& txout : vout)
|
|
str += " " + txout.ToString() + "\n";
|
|
return str;
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s", ToString().c_str());
|
|
}
|
|
|
|
|
|
bool ReadFromDisk(CTxDBBase& txdb, COutPoint prevout, CTxIndex& txindexRet);
|
|
bool ReadFromDisk(CTxDBBase& txdb, COutPoint prevout);
|
|
bool ReadFromDisk(COutPoint prevout);
|
|
bool DisconnectInputs(CTxDBBase& txdb);
|
|
|
|
/** Fetch UTXO entries for all inputs from the UTXO database or mempool.
|
|
|
|
@param[in] txdb Transaction database
|
|
@param[in] mapPendingUtxos UTXOs created by earlier transactions in the same block
|
|
@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 UTXO entries for this transaction's inputs (keyed by COutPoint)
|
|
@param[out] fInvalid returns true if transaction is invalid
|
|
@return Returns true if all inputs are found
|
|
*/
|
|
bool FetchInputs(CTxDBBase& txdb, const MapPrevTx& mapPendingUtxos,
|
|
bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid);
|
|
|
|
/** Validate inputs against UTXO entries and verify signatures.
|
|
|
|
@param[in] inputs UTXO entries for inputs (from FetchInputs)
|
|
@param[in] pindexBlock Block being connected
|
|
@param[in] fBlock true if called from ConnectBlock
|
|
@param[in] fMiner true if called from CreateNewBlock
|
|
@return Returns true if all checks succeed
|
|
*/
|
|
[[nodiscard]] bool ConnectInputs(CTxDBBase& txdb, const MapPrevTx& inputs,
|
|
const CBlockIndex* pindexBlock, bool fBlock, bool fMiner,
|
|
std::vector<CScriptCheck>* pvChecks = nullptr);
|
|
bool ClientConnectInputs();
|
|
[[nodiscard]] bool CheckTransaction() const;
|
|
[[nodiscard]] bool AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs=true, bool* pfMissingInputs=nullptr);
|
|
bool GetCoinAge(CTxDBBase& txdb, uint64_t& nCoinAge) const; // triangles: get transaction coin age
|
|
|
|
protected:
|
|
const CTxOut& GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** A transaction with a merkle branch linking it to the block chain. */
|
|
class CMerkleTx : public CTransaction
|
|
{
|
|
private:
|
|
int GetDepthInMainChainINTERNAL(CBlockIndex* &pindexRet) const;
|
|
public:
|
|
uint256 hashBlock;
|
|
std::vector<uint256> vMerkleBranch;
|
|
int nIndex;
|
|
|
|
// memory only
|
|
mutable bool fMerkleVerified;
|
|
|
|
|
|
CMerkleTx()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
CMerkleTx(const CTransaction& txIn) : CTransaction(txIn)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
hashBlock = 0;
|
|
nIndex = -1;
|
|
fMerkleVerified = false;
|
|
}
|
|
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
nSerSize += SerReadWrite(s, *(CTransaction*)this, nType, nVersion, ser_action);
|
|
nVersion = this->nVersion;
|
|
READWRITE(hashBlock);
|
|
READWRITE(vMerkleBranch);
|
|
READWRITE(nIndex);
|
|
)
|
|
|
|
|
|
int SetMerkleBranch(const CBlock* pblock=nullptr);
|
|
|
|
// Return depth of transaction in blockchain:
|
|
// -1 : not in blockchain, and not in memory pool (conflicted transaction)
|
|
// 0 : in memory pool, waiting to be included in a block
|
|
// >=1 : this many blocks deep in the main chain
|
|
int GetDepthInMainChain(CBlockIndex* &pindexRet) const;
|
|
int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
|
|
bool IsInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; }
|
|
int GetBlocksToMaturity() const;
|
|
bool AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs=true);
|
|
bool AcceptToMemoryPool();
|
|
};
|
|
|
|
|
|
|
|
|
|
/** A txdb record that contains the disk location of a transaction.
|
|
* Used for getrawtransaction and wallet position lookups.
|
|
* vSpent is legacy (kept for serialization compat) — spent state is
|
|
* tracked by the UTXO set (CUtxoEntry) since dbformat=3.
|
|
*/
|
|
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) = default;
|
|
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
|
|
{
|
|
public:
|
|
// header
|
|
static const int CURRENT_VERSION=6;
|
|
int nVersion;
|
|
uint256 hashPrevBlock;
|
|
uint256 hashMerkleRoot;
|
|
unsigned int nTime;
|
|
unsigned int nBits;
|
|
unsigned int nNonce;
|
|
|
|
// network and disk
|
|
std::vector<CTransaction> vtx;
|
|
|
|
// triangles: block signature - signed by one of the coin base txout[N]'s owner
|
|
std::vector<unsigned char> vchBlockSig;
|
|
|
|
// memory only
|
|
mutable std::vector<uint256> vMerkleTree;
|
|
|
|
// Denial-of-service detection:
|
|
mutable int nDoS;
|
|
bool DoS(int nDoSIn, bool fIn) const { nDoS += nDoSIn; return fIn; }
|
|
|
|
CBlock()
|
|
{
|
|
SetNull();
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(this->nVersion);
|
|
nVersion = this->nVersion;
|
|
READWRITE(hashPrevBlock);
|
|
READWRITE(hashMerkleRoot);
|
|
READWRITE(nTime);
|
|
READWRITE(nBits);
|
|
READWRITE(nNonce);
|
|
|
|
// ConnectBlock depends on vtx following header to generate CDiskTxPos
|
|
if (!(nType & (SER_GETHASH|SER_BLOCKHEADERONLY)))
|
|
{
|
|
READWRITE(vtx);
|
|
READWRITE(vchBlockSig);
|
|
}
|
|
else if (fRead)
|
|
{
|
|
const_cast<CBlock*>(this)->vtx.clear();
|
|
const_cast<CBlock*>(this)->vchBlockSig.clear();
|
|
}
|
|
)
|
|
|
|
void SetNull()
|
|
{
|
|
nVersion = CBlock::CURRENT_VERSION;
|
|
hashPrevBlock = 0;
|
|
hashMerkleRoot = 0;
|
|
nTime = 0;
|
|
nBits = 0;
|
|
nNonce = 0;
|
|
vtx.clear();
|
|
vchBlockSig.clear();
|
|
vMerkleTree.clear();
|
|
nDoS = 0;
|
|
fCachedHash = false;
|
|
fMerkleTreeCached = false;
|
|
}
|
|
|
|
bool IsNull() const
|
|
{
|
|
return (nBits == 0);
|
|
}
|
|
|
|
mutable uint256 cachedHash;
|
|
mutable bool fCachedHash;
|
|
mutable bool fMerkleTreeCached;
|
|
|
|
uint256 GetHash() const
|
|
{
|
|
if (!fCachedHash)
|
|
{
|
|
cachedHash = Hash9(BEGIN(nVersion), END(nNonce));
|
|
fCachedHash = true;
|
|
}
|
|
return cachedHash;
|
|
}
|
|
|
|
int64_t GetBlockTime() const
|
|
{
|
|
return (int64_t)nTime;
|
|
}
|
|
|
|
void UpdateTime(const CBlockIndex* pindexPrev);
|
|
|
|
// triangles: entropy bit for stake modifier if chosen by modifier
|
|
unsigned int GetStakeEntropyBit() const
|
|
{
|
|
// Take last bit of block hash as entropy bit
|
|
unsigned int nEntropyBit = ((GetHash().Get64()) & 1llu);
|
|
if (fDebug && GetBoolArg("-printstakemodifier"))
|
|
printf("GetStakeEntropyBit: hashBlock=%s nEntropyBit=%u\n", GetHash().ToString().c_str(), nEntropyBit);
|
|
return nEntropyBit;
|
|
}
|
|
|
|
// triangles: two types of block: proof-of-work or proof-of-stake
|
|
bool IsProofOfStake() const
|
|
{
|
|
return (vtx.size() > 1 && vtx[1].IsCoinStake());
|
|
}
|
|
|
|
bool IsProofOfWork() const
|
|
{
|
|
return !IsProofOfStake();
|
|
}
|
|
|
|
std::pair<COutPoint, unsigned int> GetProofOfStake() const
|
|
{
|
|
return IsProofOfStake()? std::pair{vtx[1].vin[0].prevout, vtx[1].nTime} : std::pair{COutPoint(), (unsigned int)0};
|
|
}
|
|
|
|
// triangles: get max transaction timestamp
|
|
int64_t GetMaxTransactionTime() const
|
|
{
|
|
int64_t maxTransactionTime = 0;
|
|
for (const CTransaction& tx : vtx)
|
|
maxTransactionTime = std::max(maxTransactionTime, (int64_t)tx.nTime);
|
|
return maxTransactionTime;
|
|
}
|
|
|
|
uint256 BuildMerkleTree() const
|
|
{
|
|
if (fMerkleTreeCached)
|
|
return (vMerkleTree.empty() ? 0 : vMerkleTree.back());
|
|
|
|
vMerkleTree.clear();
|
|
for (const CTransaction& tx : vtx)
|
|
vMerkleTree.push_back(tx.GetHash());
|
|
int j = 0;
|
|
for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2)
|
|
{
|
|
for (int i = 0; i < nSize; i += 2)
|
|
{
|
|
int i2 = std::min(i+1, nSize-1);
|
|
vMerkleTree.push_back(Hash(BEGIN(vMerkleTree[j+i]), END(vMerkleTree[j+i]),
|
|
BEGIN(vMerkleTree[j+i2]), END(vMerkleTree[j+i2])));
|
|
}
|
|
j += nSize;
|
|
}
|
|
fMerkleTreeCached = true;
|
|
return (vMerkleTree.empty() ? 0 : vMerkleTree.back());
|
|
}
|
|
|
|
std::vector<uint256> GetMerkleBranch(int nIndex) const
|
|
{
|
|
if (vMerkleTree.empty())
|
|
BuildMerkleTree();
|
|
std::vector<uint256> vMerkleBranch;
|
|
int j = 0;
|
|
for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2)
|
|
{
|
|
int i = std::min(nIndex^1, nSize-1);
|
|
vMerkleBranch.push_back(vMerkleTree[j+i]);
|
|
nIndex >>= 1;
|
|
j += nSize;
|
|
}
|
|
return vMerkleBranch;
|
|
}
|
|
|
|
static uint256 CheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex)
|
|
{
|
|
if (nIndex == -1)
|
|
return 0;
|
|
for (const uint256& otherside : vMerkleBranch)
|
|
{
|
|
if (nIndex & 1)
|
|
hash = Hash(BEGIN(otherside), END(otherside), BEGIN(hash), END(hash));
|
|
else
|
|
hash = Hash(BEGIN(hash), END(hash), BEGIN(otherside), END(otherside));
|
|
nIndex >>= 1;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
|
|
bool WriteToDisk(unsigned int& nFileRet, unsigned int& nBlockPosRet)
|
|
{
|
|
// Open history file to append
|
|
CAutoFile fileout = CAutoFile(AppendBlockFile(nFileRet), SER_DISK, CLIENT_VERSION);
|
|
if (!fileout)
|
|
return error("CBlock::WriteToDisk() : AppendBlockFile failed");
|
|
|
|
// Write index header
|
|
unsigned int nSize = fileout.GetSerializeSize(*this);
|
|
fileout << FLATDATA(pchMessageStart) << nSize;
|
|
|
|
// Write block
|
|
long fileOutPos = ftell(fileout);
|
|
if (fileOutPos < 0)
|
|
return error("CBlock::WriteToDisk() : ftell failed");
|
|
nBlockPosRet = fileOutPos;
|
|
fileout << *this;
|
|
|
|
// Flush stdio buffers and commit to disk before returning
|
|
fflush(fileout);
|
|
if (!IsInitialBlockDownload() || (nBestHeight+1) % 500 == 0)
|
|
FileCommit(fileout);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ReadFromDisk(unsigned int nFile, unsigned int nBlockPos, bool fReadTransactions=true)
|
|
{
|
|
SetNull();
|
|
|
|
// Open history file to read
|
|
CAutoFile filein = CAutoFile(OpenBlockFile(nFile, nBlockPos, "rb"), SER_DISK, CLIENT_VERSION);
|
|
if (!filein)
|
|
return error("CBlock::ReadFromDisk() : OpenBlockFile failed");
|
|
if (!fReadTransactions)
|
|
filein.nType |= SER_BLOCKHEADERONLY;
|
|
|
|
// Read block
|
|
try {
|
|
filein >> *this;
|
|
}
|
|
catch (std::exception &e) {
|
|
return error("%s() : deserialize or I/O error", __PRETTY_FUNCTION__);
|
|
}
|
|
|
|
// Check the header
|
|
if (fReadTransactions && IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits))
|
|
return error("CBlock::ReadFromDisk() : errors in block header");
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void print() const
|
|
{
|
|
printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%" PRIszu ", vchBlockSig=%s)\n",
|
|
GetHash().ToString().c_str(),
|
|
nVersion,
|
|
hashPrevBlock.ToString().c_str(),
|
|
hashMerkleRoot.ToString().c_str(),
|
|
nTime, nBits, nNonce,
|
|
vtx.size(),
|
|
HexStr(vchBlockSig.begin(), vchBlockSig.end()).c_str());
|
|
for (const CTransaction& tx : vtx)
|
|
{
|
|
printf(" ");
|
|
tx.print();
|
|
}
|
|
printf(" vMerkleTree: ");
|
|
for (const uint256& merkle : vMerkleTree)
|
|
printf("%s ", merkle.ToString().substr(0,10).c_str());
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
bool DisconnectBlock(CTxDBBase& txdb, CBlockIndex* pindex);
|
|
[[nodiscard]] bool ConnectBlock(CTxDBBase& txdb, CBlockIndex* pindex, bool fJustCheck=false);
|
|
bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true);
|
|
bool SetBestChain(CTxDBBase& txdb, CBlockIndex* pindexNew);
|
|
bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const uint256& hashProofOfStake);
|
|
bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true, bool fCheckSig=true) const;
|
|
[[nodiscard]] bool AcceptBlock();
|
|
bool GetCoinAge(uint64_t& nCoinAge) const; // triangles: calculate total coin age spent in block
|
|
bool SignBlock(CWallet& keystore, int64_t nFees);
|
|
bool CheckBlockSignature() const;
|
|
|
|
private:
|
|
bool SetBestChainInner(CTxDBBase& txdb, CBlockIndex *pindexNew);
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** The block chain is a tree shaped structure starting with the
|
|
* genesis block at the root, with each block potentially having multiple
|
|
* candidates to be the next block. pprev and pnext link a path through the
|
|
* main/longest chain. A blockindex may have multiple pprev pointing back
|
|
* to it, but pnext will only point forward to the longest branch, or will
|
|
* be null if the block is not part of the longest chain.
|
|
*/
|
|
class CBlockIndex
|
|
{
|
|
public:
|
|
const uint256* phashBlock;
|
|
CBlockIndex* pprev;
|
|
CBlockIndex* pnext;
|
|
unsigned int nFile;
|
|
unsigned int nBlockPos;
|
|
uint256 nChainTrust; // triangles: trust score of block chain
|
|
int nHeight;
|
|
|
|
int64_t nMint;
|
|
int64_t nMoneySupply;
|
|
|
|
unsigned int nFlags; // triangles: block index flags
|
|
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
|
|
};
|
|
|
|
uint64_t nStakeModifier; // hash modifier for proof-of-stake
|
|
unsigned int nStakeModifierChecksum; // checksum of index; in-memeory only
|
|
|
|
// proof-of-stake specific fields
|
|
COutPoint prevoutStake;
|
|
unsigned int nStakeTime;
|
|
uint256 hashProofOfStake;
|
|
|
|
// block header
|
|
int nVersion;
|
|
uint256 hashMerkleRoot;
|
|
unsigned int nTime;
|
|
unsigned int nBits;
|
|
unsigned int nNonce;
|
|
|
|
CBlockIndex()
|
|
{
|
|
phashBlock = nullptr;
|
|
pprev = nullptr;
|
|
pnext = nullptr;
|
|
nFile = 0;
|
|
nBlockPos = 0;
|
|
nHeight = 0;
|
|
nChainTrust = 0;
|
|
nMint = 0;
|
|
nMoneySupply = 0;
|
|
nFlags = 0;
|
|
nStakeModifier = 0;
|
|
nStakeModifierChecksum = 0;
|
|
hashProofOfStake = 0;
|
|
prevoutStake.SetNull();
|
|
nStakeTime = 0;
|
|
|
|
nVersion = 0;
|
|
hashMerkleRoot = 0;
|
|
nTime = 0;
|
|
nBits = 0;
|
|
nNonce = 0;
|
|
}
|
|
|
|
CBlockIndex(unsigned int nFileIn, unsigned int nBlockPosIn, CBlock& block) : CBlockIndex()
|
|
{
|
|
nFile = nFileIn;
|
|
nBlockPos = nBlockPosIn;
|
|
if (block.IsProofOfStake())
|
|
{
|
|
SetProofOfStake();
|
|
prevoutStake = block.vtx[1].vin[0].prevout;
|
|
nStakeTime = block.vtx[1].nTime;
|
|
}
|
|
|
|
nVersion = block.nVersion;
|
|
hashMerkleRoot = block.hashMerkleRoot;
|
|
nTime = block.nTime;
|
|
nBits = block.nBits;
|
|
nNonce = block.nNonce;
|
|
}
|
|
|
|
CBlock GetBlockHeader() const
|
|
{
|
|
CBlock block;
|
|
block.nVersion = nVersion;
|
|
if (pprev)
|
|
block.hashPrevBlock = pprev->GetBlockHash();
|
|
block.hashMerkleRoot = hashMerkleRoot;
|
|
block.nTime = nTime;
|
|
block.nBits = nBits;
|
|
block.nNonce = nNonce;
|
|
return block;
|
|
}
|
|
|
|
uint256 GetBlockHash() const
|
|
{
|
|
return *phashBlock;
|
|
}
|
|
|
|
int64_t GetBlockTime() const
|
|
{
|
|
return (int64_t)nTime;
|
|
}
|
|
|
|
uint256 GetBlockTrust() const;
|
|
|
|
bool IsInMainChain() const
|
|
{
|
|
return (pnext || this == pindexBest);
|
|
}
|
|
|
|
bool CheckIndex() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int64_t GetPastTimeLimit() const
|
|
{
|
|
return GetMedianTimePast();
|
|
}
|
|
|
|
enum { nMedianTimeSpan=11 };
|
|
|
|
int64_t GetMedianTimePast() const
|
|
{
|
|
std::array<int64_t, nMedianTimeSpan> pmedian{};
|
|
auto pbegin = pmedian.end();
|
|
auto pend = pmedian.end();
|
|
|
|
const CBlockIndex* pindex = this;
|
|
for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev)
|
|
*(--pbegin) = pindex->GetBlockTime();
|
|
|
|
std::sort(pbegin, pend);
|
|
return pbegin[(pend - pbegin)/2];
|
|
}
|
|
|
|
int64_t GetMedianTime() const
|
|
{
|
|
const CBlockIndex* pindex = this;
|
|
for (int i = 0; i < nMedianTimeSpan/2; i++)
|
|
{
|
|
if (!pindex->pnext)
|
|
return GetBlockTime();
|
|
pindex = pindex->pnext;
|
|
}
|
|
return pindex->GetMedianTimePast();
|
|
}
|
|
|
|
/**
|
|
* Returns true if there are nRequired or more blocks of minVersion or above
|
|
* in the last nToCheck blocks, starting at pstart and going backwards.
|
|
*/
|
|
static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart,
|
|
unsigned int nRequired, unsigned int nToCheck);
|
|
|
|
|
|
bool IsProofOfWork() const
|
|
{
|
|
return !(nFlags & BLOCK_PROOF_OF_STAKE);
|
|
}
|
|
|
|
bool IsProofOfStake() const
|
|
{
|
|
return (nFlags & BLOCK_PROOF_OF_STAKE);
|
|
}
|
|
|
|
void SetProofOfStake()
|
|
{
|
|
nFlags |= BLOCK_PROOF_OF_STAKE;
|
|
}
|
|
|
|
unsigned int GetStakeEntropyBit() const
|
|
{
|
|
return ((nFlags & BLOCK_STAKE_ENTROPY) >> 1);
|
|
}
|
|
|
|
bool SetStakeEntropyBit(unsigned int nEntropyBit)
|
|
{
|
|
if (nEntropyBit > 1)
|
|
return false;
|
|
nFlags |= (nEntropyBit? BLOCK_STAKE_ENTROPY : 0);
|
|
return true;
|
|
}
|
|
|
|
bool GeneratedStakeModifier() const
|
|
{
|
|
return (nFlags & BLOCK_STAKE_MODIFIER);
|
|
}
|
|
|
|
void SetStakeModifier(uint64_t nModifier, bool fGeneratedStakeModifier)
|
|
{
|
|
nStakeModifier = nModifier;
|
|
if (fGeneratedStakeModifier)
|
|
nFlags |= BLOCK_STAKE_MODIFIER;
|
|
}
|
|
|
|
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" PRIx64 ", nStakeModifierChecksum=%08x, hashProofOfStake=%s, prevoutStake=(%s), nStakeTime=%d merkle=%s, hashBlock=%s)",
|
|
pprev, pnext, nFile, nBlockPos, nHeight,
|
|
FormatMoney(nMint).c_str(), FormatMoney(nMoneySupply).c_str(),
|
|
GeneratedStakeModifier() ? "MOD" : "-", GetStakeEntropyBit(), IsProofOfStake()? "PoS" : "PoW",
|
|
nStakeModifier, nStakeModifierChecksum,
|
|
hashProofOfStake.ToString().c_str(),
|
|
prevoutStake.ToString().c_str(), nStakeTime,
|
|
hashMerkleRoot.ToString().c_str(),
|
|
GetBlockHash().ToString().c_str());
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/** Used to marshal pointers into hashes for db storage. */
|
|
class CDiskBlockIndex : public CBlockIndex
|
|
{
|
|
private:
|
|
uint256 blockHash;
|
|
|
|
public:
|
|
uint256 hashPrev;
|
|
uint256 hashNext;
|
|
|
|
// When true, nChainTrust is included in the on-disk serialization.
|
|
// Set by LoadBlockIndex based on the DB format version before deserializing.
|
|
static bool fSerializeChainTrust;
|
|
|
|
CDiskBlockIndex()
|
|
{
|
|
hashPrev = 0;
|
|
hashNext = 0;
|
|
blockHash = 0;
|
|
}
|
|
|
|
explicit CDiskBlockIndex(CBlockIndex* pindex) : CBlockIndex(*pindex)
|
|
{
|
|
hashPrev = (pprev ? pprev->GetBlockHash() : 0);
|
|
hashNext = (pnext ? pnext->GetBlockHash() : 0);
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
if (!(nType & SER_GETHASH))
|
|
READWRITE(nVersion);
|
|
|
|
READWRITE(hashNext);
|
|
READWRITE(nFile);
|
|
READWRITE(nBlockPos);
|
|
READWRITE(nHeight);
|
|
READWRITE(nMint);
|
|
READWRITE(nMoneySupply);
|
|
READWRITE(nFlags);
|
|
READWRITE(nStakeModifier);
|
|
if (IsProofOfStake())
|
|
{
|
|
READWRITE(prevoutStake);
|
|
READWRITE(nStakeTime);
|
|
READWRITE(hashProofOfStake);
|
|
}
|
|
else if (fRead)
|
|
{
|
|
const_cast<CDiskBlockIndex*>(this)->prevoutStake.SetNull();
|
|
const_cast<CDiskBlockIndex*>(this)->nStakeTime = 0;
|
|
const_cast<CDiskBlockIndex*>(this)->hashProofOfStake = 0;
|
|
}
|
|
|
|
// block header
|
|
READWRITE(this->nVersion);
|
|
READWRITE(hashPrev);
|
|
READWRITE(hashMerkleRoot);
|
|
READWRITE(nTime);
|
|
READWRITE(nBits);
|
|
READWRITE(nNonce);
|
|
READWRITE(blockHash);
|
|
|
|
// DB format v2+: persist chain trust to skip expensive recalculation on startup
|
|
if (fSerializeChainTrust)
|
|
READWRITE(nChainTrust);
|
|
)
|
|
|
|
uint256 GetBlockHash() const
|
|
{
|
|
if (fUseFastIndex && (nTime < GetAdjustedTime() - 24 * 60 * 60) && blockHash != 0)
|
|
return blockHash;
|
|
|
|
CBlock block;
|
|
block.nVersion = nVersion;
|
|
block.hashPrevBlock = hashPrev;
|
|
block.hashMerkleRoot = hashMerkleRoot;
|
|
block.nTime = nTime;
|
|
block.nBits = nBits;
|
|
block.nNonce = nNonce;
|
|
|
|
const_cast<CDiskBlockIndex*>(this)->blockHash = block.GetHash();
|
|
|
|
return blockHash;
|
|
}
|
|
|
|
std::string ToString() const
|
|
{
|
|
std::string str = "CDiskBlockIndex(";
|
|
str += CBlockIndex::ToString();
|
|
str += strprintf("\n hashBlock=%s, hashPrev=%s, hashNext=%s)",
|
|
GetBlockHash().ToString().c_str(),
|
|
hashPrev.ToString().c_str(),
|
|
hashNext.ToString().c_str());
|
|
return str;
|
|
}
|
|
|
|
void print() const
|
|
{
|
|
printf("%s\n", ToString().c_str());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Describes a place in the block chain to another node such that if the
|
|
* other node doesn't have the same branch, it can find a recent common trunk.
|
|
* The further back it is, the further before the fork it may be.
|
|
*/
|
|
class CBlockLocator
|
|
{
|
|
protected:
|
|
std::vector<uint256> vHave;
|
|
public:
|
|
|
|
CBlockLocator()
|
|
{
|
|
}
|
|
|
|
explicit CBlockLocator(const CBlockIndex* pindex)
|
|
{
|
|
Set(pindex);
|
|
}
|
|
|
|
explicit CBlockLocator(uint256 hashBlock)
|
|
{
|
|
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock);
|
|
if (mi != mapBlockIndex.end())
|
|
Set((*mi).second);
|
|
}
|
|
|
|
CBlockLocator(std::vector<uint256> vHaveIn) : vHave(std::move(vHaveIn)) {}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
if (!(nType & SER_GETHASH))
|
|
READWRITE(nVersion);
|
|
READWRITE(vHave);
|
|
)
|
|
|
|
void SetNull()
|
|
{
|
|
vHave.clear();
|
|
}
|
|
|
|
bool IsNull()
|
|
{
|
|
return vHave.empty();
|
|
}
|
|
|
|
// Return the first hash in the locator (peer's tip), or 0 if empty
|
|
uint256 GetTipHash() const
|
|
{
|
|
return vHave.empty() ? uint256(0) : vHave[0];
|
|
}
|
|
|
|
void Set(const CBlockIndex* pindex)
|
|
{
|
|
vHave.clear();
|
|
int nStep = 1;
|
|
while (pindex)
|
|
{
|
|
vHave.push_back(pindex->GetBlockHash());
|
|
|
|
// Exponentially larger steps back
|
|
for (int i = 0; pindex && i < nStep; i++)
|
|
pindex = pindex->pprev;
|
|
if (vHave.size() > 10)
|
|
nStep *= 2;
|
|
}
|
|
vHave.push_back((!fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet));
|
|
}
|
|
|
|
int GetDistanceBack()
|
|
{
|
|
// Retrace how far back it was in the sender's branch
|
|
int nDistance = 0;
|
|
int nStep = 1;
|
|
for (const uint256& hash : vHave)
|
|
{
|
|
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
CBlockIndex* pindex = (*mi).second;
|
|
if (pindex->IsInMainChain())
|
|
return nDistance;
|
|
}
|
|
nDistance += nStep;
|
|
if (nDistance > 10)
|
|
nStep *= 2;
|
|
}
|
|
return nDistance;
|
|
}
|
|
|
|
CBlockIndex* GetBlockIndex()
|
|
{
|
|
// Find the first block the caller has in the main chain
|
|
for (const uint256& hash : vHave)
|
|
{
|
|
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
CBlockIndex* pindex = (*mi).second;
|
|
if (pindex->IsInMainChain())
|
|
return pindex;
|
|
}
|
|
}
|
|
return pindexGenesisBlock;
|
|
}
|
|
|
|
uint256 GetBlockHash()
|
|
{
|
|
// Find the first block the caller has in the main chain
|
|
for (const uint256& hash : vHave)
|
|
{
|
|
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
CBlockIndex* pindex = (*mi).second;
|
|
if (pindex->IsInMainChain())
|
|
return hash;
|
|
}
|
|
}
|
|
return (!fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet);
|
|
}
|
|
|
|
int GetHeight()
|
|
{
|
|
CBlockIndex* pindex = GetBlockIndex();
|
|
if (!pindex)
|
|
return 0;
|
|
return pindex->nHeight;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CTxMemPool
|
|
{
|
|
public:
|
|
mutable CCriticalSection cs;
|
|
std::map<uint256, CTransaction> mapTx;
|
|
std::map<COutPoint, CInPoint> mapNextTx;
|
|
|
|
bool accept(CTxDBBase& txdb, CTransaction &tx,
|
|
bool fCheckInputs, bool* pfMissingInputs);
|
|
bool addUnchecked(const uint256& hash, CTransaction &tx);
|
|
bool remove(const CTransaction &tx, bool fRecursive = false);
|
|
bool removeConflicts(const CTransaction &tx);
|
|
void clear();
|
|
void queryHashes(std::vector<uint256>& vtxid);
|
|
|
|
unsigned long size()
|
|
{
|
|
LOCK(cs);
|
|
return mapTx.size();
|
|
}
|
|
|
|
bool exists(uint256 hash)
|
|
{
|
|
LOCK(cs);
|
|
return (mapTx.count(hash) != 0);
|
|
}
|
|
|
|
CTransaction& lookup(uint256 hash)
|
|
{
|
|
return mapTx[hash];
|
|
}
|
|
};
|
|
|
|
extern CTxMemPool mempool;
|
|
extern CScriptVerifyCache scriptVerifyCache;
|
|
|
|
/**
|
|
* Compact block relay for Tor-only networks.
|
|
*
|
|
* Instead of sending a full block, send the header + short transaction IDs.
|
|
* The receiver reconstructs the block from its mempool. For PoS blocks with
|
|
* 0-2 transactions (the common case), the coinstake is always prefilled, so
|
|
* the compact block IS the complete block — no extra round-trip needed.
|
|
*/
|
|
|
|
/** Short transaction ID: first 6 bytes of SipHash(txid) */
|
|
static inline uint64_t GetShortTxId(const uint256& txhash, uint64_t nonce)
|
|
{
|
|
// Simple short ID: XOR txhash prefix with nonce
|
|
uint64_t id = 0;
|
|
memcpy(&id, txhash.begin(), 6); // first 6 bytes
|
|
id ^= nonce;
|
|
return id & 0xFFFFFFFFFFFFULL; // mask to 48 bits
|
|
}
|
|
|
|
class CCompactBlock
|
|
{
|
|
public:
|
|
// Block header fields
|
|
int nVersion;
|
|
uint256 hashPrevBlock;
|
|
uint256 hashMerkleRoot;
|
|
unsigned int nTime;
|
|
unsigned int nBits;
|
|
unsigned int nNonce;
|
|
std::vector<unsigned char> vchBlockSig;
|
|
|
|
// Compact block data
|
|
uint64_t nShortIdNonce; // nonce for short ID calculation
|
|
std::vector<uint64_t> vShortTxIds; // short IDs for non-prefilled txs
|
|
std::vector<std::pair<uint16_t, CTransaction>> vPrefilledTxn; // index + full tx
|
|
|
|
CCompactBlock() : nVersion(0), nTime(0), nBits(0), nNonce(0), nShortIdNonce(0) {}
|
|
|
|
// Construct from a full block: prefill coinbase + coinstake, short-ID the rest
|
|
CCompactBlock(const CBlock& block)
|
|
{
|
|
nVersion = block.nVersion;
|
|
hashPrevBlock = block.hashPrevBlock;
|
|
hashMerkleRoot = block.hashMerkleRoot;
|
|
nTime = block.nTime;
|
|
nBits = block.nBits;
|
|
nNonce = block.nNonce;
|
|
vchBlockSig = block.vchBlockSig;
|
|
nShortIdNonce = GetRand(std::numeric_limits<uint64_t>::max());
|
|
|
|
for (uint16_t i = 0; i < block.vtx.size(); i++)
|
|
{
|
|
if (i <= 1) {
|
|
// Always prefill coinbase (idx 0) and coinstake (idx 1)
|
|
vPrefilledTxn.push_back({i, block.vtx[i]});
|
|
} else {
|
|
vShortTxIds.push_back(GetShortTxId(block.vtx[i].GetHash(), nShortIdNonce));
|
|
}
|
|
}
|
|
}
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(nVersion);
|
|
READWRITE(hashPrevBlock);
|
|
READWRITE(hashMerkleRoot);
|
|
READWRITE(nTime);
|
|
READWRITE(nBits);
|
|
READWRITE(nNonce);
|
|
READWRITE(vchBlockSig);
|
|
READWRITE(nShortIdNonce);
|
|
READWRITE(vShortTxIds);
|
|
READWRITE(vPrefilledTxn);
|
|
)
|
|
|
|
uint256 GetBlockHash() const
|
|
{
|
|
CBlock hdr;
|
|
hdr.nVersion = nVersion;
|
|
hdr.hashPrevBlock = hashPrevBlock;
|
|
hdr.hashMerkleRoot = hashMerkleRoot;
|
|
hdr.nTime = nTime;
|
|
hdr.nBits = nBits;
|
|
hdr.nNonce = nNonce;
|
|
return hdr.GetHash();
|
|
}
|
|
};
|
|
|
|
class CBlockTxnRequest
|
|
{
|
|
public:
|
|
uint256 blockhash;
|
|
std::vector<uint16_t> vIndex; // indices of missing transactions
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(blockhash);
|
|
READWRITE(vIndex);
|
|
)
|
|
};
|
|
|
|
class CBlockTxnResponse
|
|
{
|
|
public:
|
|
uint256 blockhash;
|
|
std::vector<CTransaction> vTxn;
|
|
|
|
IMPLEMENT_SERIALIZE
|
|
(
|
|
READWRITE(blockhash);
|
|
READWRITE(vTxn);
|
|
)
|
|
};
|
|
|
|
/**
|
|
* Closure representing one script check for parallel verification.
|
|
* Captures everything needed to call VerifyScript independently.
|
|
*/
|
|
class CScriptCheck
|
|
{
|
|
private:
|
|
CScript scriptPubKey;
|
|
CScript scriptSig;
|
|
const CTransaction* ptxTo;
|
|
unsigned int nIn;
|
|
int nHashType;
|
|
|
|
public:
|
|
CScriptCheck() : ptxTo(nullptr), nIn(0), nHashType(0) {}
|
|
|
|
CScriptCheck(const CScript& scriptPubKeyIn, const CScript& scriptSigIn,
|
|
const CTransaction& txToIn, unsigned int nInIn, int nHashTypeIn)
|
|
: scriptPubKey(scriptPubKeyIn), scriptSig(scriptSigIn),
|
|
ptxTo(&txToIn), nIn(nInIn), nHashType(nHashTypeIn) {}
|
|
|
|
bool operator()()
|
|
{
|
|
if (!ptxTo)
|
|
return false;
|
|
if (!VerifyScript(scriptSig, scriptPubKey, *ptxTo, nIn, nHashType))
|
|
return false;
|
|
scriptVerifyCache.Set(ptxTo->GetHash(), nIn);
|
|
return true;
|
|
}
|
|
|
|
void swap(CScriptCheck& other)
|
|
{
|
|
std::swap(scriptPubKey, other.scriptPubKey);
|
|
std::swap(scriptSig, other.scriptSig);
|
|
std::swap(ptxTo, other.ptxTo);
|
|
std::swap(nIn, other.nIn);
|
|
std::swap(nHashType, other.nHashType);
|
|
}
|
|
};
|
|
|
|
extern std::unique_ptr<CCheckQueue<CScriptCheck>> pScriptCheckQueue;
|
|
|
|
#endif
|