Changeset View
Changeset View
Standalone View
Standalone View
src/avalanche/proof.cpp
// Copyright (c) 2020 The Bitcoin developers | // Copyright (c) 2020 The Bitcoin developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <avalanche/proof.h> | #include <avalanche/proof.h> | ||||
#include <avalanche/validation.h> | #include <avalanche/validation.h> | ||||
#include <coins.h> | #include <coins.h> | ||||
#include <hash.h> | #include <hash.h> | ||||
#include <script/standard.h> | #include <script/standard.h> | ||||
#include <tinyformat.h> | |||||
#include <unordered_set> | #include <unordered_set> | ||||
namespace avalanche { | namespace avalanche { | ||||
uint256 Stake::getHash(const ProofId &proofid) const { | uint256 Stake::getHash(const ProofId &proofid) const { | ||||
CHashWriter ss(SER_GETHASH, 0); | CHashWriter ss(SER_GETHASH, 0); | ||||
ss << proofid; | ss << proofid; | ||||
ss << *this; | ss << *this; | ||||
Show All 24 Lines | for (const SignedStake &s : stakes) { | ||||
total += s.getStake().getAmount(); | total += s.getStake().getAmount(); | ||||
} | } | ||||
return uint32_t((100 * total) / COIN); | return uint32_t((100 * total) / COIN); | ||||
} | } | ||||
bool Proof::verify(ProofValidationState &state) const { | bool Proof::verify(ProofValidationState &state) const { | ||||
if (stakes.empty()) { | if (stakes.empty()) { | ||||
return state.Invalid(ProofValidationResult::NO_STAKE); | return state.Invalid(ProofValidationResult::NO_STAKE, "no-stake"); | ||||
} | } | ||||
if (stakes.size() > AVALANCHE_MAX_PROOF_STAKES) { | if (stakes.size() > AVALANCHE_MAX_PROOF_STAKES) { | ||||
return state.Invalid(ProofValidationResult::TOO_MANY_UTXOS); | return state.Invalid( | ||||
ProofValidationResult::TOO_MANY_UTXOS, "too-many-utxos", | |||||
strprintf("%u > %u", stakes.size(), AVALANCHE_MAX_PROOF_STAKES)); | |||||
} | } | ||||
std::unordered_set<COutPoint, SaltedOutpointHasher> utxos; | std::unordered_set<COutPoint, SaltedOutpointHasher> utxos; | ||||
for (const SignedStake &ss : stakes) { | for (const SignedStake &ss : stakes) { | ||||
const Stake &s = ss.getStake(); | const Stake &s = ss.getStake(); | ||||
if (s.getAmount() < PROOF_DUST_THRESHOLD) { | if (s.getAmount() < PROOF_DUST_THRESHOLD) { | ||||
return state.Invalid(ProofValidationResult::DUST_THRESOLD); | return state.Invalid(ProofValidationResult::DUST_THRESOLD, | ||||
"amount-below-dust-threshold", | |||||
strprintf("%s < %s", s.getAmount().ToString(), | |||||
PROOF_DUST_THRESHOLD.ToString())); | |||||
} | } | ||||
if (!utxos.insert(s.getUTXO()).second) { | if (!utxos.insert(s.getUTXO()).second) { | ||||
return state.Invalid(ProofValidationResult::DUPLICATE_STAKE); | return state.Invalid(ProofValidationResult::DUPLICATE_STAKE, | ||||
"duplicated-stake"); | |||||
} | } | ||||
if (!ss.verify(proofid)) { | if (!ss.verify(proofid)) { | ||||
return state.Invalid(ProofValidationResult::INVALID_SIGNATURE); | return state.Invalid(ProofValidationResult::INVALID_SIGNATURE, | ||||
"invalid-signature"); | |||||
} | } | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
bool Proof::verify(ProofValidationState &state, const CCoinsView &view) const { | bool Proof::verify(ProofValidationState &state, const CCoinsView &view) const { | ||||
if (!verify(state)) { | if (!verify(state)) { | ||||
// state is set by verify. | // state is set by verify. | ||||
return false; | return false; | ||||
} | } | ||||
for (const SignedStake &ss : stakes) { | for (const SignedStake &ss : stakes) { | ||||
const Stake &s = ss.getStake(); | const Stake &s = ss.getStake(); | ||||
const COutPoint &utxo = s.getUTXO(); | const COutPoint &utxo = s.getUTXO(); | ||||
Coin coin; | Coin coin; | ||||
if (!view.GetCoin(utxo, coin)) { | if (!view.GetCoin(utxo, coin)) { | ||||
// The coins are not in the UTXO set. | // The coins are not in the UTXO set. | ||||
return state.Invalid(ProofValidationResult::MISSING_UTXO); | return state.Invalid(ProofValidationResult::MISSING_UTXO, | ||||
"utxo-missing-or-spent"); | |||||
} | } | ||||
if (s.isCoinbase() != coin.IsCoinBase()) { | if (s.isCoinbase() != coin.IsCoinBase()) { | ||||
return state.Invalid(ProofValidationResult::COINBASE_MISMATCH); | return state.Invalid( | ||||
ProofValidationResult::COINBASE_MISMATCH, "coinbase-mismatch", | |||||
strprintf("expected %s, found %s", | |||||
s.isCoinbase() ? "true" : "false", | |||||
coin.IsCoinBase() ? "true" : "false")); | |||||
} | } | ||||
if (s.getHeight() != coin.GetHeight()) { | if (s.getHeight() != coin.GetHeight()) { | ||||
return state.Invalid(ProofValidationResult::HEIGHT_MISMATCH); | return state.Invalid(ProofValidationResult::HEIGHT_MISMATCH, | ||||
"height-mismatch", | |||||
strprintf("expected %u, found %u", | |||||
s.getHeight(), coin.GetHeight())); | |||||
} | } | ||||
const CTxOut &out = coin.GetTxOut(); | const CTxOut &out = coin.GetTxOut(); | ||||
if (s.getAmount() != out.nValue) { | if (s.getAmount() != out.nValue) { | ||||
// Wrong amount. | // Wrong amount. | ||||
return state.Invalid(ProofValidationResult::AMOUNT_MISMATCH); | return state.Invalid( | ||||
ProofValidationResult::AMOUNT_MISMATCH, "amount-mismatch", | |||||
strprintf("expected %s, found %s", s.getAmount().ToString(), | |||||
out.nValue.ToString())); | |||||
} | } | ||||
CTxDestination dest; | CTxDestination dest; | ||||
if (!ExtractDestination(out.scriptPubKey, dest)) { | if (!ExtractDestination(out.scriptPubKey, dest)) { | ||||
// Can't extract destination. | // Can't extract destination. | ||||
return state.Invalid( | return state.Invalid( | ||||
ProofValidationResult::NON_STANDARD_DESTINATION); | ProofValidationResult::NON_STANDARD_DESTINATION, | ||||
"non-standard-destination"); | |||||
} | } | ||||
PKHash *pkhash = boost::get<PKHash>(&dest); | PKHash *pkhash = boost::get<PKHash>(&dest); | ||||
if (!pkhash) { | if (!pkhash) { | ||||
// Only PKHash are supported. | // Only PKHash are supported. | ||||
return state.Invalid( | return state.Invalid( | ||||
ProofValidationResult::DESTINATION_NOT_SUPPORTED); | ProofValidationResult::DESTINATION_NOT_SUPPORTED, | ||||
"destination-type-not-supported"); | |||||
} | } | ||||
const CPubKey &pubkey = s.getPubkey(); | const CPubKey &pubkey = s.getPubkey(); | ||||
if (*pkhash != PKHash(pubkey)) { | if (*pkhash != PKHash(pubkey)) { | ||||
// Wrong pubkey. | // Wrong pubkey. | ||||
return state.Invalid(ProofValidationResult::DESTINATION_MISMATCH); | return state.Invalid(ProofValidationResult::DESTINATION_MISMATCH, | ||||
"destination-mismatch"); | |||||
} | } | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
} // namespace avalanche | } // namespace avalanche |