Implement transaction expiry for Overwinter
This commit is contained in:
parent
c5904fb2a4
commit
9bb37bf0d5
|
@ -164,6 +164,15 @@ static void MutateTxVersion(CMutableTransaction& tx, const string& cmdVal)
|
|||
tx.nVersion = (int) newVersion;
|
||||
}
|
||||
|
||||
static void MutateTxExpiry(CMutableTransaction& tx, const string& cmdVal)
|
||||
{
|
||||
int64_t newExpiry = atoi64(cmdVal);
|
||||
if (newExpiry >= TX_EXPIRY_HEIGHT_THRESHOLD) {
|
||||
throw runtime_error("Invalid TX expiry requested");
|
||||
}
|
||||
tx.nExpiryHeight = (int) newExpiry;
|
||||
}
|
||||
|
||||
static void MutateTxLocktime(CMutableTransaction& tx, const string& cmdVal)
|
||||
{
|
||||
int64_t newLocktime = atoi64(cmdVal);
|
||||
|
@ -503,6 +512,8 @@ static void MutateTx(CMutableTransaction& tx, const string& command,
|
|||
MutateTxVersion(tx, commandVal);
|
||||
else if (command == "locktime")
|
||||
MutateTxLocktime(tx, commandVal);
|
||||
else if (command == "expiry")
|
||||
MutateTxExpiry(tx, commandVal);
|
||||
|
||||
else if (command == "delin")
|
||||
MutateTxDelInput(tx, commandVal);
|
||||
|
|
|
@ -22,6 +22,8 @@ static const unsigned int MAX_BLOCK_SIGOPS = 20000;
|
|||
static const unsigned int MAX_TX_SIZE = 100000;
|
||||
/** Coinbase transaction outputs can only be spent after this number of new blocks (network rule) */
|
||||
static const int COINBASE_MATURITY = 100;
|
||||
/** The minimum value which is invalid for expiry height, used by CTransaction and CMutableTransaction */
|
||||
static constexpr uint32_t TX_EXPIRY_HEIGHT_THRESHOLD = 500000000;
|
||||
|
||||
/** Flags for LockTime() */
|
||||
enum {
|
||||
|
|
19
src/main.cpp
19
src/main.cpp
|
@ -73,6 +73,8 @@ size_t nCoinCacheUsage = 5000 * 300;
|
|||
uint64_t nPruneTarget = 0;
|
||||
bool fAlerts = DEFAULT_ALERTS;
|
||||
|
||||
unsigned int expiryDelta = DEFAULT_TX_EXPIRY_DELTA;
|
||||
|
||||
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
|
||||
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
|
||||
|
||||
|
@ -718,6 +720,14 @@ bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool IsExpiredTx(const CTransaction &tx, int nBlockHeight)
|
||||
{
|
||||
if (tx.nExpiryHeight == 0 || tx.IsCoinBase()) {
|
||||
return false;
|
||||
}
|
||||
return static_cast<uint32_t>(nBlockHeight) > tx.nExpiryHeight;
|
||||
}
|
||||
|
||||
bool CheckFinalTx(const CTransaction &tx, int flags)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
|
@ -884,6 +894,11 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state,
|
|||
return state.DoS(dosLevel, error("ContextualCheckTransaction: overwinter is active"),
|
||||
REJECT_INVALID, "tx-overwinter-active");
|
||||
}
|
||||
|
||||
// Check that all transactions are unexpired
|
||||
if (IsExpiredTx(tx, nHeight)) {
|
||||
return state.DoS(dosLevel, error("ContextualCheckTransaction(): transaction is expired"), REJECT_INVALID, "tx-overwinter-expired");
|
||||
}
|
||||
}
|
||||
|
||||
if (!(tx.IsCoinBase() || tx.vjoinsplit.empty())) {
|
||||
|
@ -2659,6 +2674,10 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
|
|||
// Remove conflicting transactions from the mempool.
|
||||
list<CTransaction> txConflicted;
|
||||
mempool.removeForBlock(pblock->vtx, pindexNew->nHeight, txConflicted, !IsInitialBlockDownload());
|
||||
|
||||
// Remove transactions that expire at new block height from mempool
|
||||
mempool.removeExpired(pindexNew->nHeight);
|
||||
|
||||
// Update chainActive & related variables.
|
||||
UpdateTip(pindexNew);
|
||||
// Tell wallet about transactions that went from mempool
|
||||
|
|
|
@ -68,6 +68,8 @@ static const unsigned int MAX_STANDARD_TX_SIGOPS = MAX_BLOCK_SIGOPS/5;
|
|||
static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 100;
|
||||
/** Default for -maxorphantx, maximum number of orphan transactions kept in memory */
|
||||
static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100;
|
||||
/** Default for -txexpirydelta, in number of blocks */
|
||||
static const unsigned int DEFAULT_TX_EXPIRY_DELTA = 20;
|
||||
/** The maximum size of a blk?????.dat file (since 0.8) */
|
||||
static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB
|
||||
/** The pre-allocation chunk size for blk?????.dat files (since 0.8) */
|
||||
|
@ -110,6 +112,7 @@ struct BlockHasher
|
|||
size_t operator()(const uint256& hash) const { return hash.GetCheapHash(); }
|
||||
};
|
||||
|
||||
extern unsigned int expiryDelta;
|
||||
extern CScript COINBASE_FLAGS;
|
||||
extern CCriticalSection cs_main;
|
||||
extern CTxMemPool mempool;
|
||||
|
@ -371,6 +374,12 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
|
|||
*/
|
||||
bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime);
|
||||
|
||||
/**
|
||||
* Check if transaction is expired and can be included in a block with the
|
||||
* specified height. Consensus critical.
|
||||
*/
|
||||
bool IsExpiredTx(const CTransaction &tx, int nBlockHeight);
|
||||
|
||||
/**
|
||||
* Check if transaction will be final in the next block to be created.
|
||||
*
|
||||
|
|
|
@ -166,7 +166,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
|
|||
? nMedianTimePast
|
||||
: pblock->GetBlockTime();
|
||||
|
||||
if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight, nLockTimeCutoff))
|
||||
if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight, nLockTimeCutoff) || IsExpiredTx(tx, nHeight))
|
||||
continue;
|
||||
|
||||
COrphan* porphan = NULL;
|
||||
|
@ -345,6 +345,8 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
|
|||
txNew.vout.resize(1);
|
||||
txNew.vout[0].scriptPubKey = scriptPubKeyIn;
|
||||
txNew.vout[0].nValue = GetBlockSubsidy(nHeight, chainparams.GetConsensus());
|
||||
// Set to 0 so expiry height does not apply to coinbase txs
|
||||
txNew.nExpiryHeight = 0;
|
||||
|
||||
if ((nHeight > 0) && (nHeight <= chainparams.GetConsensus().GetLastFoundersRewardBlockHeight())) {
|
||||
// Founders reward is 20% of the block subsidy
|
||||
|
|
|
@ -304,9 +304,6 @@ public:
|
|||
std::string ToString() const;
|
||||
};
|
||||
|
||||
// The maximum value which is valid for expiry height, used by CTransaction and CMutableTransaction
|
||||
static constexpr uint32_t TX_EXPIRY_HEIGHT_THRESHOLD = 500000000;
|
||||
|
||||
// Overwinter version group id
|
||||
static constexpr uint32_t OVERWINTER_VERSION_GROUP_ID = 0x03C48270;
|
||||
static_assert(OVERWINTER_VERSION_GROUP_ID != 0, "version group id must be non-zero as specified in ZIP 202");
|
||||
|
|
|
@ -198,6 +198,7 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp)
|
|||
" \"txid\" : \"id\", (string) The transaction id (same as provided)\n"
|
||||
" \"version\" : n, (numeric) The version\n"
|
||||
" \"locktime\" : ttt, (numeric) The lock time\n"
|
||||
" \"expiryheight\" : ttt, (numeric, optional) The block height after which the transaction expires\n"
|
||||
" \"vin\" : [ (array of json objects)\n"
|
||||
" {\n"
|
||||
" \"txid\": \"id\", (string) The transaction id\n"
|
||||
|
@ -443,8 +444,16 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp)
|
|||
UniValue inputs = params[0].get_array();
|
||||
UniValue sendTo = params[1].get_obj();
|
||||
|
||||
int nextBlockHeight = chainActive.Height() + 1;
|
||||
CMutableTransaction rawTx = CreateNewContextualCMutableTransaction(
|
||||
Params().GetConsensus(), chainActive.Height() + 1);
|
||||
Params().GetConsensus(), nextBlockHeight);
|
||||
|
||||
if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
|
||||
rawTx.nExpiryHeight = nextBlockHeight + expiryDelta;
|
||||
if (rawTx.nExpiryHeight >= TX_EXPIRY_HEIGHT_THRESHOLD){
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "nExpiryHeight must be less than TX_EXPIRY_HEIGHT_THRESHOLD.");
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t idx = 0; idx < inputs.size(); idx++) {
|
||||
const UniValue& input = inputs[idx];
|
||||
|
|
|
@ -256,6 +256,24 @@ void CTxMemPool::removeConflicts(const CTransaction &tx, std::list<CTransaction>
|
|||
}
|
||||
}
|
||||
|
||||
void CTxMemPool::removeExpired(unsigned int nBlockHeight)
|
||||
{
|
||||
// Remove expired txs from the mempool
|
||||
LOCK(cs);
|
||||
list<CTransaction> transactionsToRemove;
|
||||
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++)
|
||||
{
|
||||
const CTransaction& tx = it->GetTx();
|
||||
if (IsExpiredTx(tx, nBlockHeight)) {
|
||||
transactionsToRemove.push_back(tx);
|
||||
}
|
||||
}
|
||||
for (const CTransaction& tx : transactionsToRemove) {
|
||||
list<CTransaction> removed;
|
||||
remove(tx, removed, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a block is connected. Removes from mempool and updates the miner fee estimator.
|
||||
*/
|
||||
|
|
|
@ -168,6 +168,7 @@ public:
|
|||
void removeWithAnchor(const uint256 &invalidRoot);
|
||||
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);
|
||||
void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed);
|
||||
void removeExpired(unsigned int nBlockHeight);
|
||||
void removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
|
||||
std::list<CTransaction>& conflicts, bool fCurrentEstimate = true);
|
||||
void removeWithoutBranchId(uint32_t nMemPoolBranchId);
|
||||
|
|
|
@ -86,6 +86,7 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
|
|||
entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
|
||||
entry.push_back(Pair("blockindex", wtx.nIndex));
|
||||
entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
|
||||
entry.push_back(Pair("expiryheight", (int64_t)wtx.nExpiryHeight));
|
||||
}
|
||||
uint256 hash = wtx.GetHash();
|
||||
entry.push_back(Pair("txid", hash.GetHex()));
|
||||
|
@ -3534,11 +3535,15 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
UniValue contextInfo = o;
|
||||
|
||||
// Contextual transaction we will build on
|
||||
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), chainActive.Height() + 1);
|
||||
int nextBlockHeight = chainActive.Height() + 1;
|
||||
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight);
|
||||
bool isShielded = !fromTaddr || zaddrRecipients.size() > 0;
|
||||
if (contextualTx.nVersion == 1 && isShielded) {
|
||||
contextualTx.nVersion = 2; // Tx format should support vjoinsplits
|
||||
}
|
||||
if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
|
||||
contextualTx.nExpiryHeight = nextBlockHeight + expiryDelta;
|
||||
}
|
||||
|
||||
// Create operation and add to global queue
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
|
@ -3725,12 +3730,15 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
|
|||
contextInfo.push_back(Pair("fee", ValueFromAmount(nFee)));
|
||||
|
||||
// Contextual transaction we will build on
|
||||
int nextBlockHeight = chainActive.Height() + 1;
|
||||
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
|
||||
Params().GetConsensus(),
|
||||
chainActive.Height() + 1);
|
||||
Params().GetConsensus(), nextBlockHeight);
|
||||
if (contextualTx.nVersion == 1) {
|
||||
contextualTx.nVersion = 2; // Tx format should support vjoinsplits
|
||||
}
|
||||
if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
|
||||
contextualTx.nExpiryHeight = nextBlockHeight + expiryDelta;
|
||||
}
|
||||
|
||||
// Create operation and add to global queue
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "coincontrol.h"
|
||||
#include "consensus/upgrades.h"
|
||||
#include "consensus/validation.h"
|
||||
#include "consensus/consensus.h"
|
||||
#include "init.h"
|
||||
#include "main.h"
|
||||
#include "net.h"
|
||||
|
@ -2523,6 +2524,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
|
|||
|
||||
CReserveKey reservekey(this);
|
||||
CWalletTx wtx;
|
||||
|
||||
if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false))
|
||||
return false;
|
||||
|
||||
|
@ -2573,8 +2575,19 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
|
|||
|
||||
wtxNew.fTimeReceivedIsTxTime = true;
|
||||
wtxNew.BindWallet(this);
|
||||
int nextBlockHeight = chainActive.Height() + 1;
|
||||
CMutableTransaction txNew = CreateNewContextualCMutableTransaction(
|
||||
Params().GetConsensus(), chainActive.Height() + 1);
|
||||
Params().GetConsensus(), nextBlockHeight);
|
||||
|
||||
// Activates after Overwinter network upgrade
|
||||
// Set nExpiryHeight to expiryDelta (default 20) blocks past current block height
|
||||
if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
|
||||
if (nextBlockHeight + expiryDelta >= TX_EXPIRY_HEIGHT_THRESHOLD){
|
||||
strFailReason = _("nExpiryHeight must be less than TX_EXPIRY_HEIGHT_THRESHOLD.");
|
||||
} else {
|
||||
txNew.nExpiryHeight = nextBlockHeight + expiryDelta;
|
||||
}
|
||||
}
|
||||
|
||||
// Discourage fee sniping.
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue