Skip to content

Commit

Permalink
Llmq improvements (#978)
Browse files Browse the repository at this point in the history
* Removed znode-payments

* Znodesync-interface cleaned

* darksend has been dealt with

* Removed RPC broadcast

* Znode GUI removed

* Removed znodeman

* Removed znode-sync

* Removed activeznode

* Removed ping and broadcast

* Removed znode

* Removed from GUI and net exchange

* Removed -znodeprivkey

* Removed znode-payments

* Znodesync-interface cleaned

* darksend has been dealt with

* Removed RPC broadcast

* Znode GUI removed

* Removed znodeman

* Removed znode-sync

* Removed activeznode

* Removed ping and broadcast

* Removed znode

* Removed from GUI and net exchange

* Removed -znodeprivkey

* Tests are adjusted

* Removed sporks

* Cleanup in comments, other minor clean-ups

* Stopped using *.dat cache files

* Minor improvements for LLMQs

* Minor improvements for LLMQs

* All LLMQ tests are done

* Llmqs for chainlocks were enabled

* Chainlocks start working

* Wrapping up with chainlocks

* Removed znode-payments

* Znodesync-interface cleaned

* darksend has been dealt with

* Removed znodeman

* Removed znode

* Cleanup in comments, other minor clean-ups

* Minor improvements for LLMQs

* All LLMQ tests are done

* Llmqs for chainlocks were enabled

* Chainlocks start working

* Wrapping up with chainlocks

* Tests are fixed

* Removed the original test

* Evospork controls chainlocks

* More tests for CL

* Blockchain params change for testnet

* Formatting fix

* Minor improvments

* Dealing with the code review comments

* Version update

* HF date is set

* Minor cleanup

* Dealing with code review

Co-authored-by: Andrey <[email protected]>
Co-authored-by: Peter Shugalev <[email protected]>
  • Loading branch information
3 people authored Jan 21, 2021
1 parent e743e9c commit 5525561
Show file tree
Hide file tree
Showing 31 changed files with 1,096 additions and 91 deletions.
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
AC_PREREQ([2.60])
define(_CLIENT_VERSION_MAJOR, 0)
define(_CLIENT_VERSION_MINOR, 14)
define(_CLIENT_VERSION_REVISION, 3)
define(_CLIENT_VERSION_REVISION, 4)
define(_CLIENT_VERSION_BUILD, 0)
define(_CLIENT_VERSION_IS_RELEASE, true)
define(_COPYRIGHT_YEAR, 2021)
Expand Down
9 changes: 8 additions & 1 deletion qa/pull-tester/rpc-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,14 @@
'sigma_zapwalletmints_unconf_trans.py',

# Evo Znodes
'dip3-deterministicmns.py'
'dip3-deterministicmns.py',
'llmq-signing.py',
'llmq-dkgerrors.py',
'llmq-simplepose.py',
'llmq-chainlocks.py',
'llmq-cl-evospork.py',
# 'llmq-is-cl-conflicts.py',
# 'llmq-is-retroactive.py'

# Unstable tests
#, 'dip4-coinbasemerkleroots.py'
Expand Down
132 changes: 132 additions & 0 deletions qa/rpc-tests/llmq-chainlocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2018 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

from test_framework.mininode import *
from test_framework.test_framework import EvoZnodeTestFramework
from test_framework.util import *
from time import *

'''
llmq-chainlocks.py
Checks LLMQs based ChainLocks
'''

class LLMQChainLocksTest(EvoZnodeTestFramework):
def __init__(self):
super().__init__(6, 5, extra_args=[['-debug=chainlocks']] * 6)

def run_test(self):

for i in range(4):
self.mine_quorum()

# mine single block, wait for chainlock
self.nodes[0].generate(1)

self.wait_for_chainlock_tip_all_nodes()

# mine many blocks, wait for chainlock
self.nodes[0].generate(20)
self.wait_for_chainlock_tip_all_nodes()

# assert that all blocks up until the tip are chainlocked
for h in range(1, self.nodes[0].getblockcount()):
block = self.nodes[0].getblock(self.nodes[0].getblockhash(h))
assert(block['chainlock'])

# Isolate node, mine on another, and reconnect
isolate_node(self.nodes[0])
node0_tip = self.nodes[0].getbestblockhash()
self.nodes[1].generate(5)
self.wait_for_chainlock_tip(self.nodes[1])
assert(self.nodes[0].getbestblockhash() == node0_tip)
reconnect_isolated_node(self.nodes[0], 1)
self.nodes[1].generate(1)
self.wait_for_chainlock(self.nodes[0], self.nodes[1].getbestblockhash())

# Isolate node, mine on both parts of the network, and reconnect
isolate_node(self.nodes[0])
self.nodes[0].generate(5)
self.nodes[1].generate(1)
good_tip = self.nodes[1].getbestblockhash()
self.wait_for_chainlock_tip(self.nodes[1])
assert(not self.nodes[0].getblock(self.nodes[0].getbestblockhash())["chainlock"])
reconnect_isolated_node(self.nodes[0], 1)
self.nodes[1].generate(1)
self.wait_for_chainlock(self.nodes[0], self.nodes[1].getbestblockhash())
assert(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["previousblockhash"] == good_tip)
assert(self.nodes[1].getblock(self.nodes[1].getbestblockhash())["previousblockhash"] == good_tip)

# Keep node connected and let it try to reorg the chain
good_tip = self.nodes[0].getbestblockhash()
# Restart it so that it forgets all the chainlocks from the past
stop_node(self.nodes[0], 0)
self.nodes[0] = start_node(0, self.options.tmpdir, self.extra_args[0])
connect_nodes(self.nodes[0], 1)
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
# Now try to reorg the chain
self.nodes[0].generate(2)
assert(self.nodes[1].getbestblockhash() == good_tip)
self.nodes[0].generate(2)
sleep(6)
assert(self.nodes[1].getbestblockhash() == good_tip)

# Now let the node which is on the wrong chain reorg back to the locked chain
self.nodes[0].reconsiderblock(good_tip)
assert(self.nodes[0].getbestblockhash() != good_tip)
self.nodes[1].generate(1)
self.wait_for_chainlock(self.nodes[0], self.nodes[1].getbestblockhash())
assert(self.nodes[0].getbestblockhash() == self.nodes[1].getbestblockhash())

isolate_node(self.nodes[0])
self.nodes[0].generate(1)
reconnect_isolated_node(self.nodes[0], 1)
self.wait_for_chainlock(self.nodes[0], self.nodes[1].getbestblockhash())

def wait_for_chainlock_tip_all_nodes(self):
for node in self.nodes:
tip = node.getbestblockhash()
self.wait_for_chainlock(node, tip)

def wait_for_chainlock_tip(self, node):
tip = node.getbestblockhash()
self.wait_for_chainlock(node, tip)

def wait_for_chainlock(self, node, block_hash):
t = time()
while time() - t < 15:
try:
block = node.getblock(block_hash)
if block["confirmations"] > 0 and block["chainlock"]:
return
except:
# block might not be on the node yet
pass
sleep(0.1)
raise AssertionError("wait_for_chainlock timed out")

def create_chained_txs(self, node, amount):
txid = node.sendtoaddress(node.getnewaddress(), amount)
tx = node.getrawtransaction(txid, 1)
inputs = []
valueIn = 0
for txout in tx["vout"]:
inputs.append({"txid": txid, "vout": txout["n"]})
valueIn += txout["value"]
outputs = {
node.getnewaddress(): round(float(valueIn) - 0.0001, 6)
}

rawtx = node.createrawtransaction(inputs, outputs)
rawtx = node.signrawtransaction(rawtx)
rawtxid = node.sendrawtransaction(rawtx["hex"])

return [txid, rawtxid]


if __name__ == '__main__':
LLMQChainLocksTest().main()
118 changes: 118 additions & 0 deletions qa/rpc-tests/llmq-cl-evospork.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2018 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

from test_framework.mininode import *
from test_framework.test_framework import EvoZnodeTestFramework
from test_framework.util import *
from time import *

'''
llmq-chainlocks.py
Checks LLMQs based ChainLocks
00 Mine quorum and produce chainlocks
01 Make sure the chainlocked tip does not change after invalidateblock
02 Make sure a rogue miner cannot inject a longer chain
10 Disable chainlocks
11 Make sure 01-09 work as usual
-
'''

class LLMQChainLocksTest(EvoZnodeTestFramework):
def __init__(self):
super().__init__(6, 5, extra_args=[['-debug=chainlocks']] * 6)
self.sporkprivkey = "cW2YM2xaeCaebfpKguBahUAgEzLXgSserWRuD29kSyKHq1TTgwRQ"

def run_test(self):

for i in range(4):
self.mine_quorum()

# mine single block, wait for chainlock
self.nodes[0].generate(1)

self.wait_for_chainlock_tip_all_nodes()
self.payment_address = self.nodes[0].getaccountaddress("")
self.nodes[0].sendtoaddress(self.payment_address, 1)

# mine many blocks, wait for chainlock
while self.nodes[0].getblockcount() < 1000:
self.nodes[0].generate(20)
self.wait_for_chainlock_tip_all_nodes()

# assert that all blocks up until the tip are chainlocked
for h in range(1, self.nodes[0].getblockcount()):
block = self.nodes[0].getblock(self.nodes[0].getblockhash(h))
assert(block['chainlock'])

# cannot invalidate tip
current_tip = self.nodes[0].getbestblockhash()
self.nodes[0].invalidateblock(current_tip)
assert(current_tip == self.nodes[0].getbestblockhash())

##### Disable chainlocks for 10 blocks

self.nodes[0].importprivkey(self.sporkprivkey)
self.disable_chainlocks(self.nodes[0].getblockcount() + 10)
self.nodes[0].generate(1)
assert(False == self.nodes[0].getblock(self.nodes[0].getbestblockhash())["chainlock"])

# can invalidate block now
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
assert(current_tip == self.nodes[0].getbestblockhash())

isolate_node(self.nodes[5])

##### Enable chainlocks

self.nodes[0].generate(10)
self.nodes[0].spork('list')
self.wait_for_chainlock_tip_all_nodes()
sporks = self.nodes[0].spork("list")
assert(not sporks["blockchain"])
assert(not sporks["mempool"])
assert(True == self.nodes[0].getblock(self.nodes[0].getbestblockhash())["chainlock"])

# generate a longer chain on the isolated node then reconnect it back and make sure it picks the chainlocked chain
self.nodes[5].generate(20)
reconnect_isolated_node(self.nodes[5], 1)
self.nodes[0].generate(1)
current_tip = self.nodes[0].getbestblockhash()
timeout = 10
while current_tip != self.nodes[5].getbestblockhash():
assert timeout > 0, "Timed out when waiting for a chainlocked chain"
sleep(1)
timeout = timeout - 1


def wait_for_chainlock_tip_all_nodes(self):
for node in self.nodes:
tip = node.getbestblockhash()
self.wait_for_chainlock(node, tip)

def wait_for_chainlock_tip(self, node):
tip = node.getbestblockhash()
self.wait_for_chainlock(node, tip)

def wait_for_chainlock(self, node, block_hash):
t = time()
while time() - t < 15:
try:
block = node.getblock(block_hash)
if block["confirmations"] > 0 and block["chainlock"]:
return
except:
# block might not be on the node yet
pass
sleep(0.1)
raise AssertionError("wait_for_chainlock timed out")

def disable_chainlocks(self, till_height):
self.nodes[0].spork(self.sporkprivkey, self.payment_address, {"disable":{"chainlocks": till_height}})


if __name__ == '__main__':
LLMQChainLocksTest().main()
94 changes: 94 additions & 0 deletions qa/rpc-tests/llmq-dkgerrors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2018 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

from test_framework.test_framework import EvoZnodeTestFramework
from test_framework.util import *

'''
llmq-dkgerrors.py
Simulate and check DKG errors
'''

class LLMQDKGErrors(EvoZnodeTestFramework):
def __init__(self):
super().__init__(6, 5)

def run_test(self):

# Mine one quorum without simulating any errors
qh = self.mine_quorum()
self.assert_member_valid(qh, self.mninfo[0].proTxHash, True)

# Lets omit the contribution
self.mninfo[0].node.quorum('dkgsimerror', 'contribution-omit', '1')
qh = self.mine_quorum(expected_contributions=4)
self.assert_member_valid(qh, self.mninfo[0].proTxHash, False)

# Lets lie in the contribution but provide a correct justification
self.mninfo[0].node.quorum('dkgsimerror', 'contribution-omit', '0')
self.mninfo[0].node.quorum('dkgsimerror', 'contribution-lie', '1')
qh = self.mine_quorum(expected_contributions=5, expected_complaints=4, expected_justifications=1)
self.assert_member_valid(qh, self.mninfo[0].proTxHash, True)

# Lets lie in the contribution and then omit the justification
self.mninfo[0].node.quorum('dkgsimerror', 'justify-omit', '1')
qh = self.mine_quorum(expected_contributions=4, expected_complaints=4)
self.assert_member_valid(qh, self.mninfo[0].proTxHash, False)

# Heal some damage (don't get PoSe banned)
self.heal_masternodes(33)

# Lets lie in the contribution and then also lie in the justification
self.mninfo[0].node.quorum('dkgsimerror', 'justify-omit', '0')
self.mninfo[0].node.quorum('dkgsimerror', 'justify-lie', '1')
qh = self.mine_quorum(expected_contributions=4, expected_complaints=4, expected_justifications=1)
self.assert_member_valid(qh, self.mninfo[0].proTxHash, False)

# Lets lie about another MN
self.mninfo[0].node.quorum('dkgsimerror', 'contribution-lie', '0')
self.mninfo[0].node.quorum('dkgsimerror', 'justify-lie', '0')
self.mninfo[0].node.quorum('dkgsimerror', 'complain-lie', '1')
qh = self.mine_quorum(expected_contributions=5, expected_complaints=1, expected_justifications=4)
self.assert_member_valid(qh, self.mninfo[0].proTxHash, True)

# Lets omit 2 premature commitments
self.mninfo[0].node.quorum('dkgsimerror', 'complain-lie', '0')
self.mninfo[0].node.quorum('dkgsimerror', 'commit-omit', '1')
self.mninfo[1].node.quorum('dkgsimerror', 'commit-omit', '1')
qh = self.mine_quorum(expected_contributions=5, expected_complaints=0, expected_justifications=0, expected_commitments=3)
self.assert_member_valid(qh, self.mninfo[0].proTxHash, True)

# Lets lie in 2 premature commitments
self.mninfo[0].node.quorum('dkgsimerror', 'commit-omit', '0')
self.mninfo[1].node.quorum('dkgsimerror', 'commit-omit', '0')
self.mninfo[0].node.quorum('dkgsimerror', 'commit-lie', '1')
self.mninfo[1].node.quorum('dkgsimerror', 'commit-lie', '1')
qh = self.mine_quorum(expected_contributions=5, expected_complaints=0, expected_justifications=0, expected_commitments=3)
self.assert_member_valid(qh, self.mninfo[0].proTxHash, True)

def assert_member_valid(self, quorumHash, proTxHash, expectedValid):
q = self.nodes[0].quorum('info', 100, quorumHash, True)
for m in q['members']:
if m['proTxHash'] == proTxHash:
if expectedValid:
assert(m['valid'])
else:
assert(not m['valid'])
else:
assert(m['valid'])

def heal_masternodes(self, blockCount):
# We're not testing PoSe here, so lets heal the MNs :)
for i in range(blockCount):
set_mocktime(get_mocktime() + 1)
set_node_times(self.nodes, get_mocktime())
self.nodes[0].generate(1)
self.sync_all()


if __name__ == '__main__':
LLMQDKGErrors().main()
Loading

0 comments on commit 5525561

Please sign in to comment.