mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 12:32:48 +01:00
baa28b9854
## Issue being fixed or feature implemented #5564 is a bit too optimistic about incoming triggers ## What was done? Rework governance logic to only approve triggers that match our expectations i.e. have the same data hash as our own trigger would have if we would have to submit it. ## How Has This Been Tested? Run tests ## Breaking Changes Voting is done in `CreateGovernanceTrigger` only now meaning that it only happens on next block for incoming triggers. Tweaked tests accordingly. ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_
184 lines
8.6 KiB
Python
Executable File
184 lines
8.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2018-2020 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 around dash governance."""
|
|
|
|
import json
|
|
import time
|
|
|
|
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 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 + 24 * 60 * 60,
|
|
"payment_amount": 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):
|
|
map_vote_outcomes = {
|
|
0: "none",
|
|
1: "yes",
|
|
2: "no",
|
|
3: "abstain"
|
|
}
|
|
map_vote_signals = {
|
|
0: "none",
|
|
1: "funding",
|
|
2: "valid",
|
|
3: "delete",
|
|
4: "endorsed"
|
|
}
|
|
|
|
self.nodes[0].sporkupdate("SPORK_2_INSTANTSEND_ENABLED", 4070908800)
|
|
self.nodes[0].sporkupdate("SPORK_9_SUPERBLOCKS_ENABLED", 0)
|
|
self.wait_for_sporks_same()
|
|
|
|
assert_equal(len(self.nodes[0].gobject("list-prepared")), 0)
|
|
|
|
proposal_time = self.mocktime
|
|
sb_block_height = self.nodes[0].getblockcount() + 10 - self.nodes[0].getblockcount() % 10
|
|
p0_payout_address = self.nodes[0].getnewaddress()
|
|
p1_payout_address = self.nodes[0].getnewaddress()
|
|
p2_payout_address = self.nodes[0].getnewaddress()
|
|
p0_amount = float(1.1)
|
|
p1_amount = float(3.3)
|
|
p2_amount = float(self.nodes[0].getsuperblockbudget(sb_block_height)) - p1_amount
|
|
|
|
p0_collateral_prepare = self.prepare_object(1, uint256_to_string(0), proposal_time, 1, "Proposal_0", p0_amount, p0_payout_address)
|
|
p1_collateral_prepare = self.prepare_object(1, uint256_to_string(0), proposal_time, 1, "Proposal_1", p1_amount, p1_payout_address)
|
|
p2_collateral_prepare = self.prepare_object(1, uint256_to_string(0), proposal_time, 1, "Proposal_2", p2_amount, p2_payout_address)
|
|
|
|
self.nodes[0].generate(6)
|
|
self.sync_blocks()
|
|
|
|
assert_equal(len(self.nodes[0].gobject("list-prepared")), 3)
|
|
assert_equal(len(self.nodes[0].gobject("list")), 0)
|
|
|
|
p0_hash = self.nodes[0].gobject("submit", "0", 1, proposal_time, p0_collateral_prepare["hex"], p0_collateral_prepare["collateralHash"])
|
|
p1_hash = self.nodes[0].gobject("submit", "0", 1, proposal_time, p1_collateral_prepare["hex"], p1_collateral_prepare["collateralHash"])
|
|
p2_hash = self.nodes[0].gobject("submit", "0", 1, proposal_time, p2_collateral_prepare["hex"], p2_collateral_prepare["collateralHash"])
|
|
|
|
assert_equal(len(self.nodes[0].gobject("list")), 3)
|
|
|
|
assert_equal(self.nodes[0].gobject("get", p0_hash)["FundingResult"]["YesCount"], 0)
|
|
assert_equal(self.nodes[0].gobject("get", p0_hash)["FundingResult"]["NoCount"], 0)
|
|
|
|
assert_equal(self.nodes[0].gobject("get", p1_hash)["FundingResult"]["YesCount"], 0)
|
|
assert_equal(self.nodes[0].gobject("get", p1_hash)["FundingResult"]["NoCount"], 0)
|
|
|
|
assert_equal(self.nodes[0].gobject("get", p2_hash)["FundingResult"]["YesCount"], 0)
|
|
assert_equal(self.nodes[0].gobject("get", p2_hash)["FundingResult"]["NoCount"], 0)
|
|
|
|
self.nodes[0].gobject("vote-alias", p0_hash, map_vote_signals[1], map_vote_outcomes[2], self.mninfo[0].proTxHash)
|
|
self.nodes[0].gobject("vote-many", p0_hash, map_vote_signals[1], map_vote_outcomes[1])
|
|
assert_equal(self.nodes[0].gobject("get", p0_hash)["FundingResult"]["YesCount"], self.mn_count - 1)
|
|
assert_equal(self.nodes[0].gobject("get", p0_hash)["FundingResult"]["NoCount"], 1)
|
|
|
|
self.nodes[0].gobject("vote-alias", p1_hash, map_vote_signals[1], map_vote_outcomes[2], self.mninfo[0].proTxHash)
|
|
self.nodes[0].gobject("vote-alias", p1_hash, map_vote_signals[1], map_vote_outcomes[2], self.mninfo[1].proTxHash)
|
|
self.nodes[0].gobject("vote-many", p1_hash, map_vote_signals[1], map_vote_outcomes[1])
|
|
assert_equal(self.nodes[0].gobject("get", p1_hash)["FundingResult"]["YesCount"], self.mn_count - 2)
|
|
assert_equal(self.nodes[0].gobject("get", p1_hash)["FundingResult"]["NoCount"], 2)
|
|
|
|
self.nodes[0].gobject("vote-alias", p2_hash, map_vote_signals[1], map_vote_outcomes[2], self.mninfo[0].proTxHash)
|
|
self.nodes[0].gobject("vote-alias", p2_hash, map_vote_signals[1], map_vote_outcomes[2], self.mninfo[1].proTxHash)
|
|
self.nodes[0].gobject("vote-many", p2_hash, map_vote_signals[1], map_vote_outcomes[1])
|
|
assert_equal(self.nodes[0].gobject("get", p2_hash)["FundingResult"]["YesCount"], self.mn_count - 2)
|
|
assert_equal(self.nodes[0].gobject("get", p2_hash)["FundingResult"]["NoCount"], 2)
|
|
|
|
assert_equal(len(self.nodes[0].gobject("list", "valid", "triggers")), 0)
|
|
|
|
block_count = self.nodes[0].getblockcount()
|
|
sb_cycle = 10
|
|
sb_maturity_window = 2
|
|
sb_maturity_cycle = sb_cycle - sb_maturity_window
|
|
|
|
# Move until 1 block before the Superblock maturity window starts
|
|
n = sb_maturity_cycle - block_count % sb_cycle
|
|
self.nodes[0].generate(n - 1)
|
|
self.sync_blocks()
|
|
time.sleep(1)
|
|
|
|
assert_equal(len(self.nodes[0].gobject("list", "valid", "triggers")), 0)
|
|
|
|
# Move 1 block enabling the Superblock maturity window
|
|
self.nodes[0].generate(1)
|
|
self.sync_blocks()
|
|
time.sleep(1)
|
|
|
|
# The "winner" should submit new trigger and vote for it, no one else should vote yet
|
|
valid_triggers = self.nodes[0].gobject("list", "valid", "triggers")
|
|
assert_equal(len(valid_triggers), 1)
|
|
trigger_data = list(valid_triggers.values())[0]
|
|
assert_equal(trigger_data['YesCount'], 1)
|
|
|
|
# Move 1 block inside the Superblock maturity window
|
|
self.nodes[0].generate(1)
|
|
self.sync_blocks()
|
|
time.sleep(1)
|
|
|
|
# Every MN should vote for the same trigger now, no new triggers should be created
|
|
triggers_rpc = self.nodes[0].gobject("list", "valid", "triggers")
|
|
assert_equal(len(triggers_rpc), 1)
|
|
trigger_data = list(triggers_rpc.values())[0]
|
|
assert_equal(trigger_data['YesCount'], self.mn_count)
|
|
|
|
block_count = self.nodes[0].getblockcount()
|
|
n = sb_cycle - block_count % sb_cycle
|
|
|
|
# Move remaining n blocks until actual Superblock
|
|
for i in range(n):
|
|
time.sleep(1)
|
|
self.nodes[0].generate(1)
|
|
self.sync_blocks()
|
|
|
|
# Make sure Superblock has only payments that fit into the budget
|
|
# p0 must always be included because it has most votes
|
|
# p1 and p2 have equal number of votes (but less votes than p0)
|
|
# so only one of them can be included (depends on proposal hashes).
|
|
coinbase_outputs = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 2)["tx"][0]["vout"]
|
|
payments_found = 0
|
|
for txout in coinbase_outputs:
|
|
if txout["value"] == satoshi_round(str(p0_amount)) and txout["scriptPubKey"]["addresses"][0] == p0_payout_address:
|
|
payments_found += 1
|
|
if txout["value"] == satoshi_round(str(p1_amount)) and txout["scriptPubKey"]["addresses"][0] == p1_payout_address:
|
|
if p1_hash > p2_hash:
|
|
payments_found += 1
|
|
else:
|
|
assert False
|
|
if txout["value"] == satoshi_round(str(p2_amount)) and txout["scriptPubKey"]["addresses"][0] == p2_payout_address:
|
|
if p2_hash > p1_hash:
|
|
payments_found += 1
|
|
else:
|
|
assert False
|
|
|
|
assert_equal(payments_found, 2)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
DashGovernanceTest().main()
|