diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -120,6 +120,7 @@ avalanche/proof.h \ avalanche/proofbuilder.h \ avalanche/protocol.h \ + avalanche/validation.h \ banman.h \ base58.h \ bloom.h \ diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -4,6 +4,7 @@ #include +#include #include #include @@ -18,7 +19,8 @@ } // Reject invalid proof. - if (!proof.verify()) { + ProofValidationState state; + if (!proof.verify(state)) { return NO_PEER; } diff --git a/src/avalanche/proof.h b/src/avalanche/proof.h --- a/src/avalanche/proof.h +++ b/src/avalanche/proof.h @@ -17,6 +17,8 @@ namespace avalanche { +class ProofValidationState; + struct ProofId : public uint256 { explicit ProofId() : uint256() {} explicit ProofId(const uint256 &b) : uint256(b) {} @@ -122,7 +124,7 @@ const ProofId &getId() const { return proofid; } uint32_t getScore() const; - bool verify() const; + bool verify(ProofValidationState &state) const; }; } // namespace avalanche diff --git a/src/avalanche/proof.cpp b/src/avalanche/proof.cpp --- a/src/avalanche/proof.cpp +++ b/src/avalanche/proof.cpp @@ -4,6 +4,7 @@ #include +#include #include // For SaltedOutpointHasher #include @@ -47,22 +48,26 @@ return uint32_t((100 * total) / COIN); } -bool Proof::verify() const { - if (getScore() == 0) { - // No stake. - return false; +static constexpr Amount PROOF_DUST_THRESOLD = 1 * SATOSHI; + +bool Proof::verify(ProofValidationState &state) const { + if (stakes.empty()) { + return state.Invalid(ProofValidationResult::NO_STAKE); } std::unordered_set utxos; - for (auto &s : stakes) { - if (!utxos.insert(s.getStake().getUTXO()).second) { - // Duplicated stake. - return false; + for (const SignedStake &ss : stakes) { + const Stake &s = ss.getStake(); + if (s.getAmount() < PROOF_DUST_THRESOLD) { + return state.Invalid(ProofValidationResult::DUST_THRESOLD); + } + + if (!utxos.insert(s.getUTXO()).second) { + return state.Invalid(ProofValidationResult::DUPLICATE_STAKE); } - if (!s.verify(proofid)) { - // Improperly signed stake. - return false; + if (!ss.verify(proofid)) { + return state.Invalid(ProofValidationResult::INVALID_SIGNATURE); } } diff --git a/src/avalanche/test/proof_tests.cpp b/src/avalanche/test/proof_tests.cpp --- a/src/avalanche/test/proof_tests.cpp +++ b/src/avalanche/test/proof_tests.cpp @@ -3,9 +3,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include -#include +#include #include +#include #include #include @@ -21,8 +22,11 @@ for (int i = 0; i < 1000; i++) { const uint32_t score = InsecureRand32(); const Proof p = buildRandomProof(score); - BOOST_CHECK(p.verify()); BOOST_CHECK_EQUAL(p.getScore(), score); + + ProofValidationState state; + BOOST_CHECK(p.verify(state)); + BOOST_CHECK(state.GetResult() == ProofValidationResult::NONE); } } @@ -46,11 +50,15 @@ } Proof p = pb.build(); - BOOST_CHECK(p.verify()); + + ProofValidationState state; + BOOST_CHECK(p.verify(state)); BOOST_CHECK_EQUAL(p.getSequence(), sequence); BOOST_CHECK_EQUAL(p.getExpirationTime(), expiration); BOOST_CHECK(p.getMaster() == master); + + BOOST_CHECK(state.GetResult() == ProofValidationResult::NONE); } struct TestVector { @@ -58,7 +66,7 @@ std::string hex; ProofId proofid; uint32_t score; - bool valid; + ProofValidationResult result; }; BOOST_AUTO_TEST_CASE(deserialization) { @@ -68,7 +76,7 @@ "3e95b02cacfd357b64e4fb6c92e92dd00", ProofId::fromHex("df721b6e2a857ce8abac63d8d5eca35f3bdb0293b6e8295942c7" "6274c5418c0c"), - 0, false}, + 0, ProofValidationResult::NO_STAKE}, {"1 utxo staked", "a6d66db9fe9378fdd37a0ad2c01c2acd2103648144bb6a0c1d09b0f04d0df6d55f914" "fd81efc65f23a718b68b7c9e42bd5430145a4d07798547464daa53acefb7c97c0c415" @@ -79,7 +87,7 @@ "77d8fcf68c54ebfadf08b9a446c251a0088301c50d53", ProofId::fromHex("048235ee870030f11c287d898dd3ec184f9b38cf4fb274334966" "c6aad83b769d"), - 7584312, false}, + 7584312, ProofValidationResult::INVALID_SIGNATURE}, {"2 utxo staked", "872379ab64f55b4166ca0e79639999ec4104a66861de557a54eefc0375264cc17c3a3" "50ccabca6fd9c91883e899ab55bb140517aa56c5b4041908e7027a786b99f66488a04" @@ -94,7 +102,7 @@ "91c965cba4dbc11c210979217f1ac3ece7a748f5b2fcf5cced40a5d4c40e", ProofId::fromHex("7319126f0d4efc188440dd50105ea30d792687b65e9cdde6c4d6" "08ed226cba00"), - 15610172, false}, + 15610172, ProofValidationResult::INVALID_SIGNATURE}, {"3 utxo staked", "525e2aa04af0e2457c66ac9e7f66257f210252db8e3ceea6fca44a7696e82f7b77e5a" "4025e60ac60271b174e91ffbb6ce01f039ce8d3b77938e49ce3bc9824e90b72c65542" @@ -114,7 +122,7 @@ "91b185e", ProofId::fromHex("3b3204993240ab338324310ecadc5f234da1dac0627029cef63e" "1169a049d18f"), - 29026903, false}, + 29026903, ProofValidationResult::INVALID_SIGNATURE}, {"4 utxo staked", "eef33172651f752ac255c85a4e1374992102c12b37ff6139157865fc4c3a9d7ad999b" "686ade45d453545d04e76f6e14793b404295de5ebf9fbbbb65fc1d9a71587c5284cff" @@ -138,7 +146,7 @@ "14a348c80ca1833d68b3d7b", ProofId::fromHex("62f256d73411fe69bf0db6083248e4efb75be9788850851468ac" "c913c5c14360"), - 44059793, false}, + 44059793, ProofValidationResult::INVALID_SIGNATURE}, {"Properly signed 1 UTXO proof", "d97587e6c882615796011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -150,7 +158,7 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23e", ProofId::fromHex("17717dbf1cee175925be12321bb04eda7bc861a28b04d764c2d5" "5eabf01cabd0"), - 444638638, true}, + 444638638, ProofValidationResult::NONE}, {"Duplicated UTXO", "60f8332a3ff3430a4f3c9010160cc63e2102f2df6361a5744521d16753f9efb5ace68" "d43121e90ff67290540b54341cbab0e02d1e26c2287948bc6ab2b55945c591b8ba3ff" @@ -164,7 +172,7 @@ "0b3455e6c815767cbe8ccdd04e0e2d33d5f2f092667ef081acdaafde869d58dae", ProofId::fromHex("b0c2c07cbc766df0a934bcb252063ad5d7d5167b080369f6feca" "a3aabae29785"), - 3280755132, false}, + 3280755132, ProofValidationResult::DUPLICATE_STAKE}, {"Properly signed 3 UTXO proof", "c96492f9e27a25d7e8404581c7be874e210282eb68a587dc586673f439fe317fa0b76" "aceefa9f99820eadaa70d3b19c0cfe10305d427b706705a5d4b6a368a231d6db62aba" @@ -185,7 +193,7 @@ "a7", ProofId::fromHex("5948d32ee26882a2d9f96c18e9e51ad13a79234b627a020ee7db" "733684cb05a7"), - 353426051, true}, + 353426051, ProofValidationResult::NONE}, {"Changing sequence affect ProofId", "d87587e6c882615796011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -197,7 +205,7 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23e", ProofId::fromHex("3fdcb56110cfe2f394689be3dd28dedee674f79a567c8391dbd9" "3900c9845ca8"), - 444638638, false}, + 444638638, ProofValidationResult::INVALID_SIGNATURE}, {"Changing expiration affect ProofId", "d97587e6c882615797011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -209,7 +217,7 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23e", ProofId::fromHex("a9ffbb2b37526e05a1f72b43314f872c381281ce8d3d96a5bf56" "47db057a64dc"), - 444638638, false}, + 444638638, ProofValidationResult::INVALID_SIGNATURE}, {"Changing the master key affect ProofId", "d97587e6c882615796011ec8f9a7b1c64104e556ba897297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -222,7 +230,7 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23e", ProofId::fromHex("c237e5436f2d21e4ae990422bfb8ec97df52a9f6db5c20c40f54" "64e590dcda39"), - 444638638, false}, + 444638638, ProofValidationResult::INVALID_SIGNATURE}, {"Changing the TxId affect the ProofId", "d97587e6c882615796011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -234,7 +242,7 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23e", ProofId::fromHex("9d372d7580917985824c632c919ce8bf89fc6f45c07ee60b2564" "6aea23390ec6"), - 444638638, false}, + 444638638, ProofValidationResult::INVALID_SIGNATURE}, {"Changing the outpoint index change the ProofId", "d97587e6c882615796011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -246,7 +254,7 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23e", ProofId::fromHex("1a98c5f7684508665fbc946c64c9486e3f7c91b2f42d299320c5" "56e68e4028fe"), - 444638638, false}, + 444638638, ProofValidationResult::INVALID_SIGNATURE}, {"Changing the amount changes the ProofId", "d97587e6c882615796011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -258,7 +266,7 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23e", ProofId::fromHex("528fa9654e845dfe30ed03966240ab615a8d374d86256e1e4892" "83eb0a0be44b"), - 444638638, false}, + 444638638, ProofValidationResult::INVALID_SIGNATURE}, {"Changing the height changes the ProofId", "d97587e6c882615796011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -270,7 +278,7 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23e", ProofId::fromHex("a932294f46f106b2b38e57a4b1e4b5849730aba2124662f680a6" "1c8cc429adb0"), - 444638638, false}, + 444638638, ProofValidationResult::INVALID_SIGNATURE}, {"Changing the pubkey changes the ProofId", "d97587e6c882615796011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -282,7 +290,7 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23e", ProofId::fromHex("336c9a502b22ba2edcd78298c1ce6cc5e8b5e94cdc60b1047d1f" "96810d5d17a3"), - 444638638, false}, + 444638638, ProofValidationResult::INVALID_SIGNATURE}, {"Changing the signatue does NOT change the ProofId", "d97587e6c882615796011ec8f9a7b1c64104e556ba887297fd6a655bc2579d26814e8" "54c902448ec3ce3c6fe965e3420aa3f0e9611d3c0b8a73b6329741e2e726fd17f5e8a" @@ -294,7 +302,18 @@ "7c0276a293ee5b3f5a130fc5f9b217585cae23f", ProofId::fromHex("17717dbf1cee175925be12321bb04eda7bc861a28b04d764c2d5" "5eabf01cabd0"), - 444638638, false}, + 444638638, ProofValidationResult::INVALID_SIGNATURE}, + {"1 utxo staked but zero coins", + "a6d66db9fe9378fdd37a0ad2c01c2acd2103648144bb6a0c1d09b0f04d0df6d55f914" + "fd81efc65f23a718b68b7c9e42bd5430145a4d07798547464daa53acefb7c97c0c415" + "ed8e81e549ff56a0ef6f847fcc9ca855b3620000000000000000e707d7274104fb662" + "6e21dbd1cc9feeecdefc9213fdce2b51ac4bb44e1f8dc6f14c2052f5dd7bfaeb2267a" + "97ca2bec6e0dd4acf50a66204bde1ebb5d6c551684cff2f939920f7fbb2efd860d6d5" + "926bf425eb47b78bf6979cdcd67eb705e2c9a4d45a0930ba25463178a3fb99cb28c8b" + "77d8fcf68c54ebfadf08b9a446c251a0088301c50d53", + ProofId::fromHex("32c852db61880e9e2d4de3edba2daf3f5449e00bf1bc5b18595f" + "80113d5b0722"), + 0, ProofValidationResult::DUST_THRESOLD}, }; for (auto &c : testcases) { @@ -303,7 +322,11 @@ stream >> p; BOOST_CHECK_EQUAL(p.getId(), c.proofid); BOOST_CHECK_EQUAL(p.getScore(), c.score); - BOOST_CHECK_EQUAL(p.verify(), c.valid); + + ProofValidationState state; + BOOST_CHECK_EQUAL(p.verify(state), + c.result == ProofValidationResult::NONE); + BOOST_CHECK(state.GetResult() == c.result); } } diff --git a/src/avalanche/validation.h b/src/avalanche/validation.h new file mode 100644 --- /dev/null +++ b/src/avalanche/validation.h @@ -0,0 +1,37 @@ +// Copyright (c) 2020 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_AVALANCHE_VALIDATION_H +#define BITCOIN_AVALANCHE_VALIDATION_H + +#include + +namespace avalanche { + +enum class ProofValidationResult { + NONE, + NO_STAKE, + DUST_THRESOLD, + DUPLICATE_STAKE, + INVALID_SIGNATURE, +}; + +class ProofValidationState : public ValidationState { +private: + ProofValidationResult m_result = ProofValidationResult::NONE; + +public: + bool Invalid(ProofValidationResult result, unsigned int chRejectCodeIn = 0, + const std::string &reject_reason = "", + const std::string &debug_message = "") { + m_result = result; + ValidationState::Invalid(chRejectCodeIn, reject_reason, debug_message); + return false; + } + ProofValidationResult GetResult() const { return m_result; } +}; + +} // namespace avalanche + +#endif // BITCOIN_AVALANCHE_VALIDATION_H