mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
Merge pull request #2833 from codablock/pr_dip4_quorums
Implement quorum merkle roots for DIP4 coinbases and add quorums to MNLISTDIFF
This commit is contained in:
commit
20ec1de4c6
@ -81,6 +81,81 @@ class LLMQCoinbaseCommitmentsTest(DashTestFramework):
|
||||
# When comparing genesis and best block, we shouldn't see the previously added and then deleted MN
|
||||
mnList = self.test_getmnlistdiff(null_hash, self.nodes[0].getbestblockhash(), {}, [], expectedUpdated2)
|
||||
|
||||
#############################
|
||||
# Now start testing quorum commitment merkle roots
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
oldhash = self.nodes[0].getbestblockhash()
|
||||
# Test DIP8 activation once with a pre-existing quorum and once without (we don't know in which order it will activate on mainnet)
|
||||
self.test_dip8_quorum_merkle_root_activation(True)
|
||||
for n in self.nodes:
|
||||
n.invalidateblock(oldhash)
|
||||
self.sync_all()
|
||||
first_quorum = self.test_dip8_quorum_merkle_root_activation(False)
|
||||
|
||||
self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0)
|
||||
self.wait_for_sporks_same()
|
||||
|
||||
# Verify that the first quorum appears in MNLISTDIFF
|
||||
expectedDeleted = []
|
||||
expectedNew = [QuorumId(100, int(first_quorum, 16))]
|
||||
quorumList = self.test_getmnlistdiff_quorums(null_hash, self.nodes[0].getbestblockhash(), {}, expectedDeleted, expectedNew)
|
||||
baseBlockHash = self.nodes[0].getbestblockhash()
|
||||
|
||||
second_quorum = self.mine_quorum()
|
||||
|
||||
# Verify that the second quorum appears in MNLISTDIFF
|
||||
expectedDeleted = []
|
||||
expectedNew = [QuorumId(100, int(second_quorum, 16))]
|
||||
quorums_before_third = self.test_getmnlistdiff_quorums(baseBlockHash, self.nodes[0].getbestblockhash(), quorumList, expectedDeleted, expectedNew)
|
||||
block_before_third = self.nodes[0].getbestblockhash()
|
||||
|
||||
third_quorum = self.mine_quorum()
|
||||
|
||||
# Verify that the first quorum is deleted and the third quorum is added in MNLISTDIFF (the first got inactive)
|
||||
expectedDeleted = [QuorumId(100, int(first_quorum, 16))]
|
||||
expectedNew = [QuorumId(100, int(third_quorum, 16))]
|
||||
self.test_getmnlistdiff_quorums(block_before_third, self.nodes[0].getbestblockhash(), quorums_before_third, expectedDeleted, expectedNew)
|
||||
|
||||
# Verify that the diff between genesis and best block is the current active set (second and third quorum)
|
||||
expectedDeleted = []
|
||||
expectedNew = [QuorumId(100, int(second_quorum, 16)), QuorumId(100, int(third_quorum, 16))]
|
||||
self.test_getmnlistdiff_quorums(null_hash, self.nodes[0].getbestblockhash(), {}, expectedDeleted, expectedNew)
|
||||
|
||||
# Now verify that diffs are correct around the block that mined the third quorum.
|
||||
# This tests the logic in CalcCbTxMerkleRootQuorums, which has to manually add the commitment from the current
|
||||
# block
|
||||
mined_in_block = self.nodes[0].quorum("info", 100, third_quorum)["minedBlock"]
|
||||
prev_block = self.nodes[0].getblock(mined_in_block)["previousblockhash"]
|
||||
prev_block2 = self.nodes[0].getblock(prev_block)["previousblockhash"]
|
||||
next_block = self.nodes[0].getblock(mined_in_block)["nextblockhash"]
|
||||
next_block2 = self.nodes[0].getblock(mined_in_block)["nextblockhash"]
|
||||
# The 2 block before the quorum was mined should both give an empty diff
|
||||
expectedDeleted = []
|
||||
expectedNew = []
|
||||
self.test_getmnlistdiff_quorums(block_before_third, prev_block2, quorums_before_third, expectedDeleted, expectedNew)
|
||||
self.test_getmnlistdiff_quorums(block_before_third, prev_block, quorums_before_third, expectedDeleted, expectedNew)
|
||||
# The block in which the quorum was mined and the 2 after that should all give the same diff
|
||||
expectedDeleted = [QuorumId(100, int(first_quorum, 16))]
|
||||
expectedNew = [QuorumId(100, int(third_quorum, 16))]
|
||||
quorums_with_third = self.test_getmnlistdiff_quorums(block_before_third, mined_in_block, quorums_before_third, expectedDeleted, expectedNew)
|
||||
self.test_getmnlistdiff_quorums(block_before_third, next_block, quorums_before_third, expectedDeleted, expectedNew)
|
||||
self.test_getmnlistdiff_quorums(block_before_third, next_block2, quorums_before_third, expectedDeleted, expectedNew)
|
||||
# A diff between the two block that happened after the quorum was mined should give an empty diff
|
||||
expectedDeleted = []
|
||||
expectedNew = []
|
||||
self.test_getmnlistdiff_quorums(mined_in_block, next_block, quorums_with_third, expectedDeleted, expectedNew)
|
||||
self.test_getmnlistdiff_quorums(mined_in_block, next_block2, quorums_with_third, expectedDeleted, expectedNew)
|
||||
self.test_getmnlistdiff_quorums(next_block, next_block2, quorums_with_third, expectedDeleted, expectedNew)
|
||||
|
||||
# Using the same block for baseBlockHash and blockHash should give empty diffs
|
||||
self.test_getmnlistdiff_quorums(prev_block, prev_block, quorums_before_third, expectedDeleted, expectedNew)
|
||||
self.test_getmnlistdiff_quorums(prev_block2, prev_block2, quorums_before_third, expectedDeleted, expectedNew)
|
||||
self.test_getmnlistdiff_quorums(mined_in_block, mined_in_block, quorums_with_third, expectedDeleted, expectedNew)
|
||||
self.test_getmnlistdiff_quorums(next_block, next_block, quorums_with_third, expectedDeleted, expectedNew)
|
||||
self.test_getmnlistdiff_quorums(next_block2, next_block2, quorums_with_third, expectedDeleted, expectedNew)
|
||||
|
||||
|
||||
def test_getmnlistdiff(self, baseBlockHash, blockHash, baseMNList, expectedDeleted, expectedUpdated):
|
||||
d = self.test_getmnlistdiff_base(baseBlockHash, blockHash)
|
||||
|
||||
@ -107,6 +182,34 @@ class LLMQCoinbaseCommitmentsTest(DashTestFramework):
|
||||
|
||||
return newMNList
|
||||
|
||||
def test_getmnlistdiff_quorums(self, baseBlockHash, blockHash, baseQuorumList, expectedDeleted, expectedNew):
|
||||
d = self.test_getmnlistdiff_base(baseBlockHash, blockHash)
|
||||
|
||||
assert_equal(set(d.deletedQuorums), set(expectedDeleted))
|
||||
assert_equal(set([QuorumId(e.llmqType, e.quorumHash) for e in d.newQuorums]), set(expectedNew))
|
||||
|
||||
newQuorumList = baseQuorumList.copy()
|
||||
|
||||
for e in d.deletedQuorums:
|
||||
newQuorumList.pop(e)
|
||||
|
||||
for e in d.newQuorums:
|
||||
newQuorumList[QuorumId(e.llmqType, e.quorumHash)] = e
|
||||
|
||||
cbtx = CCbTx()
|
||||
cbtx.deserialize(BytesIO(d.cbTx.vExtraPayload))
|
||||
|
||||
if cbtx.version >= 2:
|
||||
hashes = []
|
||||
for qc in newQuorumList.values():
|
||||
hashes.append(hash256(qc.serialize()))
|
||||
hashes.sort()
|
||||
merkleRoot = CBlock.get_merkle_root(hashes)
|
||||
assert_equal(merkleRoot, cbtx.merkleRootQuorums)
|
||||
|
||||
return newQuorumList
|
||||
|
||||
|
||||
def test_getmnlistdiff_base(self, baseBlockHash, blockHash):
|
||||
hexstr = self.nodes[0].getblockheader(blockHash, False)
|
||||
header = FromHex(CBlockHeader(), hexstr)
|
||||
@ -128,9 +231,56 @@ class LLMQCoinbaseCommitmentsTest(DashTestFramework):
|
||||
assert_equal(d2["cbTx"], d.cbTx.serialize().hex())
|
||||
assert_equal(set([int(e, 16) for e in d2["deletedMNs"]]), set(d.deletedMNs))
|
||||
assert_equal(set([int(e["proRegTxHash"], 16) for e in d2["mnList"]]), set([e.proRegTxHash for e in d.mnList]))
|
||||
assert_equal(set([QuorumId(e["llmqType"], int(e["quorumHash"], 16)) for e in d2["deletedQuorums"]]), set(d.deletedQuorums))
|
||||
assert_equal(set([QuorumId(e["llmqType"], int(e["quorumHash"], 16)) for e in d2["newQuorums"]]), set([QuorumId(e.llmqType, e.quorumHash) for e in d.newQuorums]))
|
||||
|
||||
return d
|
||||
|
||||
def test_dip8_quorum_merkle_root_activation(self, with_initial_quorum):
|
||||
if with_initial_quorum:
|
||||
self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0)
|
||||
self.wait_for_sporks_same()
|
||||
|
||||
# Mine one quorum before dip8 is activated
|
||||
self.mine_quorum()
|
||||
|
||||
self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 4070908800)
|
||||
self.wait_for_sporks_same()
|
||||
|
||||
cbtx = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 2)["tx"][0]
|
||||
assert(cbtx["cbTx"]["version"] == 1)
|
||||
|
||||
assert(self.nodes[0].getblockchaininfo()["bip9_softforks"]["dip0008"]["status"] != "active")
|
||||
|
||||
while self.nodes[0].getblockchaininfo()["bip9_softforks"]["dip0008"]["status"] != "active":
|
||||
self.nodes[0].generate(4)
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(1)
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
# Assert that merkleRootQuorums is present and 0 (we have no quorums yet)
|
||||
cbtx = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 2)["tx"][0]
|
||||
assert_equal(cbtx["cbTx"]["version"], 2)
|
||||
assert("merkleRootQuorums" in cbtx["cbTx"])
|
||||
merkleRootQuorums = int(cbtx["cbTx"]["merkleRootQuorums"], 16)
|
||||
|
||||
if with_initial_quorum:
|
||||
assert(merkleRootQuorums != 0)
|
||||
else:
|
||||
assert_equal(merkleRootQuorums, 0)
|
||||
|
||||
set_mocktime(get_mocktime() + 1)
|
||||
set_node_times(self.nodes, get_mocktime())
|
||||
self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0)
|
||||
self.wait_for_sporks_same()
|
||||
|
||||
# Mine quorum and verify that merkleRootQuorums has changed
|
||||
quorum = self.mine_quorum()
|
||||
cbtx = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 2)["tx"][0]
|
||||
assert(int(cbtx["cbTx"]["merkleRootQuorums"], 16) != merkleRootQuorums)
|
||||
|
||||
return quorum
|
||||
|
||||
def confirm_mns(self):
|
||||
while True:
|
||||
diff = self.nodes[0].protx("diff", 1, self.nodes[0].getblockcount())
|
||||
|
@ -56,7 +56,7 @@ def create_coinbase(height, pubkey = None, dip4_activated=False):
|
||||
if dip4_activated:
|
||||
coinbase.nVersion = 3
|
||||
coinbase.nType = 5
|
||||
cbtx_payload = CCbTx(1, height, 0)
|
||||
cbtx_payload = CCbTx(2, height, 0, 0)
|
||||
coinbase.vExtraPayload = cbtx_payload.serialize()
|
||||
coinbase.calc_sha256()
|
||||
return coinbase
|
||||
|
@ -23,6 +23,8 @@ ser_*, deser_*: functions that handle serialization/deserialization
|
||||
import struct
|
||||
import socket
|
||||
import asyncore
|
||||
from collections import namedtuple
|
||||
|
||||
import time
|
||||
import sys
|
||||
import random
|
||||
@ -39,7 +41,7 @@ from test_framework.siphash import siphash256
|
||||
import dash_hash
|
||||
|
||||
BIP0031_VERSION = 60000
|
||||
MY_VERSION = 70213 # MIN_PEER_PROTO_VERSION
|
||||
MY_VERSION = 70214 # MIN_PEER_PROTO_VERSION
|
||||
MY_SUBVERSION = b"/python-mininode-tester:0.0.3/"
|
||||
MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37)
|
||||
|
||||
@ -309,7 +311,7 @@ class CInv(object):
|
||||
|
||||
def __repr__(self):
|
||||
return "CInv(type=%s hash=%064x)" \
|
||||
% (self.typemap[self.type], self.hash)
|
||||
% (self.typemap.get(self.type, "%d" % self.type), self.hash)
|
||||
|
||||
|
||||
class CBlockLocator(object):
|
||||
@ -883,7 +885,7 @@ class CMerkleBlock(object):
|
||||
|
||||
|
||||
class CCbTx(object):
|
||||
def __init__(self, version=None, height=None, merkleRootMNList=None):
|
||||
def __init__(self, version=None, height=None, merkleRootMNList=None, merkleRootQuorums=None):
|
||||
self.set_null()
|
||||
if version is not None:
|
||||
self.version = version
|
||||
@ -891,6 +893,8 @@ class CCbTx(object):
|
||||
self.height = height
|
||||
if merkleRootMNList is not None:
|
||||
self.merkleRootMNList = merkleRootMNList
|
||||
if merkleRootQuorums is not None:
|
||||
self.merkleRootQuorums = merkleRootQuorums
|
||||
|
||||
def set_null(self):
|
||||
self.version = 0
|
||||
@ -901,12 +905,16 @@ class CCbTx(object):
|
||||
self.version = struct.unpack("<H", f.read(2))[0]
|
||||
self.height = struct.unpack("<i", f.read(4))[0]
|
||||
self.merkleRootMNList = deser_uint256(f)
|
||||
if self.version >= 2:
|
||||
self.merkleRootQuorums = deser_uint256(f)
|
||||
|
||||
def serialize(self):
|
||||
r = b""
|
||||
r += struct.pack("<H", self.version)
|
||||
r += struct.pack("<i", self.height)
|
||||
r += ser_uint256(self.merkleRootMNList)
|
||||
if self.version >= 2:
|
||||
r += ser_uint256(self.merkleRootQuorums)
|
||||
return r
|
||||
|
||||
|
||||
@ -941,6 +949,46 @@ class CSimplifiedMNListEntry(object):
|
||||
return r
|
||||
|
||||
|
||||
class CFinalCommitment:
|
||||
def __init__(self):
|
||||
self.set_null()
|
||||
|
||||
def set_null(self):
|
||||
self.nVersion = 0
|
||||
self.llmqType = 0
|
||||
self.quorumHash = 0
|
||||
self.signers = []
|
||||
self.validMembers = []
|
||||
self.quorumPublicKey = b'\\x0' * 48
|
||||
self.quorumVvecHash = 0
|
||||
self.quorumSig = b'\\x0' * 96
|
||||
self.membersSig = b'\\x0' * 96
|
||||
|
||||
def deserialize(self, f):
|
||||
self.nVersion = struct.unpack("<H", f.read(2))[0]
|
||||
self.llmqType = struct.unpack("<B", f.read(1))[0]
|
||||
self.quorumHash = deser_uint256(f)
|
||||
self.signers = deser_dyn_bitset(f, False)
|
||||
self.validMembers = deser_dyn_bitset(f, False)
|
||||
self.quorumPublicKey = f.read(48)
|
||||
self.quorumVvecHash = deser_uint256(f)
|
||||
self.quorumSig = f.read(96)
|
||||
self.membersSig = f.read(96)
|
||||
|
||||
def serialize(self):
|
||||
r = b""
|
||||
r += struct.pack("<H", self.nVersion)
|
||||
r += struct.pack("<B", self.llmqType)
|
||||
r += ser_uint256(self.quorumHash)
|
||||
r += ser_dyn_bitset(self.signers, False)
|
||||
r += ser_dyn_bitset(self.validMembers, False)
|
||||
r += self.quorumPublicKey
|
||||
r += ser_uint256(self.quorumVvecHash)
|
||||
r += self.quorumSig
|
||||
r += self.membersSig
|
||||
return r
|
||||
|
||||
|
||||
# Objects that correspond to messages on the wire
|
||||
class msg_version(object):
|
||||
command = b"version"
|
||||
@ -1455,6 +1503,8 @@ class msg_getmnlistd(object):
|
||||
def __repr__(self):
|
||||
return "msg_getmnlistd(baseBlockHash=%064x, blockHash=%064x)" % (self.baseBlockHash, self.blockHash)
|
||||
|
||||
QuorumId = namedtuple('QuorumId', ['llmqType', 'quorumHash'])
|
||||
|
||||
class msg_mnlistdiff(object):
|
||||
command = b"mnlistdiff"
|
||||
|
||||
@ -1465,6 +1515,8 @@ class msg_mnlistdiff(object):
|
||||
self.cbTx = None
|
||||
self.deletedMNs = []
|
||||
self.mnList = []
|
||||
self.deletedQuorums = []
|
||||
self.newQuorums = []
|
||||
|
||||
def deserialize(self, f):
|
||||
self.baseBlockHash = deser_uint256(f)
|
||||
@ -1480,6 +1532,17 @@ class msg_mnlistdiff(object):
|
||||
e.deserialize(f)
|
||||
self.mnList.append(e)
|
||||
|
||||
self.deletedQuorums = []
|
||||
for i in range(deser_compact_size(f)):
|
||||
llmqType = struct.unpack("<B", f.read(1))[0]
|
||||
quorumHash = deser_uint256(f)
|
||||
self.deletedQuorums.append(QuorumId(llmqType, quorumHash))
|
||||
self.newQuorums = []
|
||||
for i in range(deser_compact_size(f)):
|
||||
qc = CFinalCommitment()
|
||||
qc.deserialize(f)
|
||||
self.newQuorums.append(qc)
|
||||
|
||||
def __repr__(self):
|
||||
return "msg_mnlistdiff(baseBlockHash=%064x, blockHash=%064x)" % (self.baseBlockHash, self.blockHash)
|
||||
|
||||
|
@ -683,9 +683,15 @@ class DashTestFramework(BitcoinTestFramework):
|
||||
set_node_times(self.nodes, get_mocktime())
|
||||
self.nodes[0].generate(1)
|
||||
sync_blocks(self.nodes)
|
||||
new_quorum = self.nodes[0].quorum("list", 1)["llmq_5_60"][0]
|
||||
|
||||
# Mine 8 (SIGN_HEIGHT_OFFSET) more blocks to make sure that the new quorum gets eligable for signing sessions
|
||||
self.nodes[0].generate(8)
|
||||
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
return new_quorum
|
||||
|
||||
# Test framework for doing p2p comparison testing, which sets up some bitcoind
|
||||
# binaries:
|
||||
# 1 binary: test binary
|
||||
|
388
src/dbwrapper.h
388
src/dbwrapper.h
@ -77,7 +77,14 @@ public:
|
||||
{
|
||||
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
||||
ssKey << key;
|
||||
leveldb::Slice slKey(ssKey.data(), ssKey.size());
|
||||
Write(ssKey, value);
|
||||
ssKey.clear();
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
void Write(const CDataStream& _ssKey, const V& value)
|
||||
{
|
||||
leveldb::Slice slKey(_ssKey.data(), _ssKey.size());
|
||||
|
||||
ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE);
|
||||
ssValue << value;
|
||||
@ -91,7 +98,6 @@ public:
|
||||
// - byte[]: value
|
||||
// The formula below assumes the key and value are both less than 16k.
|
||||
size_estimate += 3 + (slKey.size() > 127) + slKey.size() + (slValue.size() > 127) + slValue.size();
|
||||
ssKey.clear();
|
||||
ssValue.clear();
|
||||
}
|
||||
|
||||
@ -100,7 +106,12 @@ public:
|
||||
{
|
||||
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
||||
ssKey << key;
|
||||
leveldb::Slice slKey(ssKey.data(), ssKey.size());
|
||||
Erase(ssKey);
|
||||
ssKey.clear();
|
||||
}
|
||||
|
||||
void Erase(const CDataStream& _ssKey) {
|
||||
leveldb::Slice slKey(_ssKey.data(), _ssKey.size());
|
||||
|
||||
batch.Delete(slKey);
|
||||
// - byte: header
|
||||
@ -108,7 +119,6 @@ public:
|
||||
// - byte[]: key
|
||||
// The formula below assumes the key is less than 16kB.
|
||||
size_estimate += 2 + (slKey.size() > 127) + slKey.size();
|
||||
ssKey.clear();
|
||||
}
|
||||
|
||||
size_t SizeEstimate() const { return size_estimate; }
|
||||
@ -138,6 +148,10 @@ public:
|
||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
||||
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
||||
ssKey << key;
|
||||
Seek(ssKey);
|
||||
}
|
||||
|
||||
void Seek(const CDataStream& ssKey) {
|
||||
leveldb::Slice slKey(ssKey.data(), ssKey.size());
|
||||
piter->Seek(slKey);
|
||||
}
|
||||
@ -145,9 +159,8 @@ public:
|
||||
void Next();
|
||||
|
||||
template<typename K> bool GetKey(K& key) {
|
||||
leveldb::Slice slKey = piter->key();
|
||||
try {
|
||||
CDataStream ssKey(slKey.data(), slKey.data() + slKey.size(), SER_DISK, CLIENT_VERSION);
|
||||
CDataStream ssKey = GetKey();
|
||||
ssKey >> key;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
@ -155,6 +168,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
CDataStream GetKey() {
|
||||
leveldb::Slice slKey = piter->key();
|
||||
return CDataStream(slKey.data(), slKey.data() + slKey.size(), SER_DISK, CLIENT_VERSION);
|
||||
}
|
||||
|
||||
unsigned int GetKeySize() {
|
||||
return piter->key().size();
|
||||
}
|
||||
@ -231,6 +249,11 @@ public:
|
||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
||||
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
||||
ssKey << key;
|
||||
return ReadDataStream(ssKey, ssValue);
|
||||
}
|
||||
|
||||
bool ReadDataStream(const CDataStream& ssKey, CDataStream& ssValue) const
|
||||
{
|
||||
leveldb::Slice slKey(ssKey.data(), ssKey.size());
|
||||
|
||||
std::string strValue;
|
||||
@ -249,9 +272,18 @@ public:
|
||||
|
||||
template <typename K, typename V>
|
||||
bool Read(const K& key, V& value) const
|
||||
{
|
||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
||||
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
||||
ssKey << key;
|
||||
return Read(ssKey, value);
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
bool Read(const CDataStream& ssKey, V& value) const
|
||||
{
|
||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
||||
if (!ReadDataStream(key, ssValue)) {
|
||||
if (!ReadDataStream(ssKey, ssValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -277,7 +309,12 @@ public:
|
||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
||||
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
||||
ssKey << key;
|
||||
leveldb::Slice slKey(ssKey.data(), ssKey.size());
|
||||
return Exists(ssKey);
|
||||
}
|
||||
|
||||
bool Exists(const CDataStream& key) const
|
||||
{
|
||||
leveldb::Slice slKey(key.data(), key.size());
|
||||
|
||||
std::string strValue;
|
||||
leveldb::Status status = pdb->Get(readoptions, slKey, &strValue);
|
||||
@ -356,168 +393,262 @@ public:
|
||||
|
||||
};
|
||||
|
||||
template<typename CDBTransaction>
|
||||
class CDBTransactionIterator
|
||||
{
|
||||
private:
|
||||
CDBTransaction& transaction;
|
||||
|
||||
typedef typename std::remove_pointer<decltype(transaction.parent.NewIterator())>::type ParentIterator;
|
||||
|
||||
// We maintain 2 iterators, one for the transaction and one for the parent
|
||||
// At all times, only one of both provides the current value. The decision is made by comparing the current keys
|
||||
// of both iterators, so that always the smaller key is the current one. On Next(), the previously chosen iterator
|
||||
// is advanced.
|
||||
typename CDBTransaction::WritesMap::iterator transactionIt;
|
||||
std::unique_ptr<ParentIterator> parentIt;
|
||||
CDataStream parentKey;
|
||||
bool curIsParent{false};
|
||||
|
||||
public:
|
||||
CDBTransactionIterator(CDBTransaction& _transaction) :
|
||||
transaction(_transaction),
|
||||
parentKey(SER_DISK, CLIENT_VERSION)
|
||||
{
|
||||
transactionIt = transaction.writes.end();
|
||||
parentIt = std::unique_ptr<ParentIterator>(transaction.parent.NewIterator());
|
||||
}
|
||||
|
||||
void SeekToFirst() {
|
||||
transactionIt = transaction.writes.begin();
|
||||
parentIt->SeekToFirst();
|
||||
SkipDeletedAndOverwritten();
|
||||
DecideCur();
|
||||
}
|
||||
|
||||
template<typename K>
|
||||
void Seek(const K& key) {
|
||||
Seek(CDBTransaction::KeyToDataStream(key));
|
||||
}
|
||||
|
||||
void Seek(const CDataStream& ssKey) {
|
||||
transactionIt = transaction.writes.lower_bound(ssKey);
|
||||
parentIt->Seek(ssKey);
|
||||
SkipDeletedAndOverwritten();
|
||||
DecideCur();
|
||||
}
|
||||
|
||||
bool Valid() {
|
||||
return transactionIt != transaction.writes.end() || parentIt->Valid();
|
||||
}
|
||||
|
||||
void Next() {
|
||||
if (transactionIt == transaction.writes.end() && !parentIt->Valid()) {
|
||||
return;
|
||||
}
|
||||
if (curIsParent) {
|
||||
assert(parentIt->Valid());
|
||||
parentIt->Next();
|
||||
SkipDeletedAndOverwritten();
|
||||
} else {
|
||||
assert(transactionIt != transaction.writes.end());
|
||||
++transactionIt;
|
||||
}
|
||||
DecideCur();
|
||||
}
|
||||
|
||||
template<typename K>
|
||||
bool GetKey(K& key) {
|
||||
if (!Valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (curIsParent) {
|
||||
return parentIt->GetKey(key);
|
||||
} else {
|
||||
try {
|
||||
// TODO try to avoid this copy (we need a stream that allows reading from external buffers)
|
||||
CDataStream ssKey = transactionIt->first;
|
||||
ssKey >> key;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
CDataStream GetKey() {
|
||||
if (!Valid()) {
|
||||
return CDataStream(SER_DISK, CLIENT_VERSION);
|
||||
}
|
||||
if (curIsParent) {
|
||||
return parentIt->GetKey();
|
||||
} else {
|
||||
return transactionIt->first;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int GetKeySize() {
|
||||
if (!Valid()) {
|
||||
return 0;
|
||||
}
|
||||
if (curIsParent) {
|
||||
return parentIt->GetKeySize();
|
||||
} else {
|
||||
return transactionIt->first.vKey.size();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
bool GetValue(V& value) {
|
||||
if (!Valid()) {
|
||||
return false;
|
||||
}
|
||||
if (curIsParent) {
|
||||
return transaction.Read(parentKey, value);
|
||||
} else {
|
||||
return transaction.Read(transactionIt->first, value);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
void SkipDeletedAndOverwritten() {
|
||||
while (parentIt->Valid()) {
|
||||
parentKey = parentIt->GetKey();
|
||||
if (!transaction.deletes.count(parentKey) && !transaction.writes.count(parentKey)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DecideCur() {
|
||||
if (transactionIt != transaction.writes.end() && !parentIt->Valid()) {
|
||||
curIsParent = false;
|
||||
} else if (transactionIt == transaction.writes.end() && parentIt->Valid()) {
|
||||
curIsParent = true;
|
||||
} else if (transactionIt != transaction.writes.end() && parentIt->Valid()) {
|
||||
if (CDBTransaction::DataStreamCmp::less(transactionIt->first, parentKey)) {
|
||||
curIsParent = false;
|
||||
} else {
|
||||
curIsParent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Parent, typename CommitTarget>
|
||||
class CDBTransaction {
|
||||
friend class CDBTransactionIterator<CDBTransaction>;
|
||||
|
||||
protected:
|
||||
Parent &parent;
|
||||
CommitTarget &commitTarget;
|
||||
|
||||
struct KeyHolder {
|
||||
virtual ~KeyHolder() = default;
|
||||
virtual bool Less(const KeyHolder &b) const = 0;
|
||||
virtual void Erase(CommitTarget &commitTarget) = 0;
|
||||
};
|
||||
typedef std::unique_ptr<KeyHolder> KeyHolderPtr;
|
||||
|
||||
template <typename K>
|
||||
struct KeyHolderImpl : KeyHolder {
|
||||
KeyHolderImpl(const K &_key)
|
||||
: key(_key) {
|
||||
struct DataStreamCmp {
|
||||
static bool less(const CDataStream& a, const CDataStream& b) {
|
||||
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
|
||||
}
|
||||
virtual bool Less(const KeyHolder &b) const {
|
||||
auto *b2 = dynamic_cast<const KeyHolderImpl<K>*>(&b);
|
||||
return key < b2->key;
|
||||
bool operator()(const CDataStream& a, const CDataStream& b) const {
|
||||
return less(a, b);
|
||||
}
|
||||
virtual void Erase(CommitTarget &commitTarget) {
|
||||
commitTarget.Erase(key);
|
||||
}
|
||||
K key;
|
||||
};
|
||||
|
||||
struct KeyValueHolder {
|
||||
virtual ~KeyValueHolder() = default;
|
||||
virtual void Write(CommitTarget &parent) = 0;
|
||||
struct ValueHolder {
|
||||
virtual ~ValueHolder() = default;
|
||||
virtual void Write(const CDataStream& ssKey, CommitTarget &parent) = 0;
|
||||
};
|
||||
typedef std::unique_ptr<KeyValueHolder> KeyValueHolderPtr;
|
||||
typedef std::unique_ptr<ValueHolder> ValueHolderPtr;
|
||||
|
||||
template <typename K, typename V>
|
||||
struct KeyValueHolderImpl : KeyValueHolder {
|
||||
KeyValueHolderImpl(const KeyHolderImpl<K> &_key, const V &_value)
|
||||
: key(_key),
|
||||
value(_value) { }
|
||||
KeyValueHolderImpl(const KeyHolderImpl<K> &_key, V &&_value)
|
||||
: key(_key),
|
||||
value(std::forward<V>(_value)) { }
|
||||
virtual void Write(CommitTarget &commitTarget) {
|
||||
template <typename V>
|
||||
struct ValueHolderImpl : ValueHolder {
|
||||
ValueHolderImpl(const V &_value) : value(_value) { }
|
||||
|
||||
virtual void Write(const CDataStream& ssKey, CommitTarget &commitTarget) {
|
||||
// we're moving the value instead of copying it. This means that Write() can only be called once per
|
||||
// KeyValueHolderImpl instance. Commit() clears the write maps, so this ok.
|
||||
commitTarget.Write(key.key, std::move(value));
|
||||
// ValueHolderImpl instance. Commit() clears the write maps, so this ok.
|
||||
commitTarget.Write(ssKey, std::move(value));
|
||||
}
|
||||
const KeyHolderImpl<K> &key;
|
||||
V value;
|
||||
};
|
||||
|
||||
struct keyCmp {
|
||||
bool operator()(const KeyHolderPtr &a, const KeyHolderPtr &b) const {
|
||||
return a->Less(*b);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<KeyHolderPtr, KeyValueHolderPtr, keyCmp> KeyValueMap;
|
||||
typedef std::map<std::type_index, KeyValueMap> TypeKeyValueMap;
|
||||
|
||||
TypeKeyValueMap writes;
|
||||
TypeKeyValueMap deletes;
|
||||
|
||||
template <typename K>
|
||||
KeyValueMap *getMapForType(TypeKeyValueMap &m, bool create) {
|
||||
auto it = m.find(typeid(K));
|
||||
if (it != m.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
if (!create)
|
||||
return nullptr;
|
||||
auto it2 = m.emplace(typeid(K), KeyValueMap());
|
||||
return &it2.first->second;
|
||||
template<typename K>
|
||||
static CDataStream KeyToDataStream(const K& key) {
|
||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
||||
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
||||
ssKey << key;
|
||||
return ssKey;
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
KeyValueMap *getWritesMap(bool create) {
|
||||
return getMapForType<K>(writes, create);
|
||||
}
|
||||
typedef std::map<CDataStream, ValueHolderPtr, DataStreamCmp> WritesMap;
|
||||
typedef std::set<CDataStream, DataStreamCmp> DeletesSet;
|
||||
|
||||
template <typename K>
|
||||
KeyValueMap *getDeletesMap(bool create) {
|
||||
return getMapForType<K>(deletes, create);
|
||||
}
|
||||
|
||||
template <typename K, typename KV>
|
||||
void writeImpl(KeyHolderImpl<K>* k, KV&& kv) {
|
||||
auto k2 = KeyHolderPtr(k);
|
||||
|
||||
KeyValueMap *ds = getDeletesMap<K>(false);
|
||||
if (ds)
|
||||
ds->erase(k2);
|
||||
|
||||
KeyValueMap *ws = getWritesMap<K>(true);
|
||||
ws->erase(k2);
|
||||
ws->emplace(std::make_pair(std::move(k2), std::forward<KV>(kv)));
|
||||
}
|
||||
WritesMap writes;
|
||||
DeletesSet deletes;
|
||||
|
||||
public:
|
||||
CDBTransaction(Parent &_parent, CommitTarget &_commitTarget) : parent(_parent), commitTarget(_commitTarget) {}
|
||||
|
||||
template <typename K, typename V>
|
||||
void Write(const K& key, const V& v) {
|
||||
auto k = new KeyHolderImpl<K>(key);
|
||||
auto kv = std::make_unique<KeyValueHolderImpl<K, V>>(*k, v);
|
||||
writeImpl(k, std::move(kv));
|
||||
Write(KeyToDataStream(key), v);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void Write(const K& key, V&& v) {
|
||||
auto k = new KeyHolderImpl<K>(key);
|
||||
auto kv = std::make_unique<KeyValueHolderImpl<K, typename std::remove_reference<V>::type>>(*k, std::forward<V>(v));
|
||||
writeImpl(k, std::move(kv));
|
||||
template <typename V>
|
||||
void Write(const CDataStream& ssKey, const V& v) {
|
||||
deletes.erase(ssKey);
|
||||
auto it = writes.emplace(ssKey, nullptr).first;
|
||||
it->second = std::make_unique<ValueHolderImpl<V>>(v);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
bool Read(const K& key, V& value) {
|
||||
KeyHolderPtr k(new KeyHolderImpl<K>(key));
|
||||
return Read(KeyToDataStream(key), value);
|
||||
}
|
||||
|
||||
KeyValueMap *ds = getDeletesMap<K>(false);
|
||||
if (ds && ds->count(k))
|
||||
template <typename V>
|
||||
bool Read(const CDataStream& ssKey, V& value) {
|
||||
if (deletes.count(ssKey)) {
|
||||
return false;
|
||||
|
||||
KeyValueMap *ws = getWritesMap<K>(false);
|
||||
if (ws) {
|
||||
auto it = ws->find(k);
|
||||
if (it != ws->end()) {
|
||||
auto *impl = dynamic_cast<KeyValueHolderImpl<K, V> *>(it->second.get());
|
||||
if (!impl)
|
||||
return false;
|
||||
value = impl->value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return parent.Read(key, value);
|
||||
auto it = writes.find(ssKey);
|
||||
if (it != writes.end()) {
|
||||
auto *impl = dynamic_cast<ValueHolderImpl<V> *>(it->second.get());
|
||||
if (!impl) {
|
||||
throw std::runtime_error("Read called with V != previously written type");
|
||||
}
|
||||
value = impl->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent.Read(ssKey, value);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
bool Exists(const K& key) {
|
||||
KeyHolderPtr k(new KeyHolderImpl<K>(key));
|
||||
return Exists(KeyToDataStream(key));
|
||||
}
|
||||
|
||||
KeyValueMap *ds = getDeletesMap<K>(false);
|
||||
if (ds && ds->count(k))
|
||||
bool Exists(const CDataStream& ssKey) {
|
||||
if (deletes.count(ssKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyValueMap *ws = getWritesMap<K>(false);
|
||||
if (ws && ws->count(k))
|
||||
if (writes.count(ssKey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent.Exists(key);
|
||||
return parent.Exists(ssKey);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
void Erase(const K& key) {
|
||||
KeyHolderPtr k(new KeyHolderImpl<K>(key));
|
||||
return Erase(KeyToDataStream(key));
|
||||
}
|
||||
|
||||
KeyValueMap *ws = getWritesMap<K>(false);
|
||||
if (ws)
|
||||
ws->erase(k);
|
||||
KeyValueMap *ds = getDeletesMap<K>(true);
|
||||
ds->emplace(std::move(k), nullptr);
|
||||
void Erase(const CDataStream& ssKey) {
|
||||
writes.erase(ssKey);
|
||||
deletes.emplace(ssKey);
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
@ -526,15 +657,11 @@ public:
|
||||
}
|
||||
|
||||
void Commit() {
|
||||
for (auto &p : deletes) {
|
||||
for (auto &p2 : p.second) {
|
||||
p2.first->Erase(commitTarget);
|
||||
}
|
||||
for (const auto &k : deletes) {
|
||||
commitTarget.Erase(k);
|
||||
}
|
||||
for (auto &p : writes) {
|
||||
for (auto &p2 : p.second) {
|
||||
p2.second->Write(commitTarget);
|
||||
}
|
||||
p.second->Write(p.first, commitTarget);
|
||||
}
|
||||
Clear();
|
||||
}
|
||||
@ -542,6 +669,13 @@ public:
|
||||
bool IsClean() {
|
||||
return writes.empty() && deletes.empty();
|
||||
}
|
||||
|
||||
CDBTransactionIterator<CDBTransaction>* NewIterator() {
|
||||
return new CDBTransactionIterator<CDBTransaction>(*this);
|
||||
}
|
||||
std::unique_ptr<CDBTransactionIterator<CDBTransaction>> NewIteratorUniquePtr() {
|
||||
return std::make_unique<CDBTransactionIterator<CDBTransaction>>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Parent, typename CommitTarget>
|
||||
|
@ -4,10 +4,14 @@
|
||||
|
||||
#include "cbtx.h"
|
||||
#include "deterministicmns.h"
|
||||
#include "llmq/quorums.h"
|
||||
#include "llmq/quorums_blockprocessor.h"
|
||||
#include "llmq/quorums_commitment.h"
|
||||
#include "simplifiedmns.h"
|
||||
#include "specialtx.h"
|
||||
|
||||
#include "chainparams.h"
|
||||
#include "consensus/merkle.h"
|
||||
#include "univalue.h"
|
||||
#include "validation.h"
|
||||
|
||||
@ -34,11 +38,18 @@ bool CheckCbTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidatio
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-height");
|
||||
}
|
||||
|
||||
if (pindexPrev) {
|
||||
bool fDIP0008Active = VersionBitsState(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0008, versionbitscache) == THRESHOLD_ACTIVE;
|
||||
if (fDIP0008Active && cbTx.nVersion < 2) {
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-version");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This can only be done after the block has been fully processed, as otherwise we won't have the finished MN list
|
||||
bool CheckCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindex, CValidationState& state)
|
||||
bool CheckCbTxMerkleRoots(const CBlock& block, const CBlockIndex* pindex, CValidationState& state)
|
||||
{
|
||||
if (block.vtx[0]->nType != TRANSACTION_COINBASE) {
|
||||
return true;
|
||||
@ -57,6 +68,14 @@ bool CheckCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindex, C
|
||||
if (calculatedMerkleRoot != cbTx.merkleRootMNList) {
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-mnmerkleroot");
|
||||
}
|
||||
if (cbTx.nVersion >= 2) {
|
||||
if (!CalcCbTxMerkleRootQuorums(block, pindex->pprev, calculatedMerkleRoot, state)) {
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-quorummerkleroot");
|
||||
}
|
||||
if (calculatedMerkleRoot != cbTx.merkleRootQuorums) {
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-quorummerkleroot");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -78,10 +97,68 @@ bool CalcCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindexPrev
|
||||
return !mutated;
|
||||
}
|
||||
|
||||
bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPrev, uint256& merkleRootRet, CValidationState& state)
|
||||
{
|
||||
auto quorums = llmq::quorumBlockProcessor->GetMinedAndActiveCommitmentsUntilBlock(pindexPrev);
|
||||
std::map<Consensus::LLMQType, std::vector<uint256>> qcHashes;
|
||||
size_t hashCount = 0;
|
||||
for (const auto& p : quorums) {
|
||||
auto& v = qcHashes[p.first];
|
||||
v.reserve(p.second.size());
|
||||
for (const auto& p2 : p.second) {
|
||||
llmq::CFinalCommitment qc;
|
||||
uint256 minedBlockHash;
|
||||
bool found = llmq::quorumBlockProcessor->GetMinedCommitment(p.first, p2->GetBlockHash(), qc, minedBlockHash);
|
||||
assert(found);
|
||||
v.emplace_back(::SerializeHash(qc));
|
||||
hashCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// now add the commitments from the current block, which are not returned by GetMinedAndActiveCommitmentsUntilBlock
|
||||
// due to the use of pindexPrev (we don't have the tip index here)
|
||||
for (size_t i = 1; i < block.vtx.size(); i++) {
|
||||
auto& tx = block.vtx[i];
|
||||
|
||||
if (tx->nVersion == 3 && tx->nType == TRANSACTION_QUORUM_COMMITMENT) {
|
||||
llmq::CFinalCommitmentTxPayload qc;
|
||||
if (!GetTxPayload(*tx, qc)) {
|
||||
assert(false);
|
||||
}
|
||||
if (qc.commitment.IsNull()) {
|
||||
continue;
|
||||
}
|
||||
auto qcHash = ::SerializeHash(qc.commitment);
|
||||
const auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)qc.commitment.llmqType);
|
||||
auto& v = qcHashes[params.type];
|
||||
if (v.size() == params.signingActiveQuorumCount) {
|
||||
v.pop_back();
|
||||
}
|
||||
v.emplace_back(qcHash);
|
||||
hashCount++;
|
||||
assert(v.size() <= params.signingActiveQuorumCount);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint256> qcHashesVec;
|
||||
qcHashesVec.reserve(hashCount);
|
||||
|
||||
for (const auto& p : qcHashes) {
|
||||
for (const auto& h : p.second) {
|
||||
qcHashesVec.emplace_back(h);
|
||||
}
|
||||
}
|
||||
std::sort(qcHashesVec.begin(), qcHashesVec.end());
|
||||
|
||||
bool mutated = false;
|
||||
merkleRootRet = ComputeMerkleRoot(qcHashesVec, &mutated);
|
||||
return !mutated;
|
||||
}
|
||||
|
||||
std::string CCbTx::ToString() const
|
||||
{
|
||||
return strprintf("CCbTx(nHeight=%d, nVersion=%d, merkleRootMNList=%s)",
|
||||
nVersion, nHeight, merkleRootMNList.ToString());
|
||||
return strprintf("CCbTx(nHeight=%d, nVersion=%d, merkleRootMNList=%s, merkleRootQuorums=%s)",
|
||||
nVersion, nHeight, merkleRootMNList.ToString(), merkleRootQuorums.ToString());
|
||||
}
|
||||
|
||||
void CCbTx::ToJson(UniValue& obj) const
|
||||
@ -91,4 +168,7 @@ void CCbTx::ToJson(UniValue& obj) const
|
||||
obj.push_back(Pair("version", (int)nVersion));
|
||||
obj.push_back(Pair("height", (int)nHeight));
|
||||
obj.push_back(Pair("merkleRootMNList", merkleRootMNList.ToString()));
|
||||
if (nVersion >= 2) {
|
||||
obj.push_back(Pair("merkleRootQuorums", merkleRootQuorums.ToString()));
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,13 @@ class UniValue;
|
||||
class CCbTx
|
||||
{
|
||||
public:
|
||||
static const uint16_t CURRENT_VERSION = 1;
|
||||
static const uint16_t CURRENT_VERSION = 2;
|
||||
|
||||
public:
|
||||
uint16_t nVersion{CURRENT_VERSION};
|
||||
int32_t nHeight{0};
|
||||
uint256 merkleRootMNList;
|
||||
uint256 merkleRootQuorums;
|
||||
|
||||
public:
|
||||
ADD_SERIALIZE_METHODS;
|
||||
@ -32,6 +33,10 @@ public:
|
||||
READWRITE(nVersion);
|
||||
READWRITE(nHeight);
|
||||
READWRITE(merkleRootMNList);
|
||||
|
||||
if (nVersion >= 2) {
|
||||
READWRITE(merkleRootQuorums);
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString() const;
|
||||
@ -40,7 +45,8 @@ public:
|
||||
|
||||
bool CheckCbTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state);
|
||||
|
||||
bool CheckCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindex, CValidationState& state);
|
||||
bool CheckCbTxMerkleRoots(const CBlock& block, const CBlockIndex* pindex, CValidationState& state);
|
||||
bool CalcCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindexPrev, uint256& merkleRootRet, CValidationState& state);
|
||||
bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPrev, uint256& merkleRootRet, CValidationState& state);
|
||||
|
||||
#endif //DASH_CBTX_H
|
||||
|
@ -35,6 +35,11 @@ public:
|
||||
return t;
|
||||
}
|
||||
|
||||
CurTransaction& GetCurTransaction()
|
||||
{
|
||||
return curDBTransaction;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
bool Read(const K& key, V& value)
|
||||
{
|
||||
|
@ -5,6 +5,9 @@
|
||||
#include "cbtx.h"
|
||||
#include "core_io.h"
|
||||
#include "deterministicmns.h"
|
||||
#include "llmq/quorums.h"
|
||||
#include "llmq/quorums_blockprocessor.h"
|
||||
#include "llmq/quorums_commitment.h"
|
||||
#include "simplifiedmns.h"
|
||||
#include "specialtx.h"
|
||||
|
||||
@ -81,6 +84,50 @@ uint256 CSimplifiedMNList::CalcMerkleRoot(bool* pmutated) const
|
||||
return ComputeMerkleRoot(leaves, pmutated);
|
||||
}
|
||||
|
||||
CSimplifiedMNListDiff::CSimplifiedMNListDiff()
|
||||
{
|
||||
}
|
||||
|
||||
CSimplifiedMNListDiff::~CSimplifiedMNListDiff()
|
||||
{
|
||||
}
|
||||
|
||||
bool CSimplifiedMNListDiff::BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, const CBlockIndex* blockIndex)
|
||||
{
|
||||
auto baseQuorums = llmq::quorumBlockProcessor->GetMinedAndActiveCommitmentsUntilBlock(baseBlockIndex);
|
||||
auto quorums = llmq::quorumBlockProcessor->GetMinedAndActiveCommitmentsUntilBlock(blockIndex);
|
||||
|
||||
std::set<std::pair<Consensus::LLMQType, uint256>> baseQuorumHashes;
|
||||
std::set<std::pair<Consensus::LLMQType, uint256>> quorumHashes;
|
||||
for (auto& p : baseQuorums) {
|
||||
for (auto& p2 : p.second) {
|
||||
baseQuorumHashes.emplace(p.first, p2->GetBlockHash());
|
||||
}
|
||||
}
|
||||
for (auto& p : quorums) {
|
||||
for (auto& p2 : p.second) {
|
||||
quorumHashes.emplace(p.first, p2->GetBlockHash());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& p : baseQuorumHashes) {
|
||||
if (!quorumHashes.count(p)) {
|
||||
deletedQuorums.emplace_back((uint8_t)p.first, p.second);
|
||||
}
|
||||
}
|
||||
for (auto& p : quorumHashes) {
|
||||
if (!baseQuorumHashes.count(p)) {
|
||||
llmq::CFinalCommitment qc;
|
||||
uint256 minedBlockHash;
|
||||
if (!llmq::quorumBlockProcessor->GetMinedCommitment(p.first, p.second, qc, minedBlockHash)) {
|
||||
return false;
|
||||
}
|
||||
newQuorums.emplace_back(qc);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSimplifiedMNListDiff::ToJson(UniValue& obj) const
|
||||
{
|
||||
obj.setObject();
|
||||
@ -108,9 +155,29 @@ void CSimplifiedMNListDiff::ToJson(UniValue& obj) const
|
||||
}
|
||||
obj.push_back(Pair("mnList", mnListArr));
|
||||
|
||||
UniValue deletedQuorumsArr(UniValue::VARR);
|
||||
for (const auto& e : deletedQuorums) {
|
||||
UniValue eObj(UniValue::VOBJ);
|
||||
eObj.push_back(Pair("llmqType", e.first));
|
||||
eObj.push_back(Pair("quorumHash", e.second.ToString()));
|
||||
deletedQuorumsArr.push_back(eObj);
|
||||
}
|
||||
obj.push_back(Pair("deletedQuorums", deletedQuorumsArr));
|
||||
|
||||
UniValue newQuorumsArr(UniValue::VARR);
|
||||
for (const auto& e : newQuorums) {
|
||||
UniValue eObj;
|
||||
e.ToJson(eObj);
|
||||
newQuorumsArr.push_back(eObj);
|
||||
}
|
||||
obj.push_back(Pair("newQuorums", newQuorumsArr));
|
||||
|
||||
CCbTx cbTxPayload;
|
||||
if (GetTxPayload(*cbTx, cbTxPayload)) {
|
||||
obj.push_back(Pair("merkleRootMNList", cbTxPayload.merkleRootMNList.ToString()));
|
||||
if (cbTxPayload.nVersion >= 2) {
|
||||
obj.push_back(Pair("merkleRootQuorums", cbTxPayload.merkleRootQuorums.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,6 +217,11 @@ bool BuildSimplifiedMNListDiff(const uint256& baseBlockHash, const uint256& bloc
|
||||
auto dmnList = deterministicMNManager->GetListForBlock(blockHash);
|
||||
mnListDiffRet = baseDmnList.BuildSimplifiedDiff(dmnList);
|
||||
|
||||
if (!mnListDiffRet.BuildQuorumsDiff(baseBlockIndex, blockIndex)) {
|
||||
errorRet = strprintf("failed to build quorums diff");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO store coinbase TX in CBlockIndex
|
||||
CBlock block;
|
||||
if (!ReadBlockFromDisk(block, blockIndex, Params().GetConsensus())) {
|
||||
|
@ -10,11 +10,17 @@
|
||||
#include "netaddress.h"
|
||||
#include "pubkey.h"
|
||||
#include "serialize.h"
|
||||
#include "version.h"
|
||||
|
||||
class UniValue;
|
||||
class CDeterministicMNList;
|
||||
class CDeterministicMN;
|
||||
|
||||
namespace llmq
|
||||
{
|
||||
class CFinalCommitment;
|
||||
}
|
||||
|
||||
class CSimplifiedMNListEntry
|
||||
{
|
||||
public:
|
||||
@ -107,6 +113,10 @@ public:
|
||||
std::vector<uint256> deletedMNs;
|
||||
std::vector<CSimplifiedMNListEntry> mnList;
|
||||
|
||||
// starting with proto version LLMQS_PROTO_VERSION, we also transfer changes in active quorums
|
||||
std::vector<std::pair<uint8_t, uint256>> deletedQuorums; // p<LLMQType, quorumHash>
|
||||
std::vector<llmq::CFinalCommitment> newQuorums;
|
||||
|
||||
public:
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
@ -119,9 +129,19 @@ public:
|
||||
READWRITE(cbTx);
|
||||
READWRITE(deletedMNs);
|
||||
READWRITE(mnList);
|
||||
|
||||
if (s.GetVersion() >= LLMQS_PROTO_VERSION) {
|
||||
READWRITE(deletedQuorums);
|
||||
READWRITE(newQuorums);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
CSimplifiedMNListDiff();
|
||||
~CSimplifiedMNListDiff();
|
||||
|
||||
bool BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, const CBlockIndex* blockIndex);
|
||||
|
||||
void ToJson(UniValue& obj) const;
|
||||
};
|
||||
|
||||
|
@ -106,7 +106,7 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CV
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckCbTxMerkleRootMNList(block, pindex, state)) {
|
||||
if (!CheckCbTxMerkleRoots(block, pindex, state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ static uint256 MakeQuorumKey(const CQuorum& q)
|
||||
{
|
||||
CHashWriter hw(SER_NETWORK, 0);
|
||||
hw << (uint8_t)q.params.type;
|
||||
hw << q.quorumHash;
|
||||
hw << q.qc.quorumHash;
|
||||
for (const auto& dmn : q.members) {
|
||||
hw << dmn->proTxHash;
|
||||
}
|
||||
@ -48,13 +48,12 @@ CQuorum::~CQuorum()
|
||||
}
|
||||
}
|
||||
|
||||
void CQuorum::Init(const uint256& _quorumHash, int _height, const std::vector<CDeterministicMNCPtr>& _members, const std::vector<bool>& _validMembers, const CBLSPublicKey& _quorumPublicKey)
|
||||
void CQuorum::Init(const CFinalCommitment& _qc, int _height, const uint256& _minedBlockHash, const std::vector<CDeterministicMNCPtr>& _members)
|
||||
{
|
||||
quorumHash = _quorumHash;
|
||||
qc = _qc;
|
||||
height = _height;
|
||||
members = _members;
|
||||
validMembers = _validMembers;
|
||||
quorumPublicKey = _quorumPublicKey;
|
||||
minedBlockHash = _minedBlockHash;
|
||||
}
|
||||
|
||||
bool CQuorum::IsMember(const uint256& proTxHash) const
|
||||
@ -71,7 +70,7 @@ bool CQuorum::IsValidMember(const uint256& proTxHash) const
|
||||
{
|
||||
for (size_t i = 0; i < members.size(); i++) {
|
||||
if (members[i]->proTxHash == proTxHash) {
|
||||
return validMembers[i];
|
||||
return qc.validMembers[i];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -79,7 +78,7 @@ bool CQuorum::IsValidMember(const uint256& proTxHash) const
|
||||
|
||||
CBLSPublicKey CQuorum::GetPubKeyShare(size_t memberIdx) const
|
||||
{
|
||||
if (quorumVvec == nullptr || memberIdx >= members.size() || !validMembers[memberIdx]) {
|
||||
if (quorumVvec == nullptr || memberIdx >= members.size() || !qc.validMembers[memberIdx]) {
|
||||
return CBLSPublicKey();
|
||||
}
|
||||
auto& m = members[memberIdx];
|
||||
@ -145,7 +144,7 @@ void CQuorum::StartCachePopulatorThread(std::shared_ptr<CQuorum> _this)
|
||||
_this->cachePopulatorThread = std::thread([_this, t]() {
|
||||
RenameThread("quorum-cachepop");
|
||||
for (size_t i = 0; i < _this->members.size() && !_this->stopCachePopulatorThread && !ShutdownRequested(); i++) {
|
||||
if (_this->validMembers[i]) {
|
||||
if (_this->qc.validMembers[i]) {
|
||||
_this->GetPubKeyShare(i);
|
||||
}
|
||||
}
|
||||
@ -190,26 +189,26 @@ void CQuorumManager::EnsureQuorumConnections(Consensus::LLMQType llmqType, const
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!g_connman->HasMasternodeQuorumNodes(llmqType, quorum->quorumHash)) {
|
||||
if (!g_connman->HasMasternodeQuorumNodes(llmqType, quorum->qc.quorumHash)) {
|
||||
std::map<CService, uint256> connections;
|
||||
if (quorum->IsMember(myProTxHash)) {
|
||||
connections = CLLMQUtils::GetQuorumConnections(llmqType, quorum->quorumHash, myProTxHash);
|
||||
connections = CLLMQUtils::GetQuorumConnections(llmqType, quorum->qc.quorumHash, myProTxHash);
|
||||
} else {
|
||||
auto cindexes = CLLMQUtils::CalcDeterministicWatchConnections(llmqType, quorum->quorumHash, quorum->members.size(), 1);
|
||||
auto cindexes = CLLMQUtils::CalcDeterministicWatchConnections(llmqType, quorum->qc.quorumHash, quorum->members.size(), 1);
|
||||
for (auto idx : cindexes) {
|
||||
connections.emplace(quorum->members[idx]->pdmnState->addr, quorum->members[idx]->proTxHash);
|
||||
}
|
||||
}
|
||||
if (!connections.empty()) {
|
||||
std::string debugMsg = strprintf("CQuorumManager::%s -- adding masternodes quorum connections for quorum %s:\n", __func__, quorum->quorumHash.ToString());
|
||||
std::string debugMsg = strprintf("CQuorumManager::%s -- adding masternodes quorum connections for quorum %s:\n", __func__, quorum->qc.quorumHash.ToString());
|
||||
for (auto& c : connections) {
|
||||
debugMsg += strprintf(" %s\n", c.first.ToString(false));
|
||||
}
|
||||
LogPrint("llmq", debugMsg);
|
||||
g_connman->AddMasternodeQuorumNodes(llmqType, quorum->quorumHash, connections);
|
||||
g_connman->AddMasternodeQuorumNodes(llmqType, quorum->qc.quorumHash, connections);
|
||||
}
|
||||
}
|
||||
connmanQuorumsToDelete.erase(quorum->quorumHash);
|
||||
connmanQuorumsToDelete.erase(quorum->qc.quorumHash);
|
||||
}
|
||||
|
||||
for (auto& qh : connmanQuorumsToDelete) {
|
||||
@ -218,14 +217,14 @@ void CQuorumManager::EnsureQuorumConnections(Consensus::LLMQType llmqType, const
|
||||
}
|
||||
}
|
||||
|
||||
bool CQuorumManager::BuildQuorumFromCommitment(const CFinalCommitment& qc, const CBlockIndex* pindexQuorum, std::shared_ptr<CQuorum>& quorum) const
|
||||
bool CQuorumManager::BuildQuorumFromCommitment(const CFinalCommitment& qc, const CBlockIndex* pindexQuorum, const uint256& minedBlockHash, std::shared_ptr<CQuorum>& quorum) const
|
||||
{
|
||||
assert(pindexQuorum);
|
||||
assert(qc.quorumHash == pindexQuorum->GetBlockHash());
|
||||
|
||||
auto members = CLLMQUtils::GetAllQuorumMembers((Consensus::LLMQType)qc.llmqType, qc.quorumHash);
|
||||
|
||||
quorum->Init(qc.quorumHash, pindexQuorum->nHeight, members, qc.validMembers, qc.quorumPublicKey);
|
||||
quorum->Init(qc, pindexQuorum->nHeight, minedBlockHash, members);
|
||||
|
||||
bool hasValidVvec = false;
|
||||
if (quorum->ReadContributions(evoDb)) {
|
||||
@ -302,34 +301,18 @@ std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqTyp
|
||||
|
||||
std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t maxCount)
|
||||
{
|
||||
std::vector<CQuorumCPtr> result;
|
||||
result.reserve(maxCount);
|
||||
|
||||
auto& params = Params().GetConsensus().llmqs.at(llmqType);
|
||||
|
||||
auto firstQuorumHash = quorumBlockProcessor->GetFirstMinedQuorumHash(llmqType);
|
||||
if (firstQuorumHash.IsNull()) {
|
||||
// no quorum mined yet, avoid scanning the whole chain down to genesis
|
||||
return result;
|
||||
}
|
||||
auto quorumIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(params.type, pindexStart, maxCount);
|
||||
|
||||
auto pindex = pindexStart->GetAncestor(pindexStart->nHeight - (pindexStart->nHeight % params.dkgInterval));
|
||||
std::vector<CQuorumCPtr> result;
|
||||
result.reserve(quorumIndexes.size());
|
||||
|
||||
while (pindex != nullptr
|
||||
&& pindex->nHeight >= params.dkgInterval
|
||||
&& result.size() < maxCount
|
||||
&& deterministicMNManager->IsDIP3Enforced(pindex->nHeight)) {
|
||||
auto quorum = GetQuorum(llmqType, pindex);
|
||||
if (quorum) {
|
||||
result.emplace_back(quorum);
|
||||
}
|
||||
|
||||
if (pindex->GetBlockHash() == firstQuorumHash) {
|
||||
// no need to scan further if we know that there are no quorums below this block
|
||||
break;
|
||||
}
|
||||
|
||||
pindex = pindex->GetAncestor(pindex->nHeight - params.dkgInterval);
|
||||
for (auto& quorumIndex : quorumIndexes) {
|
||||
assert(quorumIndex);
|
||||
auto quorum = GetQuorum(params.type, quorumIndex);
|
||||
assert(quorum != nullptr);
|
||||
result.emplace_back(quorum);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -371,7 +354,8 @@ CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const CBlock
|
||||
}
|
||||
|
||||
CFinalCommitment qc;
|
||||
if (!quorumBlockProcessor->GetMinedCommitment(llmqType, quorumHash, qc)) {
|
||||
uint256 minedBlockHash;
|
||||
if (!quorumBlockProcessor->GetMinedCommitment(llmqType, quorumHash, qc, minedBlockHash)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -379,7 +363,7 @@ CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const CBlock
|
||||
|
||||
auto quorum = std::make_shared<CQuorum>(params, blsWorker);
|
||||
|
||||
if (!BuildQuorumFromCommitment(qc, pindexQuorum, quorum)) {
|
||||
if (!BuildQuorumFromCommitment(qc, pindexQuorum, minedBlockHash, quorum)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "evo/evodb.h"
|
||||
#include "evo/deterministicmns.h"
|
||||
#include "llmq/quorums_commitment.h"
|
||||
|
||||
#include "validationinterface.h"
|
||||
#include "consensus/params.h"
|
||||
@ -33,11 +34,10 @@ class CQuorum
|
||||
friend class CQuorumManager;
|
||||
public:
|
||||
const Consensus::LLMQParams& params;
|
||||
uint256 quorumHash;
|
||||
CFinalCommitment qc;
|
||||
int height;
|
||||
uint256 minedBlockHash;
|
||||
std::vector<CDeterministicMNCPtr> members;
|
||||
std::vector<bool> validMembers;
|
||||
CBLSPublicKey quorumPublicKey;
|
||||
|
||||
// These are only valid when we either participated in the DKG or fully watched it
|
||||
BLSVerificationVectorPtr quorumVvec;
|
||||
@ -53,7 +53,7 @@ private:
|
||||
public:
|
||||
CQuorum(const Consensus::LLMQParams& _params, CBLSWorker& _blsWorker) : params(_params), blsCache(_blsWorker), stopCachePopulatorThread(false) {}
|
||||
~CQuorum();
|
||||
void Init(const uint256& quorumHash, int height, const std::vector<CDeterministicMNCPtr>& members, const std::vector<bool>& validMembers, const CBLSPublicKey& quorumPublicKey);
|
||||
void Init(const CFinalCommitment& _qc, int _height, const uint256& _minedBlockHash, const std::vector<CDeterministicMNCPtr>& _members);
|
||||
|
||||
bool IsMember(const uint256& proTxHash) const;
|
||||
bool IsValidMember(const uint256& proTxHash) const;
|
||||
@ -105,7 +105,7 @@ private:
|
||||
// all private methods here are cs_main-free
|
||||
void EnsureQuorumConnections(Consensus::LLMQType llmqType, const CBlockIndex *pindexNew);
|
||||
|
||||
bool BuildQuorumFromCommitment(const CFinalCommitment& qc, const CBlockIndex* pindexQuorum, std::shared_ptr<CQuorum>& quorum) const;
|
||||
bool BuildQuorumFromCommitment(const CFinalCommitment& qc, const CBlockIndex* pindexQuorum, const uint256& minedBlockHash, std::shared_ptr<CQuorum>& quorum) const;
|
||||
bool BuildQuorumContributions(const CFinalCommitment& fqc, std::shared_ptr<CQuorum>& quorum) const;
|
||||
|
||||
CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, const CBlockIndex* pindex);
|
||||
|
@ -23,7 +23,9 @@ namespace llmq
|
||||
CQuorumBlockProcessor* quorumBlockProcessor;
|
||||
|
||||
static const std::string DB_MINED_COMMITMENT = "q_mc";
|
||||
static const std::string DB_FIRST_MINED_COMMITMENT = "q_fmc";
|
||||
static const std::string DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT = "q_mcih";
|
||||
|
||||
static const std::string DB_BEST_BLOCK_UPGRADE = "q_bbu";
|
||||
|
||||
void CQuorumBlockProcessor::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
|
||||
{
|
||||
@ -119,6 +121,7 @@ bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, const CBlockIndex*
|
||||
|
||||
bool fDIP0003Active = VersionBitsState(pindex->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003, versionbitscache) == THRESHOLD_ACTIVE;
|
||||
if (!fDIP0003Active) {
|
||||
evoDb.Write(DB_BEST_BLOCK_UPGRADE, block.GetHash());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -149,20 +152,32 @@ bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, const CBlockIndex*
|
||||
}
|
||||
}
|
||||
|
||||
auto blockHash = block.GetHash();
|
||||
|
||||
for (auto& p : qcs) {
|
||||
auto& qc = p.second;
|
||||
if (!ProcessCommitment(pindex, qc, state)) {
|
||||
if (!ProcessCommitment(pindex->nHeight, blockHash, qc, state)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
evoDb.Write(DB_BEST_BLOCK_UPGRADE, blockHash);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CQuorumBlockProcessor::ProcessCommitment(const CBlockIndex* pindex, const CFinalCommitment& qc, CValidationState& state)
|
||||
// We store a mapping from minedHeight->quorumHeight in the DB
|
||||
// minedHeight is inversed so that entries are traversable in reversed order
|
||||
static std::tuple<std::string, uint8_t, int> BuildInversedHeightKey(Consensus::LLMQType llmqType, int nMinedHeight)
|
||||
{
|
||||
return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT, (uint8_t)llmqType, std::numeric_limits<int>::max() - nMinedHeight);
|
||||
}
|
||||
|
||||
bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc, CValidationState& state)
|
||||
{
|
||||
auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)qc.llmqType);
|
||||
|
||||
uint256 quorumHash = GetQuorumBlockHash((Consensus::LLMQType)qc.llmqType, pindex->nHeight);
|
||||
uint256 quorumHash = GetQuorumBlockHash((Consensus::LLMQType)qc.llmqType, nHeight);
|
||||
if (quorumHash.IsNull()) {
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-qc-block");
|
||||
}
|
||||
@ -182,7 +197,7 @@ bool CQuorumBlockProcessor::ProcessCommitment(const CBlockIndex* pindex, const C
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-qc-dup");
|
||||
}
|
||||
|
||||
if (!IsMiningPhase(params.type, pindex->nHeight)) {
|
||||
if (!IsMiningPhase(params.type, nHeight)) {
|
||||
// should not happen as it's already handled in ProcessBlock
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-qc-height");
|
||||
}
|
||||
@ -194,10 +209,9 @@ bool CQuorumBlockProcessor::ProcessCommitment(const CBlockIndex* pindex, const C
|
||||
}
|
||||
|
||||
// Store commitment in DB
|
||||
evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)params.type, quorumHash)), qc);
|
||||
if (!evoDb.Exists(std::make_pair(DB_FIRST_MINED_COMMITMENT, (uint8_t)params.type))) {
|
||||
evoDb.Write(std::make_pair(DB_FIRST_MINED_COMMITMENT, (uint8_t)params.type), quorumHash);
|
||||
}
|
||||
auto quorumIndex = mapBlockIndex.at(qc.quorumHash);
|
||||
evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)params.type, quorumHash)), std::make_pair(qc, blockHash));
|
||||
evoDb.Write(BuildInversedHeightKey(params.type, nHeight), quorumIndex->nHeight);
|
||||
|
||||
{
|
||||
LOCK(minableCommitmentsCs);
|
||||
@ -227,6 +241,7 @@ bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, const CBlockIndex* pi
|
||||
}
|
||||
|
||||
evoDb.Erase(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(qc.llmqType, qc.quorumHash)));
|
||||
evoDb.Erase(BuildInversedHeightKey((Consensus::LLMQType)qc.llmqType, pindex->nHeight));
|
||||
{
|
||||
LOCK(minableCommitmentsCs);
|
||||
hasMinedCommitmentCache.erase(std::make_pair((Consensus::LLMQType)qc.llmqType, qc.quorumHash));
|
||||
@ -234,14 +249,52 @@ bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, const CBlockIndex* pi
|
||||
|
||||
// if a reorg happened, we should allow to mine this commitment later
|
||||
AddMinableCommitment(qc);
|
||||
}
|
||||
|
||||
uint256 fmq = GetFirstMinedQuorumHash(p.first);
|
||||
if (fmq == qc.quorumHash) {
|
||||
evoDb.Erase(std::make_pair(DB_FIRST_MINED_COMMITMENT, (uint8_t)p.first));
|
||||
evoDb.Write(DB_BEST_BLOCK_UPGRADE, pindex->pprev->GetBlockHash());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO remove this with 0.15.0
|
||||
void CQuorumBlockProcessor::UpgradeDB()
|
||||
{
|
||||
LOCK(cs_main);
|
||||
uint256 bestBlock;
|
||||
if (evoDb.GetRawDB().Read(DB_BEST_BLOCK_UPGRADE, bestBlock) && bestBlock == chainActive.Tip()->GetBlockHash()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogPrintf("CQuorumBlockProcessor::%s -- Upgrading DB...\n", __func__);
|
||||
|
||||
if (chainActive.Height() >= Params().GetConsensus().DIP0003EnforcementHeight) {
|
||||
auto pindex = chainActive[Params().GetConsensus().DIP0003EnforcementHeight];
|
||||
while (pindex) {
|
||||
CBlock block;
|
||||
bool r = ReadBlockFromDisk(block, pindex, Params().GetConsensus());
|
||||
assert(r);
|
||||
|
||||
std::map<Consensus::LLMQType, CFinalCommitment> qcs;
|
||||
CValidationState dummyState;
|
||||
GetCommitmentsFromBlock(block, pindex->pprev, qcs, dummyState);
|
||||
|
||||
for (const auto& p : qcs) {
|
||||
const auto& qc = p.second;
|
||||
if (qc.IsNull()) {
|
||||
continue;
|
||||
}
|
||||
auto quorumIndex = mapBlockIndex.at(qc.quorumHash);
|
||||
evoDb.GetRawDB().Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)qc.llmqType, qc.quorumHash)), std::make_pair(qc, pindex->GetBlockHash()));
|
||||
evoDb.GetRawDB().Write(BuildInversedHeightKey((Consensus::LLMQType)qc.llmqType, pindex->nHeight), quorumIndex->nHeight);
|
||||
}
|
||||
|
||||
evoDb.GetRawDB().Write(DB_BEST_BLOCK_UPGRADE, pindex->GetBlockHash());
|
||||
|
||||
pindex = chainActive.Next(pindex);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
LogPrintf("CQuorumBlockProcessor::%s -- Upgrade done...\n", __func__);
|
||||
}
|
||||
|
||||
bool CQuorumBlockProcessor::GetCommitmentsFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, std::map<Consensus::LLMQType, CFinalCommitment>& ret, CValidationState& state)
|
||||
@ -335,20 +388,73 @@ bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, con
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& ret)
|
||||
bool CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& retQc, uint256& retMinedBlockHash)
|
||||
{
|
||||
auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)llmqType, quorumHash));
|
||||
return evoDb.Read(key, ret);
|
||||
std::pair<CFinalCommitment, uint256> p;
|
||||
if (!evoDb.Read(key, p)) {
|
||||
return false;
|
||||
}
|
||||
retQc = std::move(p.first);
|
||||
retMinedBlockHash = p.second;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint256 CQuorumBlockProcessor::GetFirstMinedQuorumHash(Consensus::LLMQType llmqType)
|
||||
std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t maxCount)
|
||||
{
|
||||
auto key = std::make_pair(DB_FIRST_MINED_COMMITMENT, (uint8_t)llmqType);
|
||||
uint256 quorumHash;
|
||||
if (!evoDb.Read(key, quorumHash)) {
|
||||
return uint256();
|
||||
auto dbIt = evoDb.GetCurTransaction().NewIteratorUniquePtr();
|
||||
|
||||
auto firstKey = BuildInversedHeightKey(llmqType, pindex->nHeight);
|
||||
auto lastKey = BuildInversedHeightKey(llmqType, 0);
|
||||
|
||||
dbIt->Seek(firstKey);
|
||||
|
||||
std::vector<const CBlockIndex*> ret;
|
||||
ret.reserve(maxCount);
|
||||
|
||||
while (dbIt->Valid() && ret.size() < maxCount) {
|
||||
decltype(firstKey) curKey;
|
||||
int quorumHeight;
|
||||
if (!dbIt->GetKey(curKey) || curKey >= lastKey) {
|
||||
break;
|
||||
}
|
||||
if (std::get<0>(curKey) != DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT || std::get<1>(curKey) != (uint8_t)llmqType) {
|
||||
break;
|
||||
}
|
||||
|
||||
int nMinedHeight = std::numeric_limits<int>::max() - std::get<2>(curKey);
|
||||
if (nMinedHeight > pindex->nHeight) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!dbIt->GetValue(quorumHeight)) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto quorumIndex = pindex->GetAncestor(quorumHeight);
|
||||
assert(quorumIndex);
|
||||
ret.emplace_back(pindex->GetAncestor(quorumHeight));
|
||||
|
||||
dbIt->Next();
|
||||
}
|
||||
return quorumHash;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> CQuorumBlockProcessor::GetMinedAndActiveCommitmentsUntilBlock(const CBlockIndex* pindex)
|
||||
{
|
||||
std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> ret;
|
||||
|
||||
for (const auto& p : Params().GetConsensus().llmqs) {
|
||||
auto& v = ret[p.second.type];
|
||||
v.reserve(p.second.signingActiveQuorumCount);
|
||||
auto commitments = GetMinedCommitmentsUntilBlock(p.second.type, pindex, p.second.signingActiveQuorumCount);
|
||||
for (auto& c : commitments) {
|
||||
v.emplace_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CQuorumBlockProcessor::HasMinableCommitment(const uint256& hash)
|
||||
|
@ -37,6 +37,8 @@ private:
|
||||
public:
|
||||
CQuorumBlockProcessor(CEvoDB& _evoDb) : evoDb(_evoDb) {}
|
||||
|
||||
void UpgradeDB();
|
||||
|
||||
void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman);
|
||||
|
||||
bool ProcessBlock(const CBlock& block, const CBlockIndex* pindex, CValidationState& state);
|
||||
@ -49,13 +51,14 @@ public:
|
||||
bool GetMinableCommitmentTx(Consensus::LLMQType llmqType, int nHeight, CTransactionRef& ret);
|
||||
|
||||
bool HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash);
|
||||
bool GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& ret);
|
||||
bool GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& ret, uint256& retMinedBlockHash);
|
||||
|
||||
uint256 GetFirstMinedQuorumHash(Consensus::LLMQType llmqType);
|
||||
std::vector<const CBlockIndex*> GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t maxCount);
|
||||
std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> GetMinedAndActiveCommitmentsUntilBlock(const CBlockIndex* pindex);
|
||||
|
||||
private:
|
||||
bool GetCommitmentsFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, std::map<Consensus::LLMQType, CFinalCommitment>& ret, CValidationState& state);
|
||||
bool ProcessCommitment(const CBlockIndex* pindex, const CFinalCommitment& qc, CValidationState& state);
|
||||
bool ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc, CValidationState& state);
|
||||
bool IsMiningPhase(Consensus::LLMQType llmqType, int nHeight);
|
||||
bool IsCommitmentRequired(Consensus::LLMQType llmqType, int nHeight);
|
||||
uint256 GetQuorumBlockHash(Consensus::LLMQType llmqType, int nHeight);
|
||||
|
@ -65,6 +65,8 @@ void DestroyLLMQSystem()
|
||||
|
||||
void StartLLMQSystem()
|
||||
{
|
||||
quorumBlockProcessor->UpgradeDB();
|
||||
|
||||
if (blsWorker) {
|
||||
blsWorker->Start();
|
||||
}
|
||||
|
@ -578,8 +578,8 @@ void CInstantSendManager::ProcessPendingInstantSendLocks()
|
||||
// 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);
|
||||
uint256 signHash = CLLMQUtils::BuildSignHash(llmqType, quorum->qc.quorumHash, id, islock.txid);
|
||||
batchVerifier.PushMessage(nodeId, hash, signHash, islock.sig, quorum->qc.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
|
||||
@ -587,7 +587,7 @@ void CInstantSendManager::ProcessPendingInstantSendLocks()
|
||||
if (!quorumSigningManager->HasRecoveredSigForId(llmqType, id)) {
|
||||
CRecoveredSig recSig;
|
||||
recSig.llmqType = llmqType;
|
||||
recSig.quorumHash = quorum->quorumHash;
|
||||
recSig.quorumHash = quorum->qc.quorumHash;
|
||||
recSig.id = id;
|
||||
recSig.msgHash = islock.txid;
|
||||
recSig.sig = islock.sig;
|
||||
|
@ -324,7 +324,7 @@ bool CSigningManager::PreVerifyRecoveredSig(NodeId nodeId, const CRecoveredSig&
|
||||
recoveredSig.quorumHash.ToString(), nodeId);
|
||||
return false;
|
||||
}
|
||||
if (!CLLMQUtils::IsQuorumActive(llmqType, quorum->quorumHash)) {
|
||||
if (!CLLMQUtils::IsQuorumActive(llmqType, quorum->qc.quorumHash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -387,7 +387,7 @@ void CSigningManager::CollectPendingRecoveredSigsToVerify(
|
||||
it = v.erase(it);
|
||||
continue;
|
||||
}
|
||||
if (!CLLMQUtils::IsQuorumActive(llmqType, quorum->quorumHash)) {
|
||||
if (!CLLMQUtils::IsQuorumActive(llmqType, quorum->qc.quorumHash)) {
|
||||
LogPrint("llmq", "CSigningManager::%s -- quorum %s not active anymore, node=%d\n", __func__,
|
||||
recSig.quorumHash.ToString(), nodeId);
|
||||
it = v.erase(it);
|
||||
@ -437,7 +437,7 @@ bool CSigningManager::ProcessPendingRecoveredSigs(CConnman& connman)
|
||||
|
||||
for (auto& recSig : v) {
|
||||
const auto& quorum = quorums.at(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.quorumHash));
|
||||
batchVerifier.PushMessage(nodeId, recSig.GetHash(), CLLMQUtils::BuildSignHash(recSig), recSig.sig, quorum->quorumPublicKey);
|
||||
batchVerifier.PushMessage(nodeId, recSig.GetHash(), CLLMQUtils::BuildSignHash(recSig), recSig.sig, quorum->qc.quorumPublicKey);
|
||||
verifyCount++;
|
||||
}
|
||||
}
|
||||
@ -687,7 +687,7 @@ CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType
|
||||
for (size_t i = 0; i < quorums.size(); i++) {
|
||||
CHashWriter h(SER_NETWORK, 0);
|
||||
h << (uint8_t)llmqType;
|
||||
h << quorums[i]->quorumHash;
|
||||
h << quorums[i]->qc.quorumHash;
|
||||
h << selectionHash;
|
||||
scores.emplace_back(h.GetHash(), i);
|
||||
}
|
||||
@ -704,8 +704,8 @@ bool CSigningManager::VerifyRecoveredSig(Consensus::LLMQType llmqType, int signe
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 signHash = CLLMQUtils::BuildSignHash(llmqParams.type, quorum->quorumHash, id, msgHash);
|
||||
return sig.VerifyInsecure(quorum->quorumPublicKey, signHash);
|
||||
uint256 signHash = CLLMQUtils::BuildSignHash(llmqParams.type, quorum->qc.quorumHash, id, msgHash);
|
||||
return sig.VerifyInsecure(quorum->qc.quorumPublicKey, signHash);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -463,7 +463,7 @@ bool CSigSharesManager::PreVerifyBatchedSigShares(NodeId nodeId, const CSigShare
|
||||
{
|
||||
retBan = false;
|
||||
|
||||
if (!CLLMQUtils::IsQuorumActive(session.llmqType, session.quorum->quorumHash)) {
|
||||
if (!CLLMQUtils::IsQuorumActive(session.llmqType, session.quorum->qc.quorumHash)) {
|
||||
// quorum is too old
|
||||
return false;
|
||||
}
|
||||
@ -492,7 +492,7 @@ bool CSigSharesManager::PreVerifyBatchedSigShares(NodeId nodeId, const CSigShare
|
||||
retBan = true;
|
||||
return false;
|
||||
}
|
||||
if (!session.quorum->validMembers[quorumMember]) {
|
||||
if (!session.quorum->qc.validMembers[quorumMember]) {
|
||||
LogPrintf("CSigSharesManager::%s -- quorumMember not valid\n", __func__);
|
||||
retBan = true;
|
||||
return false;
|
||||
@ -725,7 +725,7 @@ void CSigSharesManager::TryRecoverSig(const CQuorumCPtr& quorum, const uint256&
|
||||
|
||||
auto k = std::make_pair(quorum->params.type, id);
|
||||
|
||||
auto signHash = CLLMQUtils::BuildSignHash(quorum->params.type, quorum->quorumHash, id, msgHash);
|
||||
auto signHash = CLLMQUtils::BuildSignHash(quorum->params.type, quorum->qc.quorumHash, id, msgHash);
|
||||
auto sigShares = this->sigShares.GetAllForSignHash(signHash);
|
||||
if (!sigShares) {
|
||||
return;
|
||||
@ -759,14 +759,14 @@ void CSigSharesManager::TryRecoverSig(const CQuorumCPtr& quorum, const uint256&
|
||||
|
||||
CRecoveredSig rs;
|
||||
rs.llmqType = quorum->params.type;
|
||||
rs.quorumHash = quorum->quorumHash;
|
||||
rs.quorumHash = quorum->qc.quorumHash;
|
||||
rs.id = id;
|
||||
rs.msgHash = msgHash;
|
||||
rs.sig = recoveredSig;
|
||||
rs.UpdateHash();
|
||||
|
||||
auto signHash = CLLMQUtils::BuildSignHash(rs);
|
||||
bool valid = rs.sig.VerifyInsecure(quorum->quorumPublicKey, signHash);
|
||||
bool valid = rs.sig.VerifyInsecure(quorum->qc.quorumPublicKey, signHash);
|
||||
if (!valid) {
|
||||
// this should really not happen as we have verified all signature shares before
|
||||
LogPrintf("CSigSharesManager::%s -- own recovered signature is invalid. id=%s, msgHash=%s\n", __func__,
|
||||
@ -1408,7 +1408,7 @@ void CSigSharesManager::Sign(const CQuorumCPtr& quorum, const uint256& id, const
|
||||
|
||||
CBLSSecretKey skShare = quorum->GetSkShare();
|
||||
if (!skShare.IsValid()) {
|
||||
LogPrint("llmq-sigs", "CSigSharesManager::%s -- we don't have our skShare for quorum %s\n", __func__, quorum->quorumHash.ToString());
|
||||
LogPrint("llmq-sigs", "CSigSharesManager::%s -- we don't have our skShare for quorum %s\n", __func__, quorum->qc.quorumHash.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1420,7 +1420,7 @@ void CSigSharesManager::Sign(const CQuorumCPtr& quorum, const uint256& id, const
|
||||
|
||||
CSigShare sigShare;
|
||||
sigShare.llmqType = quorum->params.type;
|
||||
sigShare.quorumHash = quorum->quorumHash;
|
||||
sigShare.quorumHash = quorum->qc.quorumHash;
|
||||
sigShare.id = id;
|
||||
sigShare.msgHash = msgHash;
|
||||
sigShare.quorumMember = (uint16_t)memberIdx;
|
||||
|
@ -104,7 +104,7 @@ bool CLLMQUtils::IsQuorumActive(Consensus::LLMQType llmqType, const uint256& quo
|
||||
// fail while we are on the brink of a new quorum
|
||||
auto quorums = quorumManager->ScanQuorums(llmqType, (int)params.signingActiveQuorumCount + 1);
|
||||
for (auto& q : quorums) {
|
||||
if (q->quorumHash == quorumHash) {
|
||||
if (q->qc.quorumHash == quorumHash) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -147,6 +147,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
|
||||
LOCK2(cs_main, mempool.cs);
|
||||
|
||||
bool fDIP0003Active_context = VersionBitsState(chainActive.Tip(), chainparams.GetConsensus(), Consensus::DEPLOYMENT_DIP0003, versionbitscache) == THRESHOLD_ACTIVE;
|
||||
bool fDIP0008Active_context = VersionBitsState(chainActive.Tip(), chainparams.GetConsensus(), Consensus::DEPLOYMENT_DIP0008, versionbitscache) == THRESHOLD_ACTIVE;
|
||||
|
||||
CBlockIndex* pindexPrev = chainActive.Tip();
|
||||
nHeight = pindexPrev->nHeight + 1;
|
||||
@ -209,11 +210,23 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
|
||||
coinbaseTx.nType = TRANSACTION_COINBASE;
|
||||
|
||||
CCbTx cbTx;
|
||||
|
||||
if (fDIP0008Active_context) {
|
||||
cbTx.nVersion = 2;
|
||||
} else {
|
||||
cbTx.nVersion = 1;
|
||||
}
|
||||
|
||||
cbTx.nHeight = nHeight;
|
||||
|
||||
CValidationState state;
|
||||
if (!CalcCbTxMerkleRootMNList(*pblock, pindexPrev, cbTx.merkleRootMNList, state)) {
|
||||
throw std::runtime_error(strprintf("%s: CalcSMLMerkleRootForNewBlock failed: %s", __func__, FormatStateMessage(state)));
|
||||
throw std::runtime_error(strprintf("%s: CalcCbTxMerkleRootMNList failed: %s", __func__, FormatStateMessage(state)));
|
||||
}
|
||||
if (fDIP0008Active_context) {
|
||||
if (!CalcCbTxMerkleRootQuorums(*pblock, pindexPrev, cbTx.merkleRootQuorums, state)) {
|
||||
throw std::runtime_error(strprintf("%s: CalcCbTxMerkleRootQuorums failed: %s", __func__, FormatStateMessage(state)));
|
||||
}
|
||||
}
|
||||
|
||||
SetTxPayload(coinbaseTx, cbTx);
|
||||
|
@ -40,7 +40,7 @@ UniValue quorum_list(const JSONRPCRequest& request)
|
||||
|
||||
auto quorums = llmq::quorumManager->ScanQuorums(p.first, chainActive.Tip(), count);
|
||||
for (auto& q : quorums) {
|
||||
v.push_back(q->quorumHash.ToString());
|
||||
v.push_back(q->qc.quorumHash.ToString());
|
||||
}
|
||||
|
||||
ret.push_back(Pair(p.second.name, v));
|
||||
@ -73,13 +73,15 @@ UniValue quorum_info(const JSONRPCRequest& request)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
|
||||
}
|
||||
|
||||
uint256 blockHash = ParseHashV(request.params[2], "quorumHash");
|
||||
const auto& llmqParams = Params().GetConsensus().llmqs.at(llmqType);
|
||||
|
||||
uint256 quorumHash = ParseHashV(request.params[2], "quorumHash");
|
||||
bool includeSkShare = false;
|
||||
if (request.params.size() > 3) {
|
||||
includeSkShare = ParseBoolV(request.params[3], "includeSkShare");
|
||||
}
|
||||
|
||||
auto quorum = llmq::quorumManager->GetQuorum(llmqType, blockHash);
|
||||
auto quorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash);
|
||||
if (!quorum) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
|
||||
}
|
||||
@ -87,15 +89,16 @@ UniValue quorum_info(const JSONRPCRequest& request)
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
|
||||
ret.push_back(Pair("height", quorum->height));
|
||||
ret.push_back(Pair("quorumHash", quorum->quorumHash.ToString()));
|
||||
ret.push_back(Pair("quorumHash", quorum->qc.quorumHash.ToString()));
|
||||
ret.push_back(Pair("minedBlock", quorum->minedBlockHash.ToString()));
|
||||
|
||||
UniValue membersArr(UniValue::VARR);
|
||||
for (size_t i = 0; i < quorum->members.size(); i++) {
|
||||
auto& dmn = quorum->members[i];
|
||||
UniValue mo(UniValue::VOBJ);
|
||||
mo.push_back(Pair("proTxHash", dmn->proTxHash.ToString()));
|
||||
mo.push_back(Pair("valid", quorum->validMembers[i]));
|
||||
if (quorum->validMembers[i]) {
|
||||
mo.push_back(Pair("valid", quorum->qc.validMembers[i]));
|
||||
if (quorum->qc.validMembers[i]) {
|
||||
CBLSPublicKey pubKey = quorum->GetPubKeyShare(i);
|
||||
if (pubKey.IsValid()) {
|
||||
mo.push_back(Pair("pubKeyShare", pubKey.ToString()));
|
||||
@ -105,7 +108,7 @@ UniValue quorum_info(const JSONRPCRequest& request)
|
||||
}
|
||||
|
||||
ret.push_back(Pair("members", membersArr));
|
||||
ret.push_back(Pair("quorumPublicKey", quorum->quorumPublicKey.ToString()));
|
||||
ret.push_back(Pair("quorumPublicKey", quorum->qc.quorumPublicKey.ToString()));
|
||||
CBLSSecretKey skShare = quorum->GetSkShare();
|
||||
if (includeSkShare && skShare.IsValid()) {
|
||||
ret.push_back(Pair("secretKeyShare", skShare.ToString()));
|
||||
|
@ -177,6 +177,9 @@ CBlock TestChainSetup::CreateBlock(const std::vector<CMutableTransaction>& txns,
|
||||
if (!CalcCbTxMerkleRootMNList(block, chainActive.Tip(), cbTx.merkleRootMNList, state)) {
|
||||
BOOST_ASSERT(false);
|
||||
}
|
||||
if (!CalcCbTxMerkleRootQuorums(block, chainActive.Tip(), cbTx.merkleRootQuorums, state)) {
|
||||
BOOST_ASSERT(false);
|
||||
}
|
||||
CMutableTransaction tmpTx = *block.vtx[0];
|
||||
SetTxPayload(tmpTx, cbTx);
|
||||
block.vtx[0] = MakeTransactionRef(tmpTx);
|
||||
|
Loading…
Reference in New Issue
Block a user