示例#1
0
    def run_test(self):
        '''
        Test that a backward transfer amount under its dust threshold is not accepted in the mempool.
        Since this dust threshold depends on minrelaytxfee and this is an optional zend flag, test that 
        a node with a different value set behaves correctly and the network is not affected (no forks happen)
        '''

        mark_logs("Node 1 generates 2 block",self.nodes,DEBUG_MODE)
        self.nodes[1].generate(2)
        self.sync_all()

        mark_logs("Node 0 generates {} block".format(MINIMAL_SC_HEIGHT-2),self.nodes,DEBUG_MODE)
        self.nodes[0].generate(MINIMAL_SC_HEIGHT-2)
        self.sync_all()

        #generate wCertVk and constant
        mc_test = CertTestUtils(self.options.tmpdir, self.options.srcdir)
        vk = mc_test.generate_params('sc1')
        constant = generate_random_field_element_hex()

        # create SC
        #------------------------------------------------------------------------------------------------------------
        cmd_input = {
            'version': 0,
            'toaddress': "abcd",
            'amount': 1.0,
            'wCertVk': vk,
            'withdrawalEpochLength': EPOCH_LENGTH,
            'constant': constant
        }

        mark_logs("\nNode 1 create SC", self.nodes, DEBUG_MODE)
        try:
            res = self.nodes[1].sc_create(cmd_input)
            scid = res['scid']
            pprint.pprint(res)
            self.sync_all()
        except JSONRPCException as e:
            error_string = e.error['message']
            mark_logs(error_string,self.nodes,DEBUG_MODE)
            assert_true(False)

        mark_logs("\nNode 0 generates 1 block", self.nodes, DEBUG_MODE)
        self.nodes[0].generate(1)
        self.sync_all()

        scid_swapped = str(swap_bytes(scid))
        addr_node2   = self.nodes[2].getnewaddress()

        # this amount is next to the border of the dust (54 zat, according to mintxrelayfee default value) but it is sufficiently big not to be refused
        bwt_amount = Decimal('0.00000060')
        bwt_cert = [{"address": addr_node2, "amount": bwt_amount}, {"address": addr_node2, "amount": bwt_amount}]
        bwt_amount_array = [bwt_amount, bwt_amount]
        addr_array = [addr_node2, addr_node2]
        q = 10

        bl_list = []
        # advance some epoch and send a small backward transfer via a certificate for any epoch
        for i in range(3):

            if i == 1:
                # On the second loop, connect a fourth node from scratch with a greater mintxrelayfee, which would make the
                # node mempool reject the cert, and check this option does not prevent the chain update anyway
                mark_logs("Connecting a new Node3 with a greater -mintxrelayfee", self.nodes, DEBUG_MODE)
                self.nodes.append(start_node(
                    3, self.options.tmpdir , extra_args=[
                        '-logtimemicros=1', '-debug=cert', '-debug=sc', '-debug=py', '-debug=mempool',
                        '-allowdustoutput=0', '-minrelaytxfee='+str(CUSTOM_FEE_RATE_ZEN_PER_KBYTE)]))

                connect_nodes_bi(self.nodes, 2, 3)
                self.sync_all()

            mark_logs("\nAdvance epoch...", self.nodes, DEBUG_MODE)
            self.nodes[0].generate(EPOCH_LENGTH - 1)
            self.sync_all()
            epoch_number, epoch_cum_tree_hash = get_epoch_data(scid, self.nodes[0], EPOCH_LENGTH)

            mark_logs("Node 1 sends a cert with a bwd transfers of {} coins to Node2".format(bwt_amount), self.nodes, DEBUG_MODE)
            #==============================================================
            proof = mc_test.create_test_proof(
                "sc1", scid_swapped, epoch_number, q, MBTR_SC_FEE, FT_SC_FEE, epoch_cum_tree_hash,
                constant, addr_array, bwt_amount_array)

            try:
                cert = self.nodes[1].sc_send_certificate(scid, epoch_number, q,
                    epoch_cum_tree_hash, proof, bwt_cert, FT_SC_FEE, MBTR_SC_FEE)
            except JSONRPCException as e:
                error_string = e.error['message']
                print ("Send certificate failed with reason {}".format(error_string))
                assert_true(False)

            sync_blocks(self.nodes[0:2])
            sync_mempools(self.nodes[0:2])

            mark_logs("cert = {}".format(cert), self.nodes, DEBUG_MODE)

            if i == 1:
                # check that the certificate has ben accepted by Node0 but not by Node3 wich has a greater mintxrelayfee
                mp0 = self.nodes[0].getrawmempool()
                mp3 = self.nodes[3].getrawmempool()
                assert_true(cert in mp0)
                assert_false(cert in mp3)

            mark_logs("\nNode 0 generates 1 block", self.nodes, DEBUG_MODE)
            bl_last = self.nodes[0].generate(1)[-1]
            bl_list.append(bl_last)
            self.sync_all()

        # dust amont for a cert backward transfer is 54 Zat using the 100 Zat/Kbyte default mintxrelayfee rate
        # check that no certificates with dust amount can be sent via any command type
        dust_amount = Decimal("0.00000053")
        bwt_cert = [{"address": addr_node2, "amount": dust_amount}]
        bwt_amount_array = [dust_amount]
        addr_array = [addr_node2]
        quality = 0
        proof = mc_test.create_test_proof(
            "sc1", scid_swapped, epoch_number, quality, MBTR_SC_FEE, FT_SC_FEE,
            epoch_cum_tree_hash, constant, addr_array, bwt_amount_array)

        utx, change = get_spendable(self.nodes[0], CERT_FEE)
        raw_inputs  = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
        raw_outs    = { self.nodes[0].getnewaddress() : change }
        raw_bwt_outs = {addr_node2: dust_amount}

        raw_params = {
            "scid": scid,
            "quality": quality,
            "endEpochCumScTxCommTreeRoot": epoch_cum_tree_hash,
            "scProof": proof,
            "withdrawalEpochNumber": epoch_number
        }
        raw_cert = []
        cert = []

        try:
            raw_cert    = self.nodes[0].createrawcertificate(raw_inputs, raw_outs, raw_bwt_outs, raw_params)
            signed_cert = self.nodes[0].signrawtransaction(raw_cert)
            mark_logs("Node 0 sends a raw cert with a bwd transfers of {} coins to Node2 ... expecting failure".format(dust_amount), self.nodes, DEBUG_MODE)
            cert = self.nodes[0].sendrawtransaction(signed_cert['hex'])
            assert False
        except JSONRPCException as e:
            error_string = e.error['message']
            print ("======> " + error_string)

        try:
            mark_logs("Node 0 sends a cert with a bwd transfers of {} coins to Node2 ... expecting failure".format(dust_amount), self.nodes, DEBUG_MODE)
            cert = self.nodes[0].sc_send_certificate(scid, epoch_number, q,
                epoch_cum_tree_hash, proof, bwt_cert, FT_SC_FEE, MBTR_SC_FEE)
            assert False
        except JSONRPCException as e:
            error_string = e.error['message']
            print ("======> " + error_string)

        bal = self.nodes[2].getbalance()
        utx = self.nodes[2].listunspent()
        print ("Node2 balance = {}".format(bal))
        assert_equal(bal, 2*bwt_amount)

        # the dust threshold for a bwt (54 Zat) is lower than the one for a standard output due to the replay protection
        # extension in the pub script (63 Zat). As a result we can not spend exactly one UTXOs coming from backward transfer
        # otherwise we would create a dust output.
        mark_logs("Node2 tries to spent one utxo from bwt sending {} coins to Node0 ... expecting failure".format(utx[0]['amount']), self.nodes, DEBUG_MODE)
        # try spending one utxo
        inputs  = [ {'txid' : utx[0]['txid'], 'vout' : utx[0]['vout']}]
        outputs = { self.nodes[0].getnewaddress() : utx[0]['amount'] }
        rawtx   = self.nodes[2].createrawtransaction(inputs, outputs)
        rawtx   = self.nodes[2].signrawtransaction(rawtx)

        error_string = ""
        try:
            rawtx   = self.nodes[2].sendrawtransaction(rawtx['hex'])
            assert False
        except JSONRPCException as e:
            error_string = e.error['message']
            print (error_string)

        # we can spend a pair of them instead
        mark_logs("Node2 tries to spent both utxo from bwt sending {} coins to Node0".format(bal), self.nodes, DEBUG_MODE)
        # try spending two utxos
        inputs  = [ {'txid' : utx[0]['txid'], 'vout' : utx[0]['vout']}, {'txid' : utx[1]['txid'], 'vout' : utx[1]['vout']}]
        outputs = { self.nodes[0].getnewaddress() : bal }
        rawtx   = self.nodes[2].createrawtransaction(inputs, outputs)
        rawtx   = self.nodes[2].signrawtransaction(rawtx)

        error_string = ""
        try:
            rawtx   = self.nodes[2].sendrawtransaction(rawtx['hex'])
        except JSONRPCException as e:
            error_string = e.error['message']
            print (error_string)
            assert False

        print ("tx = {}".format(rawtx))

        sync_blocks(self.nodes[0:2])
        sync_mempools(self.nodes[0:2])
        # just to be sure tx is propagated
        time.sleep(2)

        mark_logs("Node 2 generates 1 block", self.nodes, DEBUG_MODE)
        self.nodes[2].generate(1)
        self.sync_all()

        mark_logs("\nChecking persistance stopping and restarting nodes", self.nodes, DEBUG_MODE)
        stop_nodes(self.nodes)
        wait_bitcoinds()
        self.setup_network(False)
    def run_test(self):
        self.init_nodes()

        ###########################
        # TRANSPARENT TX          #
        ###########################
        print("TESTING TRANSPARENT TX")

        # Arrange
        unspent = self.nodes[0].listunspent()[0]
        inputs = [{'txid': unspent['txid'], 'vout': unspent['vout']}]
        outputs = {
            self.nodes[0].getnewaddress(): unspent['amount'] - Decimal('0.001')
        }
        raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
        signed_tx = self.nodes[0].signrawtransaction(raw_tx)

        # Act
        r = requests.post(url=BASE_URL + "tx/send",
                          json={"rawtx": signed_tx['hex']})

        # Assert
        final_raw_tx = r.json()['txid']
        assert_equal(final_raw_tx is not None, True)
        print('SUCCESS - txid: {}'.format(final_raw_tx))

        self.nodes[0].generate(1)
        self.sync_all()

        ###########################
        # CREATE SC TX            #
        ###########################
        print("TESTING CREATE SC TX")

        # Arrange

        # 1. Generate enough blocks to create a side chain
        self.nodes[1].generate(MINIMAL_SC_VERSION_HEIGHT -
                               self.nodes[0].getblockcount())
        self.sync_all()

        # 2. Define side chain values
        cert_mc_test = CertTestUtils(self.options.tmpdir, self.options.srcdir)
        csw_mc_test = CSWTestUtils(self.options.tmpdir, self.options.srcdir)
        sc_address = "0000000000000000000000000000000000000000000000000000000000000abc"
        sc_epoch_len = 123
        sc_cr_amount = Decimal('10.00000000')
        vk = cert_mc_test.generate_params("sc1")
        csw_vk = csw_mc_test.generate_params("csw1")
        constant = generate_random_field_element_hex()
        sc_cr = [{
            "epoch_length": sc_epoch_len,
            "amount": sc_cr_amount,
            "address": sc_address,
            "wCertVk": vk,
            "wCeasedVk": csw_vk,
            "constant": constant,
            "version": 1
        }]

        # 3. Prepare a UTXO to fund the side chain
        txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(),
                                           sc_cr_amount)
        self.sync_all()
        self.nodes[0].generate(1)
        self.sync_all()
        decoded_tx = self.nodes[0].decoderawtransaction(
            self.nodes[0].gettransaction(txid)['hex'])
        vout = {}
        for outpoint in decoded_tx['vout']:
            if outpoint['value'] == sc_cr_amount:
                vout = outpoint
                break

        # 4. Prepare raw transaction
        inputs = [{'txid': txid, 'vout': vout['n']}]
        raw_tx = self.nodes[0].createrawtransaction(inputs, {}, [], sc_cr, [])
        sign_raw_tx = self.nodes[0].signrawtransaction(raw_tx)

        # Act
        r = requests.post(url=BASE_URL + "tx/send",
                          json={"rawtx": sign_raw_tx['hex']})

        # Assert
        final_raw_tx = r.json()['txid']
        decoded_tx = self.nodes[0].getrawtransaction(final_raw_tx, 1)
        scid = decoded_tx['vsc_ccout'][0]['scid']

        assert_equal(final_raw_tx is not None, True)
        assert_equal(scid is not None, True)
        print('SUCCESS - scid: {}'.format(scid))

        self.nodes[0].generate(1)
        self.sync_all()

        ###########################
        # SC FORWARD TRANSFER TX  #
        ###########################
        print("TESTING SC FORWARD TRANSFER TX")

        # Arrange
        inputs = []
        sc_ft_amount = Decimal('10.00000000')
        mc_return_address = self.nodes[0].getnewaddress()
        sc_ft = [{
            "address": sc_address,
            "amount": sc_ft_amount,
            "scid": scid,
            "mcReturnAddress": mc_return_address
        }]
        raw_tx = self.nodes[0].createrawtransaction(inputs, {}, [], [], sc_ft)
        funded_tx = self.nodes[0].fundrawtransaction(raw_tx)
        sign_raw_tx = self.nodes[0].signrawtransaction(funded_tx['hex'])

        # Act
        r = requests.post(url=BASE_URL + "tx/send",
                          json={"rawtx": sign_raw_tx['hex']})

        # Assert
        final_raw_tx = r.json()['txid']
        assert_equal(final_raw_tx is not None, True)
        print('SUCCESS - txid: {}'.format(final_raw_tx))

        self.nodes[0].generate(1)
        self.sync_all()

        ###########################
        # SC CERTIFICATE TX       #
        ###########################
        print("TESTING SC CERTIFICATE TX")

        # Arrange
        self.nodes[0].generate(sc_epoch_len)
        self.sync_all()
        epoch_number, epoch_cum_tree_hash = get_epoch_data(
            scid, self.nodes[0], sc_epoch_len)
        proof = cert_mc_test.create_test_proof("sc1", swap_bytes(scid),
                                               epoch_number, 0, Decimal('0'),
                                               Decimal('0'),
                                               epoch_cum_tree_hash, constant)

        utx, change = get_spendable(self.nodes[0], CERT_FEE)
        raw_inputs = [{'txid': utx['txid'], 'vout': utx['vout']}]
        raw_outs = {self.nodes[0].getnewaddress(): change}
        raw_bwt_outs = []
        raw_params = {
            "scid": scid,
            "quality": 0,
            "endEpochCumScTxCommTreeRoot": epoch_cum_tree_hash,
            "scProof": proof,
            "withdrawalEpochNumber": epoch_number
        }
        raw_cert = self.nodes[0].createrawcertificate(raw_inputs, raw_outs,
                                                      raw_bwt_outs, raw_params)
        signed_cert = self.nodes[0].signrawtransaction(raw_cert)

        # Act
        r = requests.post(url=BASE_URL + "tx/send",
                          json={"rawtx": signed_cert['hex']})

        # Assert
        final_raw_tx = r.json()['txid']
        assert_equal(final_raw_tx is not None, True)
        print('SUCCESS - txid: {}'.format(final_raw_tx))

        self.nodes[0].generate(1)
        self.sync_all()
示例#3
0
    def run_test(self):
        '''
        This script is useful for the generation of json outputs to be stored in github:
        (https://github.com/HorizenOfficial/zen/tree/master/doc/json-examples)
        ---
        System calls to the zen-cli are used (instead for instance of json.dump()) in order to preserve the exact order 
        of the JSON keys as the one the user gets when sending rpc commands on the console.
        ---
        In order to enable the writing of the cmds output to a file please set the constant WRITE_OUTPUT_TO_FILE to 'True'
        and if needed, set the preferred path where to write to, in the constant JSON_FILES_FOLDER_PATH
        '''
        WRITE_OUTPUT_TO_FILE = False
        JSON_FILES_FOLDER_PATH = "../../doc/json-examples/"

        def _get_path_info(nodeid, fileName):
            node_data_dir = os.path.join(self.options.tmpdir,
                                         "node" + str(nodeid))
            node_conf_dir = os.path.join(node_data_dir, "zen.conf")
            file_with_path = os.path.join(JSON_FILES_FOLDER_PATH + fileName)
            return node_conf_dir, file_with_path

        def dump_json_tx(fileName, tx, nodeid=0):

            if WRITE_OUTPUT_TO_FILE == False:
                return

            node_conf_dir, file_with_path = _get_path_info(nodeid, fileName)

            hex_tx = self.nodes[nodeid].getrawtransaction(tx)
            cmd_ret = subprocess.check_output([
                os.getenv("BITCOINCLI", "zen-cli"), "-conf=" + node_conf_dir,
                "-rpcwait", "decoderawtransaction",
                str(hex_tx).rstrip()
            ])
            with open(file_with_path, 'w') as f:
                f.write(cmd_ret)

        def dump_json_block(fileName, blockhash, verbose=2, nodeid=0):
            if WRITE_OUTPUT_TO_FILE == False:
                return

            node_conf_dir, file_with_path = _get_path_info(nodeid, fileName)

            cmd_ret = subprocess.check_output([
                os.getenv("BITCOINCLI", "zen-cli"), "-conf=" + node_conf_dir,
                "-rpcwait", "getblock", blockhash,
                str(verbose)
            ])
            with open(file_with_path, 'w') as f:
                f.write(cmd_ret)

        def dump_json_getscinfo(fileName, nodeid=0):
            if WRITE_OUTPUT_TO_FILE == False:
                return

            node_conf_dir, file_with_path = _get_path_info(nodeid, fileName)

            cmd_ret = subprocess.check_output([
                os.getenv("BITCOINCLI", "zen-cli"), "-conf=" + node_conf_dir,
                "-rpcwait", "getscinfo", "*"
            ])
            with open(file_with_path, 'w') as f:
                f.write(cmd_ret)

        def dump_json_getblocktemplate(fileName, nodeid=0):
            if WRITE_OUTPUT_TO_FILE == False:
                return

            node_conf_dir, file_with_path = _get_path_info(nodeid, fileName)

            cmd_ret = subprocess.check_output([
                os.getenv("BITCOINCLI", "zen-cli"), "-conf=" + node_conf_dir,
                "-rpcwait", "getblocktemplate"
            ])
            with open(file_with_path, 'w') as f:
                f.write(cmd_ret)

        # network topology: (0)--(1)

        mark_logs("Node 1 generates 2 block", self.nodes, DEBUG_MODE)
        self.nodes[1].generate(2)
        self.sync_all()

        mark_logs("Node 0 generates {} block".format(MINIMAL_SC_HEIGHT),
                  self.nodes, DEBUG_MODE)
        self.nodes[0].generate(MINIMAL_SC_HEIGHT)
        self.sync_all()

        #generate wCertVk and constant
        certMcTest = CertTestUtils(self.options.tmpdir, self.options.srcdir)
        cswMcTest = CSWTestUtils(self.options.tmpdir, self.options.srcdir)
        vk = certMcTest.generate_params('sc1')
        cswVk = cswMcTest.generate_params("sc1")
        constant1 = generate_random_field_element_hex()

        amount = Decimal('10.0')
        fee = Decimal('0.000025')
        feCfg = []
        cmtCfg = []

        # all certs must have custom FieldElements with exactly those values as size in bits
        feCfg.append([31, 48, 16])

        # one custom bv element with:
        # - as many bits in the uncompressed form (must be divisible by 254 and 8)
        # - a compressed size that allows the usage of BIT_VECTOR_BUF
        cmtCfg.append([[254 * 4, len(BIT_VECTOR_BUF) / 2]])

        # ascii chars, just for storing a text string
        customData = "746869732069732061207465737420737472696e67"

        cmdInput = {
            'version': 0,
            'withdrawalEpochLength': EPOCH_LENGTH,
            'amount': amount,
            'fee': fee,
            'constant': constant1,
            'wCertVk': vk,
            'toaddress': "cdcd",
            'wCeasedVk': cswVk,
            'customData': customData,
            'vFieldElementCertificateFieldConfig': feCfg[0],
            'vBitVectorCertificateFieldConfig': cmtCfg[0],
            'forwardTransferScFee': Decimal('0.001'),
            'mainchainBackwardTransferScFee': Decimal('0.002'),
            'mainchainBackwardTransferRequestDataLength': 2
        }

        mark_logs(
            "\nNode 1 create SC1 with valid vFieldElementCertificateFieldConfig / vBitVectorCertificateFieldConfig pair",
            self.nodes, DEBUG_MODE)
        try:
            res = self.nodes[1].sc_create(cmdInput)
            tx = res['txid']
            scid1 = res['scid']
            scid1_swapped = str(swap_bytes(scid1))
        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs(errorString, self.nodes, DEBUG_MODE)
            assert_true(False)

        self.sync_all()
        print("tx = {}".format(tx))

        dump_json_tx('sidechain-creation-output.json', tx)

        # two more SC creations
        #-------------------------------------------------------
        vk = certMcTest.generate_params("sc2")
        constant2 = generate_random_field_element_hex()
        customData = "c0ffee"
        cswVk = ""
        feCfg.append([16])
        cmtCfg.append([])

        cmdInput = {
            'version': 0,
            'withdrawalEpochLength': EPOCH_LENGTH,
            'toaddress': "dada",
            'amount': amount,
            'wCertVk': vk,
            'customData': customData,
            'constant': constant2,
            'wCeasedVk': cswVk,
            'vFieldElementCertificateFieldConfig': feCfg[1],
            'vBitVectorCertificateFieldConfig': cmtCfg[1],
            'forwardTransferScFee': 0,
            'mainchainBackwardTransferScFee': 0,
            'mainchainBackwardTransferRequestDataLength': 1
        }

        mark_logs(
            "\nNode 1 create SC2 with valid vFieldElementCertificateFieldConfig / vBitVectorCertificateFieldConfig pair",
            self.nodes, DEBUG_MODE)
        try:
            ret = self.nodes[1].sc_create(cmdInput)
        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs(errorString, self.nodes, DEBUG_MODE)
            assert_true(False)
        self.sync_all()

        creating_tx = ret['txid']
        scid2 = ret['scid']
        scid2_swapped = str(swap_bytes(scid2))

        print("tx = {}".format(creating_tx))

        decoded_tx = self.nodes[1].getrawtransaction(creating_tx, 1)
        dec_sc_id = decoded_tx['vsc_ccout'][0]['scid']

        #-------------------------------------------------------
        vk = certMcTest.generate_params("sc3")
        constant3 = generate_random_field_element_hex()
        customData = "badc0ffee"
        feCfg.append([])
        cmtCfg.append([[254 * 8 * 4, 1967]])

        sc_cr = [{
            "version": 0,
            "epoch_length": EPOCH_LENGTH,
            "amount": amount,
            "address": "ddaa",
            "wCertVk": vk,
            "constant": constant3,
            "vFieldElementCertificateFieldConfig": feCfg[2],
            "vBitVectorCertificateFieldConfig": cmtCfg[2]
        }]

        mark_logs(
            "\nNode 0 create SC3 with valid vFieldElementCertificateFieldConfig / vBitVectorCertificateFieldConfig pair",
            self.nodes, DEBUG_MODE)
        try:
            rawtx = self.nodes[0].createrawtransaction([], {}, [], sc_cr)
            funded_tx = self.nodes[0].fundrawtransaction(rawtx)
            sigRawtx = self.nodes[0].signrawtransaction(funded_tx['hex'])
            creating_tx = self.nodes[0].sendrawtransaction(sigRawtx['hex'])
        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs(errorString, self.nodes, DEBUG_MODE)
            assert_true(False)
        self.sync_all()

        decoded_tx = self.nodes[0].getrawtransaction(creating_tx, 1)
        scid3 = decoded_tx['vsc_ccout'][0]['scid']
        print("tx = {}".format(creating_tx))

        #-------------------------------------------------------
        mark_logs("\nNode 0 generates 1 block confirming SC creations",
                  self.nodes, DEBUG_MODE)
        self.nodes[0].generate(1)
        self.sync_all()

        # send funds to SC1
        amounts = []
        fwt_amount_1 = Decimal("2.0")
        fwt_amount_2 = Decimal("10.0")
        fwt_amount_3 = Decimal("3.0")
        fwt_amount_many = fwt_amount_1 + fwt_amount_2 + fwt_amount_3

        mark_logs(
            "\nNode 0 sends 3 amounts to SC 1 (tot: " + str(fwt_amount_many) +
            ")", self.nodes, DEBUG_MODE)
        mc_return_address1 = self.nodes[0].getnewaddress()
        mc_return_address2 = self.nodes[0].getnewaddress()
        mc_return_address3 = self.nodes[0].getnewaddress()
        amounts.append({
            "toaddress": "add1",
            "amount": fwt_amount_1,
            "scid": scid1,
            "mcReturnAddress": mc_return_address1
        })
        amounts.append({
            "toaddress": "add2",
            "amount": fwt_amount_2,
            "scid": scid2,
            "mcReturnAddress": mc_return_address2
        })
        amounts.append({
            "toaddress": "add3",
            "amount": fwt_amount_3,
            "scid": scid3,
            "mcReturnAddress": mc_return_address3
        })
        tx = self.nodes[0].sc_send(amounts)
        self.sync_all()

        print("tx = {}".format(tx))
        dump_json_tx('forward-transfer-output.json', tx)

        # request some mainchain backward transfer
        mark_logs("\nNode0 creates a tx with a bwt request", self.nodes,
                  DEBUG_MODE)
        fe1 = generate_random_field_element_hex()
        fe2 = generate_random_field_element_hex()
        fe3 = generate_random_field_element_hex()
        mc_dest_addr0 = self.nodes[0].getnewaddress()
        mc_dest_addr1 = self.nodes[1].getnewaddress()
        outputs = [{
            'vScRequestData': [fe1, fe2],
            'scFee': Decimal("0.0025"),
            'scid': scid1,
            'mcDestinationAddress': mc_dest_addr0
        }, {
            'vScRequestData': [fe3],
            'scFee': Decimal("0.0026"),
            'scid': scid2,
            'mcDestinationAddress': mc_dest_addr1
        }]

        cmdParms = {"minconf": 0, "changeaddress": mc_dest_addr0}
        try:
            tx = self.nodes[0].sc_request_transfer(outputs, cmdParms)
        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs(errorString, self.nodes, DEBUG_MODE)
            assert_true(False)
        print("tx = {}".format(tx))
        dump_json_tx('mainchain-backward-transfer-request.json', tx)

        #-------------------------------------------------------
        # advance epoch
        mark_logs("\nNode 0 generates {} block".format(EPOCH_LENGTH - 1),
                  self.nodes, DEBUG_MODE)
        self.nodes[0].generate(EPOCH_LENGTH - 1)
        self.sync_all()

        epoch_number_1, epoch_cum_tree_hash_1 = get_epoch_data(
            scid1, self.nodes[0], EPOCH_LENGTH)
        mark_logs(
            "epoch_number = {}, epoch_cum_tree_hash = {}".format(
                epoch_number_1, epoch_cum_tree_hash_1), self.nodes, DEBUG_MODE)

        addr_node1a = self.nodes[1].getnewaddress()
        addr_node1b = self.nodes[1].getnewaddress()
        addr_node0 = self.nodes[1].getnewaddress()
        bwt_amount_1 = Decimal("0.2")
        bwt_amount_2 = Decimal("0.15")

        # get a UTXO
        utx, change = get_spendable(self.nodes[0], CERT_FEE)

        inputs = [{'txid': utx['txid'], 'vout': utx['vout']}]
        outputs = {addr_node0: change}
        bwt_outs = [{
            "address": addr_node1a,
            "amount": bwt_amount_1
        }, {
            "address": addr_node1b,
            "amount": bwt_amount_2
        }]
        addresses = []
        amounts = []

        # preserve order for proof validity
        for entry in bwt_outs:
            addresses.append(entry["address"])
            amounts.append(entry["amount"])

        #-------------------------------------------------------
        mark_logs(
            "\nCreate raw cert with good custom field elements for SC2...",
            self.nodes, DEBUG_MODE)
        # cfgs for SC2: [16], []
        # we must be careful with ending bits for having valid fe.
        vCfe = ["0100"]
        vCmt = []

        # serialized fe for the proof has 32 byte size
        fe1 = get_field_element_with_padding("0100", 0)

        quality = 72
        scProof3 = certMcTest.create_test_proof('sc2', scid2_swapped,
                                                epoch_number_1, quality,
                                                MBTR_SC_FEE, FT_SC_FEE,
                                                epoch_cum_tree_hash_1,
                                                constant2, addresses, amounts,
                                                [fe1])

        params = {
            'scid': scid2,
            'quality': quality,
            'endEpochCumScTxCommTreeRoot': epoch_cum_tree_hash_1,
            'scProof': scProof3,
            'withdrawalEpochNumber': epoch_number_1,
            'vFieldElementCertificateField': vCfe,
            'vBitVectorCertificateField': vCmt,
            'ftScFee': FT_SC_FEE,
            'mbtrScFee': MBTR_SC_FEE
        }

        try:
            rawcert = self.nodes[0].createrawcertificate(
                inputs, outputs, bwt_outs, params)
            signed_cert = self.nodes[0].signrawtransaction(rawcert)
            cert = self.nodes[0].sendrawtransaction(signed_cert['hex'])
        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs(
                "Send certificate failed with reason {}".format(errorString),
                self.nodes, DEBUG_MODE)
            assert (False)

        self.sync_all()
        print("cert = {}".format(cert))

        #-------------------------------------------------------
        # get another UTXO
        utx, change = get_spendable(self.nodes[0], CERT_FEE)
        inputs = [{'txid': utx['txid'], 'vout': utx['vout']}]
        outputs = {self.nodes[0].getnewaddress(): change}

        mark_logs(
            "\nCreate raw cert with good custom field elements for SC1...",
            self.nodes, DEBUG_MODE)

        # Any number ending with 0x00 is not over module for being a valid field element, therefore it is OK
        vCfe = ["ab000100", "ccccdddd0000", "0100"]
        # this is a compressed buffer which will yield a valid field element for the proof (see below)
        vCmt = [BIT_VECTOR_BUF]

        fe1 = get_field_element_with_padding("ab000100", 0)
        fe2 = get_field_element_with_padding("ccccdddd0000", 0)
        fe3 = get_field_element_with_padding("0100", 0)
        fe4 = BIT_VECTOR_FE

        quality = 18
        scProof3 = certMcTest.create_test_proof('sc1', scid1_swapped,
                                                epoch_number_1, quality,
                                                MBTR_SC_FEE, FT_SC_FEE,
                                                epoch_cum_tree_hash_1,
                                                constant1, addresses, amounts,
                                                [fe1, fe2, fe3, fe4])

        params = {
            'scid': scid1,
            'quality': quality,
            'endEpochCumScTxCommTreeRoot': epoch_cum_tree_hash_1,
            'scProof': scProof3,
            'withdrawalEpochNumber': epoch_number_1,
            'vFieldElementCertificateField': vCfe,
            'vBitVectorCertificateField': vCmt,
            'ftScFee': FT_SC_FEE,
            'mbtrScFee': MBTR_SC_FEE
        }

        try:
            rawcert = self.nodes[0].createrawcertificate(
                inputs, outputs, bwt_outs, params)
            signed_cert = self.nodes[0].signrawtransaction(rawcert)
            cert = self.nodes[0].sendrawtransaction(signed_cert['hex'])
        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs(
                "Send certificate failed with reason {}".format(errorString),
                self.nodes, DEBUG_MODE)
            assert (False)
        self.sync_all()

        print("cert = {}".format(cert))
        dump_json_tx('certificate-with-backward-transfer.json', cert)

        # add a pair of standard txes
        self.nodes[0].sendtoaddress(addr_node1a, Decimal('0.1'))
        self.nodes[1].sendtoaddress(addr_node0, Decimal('0.2'))

        dump_json_getblocktemplate('getblocktemplate.json', nodeid=0)

        bl = self.nodes[0].generate(1)[-1]
        self.sync_all()

        dump_json_block('block-with-certificates.json', bl, 1)
        dump_json_block('block-with-certificates-expanded.json', bl, 2)

        # advance one epochs for SC1 and let the others cease
        mark_logs(
            "\nLet 1 epoch pass by and send a cert for SC1 only...".format(
                EPOCH_LENGTH), self.nodes, DEBUG_MODE)

        cert, epoch_number = advance_epoch(certMcTest, self.nodes[0],
                                           self.sync_all, scid1, "sc1",
                                           constant1, EPOCH_LENGTH, 10,
                                           CERT_FEE, FT_SC_FEE, MBTR_SC_FEE,
                                           vCfe, vCmt, [fe1, fe2, fe3, fe4])

        mark_logs(
            "\n==> certificate from SC1 for epoch {} {}".format(
                epoch_number, cert), self.nodes, DEBUG_MODE)

        dump_json_getscinfo('getscinfo-output.json', nodeid=0)

        mark_logs("Let also SC1 cease... ".format(scid2), self.nodes,
                  DEBUG_MODE)

        nbl = int(EPOCH_LENGTH * 1.5)
        mark_logs("Node0 generates {} blocks".format(nbl), self.nodes,
                  DEBUG_MODE)
        # let all sidechains cease
        self.nodes[0].generate(3 * EPOCH_LENGTH)
        self.sync_all()

        mark_logs(
            "\nCreate a CSW for SC1 withdrawing coins for two different addresses... ",
            self.nodes, DEBUG_MODE)

        # CSW sender MC address
        csw_mc_address = self.nodes[0].getnewaddress()

        sc_csw_amount_0 = Decimal('2.0')
        sc_csw_amount_1 = Decimal('1.0')
        null0 = generate_random_field_element_hex()
        null1 = generate_random_field_element_hex()
        actCertData = self.nodes[0].getactivecertdatahash(
            scid1)['certDataHash']

        ceasingCumScTxCommTree = self.nodes[0].getceasingcumsccommtreehash(
            scid1)['ceasingCumScTxCommTree']

        sc_proof0 = cswMcTest.create_test_proof("sc1", sc_csw_amount_0,
                                                scid1_swapped, null0,
                                                csw_mc_address,
                                                ceasingCumScTxCommTree,
                                                actCertData, constant1)

        sc_proof1 = cswMcTest.create_test_proof("sc1", sc_csw_amount_1,
                                                scid1_swapped, null1,
                                                csw_mc_address,
                                                ceasingCumScTxCommTree,
                                                actCertData, constant1)

        sc_csws = [{
            "amount": sc_csw_amount_0,
            "senderAddress": csw_mc_address,
            "scId": scid1,
            "epoch": 0,
            "nullifier": null0,
            "activeCertData": actCertData,
            "ceasingCumScTxCommTree": ceasingCumScTxCommTree,
            "scProof": sc_proof0
        }, {
            "amount": sc_csw_amount_1,
            "senderAddress": csw_mc_address,
            "scId": scid1,
            "epoch": 0,
            "nullifier": null1,
            "activeCertData": actCertData,
            "ceasingCumScTxCommTree": ceasingCumScTxCommTree,
            "scProof": sc_proof1
        }]

        # recipient MC address
        taddr_0 = self.nodes[0].getnewaddress()
        taddr_1 = self.nodes[1].getnewaddress()
        sc_csw_tx_outs = {taddr_0: sc_csw_amount_0, taddr_1: sc_csw_amount_1}

        rawtx = self.nodes[0].createrawtransaction([], sc_csw_tx_outs, sc_csws)
        funded_tx = self.nodes[0].fundrawtransaction(rawtx)
        sigRawtx = self.nodes[0].signrawtransaction(funded_tx['hex'], None,
                                                    None, "NONE")
        tx = self.nodes[0].sendrawtransaction(sigRawtx['hex'])
        mark_logs("sent csw retrieving coins on Node0 and Node1 behalf",
                  self.nodes, DEBUG_MODE)
        self.sync_all()

        print("tx = {}".format(tx))
        dump_json_tx('ceased-sidechain-withdrawal.json', tx)
示例#4
0
        self.nodes[0].generate(EPOCH_LENGTH - 1)
        self.sync_all()

        epoch_number_1, epoch_cum_tree_hash_1 = get_epoch_data(
            scid1, self.nodes[0], EPOCH_LENGTH)
        mark_logs(
            "epoch_number = {}, epoch_cum_tree_hash = {}".format(
                epoch_number_1, epoch_cum_tree_hash_1), self.nodes, DEBUG_MODE)

        #-------------------------------------------------------
        # do some negative test for having a raw cert rejected by mempool
        addr_node1 = self.nodes[1].getnewaddress()
        bwt_amount = Decimal("0.1")

        # get a UTXO
        utx, change = get_spendable(self.nodes[0], CERT_FEE)

        inputs = [{'txid': utx['txid'], 'vout': utx['vout']}]
        outputs = {self.nodes[0].getnewaddress(): change}
        bwt_outs = [{"address": addr_node1, "amount": bwt_amount}]

        # cfgs for SC2: [16], []
        mark_logs(
            "\nCreate raw cert with wrong field element for the referred SC2 (expecting failure)...",
            self.nodes, DEBUG_MODE)

        # custom fields are not consistent with cfg: should fail
        vCfe = ["abcd1234", "ccccddddeeee", "aaee"]
        vCmt = ["1111", "0660101a"]

        # this proof would be invalid but we expect an early failure
    def run_test(self):

        '''
        Test the accept and removal of certificates in the mempool, checking that the quality is correctly handled
        and reported as expected in the output of the rpc cmd getaddressmempool().
        This test can be run only if the -addressindex', '-timestampindex', '-spentindex' options are supported by the zend_oo binary
        '''

        logging.info("Generating initial blockchain")
        self.nodes[0].generate(100)
        self.sync_all()
        self.nodes[1].generate(1)
        self.sync_all()

        # reach sidechain fork
        nb = int(self.nodes[0].getblockcount())
        node2_cb_height = 341

        nb_to_gen1 = node2_cb_height - nb
        if nb_to_gen1 > 0:
            mark_logs("Node 0 generates {} block".format(nb_to_gen1), self.nodes, DEBUG_MODE)
            self.nodes[0].generate(nb_to_gen1)
            self.sync_all()

        mark_logs("Node 2 generates 1 block for coinbase to be used after 100 blocks", self.nodes, DEBUG_MODE)
        self.nodes[2].generate(1)
        self.sync_all()
        print("Chain height=", self.nodes[3].getblockcount())

        # reach sidechain fork
        nb = int(self.nodes[0].getblockcount())
        nb_to_gen = MINIMAL_SC_HEIGHT - nb -1
        if nb_to_gen > 0:
            mark_logs("Node 0 generates {} block for reaching sc fork".format(nb_to_gen), self.nodes, DEBUG_MODE)
            self.nodes[0].generate(nb_to_gen)
            self.sync_all()

        print("Chain height=", self.nodes[3].getblockcount())

        safe_guard_size = EPOCH_LENGTH//5
        if safe_guard_size < 2:
            safe_guard_size = 2

        creation_amount = Decimal("1.0")

        prev_epoch_hash = self.nodes[0].getbestblockhash()

        #generate wCertVk and constant
        mcTest = CertTestUtils(self.options.tmpdir, self.options.srcdir)
        vk = mcTest.generate_params("sc1")
        constant = generate_random_field_element_hex()

        # Create a SC
        cmdInput = {
            'version': 0,
            'withdrawalEpochLength': EPOCH_LENGTH,
            'toaddress': "dada",
            'amount': creation_amount,
            'wCertVk': vk,
            'constant': constant
        }

        ret = self.nodes[0].sc_create(cmdInput)
        scid = ret['scid']
        tx_cr = ret['txid']
        scid_swapped = str(swap_bytes(scid))
        mark_logs("tx={} created SC id: {}".format(tx_cr, scid), self.nodes, DEBUG_MODE)
        self.sync_all()

        mark_logs("Node0 confirms Sc creation generating 1 block", self.nodes, DEBUG_MODE)
        self.nodes[0].generate(1)
        sc_creating_height = self.nodes[0].getblockcount()
        self.sync_all()

        mark_logs("Node0 generates {} more blocks to achieve end of withdrawal epochs".format(EPOCH_LENGTH - 1), self.nodes, DEBUG_MODE)
        self.nodes[0].generate(EPOCH_LENGTH - 1)
        self.sync_all()

        mark_logs("Node0 generates 3 more blocks to give spendable funds to node2", self.nodes, DEBUG_MODE)
        bl = self.nodes[0].generate(3)
        #self.nodes[0].generate(1)
        self.sync_all()

        utxos_2 = self.nodes[2].listunspent()
        #pprint.pprint(utxos_2)
        assert_equal(len(utxos_2), 1)
        assert_equal(utxos_2[0]['confirmations'], 101)

        epoch_number, epoch_cum_tree_hash = get_epoch_data(scid, self.nodes[0], EPOCH_LENGTH)

        taddr0 = self.nodes[0].getnewaddress()
        taddr1 = self.nodes[1].getnewaddress()
        taddr2 = self.nodes[2].getnewaddress()

        node0Addr = self.nodes[0].validateaddress(taddr0)['address']
        node1Addr = self.nodes[1].validateaddress(taddr1)['address']
        node2Addr = self.nodes[2].validateaddress(taddr2)['address']

        bwt_amount0      = Decimal("0.10")
        bwt_amount1      = Decimal("0.20")
        bwt_amount2      = Decimal("0.30")

        try:
            #Create proof for WCert
            quality = 1
            amounts = [{"address": node0Addr, "amount": bwt_amount0}]
            proof = mcTest.create_test_proof("sc1", scid_swapped, epoch_number, quality,
                MBTR_SC_FEE, FT_SC_FEE, epoch_cum_tree_hash, constant, [node0Addr], [bwt_amount0])

            mark_logs("Node 0 sends a cert with a bwd transfers of {} coins to Node0 taddr {}".format(bwt_amount0, taddr0), self.nodes, DEBUG_MODE)
            cert_0_top = self.nodes[0].sc_send_certificate(scid, epoch_number, quality,
                epoch_cum_tree_hash, proof, amounts, FT_SC_FEE, MBTR_SC_FEE, CERT_FEE)
            mark_logs("==> certificate is {}".format(cert_0_top), self.nodes, DEBUG_MODE)
            self.sync_all()

            quality = 2
            amounts = [{"address": node1Addr, "amount": bwt_amount1}]
            proof = mcTest.create_test_proof("sc1", scid_swapped, epoch_number, quality,
                MBTR_SC_FEE, FT_SC_FEE, epoch_cum_tree_hash, constant, [node1Addr], [bwt_amount1])

            mark_logs("Node 1 sends a cert with a bwd transfers of {} coins to Node1 taddr {}".format(bwt_amount1, taddr1), self.nodes, DEBUG_MODE)
            cert_1_top = self.nodes[1].sc_send_certificate(scid, epoch_number, quality,
                epoch_cum_tree_hash, proof, amounts, FT_SC_FEE, MBTR_SC_FEE, CERT_FEE)
            mark_logs("==> certificate is {}".format(cert_1_top), self.nodes, DEBUG_MODE)

            self.sync_all()

            quality = 3
            amounts = [{"address": node2Addr, "amount": bwt_amount2}]
            proof = mcTest.create_test_proof("sc1", scid_swapped, epoch_number, quality,
                MBTR_SC_FEE, FT_SC_FEE, epoch_cum_tree_hash, constant, [node2Addr], [bwt_amount2])

            mark_logs("Node 2 sends a cert with a bwd transfers of {} coins to Node2 taddr {}".format(bwt_amount2, taddr2), self.nodes, DEBUG_MODE)
            cert_2_top = self.nodes[2].sc_send_certificate(scid, epoch_number, quality,
                epoch_cum_tree_hash, proof, amounts, FT_SC_FEE, MBTR_SC_FEE, CERT_FEE)
            mark_logs("==> certificate is {}".format(cert_2_top), self.nodes, DEBUG_MODE)

            self.sync_all()

            cert_hex = self.nodes[2].getrawtransaction(cert_2_top)

        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs("Send certificate failed with reason {}".format(errorString), self.nodes, DEBUG_MODE)
            assert(False)

        print("Calling getcertmaturityinfo for cert {} , it should be in mempool, non top quality".format(cert_0_top))
        ret = self.nodes[0].getcertmaturityinfo(cert_0_top)
        assert_equal(ret['blocksToMaturity'], -1)
        assert_equal(ret['certificateState'], "LOW_QUALITY_MEMPOOL")
        assert_equal(ret['maturityHeight'], -1)

        print("Calling getcertmaturityinfo for cert {} , it should be in mempool, top quality".format(cert_2_top))
        ret = self.nodes[0].getcertmaturityinfo(cert_2_top)
        assert_equal(ret['blocksToMaturity'], -1)
        assert_equal(ret['certificateState'], "TOP_QUALITY_MEMPOOL")
        assert_equal(ret['maturityHeight'], -1)

        addr_list = []
        addr_list.append(taddr0)
        addr_list.append(taddr1)
        addr_list.append(taddr2)

        args = {"addresses": addr_list} 
        ret = self.nodes[3].getaddressmempool(args)
        #pprint.pprint(ret)

        for x in ret:
            addr = x['address']
            sat = x['satoshis']
            if sat < 0:
                # skip inputs
                continue

            cert_id = x['txid']
            out_status = x['outstatus']
            if addr == taddr0:
                assert_equal(sat, to_satoshis(bwt_amount0)) 
                assert_equal(cert_id, cert_0_top)
                assert_equal(out_status, 2)
            if addr == taddr1:
                assert_equal(sat, to_satoshis(bwt_amount1)) 
                assert_equal(cert_id, cert_1_top)
                assert_equal(out_status, 2)
            if addr == taddr2:
                assert_equal(sat, to_satoshis(bwt_amount2)) 
                assert_equal(cert_id, cert_2_top)
                assert_equal(out_status, 1)

        # start first epoch + 2*epocs + safe guard
        bwtMaturityHeight = (sc_creating_height-1) + 2*EPOCH_LENGTH + safe_guard_size

        # revert last 3 blocks, thus causing the eviction of top quality cert from mempool
        # since its input, which is a coinbase, is not mature anymore
        mark_logs("Nodes revert last 3 blocks", self.nodes, DEBUG_MODE)
        for i in range(0, NUMB_OF_NODES):
            self.nodes[i].invalidateblock(bl[0])
        self.sync_all()

        # cert_2_top is not in mempool, has been removed
        for i in range(0, NUMB_OF_NODES):
            assert_equal(False, cert_2_top in self.nodes[i].getrawmempool())

        ret = self.nodes[3].getaddressmempool(args)

        for x in ret:
            addr = x['address']

            # last top quality cert has been removed from mempool address data too
            assert_false(addr == taddr2)

            sat = x['satoshis']
            if sat < 0:
                # skip inputs
                continue

            cert_id = x['txid']
            out_status = x['outstatus']
            if addr == taddr0:
                assert_equal(sat, to_satoshis(bwt_amount0)) 
                assert_equal(cert_id, cert_0_top)
                assert_equal(out_status, 2)
            if addr == taddr1:
                assert_equal(sat, to_satoshis(bwt_amount1)) 
                assert_equal(cert_id, cert_1_top)
                # last superseeded quality cert has been promoted to top
                assert_equal(out_status, 1)


        mark_logs("Node0 generates 3 more block", self.nodes, DEBUG_MODE)
        self.nodes[0].generate(3)
        self.sync_all()

        # coinbase utxo in node2 wallet is spendable again
        utxos_2 = self.nodes[2].listunspent()
        assert_equal(len(utxos_2), 1)
        assert_equal(utxos_2[0]['confirmations'], 101)

        mark_logs("Node2 resend a certificate", self.nodes, DEBUG_MODE)
        try:
            # slightly increase the fee, just for not having the same cert id hash. The reason is that an inventory already
            # known (and this would be since already broadcasted and then evicted) are not broadcasted by th p2p network
            new_cert_fee = CERT_FEE + Decimal(0.00001)
            cert_2_top_retried = self.nodes[2].sc_send_certificate(scid, epoch_number, quality,
                epoch_cum_tree_hash, proof, amounts, FT_SC_FEE, MBTR_SC_FEE, new_cert_fee)

            mark_logs("==> certificate is {}".format(cert_2_top_retried), self.nodes, DEBUG_MODE)
            assert_true(cert_2_top_retried != cert_2_top)

            self.sync_all()

        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs(errorString, self.nodes, DEBUG_MODE)
            assert(False)

        assert_true(cert_2_top_retried in self.nodes[0].getrawmempool())

        # the certificate in blockchain is still the top quality
        print("Calling getcertmaturityinfo for cert {}".format(cert_1_top))
        ret = self.nodes[3].getcertmaturityinfo(cert_1_top)
        #pprint.pprint(ret)

        nb = self.nodes[3].getblockcount()
        bl_to_mat = bwtMaturityHeight - nb 
        print("nb = {}, bwtMaturityHeight = {}".format(nb, bwtMaturityHeight))
        assert_equal(ret['blocksToMaturity'], bl_to_mat)
        assert_equal(ret['certificateState'], "IMMATURE")
        assert_equal(ret['maturityHeight'], bwtMaturityHeight)

        mark_logs("Calling getcertmaturityinfo for cert {} ...".format(cert_2_top_retried), self.nodes, DEBUG_MODE)
        try:
            ret = self.nodes[3].getcertmaturityinfo(cert_2_top_retried)
            assert_equal(ret['blocksToMaturity'], -1)
            assert_equal(ret['certificateState'], "TOP_QUALITY_MEMPOOL")
            assert_equal(ret['maturityHeight'], -1)
        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs(errorString, self.nodes, DEBUG_MODE)

        mark_logs("Calling getaddressmempool for cert {}".format(cert_2_top_retried), self.nodes, DEBUG_MODE)
        ret = self.nodes[3].getaddressmempool(args)
        #pprint.pprint(ret)

        for x in ret:
            sat = x['satoshis']
            if sat < 0:
                # skip inputs
                continue

            addr       = x['address']
            cert_id    = x['txid']
            out_status = x['outstatus']

            assert_equal(addr, taddr2)
            assert_equal(sat, to_satoshis(bwt_amount2)) 
            assert_equal(cert_id, cert_2_top_retried)
            assert_equal(out_status, 1)

        
        # create wCert proof
        mark_logs("Node 0 sends a cert with 2 outs and 2 bwts", self.nodes, DEBUG_MODE)
        quality = 10
        am_bwt1 = Decimal(0.011)
        am_bwt2 = Decimal(0.022)
        am_out  = Decimal(0.001)

        # python orders dictionaries by key, therefore we must use the same order when creating the proof
        pkh_arr = []
        am_bwt_arr = []
        raw_bwt_outs = [
            {"address": node1Addr, "amount": am_bwt1},
            {"address": node2Addr, "amount": am_bwt2}
        ]
        for entry in raw_bwt_outs:
            pkh_arr.append(entry["address"])
            am_bwt_arr.append(entry["amount"])
 
        proof = mcTest.create_test_proof(
            "sc1", scid_swapped, epoch_number, quality, MBTR_SC_FEE, FT_SC_FEE, epoch_cum_tree_hash, constant,
            pkh_arr, am_bwt_arr)
        
        utx, change = get_spendable(self.nodes[0], CERT_FEE + am_out)
        raw_inputs  = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]

        # do not use the same address, this is not supported (and python would prevent it anyway since this is a dictionary)
        taddr3 = self.nodes[3].getnewaddress()
        raw_outs    = { taddr0: change, taddr3: am_out }

        raw_params = {
            "scid": scid,
            "quality": quality,
            "endEpochCumScTxCommTreeRoot": epoch_cum_tree_hash,
            "scProof": proof,
            "withdrawalEpochNumber": epoch_number
        }
        raw_cert = []
        cert_last = []

        try:
            raw_cert    = self.nodes[0].createrawcertificate(raw_inputs, raw_outs, raw_bwt_outs, raw_params)
            signed_cert = self.nodes[0].signrawtransaction(raw_cert)
        except JSONRPCException as e:
            errorString = e.error['message']
            print("\n======> ", errorString)
            assert_true(False)

        #pprint.pprint(self.nodes[0].decoderawcertificate(signed_cert['hex']))

        try:
            cert_last = self.nodes[0].sendrawtransaction(signed_cert['hex'])
            self.sync_all()
        except JSONRPCException as e:
            errorString = e.error['message']
            print("======> ", errorString, "\n")
            assert_true(False)

        addr_list.append(taddr3)
        args = {"addresses": addr_list} 
        mark_logs("Calling getaddressmempool for cert {}".format(cert_last), self.nodes, DEBUG_MODE)
        ret = self.nodes[3].getaddressmempool(args)
        #pprint.pprint(ret)

        for x in ret:
            sat = x['satoshis']
            if sat < 0:
                # skip inputs
                continue

            addr = x['address']
            cert_id = x['txid']
            out_status = x['outstatus']

            # ordinary outputs
            if addr == taddr0:
                assert_equal(sat, to_satoshis(change)) 
                assert_equal(out_status, 0)
            if addr == taddr3:
                assert_equal(sat, to_satoshis(am_out)) 
                assert_equal(out_status, 0)

            # certificates
            if addr == taddr2:
                # two certificates refer to this address, the latest is top quality, the former has been superseeded 
                if cert_id == cert_last:
                    assert_equal(sat, to_satoshis(am_bwt2)) 
                    assert_equal(out_status, 1)
                if cert_id == cert_2_top_retried:
                    assert_equal(sat, to_satoshis(bwt_amount2)) 
                    assert_equal(out_status, 2)
            if addr == taddr1:
                assert_equal(cert_id, cert_last)
                assert_equal(sat, to_satoshis(am_bwt1)) 
                assert_equal(out_status, 1)

        # the certificate in blockchain is still the top quality
        print("Calling getcertmaturityinfo for cert {}".format(cert_1_top))
        ret = self.nodes[3].getcertmaturityinfo(cert_1_top)
        pprint.pprint(ret)

        bl = self.nodes[0].generate(1)
        self.sync_all()

        print("Calling getcertmaturityinfo for cert {}".format(cert_1_top))
        ret = self.nodes[3].getcertmaturityinfo(cert_1_top)
        pprint.pprint(ret)

        print("Calling getcertmaturityinfo for cert {}".format(cert_last))
        ret = self.nodes[3].getcertmaturityinfo(cert_last)
        pprint.pprint(ret)

        mark_logs("\nInvalidating the last block and checking RPC call results...", self.nodes, DEBUG_MODE)
        for i in range(0, NUMB_OF_NODES):
            self.nodes[i].invalidateblock(bl[0])
        self.sync_all()

        print("Calling getcertmaturityinfo for cert {} , it should be in mempool".format(cert_last))
        assert_true(cert_last in self.nodes[3].getrawmempool())
        ret = self.nodes[3].getcertmaturityinfo(cert_last)
        pprint.pprint(ret)
        assert_equal(ret['blocksToMaturity'], -1)
        assert_equal(ret['certificateState'], "TOP_QUALITY_MEMPOOL")
        assert_equal(ret['maturityHeight'], -1)

        print("Clearing the mempool of all nodes...")
        for i in range(0, NUMB_OF_NODES):
            self.nodes[i].clearmempool()
        self.sync_all()

        for i in range(0, NUMB_OF_NODES):
            assert_equal(len(self.nodes[i].getrawmempool()), 0)

        print("Calling getcertmaturityinfo for cert {} , it shouldn't be in mempool anymore".format(cert_last))
        assert_false(cert_last in self.nodes[3].getrawmempool())
        ret = self.nodes[3].getcertmaturityinfo(cert_last)
        pprint.pprint(ret)
        assert_equal(ret['blocksToMaturity'], -1)
        assert_equal(ret['certificateState'], "INVALID")
        assert_equal(ret['maturityHeight'], -1)

        ret = self.nodes[1].verifychain(4, 0)
        assert_equal(ret, True)
    def run_test(self):
        '''
        Test that the JSON result of the rpc command listsinceblock includes a matured certificate backward transfer even when:
        1) the input block range specified does not contain the block where such certificate has been mined.
        2) The certificate matures in one of the blocks of the specified range
        '''

        mark_logs("Node 1 generates 2 block", self.nodes, DEBUG_MODE)
        self.nodes[1].generate(2)
        self.sync_all()

        mark_logs("Node 0 generates {} block".format(MINIMAL_SC_HEIGHT - 2),
                  self.nodes, DEBUG_MODE)
        self.nodes[0].generate(MINIMAL_SC_HEIGHT - 2)
        self.sync_all()

        #generate wCertVk and constant
        mcTest = CertTestUtils(self.options.tmpdir, self.options.srcdir)
        vk = mcTest.generate_params('sc1')
        constant = generate_random_field_element_hex()

        # create SC
        #------------------------------------------------------------------------------------------------------------
        cmdInput = {
            'version': 0,
            'toaddress': "abcd",
            'amount': 20.0,
            'wCertVk': vk,
            'withdrawalEpochLength': EPOCH_LENGTH,
            'constant': constant
        }

        mark_logs("\nNode 1 create SC", self.nodes, DEBUG_MODE)
        try:
            res = self.nodes[1].sc_create(cmdInput)
            tx = res['txid']
            scid = res['scid']
            pprint.pprint(res)
            self.sync_all()
        except JSONRPCException as e:
            errorString = e.error['message']
            mark_logs(errorString, self.nodes, DEBUG_MODE)
            assert_true(False)

        mark_logs("\nNode 0 generates 1 block", self.nodes, DEBUG_MODE)
        bl = self.nodes[0].generate(1)[-1]
        self.sync_all()

        item = self.nodes[0].getscinfo(scid)['items'][0]
        elen = item['withdrawalEpochLength']
        wlen = item['certSubmissionWindowLength']
        ch = item['createdAtBlockHeight']

        scid_swapped = str(swap_bytes(scid))

        q = 10
        blocks_d = {}
        block_heights_d = {}
        certs_d = {}
        mat_height_d = {}

        taddr1 = self.nodes[1].getnewaddress()
        taddr2 = self.nodes[2].getnewaddress()

        # advance some epochs and send a certificate for any of them
        for i in range(3):

            am_bwt1 = Decimal(i + 1) + Decimal('0.01')
            am_bwt2 = Decimal(i + 1) + Decimal('0.02')
            am_out = Decimal('0.001') * (i + 1)

            mark_logs("Advance epoch...", self.nodes, DEBUG_MODE)
            self.nodes[0].generate(EPOCH_LENGTH - 1)
            self.sync_all()
            epoch_number, epoch_cum_tree_hash = get_epoch_data(
                scid, self.nodes[0], EPOCH_LENGTH)

            mark_logs("Node 1 sends a cert", self.nodes, DEBUG_MODE)
            #==============================================================
            pkh_arr = []
            am_bwt_arr = []
            raw_bwt_outs = [{
                "address": taddr1,
                "amount": am_bwt1
            }, {
                "address": taddr2,
                "amount": am_bwt2
            }]
            for entry in raw_bwt_outs:
                pkh_arr.append(entry["address"])
                am_bwt_arr.append(entry["amount"])

            proof = mcTest.create_test_proof("sc1", scid_swapped, epoch_number,
                                             q, MBTR_SC_FEE, FT_SC_FEE,
                                             epoch_cum_tree_hash, constant,
                                             pkh_arr, am_bwt_arr)

            utx, change = get_spendable(self.nodes[1], CERT_FEE + am_out)
            raw_inputs = [{'txid': utx['txid'], 'vout': utx['vout']}]
            raw_outs = {taddr1: change, taddr2: am_out}

            raw_params = {
                "scid": scid,
                "quality": q,
                "endEpochCumScTxCommTreeRoot": epoch_cum_tree_hash,
                "scProof": proof,
                "withdrawalEpochNumber": epoch_number
            }

            # use the raw version of the command for having more than one standard output
            # -------------------------------------
            # vout 1: standard output (taddr1) - change
            # vout 2: standard output (taddr2)
            # vout 3: bwt (taddr1)
            # vout 3: bwt (taddr2)
            try:
                raw_cert = self.nodes[1].createrawcertificate(
                    raw_inputs, raw_outs, raw_bwt_outs, raw_params)
                signed_cert = self.nodes[1].signrawtransaction(raw_cert)
                certs_d[i] = self.nodes[1].sendrawtransaction(
                    signed_cert['hex'])
            except JSONRPCException as e:
                errorString = e.error['message']
                print("\n======> ", errorString)
                assert_true(False)

            self.sync_all()

            mark_logs("cert = {}".format(certs_d[i]), self.nodes, DEBUG_MODE)
            mat_height_d[i] = ch - 1 + (i + 2) * elen + wlen
            print("mat height = {}".format(mat_height_d[i]))

            mark_logs("Node 0 generates 1 block", self.nodes, DEBUG_MODE)
            bl = self.nodes[0].generate(1)[-1]
            self.sync_all()
            c = self.nodes[0].getblockcount()
            blocks_d[i] = bl
            block_heights_d[i] = c

        # the first of the 3 certificates has reached maturity and Node2 has a consistent balance
        bal = self.nodes[2].getbalance()
        print("Node2 balance = {}".format(bal))
        assert_equal(
            bal,
            Decimal('1.02') + Decimal('0.001') + Decimal('0.002') +
            Decimal('0.003'))

        mark_logs("Calling listsinceblock on Node2 for all transactions",
                  self.nodes, DEBUG_MODE)
        ret = self.nodes[2].listsinceblock("", 1, False, True)
        # pprint.pprint(ret)

        mat_block_hash = self.nodes[2].getblockhash(mat_height_d[0])
        # first cert is there and its backward transfer to Node2 is mature
        # we also have one ordinary output from this cert, and we should have no maturity info
        cert = certs_d[0]
        found_bwt = False
        found_out = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                assert_equal(x['category'], 'receive')
                assert_equal(x['blockhash'], blocks_d[0])
                if 'isBackwardTransfer' in x:
                    assert_equal(x['amount'], Decimal('1.02'))
                    assert_equal(x['isBackwardTransfer'], True)
                    assert_equal(x['maturityblockhash'], mat_block_hash)
                    found_bwt = True
                else:
                    assert_false('maturityblockhash' in x)
                    assert_equal(x['amount'], Decimal('0.001'))
                    found_out = True
        assert_true(found_bwt)
        assert_true(found_out)

        # cert 2 is there and is immature
        # we also have one ordinary output from this cert
        cert = certs_d[1]
        found_bwt = False
        found_out = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                assert_equal(x['blockhash'], blocks_d[1])
                if 'isBackwardTransfer' in x:
                    assert_equal(x['category'], 'immature')
                    assert_equal(x['amount'], Decimal('2.02'))
                    assert_equal(x['isBackwardTransfer'], True)
                    found_bwt = True
                else:
                    assert_equal(x['category'], 'receive')
                    assert_equal(x['amount'], Decimal('0.002'))
                    found_out = True
        assert_true(found_bwt)
        assert_true(found_out)

        # last cert is there and is immature
        # we also have one ordinary output from this cert
        cert = certs_d[2]
        found_bwt = False
        found_out = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                assert_equal(x['blockhash'], blocks_d[2])
                if 'isBackwardTransfer' in x:
                    assert_equal(x['category'], 'immature')
                    assert_equal(x['amount'], Decimal('3.02'))
                    assert_equal(x['isBackwardTransfer'], True)
                    found_bwt = True
                else:
                    assert_equal(x['category'], 'receive')
                    assert_equal(x['amount'], Decimal('0.003'))
                    found_out = True
        assert_true(found_bwt)
        assert_true(found_out)

        # calling the cmd targeting the block where the second certificate has been mined
        mark_logs(
            "Calling listsinceblock on Node2 for h={}, hash={}".format(
                block_heights_d[1], blocks_d[1]), self.nodes, DEBUG_MODE)
        ret = self.nodes[2].listsinceblock(blocks_d[1], 1, False, True)
        pprint.pprint(ret)

        cert = certs_d[0]
        # first cert is there, it is mature and it refers to the block where it matured
        # we should not see the standard output anymore
        mat_block_hash = self.nodes[2].getblockhash(mat_height_d[0])
        found = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                found = True
                assert_equal(x['amount'], Decimal('1.02'))
                assert_equal(x['category'], 'receive')
                assert_equal(x['blockhash'], blocks_d[0])
                assert_equal(x['maturityblockhash'], mat_block_hash)
                assert_equal(x['isBackwardTransfer'], True)
        assert_true(found)

        # cert 2 is not there (since-block is not part of the range)
        cert = certs_d[1]
        found = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                found = True
        assert_false(found)

        # last cert is there and is immature, we have also standard output
        cert = certs_d[2]
        found_bwt = False
        found_out = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                assert_equal(x['blockhash'], blocks_d[2])
                if 'isBackwardTransfer' in x:
                    assert_equal(x['category'], 'immature')
                    assert_equal(x['amount'], Decimal('3.02'))
                    assert_equal(x['isBackwardTransfer'], True)
                    found_bwt = True
                else:
                    assert_equal(x['category'], 'receive')
                    assert_equal(x['amount'], Decimal('0.003'))
                    found_out = True
        assert_true(found_bwt)
        assert_true(found_out)

        # calling the cmd targeting the block where the last certificate has been mined
        # There must be no certificates at all, since none of them is contained or matures in this block range
        mark_logs(
            "Calling listsinceblock on Node2 for h={}, hash={}".format(
                block_heights_d[2], blocks_d[2]), self.nodes, DEBUG_MODE)
        ret = self.nodes[2].listsinceblock(blocks_d[2], 1, False, True)
        assert_true(len(ret['transactions']) == 0)

        # reach the height of the second certificate and re-issue the command
        c = self.nodes[0].getblockcount()
        mark_logs("Node 0 generates {} block".format(mat_height_d[1] - c),
                  self.nodes, DEBUG_MODE)
        self.nodes[0].generate(mat_height_d[1] - c)
        self.sync_all()
        print("chain height = {}".format(self.nodes[0].getblockcount()))

        mark_logs(
            "Calling listsinceblock on Node2 for h={}, hash={}".format(
                block_heights_d[2], blocks_d[2]), self.nodes, DEBUG_MODE)
        ret = self.nodes[2].listsinceblock(blocks_d[2], 1, False, True)

        cert = certs_d[0]
        # first cert is not there
        found = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                found = True
        assert_false(found)

        cert = certs_d[1]
        # second cert is there, it is mature and it refers to the block where it matured
        found = False
        mat_block_hash = self.nodes[2].getblockhash(mat_height_d[1])
        for x in ret['transactions']:
            if x['txid'] == cert:
                found = True
                assert_equal(x['amount'], Decimal('2.02'))
                assert_equal(x['category'], 'receive')
                assert_equal(x['blockhash'], blocks_d[1])
                assert_equal(x['maturityblockhash'], mat_block_hash)
                assert_equal(x['isBackwardTransfer'], True)
        assert_true(found)

        cert = certs_d[2]
        # last cert is not there
        found = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                found = True
        assert_false(found)

        # get the block before the one containing the last certificate
        h = block_heights_d[2] - 1
        bl = self.nodes[0].getblockhash(h)

        mark_logs(
            "Calling listsinceblock on Node2 for h={}, hash={}".format(h, bl),
            self.nodes, DEBUG_MODE)
        ret = self.nodes[2].listsinceblock(bl, 1, False, True)

        # last cert is there and is immature
        # we also have one ordinary output from this cert
        cert = certs_d[2]
        found_bwt = False
        found_out = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                assert_equal(x['blockhash'], blocks_d[2])
                if 'isBackwardTransfer' in x:
                    assert_equal(x['category'], 'immature')
                    assert_equal(x['amount'], Decimal('3.02'))
                    assert_equal(x['isBackwardTransfer'], True)
                    found_bwt = True
                else:
                    assert_equal(x['category'], 'receive')
                    assert_equal(x['amount'], Decimal('0.003'))
                    found_out = True
        assert_true(found_bwt)
        assert_true(found_out)

        # reach the height of the third certificate, that will make the SC cease, and re-issue the command
        c = self.nodes[0].getblockcount()
        mark_logs("Node 0 generates {} block".format(mat_height_d[2] - c),
                  self.nodes, DEBUG_MODE)
        self.nodes[0].generate(mat_height_d[2] - c)
        self.sync_all()
        print("chain height = {}".format(self.nodes[0].getblockcount()))

        ret = self.nodes[0].getscinfo(scid, False, False)['items'][0]
        assert_equal(ret['ceasingHeight'], mat_height_d[2])
        assert_equal(ret['state'], "CEASED")

        mark_logs(
            "Calling listsinceblock on Node2 for h={}, hash={}".format(h, bl),
            self.nodes, DEBUG_MODE)
        ret = self.nodes[2].listsinceblock(bl, 1, False, True)

        # last cert bwt is not there anymore as expected
        # but we still have one ordinary output from this cert
        cert = certs_d[2]
        found = False
        for x in ret['transactions']:
            if x['txid'] == cert:
                found = True
                assert_false('isBackwardTransfer' in x)
                assert_equal(x['blockhash'], blocks_d[2])
                assert_equal(x['category'], 'receive')
                assert_equal(x['amount'], Decimal('0.003'))
        assert_true(found)