diff --git a/src/rpc/avalanche.cpp b/src/rpc/avalanche.cpp --- a/src/rpc/avalanche.cpp +++ b/src/rpc/avalanche.cpp @@ -23,18 +23,49 @@ } if (!g_avalanche) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Avalanche is not initialized"); + throw JSONRPCError(RPC_INTERNAL_ERROR, "Avalanche is not initialized"); } return HexStr(g_avalanche->getSessionPubKey()); } +static UniValue addavalanchepeer(const Config &config, + const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error( + RPCHelpMan{ + "addavalanchepeer", + "\nAdd a peer to the set of peer to poll for avalanche.\n", + { + {"nodeid", RPCArg::Type::NUM, false}, + }} + .ToString() + + "\nArguments\n" + + "1. nodeid (number, required) Node to be added to avalanche.\n" + + "\nExamples:\n" + HelpExampleRpc("addavalanchepeer", "5")); + } + + if (!g_avalanche) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Avalanche is not initialized"); + } + + if (!request.params[0].isNum()) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + std::string("Invalid parameter, nodeid must be an integer")); + } + + NodeId nodeid = request.params[0].get_int64(); + g_avalanche->addPeer(nodeid, 0); + return {}; +} + // clang-format off static const ContextFreeRPCCommand commands[] = { // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- { "avalanche", "getavalanchekey", getavalanchekey, {}}, + { "avalanche", "addavalanchepeer", addavalanchepeer, {"nodeid"}}, }; // clang-format on diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -150,6 +150,8 @@ {"createwallet", 2, "blank"}, {"getnodeaddresses", 0, "count"}, {"stop", 0, "wait"}, + // Avalanche + {"addavalanchepeer", 0, "nodeid"}, // ABC specific RPC {"setexcessiveblock", 0, "blockSize"}, }; diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -4269,6 +4270,10 @@ pindex->GetBlockHash().ToString()); pindex->nStatus = pindex->nStatus.withParked(); setDirtyBlockIndex.insert(pindex); + if (g_avalanche && + pindex->nChainWork >= m_chain.Tip()->nChainWork) { + g_avalanche->addBlockToReconcile(pindex); + } } } diff --git a/test/functional/abc-p2p-avalanche.py b/test/functional/abc-p2p-avalanche.py --- a/test/functional/abc-p2p-avalanche.py +++ b/test/functional/abc-p2p-avalanche.py @@ -20,19 +20,26 @@ class TestNode(P2PInterface): def __init__(self): + self.round = 0 self.last_avaresponse = None + self.last_avapoll = None super().__init__() def on_avaresponse(self, message): self.last_avaresponse = message.response + def on_avapoll(self, message): + self.last_avapoll = message.poll + def send_poll(self, hashes): msg = msg_avapoll() + msg.poll.round = self.round + self.round += 1 for h in hashes: msg.poll.invs.append(CInv(2, h)) self.send_message(msg) - def wait_for_avaresponse(self, timeout=10): + def wait_for_avaresponse(self, timeout=5): self.sync_with_ping() def test_function(): @@ -40,12 +47,22 @@ return m is not None and m != self.last_avaresponse wait_until(test_function, timeout=timeout, lock=mininode_lock) + def wait_for_avapoll(self, timeout=5): + self.sync_with_ping() + + def test_function(): + m = self.last_message.get("avapoll") + return m is not None and m != self.last_avapoll + wait_until(test_function, timeout=timeout, lock=mininode_lock) + class AvalancheTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 1 - self.extra_args = [['-enableavalanche=1', '-avacooldown=0']] + self.num_nodes = 2 + self.extra_args = [ + ['-enableavalanche=1', '-avacooldown=0'], + ['-enableavalanche=1', '-avacooldown=0', '-noparkdeepreorg', '-maxreorgdepth=-1']] def run_test(self): node = self.nodes[0] @@ -56,6 +73,9 @@ poll_node.wait_for_verack() poll_node.sync_with_ping() + # Get our own node id so we can use it later. + nodeid = node.getpeerinfo()[-1]['id'] + # Generate many block and poll for them. address = node.get_deterministic_priv_key().address node.generatetoaddress(100, address) @@ -76,7 +96,6 @@ assert schnorr.verify(response.sig, avakey, r.get_hash()) votes = r.votes - self.log.info("response: {}".format(repr(response))) assert_equal(len(votes), len(expected)) for i in range(0, len(votes)): assert_equal(repr(votes[i]), repr(expected[i])) @@ -103,12 +122,12 @@ self.log.info( "Poll for a selection of blocks, but some are now invalid...") - invalidated_block = node.getblockhash(75) + 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( - 30, 'bchreg:pqv2r67sgz3qumufap3h2uuj0zfmnzuv8v7ej0fffv') + 26, 'bchreg:pqv2r67sgz3qumufap3h2uuj0zfmnzuv8v7ej0fffv') node.reconsiderblock(invalidated_block) poll_node.send_poll(various_block_hashes) @@ -136,6 +155,34 @@ [AvalancheVote(BLOCK_REJECTED, 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...") + node.addavalanchepeer(nodeid) + + # Create a fork 2 blocks deep. This should trigger polling. + fork_node = self.nodes[1] + fork_node.invalidateblock(fork_node.getblockhash(100)) + fork_node.generate(1) + + def can_find_block_in_poll(hash): + poll_node.wait_for_avapoll() + invs = poll_node.last_avapoll.invs + + votes = [] + found_hash = False + for inv in invs: + # Look for what we expect + if inv.hash == hash: + found_hash = True + # Vote yes to everything + votes.append(AvalancheVote(BLOCK_ACCEPTED, inv.hash)) + + return found_hash + + # Because the new tip is a deep reorg, the node should start to poll + # for it. + hash_to_find = int(fork_node.getbestblockhash(), 16) + wait_until(lambda: can_find_block_in_poll(hash_to_find), timeout=5) + if __name__ == '__main__': AvalancheTest().main()