diff --git a/src/avalanche/processor.h b/src/avalanche/processor.h --- a/src/avalanche/processor.h +++ b/src/avalanche/processor.h @@ -27,10 +27,12 @@ #include #include +class ArgsManager; class Config; class CBlockIndex; class CScheduler; class PeerManager; +struct bilingual_str; using NodePeerManager = PeerManager; @@ -262,11 +264,18 @@ */ bool mustRegisterProof = false; -public: Processor(interfaces::Chain &chain, CConnman *connmanIn, - NodePeerManager *nodePeerManagerIn); + NodePeerManager *nodePeerManagerIn, + std::unique_ptr peerDataIn, CKey sessionKeyIn); + +public: ~Processor(); + static std::unique_ptr + MakeProcessor(const ArgsManager &argsman, interfaces::Chain &chain, + CConnman *connman, NodePeerManager *nodePeerManager, + bilingual_str &error); + void setQueryTimeoutDuration(std::chrono::milliseconds d) { queryTimeoutDuration = d; } diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -162,47 +163,104 @@ }; Processor::Processor(interfaces::Chain &chain, CConnman *connmanIn, - NodePeerManager *nodePeerManagerIn) + NodePeerManager *nodePeerManagerIn, + std::unique_ptr peerDataIn, CKey sessionKeyIn) : connman(connmanIn), nodePeerManager(nodePeerManagerIn), queryTimeoutDuration(AVALANCHE_DEFAULT_QUERY_TIMEOUT), round(0), - peerManager(std::make_unique()) { - if (gArgs.IsArgSet("-avasessionkey")) { - sessionKey = DecodeSecret(gArgs.GetArg("-avasessionkey", "")); + peerManager(std::make_unique()), + peerData(std::move(peerDataIn)), sessionKey(std::move(sessionKeyIn)), + // Schedule proof registration at the first new block after IBD. + // FIXME: get rid of this flag + mustRegisterProof(!!peerData) { + // Make sure we get notified of chain state changes. + chainNotificationsHandler = + chain.handleNotifications(std::make_shared(this)); +} + +Processor::~Processor() { + chainNotificationsHandler.reset(); + stopEventLoop(); +} + +std::unique_ptr +Processor::MakeProcessor(const ArgsManager &argsman, interfaces::Chain &chain, + CConnman *connman, NodePeerManager *nodePeerManager, + bilingual_str &error) { + std::unique_ptr peerData; + CKey masterKey; + CKey sessionKey; + + if (argsman.IsArgSet("-avasessionkey")) { + sessionKey = DecodeSecret(argsman.GetArg("-avasessionkey", "")); + if (!sessionKey.IsValid()) { + error = _("the avalanche session key is invalid"); + return nullptr; + } } else { // Pick a random key for the session. sessionKey.MakeNewKey(true); } - if (gArgs.IsArgSet("-avaproof")) { - peerData = std::make_unique(); + if (argsman.IsArgSet("-avaproof")) { + if (!argsman.IsArgSet("-avamasterkey")) { + error = _( + "the avalanche master key is missing for the avalanche proof"); + return nullptr; + } + masterKey = DecodeSecret(argsman.GetArg("-avamasterkey", "")); + if (!masterKey.IsValid()) { + error = _("the avalanche master key is invalid"); + return nullptr; + } + + peerData = std::make_unique(); { // The proof. - CDataStream stream(ParseHex(gArgs.GetArg("-avaproof", "")), + CDataStream stream(ParseHex(argsman.GetArg("-avaproof", "")), SER_NETWORK, 0); stream >> peerData->proof; + } - // Schedule proof registration at the first new block after IBD. - mustRegisterProof = true; + ProofValidationState proof_state; + if (!peerData->proof.verify(proof_state)) { + switch (proof_state.GetResult()) { + case ProofValidationResult::NO_STAKE: + error = _("the avalanche proof has no stake"); + return nullptr; + case ProofValidationResult::DUST_THRESOLD: + error = _("the avalanche proof stake is too low"); + return nullptr; + case ProofValidationResult::DUPLICATE_STAKE: + error = _("the avalanche proof has duplicated stake"); + return nullptr; + case ProofValidationResult::INVALID_SIGNATURE: + error = + _("the avalanche proof has invalid stake signatures"); + return nullptr; + case ProofValidationResult::TOO_MANY_UTXOS: + error = strprintf( + _("the avalanche proof has too many utxos (max: %u)"), + AVALANCHE_MAX_PROOF_STAKES); + return nullptr; + default: + error = _("the avalanche proof is invalid"); + return nullptr; + } } // Generate the delegation to the session key. DelegationBuilder dgb(peerData->proof); if (sessionKey.GetPubKey() != peerData->proof.getMaster()) { - dgb.addLevel(DecodeSecret(gArgs.GetArg("-avamasterkey", "")), - sessionKey.GetPubKey()); + dgb.addLevel(masterKey, sessionKey.GetPubKey()); } peerData->delegation = dgb.build(); } - // Make sure we get notified of chain state changes. - chainNotificationsHandler = - chain.handleNotifications(std::make_shared(this)); -} - -Processor::~Processor() { - chainNotificationsHandler.reset(); - stopEventLoop(); + // We can't use std::make_unique with a private constructor + return std::unique_ptr( + new Processor(chain, connman, nodePeerManager, std::move(peerData), + std::move(sessionKey))); } bool Processor::addBlockToReconcile(const CBlockIndex *pindex) { diff --git a/src/avalanche/test/processor_tests.cpp b/src/avalanche/test/processor_tests.cpp --- a/src/avalanche/test/processor_tests.cpp +++ b/src/avalanche/test/processor_tests.cpp @@ -11,6 +11,7 @@ #include #include // For ::PeerManager #include +#include // For bilingual_str // D6970 moved LookupBlockIndex from chain.h to validation.h TODO: remove this // when LookupBlockIndex is refactored out of validation #include @@ -86,8 +87,11 @@ m_node.chain = interfaces::MakeChain(m_node, config.GetChainParams()); // Get the processor ready. - m_processor = std::make_unique( - *m_node.chain, m_node.connman.get(), m_node.peerman.get()); + bilingual_str error; + m_processor = Processor::MakeProcessor(*m_node.args, *m_node.chain, + m_node.connman.get(), + m_node.peerman.get(), error); + BOOST_CHECK(m_processor); // The master private key we delegate to. masterpriv.MakeNewKey(true); diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -2436,49 +2436,17 @@ } // Step 6.5 (I guess ?): Initialize Avalanche. - g_avalanche = std::make_unique( - *node.chain, node.connman.get(), node.peerman.get()); + bilingual_str avalancheError; + g_avalanche = avalanche::Processor::MakeProcessor( + args, *node.chain, node.connman.get(), node.peerman.get(), + avalancheError); + if (!g_avalanche) { + InitError(avalancheError); + return false; + } + if (args.GetBoolArg("-enableavalanche", AVALANCHE_DEFAULT_ENABLED)) { nLocalServices = ServiceFlags(nLocalServices | NODE_AVALANCHE); - - // If avalanche is enabled and a proof is supplied, make sure it does - // not contain garbage. At this point the validity of the utxos cannot - // be checked, so only basic verification is performed. - try { - avalanche::Proof proof; - proof = g_avalanche->getProof(); - - avalanche::ProofValidationState proof_state; - if (!proof.verify(proof_state)) { - switch (proof_state.GetResult()) { - case avalanche::ProofValidationResult::NO_STAKE: - InitError(_("the avalanche proof has no stake")); - return false; - case avalanche::ProofValidationResult::DUST_THRESOLD: - InitError(_("the avalanche proof stake is too low")); - return false; - case avalanche::ProofValidationResult::DUPLICATE_STAKE: - InitError( - _("the avalanche proof has duplicated stake")); - return false; - case avalanche::ProofValidationResult::INVALID_SIGNATURE: - InitError(_("the avalanche proof has invalid stake " - "signatures")); - return false; - case avalanche::ProofValidationResult::TOO_MANY_UTXOS: - InitError(strprintf(_("the avalanche proof has too " - "many utxos (max: %u)"), - AVALANCHE_MAX_PROOF_STAKES)); - return false; - default: - InitError(_("the avalanche proof is invalid")); - return false; - } - } - } catch (const std::runtime_error &e) { - LogPrintf("Avalanche is enabled but no proof supplied, the node " - "will not be able to vote\n"); - } } // Step 7: load block chain diff --git a/test/functional/abc_rpc_avalancheproof.py b/test/functional/abc_rpc_avalancheproof.py --- a/test/functional/abc_rpc_avalancheproof.py +++ b/test/functional/abc_rpc_avalancheproof.py @@ -187,6 +187,28 @@ 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] + [