forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathfeature_governance_cl.py
More file actions
executable file
·185 lines (147 loc) · 8.61 KB
/
Copy pathfeature_governance_cl.py
File metadata and controls
executable file
·185 lines (147 loc) · 8.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python3
# Copyright (c) 2024-2025 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests governance checks can be skipped for blocks covered by the best chainlock."""
import json
from test_framework.governance import have_trigger_for_height
from test_framework.messages import uint256_to_string
from test_framework.test_framework import DashTestFramework
from test_framework.util import assert_equal, satoshi_round
class DashGovernanceTest (DashTestFramework):
def add_options(self, parser):
self.add_wallet_options(parser)
def set_test_params(self):
self.set_dash_test_params(6, 5, [["-budgetparams=10:10:10"]] * 6)
def prepare_object(self, object_type, parent_hash, creation_time, revision, name, amount, payment_address):
proposal_rev = revision
proposal_time = int(creation_time)
proposal_template = {
"type": object_type,
"name": name,
"start_epoch": proposal_time,
"end_epoch": proposal_time + 20 * 156,
"payment_amount": float(amount),
"payment_address": payment_address,
"url": "https://dash.org"
}
proposal_hex = ''.join(format(x, '02x') for x in json.dumps(proposal_template).encode())
collateral_hash = self.nodes[0].gobject("prepare", parent_hash, proposal_rev, proposal_time, proposal_hex)
return {
"parentHash": parent_hash,
"collateralHash": collateral_hash,
"createdAt": proposal_time,
"revision": proposal_rev,
"hex": proposal_hex,
"data": proposal_template,
}
def run_test(self):
sb_cycle = 20
sb_maturity_window = 10
sb_immaturity_window = sb_cycle - sb_maturity_window
self.log.info("Make sure ChainLocks are active")
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
self.wait_for_sporks_same()
self.mine_cycle_quorum()
self.sync_blocks()
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())
# Move to the superblock cycle start block
n = sb_cycle - self.nodes[0].getblockcount() % sb_cycle
if n > 0:
self.bump_mocktime(156 * n)
self.generate(self.nodes[0], n, sync_fun=lambda: self.sync_blocks())
self.log.info("Prepare proposals")
proposal_time = self.mocktime
self.p0_payout_address = self.nodes[0].getnewaddress()
self.p1_payout_address = self.nodes[0].getnewaddress()
self.p0_amount = satoshi_round("1.1")
self.p1_amount = satoshi_round("3.3")
p0_collateral_prepare = self.prepare_object(1, uint256_to_string(0), proposal_time, 1, "Proposal_0", self.p0_amount, self.p0_payout_address)
p1_collateral_prepare = self.prepare_object(1, uint256_to_string(0), proposal_time, 1, "Proposal_1", self.p1_amount, self.p1_payout_address)
self.bump_mocktime(60 * 10 + 1)
self.generate(self.nodes[0], 6, sync_fun=self.sync_blocks())
assert_equal(len(self.nodes[0].gobject("list-prepared")), 2)
assert_equal(len(self.nodes[0].gobject("list")), 0)
self.log.info("Submit proposals")
self.p0_hash = self.nodes[0].gobject("submit", "0", 1, proposal_time, p0_collateral_prepare["hex"], p0_collateral_prepare["collateralHash"])
self.p1_hash = self.nodes[0].gobject("submit", "0", 1, proposal_time, p1_collateral_prepare["hex"], p1_collateral_prepare["collateralHash"])
assert_equal(len(self.nodes[0].gobject("list")), 2)
self.log.info("Isolate a node so that it would miss votes and triggers")
# Isolate a node
self.isolate_node(5)
self.log.info("Vote proposals")
self.nodes[0].gobject("vote-many", self.p0_hash, "funding", "yes")
self.nodes[0].gobject("vote-many", self.p1_hash, "funding", "yes")
assert_equal(self.nodes[0].gobject("get", self.p0_hash)["FundingResult"]["YesCount"], self.mn_count)
assert_equal(self.nodes[0].gobject("get", self.p1_hash)["FundingResult"]["YesCount"], self.mn_count)
assert_equal(len(self.nodes[0].gobject("list", "valid", "triggers")), 0)
self.log.info("Move into sb maturity window")
n = sb_immaturity_window - self.nodes[0].getblockcount() % sb_cycle
assert n >= 0
self.bump_mocktime(156 * n)
self.generate(self.nodes[0], n, sync_fun=lambda: self.sync_blocks(self.nodes[0:5]))
self.log.info("Wait for new trigger and votes on non-isolated nodes")
sb_block_height = self.nodes[0].getblockcount() // sb_cycle * sb_cycle + sb_cycle
assert_equal(sb_block_height % sb_cycle, 0)
assert_equal(self.mninfo[4].nodeIdx, 5)
isolated_lastpaid_height = self.nodes[0].protx("info", self.mninfo[4].proTxHash)["state"]["lastPaidHeight"]
if isolated_lastpaid_height == self.nodes[0].getblockcount() - self.mn_count + 1:
# Isolated node is the one that should create new trigger at this height,
# mine a block to pick another node
self.bump_mocktime(156)
self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(self.nodes[0:5]))
self.wait_until(lambda: have_trigger_for_height(self.nodes[0:5], sb_block_height), timeout=5)
n = sb_cycle - self.nodes[0].getblockcount() % sb_cycle
assert n > 1
self.log.info("Move remaining n blocks until the next Superblock")
self.bump_mocktime(156 * (n-1))
self.generate(self.nodes[0], n-1, sync_fun=lambda: self.sync_blocks(self.nodes[0:5]))
# Confirm all is good
self.wait_until(lambda: have_trigger_for_height(self.nodes[0:5], sb_block_height), timeout=5)
self.log.info("Mine superblock")
self.bump_mocktime(156)
self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(self.nodes[0:5]))
self.wait_for_chainlocked_block(self.nodes[0], self.nodes[0].getbestblockhash())
self.log.info("Mine (superblock cycle + 1) blocks on non-isolated nodes to forget about this trigger")
for _ in range(sb_cycle):
self.bump_mocktime(156)
self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(self.nodes[0:5]))
# Should still have at least 1 trigger for the old sb cycle and 0 for the current one
assert len(self.nodes[0].gobject("list", "valid", "triggers")) >= 1
assert not have_trigger_for_height(self.nodes[0:5], sb_block_height + sb_cycle)
self.bump_mocktime(156)
self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(self.nodes[0:5]))
self.log.info("Wait for governance module to catch up with block updates")
tip_height = self.nodes[0].getblockcount()
expected_msg = f'CGovernanceManager::UpdatedBlockTip -- nCachedBlockHeight: {tip_height}'
def governance_tip_updated(node):
with open(node.debug_log_path, "rb") as dl:
seek_pos = node.debug_log_size(mode="rb") - 100 * 1024 # read the last 100 KiB only
dl.seek(seek_pos if seek_pos > 0 else 0)
debug_log_part = dl.read().decode("utf-8", errors="replace")
return expected_msg in debug_log_part
for node in self.nodes[0:5]:
self.wait_until(lambda node=node: governance_tip_updated(node))
self.log.info("Bump mocktime to trigger governance cleanup")
for delta, expected in (
(5 * 60, ['SuperblockManager::Clean -- Removing trigger object']), # mark old triggers for deletion
(10 * 60, ['UpdateCachesAndClean -- Governance Objects: 0']), # deletion after delay
):
self.mocktime += delta
for node in self.nodes[0:5]:
with node.assert_debug_log(expected_msgs=expected):
node.setmocktime(self.mocktime)
node.mockscheduler(delta)
self.nodes[5].setmocktime(self.mocktime)
self.nodes[5].mockscheduler(delta)
# Confirm in RPC
for node in self.nodes:
assert_equal(len(node.gobject("list", "valid", "triggers")), 0)
self.log.info("Reconnect isolated node and confirm the next ChainLock will let it sync")
self.reconnect_isolated_node(5, 0)
assert_equal(self.nodes[5].mnsync("status")["IsSynced"], False)
# NOTE: bumping mocktime too much after recent reconnect can result in "timeout downloading block"
self.bump_mocktime(1)
self.generate(self.nodes[0], 1, sync_fun=self.sync_blocks())
if __name__ == '__main__':
DashGovernanceTest().main()