Exemple #1
0
    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
            })
Exemple #2
0
    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)
Exemple #4
0
    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
            })
Exemple #5
0
    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')
Exemple #6
0
    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)
Exemple #11
0
    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)
Exemple #12
0
    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)
Exemple #13
0
    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)
Exemple #14
0
    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)
Exemple #16
0
    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)
Exemple #17
0
    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
Exemple #18
0
    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
            })
Exemple #21
0
    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')
Exemple #22
0
    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])
Exemple #24
0
    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)