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:
Alexander Block 2019-04-05 05:58:05 +02:00 committed by GitHub
commit 20ec1de4c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 883 additions and 233 deletions

View File

@ -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())

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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>

View File

@ -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()));
}
}

View File

@ -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

View File

@ -35,6 +35,11 @@ public:
return t;
}
CurTransaction& GetCurTransaction()
{
return curDBTransaction;
}
template <typename K, typename V>
bool Read(const K& key, V& value)
{

View File

@ -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())) {

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -65,6 +65,8 @@ void DestroyLLMQSystem()
void StartLLMQSystem()
{
quorumBlockProcessor->UpgradeDB();
if (blsWorker) {
blsWorker->Start();
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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()));

View File

@ -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);