Browse Source

Implement transaction expiry for Overwinter

pull/4/head
Jay Graber 6 years ago
parent
commit
9bb37bf0d5
  1. 11
      src/bitcoin-tx.cpp
  2. 2
      src/consensus/consensus.h
  3. 19
      src/main.cpp
  4. 9
      src/main.h
  5. 4
      src/miner.cpp
  6. 3
      src/primitives/transaction.h
  7. 13
      src/rpcrawtransaction.cpp
  8. 18
      src/txmempool.cpp
  9. 1
      src/txmempool.h
  10. 14
      src/wallet/rpcwallet.cpp
  11. 15
      src/wallet/wallet.cpp

11
src/bitcoin-tx.cpp

@ -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);

2
src/consensus/consensus.h

@ -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

@ -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

9
src/main.h

@ -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.
*

4
src/miner.cpp

@ -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

3
src/primitives/transaction.h

@ -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");

13
src/rpcrawtransaction.cpp

@ -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];
@ -497,7 +506,7 @@ UniValue decoderawtransaction(const UniValue& params, bool fHelp)
"\nResult:\n"
"{\n"
" \"txid\" : \"id\", (string) The transaction id\n"
" \"overwintered\" : bool (boolean) The Overwintered flag\n"
" \"overwintered\" : bool (boolean) The Overwintered flag\n"
" \"version\" : n, (numeric) The version\n"
" \"versiongroupid\": \"hex\" (string, optional) The version group id (Overwintered txs)\n"
" \"locktime\" : ttt, (numeric) The lock time\n"

18
src/txmempool.cpp

@ -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.
*/

1
src/txmempool.h

@ -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);

14
src/wallet/rpcwallet.cpp

@ -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();

15
src/wallet/wallet.cpp

@ -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,9 +2575,20 @@ 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.
//
// However because of a off-by-one-error in previous versions we need to

Loading…
Cancel
Save