def gen_proof(self, node):
        blockhashes = node.generate(10)

        privkey = ECKey()
        privkey.generate()
        pubkey = privkey.get_pubkey()

        stakes = create_coinbase_stakes(node, blockhashes,
                                        node.get_deterministic_priv_key().key)
        proof_hex = node.buildavalancheproof(42, 2000000000,
                                             pubkey.get_bytes().hex(), stakes)

        return bytes_to_wif(privkey.get_bytes()), FromHex(
            AvalancheProof(), proof_hex)
Ejemplo n.º 2
0
    def run_test(self):
        node = self.nodes[0]
        peercount = 5
        nodecount = 10

        self.log.info(
            f"Generating {peercount} peers with {nodecount} nodes each")

        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(peercount, addrkey0.address)
        # Use the first coinbase to create a stake
        stakes = create_coinbase_stakes(node, blockhashes, addrkey0.key)

        def getProof(stake):
            privkey = ECKey()
            privkey.generate()
            pubkey = privkey.get_pubkey()

            proof_sequence = 11
            proof_expiration = 12
            proof = node.buildavalancheproof(proof_sequence, proof_expiration,
                                             bytes_to_wif(privkey.get_bytes()),
                                             [stake])
            return (pubkey.get_bytes().hex(), proof)

        # Create peercount * nodecount node array
        nodes = [[get_ava_p2p_interface(node) for _ in range(nodecount)]
                 for _ in range(peercount)]

        # Add peercount peers and bind all the nodes to each
        proofs = []
        for i in range(peercount):
            pubkey_hex, proof = getProof(stakes[i])
            proofs.append(proof)
            [
                node.addavalanchenode(n.nodeid, pubkey_hex, proof)
                for n in nodes[i]
            ]

        self.log.info("Testing getavalanchepeerinfo...")
        avapeerinfo = node.getavalanchepeerinfo()

        assert_equal(len(avapeerinfo), peercount)
        for i, peer in enumerate(avapeerinfo):
            assert_equal(peer["peerid"], i)
            assert_equal(peer["proof"], proofs[i])
            assert_equal(peer["nodecount"], nodecount)
            assert_equal(set(peer["nodes"]), set([n.nodeid for n in nodes[i]]))
Ejemplo n.º 3
0
    def test_getpeerinfo(self):
        self.log.info("Test getpeerinfo")
        # Create a few getpeerinfo last_block/last_transaction/last_proof
        # values.
        if self.is_wallet_compiled():
            self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1000000)
        tip = self.nodes[1].generate(1)[0]
        self.sync_all()

        stake = create_coinbase_stakes(
            self.nodes[1], [tip],
            self.nodes[1].get_deterministic_priv_key().key)
        privkey = ECKey()
        privkey.generate()
        proof = self.nodes[1].buildavalancheproof(
            42, 2000000000, bytes_to_wif(privkey.get_bytes()), stake)
        self.nodes[1].sendavalancheproof(proof)
        self.sync_proofs()

        time_now = int(time.time())
        peer_info = [x.getpeerinfo() for x in self.nodes]
        # Verify last_block, last_transaction and last_proof keys/values.
        for node, peer, field in product(
                range(self.num_nodes), range(2),
            ['last_block', 'last_transaction', 'last_proof']):
            assert field in peer_info[node][peer].keys()
            if peer_info[node][peer][field] != 0:
                assert_approx(peer_info[node][peer][field], time_now, vspan=60)
        # check both sides of bidirectional connection between nodes
        # the address bound to on one side will be the source address for the
        # other node
        assert_equal(peer_info[0][0]['addrbind'], peer_info[1][0]['addr'])
        assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr'])
        assert_equal(peer_info[0][0]['minfeefilter'], Decimal("5.00"))
        assert_equal(peer_info[1][0]['minfeefilter'], Decimal("10.00"))
        # check the `servicesnames` field
        for info in peer_info:
            assert_net_servicesnames(int(info[0]["services"], 0x10),
                                     info[0]["servicesnames"])

        assert_equal(peer_info[0][0]['connection_type'], 'inbound')
        assert_equal(peer_info[0][1]['connection_type'], 'manual')

        assert_equal(peer_info[1][0]['connection_type'], 'manual')
        assert_equal(peer_info[1][1]['connection_type'], 'inbound')
Ejemplo n.º 4
0
    def run_test(self):
        node = self.nodes[0]

        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(2, addrkey0.address)
        stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key)

        privkey = ECKey()
        privkey.generate()

        proof_master = privkey.get_pubkey().get_bytes().hex()
        proof_sequence = 42
        proof_expiration = 2000000000
        proof = node.buildavalancheproof(proof_sequence, proof_expiration,
                                         proof_master, stakes)

        nodeid = add_interface_node(node)

        def check_addavalanchenode_error(error_code,
                                         error_message,
                                         nodeid=nodeid,
                                         proof=proof,
                                         pubkey=proof_master,
                                         delegation=None):
            assert_raises_rpc_error(
                error_code,
                error_message,
                node.addavalanchenode,
                nodeid,
                pubkey,
                proof,
                delegation,
            )

        self.log.info("Invalid proof")
        check_addavalanchenode_error(-22,
                                     "Proof must be an hexadecimal string",
                                     proof="not a proof")
        check_addavalanchenode_error(-22,
                                     "Proof has invalid format",
                                     proof="f000")
        no_stake = node.buildavalancheproof(proof_sequence, proof_expiration,
                                            proof_master, [])
        check_addavalanchenode_error(-8,
                                     "The proof is invalid: no-stake",
                                     proof=no_stake)

        self.log.info("Node doesn't exist")
        check_addavalanchenode_error(-8,
                                     f"The node does not exist: {nodeid + 1}",
                                     nodeid=nodeid + 1)

        self.log.info("Invalid delegation")
        dg_privkey = ECKey()
        dg_privkey.generate()
        dg_pubkey = dg_privkey.get_pubkey().get_bytes()
        check_addavalanchenode_error(
            -22,
            "Delegation must be an hexadecimal string",
            pubkey=dg_pubkey.hex(),
            delegation="not a delegation")
        check_addavalanchenode_error(-22,
                                     "Delegation has invalid format",
                                     pubkey=dg_pubkey.hex(),
                                     delegation="f000")

        self.log.info("Delegation mismatch with the proof")
        delegation_wrong_proofid = AvalancheDelegation()
        check_addavalanchenode_error(
            -8,
            "The delegation does not match the proof",
            pubkey=dg_pubkey.hex(),
            delegation=delegation_wrong_proofid.serialize().hex())

        proofobj = FromHex(AvalancheProof(), proof)
        delegation = AvalancheDelegation(
            limited_proofid=proofobj.limited_proofid,
            proof_master=proofobj.master,
        )

        self.log.info("Delegation with bad signature")
        bad_level = AvalancheDelegationLevel(pubkey=dg_pubkey, )
        delegation.levels.append(bad_level)
        check_addavalanchenode_error(-8,
                                     "The delegation is invalid",
                                     pubkey=dg_pubkey.hex(),
                                     delegation=delegation.serialize().hex())

        delegation.levels = []
        level = AvalancheDelegationLevel(pubkey=dg_pubkey,
                                         sig=privkey.sign_schnorr(
                                             hash256(delegation.getid() +
                                                     ser_string(dg_pubkey))))
        delegation.levels.append(level)

        self.log.info("Key mismatch with the proof")
        check_addavalanchenode_error(
            -5,
            "The public key does not match the proof",
            pubkey=dg_pubkey.hex(),
        )

        self.log.info("Key mismatch with the delegation")
        random_privkey = ECKey()
        random_privkey.generate()
        random_pubkey = random_privkey.get_pubkey()
        check_addavalanchenode_error(
            -5,
            "The public key does not match the delegation",
            pubkey=random_pubkey.get_bytes().hex(),
            delegation=delegation.serialize().hex(),
        )

        self.log.info("Happy path")
        assert node.addavalanchenode(nodeid, proof_master, proof)
        # Adding several times is OK
        assert node.addavalanchenode(nodeid, proof_master, proof)

        # Use an hardcoded proof. This will help detecting proof format changes.
        # Generated using:
        # stakes = create_coinbase_stakes(node, [blockhashes[1]], addrkey0.key)
        # hardcoded_proof = node.buildavalancheproof(
        #    proof_sequence, proof_expiration, random_pubkey, stakes)
        hardcoded_pubkey = "037d20fcfe118296bb53f0a8f87c864e7b9831c4fcd7c6a0bb9a58e0e0f53d5cbc"
        hardcoded_proof = (
            "2a00000000000000009435770000000021037d20fcfe118296bb53f0a8f87c864e"
            "7b9831c4fcd7c6a0bb9a58e0e0f53d5cbc01683ef49024cf25bb55775b327f5e68"
            "c79da3a7824dc03df5623c96f4a60158f90000000000f902950000000095010000"
            "210227d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d"
            "3f612834ef0e2545d6359e9f34967c2bb69cb88fe246fed716d998f3f62eba1ef6"
            "6a547606a7ac14c1b5697f4acc20853b3f99954f4f7b6e9bf8a085616d3adfc7")
        assert node.addavalanchenode(nodeid, hardcoded_pubkey, hardcoded_proof)

        self.log.info("Add a node with a valid delegation")
        assert node.addavalanchenode(
            nodeid,
            dg_pubkey.hex(),
            proof,
            delegation.serialize().hex(),
        )

        self.log.info("Several nodes can share a proof")
        nodeid2 = add_interface_node(node)
        assert node.addavalanchenode(nodeid2, proof_master, proof)
    def run_test(self):
        node = self.nodes[0]

        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(100, addrkey0.address)

        self.log.info(
            "Make build a valid proof and restart the node to use it")
        privkey = ECKey()
        privkey.set(bytes.fromhex(
            "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"), True)

        def get_hex_pubkey(privkey):
            return privkey.get_pubkey().get_bytes().hex()

        proof_master = get_hex_pubkey(privkey)
        proof_sequence = 11
        proof_expiration = 12
        proof = node.buildavalancheproof(
            proof_sequence, proof_expiration, proof_master,
            create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key))

        # Restart the node, making sure it is initially in IBD mode
        minchainwork = int(node.getblockchaininfo()["chainwork"], 16) + 1
        self.restart_node(0, self.extra_args[0] + [
            "-avaproof={}".format(proof),
            "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            "-minimumchainwork=0x{:x}".format(minchainwork),
        ])

        self.log.info(
            "The proof verification should be delayed until IBD is complete")
        assert node.getblockchaininfo()["initialblockdownload"] is True
        # Our proof cannot be verified during IBD, so we should have no peer
        assert not node.getavalanchepeerinfo()
        # Mining a few more blocks should cause us to leave IBD
        node.generate(2)
        # Our proof is now verified and our node is added as a peer
        assert node.getblockchaininfo()["initialblockdownload"] is False
        wait_until(lambda: len(node.getavalanchepeerinfo()) == 1, timeout=5)

        if self.is_wallet_compiled():
            self.log.info(
                "A proof using the maximum number of stakes is accepted...")

            new_blocks = node.generate(AVALANCHE_MAX_PROOF_STAKES // 10 + 1)
            # confirm the coinbase UTXOs
            node.generate(101)
            too_many_stakes = create_stakes(
                node, new_blocks, AVALANCHE_MAX_PROOF_STAKES + 1)
            maximum_stakes = too_many_stakes[:-1]
            good_proof = node.buildavalancheproof(
                proof_sequence, proof_expiration,
                proof_master, maximum_stakes)
            peerid1 = add_interface_node(node)
            assert node.addavalanchenode(peerid1, proof_master, good_proof)

            self.log.info(
                "A proof using too many stakes should be rejected...")
            too_many_utxos = node.buildavalancheproof(
                proof_sequence, proof_expiration,
                proof_master, too_many_stakes)
            peerid2 = add_interface_node(node)
            assert not node.addavalanchenode(
                peerid2, proof_master, too_many_utxos)

        self.log.info("Generate delegations for the proof")

        # Stack up a few delegation levels
        def gen_privkey():
            pk = ECKey()
            pk.generate()
            return pk

        delegator_privkey = privkey
        delegation = None
        for _ in range(10):
            delegated_privkey = gen_privkey()
            delegation = node.delegateavalancheproof(
                proof,
                bytes_to_wif(delegator_privkey.get_bytes()),
                get_hex_pubkey(delegated_privkey),
                delegation,
            )
            delegator_privkey = delegated_privkey

        random_privkey = gen_privkey()
        random_pubkey = get_hex_pubkey(random_privkey)

        # Invalid proof
        no_stake = node.buildavalancheproof(
            proof_sequence, proof_expiration, proof_master, [])
        assert_raises_rpc_error(-8, "The proof is invalid",
                                node.delegateavalancheproof,
                                no_stake,
                                bytes_to_wif(privkey.get_bytes()),
                                random_pubkey,
                                )

        # Invalid privkey
        assert_raises_rpc_error(-5, "The private key is invalid",
                                node.delegateavalancheproof,
                                proof,
                                bytes_to_wif(bytes(32)),
                                random_pubkey,
                                )

        # Invalid delegation
        bad_dg = AvalancheDelegation()
        assert_raises_rpc_error(-8, "The supplied delegation is not valid",
                                node.delegateavalancheproof,
                                proof,
                                bytes_to_wif(privkey.get_bytes()),
                                random_pubkey,
                                bad_dg.serialize().hex(),
                                )

        # Wrong privkey, does not match the proof
        assert_raises_rpc_error(-8, "The private key does not match the proof or the delegation",
                                node.delegateavalancheproof,
                                proof,
                                bytes_to_wif(random_privkey.get_bytes()),
                                random_pubkey,
                                )

        # Wrong privkey, match the proof but does not match the delegation
        assert_raises_rpc_error(-8, "The private key does not match the proof or the delegation",
                                node.delegateavalancheproof,
                                proof,
                                bytes_to_wif(privkey.get_bytes()),
                                random_pubkey,
                                delegation,
                                )

        # Test invalid proofs
        self.log.info("Bad proof should be rejected at startup")
        dust = node.buildavalancheproof(
            proof_sequence, proof_expiration, proof_master,
            create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key, amount="0"))

        dust_amount = Decimal(f"{PROOF_DUST_THRESHOLD * 0.9999:.4f}")
        dust2 = node.buildavalancheproof(
            proof_sequence, proof_expiration, proof_master,
            create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key,
                                   amount=str(dust_amount)))

        duplicate_stake = node.buildavalancheproof(
            proof_sequence, proof_expiration, proof_master,
            create_coinbase_stakes(node, [blockhashes[0]] * 2, addrkey0.key))

        bad_sig = ("0b000000000000000c0000000000000021030b4c866585dd868a9d62348"
                   "a9cd008d6a312937048fff31670e7e920cfc7a7440105c5f72f5d6da3085"
                   "583e75ee79340eb4eff208c89988e7ed0efb30b87298fa30000000000f20"
                   "52a0100000003000000210227d85ba011276cf25b51df6a188b75e604b3"
                   "8770a462b2d0e9fb2fc839ef5d3faf07f001dd38e9b4a43d07d5d449cc0"
                   "f7d2888d96b82962b3ce516d1083c0e031773487fc3c4f2e38acd1db974"
                   "1321b91a79b82d1c2cfd47793261e4ba003cf5")

        self.stop_node(0)

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avasessionkey=0",
            ],
            expected_msg="Error: the avalanche session key is invalid",
        )

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avaproof={}".format(proof),
            ],
            expected_msg="Error: the avalanche master key is missing for the avalanche proof",
        )

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=0",
            ],
            expected_msg="Error: the avalanche master key is invalid",
        )

        def check_proof_init_error(proof, message):
            node.assert_start_raises_init_error(
                self.extra_args[0] + [
                    "-avaproof={}".format(proof),
                    "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
                ],
                expected_msg="Error: " + message,
            )

        check_proof_init_error(no_stake,
                               "the avalanche proof has no stake")
        check_proof_init_error(dust,
                               "the avalanche proof stake is too low")
        check_proof_init_error(dust2,
                               "the avalanche proof stake is too low")
        check_proof_init_error(duplicate_stake,
                               "the avalanche proof has duplicated stake")
        check_proof_init_error(bad_sig,
                               "the avalanche proof has invalid stake signatures")
        if self.is_wallet_compiled():
            # The too many utxos case creates a proof which is that large that it
            # cannot fit on the command line
            append_config(node.datadir, ["avaproof={}".format(too_many_utxos)])
            node.assert_start_raises_init_error(
                self.extra_args[0] + [
                    "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
                ],
                expected_msg="Error: the avalanche proof has too many utxos",
                match=ErrorMatch.PARTIAL_REGEX,
            )
    def run_test(self):
        # Turn off node 1 while node 0 mines blocks to generate stakes,
        # so that we can later try starting node 1 with an orphan proof.
        self.stop_node(1)

        node = self.nodes[0]

        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(100, addrkey0.address)

        self.log.info(
            "Make build a valid proof and restart the node to use it")
        privkey = ECKey()
        privkey.set(
            bytes.fromhex(
                "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"
            ), True)
        wif_privkey = bytes_to_wif(privkey.get_bytes())

        def get_hex_pubkey(privkey):
            return privkey.get_pubkey().get_bytes().hex()

        proof_master = get_hex_pubkey(privkey)
        proof_sequence = 11
        proof_expiration = 12
        stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key)
        proof = node.buildavalancheproof(proof_sequence, proof_expiration,
                                         wif_privkey, stakes)

        self.log.info("Test decodeavalancheproof RPC")
        proofobj = FromHex(LegacyAvalancheProof(), proof)
        decodedproof = node.decodeavalancheproof(proof)
        limited_id_hex = f"{proofobj.limited_proofid:0{64}x}"
        assert_equal(decodedproof["sequence"], proof_sequence)
        assert_equal(decodedproof["expiration"], proof_expiration)
        assert_equal(decodedproof["master"], proof_master)
        assert_equal(decodedproof["payoutscript"]["hex"], "")
        assert "signature" not in decodedproof.keys()
        assert_equal(decodedproof["proofid"], f"{proofobj.proofid:0{64}x}")
        assert_equal(decodedproof["limitedid"], limited_id_hex)
        assert_equal(decodedproof["stakes"][0]["txid"], stakes[0]["txid"])
        assert_equal(decodedproof["stakes"][0]["vout"], stakes[0]["vout"])
        assert_equal(decodedproof["stakes"][0]["height"], stakes[0]["height"])
        assert_equal(decodedproof["stakes"][0]["iscoinbase"],
                     stakes[0]["iscoinbase"])
        assert_equal(decodedproof["stakes"][0]["signature"],
                     base64.b64encode(proofobj.stakes[0].sig).decode("ascii"))

        # Invalid hex (odd number of hex digits)
        assert_raises_rpc_error(-22, "Proof must be an hexadecimal string",
                                node.decodeavalancheproof, proof[:-1])
        # Valid hex but invalid proof
        assert_raises_rpc_error(-22, "Proof has invalid format",
                                node.decodeavalancheproof, proof[:-2])

        self.log.info(
            "Testing decodeavalancheproof with legacyavaproof disabled")
        self.restart_node(0, self.extra_args[0] + ["-legacyavaproof=0"])

        regular_proof = node.buildavalancheproof(proof_sequence,
                                                 proof_expiration, wif_privkey,
                                                 stakes,
                                                 ADDRESS_ECREG_UNSPENDABLE)
        decoded_regular_proof = node.decodeavalancheproof(regular_proof)

        assert_equal(decoded_regular_proof["sequence"],
                     decodedproof["sequence"])
        assert_equal(decoded_regular_proof["expiration"],
                     decodedproof["expiration"])
        assert_equal(decoded_regular_proof["master"], decodedproof["master"])
        assert_equal(
            decoded_regular_proof["payoutscript"], {
                "asm":
                "OP_DUP OP_HASH160 0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG",
                "hex": "76a914000000000000000000000000000000000000000088ac",
                "reqSigs": 1,
                "type": "pubkeyhash",
                "addresses": [ADDRESS_ECREG_UNSPENDABLE],
            })

        regular_proof_obj = FromHex(AvalancheProof(), regular_proof)
        assert_equal(
            decoded_regular_proof["signature"],
            base64.b64encode(regular_proof_obj.signature).decode("ascii"))
        assert_equal(decoded_regular_proof["proofid"],
                     f"{regular_proof_obj.proofid:0{64}x}")
        assert_equal(decoded_regular_proof["limitedid"],
                     f"{regular_proof_obj.limited_proofid:0{64}x}")

        assert_equal(decoded_regular_proof["stakes"][0]["txid"],
                     decodedproof["stakes"][0]["txid"])
        assert_equal(decoded_regular_proof["stakes"][0]["vout"],
                     decodedproof["stakes"][0]["vout"])
        assert_equal(decoded_regular_proof["stakes"][0]["height"],
                     decodedproof["stakes"][0]["height"])
        assert_equal(decoded_regular_proof["stakes"][0]["iscoinbase"],
                     decodedproof["stakes"][0]["iscoinbase"])
        assert_equal(
            decoded_regular_proof["stakes"][0]["signature"],
            base64.b64encode(regular_proof_obj.stakes[0].sig).decode("ascii"))

        # Restart the node with this proof
        self.restart_node(
            0, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])

        self.log.info("The proof is registered at first chaintip update")
        assert_equal(len(node.getavalanchepeerinfo()), 0)
        node.generate(1)
        self.wait_until(lambda: len(node.getavalanchepeerinfo()) == 1,
                        timeout=5)

        # This case will occur for users building proofs with a third party
        # tool and then starting a new node that is not yet aware of the
        # transactions used for stakes.
        self.log.info("Start a node with an orphan proof")

        self.start_node(
            1, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])
        # Mine a block to trigger an attempt at registering the proof
        self.nodes[1].generate(1)
        wait_for_proof(self.nodes[1],
                       f"{proofobj.proofid:0{64}x}",
                       expect_orphan=True)

        self.log.info("Connect to an up-to-date node to unorphan the proof")
        self.connect_nodes(1, node.index)
        self.sync_all()
        wait_for_proof(self.nodes[1],
                       f"{proofobj.proofid:0{64}x}",
                       expect_orphan=False)

        self.log.info("Generate delegations for the proof")

        # Stack up a few delegation levels
        def gen_privkey():
            pk = ECKey()
            pk.generate()
            return pk

        delegator_privkey = privkey
        delegation = None
        for _ in range(10):
            delegated_privkey = gen_privkey()
            delegation = node.delegateavalancheproof(
                limited_id_hex,
                bytes_to_wif(delegator_privkey.get_bytes()),
                get_hex_pubkey(delegated_privkey),
                delegation,
            )
            delegator_privkey = delegated_privkey

        random_privkey = gen_privkey()
        random_pubkey = get_hex_pubkey(random_privkey)

        # Invalid proof
        no_stake = node.buildavalancheproof(proof_sequence, proof_expiration,
                                            wif_privkey, [])

        # Invalid privkey
        assert_raises_rpc_error(
            -5,
            "The private key is invalid",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(bytes(32)),
            random_pubkey,
        )

        # Invalid delegation
        bad_dg = AvalancheDelegation()
        assert_raises_rpc_error(
            -8,
            "The delegation does not match the proof",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            bad_dg.serialize().hex(),
        )

        # Still invalid, but with a matching proofid
        bad_dg.limited_proofid = proofobj.limited_proofid
        bad_dg.proof_master = proofobj.master
        bad_dg.levels = [AvalancheDelegationLevel()]
        assert_raises_rpc_error(
            -8,
            "The delegation is invalid",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            bad_dg.serialize().hex(),
        )

        # Wrong privkey, match the proof but does not match the delegation
        assert_raises_rpc_error(
            -5,
            "The private key does not match the delegation",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            delegation,
        )

        # Delegation not hex
        assert_raises_rpc_error(
            -22,
            "Delegation must be an hexadecimal string.",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            "f00",
        )
        # Delegation is hex but ill-formed
        assert_raises_rpc_error(
            -22,
            "Delegation has invalid format",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            "dead",
        )

        # Test invalid proofs
        dust = node.buildavalancheproof(
            proof_sequence, proof_expiration, wif_privkey,
            create_coinbase_stakes(node, [blockhashes[0]],
                                   addrkey0.key,
                                   amount="0"))

        dust2 = node.buildavalancheproof(
            proof_sequence, proof_expiration, wif_privkey,
            create_coinbase_stakes(
                node, [blockhashes[0]],
                addrkey0.key,
                amount=f"{PROOF_DUST_THRESHOLD * 0.9999:.2f}"))

        missing_stake = node.buildavalancheproof(
            proof_sequence, proof_expiration, wif_privkey,
            [{
                'txid': '0' * 64,
                'vout': 0,
                'amount': 10000000,
                'height': 42,
                'iscoinbase': False,
                'privatekey': addrkey0.key,
            }])

        duplicate_stake = (
            "0b000000000000000c0000000000000021030b4c866585dd868"
            "a9d62348a9cd008d6a312937048fff31670e7e920cfc7a74402"
            "05c5f72f5d6da3085583e75ee79340eb4eff208c89988e7ed0e"
            "fb30b87298fa30000000000f2052a0100000003000000210227"
            "d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2"
            "fc839ef5d3f86076def2e8bc3c40671c1a0eb505da5857a950a"
            "0cf4625a80018cdd75ac62e61273ff8142f747de67e73f6368c"
            "8648942b0ef6c065d72a81ad7438a23c11cca05c5f72f5d6da3"
            "085583e75ee79340eb4eff208c89988e7ed0efb30b87298fa30"
            "000000000f2052a0100000003000000210227d85ba011276cf2"
            "5b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d3f860"
            "76def2e8bc3c40671c1a0eb505da5857a950a0cf4625a80018c"
            "dd75ac62e61273ff8142f747de67e73f6368c8648942b0ef6c0"
            "65d72a81ad7438a23c11cca")

        bad_sig = (
            "0b000000000000000c0000000000000021030b4c866585dd868a9d62348"
            "a9cd008d6a312937048fff31670e7e920cfc7a7440105c5f72f5d6da3085"
            "583e75ee79340eb4eff208c89988e7ed0efb30b87298fa30000000000f20"
            "52a0100000003000000210227d85ba011276cf25b51df6a188b75e604b3"
            "8770a462b2d0e9fb2fc839ef5d3faf07f001dd38e9b4a43d07d5d449cc0"
            "f7d2888d96b82962b3ce516d1083c0e031773487fc3c4f2e38acd1db974"
            "1321b91a79b82d1c2cfd47793261e4ba003cf5")

        wrong_order = (
            "c964aa6fde575e4ce8404581c7be874e21023beefdde700a6bc0203"
            "6335b4df141c8bc67bb05a971f5ac2745fd683797dde30305d427b7"
            "06705a5d4b6a368a231d6db62abacf8c29bc32b61e7f65a0a6976aa"
            "8b86b687bc0260e821e4f0200b9d3bf6d2102449fb5237efe8f647d"
            "32e8b64f06c22d1d40368eaca2a71ffc6a13ecc8bce68052365271b"
            "6c71189f5cd7e3b694b77b579080f0b35bae567b96590ab6aa3019b"
            "018ff9f061f52f1426bdb195d4b6d4dff5114cee90e33dabf0c588e"
            "badf7774418f54247f6390791706af36fac782302479898b5273f9e"
            "51a92cb1fb5af43deeb6c8c269403d30ffcb380300134398c42103e"
            "49f9df52de2dea81cf7838b82521b69f2ea360f1c4eed9e6c89b7d0"
            "f9e645efa08e97ea0c60e1f0a064fbf08989c084707082727e85dcb"
            "9f79bb503f76ee6c8dad42a07ef15c89b3750a5631d604b21fafff0"
            "f4de354ade95c2f28160ae549af0d4ce48c4ca9d0714b1fa5192027"
            "0f8575e0af610f07b4e602a018ecdbb649b64fff614c0026e9fc8e0"
            "030092533d422103aac52f4cfca700e7e9824298e0184755112e32f"
            "359c832f5f6ad2ef62a2c024af812d6d7f2ecc6223a774e19bce1fb"
            "20d94d6b01ea693638f55c74fdaa5358fa9239d03e4caf3d817e8f7"
            "48ccad55a27b9d365db06ad5a0b779ac385f3dc8710")

        self.log.info(
            "Check the verifyavalancheproof and sendavalancheproof RPCs")

        if self.is_wallet_compiled():
            self.log.info(
                "Check a proof with the maximum number of UTXO is valid")
            new_blocks = node.generate(AVALANCHE_MAX_PROOF_STAKES // 10 + 1)
            # confirm the coinbase UTXOs
            node.generate(101)
            too_many_stakes = create_stakes(node, new_blocks,
                                            AVALANCHE_MAX_PROOF_STAKES + 1)
            maximum_stakes = too_many_stakes[:-1]

            good_proof = node.buildavalancheproof(proof_sequence,
                                                  proof_expiration,
                                                  wif_privkey, maximum_stakes)

            too_many_utxos = node.buildavalancheproof(proof_sequence,
                                                      proof_expiration,
                                                      wif_privkey,
                                                      too_many_stakes)

            assert node.verifyavalancheproof(good_proof)

        for rpc in [node.verifyavalancheproof, node.sendavalancheproof]:
            assert_raises_rpc_error(-22, "Proof must be an hexadecimal string",
                                    rpc, "f00")
            assert_raises_rpc_error(-22, "Proof has invalid format", rpc,
                                    "f00d")

            def check_rpc_failure(proof, message):
                assert_raises_rpc_error(-8, "The proof is invalid: " + message,
                                        rpc, proof)

            check_rpc_failure(no_stake, "no-stake")
            check_rpc_failure(dust, "amount-below-dust-threshold")
            check_rpc_failure(duplicate_stake, "duplicated-stake")
            check_rpc_failure(missing_stake, "utxo-missing-or-spent")
            check_rpc_failure(bad_sig, "invalid-stake-signature")
            check_rpc_failure(wrong_order, "wrong-stake-ordering")
            if self.is_wallet_compiled():
                check_rpc_failure(too_many_utxos, "too-many-utxos")

        conflicting_utxo = node.buildavalancheproof(proof_sequence + 1,
                                                    proof_expiration,
                                                    wif_privkey, stakes)
        assert_raises_rpc_error(
            -8, "The proof has conflicting utxo with an existing proof",
            node.sendavalancheproof, conflicting_utxo)

        # Clear the proof pool
        self.restart_node(0)

        # Good proof
        assert node.verifyavalancheproof(proof)

        peer = node.add_p2p_connection(P2PInterface())

        proofid = FromHex(LegacyAvalancheProof(), proof).proofid
        node.sendavalancheproof(proof)
        assert proofid in get_proof_ids(node)

        def inv_found():
            with p2p_lock:
                return peer.last_message.get(
                    "inv") and peer.last_message["inv"].inv[-1].hash == proofid

        self.wait_until(inv_found)

        self.log.info("Check the getrawproof RPC")

        raw_proof = node.getrawavalancheproof("{:064x}".format(proofid))
        assert_equal(raw_proof['proof'], proof)
        assert_equal(raw_proof['orphan'], False)

        assert_raises_rpc_error(-8, "Proof not found",
                                node.getrawavalancheproof, '0' * 64)

        # Orphan the proof by sending the stake
        raw_tx = node.createrawtransaction([{
            "txid": stakes[-1]["txid"],
            "vout": 0
        }], {
            ADDRESS_ECREG_UNSPENDABLE:
            stakes[-1]["amount"] - Decimal('10000')
        })
        signed_tx = node.signrawtransactionwithkey(raw_tx, [addrkey0.key])
        node.sendrawtransaction(signed_tx["hex"])
        node.generate(1)
        self.wait_until(lambda: proofid not in get_proof_ids(node))

        raw_proof = node.getrawavalancheproof("{:064x}".format(proofid))
        assert_equal(raw_proof['proof'], proof)
        assert_equal(raw_proof['orphan'], True)

        self.log.info("Bad proof should be rejected at startup")

        self.stop_node(0)

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avasessionkey=0",
            ],
            expected_msg="Error: The avalanche session key is invalid.",
        )

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avaproof={}".format(proof),
            ],
            expected_msg=
            "Error: The avalanche master key is missing for the avalanche proof.",
        )

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=0",
            ],
            expected_msg="Error: The avalanche master key is invalid.",
        )

        def check_proof_init_error(proof, message):
            node.assert_start_raises_init_error(
                self.extra_args[0] + [
                    "-avaproof={}".format(proof),
                    "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
                ],
                expected_msg="Error: " + message,
            )

        check_proof_init_error(no_stake, "The avalanche proof has no stake.")
        check_proof_init_error(dust, "The avalanche proof stake is too low.")
        check_proof_init_error(dust2, "The avalanche proof stake is too low.")
        check_proof_init_error(duplicate_stake,
                               "The avalanche proof has duplicated stake.")
        check_proof_init_error(
            bad_sig, "The avalanche proof has invalid stake signatures.")
        if self.is_wallet_compiled():
            # The too many utxos case creates a proof which is that large that it
            # cannot fit on the command line
            append_config(node.datadir, ["avaproof={}".format(too_many_utxos)])
            node.assert_start_raises_init_error(
                self.extra_args[0] + [
                    "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
                ],
                expected_msg="Error: The avalanche proof has too many utxos.",
                match=ErrorMatch.PARTIAL_REGEX,
            )

        # Master private key mismatch
        random_privkey = ECKey()
        random_privkey.generate()
        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey={}".format(
                    bytes_to_wif(random_privkey.get_bytes())),
            ],
            expected_msg=
            "Error: The master key does not match the proof public key.",
        )

        self.log.info("Bad delegation should be rejected at startup")

        def check_delegation_init_error(delegation, message):
            node.assert_start_raises_init_error(
                self.extra_args[0] + [
                    "-avadelegation={}".format(delegation),
                    "-avaproof={}".format(proof),
                    "-avamasterkey={}".format(
                        bytes_to_wif(delegated_privkey.get_bytes())),
                ],
                expected_msg="Error: " + message,
            )

        check_delegation_init_error(
            AvalancheDelegation().serialize().hex(),
            "The delegation does not match the proof.")

        bad_level_sig = FromHex(AvalancheDelegation(), delegation)
        # Tweak some key to cause the signature to mismatch
        bad_level_sig.levels[-2].pubkey = bytes.fromhex(proof_master)
        check_delegation_init_error(
            bad_level_sig.serialize().hex(),
            "The avalanche delegation has invalid signatures.")

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avadelegation={}".format(delegation),
                "-avaproof={}".format(proof),
                "-avamasterkey={}".format(
                    bytes_to_wif(random_privkey.get_bytes())),
            ],
            expected_msg=
            "Error: The master key does not match the delegation public key.",
        )
Ejemplo n.º 7
0
    def run_test(self):
        # peers that we expect to be protected from eviction
        protected_peers = set()
        current_peer = -1
        node = self.nodes[0]
        blocks = node.generatetoaddress(
            101,
            node.get_deterministic_priv_key().address)

        self.log.info(
            "Create 4 peers and protect them from eviction by sending us a block"
        )
        for _ in range(4):
            block_peer = node.add_p2p_connection(SlowP2PDataStore())
            current_peer += 1
            block_peer.sync_with_ping()
            best_block = node.getbestblockhash()
            tip = int(best_block, 16)
            best_block_time = node.getblock(best_block)['time']
            block = create_block(tip,
                                 create_coinbase(node.getblockcount() + 1),
                                 best_block_time + 1)
            block.solve()
            block_peer.send_blocks_and_test([block], node, success=True)
            protected_peers.add(current_peer)

        self.log.info(
            "Create 4 peers and protect them from eviction by sending us a proof"
        )
        privkey = ECKey()
        privkey.generate()
        wif_privkey = bytes_to_wif(privkey.get_bytes())
        pubkey = privkey.get_pubkey()

        stakes = create_coinbase_stakes(node, blocks,
                                        node.get_deterministic_priv_key().key)

        for i in range(4):
            proof_peer = node.add_p2p_connection(SlowP2PDataStore())
            current_peer += 1
            proof_peer.sync_with_ping()

            proof = node.buildavalancheproof(42, 2000000000, wif_privkey,
                                             [stakes[i]])

            avaproof_msg = msg_avaproof()
            avaproof_msg.proof = FromHex(LegacyAvalancheProof(), proof)
            proof_peer.send_message(avaproof_msg)
            protected_peers.add(current_peer)

        self.log.info(
            "Create 5 slow-pinging peers, making them eviction candidates")
        for _ in range(5):
            node.add_p2p_connection(SlowP2PInterface())
            current_peer += 1

        self.log.info(
            "Create 4 peers and protect them from eviction by sending us a tx")
        for i in range(4):
            txpeer = node.add_p2p_connection(SlowP2PInterface())
            current_peer += 1
            txpeer.sync_with_ping()

            prevtx = node.getblock(node.getblockhash(i + 1), 2)['tx'][0]
            rawtx = node.createrawtransaction(
                inputs=[{
                    'txid': prevtx['txid'],
                    'vout': 0
                }],
                outputs=[{
                    node.get_deterministic_priv_key().address:
                    50000000 - 1250.00
                }],
            )
            sigtx = node.signrawtransactionwithkey(
                hexstring=rawtx,
                privkeys=[node.get_deterministic_priv_key().key],
                prevtxs=[{
                    'txid':
                    prevtx['txid'],
                    'vout':
                    0,
                    'amount':
                    prevtx['vout'][0]['value'],
                    'scriptPubKey':
                    prevtx['vout'][0]['scriptPubKey']['hex'],
                }],
            )['hex']
            txpeer.send_message(msg_tx(FromHex(CTransaction(), sigtx)))
            protected_peers.add(current_peer)

        self.log.info(
            "Create 8 peers and protect them from eviction by having faster pings"
        )
        for _ in range(8):
            fastpeer = node.add_p2p_connection(P2PInterface())
            current_peer += 1
            self.wait_until(lambda: "ping" in fastpeer.last_message,
                            timeout=10)

        self.log.info(
            "Create 128 peers and protect them from eviction by sending an avahello message"
        )

        proof = node.buildavalancheproof(42, 2000000000, wif_privkey,
                                         [stakes[0]])
        proof_obj = FromHex(LegacyAvalancheProof(), proof)
        delegation = node.delegateavalancheproof(
            f"{proof_obj.limited_proofid:064x}",
            bytes_to_wif(privkey.get_bytes()),
            pubkey.get_bytes().hex(),
        )

        for _ in range(128):
            avapeer = node.add_p2p_connection(SlowAvaP2PInterface())
            current_peer += 1
            avapeer.sync_with_ping()
            avapeer.send_avahello(delegation, privkey)

        # Make sure by asking the node what the actual min pings are
        peerinfo = node.getpeerinfo()
        pings = {}
        for i in range(len(peerinfo)):
            pings[i] = peerinfo[i]['minping'] if 'minping' in peerinfo[
                i] else 1000000
        sorted_pings = sorted(pings.items(), key=lambda x: x[1])

        # Usually the 8 fast peers are protected. In rare case of unreliable pings,
        # one of the slower peers might have a faster min ping though.
        for i in range(8):
            protected_peers.add(sorted_pings[i][0])

        self.log.info("Create peer that triggers the eviction mechanism")
        node.add_p2p_connection(SlowP2PInterface())

        # One of the non-protected peers must be evicted. We can't be sure which one because
        # 4 peers are protected via netgroup, which is identical for all peers,
        # and the eviction mechanism doesn't preserve the order of identical
        # elements.
        evicted_peers = []
        for i in range(len(node.p2ps)):
            if not node.p2ps[i].is_connected:
                evicted_peers.append(i)

        self.log.info("Test that one peer was evicted")
        self.log.debug("{} evicted peer: {}".format(len(evicted_peers),
                                                    set(evicted_peers)))
        assert_equal(len(evicted_peers), 1)

        self.log.info("Test that no peer expected to be protected was evicted")
        self.log.debug("{} protected peers: {}".format(len(protected_peers),
                                                       protected_peers))
        assert evicted_peers[0] not in protected_peers
Ejemplo n.º 8
0
    def run_test(self):
        node = self.nodes[0]

        # Build a fake quorum of nodes.
        def get_node():
            n = TestNode()
            node.add_p2p_connection(n, services=NODE_NETWORK | NODE_AVALANCHE)
            n.wait_for_verack()

            # Get our own node id so we can use it later.
            n.nodeid = node.getpeerinfo()[-1]['id']

            return n

        def get_quorum():
            return [get_node() for _ in range(0, QUORUM_NODE_COUNT)]

        # Pick on node from the quorum for polling.
        quorum = get_quorum()
        poll_node = quorum[0]

        # Generate many block and poll for them.
        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(100, addrkey0.address)
        # Use the first coinbase to create a stake
        stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key)

        fork_node = self.nodes[1]
        # Make sure the fork node has synced the blocks
        self.sync_blocks([node, fork_node])

        # Get the key so we can verify signatures.
        avakey = ECPubKey()
        avakey.set(bytes.fromhex(node.getavalanchekey()))

        self.log.info("Poll for the chain tip...")
        best_block_hash = int(node.getbestblockhash(), 16)
        poll_node.send_poll([best_block_hash])

        def assert_response(expected):
            response = poll_node.wait_for_avaresponse()
            r = response.response
            assert_equal(r.cooldown, 0)

            # Verify signature.
            assert avakey.verify_schnorr(response.sig, r.get_hash())

            votes = r.votes
            assert_equal(len(votes), len(expected))
            for i in range(0, len(votes)):
                assert_equal(repr(votes[i]), repr(expected[i]))

        assert_response([AvalancheVote(BLOCK_ACCEPTED, best_block_hash)])

        self.log.info("Poll for a selection of blocks...")
        various_block_hashes = [
            int(node.getblockhash(0), 16),
            int(node.getblockhash(1), 16),
            int(node.getblockhash(10), 16),
            int(node.getblockhash(25), 16),
            int(node.getblockhash(42), 16),
            int(node.getblockhash(96), 16),
            int(node.getblockhash(99), 16),
            int(node.getblockhash(100), 16),
        ]

        poll_node.send_poll(various_block_hashes)
        assert_response(
            [AvalancheVote(BLOCK_ACCEPTED, h) for h in various_block_hashes])

        self.log.info(
            "Poll for a selection of blocks, but some are now invalid...")
        invalidated_block = node.getblockhash(76)
        node.invalidateblock(invalidated_block)
        # We need to send the coin to a new address in order to make sure we do
        # not regenerate the same block.
        node.generatetoaddress(
            26, 'bchreg:pqv2r67sgz3qumufap3h2uuj0zfmnzuv8v7ej0fffv')
        node.reconsiderblock(invalidated_block)

        poll_node.send_poll(various_block_hashes)
        assert_response([
            AvalancheVote(BLOCK_ACCEPTED, h) for h in various_block_hashes[:5]
        ] + [AvalancheVote(BLOCK_FORK, h) for h in various_block_hashes[-3:]])

        self.log.info("Poll for unknown blocks...")
        various_block_hashes = [
            int(node.getblockhash(0), 16),
            int(node.getblockhash(25), 16),
            int(node.getblockhash(42), 16),
            various_block_hashes[5],
            various_block_hashes[6],
            various_block_hashes[7],
            random.randrange(1 << 255, (1 << 256) - 1),
            random.randrange(1 << 255, (1 << 256) - 1),
            random.randrange(1 << 255, (1 << 256) - 1),
        ]
        poll_node.send_poll(various_block_hashes)
        assert_response([
            AvalancheVote(BLOCK_ACCEPTED, h) for h in various_block_hashes[:3]
        ] + [AvalancheVote(BLOCK_FORK, h)
             for h in various_block_hashes[3:6]] + [
                 AvalancheVote(BLOCK_UNKNOWN, h)
                 for h in various_block_hashes[-3:]
             ])

        self.log.info("Trigger polling from the node...")
        # duplicate the deterministic sig test from src/test/key_tests.cpp
        privkey = ECKey()
        privkey.set(
            bytes.fromhex(
                "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"
            ), True)
        pubkey = privkey.get_pubkey()

        proof_sequence = 11
        proof_expiration = 12
        proof = node.buildavalancheproof(proof_sequence, proof_expiration,
                                         pubkey.get_bytes().hex(), stakes)

        # Activate the quorum.
        for n in quorum:
            success = node.addavalanchenode(n.nodeid,
                                            pubkey.get_bytes().hex(), proof)
            assert success is True

        self.log.info("Testing getavalanchepeerinfo...")
        avapeerinfo = node.getavalanchepeerinfo()
        # There is a single peer because all nodes share the same proof.
        assert_equal(len(avapeerinfo), 1)
        assert_equal(avapeerinfo[0]["peerid"], 0)
        assert_equal(avapeerinfo[0]["nodecount"], len(quorum))
        # The first avalanche node index is 1, because 0 is self.nodes[1].
        assert_equal(sorted(avapeerinfo[0]["nodes"]),
                     list(range(1, QUORUM_NODE_COUNT + 1)))
        assert_equal(avapeerinfo[0]["sequence"], proof_sequence)
        assert_equal(avapeerinfo[0]["expiration"], proof_expiration)
        assert_equal(avapeerinfo[0]["master"], pubkey.get_bytes().hex())
        assert_equal(avapeerinfo[0]["proof"], proof)
        assert_equal(len(avapeerinfo[0]["stakes"]), 1)
        assert_equal(avapeerinfo[0]["stakes"][0]["txid"], stakes[0]['txid'])

        def can_find_block_in_poll(hash, resp=BLOCK_ACCEPTED):
            found_hash = False
            for n in quorum:
                poll = n.get_avapoll_if_available()

                # That node has not received a poll
                if poll is None:
                    continue

                # We got a poll, check for the hash and repond
                votes = []
                for inv in poll.invs:
                    # Vote yes to everything
                    r = BLOCK_ACCEPTED

                    # Look for what we expect
                    if inv.hash == hash:
                        r = resp
                        found_hash = True

                    votes.append(AvalancheVote(r, inv.hash))

                n.send_avaresponse(poll.round, votes, privkey)

            return found_hash

        # Now that we have a peer, we should start polling for the tip.
        hash_tip = int(node.getbestblockhash(), 16)
        wait_until(lambda: can_find_block_in_poll(hash_tip), timeout=5)

        # Make sure the fork node has synced the blocks
        self.sync_blocks([node, fork_node])

        # Create a fork 2 blocks deep. This should trigger polling.
        fork_node.invalidateblock(fork_node.getblockhash(100))
        fork_address = fork_node.get_deterministic_priv_key().address
        fork_node.generatetoaddress(2, fork_address)

        # Because the new tip is a deep reorg, the node will not accept it
        # right away, but poll for it.
        def parked_block(blockhash):
            for tip in node.getchaintips():
                if tip["hash"] == blockhash:
                    assert tip["status"] != "active"
                    return tip["status"] == "parked"
            return False

        fork_tip = fork_node.getbestblockhash()
        wait_until(lambda: parked_block(fork_tip))

        self.log.info("Answer all polls to finalize...")

        hash_to_find = int(fork_tip, 16)

        def has_accepted_new_tip():
            can_find_block_in_poll(hash_to_find)
            return node.getbestblockhash() == fork_tip

        # Because everybody answers yes, the node will accept that block.
        wait_until(has_accepted_new_tip, timeout=15)
        assert_equal(node.getbestblockhash(), fork_tip)

        self.log.info("Answer all polls to park...")
        node.generate(1)

        tip_to_park = node.getbestblockhash()
        hash_to_find = int(tip_to_park, 16)
        assert (tip_to_park != fork_tip)

        def has_parked_new_tip():
            can_find_block_in_poll(hash_to_find, BLOCK_PARKED)
            return node.getbestblockhash() == fork_tip

        # Because everybody answers no, the node will park that block.
        wait_until(has_parked_new_tip, timeout=15)
        assert_equal(node.getbestblockhash(), fork_tip)

        self.log.info(
            "Check the node is signalling the avalanche service bit only if there is a proof."
        )
        assert_equal(
            int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE,
            0)

        # Restart the node
        self.restart_node(
            0, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])

        assert_equal(
            int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE,
            NODE_AVALANCHE)

        self.log.info("Test the avahello signature")
        quorum = get_quorum()
        poll_node = quorum[0]

        avahello = poll_node.wait_for_avahello().hello

        avakey.set(bytes.fromhex(node.getavalanchekey()))
        assert avakey.verify_schnorr(avahello.sig,
                                     avahello.get_sighash(poll_node))
Ejemplo n.º 9
0
    def run_test(self):
        # Turn off node 1 while node 0 mines blocks to generate stakes,
        # so that we can later try starting node 1 with an orphan proof.
        self.stop_node(1)

        node = self.nodes[0]

        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(100, addrkey0.address)

        self.log.info(
            "Make build a valid proof and restart the node to use it")
        privkey = ECKey()
        privkey.set(
            bytes.fromhex(
                "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"
            ), True)

        def get_hex_pubkey(privkey):
            return privkey.get_pubkey().get_bytes().hex()

        proof_master = get_hex_pubkey(privkey)
        proof_sequence = 11
        proof_expiration = 12
        stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key)
        proof = node.buildavalancheproof(proof_sequence, proof_expiration,
                                         proof_master, stakes)

        self.log.info("Test decodeavalancheproof RPC")
        proofobj = FromHex(AvalancheProof(), proof)
        decodedproof = node.decodeavalancheproof(proof)
        limited_id_hex = f"{proofobj.limited_proofid:0{64}x}"
        assert_equal(decodedproof["sequence"], proof_sequence)
        assert_equal(decodedproof["expiration"], proof_expiration)
        assert_equal(decodedproof["master"], proof_master)
        assert_equal(decodedproof["proofid"], f"{proofobj.proofid:0{64}x}")
        assert_equal(decodedproof["limitedid"], limited_id_hex)
        assert_equal(decodedproof["stakes"][0]["txid"], stakes[0]["txid"])
        assert_equal(decodedproof["stakes"][0]["vout"], stakes[0]["vout"])
        assert_equal(decodedproof["stakes"][0]["height"], stakes[0]["height"])
        assert_equal(decodedproof["stakes"][0]["iscoinbase"],
                     stakes[0]["iscoinbase"])
        assert_equal(decodedproof["stakes"][0]["signature"],
                     base64.b64encode(proofobj.stakes[0].sig).decode("ascii"))

        # Invalid hex (odd number of hex digits)
        assert_raises_rpc_error(-22, "Proof must be an hexadecimal string",
                                node.decodeavalancheproof, proof[:-1])
        # Valid hex but invalid proof
        assert_raises_rpc_error(-22, "Proof has invalid format",
                                node.decodeavalancheproof, proof[:-2])

        # Restart the node with this proof
        self.restart_node(
            0, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])

        self.log.info("The proof is registered at first chaintip update")
        assert_equal(len(node.getavalanchepeerinfo()), 0)
        node.generate(1)
        wait_until(lambda: len(node.getavalanchepeerinfo()) == 1, timeout=5)

        # This case will occur for users building proofs with a third party
        # tool and then starting a new node that is not yet aware of the
        # transactions used for stakes.
        self.log.info("Start a node with an orphan proof")

        self.start_node(
            1, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])
        # Mine a block to trigger an attempt at registering the proof
        self.nodes[1].generate(1)
        wait_for_proof(self.nodes[1],
                       f"{proofobj.proofid:0{64}x}",
                       expect_orphan=True)

        self.log.info("Connect to an up-to-date node to unorphan the proof")
        connect_nodes(self.nodes[1], node)
        self.sync_all()
        wait_for_proof(self.nodes[1],
                       f"{proofobj.proofid:0{64}x}",
                       expect_orphan=False)

        self.log.info("Generate delegations for the proof")

        # Stack up a few delegation levels
        def gen_privkey():
            pk = ECKey()
            pk.generate()
            return pk

        delegator_privkey = privkey
        delegation = None
        for _ in range(10):
            delegated_privkey = gen_privkey()
            delegation = node.delegateavalancheproof(
                limited_id_hex,
                bytes_to_wif(delegator_privkey.get_bytes()),
                get_hex_pubkey(delegated_privkey),
                delegation,
            )
            delegator_privkey = delegated_privkey

        random_privkey = gen_privkey()
        random_pubkey = get_hex_pubkey(random_privkey)

        # Invalid proof
        no_stake = node.buildavalancheproof(proof_sequence, proof_expiration,
                                            proof_master, [])

        # Invalid privkey
        assert_raises_rpc_error(
            -5,
            "The private key is invalid",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(bytes(32)),
            random_pubkey,
        )

        # Invalid delegation
        bad_dg = AvalancheDelegation()
        assert_raises_rpc_error(
            -8,
            "The delegation does not match the proof",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            bad_dg.serialize().hex(),
        )

        # Still invalid, but with a matching proofid
        bad_dg.limited_proofid = proofobj.limited_proofid
        bad_dg.proof_master = proofobj.master
        bad_dg.levels = [AvalancheDelegationLevel()]
        assert_raises_rpc_error(
            -8,
            "The delegation is invalid",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            bad_dg.serialize().hex(),
        )

        # Wrong privkey, match the proof but does not match the delegation
        assert_raises_rpc_error(
            -5,
            "The private key does not match the delegation",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            delegation,
        )

        # Delegation not hex
        assert_raises_rpc_error(
            -22,
            "Delegation must be an hexadecimal string.",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            "f00",
        )
        # Delegation is hex but ill-formed
        assert_raises_rpc_error(
            -22,
            "Delegation has invalid format",
            node.delegateavalancheproof,
            limited_id_hex,
            bytes_to_wif(privkey.get_bytes()),
            random_pubkey,
            "dead",
        )

        # Test invalid proofs
        dust = node.buildavalancheproof(
            proof_sequence, proof_expiration, proof_master,
            create_coinbase_stakes(node, [blockhashes[0]],
                                   addrkey0.key,
                                   amount="0"))

        dust_amount = Decimal(f"{PROOF_DUST_THRESHOLD * 0.9999:.4f}")
        dust2 = node.buildavalancheproof(
            proof_sequence, proof_expiration, proof_master,
            create_coinbase_stakes(node, [blockhashes[0]],
                                   addrkey0.key,
                                   amount=str(dust_amount)))

        duplicate_stake = node.buildavalancheproof(
            proof_sequence, proof_expiration, proof_master,
            create_coinbase_stakes(node, [blockhashes[0]] * 2, addrkey0.key))

        missing_stake = node.buildavalancheproof(
            proof_sequence, proof_expiration, proof_master,
            [{
                'txid': '0' * 64,
                'vout': 0,
                'amount': 10000000,
                'height': 42,
                'iscoinbase': False,
                'privatekey': addrkey0.key,
            }])

        bad_sig = (
            "0b000000000000000c0000000000000021030b4c866585dd868a9d62348"
            "a9cd008d6a312937048fff31670e7e920cfc7a7440105c5f72f5d6da3085"
            "583e75ee79340eb4eff208c89988e7ed0efb30b87298fa30000000000f20"
            "52a0100000003000000210227d85ba011276cf25b51df6a188b75e604b3"
            "8770a462b2d0e9fb2fc839ef5d3faf07f001dd38e9b4a43d07d5d449cc0"
            "f7d2888d96b82962b3ce516d1083c0e031773487fc3c4f2e38acd1db974"
            "1321b91a79b82d1c2cfd47793261e4ba003cf5")

        self.log.info(
            "Check the verifyavalancheproof and sendavalancheproof RPCs")

        if self.is_wallet_compiled():
            self.log.info(
                "Check a proof with the maximum number of UTXO is valid")
            new_blocks = node.generate(AVALANCHE_MAX_PROOF_STAKES // 10 + 1)
            # confirm the coinbase UTXOs
            node.generate(101)
            too_many_stakes = create_stakes(node, new_blocks,
                                            AVALANCHE_MAX_PROOF_STAKES + 1)
            maximum_stakes = too_many_stakes[:-1]

            good_proof = node.buildavalancheproof(proof_sequence,
                                                  proof_expiration,
                                                  proof_master, maximum_stakes)

            too_many_utxos = node.buildavalancheproof(proof_sequence,
                                                      proof_expiration,
                                                      proof_master,
                                                      too_many_stakes)

            assert node.verifyavalancheproof(good_proof)

        for rpc in [node.verifyavalancheproof, node.sendavalancheproof]:
            assert_raises_rpc_error(-22, "Proof must be an hexadecimal string",
                                    rpc, "f00")
            assert_raises_rpc_error(-22, "Proof has invalid format", rpc,
                                    "f00d")

            def check_rpc_failure(proof, message):
                assert_raises_rpc_error(-8, "The proof is invalid: " + message,
                                        rpc, proof)

            check_rpc_failure(no_stake, "no-stake")
            check_rpc_failure(dust, "amount-below-dust-threshold")
            check_rpc_failure(duplicate_stake, "duplicated-stake")
            check_rpc_failure(missing_stake, "utxo-missing-or-spent")
            check_rpc_failure(bad_sig, "invalid-signature")
            if self.is_wallet_compiled():
                check_rpc_failure(too_many_utxos, "too-many-utxos")

        conflicting_utxo = node.buildavalancheproof(proof_sequence + 1,
                                                    proof_expiration,
                                                    proof_master, stakes)
        assert_raises_rpc_error(
            -8, "The proof has conflicting utxo with an existing proof",
            node.sendavalancheproof, conflicting_utxo)

        # Good proof
        assert node.verifyavalancheproof(proof)

        peer = node.add_p2p_connection(P2PInterface())

        proofid = FromHex(AvalancheProof(), proof).proofid
        node.sendavalancheproof(proof)
        assert proofid in get_proof_ids(node)

        def inv_found():
            with p2p_lock:
                return peer.last_message.get(
                    "inv") and peer.last_message["inv"].inv[-1].hash == proofid

        wait_until(inv_found)

        self.log.info("Check the getrawproof RPC")

        raw_proof = node.getrawavalancheproof("{:064x}".format(proofid))
        assert_equal(raw_proof['proof'], proof)
        assert_equal(raw_proof['orphan'], False)

        assert_raises_rpc_error(-8, "Proof not found",
                                node.getrawavalancheproof, '0' * 64)

        # Orphan the proof by sending the stake
        raw_tx = node.createrawtransaction([{
            "txid": stakes[-1]["txid"],
            "vout": 0
        }], {
            ADDRESS_BCHREG_UNSPENDABLE:
            stakes[-1]["amount"] - Decimal('10000')
        })
        signed_tx = node.signrawtransactionwithkey(raw_tx, [addrkey0.key])
        node.sendrawtransaction(signed_tx["hex"])
        node.generate(1)
        wait_until(lambda: proofid not in get_proof_ids(node))

        raw_proof = node.getrawavalancheproof("{:064x}".format(proofid))
        assert_equal(raw_proof['proof'], proof)
        assert_equal(raw_proof['orphan'], True)

        self.log.info("Bad proof should be rejected at startup")

        self.stop_node(0)

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avasessionkey=0",
            ],
            expected_msg="Error: The avalanche session key is invalid.",
        )

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avaproof={}".format(proof),
            ],
            expected_msg=
            "Error: The avalanche master key is missing for the avalanche proof.",
        )

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=0",
            ],
            expected_msg="Error: The avalanche master key is invalid.",
        )

        def check_proof_init_error(proof, message):
            node.assert_start_raises_init_error(
                self.extra_args[0] + [
                    "-avaproof={}".format(proof),
                    "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
                ],
                expected_msg="Error: " + message,
            )

        check_proof_init_error(no_stake, "The avalanche proof has no stake.")
        check_proof_init_error(dust, "The avalanche proof stake is too low.")
        check_proof_init_error(dust2, "The avalanche proof stake is too low.")
        check_proof_init_error(duplicate_stake,
                               "The avalanche proof has duplicated stake.")
        check_proof_init_error(
            bad_sig, "The avalanche proof has invalid stake signatures.")
        if self.is_wallet_compiled():
            # The too many utxos case creates a proof which is that large that it
            # cannot fit on the command line
            append_config(node.datadir, ["avaproof={}".format(too_many_utxos)])
            node.assert_start_raises_init_error(
                self.extra_args[0] + [
                    "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
                ],
                expected_msg="Error: The avalanche proof has too many utxos.",
                match=ErrorMatch.PARTIAL_REGEX,
            )

        # Master private key mismatch
        random_privkey = ECKey()
        random_privkey.generate()
        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey={}".format(
                    bytes_to_wif(random_privkey.get_bytes())),
            ],
            expected_msg=
            "Error: The master key does not match the proof public key.",
        )

        self.log.info("Bad delegation should be rejected at startup")

        def check_delegation_init_error(delegation, message):
            node.assert_start_raises_init_error(
                self.extra_args[0] + [
                    "-avadelegation={}".format(delegation),
                    "-avaproof={}".format(proof),
                    "-avamasterkey={}".format(
                        bytes_to_wif(delegated_privkey.get_bytes())),
                ],
                expected_msg="Error: " + message,
            )

        check_delegation_init_error(
            AvalancheDelegation().serialize().hex(),
            "The delegation does not match the proof.")

        bad_level_sig = FromHex(AvalancheDelegation(), delegation)
        # Tweak some key to cause the signature to mismatch
        bad_level_sig.levels[-2].pubkey = bytes.fromhex(proof_master)
        check_delegation_init_error(
            bad_level_sig.serialize().hex(),
            "The avalanche delegation has invalid signatures.")

        node.assert_start_raises_init_error(
            self.extra_args[0] + [
                "-avadelegation={}".format(delegation),
                "-avaproof={}".format(proof),
                "-avamasterkey={}".format(
                    bytes_to_wif(random_privkey.get_bytes())),
            ],
            expected_msg=
            "Error: The master key does not match the delegation public key.",
        )
    def run_test(self):
        node = self.nodes[0]

        # duplicate the deterministic sig test from src/test/key_tests.cpp
        privkey = ECKey()
        privkey.set(
            bytes.fromhex(
                "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"
            ), True)
        pubkey = privkey.get_pubkey()

        self.log.info(
            "Check the node is signalling the avalanche service bit only if there is a proof."
        )
        assert_equal(
            int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE,
            0)

        # Create stakes by mining blocks
        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(2, addrkey0.address)
        stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key)

        proof_sequence = 11
        proof_expiration = 12
        proof = node.buildavalancheproof(proof_sequence, proof_expiration,
                                         pubkey.get_bytes().hex(), stakes)

        # Restart the node
        self.restart_node(
            0, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])

        assert_equal(
            int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE,
            NODE_AVALANCHE)

        def check_avahello(args):
            # Restart the node with the given args
            self.restart_node(0, self.extra_args[0] + args)

            peer = get_ava_p2p_interface(node)

            avahello = peer.wait_for_avahello().hello

            avakey = ECPubKey()
            avakey.set(bytes.fromhex(node.getavalanchekey()))
            assert avakey.verify_schnorr(avahello.sig,
                                         avahello.get_sighash(peer))

        self.log.info(
            "Test the avahello signature with a generated delegation")
        check_avahello([
            "-avaproof={}".format(proof),
            "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN"
        ])

        master_key = ECKey()
        master_key.generate()
        limited_id = FromHex(AvalancheProof(), proof).limited_proofid
        delegation = node.delegateavalancheproof(
            f"{limited_id:0{64}x}",
            bytes_to_wif(privkey.get_bytes()),
            master_key.get_pubkey().get_bytes().hex(),
        )

        self.log.info("Test the avahello signature with a supplied delegation")
        check_avahello([
            "-avaproof={}".format(proof),
            "-avadelegation={}".format(delegation),
            "-avamasterkey={}".format(bytes_to_wif(master_key.get_bytes())),
        ])

        stakes = create_coinbase_stakes(node, [blockhashes[1]], addrkey0.key)
        interface_proof_hex = node.buildavalancheproof(
            proof_sequence, proof_expiration,
            pubkey.get_bytes().hex(), stakes)
        limited_id = FromHex(AvalancheProof(),
                             interface_proof_hex).limited_proofid

        # delegate
        delegated_key = ECKey()
        delegated_key.generate()
        interface_delegation_hex = node.delegateavalancheproof(
            f"{limited_id:0{64}x}", bytes_to_wif(privkey.get_bytes()),
            delegated_key.get_pubkey().get_bytes().hex(), None)

        self.log.info("Test that wrong avahello signature causes a ban")
        bad_interface = get_ava_p2p_interface(node)
        wrong_key = ECKey()
        wrong_key.generate()
        with node.assert_debug_log([
                "Misbehaving",
                "peer=1 (0 -> 100) BAN THRESHOLD EXCEEDED: invalid-avahello-signature"
        ]):
            bad_interface.send_avahello(interface_delegation_hex, wrong_key)
            bad_interface.wait_for_disconnect()

        self.log.info(
            'Check that receiving a valid avahello triggers a proof getdata request'
        )
        good_interface = get_ava_p2p_interface(node)
        proofid = good_interface.send_avahello(interface_delegation_hex,
                                               delegated_key)

        def getdata_found():
            with p2p_lock:
                return good_interface.last_message.get(
                    "getdata") and good_interface.last_message["getdata"].inv[
                        -1].hash == proofid

        wait_until(getdata_found)

        self.log.info('Check that we can download the proof from our peer')

        node_proofid = FromHex(AvalancheProof(), proof).proofid

        def wait_for_proof_validation():
            # Connect some blocks to trigger the proof verification
            node.generate(1)
            wait_until(lambda: node_proofid in get_proof_ids(node))

        wait_for_proof_validation()

        getdata = msg_getdata([CInv(MSG_AVA_PROOF, node_proofid)])

        self.log.info(
            "Proof has been inv'ed recently, check it can be requested")
        good_interface.send_message(getdata)

        def proof_received(peer):
            with p2p_lock:
                return peer.last_message.get("avaproof") and peer.last_message[
                    "avaproof"].proof.proofid == node_proofid

        wait_until(lambda: proof_received(good_interface))

        # Restart the node
        self.restart_node(
            0, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])
        wait_for_proof_validation()

        self.log.info(
            "The proof has not been announced, it cannot be requested")
        peer = get_ava_p2p_interface(node, services=NODE_NETWORK)
        peer.send_message(getdata)

        # Give enough time for the node to answer. Since we cannot check for a
        # non-event this is the best we can do
        time.sleep(2)
        assert not proof_received(peer)

        self.log.info("The proof is known for long enough to be requested")
        current_time = int(time.time())
        node.setmocktime(current_time + UNCONDITIONAL_RELAY_DELAY)

        peer.send_message(getdata)
        wait_until(lambda: proof_received(peer))
    def run_test(self):
        node = self.nodes[0]

        # Build a fake quorum of nodes.
        def get_quorum():
            return [
                get_ava_p2p_interface(node)
                for _ in range(0, QUORUM_NODE_COUNT)
            ]

        # Pick on node from the quorum for polling.
        quorum = get_quorum()
        poll_node = quorum[0]

        # Generate many block and poll for them.
        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(100, addrkey0.address)
        # Use the first coinbase to create a stake
        stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key)

        fork_node = self.nodes[1]
        # Make sure the fork node has synced the blocks
        self.sync_blocks([node, fork_node])

        # Get the key so we can verify signatures.
        avakey = ECPubKey()
        avakey.set(bytes.fromhex(node.getavalanchekey()))

        self.log.info("Poll for the chain tip...")
        best_block_hash = int(node.getbestblockhash(), 16)
        poll_node.send_poll([best_block_hash])

        def assert_response(expected):
            response = poll_node.wait_for_avaresponse()
            r = response.response
            assert_equal(r.cooldown, 0)

            # Verify signature.
            assert avakey.verify_schnorr(response.sig, r.get_hash())

            votes = r.votes
            assert_equal(len(votes), len(expected))
            for i in range(0, len(votes)):
                assert_equal(repr(votes[i]), repr(expected[i]))

        assert_response(
            [AvalancheVote(AvalancheVoteError.ACCEPTED, best_block_hash)])

        self.log.info("Poll for a selection of blocks...")
        various_block_hashes = [
            int(node.getblockhash(0), 16),
            int(node.getblockhash(1), 16),
            int(node.getblockhash(10), 16),
            int(node.getblockhash(25), 16),
            int(node.getblockhash(42), 16),
            int(node.getblockhash(96), 16),
            int(node.getblockhash(99), 16),
            int(node.getblockhash(100), 16),
        ]

        poll_node.send_poll(various_block_hashes)
        assert_response([
            AvalancheVote(AvalancheVoteError.ACCEPTED, h)
            for h in various_block_hashes
        ])

        self.log.info(
            "Poll for a selection of blocks, but some are now invalid...")
        invalidated_block = node.getblockhash(76)
        node.invalidateblock(invalidated_block)
        # We need to send the coin to a new address in order to make sure we do
        # not regenerate the same block.
        node.generatetoaddress(
            26, 'ecregtest:pqv2r67sgz3qumufap3h2uuj0zfmnzuv8v38gtrh5v')
        node.reconsiderblock(invalidated_block)

        poll_node.send_poll(various_block_hashes)
        assert_response([
            AvalancheVote(AvalancheVoteError.ACCEPTED, h)
            for h in various_block_hashes[:5]
        ] + [
            AvalancheVote(AvalancheVoteError.FORK, h)
            for h in various_block_hashes[-3:]
        ])

        self.log.info("Poll for unknown blocks...")
        various_block_hashes = [
            int(node.getblockhash(0), 16),
            int(node.getblockhash(25), 16),
            int(node.getblockhash(42), 16),
            various_block_hashes[5],
            various_block_hashes[6],
            various_block_hashes[7],
            random.randrange(1 << 255, (1 << 256) - 1),
            random.randrange(1 << 255, (1 << 256) - 1),
            random.randrange(1 << 255, (1 << 256) - 1),
        ]
        poll_node.send_poll(various_block_hashes)
        assert_response([
            AvalancheVote(AvalancheVoteError.ACCEPTED, h)
            for h in various_block_hashes[:3]
        ] + [
            AvalancheVote(AvalancheVoteError.FORK, h)
            for h in various_block_hashes[3:6]
        ] + [
            AvalancheVote(AvalancheVoteError.UNKNOWN, h)
            for h in various_block_hashes[-3:]
        ])

        self.log.info("Trigger polling from the node...")
        # duplicate the deterministic sig test from src/test/key_tests.cpp
        privkey = ECKey()
        privkey.set(
            bytes.fromhex(
                "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"
            ), True)

        proof_sequence = 11
        proof_expiration = 12
        proof = node.buildavalancheproof(proof_sequence, proof_expiration,
                                         bytes_to_wif(privkey.get_bytes()),
                                         stakes)

        # Activate the quorum.
        for n in quorum:
            success = node.addavalanchenode(
                n.nodeid,
                privkey.get_pubkey().get_bytes().hex(), proof)
            assert success is True

        def can_find_block_in_poll(hash, resp=AvalancheVoteError.ACCEPTED):
            found_hash = False
            for n in quorum:
                poll = n.get_avapoll_if_available()

                # That node has not received a poll
                if poll is None:
                    continue

                # We got a poll, check for the hash and repond
                votes = []
                for inv in poll.invs:
                    # Vote yes to everything
                    r = AvalancheVoteError.ACCEPTED

                    # Look for what we expect
                    if inv.hash == hash:
                        r = resp
                        found_hash = True

                    votes.append(AvalancheVote(r, inv.hash))

                n.send_avaresponse(poll.round, votes, privkey)

            return found_hash

        # Now that we have a peer, we should start polling for the tip.
        hash_tip = int(node.getbestblockhash(), 16)
        self.wait_until(lambda: can_find_block_in_poll(hash_tip), timeout=5)

        # Make sure the fork node has synced the blocks
        self.sync_blocks([node, fork_node])

        # Create a fork 2 blocks deep. This should trigger polling.
        fork_node.invalidateblock(fork_node.getblockhash(100))
        fork_address = fork_node.get_deterministic_priv_key().address
        fork_node.generatetoaddress(2, fork_address)

        # Because the new tip is a deep reorg, the node will not accept it
        # right away, but poll for it.
        def parked_block(blockhash):
            for tip in node.getchaintips():
                if tip["hash"] == blockhash:
                    assert tip["status"] != "active"
                    return tip["status"] == "parked"
            return False

        fork_tip = fork_node.getbestblockhash()
        self.wait_until(lambda: parked_block(fork_tip))

        self.log.info("Answer all polls to finalize...")

        hash_to_find = int(fork_tip, 16)

        def has_accepted_new_tip():
            can_find_block_in_poll(hash_to_find)
            return node.getbestblockhash() == fork_tip

        # Because everybody answers yes, the node will accept that block.
        self.wait_until(has_accepted_new_tip, timeout=15)
        assert_equal(node.getbestblockhash(), fork_tip)

        self.log.info("Answer all polls to park...")
        node.generate(1)

        tip_to_park = node.getbestblockhash()
        hash_to_find = int(tip_to_park, 16)
        assert (tip_to_park != fork_tip)

        def has_parked_new_tip():
            can_find_block_in_poll(hash_to_find, AvalancheVoteError.PARKED)
            return node.getbestblockhash() == fork_tip

        # Because everybody answers no, the node will park that block.
        self.wait_until(has_parked_new_tip, timeout=15)
        assert_equal(node.getbestblockhash(), fork_tip)

        self.log.info(
            "Check the node is discouraging unexpected avaresponses.")
        with node.assert_debug_log(
            ['Misbehaving', 'peer=1 (0 -> 2): unexpected-ava-response']):
            # unknown voting round
            poll_node.send_avaresponse(round=2**32 - 1,
                                       votes=[],
                                       privkey=privkey)
    def run_test(self):
        node = self.nodes[0]

        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(2, addrkey0.address)
        stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key)

        privkey = ECKey()
        privkey.generate()
        wif_privkey = bytes_to_wif(privkey.get_bytes())

        def check_buildavalancheproof_error(error_code,
                                            error_message,
                                            stakes,
                                            master_key=wif_privkey):
            assert_raises_rpc_error(
                error_code,
                error_message,
                node.buildavalancheproof,
                # Sequence
                0,
                # Expiration
                0,
                master_key,
                stakes,
            )

        good_stake = stakes[0]

        self.log.info("Error cases")

        check_buildavalancheproof_error(-8,
                                        "Invalid master key", [good_stake],
                                        master_key=bytes_to_wif(b'f00'))

        negative_vout = good_stake.copy()
        negative_vout['vout'] = -1
        check_buildavalancheproof_error(
            -22,
            "vout cannot be negative",
            [negative_vout],
        )

        zero_height = good_stake.copy()
        zero_height['height'] = 0
        check_buildavalancheproof_error(
            -22,
            "height must be positive",
            [zero_height],
        )
        negative_height = good_stake.copy()
        negative_height['height'] = -1
        check_buildavalancheproof_error(
            -22,
            "height must be positive",
            [negative_height],
        )

        missing_amount = good_stake.copy()
        del missing_amount['amount']
        check_buildavalancheproof_error(
            -8,
            "Missing amount",
            [missing_amount],
        )

        invalid_privkey = good_stake.copy()
        invalid_privkey['privatekey'] = 'foobar'
        check_buildavalancheproof_error(
            -8,
            "Invalid private key",
            [invalid_privkey],
        )

        duplicate_stake = [good_stake] * 2
        check_buildavalancheproof_error(
            -8,
            "Duplicated stake",
            duplicate_stake,
        )

        self.log.info("Happy path")
        assert node.buildavalancheproof(0, 0, wif_privkey, [good_stake])

        self.log.info("Check the payout address")
        self.restart_node(0,
                          extra_args=self.extra_args[0] +
                          ['-legacyavaproof=0'])

        assert_raises_rpc_error(
            -8,
            "A payout address is required if `-legacyavaproof` is false",
            node.buildavalancheproof,
            0,
            0,
            wif_privkey,
            [good_stake],
        )

        assert_raises_rpc_error(
            -8,
            "Invalid payout address",
            node.buildavalancheproof,
            0,
            0,
            wif_privkey,
            [good_stake],
            "ecregtest:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqcrl5mqkq",
        )

        # Happy path
        node.buildavalancheproof(0, 0, wif_privkey, [good_stake],
                                 ADDRESS_ECREG_UNSPENDABLE)
Ejemplo n.º 13
0
    def run_test(self):
        node = self.nodes[0]

        # duplicate the deterministic sig test from src/test/key_tests.cpp
        privkey = ECKey()
        privkey.set(
            bytes.fromhex(
                "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"
            ), True)
        wif_privkey = bytes_to_wif(privkey.get_bytes())

        self.log.info(
            "Check the node is signalling the avalanche service bit only if there is a proof."
        )
        assert_equal(
            int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE,
            0)

        # Create stakes by mining blocks
        addrkey0 = node.get_deterministic_priv_key()
        blockhashes = node.generatetoaddress(2, addrkey0.address)
        stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key)

        proof_sequence = 11
        proof_expiration = 12
        proof = node.buildavalancheproof(proof_sequence, proof_expiration,
                                         wif_privkey, stakes)

        # Restart the node
        self.restart_node(
            0, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])

        assert_equal(
            int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE,
            NODE_AVALANCHE)

        def check_avahello(args):
            # Restart the node with the given args
            self.restart_node(0, self.extra_args[0] + args)

            peer = get_ava_p2p_interface(node)

            avahello = peer.wait_for_avahello().hello

            avakey = ECPubKey()
            avakey.set(bytes.fromhex(node.getavalanchekey()))
            assert avakey.verify_schnorr(avahello.sig,
                                         avahello.get_sighash(peer))

        self.log.info(
            "Test the avahello signature with a generated delegation")
        check_avahello([
            "-avaproof={}".format(proof),
            "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN"
        ])

        master_key = ECKey()
        master_key.generate()
        limited_id = FromHex(LegacyAvalancheProof(), proof).limited_proofid
        delegation = node.delegateavalancheproof(
            f"{limited_id:0{64}x}",
            bytes_to_wif(privkey.get_bytes()),
            master_key.get_pubkey().get_bytes().hex(),
        )

        self.log.info("Test the avahello signature with a supplied delegation")
        check_avahello([
            "-avaproof={}".format(proof),
            "-avadelegation={}".format(delegation),
            "-avamasterkey={}".format(bytes_to_wif(master_key.get_bytes())),
        ])

        stakes = create_coinbase_stakes(node, [blockhashes[1]], addrkey0.key)
        interface_proof_hex = node.buildavalancheproof(proof_sequence,
                                                       proof_expiration,
                                                       wif_privkey, stakes)
        limited_id = FromHex(LegacyAvalancheProof(),
                             interface_proof_hex).limited_proofid

        # delegate
        delegated_key = ECKey()
        delegated_key.generate()
        interface_delegation_hex = node.delegateavalancheproof(
            f"{limited_id:0{64}x}", bytes_to_wif(privkey.get_bytes()),
            delegated_key.get_pubkey().get_bytes().hex(), None)

        self.log.info("Test that wrong avahello signature causes a ban")
        bad_interface = get_ava_p2p_interface(node)
        wrong_key = ECKey()
        wrong_key.generate()
        with node.assert_debug_log([
                "Misbehaving",
                "peer=1 (0 -> 100) BAN THRESHOLD EXCEEDED: invalid-avahello-signature"
        ]):
            bad_interface.send_avahello(interface_delegation_hex, wrong_key)
            bad_interface.wait_for_disconnect()

        self.log.info(
            'Check that receiving a valid avahello triggers a proof getdata request'
        )
        good_interface = get_ava_p2p_interface(node)
        proofid = good_interface.send_avahello(interface_delegation_hex,
                                               delegated_key)

        def getdata_found(peer, proofid):
            with p2p_lock:
                return good_interface.last_message.get(
                    "getdata") and good_interface.last_message["getdata"].inv[
                        -1].hash == proofid

        self.wait_until(lambda: getdata_found(good_interface, proofid))

        self.log.info('Check that we can download the proof from our peer')

        node_proofid = FromHex(LegacyAvalancheProof(), proof).proofid

        def wait_for_proof_validation():
            # Connect some blocks to trigger the proof verification
            node.generate(1)
            self.wait_until(lambda: node_proofid in get_proof_ids(node))

        wait_for_proof_validation()

        getdata = msg_getdata([CInv(MSG_AVA_PROOF, node_proofid)])

        self.log.info(
            "Proof has been inv'ed recently, check it can be requested")
        good_interface.send_message(getdata)

        def proof_received(peer):
            with p2p_lock:
                return peer.last_message.get("avaproof") and peer.last_message[
                    "avaproof"].proof.proofid == node_proofid

        self.wait_until(lambda: proof_received(good_interface))

        # Restart the node
        self.restart_node(
            0, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])
        wait_for_proof_validation()

        self.log.info(
            "The proof has not been announced, it cannot be requested")
        peer = get_ava_p2p_interface(node, services=NODE_NETWORK)
        peer.send_message(getdata)

        # Give enough time for the node to answer. Since we cannot check for a
        # non-event this is the best we can do
        time.sleep(2)
        assert not proof_received(peer)

        self.log.info("The proof is known for long enough to be requested")
        current_time = int(time.time())
        node.setmocktime(current_time + UNCONDITIONAL_RELAY_DELAY)

        peer.send_message(getdata)
        self.wait_until(lambda: proof_received(peer))

        # Restart the node
        self.restart_node(
            0, self.extra_args[0] + [
                "-avaproof={}".format(proof),
                "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
            ])
        wait_for_proof_validation()
        # The only peer is the node itself
        assert_equal(len(node.getavalanchepeerinfo()), 1)
        assert_equal(node.getavalanchepeerinfo()[0]["proof"], proof)

        peer = get_ava_p2p_interface(node)
        peer_proofid = peer.send_avahello(interface_delegation_hex,
                                          delegated_key)

        self.wait_until(lambda: getdata_found(peer, peer_proofid))
        assert peer_proofid not in get_proof_ids(node)

        self.log.info(
            "Check that the peer gets added as an avalanche node as soon as the node knows about the proof"
        )
        node.sendavalancheproof(interface_proof_hex)

        def has_node_count(count):
            peerinfo = node.getavalanchepeerinfo()
            return (len(peerinfo) == 2
                    and peerinfo[-1]["proof"] == interface_proof_hex
                    and peerinfo[-1]["nodecount"] == count)

        self.wait_until(lambda: has_node_count(1))

        self.log.info(
            "Check that the peer gets added immediately if the proof is already known"
        )

        # Connect another peer using the same proof
        peer_proof_known = get_ava_p2p_interface(node)
        peer_proof_known.send_avahello(interface_delegation_hex, delegated_key)

        self.wait_until(lambda: has_node_count(2))

        self.log.info("Invalidate the proof and check the nodes are removed")
        tip = node.getbestblockhash()
        # Invalidate the block with the proof utxo
        node.invalidateblock(blockhashes[1])
        # Change the address to make sure we don't generate a block identical
        # to the one we just invalidated. Can be generate(1) after D9694 or
        # D9697 is landed.
        forked_tip = node.generatetoaddress(1, ADDRESS_ECREG_UNSPENDABLE)[0]
        self.wait_until(lambda: node.getbestblockhash() == forked_tip)

        self.wait_until(lambda: len(node.getavalanchepeerinfo()) == 1)
        assert peer_proofid not in get_proof_ids(node)

        self.log.info("Reorg back and check the nodes are added back")
        node.invalidateblock(forked_tip)
        node.reconsiderblock(tip)
        self.wait_until(lambda: has_node_count(2), timeout=2)