diff --git a/src/avalanche/processor.h b/src/avalanche/processor.h --- a/src/avalanche/processor.h +++ b/src/avalanche/processor.h @@ -292,6 +292,14 @@ */ uint256 buildRemoteSighash(CNode *pfrom) const; + /** + * Get the local proof used by this node. + * + * @returns Proof for this node. + * @throws a std::runtime_error if there is no proof set for this node + */ + const Proof getProof() const; + std::vector getPeers() const; std::vector getNodeIdsForPeer(PeerId peerId) const; diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -441,6 +441,13 @@ return true; } +const Proof Processor::getProof() const { + if (!peerData) { + throw std::runtime_error("proof not set"); + } + return peerData->proof; +} + bool Processor::startEventLoop(CScheduler &scheduler) { return eventLoop.startEventLoop( scheduler, [this]() { this->runEventLoop(); }, AVALANCHE_TIME_STEP); diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -2435,6 +2436,40 @@ *node.chain, node.connman.get(), node.peerman.get()); 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; + 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_p2p_avalanche.py b/test/functional/abc_p2p_avalanche.py --- a/test/functional/abc_p2p_avalanche.py +++ b/test/functional/abc_p2p_avalanche.py @@ -414,6 +414,61 @@ get_node().nodeid, pubkey.get_bytes().hex(), bad_proof) + self.log.info("Bad proof should be rejected at startup") + + no_stake = node.buildavalancheproof( + proof_sequence, proof_expiration, pubkey.get_bytes().hex(), []) + + dust = node.buildavalancheproof( + proof_sequence, proof_expiration, pubkey.get_bytes().hex(), + [{ + 'txid': coinbases[0]['txid'], + 'vout': coinbases[0]['n'], + 'amount': '0', + 'height': coinbases[0]['height'], + 'iscoinbase': True, + 'privatekey': addrkey0.key, + }]) + + duplicate_stake = node.buildavalancheproof( + proof_sequence, proof_expiration, pubkey.get_bytes().hex(), + [{ + 'txid': coinbases[0]['txid'], + 'vout': coinbases[0]['n'], + 'amount': coinbases[0]['value'], + 'height': coinbases[0]['height'], + 'iscoinbase': True, + 'privatekey': addrkey0.key, + }] * 2) + + bad_sig = ("0b000000000000000c0000000000000021030b4c866585dd868a9d62348" + "a9cd008d6a312937048fff31670e7e920cfc7a7440105c5f72f5d6da3085" + "583e75ee79340eb4eff208c89988e7ed0efb30b87298fa30000000000f20" + "52a0100000003000000210227d85ba011276cf25b51df6a188b75e604b3" + "8770a462b2d0e9fb2fc839ef5d3faf07f001dd38e9b4a43d07d5d449cc0" + "f7d2888d96b82962b3ce516d1083c0e031773487fc3c4f2e38acd1db974" + "1321b91a79b82d1c2cfd47793261e4ba003cf5") + + self.stop_node(0) + + 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(duplicate_stake, + "the avalanche proof has duplicated stake") + check_proof_init_error(bad_sig, + "the avalanche proof has invalid stake signatures") + if __name__ == '__main__': AvalancheTest().main()