Merge pull request #2735 from codablock/pr_llmq_instantsend
Implement LLMQ based InstantSend
This commit is contained in:
commit
64ae91268a
@ -122,6 +122,21 @@ class AutoIXMempoolTest(DashTestFramework):
|
|||||||
def run_test(self):
|
def run_test(self):
|
||||||
# make sure masternodes are synced
|
# make sure masternodes are synced
|
||||||
sync_masternodes(self.nodes)
|
sync_masternodes(self.nodes)
|
||||||
|
|
||||||
|
self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0)
|
||||||
|
self.wait_for_sporks_same()
|
||||||
|
self.mine_quorum()
|
||||||
|
|
||||||
|
print("Test old InstantSend")
|
||||||
|
self.test_auto();
|
||||||
|
|
||||||
|
self.nodes[0].spork("SPORK_20_INSTANTSEND_LLMQ_BASED", 0)
|
||||||
|
self.wait_for_sporks_same()
|
||||||
|
|
||||||
|
print("Test new InstantSend")
|
||||||
|
self.test_auto(True);
|
||||||
|
|
||||||
|
def test_auto(self, new_is = False):
|
||||||
self.activate_autoix_bip9()
|
self.activate_autoix_bip9()
|
||||||
self.set_autoix_spork_state(True)
|
self.set_autoix_spork_state(True)
|
||||||
|
|
||||||
@ -146,13 +161,17 @@ class AutoIXMempoolTest(DashTestFramework):
|
|||||||
|
|
||||||
# fill mempool with transactions
|
# fill mempool with transactions
|
||||||
self.set_autoix_spork_state(False)
|
self.set_autoix_spork_state(False)
|
||||||
|
self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 4070908800)
|
||||||
|
self.wait_for_sporks_same()
|
||||||
self.fill_mempool()
|
self.fill_mempool()
|
||||||
self.set_autoix_spork_state(True)
|
self.set_autoix_spork_state(True)
|
||||||
|
self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 0)
|
||||||
|
self.wait_for_sporks_same()
|
||||||
|
|
||||||
# autoIX is not working now
|
# autoIX is not working now
|
||||||
assert(not self.send_simple_tx(sender, receiver))
|
assert(not self.send_simple_tx(sender, receiver))
|
||||||
# regular IX is still working
|
# regular IX is still working for old IS but not for new one
|
||||||
assert(self.send_regular_IX(sender, receiver))
|
assert(not self.send_regular_IX(sender, receiver) if new_is else self.send_regular_IX(sender, receiver))
|
||||||
|
|
||||||
# generate one block to clean up mempool and retry auto and regular IX
|
# generate one block to clean up mempool and retry auto and regular IX
|
||||||
# generate 2 more blocks to have enough confirmations for IX
|
# generate 2 more blocks to have enough confirmations for IX
|
||||||
|
@ -89,9 +89,10 @@ class AutoInstantSendTest(DashTestFramework):
|
|||||||
self.nodes[0].spork('SPORK_16_INSTANTSEND_AUTOLOCKS', value)
|
self.nodes[0].spork('SPORK_16_INSTANTSEND_AUTOLOCKS', value)
|
||||||
|
|
||||||
# sends regular IX with high fee and may inputs (not-simple transaction)
|
# sends regular IX with high fee and may inputs (not-simple transaction)
|
||||||
def send_regular_IX(self):
|
def send_regular_IX(self, check_fee = True):
|
||||||
receiver_addr = self.nodes[self.receiver_idx].getnewaddress()
|
receiver_addr = self.nodes[self.receiver_idx].getnewaddress()
|
||||||
txid = self.nodes[0].instantsendtoaddress(receiver_addr, 1.0)
|
txid = self.nodes[0].instantsendtoaddress(receiver_addr, 1.0)
|
||||||
|
if (check_fee):
|
||||||
MIN_FEE = satoshi_round(-0.0001)
|
MIN_FEE = satoshi_round(-0.0001)
|
||||||
fee = self.nodes[0].gettransaction(txid)['fee']
|
fee = self.nodes[0].gettransaction(txid)['fee']
|
||||||
expected_fee = MIN_FEE * len(self.nodes[0].getrawtransaction(txid, True)['vin'])
|
expected_fee = MIN_FEE * len(self.nodes[0].getrawtransaction(txid, True)['vin'])
|
||||||
@ -115,6 +116,21 @@ class AutoInstantSendTest(DashTestFramework):
|
|||||||
def run_test(self):
|
def run_test(self):
|
||||||
# make sure masternodes are synced
|
# make sure masternodes are synced
|
||||||
sync_masternodes(self.nodes)
|
sync_masternodes(self.nodes)
|
||||||
|
|
||||||
|
self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0)
|
||||||
|
self.wait_for_sporks_same()
|
||||||
|
self.mine_quorum()
|
||||||
|
|
||||||
|
print("Test old InstantSend")
|
||||||
|
self.test_auto();
|
||||||
|
|
||||||
|
self.nodes[0].spork("SPORK_20_INSTANTSEND_LLMQ_BASED", 0)
|
||||||
|
self.wait_for_sporks_same()
|
||||||
|
|
||||||
|
print("Test new InstantSend")
|
||||||
|
self.test_auto(True);
|
||||||
|
|
||||||
|
def test_auto(self, new_is = False):
|
||||||
# feed the sender with some balance
|
# feed the sender with some balance
|
||||||
sender_addr = self.nodes[self.sender_idx].getnewaddress()
|
sender_addr = self.nodes[self.sender_idx].getnewaddress()
|
||||||
self.nodes[0].sendtoaddress(sender_addr, 1)
|
self.nodes[0].sendtoaddress(sender_addr, 1)
|
||||||
@ -126,9 +142,9 @@ class AutoInstantSendTest(DashTestFramework):
|
|||||||
|
|
||||||
assert(not self.get_autoix_spork_state())
|
assert(not self.get_autoix_spork_state())
|
||||||
|
|
||||||
assert(self.send_regular_IX())
|
assert(self.send_regular_IX(not new_is))
|
||||||
assert(not self.send_simple_tx())
|
assert(self.send_simple_tx() if new_is else not self.send_simple_tx())
|
||||||
assert(not self.send_complex_tx())
|
assert(self.send_complex_tx() if new_is else not self.send_complex_tx())
|
||||||
|
|
||||||
self.activate_autoix_bip9()
|
self.activate_autoix_bip9()
|
||||||
self.set_autoix_spork_state(True)
|
self.set_autoix_spork_state(True)
|
||||||
@ -136,16 +152,16 @@ class AutoInstantSendTest(DashTestFramework):
|
|||||||
assert(self.get_autoix_bip9_status() == 'active')
|
assert(self.get_autoix_bip9_status() == 'active')
|
||||||
assert(self.get_autoix_spork_state())
|
assert(self.get_autoix_spork_state())
|
||||||
|
|
||||||
assert(self.send_regular_IX())
|
assert(self.send_regular_IX(not new_is))
|
||||||
assert(self.send_simple_tx())
|
assert(self.send_simple_tx())
|
||||||
assert(not self.send_complex_tx())
|
assert(self.send_complex_tx() if new_is else not self.send_complex_tx())
|
||||||
|
|
||||||
self.set_autoix_spork_state(False)
|
self.set_autoix_spork_state(False)
|
||||||
assert(not self.get_autoix_spork_state())
|
assert(not self.get_autoix_spork_state())
|
||||||
|
|
||||||
assert(self.send_regular_IX())
|
assert(self.send_regular_IX(not new_is))
|
||||||
assert(not self.send_simple_tx())
|
assert(self.send_simple_tx() if new_is else not self.send_simple_tx())
|
||||||
assert(not self.send_complex_tx())
|
assert(self.send_complex_tx() if new_is else not self.send_complex_tx())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -21,6 +21,20 @@ class InstantSendTest(DashTestFramework):
|
|||||||
self.sender_idx = self.num_nodes - 3
|
self.sender_idx = self.num_nodes - 3
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
|
self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0)
|
||||||
|
self.wait_for_sporks_same()
|
||||||
|
self.mine_quorum()
|
||||||
|
|
||||||
|
print("Test old InstantSend")
|
||||||
|
self.test_doublespend()
|
||||||
|
|
||||||
|
self.nodes[0].spork("SPORK_20_INSTANTSEND_LLMQ_BASED", 0)
|
||||||
|
self.wait_for_sporks_same()
|
||||||
|
|
||||||
|
print("Test new InstantSend")
|
||||||
|
self.test_doublespend()
|
||||||
|
|
||||||
|
def test_doublespend(self):
|
||||||
# feed the sender with some balance
|
# feed the sender with some balance
|
||||||
sender_addr = self.nodes[self.sender_idx].getnewaddress()
|
sender_addr = self.nodes[self.sender_idx].getnewaddress()
|
||||||
self.nodes[0].sendtoaddress(sender_addr, 1)
|
self.nodes[0].sendtoaddress(sender_addr, 1)
|
||||||
@ -73,7 +87,12 @@ class InstantSendTest(DashTestFramework):
|
|||||||
assert (res['hash'] != wrong_block)
|
assert (res['hash'] != wrong_block)
|
||||||
# wait for long time only for first node
|
# wait for long time only for first node
|
||||||
timeout = 1
|
timeout = 1
|
||||||
|
# mine more blocks
|
||||||
|
# TODO: mine these blocks on an isolated node
|
||||||
|
set_mocktime(get_mocktime() + 1)
|
||||||
|
set_node_times(self.nodes, get_mocktime())
|
||||||
|
self.nodes[0].generate(2)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
InstantSendTest().main()
|
InstantSendTest().main()
|
||||||
|
@ -498,36 +498,44 @@ class DashTestFramework(BitcoinTestFramework):
|
|||||||
set_mocktime(get_mocktime() + 1)
|
set_mocktime(get_mocktime() + 1)
|
||||||
set_node_times(self.nodes, get_mocktime())
|
set_node_times(self.nodes, get_mocktime())
|
||||||
self.nodes[0].generate(skip_count)
|
self.nodes[0].generate(skip_count)
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
# Make sure all reached phase 1 (init)
|
# Make sure all reached phase 1 (init)
|
||||||
self.wait_for_quorum_phase(1, None, 0)
|
self.wait_for_quorum_phase(1, None, 0)
|
||||||
|
# Give nodes some time to connect to neighbors
|
||||||
|
sleep(2)
|
||||||
set_mocktime(get_mocktime() + 1)
|
set_mocktime(get_mocktime() + 1)
|
||||||
set_node_times(self.nodes, get_mocktime())
|
set_node_times(self.nodes, get_mocktime())
|
||||||
self.nodes[0].generate(2)
|
self.nodes[0].generate(2)
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
# Make sure all reached phase 2 (contribute) and received all contributions
|
# Make sure all reached phase 2 (contribute) and received all contributions
|
||||||
self.wait_for_quorum_phase(2, "receivedContributions", expected_valid_count)
|
self.wait_for_quorum_phase(2, "receivedContributions", expected_valid_count)
|
||||||
set_mocktime(get_mocktime() + 1)
|
set_mocktime(get_mocktime() + 1)
|
||||||
set_node_times(self.nodes, get_mocktime())
|
set_node_times(self.nodes, get_mocktime())
|
||||||
self.nodes[0].generate(2)
|
self.nodes[0].generate(2)
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
# Make sure all reached phase 3 (complain) and received all complaints
|
# Make sure all reached phase 3 (complain) and received all complaints
|
||||||
self.wait_for_quorum_phase(3, "receivedComplaints" if expected_valid_count != 10 else None, expected_valid_count)
|
self.wait_for_quorum_phase(3, "receivedComplaints" if expected_valid_count != 10 else None, expected_valid_count)
|
||||||
set_mocktime(get_mocktime() + 1)
|
set_mocktime(get_mocktime() + 1)
|
||||||
set_node_times(self.nodes, get_mocktime())
|
set_node_times(self.nodes, get_mocktime())
|
||||||
self.nodes[0].generate(2)
|
self.nodes[0].generate(2)
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
# Make sure all reached phase 4 (justify)
|
# Make sure all reached phase 4 (justify)
|
||||||
self.wait_for_quorum_phase(4, None, 0)
|
self.wait_for_quorum_phase(4, None, 0)
|
||||||
set_mocktime(get_mocktime() + 1)
|
set_mocktime(get_mocktime() + 1)
|
||||||
set_node_times(self.nodes, get_mocktime())
|
set_node_times(self.nodes, get_mocktime())
|
||||||
self.nodes[0].generate(2)
|
self.nodes[0].generate(2)
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
# Make sure all reached phase 5 (commit)
|
# Make sure all reached phase 5 (commit)
|
||||||
self.wait_for_quorum_phase(5, "receivedPrematureCommitments", expected_valid_count)
|
self.wait_for_quorum_phase(5, "receivedPrematureCommitments", expected_valid_count)
|
||||||
set_mocktime(get_mocktime() + 1)
|
set_mocktime(get_mocktime() + 1)
|
||||||
set_node_times(self.nodes, get_mocktime())
|
set_node_times(self.nodes, get_mocktime())
|
||||||
self.nodes[0].generate(2)
|
self.nodes[0].generate(2)
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
# Make sure all reached phase 6 (mining)
|
# Make sure all reached phase 6 (mining)
|
||||||
self.wait_for_quorum_phase(6, None, 0)
|
self.wait_for_quorum_phase(6, None, 0)
|
||||||
@ -544,6 +552,7 @@ class DashTestFramework(BitcoinTestFramework):
|
|||||||
set_mocktime(get_mocktime() + 1)
|
set_mocktime(get_mocktime() + 1)
|
||||||
set_node_times(self.nodes, get_mocktime())
|
set_node_times(self.nodes, get_mocktime())
|
||||||
self.nodes[0].generate(1)
|
self.nodes[0].generate(1)
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
sync_blocks(self.nodes)
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
|
@ -173,6 +173,7 @@ BITCOIN_CORE_H = \
|
|||||||
llmq/quorums_dkgsessionmgr.h \
|
llmq/quorums_dkgsessionmgr.h \
|
||||||
llmq/quorums_dkgsession.h \
|
llmq/quorums_dkgsession.h \
|
||||||
llmq/quorums_init.h \
|
llmq/quorums_init.h \
|
||||||
|
llmq/quorums_instantsend.h \
|
||||||
llmq/quorums_signing.h \
|
llmq/quorums_signing.h \
|
||||||
llmq/quorums_signing_shares.h \
|
llmq/quorums_signing_shares.h \
|
||||||
llmq/quorums_utils.h \
|
llmq/quorums_utils.h \
|
||||||
@ -291,6 +292,7 @@ libdash_server_a_SOURCES = \
|
|||||||
llmq/quorums_dkgsessionmgr.cpp \
|
llmq/quorums_dkgsessionmgr.cpp \
|
||||||
llmq/quorums_dkgsession.cpp \
|
llmq/quorums_dkgsession.cpp \
|
||||||
llmq/quorums_init.cpp \
|
llmq/quorums_init.cpp \
|
||||||
|
llmq/quorums_instantsend.cpp \
|
||||||
llmq/quorums_signing.cpp \
|
llmq/quorums_signing.cpp \
|
||||||
llmq/quorums_signing_shares.cpp \
|
llmq/quorums_signing_shares.cpp \
|
||||||
llmq/quorums_utils.cpp \
|
llmq/quorums_utils.cpp \
|
||||||
|
@ -308,6 +308,7 @@ public:
|
|||||||
consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60;
|
consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60;
|
||||||
consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85;
|
consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85;
|
||||||
consensus.llmqChainLocks = Consensus::LLMQ_400_60;
|
consensus.llmqChainLocks = Consensus::LLMQ_400_60;
|
||||||
|
consensus.llmqForInstantSend = Consensus::LLMQ_50_60;
|
||||||
|
|
||||||
fMiningRequiresPeers = true;
|
fMiningRequiresPeers = true;
|
||||||
fDefaultConsistencyChecks = false;
|
fDefaultConsistencyChecks = false;
|
||||||
@ -476,6 +477,7 @@ public:
|
|||||||
consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60;
|
consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60;
|
||||||
consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85;
|
consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85;
|
||||||
consensus.llmqChainLocks = Consensus::LLMQ_50_60;
|
consensus.llmqChainLocks = Consensus::LLMQ_50_60;
|
||||||
|
consensus.llmqForInstantSend = Consensus::LLMQ_50_60;
|
||||||
|
|
||||||
fMiningRequiresPeers = true;
|
fMiningRequiresPeers = true;
|
||||||
fDefaultConsistencyChecks = false;
|
fDefaultConsistencyChecks = false;
|
||||||
@ -623,6 +625,7 @@ public:
|
|||||||
consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60;
|
consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60;
|
||||||
consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85;
|
consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85;
|
||||||
consensus.llmqChainLocks = Consensus::LLMQ_50_60;
|
consensus.llmqChainLocks = Consensus::LLMQ_50_60;
|
||||||
|
consensus.llmqForInstantSend = Consensus::LLMQ_50_60;
|
||||||
|
|
||||||
fMiningRequiresPeers = true;
|
fMiningRequiresPeers = true;
|
||||||
fDefaultConsistencyChecks = false;
|
fDefaultConsistencyChecks = false;
|
||||||
@ -787,6 +790,7 @@ public:
|
|||||||
consensus.llmqs[Consensus::LLMQ_10_60] = llmq10_60;
|
consensus.llmqs[Consensus::LLMQ_10_60] = llmq10_60;
|
||||||
consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60;
|
consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60;
|
||||||
consensus.llmqChainLocks = Consensus::LLMQ_10_60;
|
consensus.llmqChainLocks = Consensus::LLMQ_10_60;
|
||||||
|
consensus.llmqForInstantSend = Consensus::LLMQ_10_60;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateBIP9Parameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThreshold)
|
void UpdateBIP9Parameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThreshold)
|
||||||
|
@ -174,6 +174,7 @@ struct Params {
|
|||||||
|
|
||||||
std::map<LLMQType, LLMQParams> llmqs;
|
std::map<LLMQType, LLMQParams> llmqs;
|
||||||
LLMQType llmqChainLocks;
|
LLMQType llmqChainLocks;
|
||||||
|
LLMQType llmqForInstantSend{LLMQ_NONE};
|
||||||
};
|
};
|
||||||
} // namespace Consensus
|
} // namespace Consensus
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "llmq/quorums.h"
|
#include "llmq/quorums.h"
|
||||||
#include "llmq/quorums_chainlocks.h"
|
#include "llmq/quorums_chainlocks.h"
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
#include "llmq/quorums_dkgsessionmgr.h"
|
#include "llmq/quorums_dkgsessionmgr.h"
|
||||||
|
|
||||||
void CDSNotificationInterface::InitializeCurrentBlockTip()
|
void CDSNotificationInterface::InitializeCurrentBlockTip()
|
||||||
@ -70,8 +71,15 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con
|
|||||||
llmq::quorumDKGSessionManager->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload);
|
llmq::quorumDKGSessionManager->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDSNotificationInterface::NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr<const CBlock>& block)
|
||||||
|
{
|
||||||
|
llmq::chainLocksHandler->NewPoWValidBlock(pindex, block);
|
||||||
|
}
|
||||||
|
|
||||||
void CDSNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock)
|
void CDSNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock)
|
||||||
{
|
{
|
||||||
|
llmq::quorumInstantSendManager->SyncTransaction(tx, pindex, posInBlock);
|
||||||
|
llmq::chainLocksHandler->SyncTransaction(tx, pindex, posInBlock);
|
||||||
instantsend.SyncTransaction(tx, pindex, posInBlock);
|
instantsend.SyncTransaction(tx, pindex, posInBlock);
|
||||||
CPrivateSend::SyncTransaction(tx, pindex, posInBlock);
|
CPrivateSend::SyncTransaction(tx, pindex, posInBlock);
|
||||||
}
|
}
|
||||||
@ -82,3 +90,8 @@ void CDSNotificationInterface::NotifyMasternodeListChanged(const CDeterministicM
|
|||||||
governance.CheckMasternodeOrphanVotes(connman);
|
governance.CheckMasternodeOrphanVotes(connman);
|
||||||
governance.UpdateCachesAndClean();
|
governance.UpdateCachesAndClean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDSNotificationInterface::NotifyChainLock(const CBlockIndex* pindex)
|
||||||
|
{
|
||||||
|
llmq::quorumInstantSendManager->NotifyChainLock(pindex);
|
||||||
|
}
|
||||||
|
@ -21,8 +21,10 @@ protected:
|
|||||||
void AcceptedBlockHeader(const CBlockIndex *pindexNew) override;
|
void AcceptedBlockHeader(const CBlockIndex *pindexNew) override;
|
||||||
void NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDownload) override;
|
void NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDownload) override;
|
||||||
void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override;
|
void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override;
|
||||||
|
void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& block) override;
|
||||||
void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) override;
|
void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) override;
|
||||||
void NotifyMasternodeListChanged(const CDeterministicMNList& newList) override;
|
void NotifyMasternodeListChanged(const CDeterministicMNList& newList) override;
|
||||||
|
void NotifyChainLock(const CBlockIndex* pindex) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CConnman& connman;
|
CConnman& connman;
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "validation.h"
|
#include "validation.h"
|
||||||
|
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <univalue.h>
|
#include <univalue.h>
|
||||||
|
|
||||||
@ -618,7 +620,7 @@ bool CGovernanceObject::IsCollateralValid(std::string& strError, bool& fMissingC
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((nConfirmationsIn < GOVERNANCE_FEE_CONFIRMATIONS) &&
|
if ((nConfirmationsIn < GOVERNANCE_FEE_CONFIRMATIONS) &&
|
||||||
(!instantsend.IsLockedInstantSendTransaction(nCollateralHash))) {
|
(!instantsend.IsLockedInstantSendTransaction(nCollateralHash) || llmq::quorumInstantSendManager->IsLocked(nCollateralHash))) {
|
||||||
strError = strprintf("Collateral requires at least %d confirmations to be relayed throughout the network (it has only %d)", GOVERNANCE_FEE_CONFIRMATIONS, nConfirmationsIn);
|
strError = strprintf("Collateral requires at least %d confirmations to be relayed throughout the network (it has only %d)", GOVERNANCE_FEE_CONFIRMATIONS, nConfirmationsIn);
|
||||||
if (nConfirmationsIn >= GOVERNANCE_MIN_RELAY_FEE_CONFIRMATIONS) {
|
if (nConfirmationsIn >= GOVERNANCE_MIN_RELAY_FEE_CONFIRMATIONS) {
|
||||||
fMissingConfirmations = true;
|
fMissingConfirmations = true;
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
#include "wallet/wallet.h"
|
#include "wallet/wallet.h"
|
||||||
#endif // ENABLE_WALLET
|
#endif // ENABLE_WALLET
|
||||||
|
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
#include <boost/thread.hpp>
|
#include <boost/thread.hpp>
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ const std::string CInstantSend::SERIALIZATION_VERSION_STRING = "CInstantSend-Ver
|
|||||||
void CInstantSend::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
|
void CInstantSend::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
|
||||||
{
|
{
|
||||||
if (fLiteMode) return; // disable all Dash specific functionality
|
if (fLiteMode) return; // disable all Dash specific functionality
|
||||||
if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return;
|
if (!llmq::IsOldInstantSendEnabled()) return;
|
||||||
|
|
||||||
// NOTE: NetMsgType::TXLOCKREQUEST is handled via ProcessMessage() in net_processing.cpp
|
// NOTE: NetMsgType::TXLOCKREQUEST is handled via ProcessMessage() in net_processing.cpp
|
||||||
|
|
||||||
@ -224,7 +226,7 @@ void CInstantSend::Vote(const uint256& txHash, CConnman& connman)
|
|||||||
void CInstantSend::Vote(CTxLockCandidate& txLockCandidate, CConnman& connman)
|
void CInstantSend::Vote(CTxLockCandidate& txLockCandidate, CConnman& connman)
|
||||||
{
|
{
|
||||||
if (!fMasternodeMode) return;
|
if (!fMasternodeMode) return;
|
||||||
if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return;
|
if (!llmq::IsOldInstantSendEnabled()) return;
|
||||||
|
|
||||||
AssertLockHeld(cs_main);
|
AssertLockHeld(cs_main);
|
||||||
AssertLockHeld(cs_instantsend);
|
AssertLockHeld(cs_instantsend);
|
||||||
@ -496,7 +498,7 @@ void CInstantSend::ProcessOrphanTxLockVotes()
|
|||||||
|
|
||||||
void CInstantSend::TryToFinalizeLockCandidate(const CTxLockCandidate& txLockCandidate)
|
void CInstantSend::TryToFinalizeLockCandidate(const CTxLockCandidate& txLockCandidate)
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return;
|
if (!llmq::IsOldInstantSendEnabled()) return;
|
||||||
|
|
||||||
AssertLockHeld(cs_main);
|
AssertLockHeld(cs_main);
|
||||||
AssertLockHeld(cs_instantsend);
|
AssertLockHeld(cs_instantsend);
|
||||||
@ -547,7 +549,7 @@ void CInstantSend::UpdateLockedTransaction(const CTxLockCandidate& txLockCandida
|
|||||||
|
|
||||||
void CInstantSend::LockTransactionInputs(const CTxLockCandidate& txLockCandidate)
|
void CInstantSend::LockTransactionInputs(const CTxLockCandidate& txLockCandidate)
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return;
|
if (!llmq::IsOldInstantSendEnabled()) return;
|
||||||
|
|
||||||
LOCK(cs_instantsend);
|
LOCK(cs_instantsend);
|
||||||
|
|
||||||
@ -740,6 +742,10 @@ void CInstantSend::CheckAndRemove()
|
|||||||
|
|
||||||
bool CInstantSend::AlreadyHave(const uint256& hash)
|
bool CInstantSend::AlreadyHave(const uint256& hash)
|
||||||
{
|
{
|
||||||
|
if (!llmq::IsOldInstantSendEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
LOCK(cs_instantsend);
|
LOCK(cs_instantsend);
|
||||||
return mapLockRequestAccepted.count(hash) ||
|
return mapLockRequestAccepted.count(hash) ||
|
||||||
mapLockRequestRejected.count(hash) ||
|
mapLockRequestRejected.count(hash) ||
|
||||||
@ -766,6 +772,10 @@ bool CInstantSend::HasTxLockRequest(const uint256& txHash)
|
|||||||
|
|
||||||
bool CInstantSend::GetTxLockRequest(const uint256& txHash, CTxLockRequest& txLockRequestRet)
|
bool CInstantSend::GetTxLockRequest(const uint256& txHash, CTxLockRequest& txLockRequestRet)
|
||||||
{
|
{
|
||||||
|
if (!llmq::IsOldInstantSendEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
LOCK(cs_instantsend);
|
LOCK(cs_instantsend);
|
||||||
|
|
||||||
std::map<uint256, CTxLockCandidate>::iterator it = mapTxLockCandidates.find(txHash);
|
std::map<uint256, CTxLockCandidate>::iterator it = mapTxLockCandidates.find(txHash);
|
||||||
@ -777,6 +787,10 @@ bool CInstantSend::GetTxLockRequest(const uint256& txHash, CTxLockRequest& txLoc
|
|||||||
|
|
||||||
bool CInstantSend::GetTxLockVote(const uint256& hash, CTxLockVote& txLockVoteRet)
|
bool CInstantSend::GetTxLockVote(const uint256& hash, CTxLockVote& txLockVoteRet)
|
||||||
{
|
{
|
||||||
|
if (!llmq::IsOldInstantSendEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
LOCK(cs_instantsend);
|
LOCK(cs_instantsend);
|
||||||
|
|
||||||
std::map<uint256, CTxLockVote>::iterator it = mapTxLockVotes.find(hash);
|
std::map<uint256, CTxLockVote>::iterator it = mapTxLockVotes.find(hash);
|
||||||
@ -828,7 +842,7 @@ int CInstantSend::GetTransactionLockSignatures(const uint256& txHash)
|
|||||||
{
|
{
|
||||||
if (!fEnableInstantSend) return -1;
|
if (!fEnableInstantSend) return -1;
|
||||||
if (GetfLargeWorkForkFound() || GetfLargeWorkInvalidChainFound()) return -2;
|
if (GetfLargeWorkForkFound() || GetfLargeWorkInvalidChainFound()) return -2;
|
||||||
if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return -3;
|
if (!llmq::IsOldInstantSendEnabled()) return -3;
|
||||||
|
|
||||||
LOCK(cs_instantsend);
|
LOCK(cs_instantsend);
|
||||||
|
|
||||||
@ -932,7 +946,7 @@ void CInstantSend::DoMaintenance()
|
|||||||
|
|
||||||
bool CInstantSend::CanAutoLock()
|
bool CInstantSend::CanAutoLock()
|
||||||
{
|
{
|
||||||
if (!isAutoLockBip9Active) {
|
if (!isAutoLockBip9Active || !llmq::IsOldInstantSendEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!sporkManager.IsSporkActive(SPORK_16_INSTANTSEND_AUTOLOCKS)) {
|
if (!sporkManager.IsSporkActive(SPORK_16_INSTANTSEND_AUTOLOCKS)) {
|
||||||
|
@ -45,11 +45,12 @@ extern int nCompleteTXLocks;
|
|||||||
*/
|
*/
|
||||||
class CInstantSend
|
class CInstantSend
|
||||||
{
|
{
|
||||||
private:
|
public:
|
||||||
static const std::string SERIALIZATION_VERSION_STRING;
|
|
||||||
/// Automatic locks of "simple" transactions are only allowed
|
/// Automatic locks of "simple" transactions are only allowed
|
||||||
/// when mempool usage is lower than this threshold
|
/// when mempool usage is lower than this threshold
|
||||||
static const double AUTO_IX_MEMPOOL_THRESHOLD;
|
static const double AUTO_IX_MEMPOOL_THRESHOLD;
|
||||||
|
private:
|
||||||
|
static const std::string SERIALIZATION_VERSION_STRING;
|
||||||
|
|
||||||
// Keep track of current block height
|
// Keep track of current block height
|
||||||
int nCachedBlockHeight;
|
int nCachedBlockHeight;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "quorums.h"
|
#include "quorums.h"
|
||||||
#include "quorums_chainlocks.h"
|
#include "quorums_chainlocks.h"
|
||||||
|
#include "quorums_instantsend.h"
|
||||||
#include "quorums_signing.h"
|
#include "quorums_signing.h"
|
||||||
#include "quorums_utils.h"
|
#include "quorums_utils.h"
|
||||||
|
|
||||||
@ -11,6 +12,7 @@
|
|||||||
#include "net_processing.h"
|
#include "net_processing.h"
|
||||||
#include "scheduler.h"
|
#include "scheduler.h"
|
||||||
#include "spork.h"
|
#include "spork.h"
|
||||||
|
#include "txmempool.h"
|
||||||
#include "validation.h"
|
#include "validation.h"
|
||||||
|
|
||||||
namespace llmq
|
namespace llmq
|
||||||
@ -34,12 +36,16 @@ CChainLocksHandler::~CChainLocksHandler()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void CChainLocksHandler::RegisterAsRecoveredSigsListener()
|
void CChainLocksHandler::Start()
|
||||||
{
|
{
|
||||||
quorumSigningManager->RegisterRecoveredSigsListener(this);
|
quorumSigningManager->RegisterRecoveredSigsListener(this);
|
||||||
|
scheduler->scheduleEvery([&]() {
|
||||||
|
// regularely retry signing the current chaintip as it might have failed before due to missing ixlocks
|
||||||
|
TrySignChainTip();
|
||||||
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CChainLocksHandler::UnregisterAsRecoveredSigsListener()
|
void CChainLocksHandler::Stop()
|
||||||
{
|
{
|
||||||
quorumSigningManager->UnregisterRecoveredSigsListener(this);
|
quorumSigningManager->UnregisterRecoveredSigsListener(this);
|
||||||
}
|
}
|
||||||
@ -149,6 +155,11 @@ void CChainLocksHandler::ProcessNewChainLock(NodeId from, const llmq::CChainLock
|
|||||||
|
|
||||||
LogPrintf("CChainLocksHandler::%s -- processed new CLSIG (%s), peer=%d\n",
|
LogPrintf("CChainLocksHandler::%s -- processed new CLSIG (%s), peer=%d\n",
|
||||||
__func__, clsig.ToString(), from);
|
__func__, clsig.ToString(), from);
|
||||||
|
|
||||||
|
if (lastNotifyChainLockBlockIndex != bestChainLockBlockIndex) {
|
||||||
|
lastNotifyChainLockBlockIndex = bestChainLockBlockIndex;
|
||||||
|
GetMainSignals().NotifyChainLock(bestChainLockBlockIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CChainLocksHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
|
void CChainLocksHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
|
||||||
@ -178,55 +189,145 @@ void CChainLocksHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
|
|||||||
|
|
||||||
void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork)
|
void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork)
|
||||||
{
|
{
|
||||||
|
// don't call TrySignChainTip directly but instead let the scheduler call it. This way we ensure that cs_main is
|
||||||
|
// never locked and TrySignChainTip is not called twice in parallel
|
||||||
|
LOCK(cs);
|
||||||
|
if (tryLockChainTipScheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tryLockChainTipScheduled = true;
|
||||||
|
scheduler->scheduleFromNow([&]() {
|
||||||
|
TrySignChainTip();
|
||||||
|
LOCK(cs);
|
||||||
|
tryLockChainTipScheduled = false;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CChainLocksHandler::TrySignChainTip()
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
|
||||||
|
const CBlockIndex* pindex;
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
pindex = chainActive.Tip();
|
||||||
|
}
|
||||||
|
|
||||||
if (!fMasternodeMode) {
|
if (!fMasternodeMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!pindexNew->pprev) {
|
if (!pindex->pprev) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) {
|
if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cleanup();
|
|
||||||
|
|
||||||
// DIP8 defines a process called "Signing attempts" which should run before the CLSIG is finalized
|
// DIP8 defines a process called "Signing attempts" which should run before the CLSIG is finalized
|
||||||
// To simplify the initial implementation, we skip this process and directly try to create a CLSIG
|
// To simplify the initial implementation, we skip this process and directly try to create a CLSIG
|
||||||
// This will fail when multiple blocks compete, but we accept this for the initial implementation.
|
// This will fail when multiple blocks compete, but we accept this for the initial implementation.
|
||||||
// Later, we'll add the multiple attempts process.
|
// Later, we'll add the multiple attempts process.
|
||||||
|
|
||||||
uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindexNew->nHeight));
|
|
||||||
uint256 msgHash = pindexNew->GetBlockHash();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
LOCK(cs);
|
LOCK(cs);
|
||||||
|
|
||||||
if (bestChainLockBlockIndex == pindexNew) {
|
if (bestChainLockBlockIndex == pindex) {
|
||||||
// we first got the CLSIG, then the header, and then the block was connected.
|
// we first got the CLSIG, then the header, and then the block was connected.
|
||||||
// In this case there is no need to continue here.
|
// In this case there is no need to continue here.
|
||||||
return;
|
// However, NotifyChainLock might not have been called yet, so call it now if needed
|
||||||
}
|
if (lastNotifyChainLockBlockIndex != bestChainLockBlockIndex) {
|
||||||
|
lastNotifyChainLockBlockIndex = bestChainLockBlockIndex;
|
||||||
if (InternalHasConflictingChainLock(pindexNew->nHeight, pindexNew->GetBlockHash())) {
|
GetMainSignals().NotifyChainLock(bestChainLockBlockIndex);
|
||||||
if (!inEnforceBestChainLock) {
|
|
||||||
// we accepted this block when there was no lock yet, but now a conflicting lock appeared. Invalidate it.
|
|
||||||
LogPrintf("CChainLocksHandler::%s -- conflicting lock after block was accepted, invalidating now\n",
|
|
||||||
__func__);
|
|
||||||
ScheduleInvalidateBlock(pindexNew);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bestChainLock.nHeight >= pindexNew->nHeight) {
|
if (pindex->nHeight == lastSignedHeight) {
|
||||||
|
// already signed this one
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestChainLock.nHeight >= pindex->nHeight) {
|
||||||
// already got the same CLSIG or a better one
|
// already got the same CLSIG or a better one
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pindexNew->nHeight == lastSignedHeight) {
|
if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
|
||||||
// already signed this one
|
if (!inEnforceBestChainLock) {
|
||||||
|
// we accepted this block when there was no lock yet, but now a conflicting lock appeared. Invalidate it.
|
||||||
|
LogPrintf("CChainLocksHandler::%s -- conflicting lock after block was accepted, invalidating now\n",
|
||||||
|
__func__);
|
||||||
|
ScheduleInvalidateBlock(pindex);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastSignedHeight = pindexNew->nHeight;
|
}
|
||||||
|
|
||||||
|
LogPrintf("CChainLocksHandler::%s -- trying to sign %s, height=%d\n", __func__, pindex->GetBlockHash().ToString(), pindex->nHeight);
|
||||||
|
|
||||||
|
// When the new IX system is activated, we only try to ChainLock blocks which include safe transactions. A TX is
|
||||||
|
// considered safe when it is ixlocked or at least known since 10 minutes (from mempool or block). These checks are
|
||||||
|
// performed for the tip (which we try to sign) and the previous 5 blocks. If a ChainLocked block is found on the
|
||||||
|
// way down, we consider all TXs to be safe.
|
||||||
|
if (IsNewInstantSendEnabled() && sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) {
|
||||||
|
auto pindexWalk = pindex;
|
||||||
|
while (pindexWalk) {
|
||||||
|
if (pindex->nHeight - pindexWalk->nHeight > 5) {
|
||||||
|
// no need to check further down, 6 confs is safe to assume that TXs below this height won't be
|
||||||
|
// ixlocked anymore if they aren't already
|
||||||
|
LogPrintf("CChainLocksHandler::%s -- tip and previous 5 blocks all safe\n", __func__);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) {
|
||||||
|
// we don't care about ixlocks for TXs that are ChainLocked already
|
||||||
|
LogPrintf("CChainLocksHandler::%s -- chainlock at height %d \n", __func__, pindexWalk->nHeight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(blockTxs.begin()->second) txids;
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
auto it = blockTxs.find(pindexWalk->GetBlockHash());
|
||||||
|
if (it == blockTxs.end()) {
|
||||||
|
// this should actually not happen as NewPoWValidBlock should have been called before
|
||||||
|
LogPrintf("CChainLocksHandler::%s -- blockTxs for %s not found\n", __func__,
|
||||||
|
pindexWalk->GetBlockHash().ToString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
txids = it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& txid : *txids) {
|
||||||
|
int64_t txAge = 0;
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
auto it = txFirstSeenTime.find(txid);
|
||||||
|
if (it != txFirstSeenTime.end()) {
|
||||||
|
txAge = GetAdjustedTime() - it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txAge < WAIT_FOR_ISLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) {
|
||||||
|
LogPrintf("CChainLocksHandler::%s -- not signing block %s due to TX %s not being ixlocked and not old enough. age=%d\n", __func__,
|
||||||
|
pindexWalk->GetBlockHash().ToString(), txid.ToString(), txAge);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pindexWalk = pindexWalk->pprev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindex->nHeight));
|
||||||
|
uint256 msgHash = pindex->GetBlockHash();
|
||||||
|
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
if (bestChainLock.nHeight >= pindex->nHeight) {
|
||||||
|
// might have happened while we didn't hold cs
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastSignedHeight = pindex->nHeight;
|
||||||
lastSignedRequestId = requestId;
|
lastSignedRequestId = requestId;
|
||||||
lastSignedMsgHash = msgHash;
|
lastSignedMsgHash = msgHash;
|
||||||
}
|
}
|
||||||
@ -234,6 +335,73 @@ void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBl
|
|||||||
quorumSigningManager->AsyncSignIfMember(Params().GetConsensus().llmqChainLocks, requestId, msgHash);
|
quorumSigningManager->AsyncSignIfMember(Params().GetConsensus().llmqChainLocks, requestId, msgHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CChainLocksHandler::NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr<const CBlock>& block)
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
if (blockTxs.count(pindex->GetBlockHash())) {
|
||||||
|
// should actually not happen (blocks are only written once to disk and this is when NewPoWValidBlock is called)
|
||||||
|
// but be extra safe here in case this behaviour changes.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t curTime = GetAdjustedTime();
|
||||||
|
|
||||||
|
// We listen for NewPoWValidBlock so that we can collect all TX ids of all included TXs of newly received blocks
|
||||||
|
// We need this information later when we try to sign a new tip, so that we can determine if all included TXs are
|
||||||
|
// safe.
|
||||||
|
|
||||||
|
auto txs = std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>();
|
||||||
|
for (const auto& tx : block->vtx) {
|
||||||
|
if (tx->nVersion == 3) {
|
||||||
|
if (tx->nType == TRANSACTION_COINBASE ||
|
||||||
|
tx->nType == TRANSACTION_QUORUM_COMMITMENT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txs->emplace(tx->GetHash());
|
||||||
|
txFirstSeenTime.emplace(tx->GetHash(), curTime);
|
||||||
|
}
|
||||||
|
blockTxs[pindex->GetBlockHash()] = txs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock)
|
||||||
|
{
|
||||||
|
if (tx.nVersion == 3) {
|
||||||
|
if (tx.nType == TRANSACTION_COINBASE ||
|
||||||
|
tx.nType == TRANSACTION_QUORUM_COMMITMENT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK(cs);
|
||||||
|
int64_t curTime = GetAdjustedTime();
|
||||||
|
txFirstSeenTime.emplace(tx.GetHash(), curTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CChainLocksHandler::IsTxSafeForMining(const uint256& txid)
|
||||||
|
{
|
||||||
|
if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED) || !sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t txAge = 0;
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
auto it = txFirstSeenTime.find(txid);
|
||||||
|
if (it != txFirstSeenTime.end()) {
|
||||||
|
txAge = GetAdjustedTime() - it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txAge < WAIT_FOR_ISLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// WARNING: cs_main and cs should not be held!
|
// WARNING: cs_main and cs should not be held!
|
||||||
void CChainLocksHandler::EnforceBestChainLock()
|
void CChainLocksHandler::EnforceBestChainLock()
|
||||||
{
|
{
|
||||||
@ -410,7 +578,9 @@ void CChainLocksHandler::Cleanup()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCK2(cs_main, cs);
|
// need mempool.cs due to GetTransaction calls
|
||||||
|
LOCK2(cs_main, mempool.cs);
|
||||||
|
LOCK(cs);
|
||||||
|
|
||||||
for (auto it = seenChainLocks.begin(); it != seenChainLocks.end(); ) {
|
for (auto it = seenChainLocks.begin(); it != seenChainLocks.end(); ) {
|
||||||
if (GetTimeMillis() - it->second >= CLEANUP_SEEN_TIMEOUT) {
|
if (GetTimeMillis() - it->second >= CLEANUP_SEEN_TIMEOUT) {
|
||||||
@ -420,6 +590,38 @@ void CChainLocksHandler::Cleanup()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto it = blockTxs.begin(); it != blockTxs.end(); ) {
|
||||||
|
auto pindex = mapBlockIndex.at(it->first);
|
||||||
|
if (InternalHasChainLock(pindex->nHeight, pindex->GetBlockHash())) {
|
||||||
|
for (auto& txid : *it->second) {
|
||||||
|
txFirstSeenTime.erase(txid);
|
||||||
|
}
|
||||||
|
it = blockTxs.erase(it);
|
||||||
|
} else if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
|
||||||
|
it = blockTxs.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end(); ) {
|
||||||
|
CTransactionRef tx;
|
||||||
|
uint256 hashBlock;
|
||||||
|
if (!GetTransaction(it->first, tx, Params().GetConsensus(), hashBlock)) {
|
||||||
|
// tx has vanished, probably due to conflicts
|
||||||
|
it = txFirstSeenTime.erase(it);
|
||||||
|
} else if (!hashBlock.IsNull()) {
|
||||||
|
auto pindex = mapBlockIndex.at(hashBlock);
|
||||||
|
if (chainActive.Tip()->GetAncestor(pindex->nHeight) == pindex && chainActive.Height() - pindex->nHeight >= 6) {
|
||||||
|
// tx got confirmed >= 6 times, so we can stop keeping track of it
|
||||||
|
it = txFirstSeenTime.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lastCleanupTime = GetTimeMillis();
|
lastCleanupTime = GetTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "chainparams.h"
|
#include "chainparams.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
class CBlockIndex;
|
class CBlockIndex;
|
||||||
class CScheduler;
|
class CScheduler;
|
||||||
@ -45,9 +46,13 @@ class CChainLocksHandler : public CRecoveredSigsListener
|
|||||||
static const int64_t CLEANUP_INTERVAL = 1000 * 30;
|
static const int64_t CLEANUP_INTERVAL = 1000 * 30;
|
||||||
static const int64_t CLEANUP_SEEN_TIMEOUT = 24 * 60 * 60 * 1000;
|
static const int64_t CLEANUP_SEEN_TIMEOUT = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// how long to wait for ixlocks until we consider a block with non-ixlocked TXs to be safe to sign
|
||||||
|
static const int64_t WAIT_FOR_ISLOCK_TIMEOUT = 10 * 60;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CScheduler* scheduler;
|
CScheduler* scheduler;
|
||||||
CCriticalSection cs;
|
CCriticalSection cs;
|
||||||
|
bool tryLockChainTipScheduled{false};
|
||||||
std::atomic<bool> inEnforceBestChainLock{false};
|
std::atomic<bool> inEnforceBestChainLock{false};
|
||||||
|
|
||||||
uint256 bestChainLockHash;
|
uint256 bestChainLockHash;
|
||||||
@ -55,11 +60,16 @@ private:
|
|||||||
|
|
||||||
CChainLockSig bestChainLockWithKnownBlock;
|
CChainLockSig bestChainLockWithKnownBlock;
|
||||||
const CBlockIndex* bestChainLockBlockIndex{nullptr};
|
const CBlockIndex* bestChainLockBlockIndex{nullptr};
|
||||||
|
const CBlockIndex* lastNotifyChainLockBlockIndex{nullptr};
|
||||||
|
|
||||||
int32_t lastSignedHeight{-1};
|
int32_t lastSignedHeight{-1};
|
||||||
uint256 lastSignedRequestId;
|
uint256 lastSignedRequestId;
|
||||||
uint256 lastSignedMsgHash;
|
uint256 lastSignedMsgHash;
|
||||||
|
|
||||||
|
// We keep track of txids from recently received blocks so that we can check if all TXs got ixlocked
|
||||||
|
std::unordered_map<uint256, std::shared_ptr<std::unordered_set<uint256, StaticSaltedHasher>>> blockTxs;
|
||||||
|
std::unordered_map<uint256, int64_t> txFirstSeenTime;
|
||||||
|
|
||||||
std::map<uint256, int64_t> seenChainLocks;
|
std::map<uint256, int64_t> seenChainLocks;
|
||||||
|
|
||||||
int64_t lastCleanupTime{0};
|
int64_t lastCleanupTime{0};
|
||||||
@ -68,8 +78,8 @@ public:
|
|||||||
CChainLocksHandler(CScheduler* _scheduler);
|
CChainLocksHandler(CScheduler* _scheduler);
|
||||||
~CChainLocksHandler();
|
~CChainLocksHandler();
|
||||||
|
|
||||||
void RegisterAsRecoveredSigsListener();
|
void Start();
|
||||||
void UnregisterAsRecoveredSigsListener();
|
void Stop();
|
||||||
|
|
||||||
bool AlreadyHave(const CInv& inv);
|
bool AlreadyHave(const CInv& inv);
|
||||||
bool GetChainLockByHash(const uint256& hash, CChainLockSig& ret);
|
bool GetChainLockByHash(const uint256& hash, CChainLockSig& ret);
|
||||||
@ -78,12 +88,17 @@ public:
|
|||||||
void ProcessNewChainLock(NodeId from, const CChainLockSig& clsig, const uint256& hash);
|
void ProcessNewChainLock(NodeId from, const CChainLockSig& clsig, const uint256& hash);
|
||||||
void AcceptedBlockHeader(const CBlockIndex* pindexNew);
|
void AcceptedBlockHeader(const CBlockIndex* pindexNew);
|
||||||
void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork);
|
void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork);
|
||||||
|
void NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr<const CBlock>& block);
|
||||||
|
void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock);
|
||||||
|
void TrySignChainTip();
|
||||||
void EnforceBestChainLock();
|
void EnforceBestChainLock();
|
||||||
virtual void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig);
|
virtual void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig);
|
||||||
|
|
||||||
bool HasChainLock(int nHeight, const uint256& blockHash);
|
bool HasChainLock(int nHeight, const uint256& blockHash);
|
||||||
bool HasConflictingChainLock(int nHeight, const uint256& blockHash);
|
bool HasConflictingChainLock(int nHeight, const uint256& blockHash);
|
||||||
|
|
||||||
|
bool IsTxSafeForMining(const uint256& txid);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// these require locks to be held already
|
// these require locks to be held already
|
||||||
bool InternalHasChainLock(int nHeight, const uint256& blockHash);
|
bool InternalHasChainLock(int nHeight, const uint256& blockHash);
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "quorums_chainlocks.h"
|
#include "quorums_chainlocks.h"
|
||||||
#include "quorums_debug.h"
|
#include "quorums_debug.h"
|
||||||
#include "quorums_dkgsessionmgr.h"
|
#include "quorums_dkgsessionmgr.h"
|
||||||
|
#include "quorums_instantsend.h"
|
||||||
#include "quorums_signing.h"
|
#include "quorums_signing.h"
|
||||||
#include "quorums_signing_shares.h"
|
#include "quorums_signing_shares.h"
|
||||||
|
|
||||||
@ -29,10 +30,13 @@ void InitLLMQSystem(CEvoDB& evoDb, CScheduler* scheduler, bool unitTests)
|
|||||||
quorumSigSharesManager = new CSigSharesManager();
|
quorumSigSharesManager = new CSigSharesManager();
|
||||||
quorumSigningManager = new CSigningManager(unitTests);
|
quorumSigningManager = new CSigningManager(unitTests);
|
||||||
chainLocksHandler = new CChainLocksHandler(scheduler);
|
chainLocksHandler = new CChainLocksHandler(scheduler);
|
||||||
|
quorumInstantSendManager = new CInstantSendManager(scheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DestroyLLMQSystem()
|
void DestroyLLMQSystem()
|
||||||
{
|
{
|
||||||
|
delete quorumInstantSendManager;
|
||||||
|
quorumInstantSendManager = nullptr;
|
||||||
delete chainLocksHandler;
|
delete chainLocksHandler;
|
||||||
chainLocksHandler = nullptr;
|
chainLocksHandler = nullptr;
|
||||||
delete quorumSigningManager;
|
delete quorumSigningManager;
|
||||||
@ -62,14 +66,20 @@ void StartLLMQSystem()
|
|||||||
quorumSigSharesManager->StartWorkerThread();
|
quorumSigSharesManager->StartWorkerThread();
|
||||||
}
|
}
|
||||||
if (chainLocksHandler) {
|
if (chainLocksHandler) {
|
||||||
chainLocksHandler->RegisterAsRecoveredSigsListener();
|
chainLocksHandler->Start();
|
||||||
|
}
|
||||||
|
if (quorumInstantSendManager) {
|
||||||
|
quorumInstantSendManager->RegisterAsRecoveredSigsListener();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StopLLMQSystem()
|
void StopLLMQSystem()
|
||||||
{
|
{
|
||||||
|
if (quorumInstantSendManager) {
|
||||||
|
quorumInstantSendManager->UnregisterAsRecoveredSigsListener();
|
||||||
|
}
|
||||||
if (chainLocksHandler) {
|
if (chainLocksHandler) {
|
||||||
chainLocksHandler->UnregisterAsRecoveredSigsListener();
|
chainLocksHandler->Stop();
|
||||||
}
|
}
|
||||||
if (quorumSigSharesManager) {
|
if (quorumSigSharesManager) {
|
||||||
quorumSigSharesManager->StopWorkerThread();
|
quorumSigSharesManager->StopWorkerThread();
|
||||||
|
882
src/llmq/quorums_instantsend.cpp
Normal file
882
src/llmq/quorums_instantsend.cpp
Normal file
@ -0,0 +1,882 @@
|
|||||||
|
// Copyright (c) 2019 The Dash Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include "quorums_chainlocks.h"
|
||||||
|
#include "quorums_instantsend.h"
|
||||||
|
#include "quorums_utils.h"
|
||||||
|
|
||||||
|
#include "bls/bls_batchverifier.h"
|
||||||
|
#include "chainparams.h"
|
||||||
|
#include "coins.h"
|
||||||
|
#include "txmempool.h"
|
||||||
|
#include "masternode-sync.h"
|
||||||
|
#include "net_processing.h"
|
||||||
|
#include "scheduler.h"
|
||||||
|
#include "spork.h"
|
||||||
|
#include "validation.h"
|
||||||
|
|
||||||
|
#ifdef ENABLE_WALLET
|
||||||
|
#include "wallet/wallet.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// needed for nCompleteTXLocks
|
||||||
|
#include "instantx.h"
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
|
|
||||||
|
namespace llmq
|
||||||
|
{
|
||||||
|
|
||||||
|
static const std::string INPUTLOCK_REQUESTID_PREFIX = "inlock";
|
||||||
|
static const std::string ISLOCK_REQUESTID_PREFIX = "islock";
|
||||||
|
|
||||||
|
CInstantSendManager* quorumInstantSendManager;
|
||||||
|
|
||||||
|
uint256 CInstantSendLock::GetRequestId() const
|
||||||
|
{
|
||||||
|
CHashWriter hw(SER_GETHASH, 0);
|
||||||
|
hw << ISLOCK_REQUESTID_PREFIX;
|
||||||
|
hw << inputs;
|
||||||
|
return hw.GetHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
CInstantSendManager::CInstantSendManager(CScheduler* _scheduler) :
|
||||||
|
scheduler(_scheduler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CInstantSendManager::~CInstantSendManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::RegisterAsRecoveredSigsListener()
|
||||||
|
{
|
||||||
|
quorumSigningManager->RegisterRecoveredSigsListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::UnregisterAsRecoveredSigsListener()
|
||||||
|
{
|
||||||
|
quorumSigningManager->UnregisterRecoveredSigsListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CInstantSendManager::ProcessTx(CNode* pfrom, const CTransaction& tx, CConnman& connman, const Consensus::Params& params)
|
||||||
|
{
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto llmqType = params.llmqForInstantSend;
|
||||||
|
if (llmqType == Consensus::LLMQ_NONE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!fMasternodeMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore any InstantSend messages until blockchain is synced
|
||||||
|
if (!masternodeSync.IsBlockchainSynced()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsConflicted(tx)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CheckCanLock(tx, true, params)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint256> ids;
|
||||||
|
ids.reserve(tx.vin.size());
|
||||||
|
|
||||||
|
for (const auto& in : tx.vin) {
|
||||||
|
auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout));
|
||||||
|
ids.emplace_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
size_t alreadyVotedCount = 0;
|
||||||
|
for (size_t i = 0; i < ids.size(); i++) {
|
||||||
|
auto it = inputVotes.find(ids[i]);
|
||||||
|
if (it != inputVotes.end()) {
|
||||||
|
if (it->second != tx.GetHash()) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: input %s is conflicting with islock %s\n", __func__,
|
||||||
|
tx.GetHash().ToString(), tx.vin[i].prevout.ToStringShort(), it->second.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
alreadyVotedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (alreadyVotedCount == ids.size()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& id : ids) {
|
||||||
|
inputVotes.emplace(id, tx.GetHash());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't even try the actual signing if any input is conflicting
|
||||||
|
for (auto& id : ids) {
|
||||||
|
if (quorumSigningManager->IsConflicting(llmqType, id, tx.GetHash())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& id : ids) {
|
||||||
|
quorumSigningManager->AsyncSignIfMember(llmqType, id, tx.GetHash());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We might have received all input locks before we got the corresponding TX. In this case, we have to sign the
|
||||||
|
// islock now instead of waiting for the input locks.
|
||||||
|
TrySignInstantSendLock(tx);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CInstantSendManager::CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params)
|
||||||
|
{
|
||||||
|
if (sporkManager.IsSporkActive(SPORK_16_INSTANTSEND_AUTOLOCKS) && (mempool.UsedMemoryShare() > CInstantSend::AUTO_IX_MEMPOOL_THRESHOLD)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired;
|
||||||
|
|
||||||
|
uint256 txHash = tx.GetHash();
|
||||||
|
CAmount nValueIn = 0;
|
||||||
|
for (const auto& in : tx.vin) {
|
||||||
|
CAmount v = 0;
|
||||||
|
if (!CheckCanLock(in.prevout, printDebug, &txHash, &v, params)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nValueIn += v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO decide if we should limit max input values. This was ok to do in the old system, but in the new system
|
||||||
|
// where we want to have all TXs locked at some point, this is counterproductive (especially when ChainLocks later
|
||||||
|
// depend on all TXs being locked first)
|
||||||
|
// CAmount maxValueIn = sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE);
|
||||||
|
// if (nValueIn > maxValueIn * COIN) {
|
||||||
|
// if (printDebug) {
|
||||||
|
// LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: TX input value too high. nValueIn=%f, maxValueIn=%d", __func__,
|
||||||
|
// tx.GetHash().ToString(), nValueIn / (double)COIN, maxValueIn);
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CInstantSendManager::CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256* _txHash, CAmount* retValue, const Consensus::Params& params)
|
||||||
|
{
|
||||||
|
int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired;
|
||||||
|
|
||||||
|
if (IsLocked(outpoint.hash)) {
|
||||||
|
// if prevout was ix locked, allow locking of descendants (no matter if prevout is in mempool or already mined)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint256 txHashNull;
|
||||||
|
const uint256* txHash = &txHashNull;
|
||||||
|
if (_txHash) {
|
||||||
|
txHash = _txHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mempoolTx = mempool.get(outpoint.hash);
|
||||||
|
if (mempoolTx) {
|
||||||
|
if (printDebug) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: parent mempool TX %s is not locked\n", __func__,
|
||||||
|
txHash->ToString(), outpoint.hash.ToString());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Coin coin;
|
||||||
|
const CBlockIndex* pindexMined = nullptr;
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
if (!pcoinsTip->GetCoin(outpoint, coin) || coin.IsSpent()) {
|
||||||
|
if (printDebug) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: failed to find UTXO %s\n", __func__,
|
||||||
|
txHash->ToString(), outpoint.ToStringShort());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pindexMined = chainActive[coin.nHeight];
|
||||||
|
}
|
||||||
|
|
||||||
|
int nTxAge = chainActive.Height() - coin.nHeight + 1;
|
||||||
|
// 1 less than the "send IX" gui requires, in case of a block propagating the network at the time
|
||||||
|
int nConfirmationsRequired = nInstantSendConfirmationsRequired - 1;
|
||||||
|
|
||||||
|
if (nTxAge < nConfirmationsRequired) {
|
||||||
|
if (!llmq::chainLocksHandler->HasChainLock(pindexMined->nHeight, pindexMined->GetBlockHash())) {
|
||||||
|
if (printDebug) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nConfirmationsRequired=%d\n", __func__,
|
||||||
|
txHash->ToString(), outpoint.ToStringShort(), nTxAge, nConfirmationsRequired);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retValue) {
|
||||||
|
*retValue = coin.out.nValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig)
|
||||||
|
{
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto llmqType = Params().GetConsensus().llmqForInstantSend;
|
||||||
|
if (llmqType == Consensus::LLMQ_NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& params = Params().GetConsensus().llmqs.at(llmqType);
|
||||||
|
|
||||||
|
uint256 txid;
|
||||||
|
bool isInstantSendLock = false;
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
auto it = inputVotes.find(recoveredSig.id);
|
||||||
|
if (it != inputVotes.end()) {
|
||||||
|
txid = it->second;
|
||||||
|
}
|
||||||
|
if (creatingInstantSendLocks.count(recoveredSig.id)) {
|
||||||
|
isInstantSendLock = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!txid.IsNull()) {
|
||||||
|
HandleNewInputLockRecoveredSig(recoveredSig, txid);
|
||||||
|
} else if (isInstantSendLock) {
|
||||||
|
HandleNewInstantSendLockRecoveredSig(recoveredSig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::HandleNewInputLockRecoveredSig(const CRecoveredSig& recoveredSig, const uint256& txid)
|
||||||
|
{
|
||||||
|
auto llmqType = Params().GetConsensus().llmqForInstantSend;
|
||||||
|
|
||||||
|
CTransactionRef tx;
|
||||||
|
uint256 hashBlock;
|
||||||
|
if (!GetTransaction(txid, tx, Params().GetConsensus(), hashBlock, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LogAcceptCategory("instantsend")) {
|
||||||
|
for (auto& in : tx->vin) {
|
||||||
|
auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout));
|
||||||
|
if (id == recoveredSig.id) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: got recovered sig for input %s\n", __func__,
|
||||||
|
txid.ToString(), in.prevout.ToStringShort());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TrySignInstantSendLock(*tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::TrySignInstantSendLock(const CTransaction& tx)
|
||||||
|
{
|
||||||
|
auto llmqType = Params().GetConsensus().llmqForInstantSend;
|
||||||
|
|
||||||
|
for (auto& in : tx.vin) {
|
||||||
|
auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout));
|
||||||
|
if (!quorumSigningManager->HasRecoveredSig(llmqType, id, tx.GetHash())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: got all recovered sigs, creating CInstantSendLock\n", __func__,
|
||||||
|
tx.GetHash().ToString());
|
||||||
|
|
||||||
|
CInstantSendLockInfo islockInfo;
|
||||||
|
islockInfo.time = GetTimeMillis();
|
||||||
|
islockInfo.islock.txid = tx.GetHash();
|
||||||
|
for (auto& in : tx.vin) {
|
||||||
|
islockInfo.islock.inputs.emplace_back(in.prevout);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto id = islockInfo.islock.GetRequestId();
|
||||||
|
|
||||||
|
if (quorumSigningManager->HasRecoveredSigForId(llmqType, id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
auto e = creatingInstantSendLocks.emplace(id, islockInfo);
|
||||||
|
if (!e.second) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
txToCreatingInstantSendLocks.emplace(tx.GetHash(), &e.first->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
quorumSigningManager->AsyncSignIfMember(llmqType, id, tx.GetHash());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::HandleNewInstantSendLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
|
||||||
|
{
|
||||||
|
CInstantSendLockInfo islockInfo;
|
||||||
|
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
auto it = creatingInstantSendLocks.find(recoveredSig.id);
|
||||||
|
if (it == creatingInstantSendLocks.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
islockInfo = std::move(it->second);
|
||||||
|
creatingInstantSendLocks.erase(it);
|
||||||
|
txToCreatingInstantSendLocks.erase(islockInfo.islock.txid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (islockInfo.islock.txid != recoveredSig.msgHash) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: islock conflicts with %s, dropping own version", __func__,
|
||||||
|
islockInfo.islock.txid.ToString(), recoveredSig.msgHash.ToString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
islockInfo.islock.sig = recoveredSig.sig;
|
||||||
|
ProcessInstantSendLock(-1, ::SerializeHash(islockInfo.islock), islockInfo.islock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
|
||||||
|
{
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strCommand == NetMsgType::ISLOCK) {
|
||||||
|
CInstantSendLock islock;
|
||||||
|
vRecv >> islock;
|
||||||
|
ProcessMessageInstantSendLock(pfrom, islock, connman);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::ProcessMessageInstantSendLock(CNode* pfrom, const llmq::CInstantSendLock& islock, CConnman& connman)
|
||||||
|
{
|
||||||
|
bool ban = false;
|
||||||
|
if (!PreVerifyInstantSendLock(pfrom->id, islock, ban)) {
|
||||||
|
if (ban) {
|
||||||
|
LOCK(cs_main);
|
||||||
|
Misbehaving(pfrom->id, 100);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hash = ::SerializeHash(islock);
|
||||||
|
|
||||||
|
LOCK(cs);
|
||||||
|
if (pendingInstantSendLocks.count(hash) || finalInstantSendLocks.count(hash)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: received islock, peer=%d\n", __func__,
|
||||||
|
islock.txid.ToString(), hash.ToString(), pfrom->id);
|
||||||
|
|
||||||
|
pendingInstantSendLocks.emplace(hash, std::make_pair(pfrom->id, std::move(islock)));
|
||||||
|
|
||||||
|
if (!hasScheduledProcessPending) {
|
||||||
|
hasScheduledProcessPending = true;
|
||||||
|
scheduler->scheduleFromNow([&] {
|
||||||
|
ProcessPendingInstantSendLocks();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CInstantSendManager::PreVerifyInstantSendLock(NodeId nodeId, const llmq::CInstantSendLock& islock, bool& retBan)
|
||||||
|
{
|
||||||
|
retBan = false;
|
||||||
|
|
||||||
|
if (islock.txid.IsNull() || !islock.sig.IsValid() || islock.inputs.empty()) {
|
||||||
|
retBan = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<COutPoint> dups;
|
||||||
|
for (auto& o : islock.inputs) {
|
||||||
|
if (!dups.emplace(o).second) {
|
||||||
|
retBan = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::ProcessPendingInstantSendLocks()
|
||||||
|
{
|
||||||
|
auto llmqType = Params().GetConsensus().llmqForInstantSend;
|
||||||
|
|
||||||
|
decltype(pendingInstantSendLocks) pend;
|
||||||
|
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
hasScheduledProcessPending = false;
|
||||||
|
pend = std::move(pendingInstantSendLocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tipHeight;
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
tipHeight = chainActive.Height();
|
||||||
|
}
|
||||||
|
|
||||||
|
CBLSBatchVerifier<NodeId, uint256> batchVerifier(false, true, 8);
|
||||||
|
std::unordered_map<uint256, std::pair<CQuorumCPtr, CRecoveredSig>> recSigs;
|
||||||
|
|
||||||
|
for (const auto& p : pend) {
|
||||||
|
auto& hash = p.first;
|
||||||
|
auto nodeId = p.second.first;
|
||||||
|
auto& islock = p.second.second;
|
||||||
|
|
||||||
|
auto id = islock.GetRequestId();
|
||||||
|
|
||||||
|
// no need to verify an ISLOCK if we already have verified the recovered sig that belongs to it
|
||||||
|
if (quorumSigningManager->HasRecoveredSig(llmqType, id, islock.txid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto quorum = quorumSigningManager->SelectQuorumForSigning(llmqType, tipHeight, id);
|
||||||
|
if (!quorum) {
|
||||||
|
// should not happen, but if one fails to select, all others will also fail to select
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint256 signHash = CLLMQUtils::BuildSignHash(llmqType, quorum->quorumHash, id, islock.txid);
|
||||||
|
batchVerifier.PushMessage(nodeId, hash, signHash, islock.sig, quorum->quorumPublicKey);
|
||||||
|
|
||||||
|
// We can reconstruct the CRecoveredSig objects from the islock and pass it to the signing manager, which
|
||||||
|
// avoids unnecessary double-verification of the signature. We however only do this when verification here
|
||||||
|
// turns out to be good (which is checked further down)
|
||||||
|
if (!quorumSigningManager->HasRecoveredSigForId(llmqType, id)) {
|
||||||
|
CRecoveredSig recSig;
|
||||||
|
recSig.llmqType = llmqType;
|
||||||
|
recSig.quorumHash = quorum->quorumHash;
|
||||||
|
recSig.id = id;
|
||||||
|
recSig.msgHash = islock.txid;
|
||||||
|
recSig.sig = islock.sig;
|
||||||
|
recSigs.emplace(std::piecewise_construct,
|
||||||
|
std::forward_as_tuple(hash),
|
||||||
|
std::forward_as_tuple(std::move(quorum), std::move(recSig)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batchVerifier.Verify();
|
||||||
|
|
||||||
|
if (!batchVerifier.badSources.empty()) {
|
||||||
|
LOCK(cs_main);
|
||||||
|
for (auto& nodeId : batchVerifier.badSources) {
|
||||||
|
// Let's not be too harsh, as the peer might simply be unlucky and might have sent us an old lock which
|
||||||
|
// does not validate anymore due to changed quorums
|
||||||
|
Misbehaving(nodeId, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& p : pend) {
|
||||||
|
auto& hash = p.first;
|
||||||
|
auto nodeId = p.second.first;
|
||||||
|
auto& islock = p.second.second;
|
||||||
|
|
||||||
|
if (batchVerifier.badMessages.count(hash)) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: invalid sig in islock, peer=%d\n", __func__,
|
||||||
|
islock.txid.ToString(), hash.ToString(), nodeId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessInstantSendLock(nodeId, hash, islock);
|
||||||
|
|
||||||
|
// See comment further on top. We pass a reconstructed recovered sig to the signing manager to avoid
|
||||||
|
// double-verification of the sig.
|
||||||
|
auto it = recSigs.find(hash);
|
||||||
|
if (it != recSigs.end()) {
|
||||||
|
auto& quorum = it->second.first;
|
||||||
|
auto& recSig = it->second.second;
|
||||||
|
if (!quorumSigningManager->HasRecoveredSigForId(llmqType, recSig.id)) {
|
||||||
|
recSig.UpdateHash();
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: passing reconstructed recSig to signing mgr, peer=%d\n", __func__,
|
||||||
|
islock.txid.ToString(), hash.ToString(), nodeId);
|
||||||
|
quorumSigningManager->PushReconstructedRecoveredSig(recSig, quorum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLock& islock)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
g_connman->RemoveAskFor(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
CInstantSendLockInfo islockInfo;
|
||||||
|
islockInfo.time = GetTimeMillis();
|
||||||
|
islockInfo.islock = islock;
|
||||||
|
islockInfo.islockHash = hash;
|
||||||
|
|
||||||
|
uint256 hashBlock;
|
||||||
|
// we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally
|
||||||
|
if (GetTransaction(islock.txid, islockInfo.tx, Params().GetConsensus(), hashBlock)) {
|
||||||
|
if (!hashBlock.IsNull()) {
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
islockInfo.pindexMined = mapBlockIndex.at(hashBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes,
|
||||||
|
// we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain
|
||||||
|
if (llmq::chainLocksHandler->HasChainLock(islockInfo.pindexMined->nHeight, islockInfo.pindexMined->GetBlockHash())) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a ChainLock in block %s, peer=%d\n", __func__,
|
||||||
|
islock.txid.ToString(), hash.ToString(), hashBlock.ToString(), from);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
auto e = finalInstantSendLocks.emplace(hash, islockInfo);
|
||||||
|
if (!e.second) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto islockInfoPtr = &e.first->second;
|
||||||
|
|
||||||
|
creatingInstantSendLocks.erase(islockInfoPtr->islock.GetRequestId());
|
||||||
|
txToCreatingInstantSendLocks.erase(islockInfoPtr->islock.txid);
|
||||||
|
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: processsing islock, peer=%d\n", __func__,
|
||||||
|
islock.txid.ToString(), hash.ToString(), from);
|
||||||
|
|
||||||
|
if (!txToInstantSendLock.emplace(islock.txid, islockInfoPtr).second) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: duplicate islock, other islock=%s, peer=%d\n", __func__,
|
||||||
|
islock.txid.ToString(), hash.ToString(), txToInstantSendLock[islock.txid]->islockHash.ToString(), from);
|
||||||
|
txToInstantSendLock.erase(hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < islock.inputs.size(); i++) {
|
||||||
|
auto& in = islock.inputs[i];
|
||||||
|
if (!inputToInstantSendLock.emplace(in, islockInfoPtr).second) {
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: conflicting input in islock. input=%s, other islock=%s, peer=%d\n", __func__,
|
||||||
|
islock.txid.ToString(), hash.ToString(), in.ToStringShort(), inputToInstantSendLock[in]->islockHash.ToString(), from);
|
||||||
|
txToInstantSendLock.erase(hash);
|
||||||
|
for (size_t j = 0; j < i; j++) {
|
||||||
|
inputToInstantSendLock.erase(islock.inputs[j]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CInv inv(MSG_ISLOCK, hash);
|
||||||
|
g_connman->RelayInv(inv);
|
||||||
|
|
||||||
|
RemoveMempoolConflictsForLock(hash, islock);
|
||||||
|
RetryLockMempoolTxs(islock.txid);
|
||||||
|
|
||||||
|
UpdateWalletTransaction(islock.txid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::UpdateWalletTransaction(const uint256& txid)
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_WALLET
|
||||||
|
if (!pwalletMain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pwalletMain->UpdatedTransaction(txid)) {
|
||||||
|
// bumping this to update UI
|
||||||
|
nCompleteTXLocks++;
|
||||||
|
// notify an external script once threshold is reached
|
||||||
|
std::string strCmd = GetArg("-instantsendnotify", "");
|
||||||
|
if (!strCmd.empty()) {
|
||||||
|
boost::replace_all(strCmd, "%s", txid.GetHex());
|
||||||
|
boost::thread t(runCommand, strCmd); // thread runs free
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOCK(cs);
|
||||||
|
auto it = txToInstantSendLock.find(txid);
|
||||||
|
if (it == txToInstantSendLock.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (it->second->tx == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetMainSignals().NotifyTransactionLock(*it->second->tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock)
|
||||||
|
{
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
auto it = txToInstantSendLock.find(tx.GetHash());
|
||||||
|
if (it == txToInstantSendLock.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto islockInfo = it->second;
|
||||||
|
if (islockInfo->tx == nullptr) {
|
||||||
|
islockInfo->tx = MakeTransactionRef(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK) {
|
||||||
|
UpdateISLockMinedBlock(islockInfo, nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UpdateISLockMinedBlock(islockInfo, pindex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsLocked(tx.GetHash())) {
|
||||||
|
RetryLockMempoolTxs(tx.GetHash());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindex)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
|
||||||
|
// Let's find all islocks that correspond to TXs which are part of the freshly ChainLocked chain and then delete
|
||||||
|
// the islocks. We do this because the ChainLocks imply locking and thus it's not needed to further track
|
||||||
|
// or propagate the islocks
|
||||||
|
std::unordered_set<uint256> toDelete;
|
||||||
|
while (pindex && pindex != pindexLastChainLock) {
|
||||||
|
auto its = blockToInstantSendLocks.equal_range(pindex->GetBlockHash());
|
||||||
|
while (its.first != its.second) {
|
||||||
|
auto islockInfo = its.first->second;
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: removing islock as it got ChainLocked in block %s\n", __func__,
|
||||||
|
islockInfo->islock.txid.ToString(), islockInfo->islockHash.ToString(), pindex->GetBlockHash().ToString());
|
||||||
|
toDelete.emplace(its.first->second->islockHash);
|
||||||
|
++its.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
pindex = pindex->pprev;
|
||||||
|
}
|
||||||
|
|
||||||
|
pindexLastChainLock = pindex;
|
||||||
|
|
||||||
|
for (auto& islockHash : toDelete) {
|
||||||
|
RemoveFinalISLock(islockHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RetryLockMempoolTxs(uint256());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::UpdateISLockMinedBlock(llmq::CInstantSendLockInfo* islockInfo, const CBlockIndex* pindex)
|
||||||
|
{
|
||||||
|
AssertLockHeld(cs);
|
||||||
|
|
||||||
|
if (islockInfo->pindexMined == pindex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (islockInfo->pindexMined) {
|
||||||
|
auto its = blockToInstantSendLocks.equal_range(islockInfo->pindexMined->GetBlockHash());
|
||||||
|
while (its.first != its.second) {
|
||||||
|
if (its.first->second == islockInfo) {
|
||||||
|
its.first = blockToInstantSendLocks.erase(its.first);
|
||||||
|
} else {
|
||||||
|
++its.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pindex) {
|
||||||
|
blockToInstantSendLocks.emplace(pindex->GetBlockHash(), islockInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
islockInfo->pindexMined = pindex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::RemoveFinalISLock(const uint256& hash)
|
||||||
|
{
|
||||||
|
AssertLockHeld(cs);
|
||||||
|
|
||||||
|
auto it = finalInstantSendLocks.find(hash);
|
||||||
|
if (it == finalInstantSendLocks.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& islockInfo = it->second;
|
||||||
|
|
||||||
|
txToInstantSendLock.erase(islockInfo.islock.txid);
|
||||||
|
for (auto& in : islockInfo.islock.inputs) {
|
||||||
|
auto inputRequestId = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in));
|
||||||
|
inputVotes.erase(inputRequestId);
|
||||||
|
inputToInstantSendLock.erase(in);
|
||||||
|
}
|
||||||
|
UpdateISLockMinedBlock(&islockInfo, nullptr);
|
||||||
|
finalInstantSendLocks.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::RemoveMempoolConflictsForLock(const uint256& hash, const CInstantSendLock& islock)
|
||||||
|
{
|
||||||
|
LOCK(mempool.cs);
|
||||||
|
|
||||||
|
std::unordered_map<uint256, CTransactionRef> toDelete;
|
||||||
|
|
||||||
|
for (auto& in : islock.inputs) {
|
||||||
|
auto it = mempool.mapNextTx.find(in);
|
||||||
|
if (it == mempool.mapNextTx.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it->second->GetHash() != islock.txid) {
|
||||||
|
toDelete.emplace(it->second->GetHash(), mempool.get(it->second->GetHash()));
|
||||||
|
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: mempool TX %s with input %s conflicts with islock\n", __func__,
|
||||||
|
islock.txid.ToString(), hash.ToString(), it->second->GetHash().ToString(), in.ToStringShort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& p : toDelete) {
|
||||||
|
mempool.removeRecursive(*p.second, MemPoolRemovalReason::CONFLICT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CInstantSendManager::RetryLockMempoolTxs(const uint256& lockedParentTx)
|
||||||
|
{
|
||||||
|
// Let's retry all mempool TXs which don't have an islock yet and where the parents got ChainLocked now
|
||||||
|
|
||||||
|
std::unordered_map<uint256, CTransactionRef> txs;
|
||||||
|
|
||||||
|
{
|
||||||
|
LOCK(mempool.cs);
|
||||||
|
|
||||||
|
if (lockedParentTx.IsNull()) {
|
||||||
|
txs.reserve(mempool.mapTx.size());
|
||||||
|
for (auto it = mempool.mapTx.begin(); it != mempool.mapTx.end(); ++it) {
|
||||||
|
txs.emplace(it->GetTx().GetHash(), it->GetSharedTx());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto it = mempool.mapNextTx.lower_bound(COutPoint(lockedParentTx, 0));
|
||||||
|
while (it != mempool.mapNextTx.end() && it->first->hash == lockedParentTx) {
|
||||||
|
txs.emplace(it->second->GetHash(), mempool.get(it->second->GetHash()));
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& p : txs) {
|
||||||
|
auto& tx = p.second;
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
if (txToCreatingInstantSendLocks.count(tx->GetHash())) {
|
||||||
|
// we're already in the middle of locking this one
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (IsLocked(tx->GetHash())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (IsConflicted(*tx)) {
|
||||||
|
// should not really happen as we have already filtered these out
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckCanLock is already called by ProcessTx, so we should avoid calling it twice. But we also shouldn't spam
|
||||||
|
// the logs when retrying TXs that are not ready yet.
|
||||||
|
if (LogAcceptCategory("instantsend")) {
|
||||||
|
if (!CheckCanLock(*tx, false, Params().GetConsensus())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: retrying to lock\n", __func__,
|
||||||
|
tx->GetHash().ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessTx(nullptr, *tx, *g_connman, Params().GetConsensus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CInstantSendManager::AlreadyHave(const CInv& inv)
|
||||||
|
{
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK(cs);
|
||||||
|
return finalInstantSendLocks.count(inv.hash) != 0 || pendingInstantSendLocks.count(inv.hash) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CInstantSendManager::GetInstantSendLockByHash(const uint256& hash, llmq::CInstantSendLock& ret)
|
||||||
|
{
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK(cs);
|
||||||
|
auto it = finalInstantSendLocks.find(hash);
|
||||||
|
if (it == finalInstantSendLocks.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ret = it->second.islock;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CInstantSendManager::IsLocked(const uint256& txHash)
|
||||||
|
{
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK(cs);
|
||||||
|
return txToInstantSendLock.count(txHash) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CInstantSendManager::IsConflicted(const CTransaction& tx)
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
uint256 dummy;
|
||||||
|
return GetConflictingTx(tx, dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CInstantSendManager::GetConflictingTx(const CTransaction& tx, uint256& retConflictTxHash)
|
||||||
|
{
|
||||||
|
if (!IsNewInstantSendEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK(cs);
|
||||||
|
for (const auto& in : tx.vin) {
|
||||||
|
auto it = inputToInstantSendLock.find(in.prevout);
|
||||||
|
if (it == inputToInstantSendLock.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it->second->islock.txid != tx.GetHash()) {
|
||||||
|
retConflictTxHash = it->second->islock.txid;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsOldInstantSendEnabled()
|
||||||
|
{
|
||||||
|
return sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED) && !sporkManager.IsSporkActive(SPORK_20_INSTANTSEND_LLMQ_BASED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsNewInstantSendEnabled()
|
||||||
|
{
|
||||||
|
return sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED) && sporkManager.IsSporkActive(SPORK_20_INSTANTSEND_LLMQ_BASED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInstantSendEnabled()
|
||||||
|
{
|
||||||
|
return sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
147
src/llmq/quorums_instantsend.h
Normal file
147
src/llmq/quorums_instantsend.h
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// Copyright (c) 2019 The Dash Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#ifndef DASH_QUORUMS_INSTANTSEND_H
|
||||||
|
#define DASH_QUORUMS_INSTANTSEND_H
|
||||||
|
|
||||||
|
#include "quorums_signing.h"
|
||||||
|
|
||||||
|
#include "coins.h"
|
||||||
|
#include "primitives/transaction.h"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
class CScheduler;
|
||||||
|
|
||||||
|
namespace llmq
|
||||||
|
{
|
||||||
|
|
||||||
|
class CInstantSendLock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<COutPoint> inputs;
|
||||||
|
uint256 txid;
|
||||||
|
CBLSSignature sig;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ADD_SERIALIZE_METHODS
|
||||||
|
|
||||||
|
template<typename Stream, typename Operation>
|
||||||
|
inline void SerializationOp(Stream& s, Operation ser_action)
|
||||||
|
{
|
||||||
|
READWRITE(inputs);
|
||||||
|
READWRITE(txid);
|
||||||
|
READWRITE(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 GetRequestId() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CInstantSendLockInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// might be nullptr when islock is received before the TX itself
|
||||||
|
CTransactionRef tx;
|
||||||
|
CInstantSendLock islock;
|
||||||
|
// only valid when recovered sig was received
|
||||||
|
uint256 islockHash;
|
||||||
|
// time when it was created/received
|
||||||
|
int64_t time;
|
||||||
|
|
||||||
|
// might be null initially (when TX was not mined yet) and will later be filled by SyncTransaction
|
||||||
|
const CBlockIndex* pindexMined{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
class CInstantSendManager : public CRecoveredSigsListener
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
CCriticalSection cs;
|
||||||
|
CScheduler* scheduler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the votes/signatures we performed locally. It's indexed by the LLMQ requestId, which is
|
||||||
|
* hash(TXLOCK_REQUESTID_PREFIX, prevout). The map values are the txids we voted for. This map is used to
|
||||||
|
* avoid voting for the same input twice.
|
||||||
|
*/
|
||||||
|
std::unordered_map<uint256, uint256, StaticSaltedHasher> inputVotes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the islocks that are currently in the middle of being created. Entries are created when we observed
|
||||||
|
* recovered signatures for all inputs of a TX. At the same time, we initiate signing of our sigshare for the islock.
|
||||||
|
* When the recovered sig for the islock later arrives, we can finish the islock and propagate it.
|
||||||
|
*/
|
||||||
|
std::unordered_map<uint256, CInstantSendLockInfo, StaticSaltedHasher> creatingInstantSendLocks;
|
||||||
|
// maps from txid to the in-progress islock
|
||||||
|
std::unordered_map<uint256, CInstantSendLockInfo*, StaticSaltedHasher> txToCreatingInstantSendLocks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the final islocks, indexed by their own hash. The other maps are used to get from TXs, inputs and blocks
|
||||||
|
* to islocks.
|
||||||
|
*/
|
||||||
|
std::unordered_map<uint256, CInstantSendLockInfo, StaticSaltedHasher> finalInstantSendLocks;
|
||||||
|
std::unordered_map<uint256, CInstantSendLockInfo*, StaticSaltedHasher> txToInstantSendLock;
|
||||||
|
std::unordered_map<COutPoint, CInstantSendLockInfo*, SaltedOutpointHasher> inputToInstantSendLock;
|
||||||
|
std::unordered_multimap<uint256, CInstantSendLockInfo*, StaticSaltedHasher> blockToInstantSendLocks;
|
||||||
|
|
||||||
|
const CBlockIndex* pindexLastChainLock{nullptr};
|
||||||
|
|
||||||
|
// Incoming and not verified yet
|
||||||
|
std::unordered_map<uint256, std::pair<NodeId, CInstantSendLock>> pendingInstantSendLocks;
|
||||||
|
bool hasScheduledProcessPending{false};
|
||||||
|
|
||||||
|
public:
|
||||||
|
CInstantSendManager(CScheduler* _scheduler);
|
||||||
|
~CInstantSendManager();
|
||||||
|
|
||||||
|
void RegisterAsRecoveredSigsListener();
|
||||||
|
void UnregisterAsRecoveredSigsListener();
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool ProcessTx(CNode* pfrom, const CTransaction& tx, CConnman& connman, const Consensus::Params& params);
|
||||||
|
bool CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params);
|
||||||
|
bool CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256* _txHash, CAmount* retValue, const Consensus::Params& params);
|
||||||
|
bool IsLocked(const uint256& txHash);
|
||||||
|
bool IsConflicted(const CTransaction& tx);
|
||||||
|
bool GetConflictingTx(const CTransaction& tx, uint256& retConflictTxHash);
|
||||||
|
|
||||||
|
virtual void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig);
|
||||||
|
void HandleNewInputLockRecoveredSig(const CRecoveredSig& recoveredSig, const uint256& txid);
|
||||||
|
void HandleNewInstantSendLockRecoveredSig(const CRecoveredSig& recoveredSig);
|
||||||
|
|
||||||
|
void TrySignInstantSendLock(const CTransaction& tx);
|
||||||
|
|
||||||
|
void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman);
|
||||||
|
void ProcessMessageInstantSendLock(CNode* pfrom, const CInstantSendLock& islock, CConnman& connman);
|
||||||
|
bool PreVerifyInstantSendLock(NodeId nodeId, const CInstantSendLock& islock, bool& retBan);
|
||||||
|
void ProcessPendingInstantSendLocks();
|
||||||
|
void ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLock& islock);
|
||||||
|
void UpdateWalletTransaction(const uint256& txid);
|
||||||
|
|
||||||
|
void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock);
|
||||||
|
void NotifyChainLock(const CBlockIndex* pindex);
|
||||||
|
void UpdateISLockMinedBlock(CInstantSendLockInfo* islockInfo, const CBlockIndex* pindex);
|
||||||
|
void RemoveFinalISLock(const uint256& hash);
|
||||||
|
|
||||||
|
void RemoveMempoolConflictsForLock(const uint256& hash, const CInstantSendLock& islock);
|
||||||
|
void RetryLockMempoolTxs(const uint256& lockedParentTx);
|
||||||
|
|
||||||
|
bool AlreadyHave(const CInv& inv);
|
||||||
|
bool GetInstantSendLockByHash(const uint256& hash, CInstantSendLock& ret);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CInstantSendManager* quorumInstantSendManager;
|
||||||
|
|
||||||
|
// This involves 2 sporks: SPORK_2_INSTANTSEND_ENABLED and SPORK_20_INSTANTSEND_LLMQ_BASED
|
||||||
|
// SPORK_2_INSTANTSEND_ENABLED generally enables/disables InstantSend and SPORK_20_INSTANTSEND_LLMQ_BASED switches
|
||||||
|
// between the old and the new (LLMQ based) system
|
||||||
|
// TODO When the new system is fully deployed and enabled, we can remove this special handling in a future version
|
||||||
|
// and revert to only using SPORK_2_INSTANTSEND_ENABLED.
|
||||||
|
bool IsOldInstantSendEnabled();
|
||||||
|
bool IsNewInstantSendEnabled();
|
||||||
|
bool IsInstantSendEnabled();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif//DASH_QUORUMS_INSTANTSEND_H
|
@ -440,11 +440,25 @@ void CSigningManager::CollectPendingRecoveredSigsToVerify(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSigningManager::ProcessPendingReconstructedRecoveredSigs()
|
||||||
|
{
|
||||||
|
decltype(pendingReconstructedRecoveredSigs) l;
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
l = std::move(pendingReconstructedRecoveredSigs);
|
||||||
|
}
|
||||||
|
for (auto& p : l) {
|
||||||
|
ProcessRecoveredSig(-1, p.first, p.second, *g_connman);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CSigningManager::ProcessPendingRecoveredSigs(CConnman& connman)
|
bool CSigningManager::ProcessPendingRecoveredSigs(CConnman& connman)
|
||||||
{
|
{
|
||||||
std::unordered_map<NodeId, std::list<CRecoveredSig>> recSigsByNode;
|
std::unordered_map<NodeId, std::list<CRecoveredSig>> recSigsByNode;
|
||||||
std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr, StaticSaltedHasher> quorums;
|
std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr, StaticSaltedHasher> quorums;
|
||||||
|
|
||||||
|
ProcessPendingReconstructedRecoveredSigs();
|
||||||
|
|
||||||
CollectPendingRecoveredSigsToVerify(32, recSigsByNode, quorums);
|
CollectPendingRecoveredSigsToVerify(32, recSigsByNode, quorums);
|
||||||
if (recSigsByNode.empty()) {
|
if (recSigsByNode.empty()) {
|
||||||
return false;
|
return false;
|
||||||
@ -529,7 +543,7 @@ void CSigningManager::ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& re
|
|||||||
} else {
|
} else {
|
||||||
// Looks like we're trying to process a recSig that is already known. This might happen if the same
|
// Looks like we're trying to process a recSig that is already known. This might happen if the same
|
||||||
// recSig comes in through regular QRECSIG messages and at the same time through some other message
|
// recSig comes in through regular QRECSIG messages and at the same time through some other message
|
||||||
// which allowed to reconstruct a recSig (e.g. IXLOCK). In this case, just bail out.
|
// which allowed to reconstruct a recSig (e.g. ISLOCK). In this case, just bail out.
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@ -550,6 +564,12 @@ void CSigningManager::ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSigningManager::PushReconstructedRecoveredSig(const llmq::CRecoveredSig& recoveredSig, const llmq::CQuorumCPtr& quorum)
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
pendingReconstructedRecoveredSigs.emplace_back(recoveredSig, quorum);
|
||||||
|
}
|
||||||
|
|
||||||
void CSigningManager::Cleanup()
|
void CSigningManager::Cleanup()
|
||||||
{
|
{
|
||||||
int64_t now = GetTimeMillis();
|
int64_t now = GetTimeMillis();
|
||||||
|
@ -126,6 +126,7 @@ private:
|
|||||||
|
|
||||||
// Incoming and not verified yet
|
// Incoming and not verified yet
|
||||||
std::unordered_map<NodeId, std::list<CRecoveredSig>> pendingRecoveredSigs;
|
std::unordered_map<NodeId, std::list<CRecoveredSig>> pendingRecoveredSigs;
|
||||||
|
std::list<std::pair<CRecoveredSig, CQuorumCPtr>> pendingReconstructedRecoveredSigs;
|
||||||
|
|
||||||
// must be protected by cs
|
// must be protected by cs
|
||||||
FastRandomContext rnd;
|
FastRandomContext rnd;
|
||||||
@ -142,6 +143,10 @@ public:
|
|||||||
|
|
||||||
void ProcessMessage(CNode* pnode, const std::string& strCommand, CDataStream& vRecv, CConnman& connman);
|
void ProcessMessage(CNode* pnode, const std::string& strCommand, CDataStream& vRecv, CConnman& connman);
|
||||||
|
|
||||||
|
// This is called when a recovered signature was was reconstructed from another P2P message and is known to be valid
|
||||||
|
// This is the case for example when a signature appears as part of InstantSend or ChainLocks
|
||||||
|
void PushReconstructedRecoveredSig(const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredSig& recoveredSig, CConnman& connman);
|
void ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredSig& recoveredSig, CConnman& connman);
|
||||||
bool PreVerifyRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, bool& retBan);
|
bool PreVerifyRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, bool& retBan);
|
||||||
@ -149,6 +154,7 @@ private:
|
|||||||
void CollectPendingRecoveredSigsToVerify(size_t maxUniqueSessions,
|
void CollectPendingRecoveredSigsToVerify(size_t maxUniqueSessions,
|
||||||
std::unordered_map<NodeId, std::list<CRecoveredSig>>& retSigShares,
|
std::unordered_map<NodeId, std::list<CRecoveredSig>>& retSigShares,
|
||||||
std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr, StaticSaltedHasher>& retQuorums);
|
std::unordered_map<std::pair<Consensus::LLMQType, uint256>, CQuorumCPtr, StaticSaltedHasher>& retQuorums);
|
||||||
|
void ProcessPendingReconstructedRecoveredSigs();
|
||||||
bool ProcessPendingRecoveredSigs(CConnman& connman); // called from the worker thread of CSigSharesManager
|
bool ProcessPendingRecoveredSigs(CConnman& connman); // called from the worker thread of CSigSharesManager
|
||||||
void ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum, CConnman& connman);
|
void ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum, CConnman& connman);
|
||||||
void Cleanup(); // called from the worker thread of CSigSharesManager
|
void Cleanup(); // called from the worker thread of CSigSharesManager
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include "evo/deterministicmns.h"
|
#include "evo/deterministicmns.h"
|
||||||
|
|
||||||
#include "llmq/quorums_blockprocessor.h"
|
#include "llmq/quorums_blockprocessor.h"
|
||||||
|
#include "llmq/quorums_chainlocks.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <boost/thread.hpp>
|
#include <boost/thread.hpp>
|
||||||
@ -286,11 +287,15 @@ bool BlockAssembler::TestPackage(uint64_t packageSize, unsigned int packageSigOp
|
|||||||
|
|
||||||
// Perform transaction-level checks before adding to block:
|
// Perform transaction-level checks before adding to block:
|
||||||
// - transaction finality (locktime)
|
// - transaction finality (locktime)
|
||||||
|
// - safe TXs in regard to ChainLocks
|
||||||
bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package)
|
bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package)
|
||||||
{
|
{
|
||||||
BOOST_FOREACH (const CTxMemPool::txiter it, package) {
|
BOOST_FOREACH (const CTxMemPool::txiter it, package) {
|
||||||
if (!IsFinalTx(it->GetTx(), nHeight, nLockTimeCutoff))
|
if (!IsFinalTx(it->GetTx(), nHeight, nLockTimeCutoff))
|
||||||
return false;
|
return false;
|
||||||
|
if (!llmq::chainLocksHandler->IsTxSafeForMining(it->GetTx().GetHash())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -332,6 +337,10 @@ bool BlockAssembler::TestForBlock(CTxMemPool::txiter iter)
|
|||||||
if (!IsFinalTx(iter->GetTx(), nHeight, nLockTimeCutoff))
|
if (!IsFinalTx(iter->GetTx(), nHeight, nLockTimeCutoff))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!llmq::chainLocksHandler->IsTxSafeForMining(iter->GetTx().GetHash())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,7 +532,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda
|
|||||||
onlyUnconfirmed(ancestors);
|
onlyUnconfirmed(ancestors);
|
||||||
ancestors.insert(iter);
|
ancestors.insert(iter);
|
||||||
|
|
||||||
// Test if all tx's are Final
|
// Test if all tx's are Final and safe
|
||||||
if (!TestPackageTransactions(ancestors)) {
|
if (!TestPackageTransactions(ancestors)) {
|
||||||
if (fUsingModified) {
|
if (fUsingModified) {
|
||||||
mapModifiedTx.get<ancestor_score>().erase(modit);
|
mapModifiedTx.get<ancestor_score>().erase(modit);
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "instantx.h"
|
#include "instantx.h"
|
||||||
#include "masternode-sync.h"
|
#include "masternode-sync.h"
|
||||||
#include "privatesend.h"
|
#include "privatesend.h"
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -2786,8 +2787,12 @@ bool CConnman::DisconnectNode(NodeId id)
|
|||||||
void CConnman::RelayTransaction(const CTransaction& tx)
|
void CConnman::RelayTransaction(const CTransaction& tx)
|
||||||
{
|
{
|
||||||
uint256 hash = tx.GetHash();
|
uint256 hash = tx.GetHash();
|
||||||
int nInv = static_cast<bool>(CPrivateSend::GetDSTX(hash)) ? MSG_DSTX :
|
int nInv = MSG_TX;
|
||||||
(instantsend.HasTxLockRequest(hash) ? MSG_TXLOCK_REQUEST : MSG_TX);
|
if (CPrivateSend::GetDSTX(hash)) {
|
||||||
|
nInv = MSG_DSTX;
|
||||||
|
} else if (llmq::IsOldInstantSendEnabled() && instantsend.HasTxLockRequest(hash)) {
|
||||||
|
nInv = MSG_TXLOCK_REQUEST;
|
||||||
|
}
|
||||||
CInv inv(nInv, hash);
|
CInv inv(nInv, hash);
|
||||||
LOCK(cs_vNodes);
|
LOCK(cs_vNodes);
|
||||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
#include "llmq/quorums_debug.h"
|
#include "llmq/quorums_debug.h"
|
||||||
#include "llmq/quorums_dkgsessionmgr.h"
|
#include "llmq/quorums_dkgsessionmgr.h"
|
||||||
#include "llmq/quorums_init.h"
|
#include "llmq/quorums_init.h"
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
#include "llmq/quorums_signing.h"
|
#include "llmq/quorums_signing.h"
|
||||||
#include "llmq/quorums_signing_shares.h"
|
#include "llmq/quorums_signing_shares.h"
|
||||||
|
|
||||||
@ -976,6 +977,8 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
|||||||
return llmq::quorumSigningManager->AlreadyHave(inv);
|
return llmq::quorumSigningManager->AlreadyHave(inv);
|
||||||
case MSG_CLSIG:
|
case MSG_CLSIG:
|
||||||
return llmq::chainLocksHandler->AlreadyHave(inv);
|
return llmq::chainLocksHandler->AlreadyHave(inv);
|
||||||
|
case MSG_ISLOCK:
|
||||||
|
return llmq::quorumInstantSendManager->AlreadyHave(inv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't know what it is, just say we already got one
|
// Don't know what it is, just say we already got one
|
||||||
@ -1296,6 +1299,14 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!push && (inv.type == MSG_ISLOCK)) {
|
||||||
|
llmq::CInstantSendLock o;
|
||||||
|
if (llmq::quorumInstantSendManager->GetInstantSendLockByHash(inv.hash, o)) {
|
||||||
|
connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::ISLOCK, o));
|
||||||
|
push = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!push)
|
if (!push)
|
||||||
vNotFound.push_back(inv);
|
vNotFound.push_back(inv);
|
||||||
}
|
}
|
||||||
@ -1777,6 +1788,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
|||||||
case MSG_CLSIG:
|
case MSG_CLSIG:
|
||||||
doubleRequestDelay = 5 * 1000000;
|
doubleRequestDelay = 5 * 1000000;
|
||||||
break;
|
break;
|
||||||
|
case MSG_ISLOCK:
|
||||||
|
doubleRequestDelay = 5 * 1000000;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
pfrom->AskFor(inv, doubleRequestDelay);
|
pfrom->AskFor(inv, doubleRequestDelay);
|
||||||
}
|
}
|
||||||
@ -2001,11 +2015,16 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
|||||||
if(strCommand == NetMsgType::TX) {
|
if(strCommand == NetMsgType::TX) {
|
||||||
vRecv >> ptx;
|
vRecv >> ptx;
|
||||||
txLockRequest = CTxLockRequest(ptx);
|
txLockRequest = CTxLockRequest(ptx);
|
||||||
fCanAutoLock = CInstantSend::CanAutoLock() && txLockRequest.IsSimple();
|
fCanAutoLock = llmq::IsOldInstantSendEnabled() && CInstantSend::CanAutoLock() && txLockRequest.IsSimple();
|
||||||
} else if(strCommand == NetMsgType::TXLOCKREQUEST) {
|
} else if(strCommand == NetMsgType::TXLOCKREQUEST) {
|
||||||
vRecv >> txLockRequest;
|
vRecv >> txLockRequest;
|
||||||
ptx = txLockRequest.tx;
|
ptx = txLockRequest.tx;
|
||||||
nInvType = MSG_TXLOCK_REQUEST;
|
nInvType = MSG_TXLOCK_REQUEST;
|
||||||
|
if (llmq::IsNewInstantSendEnabled()) {
|
||||||
|
// the new system does not require explicit lock requests
|
||||||
|
// changing the inv type to MSG_TX also results in re-broadcasting the TX as normal TX
|
||||||
|
nInvType = MSG_TX;
|
||||||
|
}
|
||||||
} else if (strCommand == NetMsgType::DSTX) {
|
} else if (strCommand == NetMsgType::DSTX) {
|
||||||
vRecv >> dstx;
|
vRecv >> dstx;
|
||||||
ptx = dstx.tx;
|
ptx = dstx.tx;
|
||||||
@ -2021,7 +2040,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process custom logic, no matter if tx will be accepted to mempool later or not
|
// Process custom logic, no matter if tx will be accepted to mempool later or not
|
||||||
if (strCommand == NetMsgType::TXLOCKREQUEST || fCanAutoLock) {
|
if (nInvType == MSG_TXLOCK_REQUEST || fCanAutoLock) {
|
||||||
if(!instantsend.ProcessTxLockRequest(txLockRequest, connman)) {
|
if(!instantsend.ProcessTxLockRequest(txLockRequest, connman)) {
|
||||||
LogPrint("instantsend", "TXLOCKREQUEST -- failed %s\n", txLockRequest.GetHash().ToString());
|
LogPrint("instantsend", "TXLOCKREQUEST -- failed %s\n", txLockRequest.GetHash().ToString());
|
||||||
// Should not really happen for "fCanAutoLock == true" but just in case:
|
// Should not really happen for "fCanAutoLock == true" but just in case:
|
||||||
@ -2032,7 +2051,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
|||||||
// Fallback for normal txes to process as usual
|
// Fallback for normal txes to process as usual
|
||||||
fCanAutoLock = false;
|
fCanAutoLock = false;
|
||||||
}
|
}
|
||||||
} else if (strCommand == NetMsgType::DSTX) {
|
} else if (nInvType == MSG_DSTX) {
|
||||||
uint256 hashTx = tx.GetHash();
|
uint256 hashTx = tx.GetHash();
|
||||||
if(CPrivateSend::GetDSTX(hashTx)) {
|
if(CPrivateSend::GetDSTX(hashTx)) {
|
||||||
LogPrint("privatesend", "DSTX -- Already have %s, skipping...\n", hashTx.ToString());
|
LogPrint("privatesend", "DSTX -- Already have %s, skipping...\n", hashTx.ToString());
|
||||||
@ -2069,17 +2088,21 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
|||||||
|
|
||||||
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, true, &fMissingInputs)) {
|
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, true, &fMissingInputs)) {
|
||||||
// Process custom txes, this changes AlreadyHave to "true"
|
// Process custom txes, this changes AlreadyHave to "true"
|
||||||
if (strCommand == NetMsgType::DSTX) {
|
if (nInvType == MSG_DSTX) {
|
||||||
LogPrintf("DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n",
|
LogPrintf("DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n",
|
||||||
tx.GetHash().ToString(), pfrom->id);
|
tx.GetHash().ToString(), pfrom->id);
|
||||||
CPrivateSend::AddDSTX(dstx);
|
CPrivateSend::AddDSTX(dstx);
|
||||||
} else if (strCommand == NetMsgType::TXLOCKREQUEST || fCanAutoLock) {
|
} else if (nInvType == MSG_TXLOCK_REQUEST || fCanAutoLock) {
|
||||||
LogPrintf("TXLOCKREQUEST -- Transaction Lock Request accepted, txid=%s, peer=%d\n",
|
LogPrintf("TXLOCKREQUEST -- Transaction Lock Request accepted, txid=%s, peer=%d\n",
|
||||||
tx.GetHash().ToString(), pfrom->id);
|
tx.GetHash().ToString(), pfrom->id);
|
||||||
instantsend.AcceptLockRequest(txLockRequest);
|
instantsend.AcceptLockRequest(txLockRequest);
|
||||||
instantsend.Vote(tx.GetHash(), connman);
|
instantsend.Vote(tx.GetHash(), connman);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nInvType != MSG_TXLOCK_REQUEST) {
|
||||||
|
llmq::quorumInstantSendManager->ProcessTx(pfrom, tx, connman, chainparams.GetConsensus());
|
||||||
|
}
|
||||||
|
|
||||||
mempool.check(pcoinsTip);
|
mempool.check(pcoinsTip);
|
||||||
connman.RelayTransaction(tx);
|
connman.RelayTransaction(tx);
|
||||||
for (unsigned int i = 0; i < tx.vout.size(); i++) {
|
for (unsigned int i = 0; i < tx.vout.size(); i++) {
|
||||||
@ -2124,6 +2147,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
|||||||
vWorkQueue.emplace_back(orphanHash, i);
|
vWorkQueue.emplace_back(orphanHash, i);
|
||||||
}
|
}
|
||||||
vEraseQueue.push_back(orphanHash);
|
vEraseQueue.push_back(orphanHash);
|
||||||
|
|
||||||
|
llmq::quorumInstantSendManager->ProcessTx(pfrom, orphanTx, connman, chainparams.GetConsensus());
|
||||||
}
|
}
|
||||||
else if (!fMissingInputs2)
|
else if (!fMissingInputs2)
|
||||||
{
|
{
|
||||||
@ -2188,7 +2213,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strCommand == NetMsgType::TXLOCKREQUEST && !AlreadyHave(inv)) {
|
if (nInvType == MSG_TXLOCK_REQUEST && !AlreadyHave(inv)) {
|
||||||
// i.e. AcceptToMemoryPool failed, probably because it's conflicting
|
// i.e. AcceptToMemoryPool failed, probably because it's conflicting
|
||||||
// with existing normal tx or tx lock for another tx. For the same tx lock
|
// with existing normal tx or tx lock for another tx. For the same tx lock
|
||||||
// AlreadyHave would have return "true" already.
|
// AlreadyHave would have return "true" already.
|
||||||
@ -2943,6 +2968,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
|||||||
llmq::quorumSigSharesManager->ProcessMessage(pfrom, strCommand, vRecv, connman);
|
llmq::quorumSigSharesManager->ProcessMessage(pfrom, strCommand, vRecv, connman);
|
||||||
llmq::quorumSigningManager->ProcessMessage(pfrom, strCommand, vRecv, connman);
|
llmq::quorumSigningManager->ProcessMessage(pfrom, strCommand, vRecv, connman);
|
||||||
llmq::chainLocksHandler->ProcessMessage(pfrom, strCommand, vRecv, connman);
|
llmq::chainLocksHandler->ProcessMessage(pfrom, strCommand, vRecv, connman);
|
||||||
|
llmq::quorumInstantSendManager->ProcessMessage(pfrom, strCommand, vRecv, connman);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -71,6 +71,7 @@ const char *QGETSIGSHARES="qgetsigs";
|
|||||||
const char *QBSIGSHARES="qbsigs";
|
const char *QBSIGSHARES="qbsigs";
|
||||||
const char *QSIGREC="qsigrec";
|
const char *QSIGREC="qsigrec";
|
||||||
const char *CLSIG="clsig";
|
const char *CLSIG="clsig";
|
||||||
|
const char *ISLOCK="islock";
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char* ppszTypeName[] =
|
static const char* ppszTypeName[] =
|
||||||
@ -107,6 +108,7 @@ static const char* ppszTypeName[] =
|
|||||||
NetMsgType::QDEBUGSTATUS,
|
NetMsgType::QDEBUGSTATUS,
|
||||||
NetMsgType::QSIGREC,
|
NetMsgType::QSIGREC,
|
||||||
NetMsgType::CLSIG,
|
NetMsgType::CLSIG,
|
||||||
|
NetMsgType::ISLOCK,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** All known message types. Keep this in the same order as the list of
|
/** All known message types. Keep this in the same order as the list of
|
||||||
@ -172,6 +174,7 @@ const static std::string allNetMessageTypes[] = {
|
|||||||
NetMsgType::QBSIGSHARES,
|
NetMsgType::QBSIGSHARES,
|
||||||
NetMsgType::QSIGREC,
|
NetMsgType::QSIGREC,
|
||||||
NetMsgType::CLSIG,
|
NetMsgType::CLSIG,
|
||||||
|
NetMsgType::ISLOCK,
|
||||||
};
|
};
|
||||||
const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
|
const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
|
||||||
|
|
||||||
|
@ -277,6 +277,7 @@ extern const char *QGETSIGSHARES;
|
|||||||
extern const char *QBSIGSHARES;
|
extern const char *QBSIGSHARES;
|
||||||
extern const char *QSIGREC;
|
extern const char *QSIGREC;
|
||||||
extern const char *CLSIG;
|
extern const char *CLSIG;
|
||||||
|
extern const char *ISLOCK;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Get a vector of all valid message types (see above) */
|
/* Get a vector of all valid message types (see above) */
|
||||||
@ -379,6 +380,7 @@ enum GetDataMsg {
|
|||||||
MSG_QUORUM_DEBUG_STATUS = 27,
|
MSG_QUORUM_DEBUG_STATUS = 27,
|
||||||
MSG_QUORUM_RECOVERED_SIG = 28,
|
MSG_QUORUM_RECOVERED_SIG = 28,
|
||||||
MSG_CLSIG = 29,
|
MSG_CLSIG = 29,
|
||||||
|
MSG_ISLOCK = 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** inv message data */
|
/** inv message data */
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
#include "instantx.h"
|
#include "instantx.h"
|
||||||
|
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -52,6 +54,11 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
|
|||||||
strTxStatus = tr("%1 confirmations").arg(nDepth);
|
strTxStatus = tr("%1 confirmations").arg(nDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (llmq::quorumInstantSendManager->IsLocked(wtx.GetHash())) {
|
||||||
|
strTxStatus += tr(" (verified via LLMQ based InstantSend)");
|
||||||
|
return strTxStatus;
|
||||||
|
}
|
||||||
|
|
||||||
if(!instantsend.HasTxLockRequest(wtx.GetHash())) return strTxStatus; // regular tx
|
if(!instantsend.HasTxLockRequest(wtx.GetHash())) return strTxStatus; // regular tx
|
||||||
|
|
||||||
int nSignatures = instantsend.GetTransactionLockSignatures(wtx.GetHash());
|
int nSignatures = instantsend.GetTransactionLockSignatures(wtx.GetHash());
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "instantx.h"
|
#include "instantx.h"
|
||||||
#include "spork.h"
|
#include "spork.h"
|
||||||
#include "privatesend-client.h"
|
#include "privatesend-client.h"
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -389,7 +390,12 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran
|
|||||||
|
|
||||||
CReserveKey *keyChange = transaction.getPossibleKeyChange();
|
CReserveKey *keyChange = transaction.getPossibleKeyChange();
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
if(!wallet->CommitTransaction(*newTx, *keyChange, g_connman.get(), state, recipients[0].fUseInstantSend ? NetMsgType::TXLOCKREQUEST : NetMsgType::TX))
|
// the new IX system does not require explicit IX messages
|
||||||
|
std::string strCommand = NetMsgType::TX;
|
||||||
|
if (recipients[0].fUseInstantSend && llmq::IsOldInstantSendEnabled()) {
|
||||||
|
strCommand = NetMsgType::TXLOCKREQUEST;
|
||||||
|
}
|
||||||
|
if(!wallet->CommitTransaction(*newTx, *keyChange, g_connman.get(), state, strCommand))
|
||||||
return SendCoinsReturn(TransactionCommitFailed, QString::fromStdString(state.GetRejectReason()));
|
return SendCoinsReturn(TransactionCommitFailed, QString::fromStdString(state.GetRejectReason()));
|
||||||
|
|
||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "evo/cbtx.h"
|
#include "evo/cbtx.h"
|
||||||
|
|
||||||
#include "llmq/quorums_chainlocks.h"
|
#include "llmq/quorums_chainlocks.h"
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -409,7 +410,7 @@ void entryToJSON(UniValue &info, const CTxMemPoolEntry &e)
|
|||||||
|
|
||||||
info.push_back(Pair("depends", depends));
|
info.push_back(Pair("depends", depends));
|
||||||
info.push_back(Pair("instantsend", instantsend.HasTxLockRequest(tx.GetHash())));
|
info.push_back(Pair("instantsend", instantsend.HasTxLockRequest(tx.GetHash())));
|
||||||
info.push_back(Pair("instantlock", instantsend.IsLockedInstantSendTransaction(tx.GetHash())));
|
info.push_back(Pair("instantlock", instantsend.IsLockedInstantSendTransaction(tx.GetHash()) || llmq::quorumInstantSendManager->IsLocked(tx.GetHash())));
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue mempoolToJSON(bool fVerbose = false)
|
UniValue mempoolToJSON(bool fVerbose = false)
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include "llmq/quorums_chainlocks.h"
|
#include "llmq/quorums_chainlocks.h"
|
||||||
#include "llmq/quorums_commitment.h"
|
#include "llmq/quorums_commitment.h"
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -199,7 +200,8 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool fLocked = instantsend.IsLockedInstantSendTransaction(txid);
|
bool fLocked = instantsend.IsLockedInstantSendTransaction(txid);
|
||||||
entry.push_back(Pair("instantlock", fLocked));
|
bool fLLMQLocked = llmq::quorumInstantSendManager->IsLocked(txid);
|
||||||
|
entry.push_back(Pair("instantlock", fLocked || fLLMQLocked));
|
||||||
entry.push_back(Pair("chainlock", chainLock));
|
entry.push_back(Pair("chainlock", chainLock));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1018,6 +1020,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
|
|||||||
throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason());
|
throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
llmq::quorumInstantSendManager->ProcessTx(nullptr, *tx, *g_connman, Params().GetConsensus());
|
||||||
} else if (fHaveChain) {
|
} else if (fHaveChain) {
|
||||||
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
|
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ std::map<int, int64_t> mapSporkDefaults = {
|
|||||||
{SPORK_17_QUORUM_DKG_ENABLED, 4070908800ULL}, // OFF
|
{SPORK_17_QUORUM_DKG_ENABLED, 4070908800ULL}, // OFF
|
||||||
{SPORK_18_QUORUM_DEBUG_ENABLED, 4070908800ULL}, // OFF
|
{SPORK_18_QUORUM_DEBUG_ENABLED, 4070908800ULL}, // OFF
|
||||||
{SPORK_19_CHAINLOCKS_ENABLED, 4070908800ULL}, // OFF
|
{SPORK_19_CHAINLOCKS_ENABLED, 4070908800ULL}, // OFF
|
||||||
|
{SPORK_20_INSTANTSEND_LLMQ_BASED, 4070908800ULL}, // OFF
|
||||||
};
|
};
|
||||||
|
|
||||||
bool CSporkManager::SporkValueIsActive(int nSporkID, int64_t &nActiveValueRet) const
|
bool CSporkManager::SporkValueIsActive(int nSporkID, int64_t &nActiveValueRet) const
|
||||||
@ -291,6 +292,7 @@ int CSporkManager::GetSporkIDByName(const std::string& strName)
|
|||||||
if (strName == "SPORK_17_QUORUM_DKG_ENABLED") return SPORK_17_QUORUM_DKG_ENABLED;
|
if (strName == "SPORK_17_QUORUM_DKG_ENABLED") return SPORK_17_QUORUM_DKG_ENABLED;
|
||||||
if (strName == "SPORK_18_QUORUM_DEBUG_ENABLED") return SPORK_18_QUORUM_DEBUG_ENABLED;
|
if (strName == "SPORK_18_QUORUM_DEBUG_ENABLED") return SPORK_18_QUORUM_DEBUG_ENABLED;
|
||||||
if (strName == "SPORK_19_CHAINLOCKS_ENABLED") return SPORK_19_CHAINLOCKS_ENABLED;
|
if (strName == "SPORK_19_CHAINLOCKS_ENABLED") return SPORK_19_CHAINLOCKS_ENABLED;
|
||||||
|
if (strName == "SPORK_20_INSTANTSEND_LLMQ_BASED") return SPORK_20_INSTANTSEND_LLMQ_BASED;
|
||||||
|
|
||||||
LogPrint("spork", "CSporkManager::GetSporkIDByName -- Unknown Spork name '%s'\n", strName);
|
LogPrint("spork", "CSporkManager::GetSporkIDByName -- Unknown Spork name '%s'\n", strName);
|
||||||
return -1;
|
return -1;
|
||||||
@ -310,6 +312,7 @@ std::string CSporkManager::GetSporkNameByID(int nSporkID)
|
|||||||
case SPORK_17_QUORUM_DKG_ENABLED: return "SPORK_17_QUORUM_DKG_ENABLED";
|
case SPORK_17_QUORUM_DKG_ENABLED: return "SPORK_17_QUORUM_DKG_ENABLED";
|
||||||
case SPORK_18_QUORUM_DEBUG_ENABLED: return "SPORK_18_QUORUM_DEBUG_ENABLED";
|
case SPORK_18_QUORUM_DEBUG_ENABLED: return "SPORK_18_QUORUM_DEBUG_ENABLED";
|
||||||
case SPORK_19_CHAINLOCKS_ENABLED: return "SPORK_19_CHAINLOCKS_ENABLED";
|
case SPORK_19_CHAINLOCKS_ENABLED: return "SPORK_19_CHAINLOCKS_ENABLED";
|
||||||
|
case SPORK_20_INSTANTSEND_LLMQ_BASED: return "SPORK_20_INSTANTSEND_LLMQ_BASED";
|
||||||
default:
|
default:
|
||||||
LogPrint("spork", "CSporkManager::GetSporkNameByID -- Unknown Spork ID %d\n", nSporkID);
|
LogPrint("spork", "CSporkManager::GetSporkNameByID -- Unknown Spork ID %d\n", nSporkID);
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
|
@ -28,9 +28,10 @@ static const int SPORK_16_INSTANTSEND_AUTOLOCKS = 10015;
|
|||||||
static const int SPORK_17_QUORUM_DKG_ENABLED = 10016;
|
static const int SPORK_17_QUORUM_DKG_ENABLED = 10016;
|
||||||
static const int SPORK_18_QUORUM_DEBUG_ENABLED = 10017;
|
static const int SPORK_18_QUORUM_DEBUG_ENABLED = 10017;
|
||||||
static const int SPORK_19_CHAINLOCKS_ENABLED = 10018;
|
static const int SPORK_19_CHAINLOCKS_ENABLED = 10018;
|
||||||
|
static const int SPORK_20_INSTANTSEND_LLMQ_BASED = 10019;
|
||||||
|
|
||||||
static const int SPORK_START = SPORK_2_INSTANTSEND_ENABLED;
|
static const int SPORK_START = SPORK_2_INSTANTSEND_ENABLED;
|
||||||
static const int SPORK_END = SPORK_19_CHAINLOCKS_ENABLED;
|
static const int SPORK_END = SPORK_20_INSTANTSEND_LLMQ_BASED;
|
||||||
|
|
||||||
extern std::map<int, int64_t> mapSporkDefaults;
|
extern std::map<int, int64_t> mapSporkDefaults;
|
||||||
extern CSporkManager sporkManager;
|
extern CSporkManager sporkManager;
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
#include "evo/specialtx.h"
|
#include "evo/specialtx.h"
|
||||||
#include "evo/providertx.h"
|
#include "evo/providertx.h"
|
||||||
|
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
|
CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
|
||||||
int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
|
int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
|
||||||
CAmount _inChainInputValue,
|
CAmount _inChainInputValue,
|
||||||
@ -1497,7 +1499,7 @@ int CTxMemPool::Expire(int64_t time) {
|
|||||||
setEntries toremove;
|
setEntries toremove;
|
||||||
while (it != mapTx.get<entry_time>().end() && it->GetTime() < time) {
|
while (it != mapTx.get<entry_time>().end() && it->GetTime() < time) {
|
||||||
// locked txes do not expire until mined and have sufficient confirmations
|
// locked txes do not expire until mined and have sufficient confirmations
|
||||||
if (instantsend.IsLockedInstantSendTransaction(it->GetTx().GetHash())) {
|
if (instantsend.IsLockedInstantSendTransaction(it->GetTx().GetHash()) || llmq::quorumInstantSendManager->IsLocked(it->GetTx().GetHash())) {
|
||||||
it++;
|
it++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
#include "evo/deterministicmns.h"
|
#include "evo/deterministicmns.h"
|
||||||
#include "evo/cbtx.h"
|
#include "evo/cbtx.h"
|
||||||
|
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
#include "llmq/quorums_chainlocks.h"
|
#include "llmq/quorums_chainlocks.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@ -692,6 +693,18 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
|||||||
REJECT_INVALID, "tx-txlock-conflict");
|
REJECT_INVALID, "tx-txlock-conflict");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint256 txConflictHash;
|
||||||
|
if (llmq::quorumInstantSendManager->GetConflictingTx(tx, txConflictHash)) {
|
||||||
|
CTransactionRef txConflict;
|
||||||
|
uint256 hashBlock;
|
||||||
|
if (GetTransaction(txConflictHash, txConflict, Params().GetConsensus(), hashBlock)) {
|
||||||
|
GetMainSignals().NotifyInstantSendDoubleSpendAttempt(tx, *txConflict);
|
||||||
|
}
|
||||||
|
return state.DoS(10, error("AcceptToMemoryPool : Transaction %s conflicts with locked TX %s",
|
||||||
|
hash.ToString(), txConflictHash.ToString()),
|
||||||
|
REJECT_INVALID, "tx-txlock-conflict");
|
||||||
|
}
|
||||||
|
|
||||||
// Check for conflicts with in-memory transactions
|
// Check for conflicts with in-memory transactions
|
||||||
{
|
{
|
||||||
LOCK(pool.cs); // protect pool.mapNextTx
|
LOCK(pool.cs); // protect pool.mapNextTx
|
||||||
@ -2196,13 +2209,49 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
|
|||||||
LogPrint("bench", " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs]\n", nInputs - 1, 0.001 * (nTime4 - nTime2), nInputs <= 1 ? 0 : 0.001 * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * 0.000001);
|
LogPrint("bench", " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs]\n", nInputs - 1, 0.001 * (nTime4 - nTime2), nInputs <= 1 ? 0 : 0.001 * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * 0.000001);
|
||||||
|
|
||||||
|
|
||||||
// DASH : MODIFIED TO CHECK MASTERNODE PAYMENTS AND SUPERBLOCKS
|
// DASH
|
||||||
|
|
||||||
// It's possible that we simply don't have enough data and this could fail
|
// It's possible that we simply don't have enough data and this could fail
|
||||||
// (i.e. block itself could be a correct one and we need to store it),
|
// (i.e. block itself could be a correct one and we need to store it),
|
||||||
// that's why this is in ConnectBlock. Could be the other way around however -
|
// that's why this is in ConnectBlock. Could be the other way around however -
|
||||||
// the peer who sent us this block is missing some data and wasn't able
|
// the peer who sent us this block is missing some data and wasn't able
|
||||||
// to recognize that block is actually invalid.
|
// to recognize that block is actually invalid.
|
||||||
|
|
||||||
|
// DASH : CHECK TRANSACTIONS FOR INSTANTSEND
|
||||||
|
|
||||||
|
if (sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) {
|
||||||
|
// Require other nodes to comply, send them some data in case they are missing it.
|
||||||
|
for (const auto& tx : block.vtx) {
|
||||||
|
// skip txes that have no inputs
|
||||||
|
if (tx->vin.empty()) continue;
|
||||||
|
// LOOK FOR TRANSACTION LOCK IN OUR MAP OF OUTPOINTS
|
||||||
|
for (const auto& txin : tx->vin) {
|
||||||
|
uint256 hashLocked;
|
||||||
|
if (instantsend.GetLockedOutPointTxHash(txin.prevout, hashLocked) && hashLocked != tx->GetHash()) {
|
||||||
|
// The node which relayed this should switch to correct chain.
|
||||||
|
// TODO: relay instantsend data/proof.
|
||||||
|
LOCK(cs_main);
|
||||||
|
mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime()));
|
||||||
|
return state.DoS(10, error("ConnectBlock(DASH): transaction %s conflicts with transaction lock %s", tx->GetHash().ToString(), hashLocked.ToString()),
|
||||||
|
REJECT_INVALID, "conflict-tx-lock");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint256 txConflict;
|
||||||
|
if (llmq::quorumInstantSendManager->GetConflictingTx(*tx, txConflict)) {
|
||||||
|
// The node which relayed this should switch to correct chain.
|
||||||
|
// TODO: relay instantsend data/proof.
|
||||||
|
LOCK(cs_main);
|
||||||
|
mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime()));
|
||||||
|
return state.DoS(10, error("ConnectBlock(DASH): transaction %s conflicts with transaction lock %s", tx->GetHash().ToString(), txConflict.ToString()),
|
||||||
|
REJECT_INVALID, "conflict-tx-lock");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogPrintf("ConnectBlock(DASH): spork is off, skipping transaction locking checks\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// DASH : MODIFIED TO CHECK MASTERNODE PAYMENTS AND SUPERBLOCKS
|
||||||
|
|
||||||
// TODO: resync data (both ways?) and try to reprocess this block later.
|
// TODO: resync data (both ways?) and try to reprocess this block later.
|
||||||
CAmount blockReward = nFees + GetBlockSubsidy(pindex->pprev->nBits, pindex->pprev->nHeight, chainparams.GetConsensus());
|
CAmount blockReward = nFees + GetBlockSubsidy(pindex->pprev->nBits, pindex->pprev->nHeight, chainparams.GetConsensus());
|
||||||
std::string strError = "";
|
std::string strError = "";
|
||||||
@ -3256,35 +3305,6 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P
|
|||||||
if (block.vtx[i]->IsCoinBase())
|
if (block.vtx[i]->IsCoinBase())
|
||||||
return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase");
|
return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase");
|
||||||
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
for(const auto& tx : block.vtx) {
|
|
||||||
// skip coinbase, it has no inputs
|
|
||||||
if (tx->IsCoinBase()) continue;
|
|
||||||
// LOOK FOR TRANSACTION LOCK IN OUR MAP OF OUTPOINTS
|
|
||||||
for (const auto& txin : tx->vin) {
|
|
||||||
uint256 hashLocked;
|
|
||||||
if(instantsend.GetLockedOutPointTxHash(txin.prevout, hashLocked) && hashLocked != tx->GetHash()) {
|
|
||||||
// The node which relayed this will have to switch later,
|
|
||||||
// relaying instantsend data won't help it.
|
|
||||||
LOCK(cs_main);
|
|
||||||
mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime()));
|
|
||||||
return state.DoS(100, false, REJECT_INVALID, "conflict-tx-lock", false,
|
|
||||||
strprintf("transaction %s conflicts with transaction lock %s", tx->GetHash().ToString(), hashLocked.ToString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LogPrintf("CheckBlock(DASH): spork is off, skipping transaction locking checks\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// END DASH
|
|
||||||
|
|
||||||
// Check transactions
|
// Check transactions
|
||||||
for (const auto& tx : block.vtx)
|
for (const auto& tx : block.vtx)
|
||||||
if (!CheckTransaction(*tx, state))
|
if (!CheckTransaction(*tx, state))
|
||||||
|
@ -18,6 +18,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
|
|||||||
g_signals.UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3));
|
g_signals.UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3));
|
||||||
g_signals.SyncTransaction.connect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3));
|
g_signals.SyncTransaction.connect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3));
|
||||||
g_signals.NotifyTransactionLock.connect(boost::bind(&CValidationInterface::NotifyTransactionLock, pwalletIn, _1));
|
g_signals.NotifyTransactionLock.connect(boost::bind(&CValidationInterface::NotifyTransactionLock, pwalletIn, _1));
|
||||||
|
g_signals.NotifyChainLock.connect(boost::bind(&CValidationInterface::NotifyChainLock, pwalletIn, _1));
|
||||||
g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
|
g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
|
||||||
g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
|
g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
|
||||||
g_signals.Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
|
g_signals.Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
|
||||||
@ -40,6 +41,7 @@ void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
|
|||||||
g_signals.Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
|
g_signals.Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
|
||||||
g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
|
g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
|
||||||
g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
|
g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
|
||||||
|
g_signals.NotifyChainLock.disconnect(boost::bind(&CValidationInterface::NotifyChainLock, pwalletIn, _1));
|
||||||
g_signals.NotifyTransactionLock.disconnect(boost::bind(&CValidationInterface::NotifyTransactionLock, pwalletIn, _1));
|
g_signals.NotifyTransactionLock.disconnect(boost::bind(&CValidationInterface::NotifyTransactionLock, pwalletIn, _1));
|
||||||
g_signals.SyncTransaction.disconnect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3));
|
g_signals.SyncTransaction.disconnect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3));
|
||||||
g_signals.UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3));
|
g_signals.UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3));
|
||||||
@ -61,6 +63,7 @@ void UnregisterAllValidationInterfaces() {
|
|||||||
g_signals.SetBestChain.disconnect_all_slots();
|
g_signals.SetBestChain.disconnect_all_slots();
|
||||||
g_signals.UpdatedTransaction.disconnect_all_slots();
|
g_signals.UpdatedTransaction.disconnect_all_slots();
|
||||||
g_signals.NotifyTransactionLock.disconnect_all_slots();
|
g_signals.NotifyTransactionLock.disconnect_all_slots();
|
||||||
|
g_signals.NotifyChainLock.disconnect_all_slots();
|
||||||
g_signals.SyncTransaction.disconnect_all_slots();
|
g_signals.SyncTransaction.disconnect_all_slots();
|
||||||
g_signals.UpdatedBlockTip.disconnect_all_slots();
|
g_signals.UpdatedBlockTip.disconnect_all_slots();
|
||||||
g_signals.NewPoWValidBlock.disconnect_all_slots();
|
g_signals.NewPoWValidBlock.disconnect_all_slots();
|
||||||
|
@ -39,6 +39,7 @@ protected:
|
|||||||
virtual void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {}
|
virtual void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {}
|
||||||
virtual void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) {}
|
virtual void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) {}
|
||||||
virtual void NotifyTransactionLock(const CTransaction &tx) {}
|
virtual void NotifyTransactionLock(const CTransaction &tx) {}
|
||||||
|
virtual void NotifyChainLock(const CBlockIndex* pindex) {}
|
||||||
virtual void NotifyGovernanceVote(const CGovernanceVote &vote) {}
|
virtual void NotifyGovernanceVote(const CGovernanceVote &vote) {}
|
||||||
virtual void NotifyGovernanceObject(const CGovernanceObject &object) {}
|
virtual void NotifyGovernanceObject(const CGovernanceObject &object) {}
|
||||||
virtual void NotifyInstantSendDoubleSpendAttempt(const CTransaction ¤tTx, const CTransaction &previousTx) {}
|
virtual void NotifyInstantSendDoubleSpendAttempt(const CTransaction ¤tTx, const CTransaction &previousTx) {}
|
||||||
@ -76,6 +77,8 @@ struct CMainSignals {
|
|||||||
boost::signals2::signal<void (const CTransaction &, const CBlockIndex *pindex, int posInBlock)> SyncTransaction;
|
boost::signals2::signal<void (const CTransaction &, const CBlockIndex *pindex, int posInBlock)> SyncTransaction;
|
||||||
/** Notifies listeners of an updated transaction lock without new data. */
|
/** Notifies listeners of an updated transaction lock without new data. */
|
||||||
boost::signals2::signal<void (const CTransaction &)> NotifyTransactionLock;
|
boost::signals2::signal<void (const CTransaction &)> NotifyTransactionLock;
|
||||||
|
/** Notifies listeners of a ChainLock. */
|
||||||
|
boost::signals2::signal<void (const CBlockIndex* pindex)> NotifyChainLock;
|
||||||
/** Notifies listeners of a new governance vote. */
|
/** Notifies listeners of a new governance vote. */
|
||||||
boost::signals2::signal<void (const CGovernanceVote &)> NotifyGovernanceVote;
|
boost::signals2::signal<void (const CGovernanceVote &)> NotifyGovernanceVote;
|
||||||
/** Notifies listeners of a new governance object. */
|
/** Notifies listeners of a new governance object. */
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "privatesend-client.h"
|
#include "privatesend-client.h"
|
||||||
|
|
||||||
#include "llmq/quorums_chainlocks.h"
|
#include "llmq/quorums_chainlocks.h"
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -65,12 +66,13 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
|
|||||||
AssertLockHeld(cs_main); // for mapBlockIndex
|
AssertLockHeld(cs_main); // for mapBlockIndex
|
||||||
int confirms = wtx.GetDepthInMainChain();
|
int confirms = wtx.GetDepthInMainChain();
|
||||||
bool fLocked = instantsend.IsLockedInstantSendTransaction(wtx.GetHash());
|
bool fLocked = instantsend.IsLockedInstantSendTransaction(wtx.GetHash());
|
||||||
|
bool fLLMQLocked = llmq::quorumInstantSendManager->IsLocked(wtx.GetHash());
|
||||||
bool chainlock = false;
|
bool chainlock = false;
|
||||||
if (confirms > 0) {
|
if (confirms > 0) {
|
||||||
chainlock = llmq::chainLocksHandler->HasChainLock(mapBlockIndex[wtx.hashBlock]->nHeight, wtx.hashBlock);
|
chainlock = llmq::chainLocksHandler->HasChainLock(mapBlockIndex[wtx.hashBlock]->nHeight, wtx.hashBlock);
|
||||||
}
|
}
|
||||||
entry.push_back(Pair("confirmations", confirms));
|
entry.push_back(Pair("confirmations", confirms));
|
||||||
entry.push_back(Pair("instantlock", fLocked));
|
entry.push_back(Pair("instantlock", fLocked || fLLMQLocked));
|
||||||
entry.push_back(Pair("chainlock", chainlock));
|
entry.push_back(Pair("chainlock", chainlock));
|
||||||
if (wtx.IsCoinBase())
|
if (wtx.IsCoinBase())
|
||||||
entry.push_back(Pair("generated", true));
|
entry.push_back(Pair("generated", true));
|
||||||
@ -384,7 +386,12 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA
|
|||||||
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
||||||
}
|
}
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
if (!pwallet->CommitTransaction(wtxNew, reservekey, g_connman.get(), state, fUseInstantSend ? NetMsgType::TXLOCKREQUEST : NetMsgType::TX)) {
|
// the new IX system does not require explicit IX messages
|
||||||
|
std::string strCommand = NetMsgType::TX;
|
||||||
|
if (fUseInstantSend && llmq::IsOldInstantSendEnabled()) {
|
||||||
|
strCommand = NetMsgType::TXLOCKREQUEST;
|
||||||
|
}
|
||||||
|
if (!pwallet->CommitTransaction(wtxNew, reservekey, g_connman.get(), state, strCommand)) {
|
||||||
strError = strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason());
|
strError = strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason());
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
||||||
}
|
}
|
||||||
@ -1134,7 +1141,12 @@ UniValue sendmany(const JSONRPCRequest& request)
|
|||||||
if (!fCreated)
|
if (!fCreated)
|
||||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
|
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
if (!pwallet->CommitTransaction(wtx, keyChange, g_connman.get(), state, fUseInstantSend ? NetMsgType::TXLOCKREQUEST : NetMsgType::TX)) {
|
// the new IX system does not require explicit IX messages
|
||||||
|
std::string strCommand = NetMsgType::TX;
|
||||||
|
if (fUseInstantSend && llmq::IsOldInstantSendEnabled()) {
|
||||||
|
strCommand = NetMsgType::TXLOCKREQUEST;
|
||||||
|
}
|
||||||
|
if (!pwallet->CommitTransaction(wtx, keyChange, g_connman.get(), state, strCommand)) {
|
||||||
strFailReason = strprintf("Transaction commit failed:: %s", state.GetRejectReason());
|
strFailReason = strprintf("Transaction commit failed:: %s", state.GetRejectReason());
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
|
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
|
|
||||||
#include "evo/providertx.h"
|
#include "evo/providertx.h"
|
||||||
|
|
||||||
|
#include "llmq/quorums_instantsend.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
@ -1919,6 +1921,9 @@ bool CWalletTx::RelayWalletTransaction(CConnman* connman, const std::string& str
|
|||||||
instantsend.RejectLockRequest((CTxLockRequest)*this);
|
instantsend.RejectLockRequest((CTxLockRequest)*this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
llmq::quorumInstantSendManager->ProcessTx(nullptr, *this->tx, *connman, Params().GetConsensus());
|
||||||
|
|
||||||
if (connman) {
|
if (connman) {
|
||||||
connman->RelayTransaction((CTransaction)*this);
|
connman->RelayTransaction((CTransaction)*this);
|
||||||
return true;
|
return true;
|
||||||
@ -3340,6 +3345,12 @@ bool CWallet::ConvertList(std::vector<CTxIn> vecTxIn, std::vector<CAmount>& vecA
|
|||||||
bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
|
bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
|
||||||
int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign, AvailableCoinsType nCoinType, bool fUseInstantSend, int nExtraPayloadSize)
|
int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign, AvailableCoinsType nCoinType, bool fUseInstantSend, int nExtraPayloadSize)
|
||||||
{
|
{
|
||||||
|
if (!llmq::IsOldInstantSendEnabled()) {
|
||||||
|
// The new system does not require special handling for InstantSend as this is all done in CInstantSendManager.
|
||||||
|
// There is also no need for an extra fee anymore.
|
||||||
|
fUseInstantSend = false;
|
||||||
|
}
|
||||||
|
|
||||||
CAmount nFeePay = fUseInstantSend ? CTxLockRequest().GetMinFee(true) : 0;
|
CAmount nFeePay = fUseInstantSend ? CTxLockRequest().GetMinFee(true) : 0;
|
||||||
|
|
||||||
CAmount nValue = 0;
|
CAmount nValue = 0;
|
||||||
@ -5426,7 +5437,7 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
|
|||||||
|
|
||||||
bool CMerkleTx::IsLockedByInstantSend() const
|
bool CMerkleTx::IsLockedByInstantSend() const
|
||||||
{
|
{
|
||||||
return instantsend.IsLockedInstantSendTransaction(GetHash());
|
return instantsend.IsLockedInstantSendTransaction(GetHash()) || llmq::quorumInstantSendManager->IsLocked(GetHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
int CMerkleTx::GetBlocksToMaturity() const
|
int CMerkleTx::GetBlocksToMaturity() const
|
||||||
|
Loading…
Reference in New Issue
Block a user