InstantSend overhaul (#1288)
* Multi-quorum InstantSend, complete refactoring + cleanup for IS and partial protobump * more changes: - allow InstantSend tx to have 10 inputs max - store many unique tx hashes in mapVotedOutpoints - more checks in AcceptToMemoryPoolWorker (moved from ProcessMessage + CTxLockRequest(tx).IsValid() ) * More changes: - let multiple lock candidates compete for votes - fail to vote on the same outpoint twice early * More changes: - notify CInstantSend on UpdatedBlockTip -> remove cs_main from CheckAndRemove() - notify CInstantSend on SyncTransaction -> count expiration block starting from the block corresponding tx was confirmed instead of the block lock candidate/vote was created - fixed few locks * add comments about nConfirmedHeight * Fix "Block vs Lock" edge case * Fix "Block vs Lock" edge case, p2 * Fix issues: - fix logic for locking inputs and notifying - see UpdateLockedTransaction, TryToFinalizeLockCandidate - add missing hash inserting in ProcessTxLockVote - add nMaxBlocks param to ResolveConflicts to limit max depth allowed to disconnect blocks recursively - fix false positive mempool conflict - add missing mutex locks - fix fRequireUnspent logic in CTxLockRequest::IsValid
This commit is contained in:
parent
9c5db04953
commit
60409df822
@ -77,7 +77,6 @@ public:
|
||||
consensus.nMasternodePaymentsIncreaseBlock = 158000; // actual historical value
|
||||
consensus.nMasternodePaymentsIncreasePeriod = 576*30; // 17280 - actual historical value
|
||||
consensus.nInstantSendKeepLock = 24;
|
||||
consensus.nInstantSendReprocessBlocks = 15;
|
||||
consensus.nBudgetPaymentsStartBlock = 328008; // actual historical value
|
||||
consensus.nBudgetPaymentsCycleBlocks = 16616; // ~(60*24*30)/2.6, actual number of blocks per month is 200700 / 12 = 16725
|
||||
consensus.nBudgetPaymentsWindowBlocks = 100;
|
||||
@ -201,7 +200,6 @@ public:
|
||||
consensus.nMasternodePaymentsIncreaseBlock = 46000;
|
||||
consensus.nMasternodePaymentsIncreasePeriod = 576;
|
||||
consensus.nInstantSendKeepLock = 6;
|
||||
consensus.nInstantSendReprocessBlocks = 4;
|
||||
consensus.nBudgetPaymentsStartBlock = 60000;
|
||||
consensus.nBudgetPaymentsCycleBlocks = 50;
|
||||
consensus.nBudgetPaymentsWindowBlocks = 10;
|
||||
@ -308,7 +306,6 @@ public:
|
||||
consensus.nMasternodePaymentsIncreaseBlock = 350;
|
||||
consensus.nMasternodePaymentsIncreasePeriod = 10;
|
||||
consensus.nInstantSendKeepLock = 6;
|
||||
consensus.nInstantSendReprocessBlocks = 4;
|
||||
consensus.nBudgetPaymentsStartBlock = 1000;
|
||||
consensus.nBudgetPaymentsCycleBlocks = 50;
|
||||
consensus.nBudgetPaymentsWindowBlocks = 10;
|
||||
|
@ -41,7 +41,6 @@ struct Params {
|
||||
int nMasternodePaymentsIncreaseBlock;
|
||||
int nMasternodePaymentsIncreasePeriod; // in blocks
|
||||
int nInstantSendKeepLock; // in blocks
|
||||
int nInstantSendReprocessBlocks;
|
||||
int nBudgetPaymentsStartBlock;
|
||||
int nBudgetPaymentsCycleBlocks;
|
||||
int nBudgetPaymentsWindowBlocks;
|
||||
|
@ -2490,7 +2490,7 @@ void ThreadCheckDarkSendPool()
|
||||
mnodeman.ProcessMasternodeConnections();
|
||||
mnodeman.CheckAndRemove();
|
||||
mnpayments.CheckAndRemove();
|
||||
CleanTxLockCandidates();
|
||||
instantsend.CheckAndRemove();
|
||||
}
|
||||
|
||||
darkSendPool.CheckTimeout();
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "dsnotificationinterface.h"
|
||||
#include "darksend.h"
|
||||
#include "instantx.h"
|
||||
#include "governance.h"
|
||||
#include "masternodeman.h"
|
||||
#include "masternode-payments.h"
|
||||
@ -21,7 +22,13 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindex)
|
||||
{
|
||||
mnodeman.UpdatedBlockTip(pindex);
|
||||
darkSendPool.UpdatedBlockTip(pindex);
|
||||
instantsend.UpdatedBlockTip(pindex);
|
||||
mnpayments.UpdatedBlockTip(pindex);
|
||||
governance.UpdatedBlockTip(pindex);
|
||||
masternodeSync.UpdatedBlockTip(pindex);
|
||||
}
|
||||
|
||||
void CDSNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlock *pblock)
|
||||
{
|
||||
instantsend.SyncTransaction(tx, pblock);
|
||||
}
|
@ -17,6 +17,7 @@ public:
|
||||
protected:
|
||||
// CValidationInterface
|
||||
void UpdatedBlockTip(const CBlockIndex *pindex);
|
||||
void SyncTransaction(const CTransaction &tx, const CBlock *pblock);
|
||||
|
||||
private:
|
||||
};
|
||||
|
1263
src/instantx.cpp
1263
src/instantx.cpp
File diff suppressed because it is too large
Load Diff
229
src/instantx.h
229
src/instantx.h
@ -7,9 +7,13 @@
|
||||
#include "net.h"
|
||||
#include "primitives/transaction.h"
|
||||
|
||||
class CTransaction;
|
||||
class CTxLockVote;
|
||||
class COutPointLock;
|
||||
class CTxLockRequest;
|
||||
class CTxLockCandidate;
|
||||
class CInstantSend;
|
||||
|
||||
extern CInstantSend instantsend;
|
||||
|
||||
/*
|
||||
At 15 signatures, 1/2 of the masternode network can be owned by
|
||||
@ -20,106 +24,225 @@ class CTxLockCandidate;
|
||||
### getting 5 of 10 signatures w/ 1000 nodes of 2900
|
||||
(1000/2900.0)**5 = 0.004875397277841433
|
||||
*/
|
||||
static const int INSTANTSEND_SIGNATURES_REQUIRED = 6;
|
||||
static const int INSTANTSEND_SIGNATURES_TOTAL = 10;
|
||||
static const int INSTANTSEND_CONFIRMATIONS_REQUIRED = 6;
|
||||
static const int DEFAULT_INSTANTSEND_DEPTH = 5;
|
||||
|
||||
static const int MIN_INSTANTSEND_PROTO_VERSION = 70204;
|
||||
static const CAmount INSTANTSEND_MIN_FEE = 0.001 * COIN;
|
||||
static const int MIN_INSTANTSEND_PROTO_VERSION = 70205;
|
||||
|
||||
extern bool fEnableInstantSend;
|
||||
extern int nInstantSendDepth;
|
||||
extern int nCompleteTXLocks;
|
||||
|
||||
extern std::map<uint256, CTransaction> mapLockRequestAccepted;
|
||||
extern std::map<uint256, CTransaction> mapLockRequestRejected;
|
||||
extern std::map<uint256, CTxLockVote> mapTxLockVotes;
|
||||
extern std::map<COutPoint, uint256> mapLockedInputs;
|
||||
class CInstantSend
|
||||
{
|
||||
private:
|
||||
static const int ORPHAN_VOTE_SECONDS = 60;
|
||||
|
||||
// Keep track of current block index
|
||||
const CBlockIndex *pCurrentBlockIndex;
|
||||
|
||||
void ProcessMessageInstantSend(CNode* pfrom, std::string& strCommand, CDataStream& vRecv);
|
||||
// maps for AlreadyHave
|
||||
std::map<uint256, CTxLockRequest> mapLockRequestAccepted; // tx hash - tx
|
||||
std::map<uint256, CTxLockRequest> mapLockRequestRejected; // tx hash - tx
|
||||
std::map<uint256, CTxLockVote> mapTxLockVotes; // vote hash - vote
|
||||
std::map<uint256, CTxLockVote> mapTxLockVotesOrphan; // vote hash - vote
|
||||
|
||||
bool IsInstantSendTxValid(const CTransaction& txCandidate);
|
||||
std::map<uint256, CTxLockCandidate> mapTxLockCandidates; // tx hash - lock candidate
|
||||
|
||||
bool ProcessTxLockRequest(CNode* pfrom, const CTransaction &tx);
|
||||
std::map<COutPoint, std::set<uint256> > mapVotedOutpoints; // utxo - tx hash set
|
||||
std::map<COutPoint, uint256> mapLockedOutpoints; // utxo - tx hash
|
||||
|
||||
int64_t CreateTxLockCandidate(const CTransaction &tx);
|
||||
//track masternodes who voted with no txreq (for DOS protection)
|
||||
std::map<COutPoint, int64_t> mapMasternodeOrphanVotes; // mn outpoint - time
|
||||
|
||||
//check if we need to vote on this transaction
|
||||
void CreateTxLockVote(const CTransaction& tx, int nBlockHeight);
|
||||
bool CreateTxLockCandidate(const CTxLockRequest& txLockRequest);
|
||||
void Vote(CTxLockCandidate& txLockCandidate);
|
||||
|
||||
//process consensus vote message
|
||||
bool ProcessTxLockVote(CNode *pnode, CTxLockVote& vote);
|
||||
//process consensus vote message
|
||||
bool ProcessTxLockVote(CNode* pfrom, CTxLockVote& vote);
|
||||
void ProcessOrphanTxLockVotes();
|
||||
bool IsEnoughOrphanVotesForTx(const CTxLockRequest& txLockRequest);
|
||||
bool IsEnoughOrphanVotesForTxAndOutPoint(const uint256& txHash, const COutPoint& outpoint);
|
||||
int64_t GetAverageMasternodeOrphanVoteTime();
|
||||
|
||||
void ProcessOrphanTxLockVotes();
|
||||
void TryToFinalizeLockCandidate(const CTxLockCandidate& txLockCandidate);
|
||||
void LockTransactionInputs(const CTxLockCandidate& txLockCandidate);
|
||||
//update UI and notify external script if any
|
||||
void UpdateLockedTransaction(const CTxLockCandidate& txLockCandidate);
|
||||
bool ResolveConflicts(const CTxLockCandidate& txLockCandidate, int nMaxBlocks);
|
||||
|
||||
//update UI and notify external script if any
|
||||
void UpdateLockedTransaction(const CTransaction& tx, bool fForceNotification = false);
|
||||
bool IsInstantSendReadyToLock(const uint256 &txHash);
|
||||
|
||||
void LockTransactionInputs(const CTransaction& tx);
|
||||
public:
|
||||
CCriticalSection cs_instantsend;
|
||||
|
||||
// if two conflicting locks are approved by the network, they will cancel out
|
||||
bool FindConflictingLocks(const CTransaction& tx);
|
||||
void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv);
|
||||
|
||||
//try to resolve conflicting locks
|
||||
void ResolveConflicts(const CTransaction& tx);
|
||||
bool ProcessTxLockRequest(const CTxLockRequest& txLockRequest);
|
||||
|
||||
// keep transaction locks in memory for an hour
|
||||
void CleanTxLockCandidates();
|
||||
bool AlreadyHave(const uint256& hash);
|
||||
|
||||
// verify if transaction is currently locked
|
||||
bool IsLockedInstandSendTransaction(const uint256 &txHash);
|
||||
void AcceptLockRequest(const CTxLockRequest& txLockRequest);
|
||||
void RejectLockRequest(const CTxLockRequest& txLockRequest);
|
||||
bool HasTxLockRequest(const uint256& txHash);
|
||||
bool GetTxLockRequest(const uint256& txHash, CTxLockRequest& txLockRequestRet);
|
||||
|
||||
// get the actual uber og accepted lock signatures
|
||||
int GetTransactionLockSignatures(const uint256 &txHash);
|
||||
bool GetTxLockVote(const uint256& hash, CTxLockVote& txLockVoteRet);
|
||||
|
||||
// verify if transaction lock timed out
|
||||
bool IsTransactionLockTimedOut(const uint256 &txHash);
|
||||
bool GetLockedOutPointTxHash(const COutPoint& outpoint, uint256& hashRet);
|
||||
|
||||
int64_t GetAverageMasternodeOrphanVoteTime();
|
||||
// verify if transaction is currently locked
|
||||
bool IsLockedInstantSendTransaction(const uint256& txHash);
|
||||
// get the actual uber og accepted lock signatures
|
||||
int GetTransactionLockSignatures(const uint256& txHash);
|
||||
|
||||
// remove expired entries from maps
|
||||
void CheckAndRemove();
|
||||
// verify if transaction lock timed out
|
||||
bool IsTxLockRequestTimedOut(const uint256& txHash);
|
||||
|
||||
void Relay(const uint256& txHash);
|
||||
|
||||
void UpdatedBlockTip(const CBlockIndex *pindex);
|
||||
void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
|
||||
};
|
||||
|
||||
class CTxLockRequest : public CTransaction
|
||||
{
|
||||
private:
|
||||
static const int TIMEOUT_SECONDS = 5 * 60;
|
||||
static const CAmount MIN_FEE = 0.001 * COIN;
|
||||
|
||||
int64_t nTimeCreated;
|
||||
|
||||
public:
|
||||
static const int MAX_INPUTS = 10;
|
||||
|
||||
CTxLockRequest() :
|
||||
CTransaction(),
|
||||
nTimeCreated(GetTime())
|
||||
{}
|
||||
CTxLockRequest(const CTransaction& tx) :
|
||||
CTransaction(tx),
|
||||
nTimeCreated(GetTime())
|
||||
{}
|
||||
|
||||
bool IsValid(bool fRequireUnspent = true) const;
|
||||
CAmount GetMinFee() const;
|
||||
int GetMaxSignatures() const;
|
||||
bool IsTimedOut() const;
|
||||
};
|
||||
|
||||
class CTxLockVote
|
||||
{
|
||||
public:
|
||||
CTxIn vinMasternode;
|
||||
private:
|
||||
uint256 txHash;
|
||||
int nBlockHeight;
|
||||
std::vector<unsigned char> vchMasterNodeSignature;
|
||||
|
||||
COutPoint outpoint;
|
||||
COutPoint outpointMasternode;
|
||||
std::vector<unsigned char> vchMasternodeSignature;
|
||||
// local memory only
|
||||
int64_t nOrphanExpireTime;
|
||||
int nConfirmedHeight; // when corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is -1
|
||||
int64_t nTimeCreated;
|
||||
|
||||
public:
|
||||
CTxLockVote() :
|
||||
txHash(),
|
||||
outpoint(),
|
||||
outpointMasternode(),
|
||||
vchMasternodeSignature(),
|
||||
nConfirmedHeight(-1),
|
||||
nTimeCreated(GetTime())
|
||||
{}
|
||||
|
||||
CTxLockVote(const uint256& txHashIn, const COutPoint& outpointIn, const COutPoint& outpointMasternodeIn) :
|
||||
txHash(txHashIn),
|
||||
outpoint(outpointIn),
|
||||
outpointMasternode(outpointMasternodeIn),
|
||||
vchMasternodeSignature(),
|
||||
nConfirmedHeight(-1),
|
||||
nTimeCreated(GetTime())
|
||||
{}
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||
READWRITE(txHash);
|
||||
READWRITE(vinMasternode);
|
||||
READWRITE(vchMasterNodeSignature);
|
||||
READWRITE(nBlockHeight);
|
||||
READWRITE(outpoint);
|
||||
READWRITE(outpointMasternode);
|
||||
READWRITE(vchMasternodeSignature);
|
||||
}
|
||||
|
||||
uint256 GetHash() const;
|
||||
|
||||
uint256 GetTxHash() const { return txHash; }
|
||||
COutPoint GetOutpoint() const { return outpoint; }
|
||||
COutPoint GetMasternodeOutpoint() const { return outpointMasternode; }
|
||||
int64_t GetTimeCreated() const { return nTimeCreated; }
|
||||
|
||||
bool IsValid(CNode* pnode) const;
|
||||
void SetConfirmedHeight(int nConfirmedHeightIn) { nConfirmedHeight = nConfirmedHeightIn; }
|
||||
bool IsExpired(int nHeight) const;
|
||||
|
||||
bool Sign();
|
||||
bool CheckSignature() const;
|
||||
|
||||
void Relay() const;
|
||||
};
|
||||
|
||||
class COutPointLock
|
||||
{
|
||||
private:
|
||||
COutPoint outpoint; // utxo
|
||||
std::map<COutPoint, CTxLockVote> mapMasternodeVotes; // masternode outpoint - vote
|
||||
|
||||
public:
|
||||
static const int SIGNATURES_REQUIRED = 6;
|
||||
static const int SIGNATURES_TOTAL = 10;
|
||||
|
||||
COutPointLock(const COutPoint& outpointIn) :
|
||||
outpoint(outpointIn),
|
||||
mapMasternodeVotes()
|
||||
{}
|
||||
|
||||
COutPoint GetOutpoint() const { return outpoint; }
|
||||
|
||||
bool AddVote(const CTxLockVote& vote);
|
||||
bool HasMasternodeVoted(const COutPoint& outpointMasternodeIn);
|
||||
int CountVotes() const { return mapMasternodeVotes.size(); }
|
||||
bool IsReady() const { return CountVotes() >= SIGNATURES_REQUIRED; }
|
||||
|
||||
void Relay() const;
|
||||
};
|
||||
|
||||
class CTxLockCandidate
|
||||
{
|
||||
private:
|
||||
int nConfirmedHeight; // when corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is -1
|
||||
|
||||
public:
|
||||
int nBlockHeight;
|
||||
uint256 txHash;
|
||||
std::vector<CTxLockVote> vecTxLockVotes;
|
||||
int nExpirationBlock;
|
||||
int nTimeout;
|
||||
CTxLockCandidate(const CTxLockRequest& txLockRequestIn) :
|
||||
nConfirmedHeight(-1),
|
||||
txLockRequest(txLockRequestIn),
|
||||
mapOutPointLocks()
|
||||
{}
|
||||
|
||||
uint256 GetHash() const { return txHash; }
|
||||
CTxLockRequest txLockRequest;
|
||||
std::map<COutPoint, COutPointLock> mapOutPointLocks;
|
||||
|
||||
bool IsAllVotesValid();
|
||||
void AddVote(const CTxLockVote& vote);
|
||||
int CountVotes();
|
||||
uint256 GetHash() const { return txLockRequest.GetHash(); }
|
||||
|
||||
void AddOutPointLock(const COutPoint& outpoint);
|
||||
bool AddVote(const CTxLockVote& vote);
|
||||
bool IsAllOutPointsReady() const;
|
||||
|
||||
bool HasMasternodeVoted(const COutPoint& outpointIn, const COutPoint& outpointMasternodeIn);
|
||||
int CountVotes() const;
|
||||
|
||||
void SetConfirmedHeight(int nConfirmedHeightIn) { nConfirmedHeight = nConfirmedHeightIn; }
|
||||
bool IsExpired(int nHeight) const;
|
||||
|
||||
void Relay() const;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
128
src/main.cpp
128
src/main.cpp
@ -941,6 +941,18 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in
|
||||
return nSigOps;
|
||||
}
|
||||
|
||||
int GetUTXOHeight(const COutPoint& outpoint)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
CCoins coins;
|
||||
if(!pcoinsTip->GetCoins(outpoint.hash, coins) ||
|
||||
(unsigned int)outpoint.n>=coins.vout.size() ||
|
||||
coins.vout[outpoint.n].IsNull()) {
|
||||
return -1;
|
||||
}
|
||||
return coins.nHeight;
|
||||
}
|
||||
|
||||
int GetInputAge(const CTxIn &txin)
|
||||
{
|
||||
CCoinsView viewDummy;
|
||||
@ -966,7 +978,7 @@ int GetInputAgeIX(const uint256 &nTXHash, const CTxIn &txin)
|
||||
int nResult = GetInputAge(txin);
|
||||
if(nResult < 0) return -1;
|
||||
|
||||
if (nResult < 6 && IsLockedInstandSendTransaction(nTXHash))
|
||||
if (nResult < 6 && instantsend.IsLockedInstantSendTransaction(nTXHash))
|
||||
return nInstantSendDepth + nResult;
|
||||
|
||||
return nResult;
|
||||
@ -974,7 +986,7 @@ int GetInputAgeIX(const uint256 &nTXHash, const CTxIn &txin)
|
||||
|
||||
int GetIXConfirmations(const uint256 &nTXHash)
|
||||
{
|
||||
if (IsLockedInstandSendTransaction(nTXHash))
|
||||
if (instantsend.IsLockedInstantSendTransaction(nTXHash))
|
||||
return nInstantSendDepth;
|
||||
|
||||
return 0;
|
||||
@ -1088,14 +1100,19 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
|
||||
if (pool.exists(hash))
|
||||
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-in-mempool");
|
||||
|
||||
// ----------- InstantSend transaction scanning -----------
|
||||
// If this is a Transaction Lock Request check to see if it's valid
|
||||
if(instantsend.HasTxLockRequest(hash) && !CTxLockRequest(tx).IsValid())
|
||||
return state.DoS(10, error("AcceptToMemoryPool : CTxLockRequest %s is invalid", hash.ToString()),
|
||||
REJECT_INVALID, "bad-txlockrequest");
|
||||
|
||||
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
|
||||
if(mapLockedInputs.count(txin.prevout) && mapLockedInputs[txin.prevout] != tx.GetHash()) {
|
||||
return state.DoS(0,
|
||||
error("AcceptToMemoryPool : conflicts with existing transaction lock: %s", reason),
|
||||
REJECT_INVALID, "tx-lock-conflict");
|
||||
}
|
||||
// Check for conflicts with a completed Transaction Lock
|
||||
BOOST_FOREACH(const CTxIn &txin, tx.vin)
|
||||
{
|
||||
uint256 hashLocked;
|
||||
if(instantsend.GetLockedOutPointTxHash(txin.prevout, hashLocked) && hash != hashLocked)
|
||||
return state.DoS(10, error("AcceptToMemoryPool : Transaction %s conflicts with completed Transaction Lock %s",
|
||||
hash.ToString(), hashLocked.ToString()),
|
||||
REJECT_INVALID, "tx-txlock-conflict");
|
||||
}
|
||||
|
||||
// Check for conflicts with in-memory transactions
|
||||
@ -1109,6 +1126,18 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
|
||||
const CTransaction *ptxConflicting = pool.mapNextTx[txin.prevout].ptx;
|
||||
if (!setConflicts.count(ptxConflicting->GetHash()))
|
||||
{
|
||||
// InstantSend txes are not replacable
|
||||
if(instantsend.HasTxLockRequest(ptxConflicting->GetHash())) {
|
||||
// this tx conflicts with a Transaction Lock Request candidate
|
||||
return state.DoS(0, error("AcceptToMemoryPool : Transaction %s conflicts with Transaction Lock Request %s",
|
||||
hash.ToString(), ptxConflicting->GetHash().ToString()),
|
||||
REJECT_INVALID, "tx-txlockreq-mempool-conflict");
|
||||
} else if (instantsend.HasTxLockRequest(hash)) {
|
||||
// this tx is a tx lock request and it conflicts with a normal tx
|
||||
return state.DoS(0, error("AcceptToMemoryPool : Transaction Lock Request %s conflicts with transaction %s",
|
||||
hash.ToString(), ptxConflicting->GetHash().ToString()),
|
||||
REJECT_INVALID, "txlockreq-tx-mempool-conflict");
|
||||
}
|
||||
// Allow opt-out of transaction replacement by setting
|
||||
// nSequence >= maxint-1 on all inputs.
|
||||
//
|
||||
@ -3701,24 +3730,33 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
|
||||
REJECT_INVALID, "bad-cb-multiple");
|
||||
|
||||
|
||||
// DASH : CHECK TRANSACTIONS FOR INSTANT SEND
|
||||
// DASH : CHECK TRANSACTIONS FOR INSTANTSEND
|
||||
|
||||
if(sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) {
|
||||
// We should never accept block which conflicts with completed transaction lock,
|
||||
// that's why this is in CheckBlock unlike coinbase payee/amount.
|
||||
// Require other nodes to comply, send them some data in case they are missing it.
|
||||
BOOST_FOREACH(const CTransaction& tx, block.vtx) {
|
||||
// skip coinbase, it has no inputs
|
||||
if (tx.IsCoinBase()) continue;
|
||||
// LOOK FOR TRANSACTION LOCK IN OUR MAP OF INPUTS
|
||||
// LOOK FOR TRANSACTION LOCK IN OUR MAP OF OUTPOINTS
|
||||
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
|
||||
if(mapLockedInputs.count(txin.prevout) && mapLockedInputs[txin.prevout] != tx.GetHash()) {
|
||||
uint256 hashLocked;
|
||||
if(instantsend.GetLockedOutPointTxHash(txin.prevout, hashLocked) && hashLocked != tx.GetHash()) {
|
||||
// Every node which relayed this block to us must invalidate it
|
||||
// but they probably need more data.
|
||||
// Relay corresponding transaction lock request and all its votes
|
||||
// to let other nodes complete the lock.
|
||||
instantsend.Relay(hashLocked);
|
||||
mapRejectedBlocks.insert(make_pair(block.GetHash(), GetTime()));
|
||||
LogPrintf("CheckBlock(DASH): found conflicting transaction with transaction lock %s %s\n", mapLockedInputs[txin.prevout].ToString(), tx.GetHash().ToString());
|
||||
return state.DoS(0, error("CheckBlock(DASH): found conflicting transaction with transaction lock"),
|
||||
REJECT_INVALID, "conflicting-tx-ix");
|
||||
return state.DoS(0, error("CheckBlock(DASH): transaction %s conflicts with transaction lock %s",
|
||||
tx.GetHash().ToString(), hashLocked.ToString()),
|
||||
REJECT_INVALID, "conflict-tx-lock");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LogPrintf("CheckBlock(DASH): skipping transaction locking checks\n");
|
||||
LogPrintf("CheckBlock(DASH): spork is off, skipping transaction locking checks\n");
|
||||
}
|
||||
|
||||
// END DASH
|
||||
@ -4901,10 +4939,10 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
||||
We want to only update the time on new hits, so that we can time out appropriately if needed.
|
||||
*/
|
||||
case MSG_TXLOCK_REQUEST:
|
||||
return mapLockRequestAccepted.count(inv.hash) || mapLockRequestRejected.count(inv.hash);
|
||||
return instantsend.AlreadyHave(inv.hash);
|
||||
|
||||
case MSG_TXLOCK_VOTE:
|
||||
return mapTxLockVotes.count(inv.hash);
|
||||
return instantsend.AlreadyHave(inv.hash);
|
||||
|
||||
case MSG_SPORK:
|
||||
return mapSporks.count(inv.hash);
|
||||
@ -5063,20 +5101,22 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam
|
||||
}
|
||||
|
||||
if (!pushed && inv.type == MSG_TXLOCK_REQUEST) {
|
||||
if(mapLockRequestAccepted.count(inv.hash)) {
|
||||
CTxLockRequest txLockRequest;
|
||||
if(instantsend.GetTxLockRequest(inv.hash, txLockRequest)) {
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss.reserve(1000);
|
||||
ss << mapLockRequestAccepted[inv.hash];
|
||||
ss << txLockRequest;
|
||||
pfrom->PushMessage(NetMsgType::TXLOCKREQUEST, ss);
|
||||
pushed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pushed && inv.type == MSG_TXLOCK_VOTE) {
|
||||
if(mapTxLockVotes.count(inv.hash)) {
|
||||
CTxLockVote vote;
|
||||
if(instantsend.GetTxLockVote(inv.hash, vote)) {
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss.reserve(1000);
|
||||
ss << mapTxLockVotes[inv.hash];
|
||||
ss << vote;
|
||||
pfrom->PushMessage(NetMsgType::TXLOCKVOTE, ss);
|
||||
pushed = true;
|
||||
}
|
||||
@ -5687,13 +5727,16 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
||||
vector<uint256> vWorkQueue;
|
||||
vector<uint256> vEraseQueue;
|
||||
CTransaction tx;
|
||||
CTxLockRequest txLockRequest;
|
||||
CDarksendBroadcastTx dstx;
|
||||
int nInvType = MSG_TX;
|
||||
|
||||
// Read data and assign inv type
|
||||
if(strCommand == NetMsgType::TX) {
|
||||
vRecv >> tx;
|
||||
} else if(strCommand == NetMsgType::TXLOCKREQUEST) {
|
||||
vRecv >> tx;
|
||||
vRecv >> txLockRequest;
|
||||
tx = txLockRequest;
|
||||
nInvType = MSG_TXLOCK_REQUEST;
|
||||
} else if (strCommand == NetMsgType::DSTX) {
|
||||
vRecv >> dstx;
|
||||
@ -5705,7 +5748,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
||||
pfrom->AddInventoryKnown(inv);
|
||||
pfrom->setAskFor.erase(inv.hash);
|
||||
|
||||
if (strCommand == NetMsgType::DSTX) {
|
||||
// Process custom logic, no matter if tx will be accepted to mempool later or not
|
||||
if (strCommand == NetMsgType::TXLOCKREQUEST) {
|
||||
if(!instantsend.ProcessTxLockRequest(txLockRequest)) {
|
||||
LogPrint("instantsend", "TXLOCKREQUEST -- failed %s\n", txLockRequest.GetHash().ToString());
|
||||
return false;
|
||||
}
|
||||
} else if (strCommand == NetMsgType::DSTX) {
|
||||
uint256 hashTx = tx.GetHash();
|
||||
|
||||
if(mapDarksendBroadcastTxes.count(hashTx)) {
|
||||
@ -5745,11 +5794,15 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
||||
|
||||
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs))
|
||||
{
|
||||
// Process custom txes
|
||||
// Process custom txes, this changes AlreadyHave to "true"
|
||||
if (strCommand == NetMsgType::DSTX) {
|
||||
LogPrintf("DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n",
|
||||
tx.GetHash().ToString(), pfrom->id);
|
||||
mapDarksendBroadcastTxes.insert(make_pair(tx.GetHash(), dstx));
|
||||
} else if (strCommand == NetMsgType::TXLOCKREQUEST) {
|
||||
if(!ProcessTxLockRequest(pfrom, tx)) return false;
|
||||
LogPrintf("TXLOCKREQUEST -- Transaction Lock Request accepted, txid=%s, peer=%d\n",
|
||||
tx.GetHash().ToString(), pfrom->id);
|
||||
instantsend.AcceptLockRequest(txLockRequest);
|
||||
}
|
||||
|
||||
mempool.check(pcoinsTip);
|
||||
@ -5828,18 +5881,17 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
||||
assert(recentRejects);
|
||||
recentRejects->insert(tx.GetHash());
|
||||
|
||||
if (strCommand == NetMsgType::TXLOCKREQUEST && !AlreadyHave(inv)) { // i.e. AcceptToMemoryPool failed
|
||||
mapLockRequestRejected.insert(std::make_pair(tx.GetHash(), tx));
|
||||
if (strCommand == NetMsgType::TXLOCKREQUEST && !AlreadyHave(inv)) {
|
||||
// i.e. AcceptToMemoryPool failed, probably because it's conflicting
|
||||
// with existing normal tx or tx lock for another tx. For the same tx lock
|
||||
// AlreadyHave would have return "true" already.
|
||||
|
||||
// can we get the conflicting transaction as proof?
|
||||
|
||||
LogPrintf("TXLOCKREQUEST -- Transaction Lock Request: %s %s : rejected %s\n",
|
||||
pfrom->addr.ToString(), pfrom->cleanSubVer,
|
||||
tx.GetHash().ToString()
|
||||
);
|
||||
|
||||
LockTransactionInputs(tx);
|
||||
ResolveConflicts(tx);
|
||||
// It's the first time we failed for this tx lock request,
|
||||
// this should switch AlreadyHave to "true".
|
||||
instantsend.RejectLockRequest(txLockRequest);
|
||||
// this lets other nodes to create lock request candidate i.e.
|
||||
// this allows multiple conflicting lock requests to compete for votes
|
||||
RelayTransaction(tx);
|
||||
}
|
||||
|
||||
if (pfrom->fWhitelisted && GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) {
|
||||
@ -6259,7 +6311,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
||||
darkSendPool.ProcessMessage(pfrom, strCommand, vRecv);
|
||||
mnodeman.ProcessMessage(pfrom, strCommand, vRecv);
|
||||
mnpayments.ProcessMessage(pfrom, strCommand, vRecv);
|
||||
ProcessMessageInstantSend(pfrom, strCommand, vRecv);
|
||||
instantsend.ProcessMessage(pfrom, strCommand, vRecv);
|
||||
sporkManager.ProcessSpork(pfrom, strCommand, vRecv);
|
||||
masternodeSync.ProcessMessage(pfrom, strCommand, vRecv);
|
||||
governance.ProcessMessage(pfrom, strCommand, vRecv);
|
||||
|
@ -291,6 +291,7 @@ void PruneAndFlush();
|
||||
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
|
||||
bool* pfMissingInputs, bool fOverrideMempoolLimit=false, bool fRejectAbsurdFee=false, bool fDryRun=false);
|
||||
|
||||
int GetUTXOHeight(const COutPoint& outpoint);
|
||||
int GetInputAge(const CTxIn &txin);
|
||||
int GetInputAgeIX(const uint256 &nTXHash, const CTxIn &txin);
|
||||
int GetIXConfirmations(const uint256 &nTXHash);
|
||||
|
@ -2111,10 +2111,11 @@ void RelayTransaction(const CTransaction& tx)
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss.reserve(10000);
|
||||
uint256 hash = tx.GetHash();
|
||||
CTxLockRequest txLockRequest;
|
||||
if(mapDarksendBroadcastTxes.count(hash)) { // MSG_DSTX
|
||||
ss << mapDarksendBroadcastTxes[hash];
|
||||
} else if(mapLockRequestAccepted.count(hash)) { // MSG_TXLOCK_REQUEST
|
||||
ss << mapLockRequestAccepted[hash];
|
||||
} else if(instantsend.GetTxLockRequest(hash, txLockRequest)) { // MSG_TXLOCK_REQUEST
|
||||
ss << txLockRequest;
|
||||
} else { // MSG_TX
|
||||
ss << tx;
|
||||
}
|
||||
@ -2125,7 +2126,7 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss)
|
||||
{
|
||||
uint256 hash = tx.GetHash();
|
||||
int nInv = mapDarksendBroadcastTxes.count(hash) ? MSG_DSTX :
|
||||
(mapLockRequestAccepted.count(hash) ? MSG_TXLOCK_REQUEST : MSG_TX);
|
||||
(instantsend.HasTxLockRequest(hash) ? MSG_TXLOCK_REQUEST : MSG_TX);
|
||||
CInv inv(nInv, hash);
|
||||
{
|
||||
LOCK(cs_mapRelay);
|
||||
|
@ -585,6 +585,10 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
nBytesInputs += 148; // in all error cases, simply assume 148 here
|
||||
}
|
||||
else nBytesInputs += 148;
|
||||
|
||||
// Add inputs to calculate InstantSend Fee later
|
||||
if(coinControl->fUseInstantSend)
|
||||
txDummy.vin.push_back(CTxIn());
|
||||
}
|
||||
|
||||
// calculation
|
||||
@ -609,7 +613,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
nPayFee = coinControl->nMinimumTotalFee;
|
||||
|
||||
// InstantSend Fee
|
||||
if (coinControl->fUseInstantSend) nPayFee = std::max(nPayFee, INSTANTSEND_MIN_FEE);
|
||||
if (coinControl->fUseInstantSend) nPayFee = std::max(nPayFee, CTxLockRequest(txDummy).GetMinFee());
|
||||
|
||||
// Allow free? (require at least hard-coded threshold and default to that if no estimate)
|
||||
double dPriorityNeeded = std::max(mempoolEstimatePriority, AllowFreeThreshold());
|
||||
|
@ -41,7 +41,6 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
|
||||
|
||||
QString strTxStatus;
|
||||
bool fOffline = (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60) && (wtx.GetRequestCount() == 0);
|
||||
int nSignatures = GetTransactionLockSignatures(wtx.GetHash());
|
||||
|
||||
if (fOffline) {
|
||||
strTxStatus = tr("%1/offline").arg(nDepth);
|
||||
@ -51,14 +50,16 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
|
||||
strTxStatus = tr("%1 confirmations").arg(nDepth);
|
||||
}
|
||||
|
||||
if(nSignatures < 0) return strTxStatus; // regular tx
|
||||
if(!instantsend.HasTxLockRequest(wtx.GetHash())) return strTxStatus; // regular tx
|
||||
|
||||
int nSignatures = instantsend.GetTransactionLockSignatures(wtx.GetHash());
|
||||
int nSignaturesMax = CTxLockRequest(wtx).GetMaxSignatures();
|
||||
// InstantSend
|
||||
strTxStatus += " (";
|
||||
if(nSignatures >= INSTANTSEND_SIGNATURES_REQUIRED) {
|
||||
if(instantsend.IsLockedInstantSendTransaction(wtx.GetHash())) {
|
||||
strTxStatus += tr("verified via InstantSend");
|
||||
} else if(!IsTransactionLockTimedOut(wtx.GetHash())) {
|
||||
strTxStatus += tr("InstantSend verification in progress - %1 of %2 signatures").arg(nSignatures).arg(INSTANTSEND_SIGNATURES_TOTAL);
|
||||
} else if(!instantsend.IsTxLockRequestTimedOut(wtx.GetHash())) {
|
||||
strTxStatus += tr("InstantSend verification in progress - %1 of %2 signatures").arg(nSignatures).arg(nSignaturesMax);
|
||||
} else {
|
||||
strTxStatus += tr("InstantSend verification failed");
|
||||
}
|
||||
|
@ -307,10 +307,17 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
if (fSubtractFeeFromAmount && fCreated)
|
||||
transaction.reassignAmounts(nChangePosRet);
|
||||
|
||||
if(recipients[0].fUseInstantSend && newTx->GetValueOut() > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN){
|
||||
Q_EMIT message(tr("Send Coins"), tr("InstantSend doesn't support sending values that high yet. Transactions are currently limited to %1 DASH.").arg(sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)),
|
||||
CClientUIInterface::MSG_ERROR);
|
||||
return TransactionCreationFailed;
|
||||
if(recipients[0].fUseInstantSend) {
|
||||
if(newTx->GetValueOut() > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN) {
|
||||
Q_EMIT message(tr("Send Coins"), tr("InstantSend doesn't support sending values that high yet. Transactions are currently limited to %1 DASH.").arg(sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)),
|
||||
CClientUIInterface::MSG_ERROR);
|
||||
return TransactionCreationFailed;
|
||||
}
|
||||
if(newTx->vin.size() > CTxLockRequest::MAX_INPUTS) {
|
||||
Q_EMIT message(tr("Send Coins"), tr("InstantSend doesn't support transactions with more than %1 inputs.").arg(CTxLockRequest::MAX_INPUTS),
|
||||
CClientUIInterface::MSG_ERROR);
|
||||
return TransactionCreationFailed;
|
||||
}
|
||||
}
|
||||
|
||||
if(!fCreated)
|
||||
|
@ -871,12 +871,8 @@ UniValue sendrawtransaction(const UniValue& params, bool fHelp)
|
||||
} else if (fHaveChain) {
|
||||
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
|
||||
}
|
||||
if (fInstantSend) {
|
||||
if (!IsInstantSendTxValid(tx)) {
|
||||
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Not a valid InstantSend transaction");
|
||||
}
|
||||
mapLockRequestAccepted.insert(make_pair(hashTx, tx));
|
||||
CreateTxLockCandidate(tx);
|
||||
if (fInstantSend && !instantsend.ProcessTxLockRequest(tx)) {
|
||||
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Not a valid InstantSend transaction, see debug.log for more info");
|
||||
}
|
||||
RelayTransaction(tx);
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
* network protocol versioning
|
||||
*/
|
||||
|
||||
static const int PROTOCOL_VERSION = 70204;
|
||||
static const int PROTOCOL_VERSION = 70205;
|
||||
|
||||
//! initial proto version, to be increased after version/verack negotiation
|
||||
static const int INIT_PROTO_VERSION = 209;
|
||||
|
@ -1472,9 +1472,8 @@ bool CWalletTx::RelayWalletTransaction(std::string strCommand)
|
||||
uint256 hash = GetHash();
|
||||
LogPrintf("Relaying wtx %s\n", hash.ToString());
|
||||
|
||||
if(strCommand == NetMsgType::TXLOCKREQUEST){
|
||||
mapLockRequestAccepted.insert(make_pair(hash, (CTransaction)*this));
|
||||
CreateTxLockCandidate(((CTransaction)*this));
|
||||
if(strCommand == NetMsgType::TXLOCKREQUEST) {
|
||||
instantsend.ProcessTxLockRequest(((CTxLockRequest)*this));
|
||||
}
|
||||
RelayTransaction((CTransaction)*this);
|
||||
return true;
|
||||
@ -2098,8 +2097,8 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
|
||||
continue;
|
||||
|
||||
int nDepth = pcoin->GetDepthInMainChain(false);
|
||||
// do not use IX for inputs that have less then 6 blockchain confirmations
|
||||
if (fUseInstantSend && nDepth < 6)
|
||||
// do not use IX for inputs that have less then INSTANTSEND_CONFIRMATIONS_REQUIRED blockchain confirmations
|
||||
if (fUseInstantSend && nDepth < INSTANTSEND_CONFIRMATIONS_REQUIRED)
|
||||
continue;
|
||||
|
||||
// We should not consider coins which aren't at least in our mempool
|
||||
@ -2358,6 +2357,10 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
|
||||
nValueRet += out.tx->vout[out.i].nValue;
|
||||
setCoinsRet.insert(make_pair(out.tx, out.i));
|
||||
}
|
||||
|
||||
if(fUseInstantSend && setCoinsRet.size() > CTxLockRequest::MAX_INPUTS)
|
||||
return false;
|
||||
|
||||
return (nValueRet >= nTargetValue);
|
||||
}
|
||||
|
||||
@ -2885,7 +2888,7 @@ bool CWallet::ConvertList(std::vector<CTxIn> vecTxIn, std::vector<CAmount>& vecA
|
||||
bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
|
||||
int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, AvailableCoinsType nCoinType, bool fUseInstantSend)
|
||||
{
|
||||
CAmount nFeePay = fUseInstantSend ? INSTANTSEND_MIN_FEE : 0;
|
||||
CAmount nFeePay = fUseInstantSend ? CTxLockRequest().GetMinFee() : 0;
|
||||
|
||||
CAmount nValue = 0;
|
||||
unsigned int nSubtractFeeFromAmount = 0;
|
||||
@ -3000,19 +3003,26 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
|
||||
|
||||
if (!SelectCoins(nValueToSelect, setCoins, nValueIn, coinControl, nCoinType, fUseInstantSend))
|
||||
{
|
||||
if (nCoinType == ALL_COINS) {
|
||||
strFailReason = _("Insufficient funds.");
|
||||
} else if (nCoinType == ONLY_NOT1000IFMN) {
|
||||
if (nCoinType == ONLY_NOT1000IFMN) {
|
||||
strFailReason = _("Unable to locate enough funds for this transaction that are not equal 1000 DASH.");
|
||||
} else if (nCoinType == ONLY_NONDENOMINATED_NOT1000IFMN) {
|
||||
strFailReason = _("Unable to locate enough PrivateSend non-denominated funds for this transaction that are not equal 1000 DASH.");
|
||||
} else {
|
||||
} else if (nCoinType == ONLY_DENOMINATED) {
|
||||
strFailReason = _("Unable to locate enough PrivateSend denominated funds for this transaction.");
|
||||
strFailReason += " " + _("PrivateSend uses exact denominated amounts to send funds, you might simply need to anonymize some more coins.");
|
||||
} else if (nValueIn < nValueToSelect) {
|
||||
strFailReason = _("Insufficient funds.");
|
||||
}
|
||||
|
||||
if(fUseInstantSend){
|
||||
strFailReason += " " + _("InstantSend requires inputs with at least 6 confirmations, you might need to wait a few minutes and try again.");
|
||||
if (fUseInstantSend) {
|
||||
size_t nMaxInputs = CTxLockRequest::MAX_INPUTS;
|
||||
if(setCoins.size() > nMaxInputs) {
|
||||
strFailReason += " " + strprintf(_("InstantSend doesn't support transactions with more than %d inputs."), nMaxInputs);
|
||||
} else if (nValueIn > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN) {
|
||||
strFailReason += " " + strprintf(_("InstantSend doesn't support sending values that high yet. Transactions are currently limited to %1 DASH."), sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE));
|
||||
} else {
|
||||
// could be not true but most likely that's the reason
|
||||
strFailReason += " " + strprintf(_("InstantSend requires inputs with at least %d confirmations, you might need to wait a few minutes and try again."), INSTANTSEND_CONFIRMATIONS_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -3205,6 +3215,9 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
|
||||
if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
|
||||
nFeeNeeded = coinControl->nMinimumTotalFee;
|
||||
}
|
||||
if(fUseInstantSend) {
|
||||
nFeeNeeded = std::max(nFeeNeeded, CTxLockRequest(txNew).GetMinFee());
|
||||
}
|
||||
|
||||
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up
|
||||
// because we must be at the maximum allowed fee.
|
||||
@ -4045,7 +4058,7 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet, bool enableIX)
|
||||
}
|
||||
}
|
||||
|
||||
if(enableIX && nResult < 6 && IsLockedInstandSendTransaction(GetHash()))
|
||||
if(enableIX && nResult < 6 && instantsend.IsLockedInstantSendTransaction(GetHash()))
|
||||
return nInstantSendDepth + nResult;
|
||||
|
||||
return nResult;
|
||||
|
Loading…
Reference in New Issue
Block a user