Skip to content

Commit 37fa72e

Browse files
author
alex v
authored
CFundDB extra log and ensure read before modify (#622)
* add extra log * add __func__ * ensure read before modify * fix log * optimize log * do not access modifier * only set dirty when necessary * check for nullified * add extra log * do not insert nullified entries * HaveProposalInCache/HavePaymentRequestInCache * add cfunddb_tests.cpp * add 250 rounds and random remove * Added new test for cfund reorg scenario * Updates to the test as per aguycalled's suggestions * update qa/rpc-tests/cfund-fork-reorg.py * Added new test to the suite * move mprequest * adding (failing) test for proposal reorg * removed 5th cycle * fixed preq voting, removed logs added final payout check
1 parent b8ed018 commit 37fa72e

File tree

10 files changed

+988
-26
lines changed

10 files changed

+988
-26
lines changed

qa/pull-tester/rpc-tests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@
152152
'importprunedfunds.py',
153153
'signmessages.py',
154154
'cfund-donate.py',
155+
'cfund-fork-reorg-preq.py',
156+
'cfund-fork-reorg-proposal.py',
155157
'cfund-listproposals.py',
156158
'cfund-paymentrequest-extract-funds.py',
157159
'cfund-paymentrequest-payout.py',
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2018 The Navcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
from test_framework.test_framework import NavCoinTestFramework
7+
from test_framework.cfund_util import *
8+
9+
import urllib.parse
10+
11+
class CfundForkReorgPreq(NavCoinTestFramework):
12+
"""Tests that reorg with 2 chains with same proposals/requests does not cause a fork"""
13+
14+
def __init__(self):
15+
super().__init__()
16+
self.setup_clean_chain = True
17+
self.num_nodes = 2
18+
19+
def setup_network(self, split=False):
20+
self.nodes = self.setup_nodes()
21+
connect_nodes_bi(self.nodes, 0, 1)
22+
self.is_network_split = False
23+
24+
def run_test(self):
25+
# Make sure the nodes are not staking
26+
self.nodes[0].staking(False)
27+
self.nodes[1].staking(False)
28+
29+
# Active cfund
30+
activate_cfund(self.nodes[0])
31+
self.nodes[0].donatefund(100000)
32+
33+
# Details for proposal
34+
paymentAddress = self.nodes[0].getnewaddress()
35+
proposalAmount = 10000
36+
37+
# Create the proposal and save the id/hash
38+
proposalHash = self.nodes[0].createproposal(paymentAddress, proposalAmount, 36000, "test")["hash"]
39+
end_cycle(self.nodes[0])
40+
sync_blocks(self.nodes)
41+
42+
# Make the node vote yes and generate blocks to accept it
43+
self.nodes[0].proposalvote(proposalHash, "yes")
44+
slow_gen(self.nodes[0], 1)
45+
end_cycle(self.nodes[0])
46+
sync_blocks(self.nodes)
47+
48+
# disconnect the nodes and generate the preq on each node
49+
url = urllib.parse.urlparse(self.nodes[0].url)
50+
self.nodes[0].disconnectnode(url.hostname+":"+str(p2p_port(1)))
51+
self.nodes[1].disconnectnode(url.hostname+":"+str(p2p_port(0)))
52+
time.sleep(2) # Wait for the nodes to disconnect
53+
54+
# Create Payment request raw hex
55+
paymentHex = self.create_raw_paymentrequest(proposalAmount, paymentAddress, proposalHash, "test")
56+
57+
# Broadcast on node 0
58+
slow_gen(self.nodes[0], 1)
59+
paymentHash0 = self.nodes[0].sendrawtransaction(paymentHex)
60+
slow_gen(self.nodes[0], 1)
61+
62+
# Boardcast on node 1
63+
paymentHash1 = self.nodes[1].sendrawtransaction(paymentHex)
64+
slow_gen(self.nodes[1], 1)
65+
66+
# Assert that both hashes for payment request are identical
67+
assert(paymentHash0 == paymentHash1)
68+
69+
# Assert that both payment requests have been included in different block hashes
70+
assert(self.nodes[0].getpaymentrequest(paymentHash0)["blockHash"] != self.nodes[1].getpaymentrequest(paymentHash1)["blockHash"])
71+
72+
# Make the node vote yes and generate blocks to accept it on node 0
73+
self.nodes[1].paymentrequestvote(paymentHash0, "yes")
74+
slow_gen(self.nodes[1], 1)
75+
end_cycle(self.nodes[1])
76+
slow_gen(self.nodes[1], 1)
77+
end_cycle(self.nodes[1])
78+
79+
# Now make sure that both nodes are on different chains
80+
assert(self.nodes[0].getbestblockhash() != self.nodes[1].getbestblockhash())
81+
82+
# we save node 1 best block hash to check node 0 reorgs correctly
83+
best_block = self.nodes[1].getbestblockhash()
84+
85+
self.stop_node(0)
86+
self.nodes[0] = start_node(0, self.options.tmpdir, [])
87+
88+
# Reconnect the nodes
89+
connect_nodes_bi(self.nodes, 0, 1)
90+
91+
# Wait for nodes to sync
92+
sync_blocks(self.nodes)
93+
94+
# Now check that the hash for both nodes are the same
95+
assert_equal(self.nodes[0].getbestblockhash(), best_block)
96+
assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash())
97+
assert_equal(self.nodes[0].getblock(self.nodes[0].getpaymentrequest(paymentHash0)["blockHash"]), self.nodes[1].getblock(self.nodes[1].getpaymentrequest(paymentHash0)["blockHash"]))
98+
assert_equal(self.nodes[0].getpaymentrequest(paymentHash0), self.nodes[1].getpaymentrequest(paymentHash0))
99+
100+
slow_gen(self.nodes[0], self.nodes[0].cfundstats()["votingPeriod"]["ending"] - self.nodes[0].cfundstats()["votingPeriod"]["current"])
101+
sync_blocks(self.nodes)
102+
103+
assert_equal(self.nodes[0].getpaymentrequest(paymentHash0)["status"], "accepted")
104+
assert_equal(self.nodes[0].getblock(self.nodes[0].getpaymentrequest(paymentHash0)["paidOnBlock"]), self.nodes[1].getblock(self.nodes[1].getpaymentrequest(paymentHash0)["paidOnBlock"]))
105+
assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash())
106+
assert_equal(self.nodes[0].getblock(self.nodes[0].getpaymentrequest(paymentHash0)["blockHash"]), self.nodes[1].getblock(self.nodes[1].getpaymentrequest(paymentHash0)["blockHash"]))
107+
assert_equal(self.nodes[0].getpaymentrequest(paymentHash0), self.nodes[1].getpaymentrequest(paymentHash0))
108+
109+
def create_raw_paymentrequest(self, amount, address, proposal_hash, description):
110+
amount = amount * 100000000
111+
privkey = self.nodes[0].dumpprivkey(address)
112+
message = "I kindly ask to withdraw " + str(amount) + "NAV from the proposal " + proposal_hash + ". Payment request id: " + str(description)
113+
signature = self.nodes[0].signmessagewithprivkey(privkey, message)
114+
115+
# Create a raw payment request
116+
raw_payreq_tx = self.nodes[0].createrawtransaction(
117+
[],
118+
{"6ac1": 1},
119+
json.dumps({
120+
"v": 2,
121+
"h": proposal_hash,
122+
"n": amount,
123+
"s": signature,
124+
"i": description,
125+
})
126+
)
127+
128+
# Modify version for payreq creation
129+
raw_payreq_tx = "05" + raw_payreq_tx[2:]
130+
131+
# Fund raw transacion
132+
raw_payreq_tx = self.nodes[0].fundrawtransaction(raw_payreq_tx)['hex']
133+
134+
# Sign raw transacion
135+
raw_payreq_tx = self.nodes[0].signrawtransaction(raw_payreq_tx)['hex']
136+
137+
# Return the raw hex
138+
return raw_payreq_tx
139+
140+
if __name__ == '__main__':
141+
CfundForkReorgPreq().main()
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2018 The Navcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
from test_framework.test_framework import NavCoinTestFramework
7+
from test_framework.cfund_util import *
8+
9+
import urllib.parse
10+
import string
11+
import random
12+
13+
class CfundForkReorgProposal(NavCoinTestFramework):
14+
"""Tests that reorg with 2 chains with same proposals/requests does not cause a fork"""
15+
16+
def __init__(self):
17+
super().__init__()
18+
self.setup_clean_chain = True
19+
self.num_nodes = 2
20+
21+
def setup_network(self, split=False):
22+
self.nodes = self.setup_nodes()
23+
connect_nodes_bi(self.nodes, 0, 1)
24+
self.is_network_split = False
25+
26+
def run_test(self):
27+
# Make sure the nodes are not staking
28+
self.nodes[0].staking(False)
29+
self.nodes[1].staking(False)
30+
31+
# Active cfund & ensure donation is avaialable
32+
activate_cfund(self.nodes[0])
33+
34+
self.nodes[0].donatefund(100000)
35+
slow_gen(self.nodes[1], 1000)
36+
sync_blocks(self.nodes)
37+
38+
# Details for proposal
39+
paymentAddress = self.nodes[0].getnewaddress()
40+
proposalAmount = 10000
41+
proposalDeadline = 60*60*24*7
42+
43+
# disconnect the nodes and generate the proposal on each node
44+
url = urllib.parse.urlparse(self.nodes[0].url)
45+
self.nodes[0].disconnectnode(url.hostname+":"+str(p2p_port(1)))
46+
self.nodes[1].disconnectnode(url.hostname+":"+str(p2p_port(0)))
47+
time.sleep(2) # Wait for the nodes to disconnect
48+
49+
# Create the proposal and save the id/hash
50+
proposalHex = self.nodes[0].createproposal(paymentAddress, proposalAmount, proposalDeadline, "test", 1, True)
51+
52+
# Broadcast on node 0
53+
slow_gen(self.nodes[0], 1)
54+
proposalHash0 = self.nodes[0].sendrawtransaction(proposalHex)
55+
slow_gen(self.nodes[0], 1)
56+
57+
# Boardcast on node 1
58+
proposalHash1 = self.nodes[1].sendrawtransaction(proposalHex)
59+
slow_gen(self.nodes[1], 1)
60+
61+
# Assert that both hashes for payment request are identical
62+
assert(proposalHash0 == proposalHash1)
63+
64+
# Assert that both payment requests have been included in different block hashes
65+
assert(self.nodes[0].getproposal(proposalHash0)["blockHash"] != self.nodes[1].getproposal(proposalHash1)["blockHash"])
66+
67+
# Make the node vote yes
68+
self.nodes[1].proposalvote(proposalHash0, "yes")
69+
70+
# End cycle 0
71+
slow_gen(self.nodes[1], 1)
72+
end_cycle(self.nodes[1])
73+
74+
# End cycle 1
75+
slow_gen(self.nodes[1], 1)
76+
end_cycle(self.nodes[1])
77+
78+
# Now make sure that both nodes are on different chains
79+
assert(self.nodes[0].getbestblockhash() != self.nodes[1].getbestblockhash())
80+
81+
# we save node 1 best block hash to check node 0 reorgs correctly
82+
best_block = self.nodes[1].getbestblockhash()
83+
84+
self.stop_node(0)
85+
self.nodes[0] = start_node(0, self.options.tmpdir, [])
86+
87+
# Reconnect the nodes
88+
connect_nodes_bi(self.nodes, 0, 1)
89+
90+
# Wait for nodes to sync
91+
sync_blocks(self.nodes)
92+
93+
# Now check that the hash for both nodes are the same
94+
assert_equal(self.nodes[0].getbestblockhash(), best_block)
95+
assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash())
96+
assert_equal(self.nodes[0].getblock(self.nodes[0].getproposal(proposalHash0)["blockHash"]), self.nodes[1].getblock(self.nodes[1].getproposal(proposalHash0)["blockHash"]))
97+
assert_equal(self.nodes[0].getproposal(proposalHash0), self.nodes[1].getproposal(proposalHash0))
98+
99+
# End cycle 2
100+
slow_gen(self.nodes[1], 1)
101+
end_cycle(self.nodes[1])
102+
sync_blocks(self.nodes)
103+
104+
# Create Payment Request
105+
paymentAmount = 5000
106+
107+
letters = string.ascii_lowercase + string.ascii_uppercase + "0123456789"
108+
paymentId = "".join(random.choice(letters) for i in range(32))
109+
110+
preqHash = self.nodes[0].createpaymentrequest(proposalHash0, paymentAmount, paymentId)["hash"]
111+
slow_gen(self.nodes[0], 1)
112+
sync_blocks(self.nodes)
113+
114+
# Vote Yes
115+
self.nodes[1].paymentrequestvote(preqHash, "yes")
116+
self.nodes[0].paymentrequestvote(preqHash, "yes")
117+
118+
# End cycle 0
119+
slow_gen(self.nodes[1], 1)
120+
end_cycle(self.nodes[1])
121+
sync_blocks(self.nodes)
122+
123+
# End cycle 1
124+
slow_gen(self.nodes[1], 1)
125+
end_cycle(self.nodes[1])
126+
sync_blocks(self.nodes)
127+
128+
# End cycle 2
129+
slow_gen(self.nodes[1], 1)
130+
end_cycle(self.nodes[1])
131+
sync_blocks(self.nodes)
132+
133+
# Verify both nodes accpeted the payment
134+
135+
assert_equal(self.nodes[0].getpaymentrequest(preqHash)["status"], "accepted")
136+
assert_equal(self.nodes[0].getblock(self.nodes[0].getpaymentrequest(preqHash)["paidOnBlock"]), self.nodes[1].getblock(self.nodes[1].getpaymentrequest(preqHash)["paidOnBlock"]))
137+
assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash())
138+
assert_equal(self.nodes[0].getblock(self.nodes[0].getpaymentrequest(preqHash)["blockHash"]), self.nodes[1].getblock(self.nodes[1].getpaymentrequest(preqHash)["blockHash"]))
139+
assert_equal(self.nodes[0].getpaymentrequest(preqHash), self.nodes[1].getpaymentrequest(preqHash))
140+
141+
# Verify the payment was actually received
142+
paidBlock = self.nodes[0].getblock(self.nodes[0].getpaymentrequest(preqHash)["paidOnBlock"])
143+
unspent = self.nodes[0].listunspent(0, 80)
144+
145+
assert_equal(unspent[0]['address'], paymentAddress)
146+
assert_equal(unspent[0]['amount'], paymentAmount)
147+
assert_equal(paidBlock['tx'][0], unspent[0]['txid'])
148+
149+
if __name__ == '__main__':
150+
CfundForkReorgProposal().main()

src/Makefile.test.include

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(RAW_TEST_FILES:.raw=.r
3636

3737

3838
NAVCOIN_TESTS =\
39+
test/cfunddb_tests.cpp \
3940
test/scriptnum10.h \
4041
test/addrman_tests.cpp \
4142
test/amount_tests.cpp \
@@ -76,8 +77,7 @@ NAVCOIN_TESTS =\
7677
test/testutil.cpp \
7778
test/testutil.h \
7879
test/timedata_tests.cpp \
79-
test/univalue_tests.cpp
80-
80+
test/univalue_tests.cpp
8181

8282
#NAVCOIN_TESTS =\
8383
# test/arith_uint256_tests.cpp \

0 commit comments

Comments
 (0)