def run_test(self): # Check finalizer can vote after restart p, v = self.nodes self.setup_stake_coins(p, v) self.generate_sync(p) self.log.info("Setup deposit") v.new_address = v.getnewaddress("", "legacy") tx = v.deposit(v.new_address, 1500) self.wait_for_transaction(tx) generate_block(p) sync_blocks([p, v]) self.log.info("Restart validator") self.restart_node(v.index) self.log.info("Leave insta justification") for _ in range(24): generate_block(p) assert_equal(p.getblockcount(), 26) assert_finalizationstate( p, { "currentEpoch": 6, "lastJustifiedEpoch": 4, "lastFinalizedEpoch": 3, "validators": 1 }) self.log.info("Check finalizer votes after restart") self.wait_for_vote_and_disconnect(finalizer=v, node=p) generate_block(p) assert_equal(p.getblockcount(), 27) assert_finalizationstate( p, { "currentEpoch": 6, "lastJustifiedEpoch": 5, "lastFinalizedEpoch": 4, "validators": 1 })
def run_test(self): self.proposer = self.nodes[0] self.finalizer = self.nodes[1] self.setup_stake_coins(self.proposer, self.finalizer) self.generate_sync(self.proposer) self.assert_finalizer_status('NOT_VALIDATING') self.log.info("Setup deposit") self.setup_deposit() disconnect_nodes(self.proposer, self.finalizer.index) self.log.info("Generate 2 epochs") assert_equal(self.proposer.getblockcount(), 25) votes = self.generate_epoch( proposer=self.proposer, finalizer=self.finalizer, count=2) assert_equal(len(votes), 2) assert_equal(self.proposer.getblockcount(), 35) assert_finalizationstate(self.proposer, {'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1}) self.log.info("Restart nodes, -reindex=1") self.assert_finalizer_status('IS_VALIDATING') self.restart_nodes(True) self.assert_finalizer_status('IS_VALIDATING') votes = self.generate_epoch( proposer=self.proposer, finalizer=self.finalizer, count=2) assert_equal(len(votes), 2) assert_equal(self.proposer.getblockcount(), 45) assert_finalizationstate(self.proposer, {'currentEpoch': 9, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 7, 'validators': 1}) self.log.info("Restart nodes, -reindex=0") self.restart_nodes(reindex=False) self.assert_finalizer_status('IS_VALIDATING') votes = self.generate_epoch( proposer=self.proposer, finalizer=self.finalizer, count=2) assert_equal(len(votes), 2) assert_equal(self.proposer.getblockcount(), 55) assert_finalizationstate(self.proposer, {'currentEpoch': 11, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1})
def test_successful_deposit(self, finalizer, proposer): payto = finalizer.getnewaddress("", "legacy") txid = finalizer.deposit(payto, 1500) deposit_tx = finalizer.gettransaction(txid) assert_equal(deposit_tx['amount'], 0) # 0 because we send the money to ourselves assert_less_than(deposit_tx['fee'], 0) # fee returned by gettransaction is negative raw_deposit_tx = finalizer.decoderawtransaction(deposit_tx['hex']) assert_equal(raw_deposit_tx['vout'][0]['value'], 1500) assert_equal(raw_deposit_tx['vout'][1]['value'], 10000 - 1500 + deposit_tx['fee']) # wait for transaction to propagate self.wait_for_transaction(txid, 10) wait_until(lambda: finalizer.getvalidatorinfo()['validator_status'] == 'WAITING_DEPOSIT_CONFIRMATION', timeout=5) # mine a block to allow the deposit to get included self.generate_sync(proposer) disconnect_nodes(finalizer, proposer.index) wait_until(lambda: finalizer.getvalidatorinfo()['validator_status'] == 'WAITING_DEPOSIT_FINALIZATION', timeout=5) # move to checkpoint proposer.generate(8) assert_equal(proposer.getblockcount(), 10) assert_finalizationstate(proposer, {'currentEpoch': 1, 'currentDynasty': 0, 'lastJustifiedEpoch': 0, 'lastFinalizedEpoch': 0, 'validators': 0}) # the finalizer will be ready to operate at currentDynasty=3 for _ in range(4): proposer.generate(10) assert_finalizationstate(proposer, {'validators': 0}) # start new dynasty proposer.generate(1) assert_equal(proposer.getblockcount(), 51) assert_finalizationstate(proposer, {'currentEpoch': 6, 'currentDynasty': 3, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 1}) connect_nodes(finalizer, proposer.index) sync_blocks([finalizer, proposer], timeout=10) wait_until(lambda: finalizer.getvalidatorinfo()['enabled'] == 1, timeout=5) assert_equal(finalizer.getvalidatorinfo()['validator_status'], 'IS_VALIDATING') # creates actual vote wait_until(lambda: len(proposer.getrawmempool()) == 1, timeout=5) txraw = proposer.getrawtransaction(proposer.getrawmempool()[0]) vote = FromHex(CTransaction(), txraw) assert_equal(vote.get_type(), TxType.VOTE)
def run_test(self): p, v = self.nodes self.setup_stake_coins(p, v) self.generate_sync(p) self.log.info("Setup deposit") setup_deposit(self, p, [v]) self.log.info("Generate few epochs") self.generate_epoch(p, v, count=2) assert_equal(p.getblockcount(), 35) assert_finalizationstate( p, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1 }) self.log.info("Restarting proposer") self.restart_node(p) # check it doesn't have peers -- i.e., loaded data from disk assert_equal(p.getpeerinfo(), []) self.log.info("Generate few epochs more") generate_block(p, count=9) assert_equal(p.getblockcount(), 44) # it is not connected to validator so that finalization shouldn't move assert_finalizationstate( p, { 'currentEpoch': 9, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1 }) # connect validator and check how it votes self.wait_for_vote_and_disconnect(v, p) generate_block(p, count=1) assert_equal(p.getblockcount(), 45) assert_finalizationstate( p, { 'currentEpoch': 9, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 5, 'validators': 1 }) self.generate_epoch(p, v, count=2) assert_equal(p.getblockcount(), 55) assert_finalizationstate( p, { 'currentEpoch': 11, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1 }) self.log.info("Restarting validator") self.restart_node(v) # check it doesn't have peers -- i.e., loaded data from disk assert_equal(v.getpeerinfo(), []) self.log.info("Generate more epochs") self.generate_epoch(p, v, count=2) assert_equal(p.getblockcount(), 65) assert_finalizationstate( p, { 'currentEpoch': 13, 'lastJustifiedEpoch': 12, 'lastFinalizedEpoch': 11, 'validators': 1 }) connect_nodes(p, v.index) sync_blocks([p, v]) self.log.info("Restart proposer from empty cache") self.stop_node(p.index) cleanup_datadir(self.options.tmpdir, p.index) initialize_datadir(self.options.tmpdir, p.index) self.start_node(p.index) assert_equal(p.getblockcount(), 0) connect_nodes(p, v.index) sync_blocks([p, v]) assert_finalizationstate( p, { 'currentEpoch': 13, 'lastJustifiedEpoch': 12, 'lastFinalizedEpoch': 11, 'validators': 1 }) self.log.info("Restart proposer") self.restart_node(p) assert_finalizationstate( p, { 'currentEpoch': 13, 'lastJustifiedEpoch': 12, 'lastFinalizedEpoch': 11, 'validators': 1 })
def test_double_votes(self): def corrupt_script(script, n_byte): script = bytearray(script) script[n_byte] = 1 if script[n_byte] == 0 else 0 return bytes(script) # initial topology where arrows denote the direction of connections # finalizer2 ← fork1 → finalizer1 # ↓ ︎ # fork2 fork1 = self.nodes[0] fork2 = self.nodes[1] fork1.importmasterkey(regtest_mnemonics[0]['mnemonics']) fork2.importmasterkey(regtest_mnemonics[1]['mnemonics']) finalizer1 = self.nodes[2] finalizer2 = self.nodes[3] connect_nodes(fork1, fork2.index) connect_nodes(fork1, finalizer1.index) connect_nodes(fork1, finalizer2.index) # leave IBD generate_block(fork1) sync_blocks([fork1, fork2, finalizer1, finalizer2]) # clone finalizer finalizer2.importmasterkey(regtest_mnemonics[2]['mnemonics']) finalizer1.importmasterkey(regtest_mnemonics[2]['mnemonics']) disconnect_nodes(fork1, finalizer2.index) addr = finalizer1.getnewaddress('', 'legacy') txid1 = finalizer1.deposit(addr, 1500) wait_until(lambda: txid1 in fork1.getrawmempool()) finalizer2.setlabel(addr, '') txid2 = finalizer2.deposit(addr, 1500) assert_equal(txid1, txid2) connect_nodes(fork1, finalizer2.index) generate_block(fork1) sync_blocks([fork1, fork2, finalizer1, finalizer2]) disconnect_nodes(fork1, finalizer1.index) disconnect_nodes(fork1, finalizer2.index) # pass instant finalization # F F F F J # e0 - e1 - e2 - e3 - e4 - e5 - e[26] fork1, fork2 generate_block(fork1, count=3 + 5 + 5 + 5 + 5 + 1) assert_equal(fork1.getblockcount(), 26) assert_finalizationstate( fork1, { 'currentEpoch': 6, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 1 }) # change topology where forks are not connected # finalizer1 → fork1 # # finalizer2 → fork2 sync_blocks([fork1, fork2]) disconnect_nodes(fork1, fork2.index) # test that same vote included on different forks # doesn't create a slash transaction # v1 # - e6[27, 28, 29, 30] fork1 # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 - e6[26] # \ v1 # - e6[27, 28, 29, 30] fork2 self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=fork1) v1 = fork1.getrawtransaction(fork1.getrawmempool()[0]) generate_block(fork1, count=4) assert_equal(fork1.getblockcount(), 30) assert_finalizationstate( fork1, { 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork2) generate_block(fork2) assert_raises_rpc_error(-27, 'transaction already in block chain', fork2.sendrawtransaction, v1) assert_equal(len(fork2.getrawmempool()), 0) generate_block(fork2, count=3) assert_equal(fork2.getblockcount(), 30) assert_finalizationstate( fork1, { 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.log.info('same vote on two forks was accepted') # test that double-vote with invalid vote signature is ignored # and doesn't cause slashing # v1 v2a # - e6 - e7[31, 32] fork1 # F F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 - e6[26] # \ v1 v2a # - e6 - e7[31, 32] fork2 generate_block(fork1) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=fork1) v2a = fork1.getrawtransaction(fork1.getrawmempool()[0]) generate_block(fork1) assert_equal(fork1.getblockcount(), 32) assert_finalizationstate( fork1, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1 }) generate_block(fork2) tx_v2a = FromHex(CTransaction(), v2a) # corrupt the 1st byte of the validators pubkey in the commit script # see schema in CScript::CommitScript tx_v2a.vout[0].scriptPubKey = corrupt_script( script=tx_v2a.vout[0].scriptPubKey, n_byte=2) assert_raises_rpc_error(-26, 'bad-vote-signature', fork2.sendrawtransaction, ToHex(tx_v2a)) assert_equal(len(fork2.getrawmempool()), 0) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork2) time.sleep( 10 ) # slash transactions are processed every 10 sec. UNIT-E TODO: remove once optimized assert_equal(len(fork2.getrawmempool()), 1) v2b = fork2.getrawtransaction(fork2.getrawmempool()[0]) tx_v2b = FromHex(CTransaction(), v2b) assert_equal(tx_v2b.get_type(), TxType.VOTE) generate_block(fork2) assert_equal(len(fork2.getrawmempool()), 0) assert_equal(fork2.getblockcount(), 32) assert_finalizationstate( fork1, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1 }) self.log.info('double-vote with invalid signature is ignored') # test that valid double-vote but corrupt withdraw address # creates slash tx it is included in the next block # v1 v2a # - e6 - e7[31, 32] fork1 # F F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 - e6[26] # \ v1 v2a s1 # - e6 - e7[31, 32, 33] fork2 # corrupt the 1st byte of the address in the scriptpubkey # but keep the correct vote signature see schema in CScript::CommitScript tx_v2a = FromHex(CTransaction(), v2a) # Remove the signature tx_v2a.vin[0].scriptSig = list(CScript(tx_v2a.vin[0].scriptSig))[1] tx_v2a.vout[0].scriptPubKey = corrupt_script( script=tx_v2a.vout[0].scriptPubKey, n_byte=42) tx_v2a = sign_transaction(finalizer2, tx_v2a) assert_raises_rpc_error(-26, 'bad-vote-invalid', fork2.sendrawtransaction, ToHex(tx_v2a)) wait_until(lambda: len(fork2.getrawmempool()) == 1, timeout=20) s1_hash = fork2.getrawmempool()[0] s1 = FromHex(CTransaction(), fork2.getrawtransaction(s1_hash)) assert_equal(s1.get_type(), TxType.SLASH) b33 = generate_block(fork2)[0] block = FromHex(CBlock(), fork2.getblock(b33, 0)) assert_equal(len(block.vtx), 2) block.vtx[1].rehash() assert_equal(block.vtx[1].hash, s1_hash) self.log.info('slash tx for double-vote was successfully created')
def run_test(self): node = self.nodes[0] self.setup_stake_coins(node) first_3_blocks = node.generate(3) # Let's lock the first 3 coinbase txs so we can used them later for block_id in first_3_blocks: node.lockunspent(False, [{ "txid": node.getblock(block_id)['tx'][0], "vout": 0 }]) # Make the first 3 coinbase mature now node.generate(102) assert_equal(node.getblockcount(), 105) assert_finalizationstate( node, { 'currentDynasty': 19, 'currentEpoch': 21, 'lastJustifiedEpoch': 20, 'lastFinalizedEpoch': 20 }) node0_address = node.getnewaddress("", "bech32") # Spend block 1/2/3's coinbase transactions # Mine a block. # Create three more transactions, spending the spends # Mine another block. # ... make sure all the transactions are confirmed # Invalidate both blocks # ... make sure all the transactions are put back in the mempool # Mine a new block # ... make sure all the transactions are confirmed again. b = [self.nodes[0].getblockhash(n) for n in range(1, 4)] coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] spends1_raw = [ create_raw_transaction(self.nodes[0], txid, node0_address, amount=PROPOSER_REWARD - Decimal('0.01')) for txid in coinbase_txids ] spends1_id = [ self.nodes[0].sendrawtransaction(tx) for tx in spends1_raw ] blocks = [] blocks.extend(node.generate(1)) spends2_raw = [ create_raw_transaction(self.nodes[0], txid, node0_address, amount=PROPOSER_REWARD - Decimal('0.02')) for txid in spends1_id ] spends2_id = [ self.nodes[0].sendrawtransaction(tx) for tx in spends2_raw ] blocks.extend(node.generate(1)) # mempool should be empty, all txns confirmed assert_equal(set(node.getrawmempool()), set()) for txid in spends1_id + spends2_id: tx = node.gettransaction(txid) assert tx["confirmations"] > 0 # Use invalidateblock to re-org back for node in self.nodes: node.invalidateblock(blocks[0]) # All txns should be back in mempool with 0 confirmations assert_equal(set(node.getrawmempool()), set(spends1_id + spends2_id)) for txid in spends1_id + spends2_id: tx = node.gettransaction(txid) assert tx["confirmations"] == 0 # Generate another block, they should all get mined node.generate(1) # mempool should be empty, all txns confirmed assert_equal(set(node.getrawmempool()), set()) for txid in spends1_id + spends2_id: tx = node.gettransaction(txid) assert tx["confirmations"] > 0
def test_fork_on_justified_epoch(self): node = self.nodes[3] fork = self.nodes[4] finalizer = self.nodes[5] self.start_node(node.index) self.start_node(fork.index) self.start_node(finalizer.index) self.setup_stake_coins(node, fork, finalizer) connect_nodes(node, fork.index) connect_nodes(node, finalizer.index) # leave IBD self.generate_sync(node, nodes=[node, fork, finalizer]) # create deposit finalizer_address = finalizer.getnewaddress('', 'legacy') finalizer.deposit(finalizer_address, 1500) wait_until(lambda: len(node.getrawmempool()) > 0, timeout=10) generate_block(node) disconnect_nodes(node, finalizer.index) # leave instant justification # F F F F J # [ e0 ] - [ e1 ] - [ e2 ] - [ e3 ] - [ e4 ] - [ e5 ] generate_block(node, count=3 + 5 + 5 + 5 + 5) sync_blocks([node, fork]) assert_equal(node.getblockcount(), 25) assert_finalizationstate(node, {'currentDynasty': 2, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 0}) # justify epoch that will be finalized # F J # ... [ e4 ] - [ e5 ] - [ e6 ] node, fork generate_block(node) vote = self.wait_for_vote_and_disconnect(finalizer=finalizer, node=node) vote_tx_id = node.decoderawtransaction(vote)['txid'] generate_block(node, count=4) sync_blocks([node, fork], timeout=10) assert_equal(node.getblockcount(), 30) assert_finalizationstate(node, {'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4}) # create fork that will be longer justified # F J # ... [ e4 ] - [ e5 ] - [ e6 ] node # | # | # .. ] - [ e7 ] - [ e8 ] fork disconnect_nodes(node, fork.index) generate_block(fork, count=5 + 5) assert_equal(fork.getblockcount(), 40) assert_finalizationstate(fork, {'currentDynasty': 4, 'currentEpoch': 8, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4}) # create longer justification # F J # ... [ e4 ] - [ e5 ] - [ e6 ] node # | # | J # .. ] - [ e7 ] - [ e8 ] - [ e9 ] fork target = fork.getbestblockhash() generate_block(fork) vtx = make_vote_tx(finalizer, finalizer_address, target, source_epoch=5, target_epoch=8, input_tx_id=vote_tx_id) fork.sendrawtransaction(vtx) generate_block(fork, count=4) assert_equal(fork.getblockcount(), 45) assert_finalizationstate(fork, {'currentDynasty': 4, 'currentEpoch': 9, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 4}) # finalize epoch=5 on node # F F J # ... [ e4 ] - [ e5 ] - [ e6 ] - [ e7 ] node # | # | J # .. ] - [ e7 ] - [ e8 ] - [ e9 ] fork generate_block(node) self.wait_for_vote_and_disconnect(finalizer=finalizer, node=node) generate_block(node) assert_equal(node.getblockcount(), 32) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # node shouldn't switch to fork as it's finalization is behind connect_nodes(node, fork.index) time.sleep(5) assert_equal(node.getblockcount(), 32) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # TODO: UNIT-E: check that slash transaction was created # related issue: #680 #652 #686 # test that node has valid state after restart self.restart_node(node.index) assert_equal(node.getblockcount(), 32) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # cleanup self.stop_node(node.index) self.stop_node(fork.index) self.stop_node(finalizer.index)
def run_test(self): def assert_vote(vote_raw_tx, input_raw_tx, source_epoch, target_epoch, target_hash): vote_tx = FromHex(CTransaction(), vote_raw_tx) assert vote_tx.is_finalizer_commit() input_tx = FromHex(CTransaction(), input_raw_tx) input_tx.rehash() prevout = "%064x" % vote_tx.vin[0].prevout.hash assert_equal(prevout, input_tx.hash) vote = self.nodes[0].extractvotefromsignature( bytes_to_hex_str(vote_tx.vin[0].scriptSig)) assert_equal(vote['source_epoch'], source_epoch) assert_equal(vote['target_epoch'], target_epoch) assert_equal(vote['target_hash'], target_hash) fork0 = self.nodes[0] fork1 = self.nodes[1] finalizer = self.nodes[2] # main finalizer that being checked finalizer2 = self.nodes[ 3] # secondary finalizer to control finalization self.setup_stake_coins(fork0, fork1, finalizer, finalizer2) connect_nodes(fork0, fork1.index) connect_nodes(fork0, finalizer.index) connect_nodes(fork0, finalizer2.index) # leave IBD generate_block(fork0) sync_blocks(self.nodes) # deposit d1_hash = finalizer.deposit(finalizer.getnewaddress('', 'legacy'), 1500) d2_hash = finalizer2.deposit(finalizer2.getnewaddress('', 'legacy'), 4000) d1 = finalizer.getrawtransaction(d1_hash) self.wait_for_transaction(d1_hash, timeout=10) self.wait_for_transaction(d2_hash, timeout=10) generate_block(fork0) disconnect_nodes(fork0, finalizer.index) disconnect_nodes(fork0, finalizer2.index) # leave instant justification # F F F F J # e0 - e1 - e2 - e3 - e4 - e5 - e6[26] generate_block(fork0, count=3 + 5 + 5 + 5 + 5 + 1) assert_equal(fork0.getblockcount(), 26) assert_finalizationstate( fork0, { 'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 2 }) # move tip to one block before checkpoint to be able to # revert checkpoint on the fork # J v0 # ... - e5 - e6[26, 27, 28, 29] fork0 # \ # - fork1 v0 = self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork0) assert_vote(vote_raw_tx=v0, input_raw_tx=d1, source_epoch=4, target_epoch=5, target_hash=fork0.getblockhash(25)) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork0) generate_block(fork0, count=3) sync_blocks([fork0, fork1], timeout=10) disconnect_nodes(fork0, fork1.index) assert_equal(fork0.getblockcount(), 29) assert_finalizationstate( fork0, { 'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 2 }) # vote v1 on target_epoch=6 target_hash=30 # J v0 v1 # ... - e5 - e6[26, 27, 28, 29, 30] - e7[31, 32] fork0 # \ # - fork1 generate_block(fork0, count=2) assert_equal(fork0.getblockcount(), 31) v1 = self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork0) assert_vote(vote_raw_tx=v1, input_raw_tx=v0, source_epoch=5, target_epoch=6, target_hash=fork0.getblockhash(30)) generate_block(fork0) connect_nodes(finalizer, fork0.index) sync_blocks([finalizer, fork0], timeout=10) disconnect_nodes(finalizer, fork0.index) assert_equal(fork0.getblockcount(), 32) assert_equal(finalizer.getblockcount(), 32) self.log.info('finalizer successfully voted on the checkpoint') # re-org last checkpoint and check that finalizer doesn't vote # J v0 v1 # ... - e5 - e6[26, 27, 28, 29, 30] - e7[31, 32] fork0 # \ # - 30] - e7[31, 32, 33] fork1 generate_block(fork1, count=4) assert_equal(fork1.getblockcount(), 33) connect_nodes(finalizer, fork1.index) sync_blocks([finalizer, fork1], timeout=10) assert_equal(finalizer.getblockcount(), 33) assert_equal(len(fork1.getrawmempool()), 0) disconnect_nodes(finalizer, fork1.index) self.log.info( 'finalizer successfully detected potential double vote and did not vote' ) # continue to new epoch and check that finalizer votes on fork1 # J v0 v1 # ... - e5 - e6[26, 27, 28, 29, 30] - e7[31, 32] fork0 # \ v2 # - 30] - e7[...] - e8[36, 37] fork1 generate_block(fork1, count=3) assert_equal(fork1.getblockcount(), 36) v2 = self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork1) assert_vote(vote_raw_tx=v2, input_raw_tx=v0, source_epoch=5, target_epoch=7, target_hash=fork1.getblockhash(35)) generate_block(fork1) assert_equal(fork1.getblockcount(), 37) # create new epoch on fork1 and check that finalizer votes # J v0 v1 # ... - e5 - e6[26, 27, 28, 29, 30] - e7[31, 32] fork0 # \ v2 v3 # - 30] - e7[...] - e8[36, 37, ...] - e9[41, 42] fork1 generate_block(fork1, count=4) assert_equal(fork1.getblockcount(), 41) v3 = self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork1) assert_vote(vote_raw_tx=v3, input_raw_tx=v2, source_epoch=5, target_epoch=8, target_hash=fork1.getblockhash(40)) generate_block(fork1) assert_equal(fork1.getblockcount(), 42) # create longer fork0 and check that after reorg finalizer doesn't vote # J v0 v1 # ... - e5 - e6[26, 27, 28, 29, 30] - e7 - e8 - e9[41,42, 43] fork0 # \ v2 v3 # - 30] - e7 - e8 - e9[41, 42] fork1 generate_block(fork0, count=11) assert_equal(fork0.getblockcount(), 43) connect_nodes(finalizer, fork0.index) sync_blocks([finalizer, fork0]) assert_equal(finalizer.getblockcount(), 43) assert_equal(len(fork0.getrawmempool()), 0) disconnect_nodes(finalizer, fork0.index) self.log.info( 'finalizer successfully detected potential two consecutive double votes and did not vote' ) # check that finalizer can vote from next epoch on fork0 # J v0 v1 v4 # ... - e5 - e6[26, 27, 28, 29, 30] - e7 - e8 - e9[...] - e10[46, 47] fork0 # \ v2 v3 # - 30] - e7 - e8 - e9[41, 42] fork1 generate_block(fork0, count=3) assert_equal(fork0.getblockcount(), 46) v4 = self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork0) assert_vote(vote_raw_tx=v4, input_raw_tx=v1, source_epoch=5, target_epoch=9, target_hash=fork0.getblockhash(45)) generate_block(fork0) assert_equal(fork0.getblockcount(), 47) # finalize epoch8 on fork1 and re-broadcast all vote txs # which must not create slash tx # J v0 v1 v4 # ... - e5 - e6[26, 27, 28, 29, 30] - e7 - e8[ ... ] - e9[...] - e10[46, 47] fork0 # \ F v2 J v3 # - 30] - e7 - e8[36, 37,...] - e9[41, 42, 43] - e10[46, 47] fork1 self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork1) generate_block(fork1) assert_equal(fork1.getblockcount(), 43) assert_finalizationstate( fork1, { 'currentDynasty': 4, 'currentEpoch': 9, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 4, 'validators': 2 }) generate_block(fork1, count=3) assert_equal(fork1.getblockcount(), 46) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork1) generate_block(fork1) assert_equal(fork1.getblockcount(), 47) assert_finalizationstate( fork1, { 'currentDynasty': 4, 'currentEpoch': 10, 'lastJustifiedEpoch': 9, 'lastFinalizedEpoch': 8, 'validators': 2 }) assert_raises_rpc_error(-26, 'bad-vote-invalid', fork1.sendrawtransaction, v1) assert_raises_rpc_error(-26, 'bad-vote-invalid', fork1.sendrawtransaction, v4) assert_equal(len(fork1.getrawmempool()), 0) assert_raises_rpc_error(-26, 'bad-vote-invalid', fork0.sendrawtransaction, v2) assert_raises_rpc_error(-26, 'bad-vote-invalid', fork0.sendrawtransaction, v3) assert_equal(len(fork0.getrawmempool()), 0) self.log.info('re-broadcasting existing votes did not create slash tx')
def test_fork_on_finalized_checkpoint(self): node = self.nodes[0] fork = self.nodes[1] finalizer1 = self.nodes[2] finalizer2 = self.nodes[3] self.start_node(node.index) self.start_node(fork.index) self.start_node(finalizer1.index) self.start_node(finalizer2.index) finalizer1.importmasterkey(regtest_mnemonics[0]['mnemonics']) finalizer2.importmasterkey(regtest_mnemonics[0]['mnemonics']) node.importmasterkey(regtest_mnemonics[1]['mnemonics']) fork.importmasterkey(regtest_mnemonics[2]['mnemonics']) connect_nodes(node, fork.index) connect_nodes(node, finalizer1.index) connect_nodes(node, finalizer2.index) # leave IBD node.generatetoaddress(1, node.getnewaddress('', 'bech32')) sync_blocks([node, fork, finalizer1]) # create deposit disconnect_nodes(node, finalizer2.index) payto = finalizer1.getnewaddress('', 'legacy') txid1 = finalizer1.deposit(payto, 1500) finalizer2.setaccount(payto, '') txid2 = finalizer2.deposit(payto, 1500) assert_equal(txid1, txid2) wait_until(lambda: len(node.getrawmempool()) > 0, timeout=10) node.generatetoaddress(1, node.getnewaddress('', 'bech32')) disconnect_nodes(node, finalizer1.index) # leave instant justification node.generatetoaddress(3 + 5 + 5 + 5 + 5, node.getnewaddress('', 'bech32')) sync_blocks([node, fork], timeout=10) assert_equal(node.getblockcount(), 25) assert_finalizationstate(node, {'currentDynasty': 2, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 0}) # create longer justified fork # [ e5 ] node # | # | J # ..] - [ e6 ] - [ e7 ] - [ e8 ] fork disconnect_nodes(node, fork.index) fork.generatetoaddress(5 + 5 + 1, fork.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork) fork.generatetoaddress(1, fork.getnewaddress('', 'bech32')) assert_equal(fork.getblockcount(), 37) assert_finalizationstate(fork, {'currentDynasty': 3, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 3}) # create finalization # J # [ e5 ] - [ e6 ] node # | # | J # ..] - [ e6 ] - [ e7 ] - [ e8 ] fork node.generatetoaddress(1, node.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node) node.generatetoaddress(4, node.getnewaddress('', 'bech32')) assert_equal(node.getblockcount(), 30) assert_finalizationstate(node, {'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4}) # F J # [ e5 ] - [ e6 ] - [ e7 ] node # | # | J # ..] - [ e6 ] - [ e7 ] - [ e8 ] fork node.generatetoaddress(1, node.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node) node.generatetoaddress(4, node.getnewaddress('', 'bech32')) assert_equal(node.getblockcount(), 35) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # test that longer justification doesn't trigger re-org before finalization connect_nodes(node, fork.index) time.sleep(5) # give enough time to decide assert_equal(node.getblockcount(), 35) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # TODO: UNIT-E: check that slash transaction was created # related issue: #680 #652 #686 # test that node has valid state after restart self.restart_node(node.index) assert_equal(node.getblockcount(), 35) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # cleanup self.stop_node(node.index) self.stop_node(fork.index) self.stop_node(finalizer1.index) self.stop_node(finalizer2.index)
def test_fork_on_justified_epoch(self): node = self.nodes[4] fork = self.nodes[5] finalizer1 = self.nodes[6] finalizer2 = self.nodes[7] self.start_node(node.index) self.start_node(fork.index) self.start_node(finalizer1.index) self.start_node(finalizer2.index) finalizer1.importmasterkey(regtest_mnemonics[0]['mnemonics']) finalizer2.importmasterkey(regtest_mnemonics[0]['mnemonics']) node.importmasterkey(regtest_mnemonics[1]['mnemonics']) fork.importmasterkey(regtest_mnemonics[2]['mnemonics']) connect_nodes(node, fork.index) connect_nodes(node, finalizer1.index) connect_nodes(node, finalizer2.index) # leave IBD node.generatetoaddress(1, node.getnewaddress('', 'bech32')) sync_blocks([node, fork, finalizer1]) # create deposit disconnect_nodes(node, finalizer2.index) payto = finalizer1.getnewaddress('', 'legacy') txid1 = finalizer1.deposit(payto, 1500) finalizer2.setaccount(payto, '') txid2 = finalizer2.deposit(payto, 1500) assert_equal(txid1, txid2) wait_until(lambda: len(node.getrawmempool()) > 0, timeout=10) node.generatetoaddress(1, node.getnewaddress('', 'bech32')) disconnect_nodes(node, finalizer1.index) # leave instant justification # F F F F J # [ e0 ] - [ e1 ] - [ e2 ] - [ e3 ] - [ e4 ] - [ e5 ] node.generatetoaddress(3 + 5 + 5 + 5 + 5, node.getnewaddress('', 'bech32')) sync_blocks([node, fork]) assert_equal(node.getblockcount(), 25) assert_finalizationstate(node, {'currentDynasty': 2, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 0}) # justify epoch that will be finalized # F J # ... [ e4 ] - [ e5 ] - [ e6 ] node, fork node.generatetoaddress(1, node.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node) node.generatetoaddress(4, node.getnewaddress('', 'bech32')) sync_blocks([node, fork], timeout=10) assert_equal(node.getblockcount(), 30) assert_finalizationstate(node, {'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4}) # create fork that will be longer justified # F J # ... [ e4 ] - [ e5 ] - [ e6 ] node # | # | # .. ] - [ e7 ] - [ e8 ] fork disconnect_nodes(node, fork.index) fork.generatetoaddress(5 + 5, fork.getnewaddress('', 'bech32')) assert_equal(fork.getblockcount(), 40) assert_finalizationstate(fork, {'currentDynasty': 4, 'currentEpoch': 8, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4}) # create longer justification # F J # ... [ e4 ] - [ e5 ] - [ e6 ] node # | # | J # .. ] - [ e7 ] - [ e8 ] - [ e9 ] fork fork.generatetoaddress(1, fork.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork) fork.generatetoaddress(4, fork.getnewaddress('', 'bech32')) assert_equal(fork.getblockcount(), 45) assert_finalizationstate(fork, {'currentDynasty': 4, 'currentEpoch': 9, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 4}) # finalize epoch=5 on node # F F J # ... [ e4 ] - [ e5 ] - [ e6 ] - [ e7 ] node # | # | J # .. ] - [ e7 ] - [ e8 ] - [ e9 ] fork node.generatetoaddress(1, node.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node) node.generatetoaddress(1, node.getnewaddress('', 'bech32')) assert_equal(node.getblockcount(), 32) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # node shouldn't switch to fork as it's finalization is behind connect_nodes(node, fork.index) time.sleep(5) assert_equal(node.getblockcount(), 32) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # TODO: UNIT-E: check that slash transaction was created # related issue: #680 #652 #686 # test that node has valid state after restart self.restart_node(node.index) assert_equal(node.getblockcount(), 32) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # cleanup self.stop_node(node.index) self.stop_node(fork.index) self.stop_node(finalizer1.index) self.stop_node(finalizer2.index)
def run_test(self): proposer = self.nodes[0] finalizer1 = self.nodes[1] finalizer2 = self.nodes[2] self.setup_stake_coins(*self.nodes) # Leave IBD proposer.generate(1) sync_blocks([proposer, finalizer1, finalizer2], timeout=10) deptx_1 = finalizer1.deposit(finalizer1.getnewaddress("", "legacy"), 1500) deptx_2 = finalizer2.deposit(finalizer2.getnewaddress("", "legacy"), 3001) # wait for deposits to propagate self.wait_for_transaction(deptx_1, 60) self.wait_for_transaction(deptx_2, 60) assert_finalizationstate(proposer, {'currentEpoch': 1, 'currentDynasty': 0, 'lastJustifiedEpoch': 0, 'lastFinalizedEpoch': 0, 'validators': 0}) disconnect_nodes(finalizer1, proposer.index) disconnect_nodes(finalizer2, proposer.index) # Generate enough blocks to advance 3 dynasties and have active finalizers proposer.generate(5 * 10) assert_equal(proposer.getblockcount(), 51) assert_finalizationstate(proposer, {'currentEpoch': 6, 'currentDynasty': 3, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 2}) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=proposer) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) # Mine the votes to avoid the next logout to conflict with a vote in the mempool proposer.generate(1) assert_equal(proposer.getblockcount(), 52) # Logout included in dynasty=3 # At dynasty=3+3=6 finalizer is still voting # At dynasty=7 finalizer doesn't vote connect_nodes(finalizer1, proposer.index) sync_blocks([finalizer1, proposer], timeout=10) logout_tx = finalizer1.logout() wait_until(lambda: logout_tx in proposer.getrawmempool(), timeout=10) disconnect_nodes(finalizer1, proposer.index) # Check that the finalizer is still voting for epoch 6 proposer.generate(9) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=proposer) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) # Check that we cannot logout again assert_raises_rpc_error(-8, 'Cannot send logout transaction.', finalizer1.logout) # Mine votes and move to checkpoint proposer.generate(9) assert_equal(proposer.getblockcount(), 70) assert_finalizationstate(proposer, {'currentEpoch': 7, 'currentDynasty': 4, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 2}) # Check that the finalizer is still voting up to dynasty=6 (including) for _ in range(2): proposer.generate(1) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=proposer) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) proposer.generate(9) assert_equal(proposer.getblockcount(), 90) assert_finalizationstate(proposer, {'currentEpoch': 9, 'currentDynasty': 6, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 7, 'validators': 2}) # finalizer1 is logged out proposer.generate(1) assert_equal(proposer.getblockcount(), 91) assert_finalizationstate(proposer, {'currentEpoch': 10, 'currentDynasty': 7, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 7, 'validators': 1}) # finalizer1 is not validating so we can keep it connected connect_nodes(finalizer1, proposer.index) sync_blocks([finalizer1, proposer], timeout=10) assert_equal(finalizer1.getvalidatorinfo()['validator_status'], 'WAITING_FOR_WITHDRAW_DELAY') assert_raises_rpc_error(-25, 'The node is not validating.', finalizer1.logout) # Check that we manage to finalize even with one finalizer self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) proposer.generate(9) sync_blocks([proposer, finalizer1], timeout=20) assert_equal(proposer.getblockcount(), 100) assert_finalizationstate(proposer, {'currentEpoch': 10, 'currentDynasty': 7, 'lastJustifiedEpoch': 9, 'lastFinalizedEpoch': 8, 'validators': 1}) # check that we cannot deposit again before we withdraw assert_equal(finalizer1.getvalidatorinfo()['validator_status'], 'WAITING_FOR_WITHDRAW_DELAY') assert_raises_rpc_error(-25, "Cannot re-deposit while waiting for withdraw.", finalizer1.deposit, finalizer1.getnewaddress("", "legacy"), 1500)
def test_justification_over_chain_work(self): """ Test that justification has priority over chain work """ def seen_block(node, blockhash): try: node.getblock(blockhash) return True except JSONRPCException: return False def connect_sync_disconnect(node1, node2, blockhash): connect_nodes(node1, node2.index) wait_until(lambda: seen_block(node1, blockhash), timeout=10) wait_until(lambda: node1.getblockcount() == node2.getblockcount(), timeout=5) assert_equal(node1.getblockhash(node1.getblockcount()), blockhash) disconnect_nodes(node1, node2.index) node0 = self.nodes[0] node1 = self.nodes[1] node2 = self.nodes[2] validator = self.nodes[3] self.setup_stake_coins(node0, node1, node2, validator) connect_nodes(node0, node1.index) connect_nodes(node0, node2.index) connect_nodes(node0, validator.index) # leave IBD generate_block(node0) sync_blocks([node0, node1, node2, validator], timeout=10) payto = validator.getnewaddress('', 'legacy') txid = validator.deposit(payto, 1500) wait_until(lambda: self.have_tx_in_mempool([node0, node1, node2], txid), timeout=10) disconnect_nodes(node0, node1.index) disconnect_nodes(node0, node2.index) disconnect_nodes(node0, validator.index) assert_equal(len(node0.getpeerinfo()), 0) # F F F # e0 - e1 - e2 - e3 - e4[16] generate_block(node0, count=15) assert_equal(node0.getblockcount(), 16) assert_finalizationstate(node0, {'currentDynasty': 2, 'currentEpoch': 4, 'lastJustifiedEpoch': 2, 'lastFinalizedEpoch': 2, 'validators': 1}) connect_nodes(node0, node1.index) connect_nodes(node0, node2.index) sync_blocks([node0, node1, node2]) disconnect_nodes(node0, node1.index) disconnect_nodes(node0, node2.index) # generate fork with no commits. node0 must switch to it # 16 node1 # \ # - b17 node0, node2 b17 = generate_block(node2)[-1] connect_sync_disconnect(node0, node2, b17) assert_equal(node0.getblockcount(), 17) # generate fork with justified commits. node0 must switch to it # - 17 - b18 node0, node1 # / # 16 # \ # - b17 node2 self.wait_for_vote_and_disconnect(finalizer=validator, node=node1) b18 = generate_block(node1, count=2)[-1] connect_sync_disconnect(node0, node1, b18) assert_equal(node0.getblockcount(), 18) assert_finalizationstate(node0, {'currentDynasty': 2, 'currentEpoch': 4, 'lastJustifiedEpoch': 3, 'lastFinalizedEpoch': 3, 'validators': 1}) self.log.info('node successfully switched to longest justified fork') # generate longer but not justified fork. node0 shouldn't switch # - 17 - b18 node0, node1, node2 # / # 16 # \ # - 17 - 18 - 19 - b20 generate_block(node2, count=3)[-1] # b20 assert_equal(node2.getblockcount(), 20) assert_equal(node0.getblockcount(), 18) connect_nodes(node0, node2.index) sync_blocks([node0, node2], timeout=10) assert_equal(node0.getblockcount(), 18) assert_equal(node0.getblockhash(18), b18) assert_equal(node0.getfinalizationstate()['lastJustifiedEpoch'], 3) self.log.info('node did not switch to heaviest but less justified fork') assert_equal(node2.getblockcount(), 18) assert_equal(node2.getblockhash(18), b18) assert_equal(node2.getfinalizationstate()['lastJustifiedEpoch'], 3) self.log.info('node switched to longest justified fork with less work') self.stop_node(node0.index) self.stop_node(node1.index) self.stop_node(node2.index) self.stop_node(validator.index)
def test_heaviest_justified_epoch(self): """ Test that heaviest justified epoch wins """ fork1 = self.nodes[4] fork2 = self.nodes[5] fork3 = self.nodes[6] finalizer = self.nodes[7] self.setup_stake_coins(fork1, fork2, fork3, finalizer) connect_nodes(fork1, fork2.index) connect_nodes(fork1, fork3.index) connect_nodes(fork1, finalizer.index) # leave IBD generate_block(fork1) sync_blocks([fork1, fork2, finalizer], timeout=10) # add deposit payto = finalizer.getnewaddress('', 'legacy') txid = finalizer.deposit(payto, 1500) wait_until(lambda: self.have_tx_in_mempool([fork1, fork2], txid), timeout=10) generate_block(fork1) sync_blocks([fork1, fork2, finalizer], timeout=10) disconnect_nodes(fork1, finalizer.index) # leave instant justification # F F F # e0 - e1 - e2 - e3 - e4[16] generate_block(fork1, count=3 + 5 + 5 + 1) assert_equal(fork1.getblockcount(), 16) assert_finalizationstate(fork1, {'currentDynasty': 2, 'currentEpoch': 4, 'lastJustifiedEpoch': 2, 'lastFinalizedEpoch': 2, 'validators': 1}) # finalize epoch=3 # F # e3 - e4 fork1, fork2, fork3 self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork1) generate_block(fork1, count=4) assert_equal(fork1.getblockcount(), 20) assert_finalizationstate(fork1, {'currentDynasty': 2, 'currentEpoch': 4, 'lastJustifiedEpoch': 3, 'lastFinalizedEpoch': 3}) # create two forks at epoch=4 that use the same votes to justify epoch=3 # fork3 # F F | # e3 - e4[.., 20] - e5[21, 22] fork1 # \ # - 22, 23] fork2 sync_blocks([fork1, fork3], timeout=10) disconnect_nodes(fork1, fork3.index) generate_block(fork1) sync_blocks([fork1, fork2], timeout=10) self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork1) for fork in [fork1, fork2]: wait_until(lambda: len(fork.getrawmempool()) == 1, timeout=10) assert_equal(fork.getblockcount(), 21) assert_finalizationstate(fork, {'currentDynasty': 3, 'currentEpoch': 5, 'lastJustifiedEpoch': 3, 'lastFinalizedEpoch': 3}) disconnect_nodes(fork1, fork2.index) vote = fork1.getrawtransaction(fork1.getrawmempool()[0]) for fork in [fork1, fork2]: generate_block(fork) assert_equal(fork.getblockcount(), 22) assert_finalizationstate(fork, {'currentDynasty': 3, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 4}) b23 = generate_block(fork2)[0] # test that fork1 switches to the heaviest fork # fork3 # F F | # e3 - e4[.., 20] - e5[21, 22] # \ v # - 22, 23] fork2, fork1 connect_nodes(fork1, fork2.index) fork1.waitforblock(b23) assert_equal(fork1.getblockcount(), 23) assert_equal(fork1.getblockhash(23), b23) assert_finalizationstate(fork1, {'currentDynasty': 3, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 4}) disconnect_nodes(fork1, fork2.index) # test that fork1 switches to the heaviest fork # v # - e5[21, 22, 23, 24, 25] fork3, fork1 # F F / # e3 - e4[.., 20] - e5[21, 22] # \ v # - 22, 23] fork2 assert_equal(fork3.getblockcount(), 20) generate_block(fork3, count=4) fork3.sendrawtransaction(vote) wait_until(lambda: len(fork3.getrawmempool()) == 1, timeout=10) b25 = generate_block(fork3)[0] assert_equal(fork3.getblockcount(), 25) connect_nodes(fork1, fork3.index) fork1.waitforblock(b25) assert_equal(fork1.getblockcount(), 25) assert_equal(fork1.getblockhash(25), b25) assert_finalizationstate(fork1, {'currentDynasty': 3, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 4}) self.stop_node(fork1.index) self.stop_node(fork2.index) self.stop_node(fork3.index) self.stop_node(finalizer.index)
def run_test(self): self.setup_stake_coins(*self.nodes) assert all(n.getbalance() == 10000 for n in self.nodes) # create topology where arrows denote non-persistent connection # finalizer1 → node0 ← finalizer2 # ↑ # finalizer3 node0 = self.nodes[0] finalizer1 = self.nodes[1] finalizer2 = self.nodes[2] finalizer3 = self.nodes[3] connect_nodes(finalizer1, node0.index) connect_nodes(finalizer2, node0.index) connect_nodes(finalizer3, node0.index) # leave IBD generate_block(node0) sync_blocks(self.nodes) # leave instant finalization address1 = self.nodes[1].getnewaddress("", "legacy") address2 = self.nodes[2].getnewaddress("", "legacy") address3 = self.nodes[3].getnewaddress("", "legacy") deptx1 = self.nodes[1].deposit(address1, 1500) deptx2 = self.nodes[2].deposit(address2, 2000) deptx3 = self.nodes[3].deposit(address3, 1500) self.wait_for_transaction(deptx1, timeout=10) self.wait_for_transaction(deptx2, timeout=10) self.wait_for_transaction(deptx3, timeout=10) disconnect_nodes(finalizer1, node0.index) disconnect_nodes(finalizer2, node0.index) disconnect_nodes(finalizer3, node0.index) assert_equal(len(node0.getpeerinfo()), 0) # move tip to the height when finalizers are activated # complete epoch + 3 epochs + 1 block of new epoch generate_block(node0, count=4 + 5 + 5 + 5 + 5 + 1) assert_equal(node0.getblockcount(), 26) assert_finalizationstate(node0, {'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 3}) # test that finalizers vote after processing 1st block of new epoch self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node0) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=node0) self.wait_for_vote_and_disconnect(finalizer=finalizer3, node=node0) assert_equal(len(node0.getrawmempool()), 3) generate_block(node0, count=4) assert_equal(node0.getblockcount(), 30) assert_finalizationstate(node0, {'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 3}) self.log.info('Finalizers voted after first block of new epoch') # test that finalizers can vote on a configured epoch block number self.restart_node(finalizer1.index, ['-validating=1', '-finalizervotefromepochblocknumber=1']) self.restart_node(finalizer2.index, ['-validating=1', '-finalizervotefromepochblocknumber=2']) self.restart_node(finalizer3.index, ['-validating=1', '-finalizervotefromepochblocknumber=3']) generate_block(node0) assert_equal(node0.getblockcount(), 31) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node0) connect_nodes(finalizer2, node0.index) connect_nodes(finalizer3, node0.index) sync_blocks([finalizer2, finalizer3, node0], timeout=10) assert_equal(len(node0.getrawmempool()), 1) # no votes from finalizer2 and finalizer3 disconnect_nodes(finalizer2, node0.index) disconnect_nodes(finalizer3, node0.index) generate_block(node0) assert_equal(node0.getblockcount(), 32) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=node0) connect_nodes(finalizer3, node0.index) sync_blocks([finalizer3, node0], timeout=10) assert_equal(len(node0.getrawmempool()), 1) # no votes from finalizer3 disconnect_nodes(finalizer3, node0.index) generate_block(node0) assert_equal(node0.getblockcount(), 33) self.wait_for_vote_and_disconnect(finalizer=finalizer3, node=node0) generate_block(node0, count=2) assert_equal(node0.getblockcount(), 35) assert_finalizationstate(node0, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 3}) self.log.info('Finalizers voted on a configured block number') # test that finalizers can vote after configured epoch block number generate_block(node0, count=4) assert_equal(node0.getblockcount(), 39) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node0) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=node0) self.wait_for_vote_and_disconnect(finalizer=finalizer3, node=node0) generate_block(node0) assert_equal(node0.getblockcount(), 40) assert_finalizationstate(node0, {'currentDynasty': 5, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 6, 'validators': 3}) self.log.info('Finalizers voted after configured block number')
def test_fork_on_finalized_checkpoint(self): node = self.nodes[0] fork = self.nodes[1] finalizer = self.nodes[2] self.start_node(node.index) self.start_node(fork.index) self.start_node(finalizer.index) self.setup_stake_coins(node, fork, finalizer) connect_nodes(node, fork.index) connect_nodes(node, finalizer.index) # leave IBD self.generate_sync(node, nodes=[node, fork, finalizer]) # create deposit finalizer_address = finalizer.getnewaddress('', 'legacy') deposit_tx_id = finalizer.deposit(finalizer_address, 1500) wait_until(lambda: len(node.getrawmempool()) > 0, timeout=10) generate_block(node) disconnect_nodes(node, finalizer.index) # leave instant justification generate_block(node, count=3 + 5 + 5 + 5 + 5) sync_blocks([node, fork], timeout=10) assert_equal(node.getblockcount(), 25) assert_finalizationstate(node, {'currentDynasty': 2, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 0}) # create longer justified fork # [ e5 ] node # | # | J # ..] - [ e6 ] - [ e7 ] - [ e8 ] fork disconnect_nodes(node, fork.index) generate_block(fork, count=5 + 5) target = fork.getbestblockhash() generate_block(fork) vtx = make_vote_tx(finalizer, finalizer_address, target, source_epoch=4, target_epoch=7, input_tx_id=deposit_tx_id) fork.sendrawtransaction(vtx) generate_block(fork) assert_equal(fork.getblockcount(), 37) assert_finalizationstate(fork, {'currentDynasty': 3, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 3}) # create finalization # J # [ e5 ] - [ e6 ] node # | # | J # ..] - [ e6 ] - [ e7 ] - [ e8 ] fork generate_block(node) self.wait_for_vote_and_disconnect(finalizer=finalizer, node=node) generate_block(node, count=4) assert_equal(node.getblockcount(), 30) assert_finalizationstate(node, {'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4}) # F J # [ e5 ] - [ e6 ] - [ e7 ] node # | # | J # ..] - [ e6 ] - [ e7 ] - [ e8 ] fork generate_block(node) self.wait_for_vote_and_disconnect(finalizer=finalizer, node=node) generate_block(node, count=4) assert_equal(node.getblockcount(), 35) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # test that longer justification doesn't trigger re-org before finalization connect_nodes(node, fork.index) time.sleep(5) # give enough time to decide assert_equal(node.getblockcount(), 35) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # TODO: UNIT-E: check that slash transaction was created # related issue: #680 #652 #686 # test that node has valid state after restart self.restart_node(node.index) assert_equal(node.getblockcount(), 35) assert_finalizationstate(node, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) # cleanup self.stop_node(node.index) self.stop_node(fork.index) self.stop_node(finalizer.index)
def test_double_votes(self): fork1 = self.nodes[0] fork2 = self.nodes[1] finalizer1 = self.nodes[2] finalizer2 = self.nodes[3] self.setup_stake_coins(fork1, fork2, finalizer1) # clone finalizer1 finalizer2.importmasterkey(finalizer1.mnemonics) # leave IBD fork1.generatetoaddress(1, fork1.getnewaddress('', 'bech32')) sync_blocks([fork1, fork2, finalizer1, finalizer2]) disconnect_nodes(fork1, finalizer2.index) addr = finalizer1.getnewaddress('', 'legacy') txid1 = finalizer1.deposit(addr, 1500) wait_until(lambda: txid1 in fork1.getrawmempool()) finalizer2.setaccount(addr, '') txid2 = finalizer2.deposit(addr, 1500) assert_equal(txid1, txid2) connect_nodes(fork1, finalizer2.index) fork1.generatetoaddress(1, fork1.getnewaddress('', 'bech32')) sync_blocks([fork1, fork2, finalizer1, finalizer2]) disconnect_nodes(fork1, finalizer1.index) disconnect_nodes(fork1, finalizer2.index) # pass instant finalization # F F F F J # e0 - e1 - e2 - e3 - e4 - e5 - e[26] fork1, fork2 fork1.generatetoaddress(3 + 5 + 5 + 5 + 5 + 1, fork1.getnewaddress('', 'bech32')) assert_equal(fork1.getblockcount(), 26) assert_finalizationstate(fork1, {'currentEpoch': 6, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 1}) # change topology where forks are not connected # finalizer1 → fork1 # # finalizer2 → fork2 sync_blocks([fork1, fork2]) disconnect_nodes(fork1, fork2.index) # Create some blocks and cause finalizer to vote, then take the vote and send it to # finalizer2, when finalizer2 will vote it should not slash itself # v1 v2a # - e6 - e7[31, 32, 33] - e8[36] fork1 # F F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 - e6[26] # \ v1 v2a # - e6 - e7[31, 32] fork2 fork1.generatetoaddress(1, fork1.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=fork1) fork1.generatetoaddress(5, fork1.getnewaddress('', 'bech32')) raw_vote_1 = self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=fork1) fork1.generatetoaddress(1, fork1.getnewaddress('', 'bech32')) assert_equal(fork1.getblockcount(), 33) assert_finalizationstate(fork1, {'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1}) # We'll use a second vote to check if there is slashing when a validator tries to send a double vote after it # voted. fork1.generatetoaddress(3, fork1.getnewaddress('', 'bech32')) raw_vote_2 = self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=fork1) assert_equal(fork1.getblockcount(), 36) assert_finalizationstate(fork1, {'currentEpoch': 8, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1}) # Send the conflicting vote from the other chain to finalizer2, it should record it and slash it later assert_raises_rpc_error(-26, " bad-vote-invalid", finalizer2.sendrawtransaction, raw_vote_1) fork2.generatetoaddress(1, fork2.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork2) fork2.generatetoaddress(5, fork2.getnewaddress('', 'bech32')) assert_equal(fork2.getblockcount(), 32) assert_finalizationstate(fork2, {'currentEpoch': 7, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1}) connect_nodes(finalizer2, fork2.index) wait_until(lambda: len(finalizer2.getrawmempool()) == 1, timeout=10) sync_mempools([fork2, finalizer2]) assert_equal(len(fork2.getrawmempool()), 1) # check that a vote, and not a slash is actually in the mempool vote = fork2.decoderawtransaction(fork2.getrawtransaction(fork2.getrawmempool()[0])) assert_equal(vote['txtype'], TxType.VOTE.value) fork2.generatetoaddress(1, fork1.getnewaddress('', 'bech32')) assert_equal(len(fork2.getrawmempool()), 0) disconnect_nodes(finalizer2, fork2.index) # check if there is slashing after voting fork2.generatetoaddress(3, fork1.getnewaddress('', 'bech32')) assert_equal(fork2.getblockcount(), 36) assert_finalizationstate(fork2, {'currentEpoch': 8, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1}) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork2) assert_raises_rpc_error(-26, " bad-vote-invalid", finalizer2.sendrawtransaction, raw_vote_2) # The vote hasn't been replaces by a slash vote = finalizer2.decoderawtransaction(finalizer2.getrawtransaction(finalizer2.getrawmempool()[0])) assert_equal(vote['txtype'], TxType.VOTE.value)
def run_test(self): def verify_snapshot_result(res): if 'snapshot_hash' not in res: return False if 'valid' not in res: return False return res['valid'] is True def has_valid_snapshot_for_height(node, height): res = node.getblocksnapshot(node.getblockhash(height)) return verify_snapshot_result(res) validator = self.nodes[0] node = self.nodes[1] validator.importmasterkey(regtest_mnemonics[0]['mnemonics']) node.importmasterkey(regtest_mnemonics[1]['mnemonics']) node.generatetoaddress(1, node.getnewaddress('', 'bech32')) # IBD # test 1. node generates snapshots with the expected interval node.generatetoaddress(23, node.getnewaddress('', 'bech32')) wait_until(lambda: len(node.listsnapshots()) == 5) assert has_valid_snapshot_for_height(node, 4) assert has_valid_snapshot_for_height(node, 9) assert has_valid_snapshot_for_height(node, 14) assert has_valid_snapshot_for_height(node, 19) assert has_valid_snapshot_for_height(node, 24) # test 2. node keeps up to 5 snapshots node.generatetoaddress(5, node.getnewaddress('', 'bech32')) assert_equal(node.getblockcount(), 29) wait_until(lambda: has_valid_snapshot_for_height(node, 29), timeout=10) assert_equal(len(node.listsnapshots()), 5) assert has_valid_snapshot_for_height(node, 4) is False assert has_valid_snapshot_for_height(node, 9) assert has_valid_snapshot_for_height(node, 14) assert has_valid_snapshot_for_height(node, 19) assert has_valid_snapshot_for_height(node, 24) # disable instant justification payto = validator.getnewaddress("", "legacy") txid = validator.deposit(payto, 1500) self.wait_for_transaction(txid, 10) self.stop_node(validator.index) node.generatetoaddress(12, node.getnewaddress('', 'bech32')) assert_equal(node.getblockcount(), 41) assert_finalizationstate( node, { 'currentDynasty': 6, 'currentEpoch': 9, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 6, 'validators': 1 }) wait_until(lambda: has_valid_snapshot_for_height(node, 39), timeout=10) assert_equal(len(node.listsnapshots()), 5) assert has_valid_snapshot_for_height(node, 9) is False assert has_valid_snapshot_for_height(node, 14) is False assert node.getblocksnapshot( node.getblockhash(19))['snapshot_finalized'] assert node.getblocksnapshot( node.getblockhash(24))['snapshot_finalized'] assert node.getblocksnapshot( node.getblockhash(29))['snapshot_finalized'] assert node.getblocksnapshot( node.getblockhash(34))['snapshot_finalized'] is False assert node.getblocksnapshot( node.getblockhash(39))['snapshot_finalized'] is False # test 3. node keeps at least 2 finalized snapshots node.generatetoaddress(9, node.getnewaddress('', 'bech32')) wait_until(lambda: has_valid_snapshot_for_height(node, 49), timeout=10) assert_equal(len(node.listsnapshots()), 5) assert has_valid_snapshot_for_height(node, 19) is False assert node.getblocksnapshot( node.getblockhash(24))['snapshot_finalized'] assert node.getblocksnapshot( node.getblockhash(29))['snapshot_finalized'] assert has_valid_snapshot_for_height(node, 34) is False assert node.getblocksnapshot( node.getblockhash(39))['snapshot_finalized'] is False assert node.getblocksnapshot( node.getblockhash(44))['snapshot_finalized'] is False assert node.getblocksnapshot( node.getblockhash(49))['snapshot_finalized'] is False node.generatetoaddress(5, node.getnewaddress('', 'bech32')) wait_until(lambda: has_valid_snapshot_for_height(node, 54), timeout=10) assert_equal(len(node.listsnapshots()), 5) assert node.getblocksnapshot( node.getblockhash(24))['snapshot_finalized'] assert node.getblocksnapshot( node.getblockhash(29))['snapshot_finalized'] assert has_valid_snapshot_for_height(node, 39) is False assert node.getblocksnapshot( node.getblockhash(44))['snapshot_finalized'] is False assert node.getblocksnapshot( node.getblockhash(49))['snapshot_finalized'] is False node.generatetoaddress(5, node.getnewaddress('', 'bech32')) wait_until(lambda: has_valid_snapshot_for_height(node, 59), timeout=10) assert_equal(len(node.listsnapshots()), 5) assert node.getblocksnapshot( node.getblockhash(24))['snapshot_finalized'] assert node.getblocksnapshot( node.getblockhash(29))['snapshot_finalized'] assert has_valid_snapshot_for_height(node, 44) is False assert node.getblocksnapshot( node.getblockhash(49))['snapshot_finalized'] is False assert node.getblocksnapshot( node.getblockhash(54))['snapshot_finalized'] is False
def run_test(self): def create_justification(fork, finalizer, after_blocks): fork.generatetoaddress(after_blocks - 1, fork.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork) fork.generatetoaddress(1, fork.getnewaddress('', 'bech32')) assert_equal(len(fork.getrawmempool()), 0) def sync_node_to_fork(node, fork): connect_nodes(node, fork.index) block_hash = fork.getblockhash(fork.getblockcount()) node.waitforblock(block_hash, 5000) assert_equal(node.getblockhash(node.getblockcount()), block_hash) disconnect_nodes(node, fork.index) def wait_for_reject(p2p, err, block): wait_until(lambda: p2p.has_reject(err, block), timeout=5) # Two validators (but actually having the same key) produce parallel justifications # node must always follow the longest justified fork # finalizer1 -> fork1 # / # node # \ # finalizer2 -> fork2 node = self.nodes[0] fork1 = self.nodes[1] fork2 = self.nodes[2] finalizer1 = self.nodes[3] finalizer2 = self.nodes[4] node.importmasterkey(regtest_mnemonics[0]['mnemonics']) finalizer1.importmasterkey(regtest_mnemonics[1]['mnemonics']) finalizer2.importmasterkey(regtest_mnemonics[1]['mnemonics']) fork1.importmasterkey(regtest_mnemonics[2]['mnemonics']) fork2.importmasterkey(regtest_mnemonics[2]['mnemonics']) # create network topology connect_nodes(node, fork1.index) connect_nodes(node, fork2.index) connect_nodes(finalizer1, fork1.index) connect_nodes(finalizer2, fork2.index) # leave IBD node.generatetoaddress(2, node.getnewaddress('', 'bech32')) sync_blocks([node, fork1, fork2, finalizer1, finalizer2]) # Do not let finalizer2 to see deposit from finalizer1 disconnect_nodes(node, fork2.index) payto = finalizer1.getnewaddress('', 'legacy') txid1 = finalizer1.deposit(payto, 1500) finalizer2.setaccount(payto, '') txid2 = finalizer2.deposit(payto, 1500) if txid1 != txid2: # improve log message tx1 = FromHex(CTransaction(), finalizer1.getrawtransaction(txid1)) tx2 = FromHex(CTransaction(), finalizer2.getrawtransaction(txid2)) print(tx1) print(tx2) assert_equal(txid1, txid2) # Connect back connect_nodes(node, fork2.index) self.wait_for_transaction(txid1, timeout=150) node.generatetoaddress(1, node.getnewaddress('', 'bech32')) sync_blocks([node, fork1, fork2]) disconnect_nodes(node, fork1.index) disconnect_nodes(node, fork2.index) disconnect_nodes(finalizer1, fork1.index) disconnect_nodes(finalizer2, fork2.index) # create common 5 epochs to leave instant finalization # fork1 # F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 node # \ # fork2 node.generatetoaddress(22, node.getnewaddress('', 'bech32')) assert_equal(node.getblockcount(), 25) assert_finalizationstate( node, { 'currentDynasty': 2, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 0 }) connect_nodes(node, fork1.index) connect_nodes(node, fork2.index) sync_blocks([node, fork1, fork2]) disconnect_nodes(node, fork1.index) disconnect_nodes(node, fork2.index) # create fist justified epoch on fork1 # node must follow this fork # # - e6 fork1, node # F F F F J * / # e0 - e1 - e2 - e3 - e4 - e5 # \ # fork2 # e4 is finalized for fork1 # e5 is justified for fork1 create_justification(fork=fork1, finalizer=finalizer1, after_blocks=2) assert_equal(fork1.getblockcount(), 27) assert_finalizationstate( fork1, { 'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) sync_node_to_fork(node, fork1) assert_finalizationstate( node, { 'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.log.info('node successfully switched to the justified fork') # create longer justified epoch on fork2 # node must switch ("zig") to this fork # # - e6 fork1 # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 # \ J # - e6 - e7 - e8 fork2, node create_justification(fork=fork2, finalizer=finalizer2, after_blocks=2) assert_equal(fork2.getblockcount(), 27) assert_finalizationstate( fork2, { 'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) create_justification(fork=fork2, finalizer=finalizer2, after_blocks=10) assert_equal(fork2.getblockcount(), 37) assert_finalizationstate( fork2, { 'currentDynasty': 4, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 4, 'validators': 1 }) sync_node_to_fork(node, fork2) assert_finalizationstate( node, { 'currentDynasty': 4, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.log.info( 'node successfully switched to the longest justified fork') # create longer justified epoch on the previous fork1 # node must switch ("zag") to this fork # J # - e6 - e7 - e8 - e9 fork1, node # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 # \ J # - e6 - e7 - e8 fork2 create_justification(fork=fork1, finalizer=finalizer1, after_blocks=16) assert_equal(fork1.getblockcount(), 43) assert_finalizationstate( fork1, { 'currentDynasty': 4, 'currentEpoch': 9, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 4, 'validators': 1 }) sync_node_to_fork(node, fork1) assert_finalizationstate( node, { 'currentDynasty': 4, 'currentEpoch': 9, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.log.info( 'node successfully switched back to the longest justified fork') # test that re-org before finalization is not possible # J J* # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork1 # F F F F F J / | # e0 - e1 - e2 - e3 - e4 - e5 56] node # \ J # - e6 - e7 - e8 fork2 # e11 is not justified for node known_fork1_height = fork1.getblockcount() assert_equal(node.getblockcount(), known_fork1_height) known_fork1_hash = fork1.getblockhash(known_fork1_height) assert_equal(node.getblockhash(known_fork1_height), known_fork1_hash) create_justification(fork=fork1, finalizer=finalizer1, after_blocks=14) assert_equal(fork1.getblockcount(), 57) assert_finalizationstate( fork1, { 'currentDynasty': 4, 'currentEpoch': 12, 'lastJustifiedEpoch': 11, 'lastFinalizedEpoch': 4, 'validators': 1 }) attacker = node.add_p2p_connection(BaseNode()) network_thread_start() attacker.wait_for_verack() # send blocks without the last one that has a justified vote node_blocks = node.getblockcount() for h in range(known_fork1_height + 1, fork1.getblockcount()): block_hash = fork1.getblockhash(h) block = FromHex(CBlock(), fork1.getblock(block_hash, 0)) attacker.send_message(msg_witness_block(block)) node_blocks += 1 wait_until(lambda: node.getblockcount() == node_blocks, timeout=15) assert_equal(node.getblockcount(), 56) assert_finalizationstate( node, { 'currentDynasty': 4, 'currentEpoch': 12, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 4, 'validators': 1 }) # create finalization # J J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork1 # F F F F F J / | # e0 - e1 - e2 - e3 - e4 - e5 56] node # \ J F J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork2 create_justification(fork=fork2, finalizer=finalizer2, after_blocks=11) assert_equal(fork2.getblockcount(), 48) assert_finalizationstate( fork2, { 'currentDynasty': 4, 'currentEpoch': 10, 'lastJustifiedEpoch': 9, 'lastFinalizedEpoch': 4, 'validators': 1 }) create_justification(fork=fork2, finalizer=finalizer2, after_blocks=6) assert_equal(fork2.getblockcount(), 54) assert_finalizationstate( fork2, { 'currentDynasty': 4, 'currentEpoch': 11, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1 }) fork2.generatetoaddress(3, fork2.getnewaddress('', 'bech32')) assert_equal(fork2.getblockcount(), 57) assert_finalizationstate( fork2, { 'currentDynasty': 5, 'currentEpoch': 12, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1 }) # node follows longer finalization # J J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork1 # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 # \ J F J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork2, node tip = fork2.getblockhash(57) sync_node_to_fork(node, fork2) assert_equal(node.getblockcount(), 57) assert_finalizationstate( node, { 'currentDynasty': 5, 'currentEpoch': 12, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1 }) # send block with surrounded vote that justifies longer fork # node's view: # J J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork1 # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 # \ J F J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork2, node block_hash = fork1.getblockhash(fork1.getblockcount()) block = FromHex(CBlock(), fork1.getblock(block_hash, 0)) block.calc_sha256() attacker.send_message(msg_witness_block(block)) # node should't re-org to malicious fork wait_for_reject(attacker, b'bad-fork-before-last-finalized-epoch', block.sha256) assert_equal(node.getblockcount(), 57) assert_equal(node.getblockhash(57), tip) assert_finalizationstate( node, { 'currentDynasty': 5, 'currentEpoch': 12, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1 }) self.log.info('node did not re-org before finalization')
def run_test(self): proposer = self.nodes[0] finalizer1 = self.nodes[1] finalizer2 = self.nodes[2] self.setup_stake_coins(*self.nodes) assert_equal(finalizer1.getbalance(), Decimal('10000')) # Leave IBD generate_block(proposer) sync_blocks([proposer, finalizer1, finalizer2], timeout=10) finalizer1_address = finalizer1.getnewaddress('', 'legacy') # create deposits # F # e0 - e1 # d1 # d2 d1 = finalizer1.deposit(finalizer1_address, 1500) d2 = finalizer2.deposit(finalizer2.getnewaddress('', 'legacy'), 1500) self.wait_for_transaction(d1, timeout=10) self.wait_for_transaction(d2, timeout=10) generate_block(proposer) sync_blocks([proposer, finalizer1, finalizer2], timeout=10) disconnect_nodes(finalizer1, proposer.index) disconnect_nodes(finalizer2, proposer.index) assert_equal(proposer.getblockcount(), 2) assert_finalizationstate( proposer, { 'currentDynasty': 0, 'currentEpoch': 1, 'lastJustifiedEpoch': 0, 'lastFinalizedEpoch': 0, 'validators': 0 }) self.log.info('deposits are created') # Generate enough blocks to activate deposits # F F F F # e0 - e1 - e2 - e3 - e4[16] # d1 # d2 generate_block(proposer, count=3 + 5 + 5) assert_equal(proposer.getblockcount(), 15) assert_finalizationstate( proposer, { 'currentDynasty': 1, 'currentEpoch': 3, 'lastJustifiedEpoch': 2, 'lastFinalizedEpoch': 2, 'validators': 0 }) generate_block(proposer) assert_equal(proposer.getblockcount(), 16) assert_finalizationstate( proposer, { 'currentDynasty': 2, 'currentEpoch': 4, 'lastJustifiedEpoch': 2, 'lastFinalizedEpoch': 2, 'validators': 2 }) self.log.info('finalizers are created') # Logout finalizer1 # F F F F # e0 - e1 - e2 - e3 - e4[16, 17, 18, 19, 20] # d1 l1 # d2 self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=proposer) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) # TODO UNIT-E: logout tx can't be created if its vote is not in the block # we should check that input of logout tx is in the mempool too generate_block(proposer) connect_nodes(finalizer1, proposer.index) sync_blocks([finalizer1, proposer], timeout=10) l1 = finalizer1.logout() wait_until(lambda: l1 in proposer.getrawmempool(), timeout=10) disconnect_nodes(finalizer1, proposer.index) generate_block(proposer, count=3) assert_equal(proposer.getblockcount(), 20) assert_finalizationstate( proposer, { 'currentDynasty': 2, 'currentEpoch': 4, 'lastJustifiedEpoch': 3, 'lastFinalizedEpoch': 3, 'validators': 2 }) self.log.info('finalizer1 logged out in dynasty=2') # During LOGOUT_DYNASTY_DELAY both finalizers can vote. # Since the finalization happens at every epoch, # number of dynasties is equal to number of epochs. for _ in range(LOGOUT_DYNASTY_DELAY): generate_block(proposer) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=proposer) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) generate_block(proposer, count=4) assert_raises_rpc_error( -25, "Logout delay hasn't passed yet. Can't withdraw.", finalizer1.withdraw, finalizer1_address) assert_equal(proposer.getblockcount(), 35) assert_finalizationstate( proposer, { 'currentDynasty': 5, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 6, 'validators': 2 }) self.log.info('finalizer1 voted during logout delay successfully') # During WITHDRAW_DELAY finalizer1 can't vote and can't withdraw generate_block(proposer) assert_finalizationstate( proposer, { 'currentDynasty': 6, 'currentEpoch': 8, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 6, 'validators': 1 }) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) generate_block(proposer) assert_finalizationstate( proposer, { 'currentDynasty': 6, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 7, 'validators': 1 }) # finalizer1 can't vote so we keep it connected connect_nodes(finalizer1, proposer.index) time.sleep(2) # ensure no votes from finalizer1 assert_equal(len(proposer.getrawmempool()), 0) generate_block(proposer, count=3) assert_equal(proposer.getblockcount(), 40) assert_finalizationstate( proposer, { 'currentDynasty': 6, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 7, 'validators': 1 }) assert_equal(finalizer1.getvalidatorinfo()['validator_status'], 'WAITING_FOR_WITHDRAW_DELAY') assert_raises_rpc_error( -25, "Withdraw delay hasn't passed yet. Can't withdraw.", finalizer1.withdraw, finalizer1_address) # WITHDRAW_DELAY - 1 is because we checked the first loop manually for _ in range(WITHDRAW_EPOCH_DELAY - 1): generate_block(proposer) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) generate_block(proposer, count=4) assert_equal(proposer.getblockcount(), 95) assert_finalizationstate( proposer, { 'currentDynasty': 17, 'currentEpoch': 19, 'lastJustifiedEpoch': 18, 'lastFinalizedEpoch': 18, 'validators': 1 }) # last block that finalizer1 can't withdraw # TODO UNIT-E: allow to create a withdraw tx on checkpoint # as it will be added to the block on the next epoch only. # We have an known issue https://github.com/dtr-org/unit-e/issues/643 # that finalizer can't vote after checkpoint is processed, it looks that # finalizer can't create any finalizer commits at this point (and only at this point). assert_equal(finalizer1.getvalidatorinfo()['validator_status'], 'WAITING_FOR_WITHDRAW_DELAY') assert_raises_rpc_error( -25, "Withdraw delay hasn't passed yet. Can't withdraw.", finalizer1.withdraw, finalizer1_address) self.log.info( 'finalizer1 could not withdraw during WITHDRAW_DELAY period') # test that deposit can be withdrawn # e0 - e1 - ... - e4 - ... - e20[95, 96, 97] # d1 l1 w1 # d2 generate_block(proposer) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) assert_equal(proposer.getblockcount(), 96) assert_finalizationstate( proposer, { 'currentDynasty': 18, 'currentEpoch': 20, 'lastJustifiedEpoch': 18, 'lastFinalizedEpoch': 18, 'validators': 1 }) sync_blocks([proposer, finalizer1], timeout=10) assert_equal(finalizer1.getvalidatorinfo()['validator_status'], 'WAITING_TO_WITHDRAW') assert_equal(finalizer1.getbalance(), Decimal('9999.99993840')) w1 = finalizer1.withdraw(finalizer1_address) wait_until(lambda: w1 in proposer.getrawmempool(), timeout=10) generate_block(proposer) sync_blocks([proposer, finalizer1]) assert_equal(finalizer1.getvalidatorinfo()['validator_status'], 'NOT_VALIDATING') assert_equal(finalizer1.getbalance(), Decimal('9999.99992140')) self.log.info('finalizer1 was able to withdraw deposit at dynasty=18') # test that withdraw commit can be spent # test that deposit can be withdrawn # e0 - e1 - ... - e4 - ... - e20[95, 96, 97, 98] # d1 l1 w1 spent_w1 # d2 spent_w1_raw = finalizer1.createrawtransaction( [{ 'txid': w1, 'vout': 0 }], {finalizer1_address: Decimal('1499.999')}) spent_w1_signed = finalizer1.signrawtransactionwithwallet(spent_w1_raw) spent_w1 = finalizer1.sendrawtransaction(spent_w1_signed['hex']) self.wait_for_transaction(spent_w1, nodes=[proposer]) # mine block block_hash = generate_block(proposer)[0] assert spent_w1 in proposer.getblock(block_hash)['tx'] self.log.info('finalizer1 was able to spend withdraw commit') # Test that after withdraw the node can deposit again sync_blocks([proposer, finalizer1], timeout=10) assert_equal(proposer.getblockcount(), 98) assert_equal(finalizer1.getvalidatorinfo()['validator_status'], 'NOT_VALIDATING') deposit = finalizer1.deposit(finalizer1.getnewaddress('', 'legacy'), 1500) assert_equal(finalizer1.getvalidatorinfo()['validator_status'], 'WAITING_DEPOSIT_CONFIRMATION') self.wait_for_transaction(deposit, timeout=10, nodes=[proposer, finalizer1]) proposer.generate(1) sync_blocks([proposer, finalizer1], timeout=10) assert_equal(proposer.getblockcount(), 99) assert_equal(finalizer1.getvalidatorinfo()['validator_status'], 'WAITING_DEPOSIT_FINALIZATION') self.log.info('finalizer1 deposits again') # Test that finalizer is voting after depositing again disconnect_nodes(finalizer1, proposer.index) proposer.generate(2) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) assert_equal(proposer.getblockcount(), 101) proposer.generate(5) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=proposer) assert_equal(proposer.getblockcount(), 106) assert_finalizationstate( proposer, { 'currentDynasty': 20, 'currentEpoch': 22, 'lastJustifiedEpoch': 20, 'lastFinalizedEpoch': 20, 'validators': 2 }) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=proposer) self.log.info('finalizer1 votes again')
def run_test(self): def sync_node_to_fork(node, fork, force=False): if force: self.restart_node(node.index, cleanup=True) node.importmasterkey( regtest_mnemonics[node.index]['mnemonics']) connect_nodes(node, fork.index) block_hash = fork.getblockhash(fork.getblockcount()) node.waitforblock(block_hash, 5000) assert_equal(node.getblockhash(node.getblockcount()), block_hash) disconnect_nodes(node, fork.index) def generate_epoch_and_vote(node, finalizer, finalizer_address, prevtx): assert node.getblockcount() % 5 == 0 fs = node.getfinalizationstate() checkpoint = node.getbestblockhash() generate_block(node) vtx = make_vote_tx(finalizer, finalizer_address, checkpoint, source_epoch=fs['lastJustifiedEpoch'], target_epoch=fs['currentEpoch'], input_tx_id=prevtx) node.sendrawtransaction(vtx) generate_block(node, count=4) vtx = FromHex(CTransaction(), vtx) vtx.rehash() return vtx.hash node = self.nodes[0] fork1 = self.nodes[1] fork2 = self.nodes[2] finalizer = self.nodes[3] node.importmasterkey(regtest_mnemonics[0]['mnemonics']) fork1.importmasterkey(regtest_mnemonics[1]['mnemonics']) fork2.importmasterkey(regtest_mnemonics[2]['mnemonics']) finalizer.importmasterkey(regtest_mnemonics[3]['mnemonics']) connect_nodes(node, fork1.index) connect_nodes(node, fork2.index) connect_nodes(node, finalizer.index) # leave IBD self.generate_sync(node, 1) finalizer_address = finalizer.getnewaddress('', 'legacy') deptx = finalizer.deposit(finalizer_address, 1500) self.wait_for_transaction(deptx) # leave insta justification # - fork1 # F F F | # e0 - e1 - e2 - e3 - node # | # - fork2 generate_block(node, count=14) assert_equal(node.getblockcount(), 15) sync_blocks([node, finalizer]) assert_finalizationstate( node, { 'currentDynasty': 1, 'currentEpoch': 3, 'lastJustifiedEpoch': 2, 'lastFinalizedEpoch': 2, 'validators': 0 }) sync_blocks(self.nodes) disconnect_nodes(node, fork1.index) disconnect_nodes(node, fork2.index) disconnect_nodes(node, finalizer.index) # create first justified epoch on fork1 # J # - e4 - e5 - e6 fork1 node # F F F | # e0 - e1 - e2 - e3 - # | # - fork2 generate_block(fork1, count=5) vtx1 = generate_epoch_and_vote(fork1, finalizer, finalizer_address, deptx) generate_block(fork1, count=5) assert_equal(fork1.getblockcount(), 30) assert_finalizationstate( fork1, { 'currentDynasty': 2, 'currentEpoch': 6, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 2, 'validators': 1 }) sync_node_to_fork(node, fork1) assert_finalizationstate( node, { 'currentDynasty': 2, 'currentEpoch': 6, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 2, 'validators': 1 }) self.log.info('node successfully switched to the justified fork') # create longer justified epoch on fork2 # node must switch ("zig") to this fork # J # - e4 - e5 - e6 fork1 # F F F | # e0 - e1 - e2 - e3 - # | J # - e4 - e5 - e6 fork2 node generate_block(fork2, count=10) vtx2 = generate_epoch_and_vote(fork2, finalizer, finalizer_address, deptx) assert_equal(fork2.getblockcount(), 30) assert_finalizationstate( fork2, { 'currentDynasty': 2, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 2, 'validators': 1 }) sync_node_to_fork(node, fork2) assert_finalizationstate( node, { 'currentDynasty': 2, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 2, 'validators': 1 }) self.log.info( 'node successfully switched to the longest justified fork') # create longer justified epoch on the previous fork1 # node must switch ("zag") to this fork # J J # - e4 - e5 - e6 - e7 - e8 fork1 node # F F F | # e0 - e1 - e2 - e3 - # | J # - e4 - e5 - e6 fork2 generate_block(fork1, count=5) sync_node_to_fork(finalizer, fork1) vtx1 = generate_epoch_and_vote(fork1, finalizer, finalizer_address, vtx1) assert_equal(fork1.getblockcount(), 40) assert_finalizationstate( fork1, { 'currentDynasty': 2, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 2, 'validators': 1 }) assert_not_equal(fork1.getbestblockhash(), fork2.getbestblockhash()) sync_node_to_fork(node, fork1) assert_finalizationstate( node, { 'currentDynasty': 2, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 2, 'validators': 1 }) self.log.info( 'node successfully switched back to the longest justified fork') # UNIT-E TODO: node must follow longest finalized chain # node follows longest finalization # J J # - e4 - e5 - e6 - e7 - e8 fork1 node # F F F | # e0 - e1 - e2 - e3 - # | J F # - e4 - e5 - e6 - e7 fork2 sync_node_to_fork(finalizer, fork2, force=True) vtx2 = generate_epoch_and_vote(fork2, finalizer, finalizer_address, vtx2) assert_equal(fork2.getblockcount(), 35) assert_finalizationstate( fork2, { 'currentDynasty': 2, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 6, 'validators': 1 })
def run_test(self): self.setup_stake_coins(*self.nodes) assert all(n.getbalance() == 10000 for n in self.nodes) # create topology where arrows denote non-persistent connection # finalizer1 → node0 ← finalizer2 # ↑ # finalizer3 node0 = self.nodes[0] finalizer1 = self.nodes[1] finalizer2 = self.nodes[2] finalizer3 = self.nodes[3] connect_nodes(finalizer1, node0.index) connect_nodes(finalizer2, node0.index) connect_nodes(finalizer3, node0.index) # leave IBD generate_block(node0) sync_blocks(self.nodes) # leave instant finalization address1 = self.nodes[1].getnewaddress("", "legacy") address2 = self.nodes[2].getnewaddress("", "legacy") address3 = self.nodes[3].getnewaddress("", "legacy") deptx1 = self.nodes[1].deposit(address1, 1500) deptx2 = self.nodes[2].deposit(address2, 2000) deptx3 = self.nodes[3].deposit(address3, 1500) self.wait_for_transaction(deptx1, timeout=10) self.wait_for_transaction(deptx2, timeout=10) self.wait_for_transaction(deptx3, timeout=10) disconnect_nodes(finalizer1, node0.index) disconnect_nodes(finalizer2, node0.index) disconnect_nodes(finalizer3, node0.index) assert_equal(len(node0.getpeerinfo()), 0) # move tip to the height when finalizers are activated # complete epoch + 2 epochs + 1 block of new epoch generate_block(node0, count=4 + 5 + 5 + 1) assert_equal(node0.getblockcount(), 16) assert_finalizationstate( node0, { 'currentDynasty': 2, 'currentEpoch': 4, 'lastJustifiedEpoch': 2, 'lastFinalizedEpoch': 2, 'validators': 3 }) # test that finalizers vote after processing 1st block of new epoch self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node0) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=node0) self.wait_for_vote_and_disconnect(finalizer=finalizer3, node=node0) assert_equal(len(node0.getrawmempool()), 3) generate_block(node0, count=4) assert_equal(node0.getblockcount(), 20) assert_finalizationstate( node0, { 'currentDynasty': 2, 'currentEpoch': 4, 'lastJustifiedEpoch': 3, 'lastFinalizedEpoch': 3, 'validators': 3 }) self.log.info('Finalizers voted after first block of new epoch') # test that finalizers can vote on a configured epoch block number self.restart_node( finalizer1.index, ['-validating=1', '-finalizervotefromepochblocknumber=1']) self.restart_node( finalizer2.index, ['-validating=1', '-finalizervotefromepochblocknumber=2']) self.restart_node( finalizer3.index, ['-validating=1', '-finalizervotefromepochblocknumber=3']) generate_block(node0) assert_equal(node0.getblockcount(), 21) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node0) connect_nodes(finalizer2, node0.index) connect_nodes(finalizer3, node0.index) sync_blocks([finalizer2, finalizer3, node0], timeout=10) assert_equal(len(node0.getrawmempool()), 1) # no votes from finalizer2 and finalizer3 disconnect_nodes(finalizer2, node0.index) disconnect_nodes(finalizer3, node0.index) generate_block(node0) assert_equal(node0.getblockcount(), 22) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=node0) connect_nodes(finalizer3, node0.index) sync_blocks([finalizer3, node0], timeout=10) assert_equal(len(node0.getrawmempool()), 1) # no votes from finalizer3 disconnect_nodes(finalizer3, node0.index) generate_block(node0) assert_equal(node0.getblockcount(), 23) self.wait_for_vote_and_disconnect(finalizer=finalizer3, node=node0) generate_block(node0, count=2) assert_equal(node0.getblockcount(), 25) assert_finalizationstate( node0, { 'currentDynasty': 3, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 4, 'validators': 3 }) self.log.info('Finalizers voted on a configured block number') # test that finalizers can vote after configured epoch block number generate_block(node0, count=4) assert_equal(node0.getblockcount(), 29) prev_tx = self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=node0) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=node0) self.wait_for_vote_and_disconnect(finalizer=finalizer3, node=node0) generate_block(node0) assert_equal(node0.getblockcount(), 30) assert_finalizationstate( node0, { 'currentDynasty': 4, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 5, 'validators': 3 }) self.log.info('Finalizers voted after configured block number') generate_block(node0, count=4) prev_tx = finalizer1.decoderawtransaction(prev_tx)['txid'] # check that make_vote_tx works as expected tx = make_vote_tx(finalizer1, address1, node0.getblockhash(30), 5, 6, prev_tx) node0.sendrawtransaction(tx) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=node0) self.wait_for_vote_and_disconnect(finalizer=finalizer3, node=node0) generate_block(node0) assert_equal(node0.getblockcount(), 35) assert_finalizationstate( node0, { 'currentDynasty': 5, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 6, 'validators': 3 }) self.log.info('make_vote_tx works together with real finalizers') # test that node recognizes old and invalid votes. tx = make_vote_tx(finalizer1, address1, node0.getblockhash(30), 1, 2, prev_tx) assert_raises_rpc_error(-26, 'bad-vote-invalid', node0.sendrawtransaction, tx) tx = make_vote_tx(finalizer1, address1, node0.getblockhash(30), 2, 3, prev_tx) assert_raises_rpc_error(-26, 'bad-vote-invalid', node0.sendrawtransaction, tx) tx = make_vote_tx(finalizer1, address1, node0.getblockhash(30), 7, 9, prev_tx) assert_raises_rpc_error(-26, 'bad-vote-invalid', node0.sendrawtransaction, tx) tx = make_vote_tx(finalizer1, address1, node0.getblockhash(30), 7, 6, prev_tx) assert_raises_rpc_error(-26, 'bad-vote-invalid', node0.sendrawtransaction, tx) self.log.info('Tested outdated and invalid vote votes')
def run_test(self): p0, p1, p2, v0 = self.nodes self.setup_stake_coins(p0, p1, p2, v0) # Leave IBD self.generate_sync(p0) self.log.info("Setup deposit") setup_deposit(self, p0, [v0]) sync_blocks([p0, p1, p2, v0]) self.log.info("Setup test prerequisites") # get to up to block 49, just one before the new checkpoint generate_block(p0, count=18) assert_equal(p0.getblockcount(), 49) sync_blocks([p0, p1, p2, v0]) assert_finalizationstate(p0, { 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3 }) # disconnect p0 # v0: p1, p2 # p0: # p1: v0 # p2: v0 disconnect_nodes(p0, v0.index) disconnect_nodes(p0, p1.index) # disconnect p2 # v0: p1 # p0: # p1: v0 # p2: disconnect_nodes(p2, v0.index) # disconnect p1 # v0: # p0: # p1: # p2: disconnect_nodes(p1, v0.index) # generate long chain in p0 but don't justify it # F J # 30 .. 40 .. 89 -- p0 generate_block(p0, count=40) assert_equal(p0.getblockcount(), 89) assert_finalizationstate(p0, { 'currentEpoch': 9, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3 }) # generate short chain in p1 and justify it # on the 6th and 7th epochs sync with validator # F J # 30 .. 40 .. 49 .. .. .. .. .. .. 89 -- p0 # \ # 50 .. 60 .. 69 -- p1 # F J # get to the 6th epoch generate_block(p1, count=2) self.wait_for_vote_and_disconnect(finalizer=v0, node=p1) # get to the 7th epoch generate_block(p1, count=10) self.wait_for_vote_and_disconnect(finalizer=v0, node=p1) # generate the rest of the blocks generate_block(p1, count=8) connect_nodes(p1, v0.index) sync_blocks([p1, v0]) assert_equal(p1.getblockcount(), 69) assert_finalizationstate(p1, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5 }) # connect p2 with p0 and p1; p2 must switch to the longest justified p1 # v0: p1 # p0: p2 # p1: v0, p2 # p2: p0, p1 self.log.info("Test fresh node sync") connect_nodes(p2, p0.index) connect_nodes(p2, p1.index) sync_blocks([p1, p2]) assert_equal(p1.getblockcount(), 69) assert_equal(p2.getblockcount(), 69) assert_finalizationstate(p1, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5 }) assert_finalizationstate(p2, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5 }) # connect p0 with p1, p0 must disconnect its longest but not justified fork and choose p1 # v0: p1 # p0: p1, p2 # p1: v0, p0, p2 # p2: p0, p1 self.log.info("Test longest node reverts to justified") connect_nodes(p0, p1.index) sync_blocks([p0, p1]) # check if p0 accepted shortest in terms of blocks but longest justified chain assert_equal(p0.getblockcount(), 69) assert_equal(p1.getblockcount(), 69) assert_equal(v0.getblockcount(), 69) # generate more blocks to make sure they're processed self.log.info("Test all nodes continue to work as usual") generate_block(p0, count=30) sync_blocks([p0, p1, p2, v0]) assert_equal(p0.getblockcount(), 99) generate_block(p1, count=30) sync_blocks([p0, p1, p2, v0]) assert_equal(p1.getblockcount(), 129) generate_block(p2, count=30) sync_blocks([p0, p1, p2, v0]) assert_equal(p2.getblockcount(), 159) # disconnect all nodes # v0: # p0: # p1: # p2: self.log.info("Test nodes sync after reconnection") disconnect_nodes(v0, p1.index) disconnect_nodes(p0, p1.index) disconnect_nodes(p0, p2.index) disconnect_nodes(p1, p2.index) generate_block(p0, count=10) generate_block(p1, count=20) generate_block(p2, count=30) assert_equal(p0.getblockcount(), 169) assert_equal(p1.getblockcount(), 179) assert_equal(p2.getblockcount(), 189) # connect validator back to p1 # v0: p1 # p0: p1 # p1: v0, p0, p2 # p2: p1 connect_nodes(p1, v0.index) sync_blocks([p1, v0]) connect_nodes(p1, p0.index) connect_nodes(p1, p2.index) sync_blocks([p0, p1, p2, v0])
def run_test(self): p, v, s = self.nodes self.setup_stake_coins(p, v) self.generate_sync(p, nodes=[p, v]) self.log.info("Setup deposit") setup_deposit(self, p, [v]) disconnect_nodes(p, v.index) self.log.info("Generate few epochs") votes = self.generate_epoch(proposer=p, finalizer=v, count=2) assert (len(votes) != 0) assert_equal(p.getblockcount(), 32) assert_finalizationstate( p, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1 }) self.log.info("Connect fast-sync node") connect_nodes(s, p.index) sync_blocks([p, s]) assert_finalizationstate( s, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1 }) self.log.info("Generate next epoch") votes += self.generate_epoch(proposer=p, finalizer=v, count=1) assert_equal(p.getblockcount(), 37) assert_finalizationstate( p, { 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 6, 'validators': 1 }) sync_blocks([p, s]) assert_finalizationstate( s, { 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 6, 'validators': 1 }) self.log.info("Check slashing condition") # Create new vote with input=votes[-1] which attempts to make a double vote # To detect double vote, it's enough having two votes which are: # 1. from same validator # 2. with same source epoch # 3. with same target epoch # 4. with different target hash # So, make target hash different. vote = s.extractvotefromsignature( bytes_to_hex_str(votes[0].vin[0].scriptSig)) target_hash = list(vote['target_hash']) target_hash[0] = '0' if target_hash[0] == '1' else '1' vote['target_hash'] = "".join(target_hash) prev_tx = s.decoderawtransaction(ToHex(votes[-1])) vtx = v.createvotetransaction(vote, prev_tx['txid']) vtx = v.signrawtransactionwithwallet(vtx) vtx = FromHex(CTransaction(), vtx['hex']) assert_raises_rpc_error(-26, 'bad-vote-invalid', s.sendrawtransaction, ToHex(vtx)) wait_until(lambda: len(s.getrawmempool()) > 0, timeout=20) slash = FromHex(CTransaction(), s.getrawtransaction(s.getrawmempool()[0])) assert_equal(slash.get_type(), TxType.SLASH) self.log.info("Slashed") self.log.info("Restart fast-sync node") self.restart_node(s.index) connect_nodes(s, p.index) sync_blocks([s, p])
def test_heaviest_justified_epoch(self): """ Test that heaviest justified epoch wins """ fork1 = self.nodes[4] fork2 = self.nodes[5] fork3 = self.nodes[6] finalizer = self.nodes[7] self.setup_stake_coins(fork1, fork2, fork3, finalizer) connect_nodes(fork1, fork2.index) connect_nodes(fork1, fork3.index) connect_nodes(fork1, finalizer.index) # leave IBD fork1.generatetoaddress(1, fork1.getnewaddress('', 'bech32')) sync_blocks([fork1, fork2, finalizer], timeout=10) # add deposit payto = finalizer.getnewaddress('', 'legacy') txid = finalizer.deposit(payto, 1500) wait_until(lambda: self.have_tx_in_mempool([fork1, fork2], txid), timeout=10) fork1.generatetoaddress(1, fork1.getnewaddress('', 'bech32')) sync_blocks([fork1, fork2, finalizer], timeout=10) disconnect_nodes(fork1, finalizer.index) # leave instant justification # F F F F J # e0 - e1 - e2 - e3 - e4 - e5 - e6[26] fork1.generatetoaddress(3 + 5 + 5 + 5 + 5 + 1, fork1.getnewaddress('', 'bech32')) assert_equal(fork1.getblockcount(), 26) assert_finalizationstate(fork1, {'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 1}) # justify epoch=5 # J # e5 - e6 fork1, fork2, fork3 self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork1) fork1.generatetoaddress(4, fork1.getnewaddress('', 'bech32')) assert_equal(fork1.getblockcount(), 30) assert_finalizationstate(fork1, {'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4}) # create two forks at epoch=6 that use the same votes to justify epoch=5 # fork3 # F J | # e5 - e6[.., 30] - e7[31, 32] fork1 # \ # - 32, 33] fork2 sync_blocks([fork1, fork3], timeout=10) disconnect_nodes(fork1, fork3.index) fork1.generatetoaddress(1, fork1.getnewaddress('', 'bech32')) sync_blocks([fork1, fork2], timeout=10) self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork1) for fork in [fork1, fork2]: wait_until(lambda: len(fork.getrawmempool()) == 1, timeout=10) assert_equal(fork.getblockcount(), 31) assert_finalizationstate(fork, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4}) disconnect_nodes(fork1, fork2.index) vote = fork1.getrawtransaction(fork1.getrawmempool()[0]) for fork in [fork1, fork2]: fork.generatetoaddress(1, fork.getnewaddress('', 'bech32')) assert_equal(fork.getblockcount(), 32) assert_finalizationstate(fork, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) b33 = fork2.generatetoaddress(1, fork2.getnewaddress('', 'bech32'))[0] # test that fork1 switches to the heaviest fork # fork3 # F J | # e5 - e6[.., 30] - e7[31, 32] # \ # - 32, 33] fork2, fork1 connect_nodes(fork1, fork2.index) fork1.waitforblock(b33) assert_equal(fork1.getblockcount(), 33) assert_equal(fork1.getblockhash(33), b33) assert_finalizationstate(fork1, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) disconnect_nodes(fork1, fork2.index) # test that fork1 switches to the heaviest fork # - e7[31, 32, 33, 34, 35] fork3, fork1 # F J / # e5 - e6[.., 30] - e7[31, 32] # \ # - 32, 33] fork2 assert_equal(fork3.getblockcount(), 30) fork3.generatetoaddress(4, fork3.getnewaddress('', 'bech32')) fork3.sendrawtransaction(vote) wait_until(lambda: len(fork3.getrawmempool()) == 1, timeout=10) b35 = fork3.generatetoaddress(1, fork3.getnewaddress('', 'bech32'))[0] assert_equal(fork3.getblockcount(), 35) connect_nodes(fork1, fork3.index) fork1.waitforblock(b35) assert_equal(fork1.getblockcount(), 35) assert_equal(fork1.getblockhash(35), b35) assert_finalizationstate(fork1, {'currentDynasty': 4, 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5}) self.stop_node(fork1.index) self.stop_node(fork2.index) self.stop_node(fork3.index) self.stop_node(finalizer.index)