Skip to content

Commit 65dd0d2

Browse files
committed
Instant send deterministic lock using the same msg hash as islock.
When receiving an islock, propagate it as islock. When creating/receiving and isdlock, propagate it as isdlock to peers which support it and as islock to peers which don't. Functional tests to cover both islock and isdlock scenarios.
1 parent e60b249 commit 65dd0d2

File tree

12 files changed

+144
-35
lines changed

12 files changed

+144
-35
lines changed

src/llmq/quorums_instantsend.cpp

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,14 @@ CInstantSendLockPtr CInstantSendDb::GetInstantSendLockByHash(const uint256& hash
311311
return ret;
312312
}
313313

314-
ret = std::make_shared<CInstantSendLock>();
314+
ret = std::make_shared<CInstantSendLock>(CInstantSendLock::isdlock_version);
315315
bool exists = db->Read(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *ret);
316316
if (!exists) {
317-
ret = nullptr;
317+
ret = std::make_shared<CInstantSendLock>();
318+
exists = db->Read(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *ret);
319+
if (!exists) {
320+
ret = nullptr;
321+
}
318322
}
319323
islockCache.insert(hash, ret);
320324
return ret;
@@ -668,7 +672,7 @@ void CInstantSendManager::HandleNewInputLockRecoveredSig(const CRecoveredSig& re
668672

669673
void CInstantSendManager::TrySignInstantSendLock(const CTransaction& tx)
670674
{
671-
auto llmqType = Params().GetConsensus().llmqTypeInstantSend;
675+
const auto llmqType = Params().GetConsensus().llmqTypeInstantSend;
672676

673677
for (auto& in : tx.vin) {
674678
auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout));
@@ -680,12 +684,20 @@ void CInstantSendManager::TrySignInstantSendLock(const CTransaction& tx)
680684
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: got all recovered sigs, creating CInstantSendLock\n", __func__,
681685
tx.GetHash().ToString());
682686

683-
CInstantSendLock islock;
687+
CInstantSendLock islock(CInstantSendLock::isdlock_version);
684688
islock.txid = tx.GetHash();
685689
for (auto& in : tx.vin) {
686690
islock.inputs.emplace_back(in.prevout);
687691
}
688692

693+
// compute cycle hash
694+
{
695+
LOCK(cs_main);
696+
const auto dkgInterval = GetLLMQParams(llmqType).dkgInterval;
697+
const auto quorumHeight = chainActive.Height() - (chainActive.Height() % dkgInterval);
698+
islock.cycleHash = chainActive[quorumHeight]->GetBlockHash();
699+
}
700+
689701
auto id = islock.GetRequestId();
690702

691703
if (quorumSigningManager->HasRecoveredSigForId(llmqType, id)) {
@@ -736,8 +748,9 @@ void CInstantSendManager::ProcessMessage(CNode* pfrom, const std::string& strCom
736748
return;
737749
}
738750

739-
if (strCommand == NetMsgType::ISLOCK) {
740-
auto islock = std::make_shared<CInstantSendLock>();
751+
if (strCommand == NetMsgType::ISLOCK || strCommand == NetMsgType::ISDLOCK) {
752+
const auto islock_version = strCommand == NetMsgType::ISLOCK ? CInstantSendLock::islock_version : CInstantSendLock::isdlock_version;
753+
const auto islock = std::make_shared<CInstantSendLock>(islock_version);
741754
vRecv >> *islock;
742755
ProcessMessageInstantSendLock(pfrom, islock);
743756
}
@@ -749,7 +762,7 @@ void CInstantSendManager::ProcessMessageInstantSendLock(const CNode* pfrom, cons
749762

750763
{
751764
LOCK(cs_main);
752-
EraseObjectRequest(pfrom->GetId(), CInv(MSG_ISLOCK, hash));
765+
EraseObjectRequest(pfrom->GetId(), CInv(islock->IsDeterministic() ? MSG_ISDLOCK : MSG_ISLOCK, hash));
753766
}
754767

755768
if (!PreVerifyInstantSendLock(*islock)) {
@@ -788,6 +801,16 @@ bool CInstantSendManager::PreVerifyInstantSendLock(const llmq::CInstantSendLock&
788801
}
789802
}
790803

804+
if (islock.IsDeterministic()) {
805+
LOCK(cs_main);
806+
const auto llmqType = Params().GetConsensus().llmqTypeInstantSend;
807+
const auto dkgInterval = GetLLMQParams(llmqType).dkgInterval;
808+
const auto blockIndex = LookupBlockIndex(islock.cycleHash);
809+
if (blockIndex == nullptr || blockIndex->nHeight % dkgInterval != 0) {
810+
return false;
811+
}
812+
}
813+
791814
return true;
792815
}
793816

@@ -875,7 +898,23 @@ std::unordered_set<uint256> CInstantSendManager::ProcessPendingInstantSendLocks(
875898
continue;
876899
}
877900

878-
auto quorum = llmq::CSigningManager::SelectQuorumForSigning(llmqType, id, -1, signOffset);
901+
int nSignHeight{-1};
902+
if (islock->IsDeterministic()) {
903+
LOCK(cs_main);
904+
905+
const auto blockIndex = LookupBlockIndex(islock->cycleHash);
906+
if (blockIndex == nullptr) {
907+
batchVerifier.badSources.emplace(nodeId);
908+
continue;
909+
}
910+
911+
const auto dkgInterval = GetLLMQParams(Params().GetConsensus().llmqTypeInstantSend).dkgInterval;
912+
if (blockIndex->nHeight + dkgInterval < chainActive.Height()) {
913+
nSignHeight = blockIndex->nHeight + dkgInterval - 1;
914+
}
915+
}
916+
917+
auto quorum = llmq::CSigningManager::SelectQuorumForSigning(llmqType, id, nSignHeight, signOffset);
879918
if (!quorum) {
880919
// should not happen, but if one fails to select, all others will also fail to select
881920
return {};
@@ -1009,13 +1048,14 @@ void CInstantSendManager::ProcessInstantSendLock(NodeId from, const uint256& has
10091048
TruncateRecoveredSigsForInputs(*islock);
10101049
}
10111050

1012-
CInv inv(MSG_ISLOCK, hash);
1051+
const auto is_det = islock->IsDeterministic();
1052+
CInv inv(is_det ? MSG_ISDLOCK : MSG_ISLOCK, hash);
10131053
if (tx != nullptr) {
1014-
g_connman->RelayInvFiltered(inv, *tx, LLMQS_PROTO_VERSION);
1054+
g_connman->RelayInvFiltered(inv, *tx, is_det ? ISDLOCK_PROTO_VERSION : LLMQS_PROTO_VERSION);
10151055
} else {
10161056
// we don't have the TX yet, so we only filter based on txid. Later when that TX arrives, we will re-announce
10171057
// with the TX taken into account.
1018-
g_connman->RelayInvFiltered(inv, islock->txid, LLMQS_PROTO_VERSION);
1058+
g_connman->RelayInvFiltered(inv, islock->txid, is_det ? ISDLOCK_PROTO_VERSION : LLMQS_PROTO_VERSION);
10191059
}
10201060

10211061
ResolveBlockConflicts(hash, *islock);
@@ -1054,8 +1094,8 @@ void CInstantSendManager::TransactionAddedToMempool(const CTransactionRef& tx)
10541094
}
10551095
// In case the islock was received before the TX, filtered announcement might have missed this islock because
10561096
// we were unable to check for filter matches deep inside the TX. Now we have the TX, so we should retry.
1057-
CInv inv(MSG_ISLOCK, ::SerializeHash(*islock));
1058-
g_connman->RelayInvFiltered(inv, *tx, LLMQS_PROTO_VERSION);
1097+
CInv inv(islock->IsDeterministic() ? MSG_ISDLOCK : MSG_ISLOCK, ::SerializeHash(*islock));
1098+
g_connman->RelayInvFiltered(inv, *tx, islock->IsDeterministic() ? ISDLOCK_PROTO_VERSION : LLMQS_PROTO_VERSION);
10591099
// If the islock was received before the TX, we know we were not able to send
10601100
// the notification at that time, we need to do it now.
10611101
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- notify about an earlier received lock for tx %s\n", __func__, tx->GetHash().ToString());

src/llmq/quorums_instantsend.h

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,33 @@
2020
namespace llmq
2121
{
2222

23-
class CInstantSendLock
23+
struct CInstantSendLock
2424
{
25-
public:
25+
// This is the old format of instant send lock, it must remain 0
26+
static const uint8_t islock_version = 0;
27+
// This is the new format of instant send deterministic lock, this should be incremented for new isdlock versions
28+
static const uint8_t isdlock_version = 1;
29+
30+
uint8_t nVersion;
2631
std::vector<COutPoint> inputs;
2732
uint256 txid;
33+
uint256 cycleHash;
2834
CBLSLazySignature sig;
2935

30-
public:
36+
CInstantSendLock() : CInstantSendLock(islock_version) {}
37+
explicit CInstantSendLock(const uint8_t desiredVersion) : nVersion(desiredVersion) {}
38+
3139
SERIALIZE_METHODS(CInstantSendLock, obj)
3240
{
33-
READWRITE(obj.inputs, obj.txid, obj.sig);
41+
if (obj.IsDeterministic()) READWRITE(obj.nVersion);
42+
READWRITE(obj.inputs);
43+
READWRITE(obj.txid);
44+
if (obj.IsDeterministic()) READWRITE(obj.cycleHash);
45+
READWRITE(obj.sig);
3446
}
3547

3648
uint256 GetRequestId() const;
49+
bool IsDeterministic() const { return nVersion != islock_version; }
3750
};
3851

3952
typedef std::shared_ptr<CInstantSendLock> CInstantSendLockPtr;

src/net_processing.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ std::chrono::microseconds GetObjectInterval(int invType)
783783
case MSG_CLSIG:
784784
return std::chrono::seconds{5};
785785
case MSG_ISLOCK:
786+
case MSG_ISDLOCK:
786787
return std::chrono::seconds{10};
787788
default:
788789
return GETDATA_TX_INTERVAL;
@@ -1429,6 +1430,7 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
14291430
case MSG_CLSIG:
14301431
return llmq::chainLocksHandler->AlreadyHave(inv);
14311432
case MSG_ISLOCK:
1433+
case MSG_ISDLOCK:
14321434
return llmq::quorumInstantSendManager->AlreadyHave(inv);
14331435
}
14341436

@@ -1767,10 +1769,11 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm
17671769
}
17681770
}
17691771

1770-
if (!push && (inv.type == MSG_ISLOCK)) {
1772+
if (!push && (inv.type == MSG_ISLOCK || inv.type == MSG_ISDLOCK)) {
17711773
llmq::CInstantSendLock o;
17721774
if (llmq::quorumInstantSendManager->GetInstantSendLockByHash(inv.hash, o)) {
1773-
connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::ISLOCK, o));
1775+
const auto msg_type = inv.type == MSG_ISLOCK ? NetMsgType::ISLOCK : NetMsgType::ISDLOCK;
1776+
connman->PushMessage(pfrom, msgMaker.Make(msg_type, o));
17741777
push = true;
17751778
}
17761779
}
@@ -4368,9 +4371,11 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
43684371
int nInvType = CCoinJoin::GetDSTX(hash) ? MSG_DSTX : MSG_TX;
43694372
queueAndMaybePushInv(CInv(nInvType, hash));
43704373

4371-
uint256 islockHash;
4372-
if (!llmq::quorumInstantSendManager->GetInstantSendLockHashByTxid(hash, islockHash)) continue;
4373-
queueAndMaybePushInv(CInv(MSG_ISLOCK, islockHash));
4374+
const auto islock = llmq::quorumInstantSendManager->GetInstantSendLockByTxid(hash);
4375+
if (islock == nullptr) continue;
4376+
if (pto->nVersion < LLMQS_PROTO_VERSION) continue;
4377+
if (pto->nVersion < ISDLOCK_PROTO_VERSION && islock->IsDeterministic()) continue;
4378+
queueAndMaybePushInv(CInv(islock->IsDeterministic() ? MSG_ISDLOCK : MSG_ISLOCK, ::SerializeHash(*islock)));
43744379
}
43754380

43764381
// Send an inv for the best ChainLock we have

src/protocol.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const char* QGETDATA = "qgetdata";
7474
const char* QDATA = "qdata";
7575
const char *CLSIG="clsig";
7676
const char *ISLOCK="islock";
77+
const char *ISDLOCK="isdlock";
7778
const char *MNAUTH="mnauth";
7879
}; // namespace NetMsgType
7980

@@ -145,6 +146,7 @@ const static std::string allNetMessageTypes[] = {
145146
NetMsgType::QDATA,
146147
NetMsgType::CLSIG,
147148
NetMsgType::ISLOCK,
149+
NetMsgType::ISDLOCK,
148150
NetMsgType::MNAUTH,
149151
};
150152
const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
@@ -277,6 +279,7 @@ const char* CInv::GetCommandInternal() const
277279
case MSG_QUORUM_RECOVERED_SIG: return NetMsgType::QSIGREC;
278280
case MSG_CLSIG: return NetMsgType::CLSIG;
279281
case MSG_ISLOCK: return NetMsgType::ISLOCK;
282+
case MSG_ISDLOCK: return NetMsgType::ISDLOCK;
280283
default:
281284
return nullptr;
282285
}

src/protocol.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ extern const char* QGETDATA;
255255
extern const char* QDATA;
256256
extern const char *CLSIG;
257257
extern const char *ISLOCK;
258+
extern const char *ISDLOCK;
258259
extern const char *MNAUTH;
259260
};
260261

@@ -412,6 +413,7 @@ enum GetDataMsg {
412413
MSG_QUORUM_RECOVERED_SIG = 28,
413414
MSG_CLSIG = 29,
414415
MSG_ISLOCK = 30,
416+
MSG_ISDLOCK = 31,
415417
};
416418

417419
/** inv message data */

src/version.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313

14-
static const int PROTOCOL_VERSION = 70219;
14+
static const int PROTOCOL_VERSION = 70220;
1515

1616
//! initial proto version, to be increased after version/verack negotiation
1717
static const int INIT_PROTO_VERSION = 209;
@@ -48,6 +48,9 @@ static const int MNAUTH_NODE_VER_VERSION = 70218;
4848
//! introduction of QGETDATA/QDATA messages
4949
static const int LLMQ_DATA_MESSAGES_VERSION = 70219;
5050

51+
//! introduction of instant send deterministic lock (ISDLOCK)
52+
static const int ISDLOCK_PROTO_VERSION = 70220;
53+
5154
// Make sure that none of the values above collide with `ADDRV2_FORMAT`.
5255

5356
#endif // BITCOIN_VERSION_H

test/functional/feature_llmq_is_cl_conflicts.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ def send_clsig(self, clsig):
3535
inv = msg_inv([CInv(29, hash)])
3636
self.send_message(inv)
3737

38-
def send_islock(self, islock):
38+
def send_islock(self, islock, deterministic=False):
3939
hash = uint256_from_str(hash256(islock.serialize()))
4040
self.islocks[hash] = islock
4141

42-
inv = msg_inv([CInv(30, hash)])
42+
inv = msg_inv([CInv(31 if deterministic else 30, hash)])
4343
self.send_message(inv)
4444

4545
def on_getdata(self, message):
@@ -72,7 +72,8 @@ def run_test(self):
7272
self.test_chainlock_overrides_islock(False)
7373
self.test_chainlock_overrides_islock(True, False)
7474
self.test_chainlock_overrides_islock(True, True)
75-
self.test_chainlock_overrides_islock_overrides_nonchainlock()
75+
self.test_chainlock_overrides_islock_overrides_nonchainlock(False)
76+
self.test_chainlock_overrides_islock_overrides_nonchainlock(True)
7677

7778
def test_chainlock_overrides_islock(self, test_block_conflict, mine_confllicting=False):
7879
if not test_block_conflict:
@@ -191,7 +192,7 @@ def test_chainlock_overrides_islock(self, test_block_conflict, mine_confllicting
191192
assert rawtx['instantlock']
192193
assert not rawtx['instantlock_internal']
193194

194-
def test_chainlock_overrides_islock_overrides_nonchainlock(self):
195+
def test_chainlock_overrides_islock_overrides_nonchainlock(self, deterministic):
195196
# create two raw TXs, they will conflict with each other
196197
rawtx1 = self.create_raw_tx(self.nodes[0], self.nodes[0], 1, 1, 100)['hex']
197198
rawtx2 = self.create_raw_tx(self.nodes[0], self.nodes[0], 1, 1, 100)['hex']
@@ -200,7 +201,7 @@ def test_chainlock_overrides_islock_overrides_nonchainlock(self):
200201
rawtx2_txid = encode(hash256(hex_str_to_bytes(rawtx2))[::-1], 'hex_codec').decode('ascii')
201202

202203
# Create an ISLOCK but don't broadcast it yet
203-
islock = self.create_islock(rawtx2)
204+
islock = self.create_islock(rawtx2, deterministic)
204205

205206
# Disable ChainLocks to avoid accidental locking
206207
self.nodes[0].spork("SPORK_19_CHAINLOCKS_ENABLED", 4070908800)
@@ -229,7 +230,7 @@ def test_chainlock_overrides_islock_overrides_nonchainlock(self):
229230

230231
# Send the ISLOCK, which should result in the last 2 blocks to be invalidated, even though the nodes don't know
231232
# the locked transaction yet
232-
self.test_node.send_islock(islock)
233+
self.test_node.send_islock(islock, deterministic)
233234
time.sleep(5)
234235

235236
assert(self.nodes[0].getbestblockhash() == good_tip)

test/functional/interface_zmq_dash.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
hash256,
3333
msg_clsig,
3434
msg_inv,
35-
msg_islock,
35+
msg_isdlock,
3636
msg_tx,
3737
ser_string,
3838
uint256_from_str,
@@ -264,7 +264,7 @@ def test_instantsend_publishers(self):
264264
zmq_tx_lock_tx.deserialize(zmq_tx_lock_sig_stream)
265265
assert(zmq_tx_lock_tx.is_valid())
266266
assert_equal(zmq_tx_lock_tx.hash, rpc_raw_tx_1['txid'])
267-
zmq_tx_lock = msg_islock()
267+
zmq_tx_lock = msg_isdlock()
268268
zmq_tx_lock.deserialize(zmq_tx_lock_sig_stream)
269269
assert_equal(uint256_to_string(zmq_tx_lock.txid), rpc_raw_tx_1['txid'])
270270
# Try to send the second transaction. This must throw an RPC error because it conflicts with rpc_raw_tx_1

test/functional/rpc_verifyislock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def run_test(self):
8282
break
8383
assert selected_hash == oldest_quorum_hash
8484
# Create the ISLOCK, then mine a quorum to move the signing quorum out of the active set
85-
islock = self.create_islock(rawtx)
85+
islock = self.create_islock(rawtx, True)
8686
# Mine one block to trigger the "signHeight + dkgInterval" verification for the ISLOCK
8787
self.mine_quorum()
8888
# Verify the ISLOCK for a transaction that is not yet known by the node

0 commit comments

Comments
 (0)