Skip to content

Commit

Permalink
Allow re-signing of IS locks when performing retroactive signing (#3219)
Browse files Browse the repository at this point in the history
* Implement re-signing of InstantSend inputs when TXs come in via blocks

* Use GetAdjustedTime instead of GetTimeMillis in CSigSharesManager

This allows use of mocktime in tests.

* Expose verifiedProRegTxHash in getpeerinfo and implement wait_for_mnauth

* Allow to wait for IS and CL to NOT happen

* Bump timeout for wait_for_instantlock

* Implement tests for retroactive signing of IS and CLs

* Add wait_for_tx function to DashTestFramework

* Add -whitelist=127.0.0.1 to node0

* Use node3 for isolated block generation

* Don't test for non-receival of TXs on node4/node5
  • Loading branch information
codablock authored and UdjinM6 committed Dec 6, 2019
1 parent b4b9d34 commit 4c00d98
Show file tree
Hide file tree
Showing 14 changed files with 307 additions and 41 deletions.
20 changes: 10 additions & 10 deletions src/llmq/quorums_instantsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ void CInstantSendManager::InterruptWorkerThread()
workInterrupt();
}

bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Params& params)
bool CInstantSendManager::ProcessTx(const CTransaction& tx, bool allowReSigning, const Consensus::Params& params)
{
if (!IsInstantSendEnabled()) {
return true;
Expand Down Expand Up @@ -441,7 +441,7 @@ bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Par
return false;
}
}
if (alreadyVotedCount == ids.size()) {
if (!allowReSigning && alreadyVotedCount == ids.size()) {
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: already voted on all inputs, bailing out\n", __func__,
tx.GetHash().ToString());
return true;
Expand All @@ -454,9 +454,9 @@ bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Par
auto& in = tx.vin[i];
auto& id = ids[i];
inputRequestIds.emplace(id);
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: trying to vote on input %s with id %s\n", __func__,
tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString());
if (quorumSigningManager->AsyncSignIfMember(llmqType, id, tx.GetHash())) {
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: trying to vote on input %s with id %s. allowReSigning=%d\n", __func__,
tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString(), allowReSigning);
if (quorumSigningManager->AsyncSignIfMember(llmqType, id, tx.GetHash(), allowReSigning)) {
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: voted on input %s with id %s\n", __func__,
tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString());
}
Expand Down Expand Up @@ -961,7 +961,7 @@ void CInstantSendManager::UpdateWalletTransaction(const CTransactionRef& tx, con
mempool.AddTransactionsUpdated(1);
}

void CInstantSendManager::ProcessNewTransaction(const CTransactionRef& tx, const CBlockIndex* pindex)
void CInstantSendManager::ProcessNewTransaction(const CTransactionRef& tx, const CBlockIndex* pindex, bool allowReSigning)
{
if (!IsInstantSendEnabled()) {
return;
Expand Down Expand Up @@ -989,7 +989,7 @@ void CInstantSendManager::ProcessNewTransaction(const CTransactionRef& tx, const

bool chainlocked = pindex && chainLocksHandler->HasChainLock(pindex->nHeight, pindex->GetBlockHash());
if (islockHash.IsNull() && !chainlocked) {
ProcessTx(*tx, Params().GetConsensus());
ProcessTx(*tx, allowReSigning, Params().GetConsensus());
}

LOCK(cs);
Expand All @@ -1004,7 +1004,7 @@ void CInstantSendManager::ProcessNewTransaction(const CTransactionRef& tx, const

void CInstantSendManager::TransactionAddedToMempool(const CTransactionRef& tx)
{
ProcessNewTransaction(tx, nullptr);
ProcessNewTransaction(tx, nullptr, false);
}

void CInstantSendManager::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex, const std::vector<CTransactionRef>& vtxConflicted)
Expand All @@ -1021,7 +1021,7 @@ void CInstantSendManager::BlockConnected(const std::shared_ptr<const CBlock>& pb
}

for (const auto& tx : pblock->vtx) {
ProcessNewTransaction(tx, pindex);
ProcessNewTransaction(tx, pindex, true);
}
}

Expand Down Expand Up @@ -1400,7 +1400,7 @@ bool CInstantSendManager::ProcessPendingRetryLockTxs()
tx->GetHash().ToString());
}

ProcessTx(*tx, Params().GetConsensus());
ProcessTx(*tx, false, Params().GetConsensus());
retryCount++;
}

Expand Down
4 changes: 2 additions & 2 deletions src/llmq/quorums_instantsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class CInstantSendManager : public CRecoveredSigsListener
void InterruptWorkerThread();

public:
bool ProcessTx(const CTransaction& tx, const Consensus::Params& params);
bool ProcessTx(const CTransaction& tx, bool allowReSigning, 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);
Expand All @@ -141,7 +141,7 @@ class CInstantSendManager : public CRecoveredSigsListener
void ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLock& islock);
void UpdateWalletTransaction(const CTransactionRef& tx, const CInstantSendLock& islock);

void ProcessNewTransaction(const CTransactionRef& tx, const CBlockIndex* pindex);
void ProcessNewTransaction(const CTransactionRef& tx, const CBlockIndex* pindex, bool allowReSigning);
void TransactionAddedToMempool(const CTransactionRef& tx);
void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex, const std::vector<CTransactionRef>& vtxConflicted);
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected);
Expand Down
19 changes: 15 additions & 4 deletions src/llmq/quorums_signing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ void CSigningManager::UnregisterRecoveredSigsListener(CRecoveredSigsListener* l)
recoveredSigsListeners.erase(itRem, recoveredSigsListeners.end());
}

bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash, bool allowReSign)
{
auto& params = Params().GetConsensus().llmqs.at(llmqType);

Expand All @@ -773,24 +773,31 @@ bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint
{
LOCK(cs);

if (db.HasVotedOnId(llmqType, id)) {
bool hasVoted = db.HasVotedOnId(llmqType, id);
if (hasVoted) {
uint256 prevMsgHash;
db.GetVoteForId(llmqType, id, prevMsgHash);
if (msgHash != prevMsgHash) {
LogPrintf("CSigningManager::%s -- already voted for id=%s and msgHash=%s. Not voting on conflicting msgHash=%s\n", __func__,
id.ToString(), prevMsgHash.ToString(), msgHash.ToString());
return false;
} else if (allowReSign) {
LogPrint(BCLog::LLMQ, "CSigningManager::%s -- already voted for id=%s and msgHash=%s. Resigning!\n", __func__,
id.ToString(), prevMsgHash.ToString());
} else {
LogPrint(BCLog::LLMQ, "CSigningManager::%s -- already voted for id=%s and msgHash=%s. Not voting again.\n", __func__,
id.ToString(), prevMsgHash.ToString());
return false;
}
return false;
}

if (db.HasRecoveredSigForId(llmqType, id)) {
// no need to sign it if we already have a recovered sig
return true;
}
db.WriteVoteForId(llmqType, id, msgHash);
if (!hasVoted) {
db.WriteVoteForId(llmqType, id, msgHash);
}
}

int tipHeight;
Expand All @@ -814,6 +821,10 @@ bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint
return false;
}

if (allowReSign) {
// make us re-announce all known shares (other nodes might have run into a timeout)
quorumSigSharesManager->ForceReAnnouncement(quorum, llmqType, id, msgHash);
}
quorumSigSharesManager->AsyncSign(quorum, id, msgHash);

return true;
Expand Down
2 changes: 1 addition & 1 deletion src/llmq/quorums_signing.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class CSigningManager
void RegisterRecoveredSigsListener(CRecoveredSigsListener* l);
void UnregisterRecoveredSigsListener(CRecoveredSigsListener* l);

bool AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash);
bool AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash, bool allowReSign = false);
bool HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash);
bool HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id);
bool HasRecoveredSigForSession(const uint256& signHash);
Expand Down
42 changes: 37 additions & 5 deletions src/llmq/quorums_signing_shares.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ void CSigSharesInv::Set(uint16_t quorumMember, bool v)
inv[quorumMember] = v;
}

void CSigSharesInv::SetAll(bool v)
{
for (size_t i = 0; i < inv.size(); i++) {
inv[i] = v;
}
}

std::string CBatchedSigShares::ToInvString() const
{
CSigSharesInv inv;
Expand Down Expand Up @@ -678,7 +685,7 @@ void CSigSharesManager::ProcessSigShare(NodeId nodeId, const CSigShare& sigShare
sigSharesToAnnounce.Add(sigShare.GetKey(), true);

// Update the time we've seen the last sigShare
timeSeenForSessions[sigShare.GetSignHash()] = GetTimeMillis();
timeSeenForSessions[sigShare.GetSignHash()] = GetAdjustedTime();

if (!quorumNodes.empty()) {
// don't announce and wait for other nodes to request this share and directly send it to them
Expand Down Expand Up @@ -777,7 +784,7 @@ void CSigSharesManager::CollectSigSharesToRequest(std::unordered_map<NodeId, std
{
AssertLockHeld(cs);

int64_t now = GetTimeMillis();
int64_t now = GetAdjustedTime();
const size_t maxRequestsForNode = 32;

// avoid requesting from same nodes all the time
Expand Down Expand Up @@ -1143,8 +1150,8 @@ CSigShare CSigSharesManager::RebuildSigShare(const CSigSharesNodeState::SessionI

void CSigSharesManager::Cleanup()
{
int64_t now = GetTimeMillis();
if (now - lastCleanupTime < 5000) {
int64_t now = GetAdjustedTime();
if (now - lastCleanupTime < 5) {
return;
}

Expand Down Expand Up @@ -1265,7 +1272,7 @@ void CSigSharesManager::Cleanup()
nodeStates.erase(nodeId);
}

lastCleanupTime = GetTimeMillis();
lastCleanupTime = GetAdjustedTime();
}

void CSigSharesManager::RemoveSigSharesForSession(const uint256& signHash)
Expand Down Expand Up @@ -1426,6 +1433,31 @@ void CSigSharesManager::Sign(const CQuorumCPtr& quorum, const uint256& id, const
ProcessSigShare(-1, sigShare, *g_connman, quorum);
}

// causes all known sigShares to be re-announced
void CSigSharesManager::ForceReAnnouncement(const CQuorumCPtr& quorum, Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
{
LOCK(cs);
auto signHash = CLLMQUtils::BuildSignHash(llmqType, quorum->qc.quorumHash, id, msgHash);
auto sigs = sigShares.GetAllForSignHash(signHash);
if (sigs) {
for (auto& p : *sigs) {
// re-announce every sigshare to every node
sigSharesToAnnounce.Add(std::make_pair(signHash, p.first), true);
}
}
for (auto& p : nodeStates) {
CSigSharesNodeState& nodeState = p.second;
auto session = nodeState.GetSessionBySignHash(signHash);
if (!session) {
continue;
}
// pretend that the other node doesn't know about any shares so that we re-announce everything
session->knows.SetAll(false);
// we need to use a new session id as we don't know if the other node has run into a timeout already
session->sendSessionId = (uint32_t)-1;
}
}

void CSigSharesManager::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
{
LOCK(cs);
Expand Down
6 changes: 4 additions & 2 deletions src/llmq/quorums_signing_shares.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class CSigSharesInv
void Init(size_t size);
bool IsSet(uint16_t quorumMember) const;
void Set(uint16_t quorumMember, bool v);
void SetAll(bool v);
void Merge(const CSigSharesInv& inv2);

size_t CountSet() const;
Expand Down Expand Up @@ -328,8 +329,8 @@ class CSigSharesNodeState

class CSigSharesManager : public CRecoveredSigsListener
{
static const int64_t SESSION_NEW_SHARES_TIMEOUT = 60 * 1000;
static const int64_t SIG_SHARE_REQUEST_TIMEOUT = 5 * 1000;
static const int64_t SESSION_NEW_SHARES_TIMEOUT = 60;
static const int64_t SIG_SHARE_REQUEST_TIMEOUT = 5;

// we try to keep total message size below 10k
const size_t MAX_MSGS_CNT_QSIGSESANN = 100;
Expand Down Expand Up @@ -376,6 +377,7 @@ class CSigSharesManager : public CRecoveredSigsListener

void AsyncSign(const CQuorumCPtr& quorum, const uint256& id, const uint256& msgHash);
void Sign(const CQuorumCPtr& quorum, const uint256& id, const uint256& msgHash);
void ForceReAnnouncement(const CQuorumCPtr& quorum, Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash);

void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig);

Expand Down
5 changes: 5 additions & 0 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,11 @@ void CNode::copyStats(CNodeStats &stats)
// Leave string empty if addrLocal invalid (not filled in yet)
CService addrLocalUnlocked = GetAddrLocal();
stats.addrLocal = addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToString() : "";

{
LOCK(cs_mnauth);
X(verifiedProRegTxHash);
}
}
#undef X

Expand Down
2 changes: 2 additions & 0 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,8 @@ class CNodeStats
CAddress addr;
// Bind address of our side of the connection
CAddress addrBind;
// In case this is a verified MN, this value is the proTx of the MN
uint256 verifiedProRegTxHash;
};


Expand Down
6 changes: 6 additions & 0 deletions src/rpc/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ UniValue getpeerinfo(const JSONRPCRequest& request)
" \"addrbind\":\"ip:port\", (string) Bind address of the connection to the peer\n"
" \"addrlocal\":\"ip:port\", (string) Local address as reported by the peer\n"
" \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n"
" \"verified_proregtx_hash\": h, (hex) Only present when the peer is a masternode and succesfully\n"
" autheticated via MNAUTH. In this case, this field contains the\n"
" protx hash of the masternode\n"
" \"relaytxes\":true|false, (boolean) Whether peer has asked us to relay transactions to it\n"
" \"lastsend\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last send\n"
" \"lastrecv\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last receive\n"
Expand Down Expand Up @@ -138,6 +141,9 @@ UniValue getpeerinfo(const JSONRPCRequest& request)
if (stats.addrBind.IsValid())
obj.push_back(Pair("addrbind", stats.addrBind.ToString()));
obj.push_back(Pair("services", strprintf("%016x", stats.nServices)));
if (!stats.verifiedProRegTxHash.IsNull()) {
obj.push_back(Pair("verified_proregtx_hash", stats.verifiedProRegTxHash.ToString()));
}
obj.push_back(Pair("relaytxes", stats.fRelayTxes));
obj.push_back(Pair("lastsend", stats.nLastSend));
obj.push_back(Pair("lastrecv", stats.nLastRecv));
Expand Down
2 changes: 1 addition & 1 deletion test/functional/llmq-chainlocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def run_test(self):
# for the mined TXs, which will then allow the network to create a CLSIG
self.log.info("Reenable network on first node and wait for chainlock")
reconnect_isolated_node(self.nodes[0], 1)
self.wait_for_chainlocked_block(self.nodes[0], self.nodes[0].getbestblockhash(), 30)
self.wait_for_chainlocked_block(self.nodes[0], self.nodes[0].getbestblockhash(), timeout=30)

def create_chained_txs(self, node, amount):
txid = node.sendtoaddress(node.getnewaddress(), amount)
Expand Down

0 comments on commit 4c00d98

Please sign in to comment.