diff --git a/src/evo/assetlocktx.cpp b/src/evo/assetlocktx.cpp index 9624a21151cb6..d87d24e70884c 100644 --- a/src/evo/assetlocktx.cpp +++ b/src/evo/assetlocktx.cpp @@ -111,6 +111,34 @@ std::string CAssetLockPayload::ToString() const const std::string ASSETUNLOCK_REQUESTID_PREFIX = "plwdtx"; +bool CheckAssetUnlockTxBasic(const CTransaction& tx, TxValidationState& state) +{ + // Context-free basic validation - no chain state, UTXO, or signatures + if (tx.nType != TRANSACTION_ASSET_UNLOCK) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlocktx-type"); + } + + if (!tx.vin.empty()) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlocktx-have-input"); + } + + if (tx.vout.size() > CAssetUnlockPayload::MAXIMUM_WITHDRAWALS) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlocktx-too-many-outs"); + } + + const auto opt_assetUnlockTx = GetTxPayload(tx); + if (!opt_assetUnlockTx) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlocktx-payload"); + } + auto& assetUnlockTx = *opt_assetUnlockTx; + + if (assetUnlockTx.getVersion() == 0 || assetUnlockTx.getVersion() > CAssetUnlockPayload::CURRENT_VERSION) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlocktx-version"); + } + + return true; +} + bool CAssetUnlockPayload::VerifySig(const llmq::CQuorumManager& qman, const uint256& msgHash, gsl::not_null pindexTip, TxValidationState& state) const { // That quourm hash must be active at `requestHeight`, diff --git a/src/evo/assetlocktx.h b/src/evo/assetlocktx.h index 433dd098ee479..3830ea78891d1 100644 --- a/src/evo/assetlocktx.h +++ b/src/evo/assetlocktx.h @@ -155,6 +155,7 @@ class CAssetUnlockPayload }; bool CheckAssetLockTx(const CTransaction& tx, TxValidationState& state); +bool CheckAssetUnlockTxBasic(const CTransaction& tx, TxValidationState& state); bool CheckAssetUnlockTx(const node::BlockManager& blockman, const llmq::CQuorumManager& qman, const CTransaction& tx, gsl::not_null pindexPrev, const std::optional& indexes, TxValidationState& state); bool CheckAssetLockUnlockTx(const node::BlockManager& blockman, const llmq::CQuorumManager& qman, const CTransaction& tx, gsl::not_null pindexPrev, const std::optional& indexes, TxValidationState& state); bool GetAssetUnlockFee(const CTransaction& tx, CAmount& txfee, TxValidationState& state); diff --git a/src/evo/cbtx.cpp b/src/evo/cbtx.cpp index 8954ba5f63cce..41822eba2ed8f 100644 --- a/src/evo/cbtx.cpp +++ b/src/evo/cbtx.cpp @@ -19,6 +19,30 @@ using node::ReadBlockFromDisk; +bool CheckCbTxBasic(const CTransaction& tx, TxValidationState& state) +{ + // Context-free basic validation - no chain state + if (!tx.IsCoinBase()) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-invalid"); + } + + if (tx.nType != TRANSACTION_COINBASE) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-type"); + } + + const auto opt_cbTx = GetTxPayload(tx); + if (!opt_cbTx) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-payload"); + } + + auto& cbTx = *opt_cbTx; + if (cbTx.nVersion == CCbTx::Version::INVALID || cbTx.nVersion >= CCbTx::Version::UNKNOWN) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version"); + } + + return true; +} + bool CheckCbTx(const CCbTx& cbTx, const CBlockIndex* pindexPrev, TxValidationState& state) { if (cbTx.nVersion == CCbTx::Version::INVALID || cbTx.nVersion >= CCbTx::Version::UNKNOWN) { diff --git a/src/evo/cbtx.h b/src/evo/cbtx.h index da6d92f802a8e..91f6638bfb4f8 100644 --- a/src/evo/cbtx.h +++ b/src/evo/cbtx.h @@ -68,6 +68,7 @@ class CCbTx }; template<> struct is_serializable_enum : std::true_type {}; +bool CheckCbTxBasic(const CTransaction& tx, TxValidationState& state); bool CheckCbTx(const CCbTx& cbTx, const CBlockIndex* pindexPrev, TxValidationState& state); bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPrev, diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 0b79a38e9521a..57892b13fa7b8 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -1090,6 +1090,21 @@ bool IsVersionChangeValid(gsl::not_null pindexPrev, const ui return true; } +bool CheckProRegTxBasic(const CTransaction& tx, TxValidationState& state) +{ + // Context-free basic validation - no chain state + if (tx.nType != CProRegTx::SPECIALTX_TYPE) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-type"); + } + + auto opt_ptx = GetTxPayload(tx); + if (!opt_ptx) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-payload"); + } + + return true; +} + bool CheckProRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state, const CCoinsViewCache& view, bool check_sigs) { const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, state); @@ -1223,6 +1238,21 @@ bool CheckProRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl: return true; } +bool CheckProUpServTxBasic(const CTransaction& tx, TxValidationState& state) +{ + // Context-free basic validation - no chain state + if (tx.nType != CProUpServTx::SPECIALTX_TYPE) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-type"); + } + + auto opt_ptx = GetTxPayload(tx); + if (!opt_ptx) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-payload"); + } + + return true; +} + bool CheckProUpServTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state, bool check_sigs) { const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, state); @@ -1299,6 +1329,21 @@ bool CheckProUpServTx(CDeterministicMNManager& dmnman, const CTransaction& tx, g return true; } +bool CheckProUpRegTxBasic(const CTransaction& tx, TxValidationState& state) +{ + // Context-free basic validation - no chain state + if (tx.nType != CProUpRegTx::SPECIALTX_TYPE) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-type"); + } + + auto opt_ptx = GetTxPayload(tx); + if (!opt_ptx) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-payload"); + } + + return true; +} + bool CheckProUpRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state, const CCoinsViewCache& view, bool check_sigs) { const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, state); @@ -1369,6 +1414,21 @@ bool CheckProUpRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gs return true; } +bool CheckProUpRevTxBasic(const CTransaction& tx, TxValidationState& state) +{ + // Context-free basic validation - no chain state + if (tx.nType != CProUpRevTx::SPECIALTX_TYPE) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-type"); + } + + auto opt_ptx = GetTxPayload(tx); + if (!opt_ptx) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-payload"); + } + + return true; +} + bool CheckProUpRevTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state, bool check_sigs) { const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, state); diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index b01fbd5f8f7ed..971facf5bb356 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -767,9 +767,13 @@ class CDeterministicMNManager RecalcDiffsResult& result) EXCLUSIVE_LOCKS_REQUIRED(!cs); }; +bool CheckProRegTxBasic(const CTransaction& tx, TxValidationState& state); bool CheckProRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state, const CCoinsViewCache& view, bool check_sigs); +bool CheckProUpServTxBasic(const CTransaction& tx, TxValidationState& state); bool CheckProUpServTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state, bool check_sigs); +bool CheckProUpRegTxBasic(const CTransaction& tx, TxValidationState& state); bool CheckProUpRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state, const CCoinsViewCache& view, bool check_sigs); +bool CheckProUpRevTxBasic(const CTransaction& tx, TxValidationState& state); bool CheckProUpRevTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state, bool check_sigs); #endif // BITCOIN_EVO_DETERMINISTICMNS_H diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 54e5d0c29ce0b..09283dd638d45 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -111,6 +111,25 @@ bool MNHFTx::Verify(const llmq::CQuorumManager& qman, const uint256& quorumHash, return true; } +bool CheckMNHFTxBasic(const CTransaction& tx, TxValidationState& state) +{ + // Context-free basic validation - no chain state + if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_MNHF_SIGNAL) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-type"); + } + + const auto opt_mnhfTx = GetTxPayload(tx); + if (!opt_mnhfTx) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-payload"); + } + auto& mnhfTx = *opt_mnhfTx; + if (mnhfTx.nVersion == 0 || mnhfTx.nVersion > MNHFTxPayload::CURRENT_VERSION) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-version"); + } + + return true; +} + bool CheckMNHFTx(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) { if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_MNHF_SIGNAL) { diff --git a/src/evo/mnhftx.h b/src/evo/mnhftx.h index 2c4260956763a..f25b0411aaf32 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -169,6 +169,7 @@ class CMNHFManager : public AbstractEHFManager }; std::optional extractEHFSignal(const CTransaction& tx); +bool CheckMNHFTxBasic(const CTransaction& tx, TxValidationState& state); bool CheckMNHFTx(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state); #endif // BITCOIN_EVO_MNHFTX_H diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index 77aae4d1bd2c9..2154e97d9271d 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -5,8 +5,47 @@ #include #include +#include #include +#include +#include +#include +#include +#include + +bool CheckSpecialTxBasic(const CTransaction& tx, TxValidationState& state) +{ + // Context-free basic validation for special transactions - no chain state required + if (!tx.HasExtraPayloadField()) { + // Not a special transaction, nothing to check + return true; + } + + switch (tx.nType) { + case TRANSACTION_PROVIDER_REGISTER: + return CheckProRegTxBasic(tx, state); + case TRANSACTION_PROVIDER_UPDATE_SERVICE: + return CheckProUpServTxBasic(tx, state); + case TRANSACTION_PROVIDER_UPDATE_REGISTRAR: + return CheckProUpRegTxBasic(tx, state); + case TRANSACTION_PROVIDER_UPDATE_REVOKE: + return CheckProUpRevTxBasic(tx, state); + case TRANSACTION_COINBASE: + return CheckCbTxBasic(tx, state); + case TRANSACTION_QUORUM_COMMITMENT: + return llmq::CheckLLMQCommitmentBasic(tx, state); + case TRANSACTION_MNHF_SIGNAL: + return CheckMNHFTxBasic(tx, state); + case TRANSACTION_ASSET_LOCK: + return CheckAssetLockTx(tx, state); // Already context-free + case TRANSACTION_ASSET_UNLOCK: + return CheckAssetUnlockTxBasic(tx, state); + default: + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-tx-type"); + } +} + uint256 CalcTxInputsHash(const CTransaction& tx) { CHashWriter hw(SER_GETHASH, CLIENT_VERSION); diff --git a/src/evo/specialtx.h b/src/evo/specialtx.h index 55a53a4175d08..55cf34f969824 100644 --- a/src/evo/specialtx.h +++ b/src/evo/specialtx.h @@ -14,6 +14,10 @@ #include #include +class TxValidationState; + +bool CheckSpecialTxBasic(const CTransaction& tx, TxValidationState& state); + template std::optional GetTxPayload(const std::vector& payload) { diff --git a/src/llmq/commitment.cpp b/src/llmq/commitment.cpp index ee0c09a0f13a6..3d0325b8ee85d 100644 --- a/src/llmq/commitment.cpp +++ b/src/llmq/commitment.cpp @@ -213,6 +213,36 @@ bool CFinalCommitment::VerifySizes(const Consensus::LLMQParams& params) const return true; } +bool CheckLLMQCommitmentBasic(const CTransaction& tx, TxValidationState& state) +{ + // Context-free basic validation - no chain state + if (tx.nType != TRANSACTION_QUORUM_COMMITMENT) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-qc-type"); + } + + const auto opt_qcTx = GetTxPayload(tx); + if (!opt_qcTx) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-qc-payload"); + } + auto& qcTx = *opt_qcTx; + + const auto& llmq_params_opt = Params().GetLLMQ(qcTx.commitment.llmqType); + if (!llmq_params_opt.has_value()) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-qc-commitment-type"); + } + + if (qcTx.nVersion == 0 || qcTx.nVersion > CFinalCommitmentTxPayload::CURRENT_VERSION) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-qc-version"); + } + + // Basic size validation (context-free) + if (!qcTx.commitment.IsNull() && !qcTx.commitment.VerifySizes(llmq_params_opt.value())) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-qc-invalid-sizes"); + } + + return true; +} + bool CheckLLMQCommitment(CDeterministicMNManager& dmnman, CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state) diff --git a/src/llmq/commitment.h b/src/llmq/commitment.h index 60cf499d5a2ac..f7a9f04456a44 100644 --- a/src/llmq/commitment.h +++ b/src/llmq/commitment.h @@ -164,6 +164,7 @@ class CFinalCommitmentTxPayload [[nodiscard]] UniValue ToJson() const; }; +bool CheckLLMQCommitmentBasic(const CTransaction& tx, TxValidationState& state); bool CheckLLMQCommitment(CDeterministicMNManager& dmnman, CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state); diff --git a/src/validation.cpp b/src/validation.cpp index cb6214facd039..3566d6523f382 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4026,6 +4026,17 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), tx_state.GetDebugMessage())); } } + // Check special transactions (context-independent basic validation) + for (const auto& tx : block.vtx) { + TxValidationState tx_state; + if (!CheckSpecialTxBasic(*tx, tx_state)) { + // CheckBlock() does context-free validation checks. The only + // possible failures are consensus failures. + assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), + strprintf("Special transaction basic check failed (tx hash %s) %s", tx->GetHash().ToString(), tx_state.GetDebugMessage())); + } + } unsigned int nSigOps = 0; for (const auto& tx : block.vtx) {