#pragma once #include #include #include #include #include #include #include #include class KernelSumValidator { public: // Makes sure the sums of all utxo commitments minus the total supply // equals the sum of all kernel excesses and the total offset. // This is to be used only when validating the entire state. // // Throws a ValidationException if the utxo sum != kernel sum. static void ValidateState( const std::vector& utxo_commitments, const std::vector& kernels, const BlindingFactor& total_offset) { // Sum all utxo commitments - expected supply. int64_t total_mweb_supply = 0; for (const Kernel& kernel : kernels) { const auto supply_change = kernel.GetSupplyChange(); if (!supply_change) { ThrowValidation(EConsensusError::AMOUNT_OUT_OF_RANGE); } total_mweb_supply = AmountUtil::SafeAdd(total_mweb_supply, *supply_change); AmountUtil::ValidateAmountRange(total_mweb_supply); // Total supply can never go below 0 if (total_mweb_supply < 0) { ThrowValidation(EConsensusError::BLOCK_SUMS); } } ValidateSums( {}, utxo_commitments, Commitments::From(kernels), total_offset, total_mweb_supply ); } static void ValidateForBlock( const TxBody& body, const BlindingFactor& total_offset, const BlindingFactor& prev_total_offset) { BlindingFactor block_offset = total_offset; if (!prev_total_offset.IsZero()) { block_offset = Pedersen::AddBlindingFactors({block_offset}, {prev_total_offset}); } ValidateSums( body.GetInputCommits(), body.GetOutputCommits(), body.GetKernelCommits(), block_offset, GetAmountOrThrow(body.GetSupplyChange()) ); } static void ValidateForTx(const mw::Transaction& tx) { ValidateSums( tx.GetInputCommits(), tx.GetOutputCommits(), tx.GetKernelCommits(), tx.GetKernelOffset(), GetAmountOrThrow(tx.GetSupplyChange()) ); } private: static CAmount GetAmountOrThrow(const boost::optional& amount) { if (!amount) { ThrowValidation(EConsensusError::AMOUNT_OUT_OF_RANGE); } return *amount; } static void ValidateSums( const std::vector& input_commits, const std::vector& output_commits, const std::vector& kernel_commits, const BlindingFactor& offset, const int64_t coins_added) { AmountUtil::ValidateAmountRange(coins_added); // Calculate UTXO nonce sum Commitment sum_utxo_commitment = Pedersen::AddCommitments(output_commits, input_commits); if (coins_added > 0) { sum_utxo_commitment = Pedersen::AddCommitments( { sum_utxo_commitment }, { Commitment::Transparent(coins_added) } ); } else if (coins_added < 0) { sum_utxo_commitment = Pedersen::AddCommitments( { sum_utxo_commitment, Commitment::Transparent(AmountUtil::UnsignedAbs(coins_added)) } ); } // Calculate total kernel excess Commitment sum_excess_commitment = Pedersen::AddCommitments(kernel_commits); if (!offset.IsZero()) { sum_excess_commitment = Pedersen::AddCommitments( { sum_excess_commitment, Commitment::Blinded(offset, 0) } ); } if (sum_utxo_commitment != sum_excess_commitment) { LOG_ERROR_F( "UTXO sum {} does not match kernel excess sum {}.", sum_utxo_commitment, sum_excess_commitment ); ThrowValidation(EConsensusError::BLOCK_SUMS); } } };