Skip to content

sdk%feat(codec): add Checkable trait, move inherent validate() functions to it, drop DeploymentContext, trim down checks to be stateless#7

Merged
kwvg merged 14 commits into
dashpay:developfrom
kwvg:checkable
Jun 9, 2026
Merged

Conversation

@kwvg

@kwvg kwvg commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Additional Information

Breaking Changes

  • DeploymentContext has been removed. All validate() methods that accepted &DeploymentContext no longer exist.

  • All validate() inherent methods have been replaced by impl Checkable with fn check(&self) -> Option<Self::Error>. Callers should migrate from .validate().is_ok() to .check().is_none().

How Has This Been Tested?

cargo test --features full,_internal
./contrib/lint/all_lint.py

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional tests
  • I have made corresponding changes to the documentation (note: N/A)
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@kwvg kwvg added this to the 0.1 milestone Jun 9, 2026
@kwvg kwvg self-assigned this Jun 9, 2026
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request introduces a new Checkable trait for context-free consensus validation and migrates validation logic across block, transaction, payload, and governance types. The changes also refactor CodeQL declarations to use a typed slot representation. The migration removes the context-dependent DeploymentContext pattern throughout the codebase and updates all tests to use the new validation interface.

Changes

Checkable validation trait migration

Layer / File(s) Summary
Checkable trait definition
pkgs/types/src/codec.rs
Defines a new public trait Checkable with an associated Error type and check(&self) -> Option<Self::Error> method for internal invariant checks.
Validation infrastructure cleanup
pkgs/primitives/src/lib.rs, pkgs/primitives/src/validation.rs
Removes DeploymentContext from public exports, removes context-dependent helper functions (max_protx_version, check_protx_version, etc.), and introduces check_sptx_netinfo as a context-free net-info validator.
Block and transaction Checkable implementations
pkgs/primitives/src/block.rs, pkgs/primitives/src/transaction.rs
Implements Checkable for Block and Transaction, replacing context-dependent validation with structural checks returning Option<InvalidError> instead of Result.
Simple payload type validations
pkgs/primitives/src/payload/assetlock.rs, pkgs/primitives/src/payload/assetunlock.rs, pkgs/primitives/src/payload/cbtx.rs, pkgs/primitives/src/payload/mnhftx.rs
Implements Checkable for AssetLock, AssetUnlock, CoinbaseCommitment, and MnHardFork, removing context parameters and switching to Option-based error reporting.
ProTx payload validations
pkgs/primitives/src/payload/proregtx.rs, pkgs/primitives/src/payload/proupregtx.rs, pkgs/primitives/src/payload/prouprevtx.rs, pkgs/primitives/src/payload/proupservtx.rs
Implements Checkable for all ProTx variants, replacing context-dependent version checks with structural validations using the new check_sptx_netinfo helper.
SpecialPayload aggregation and quorum commitment
pkgs/primitives/src/payload/mod.rs, pkgs/primitives/src/payload/quorum.rs
Updates payload module re-exports, introduces unified PayloadInvalid error type, implements Checkable for SpecialPayload with per-variant error mapping, and adds Checkable for FinalCommitment with infallible error type.
Governance proposal validation
pkgs/primitives/src/gov.rs
Implements Checkable for Proposal type, migrating governance validation to the trait-based API.
Block and transaction test updates
pkgs/primitives/tests/blocks.rs, pkgs/primitives/tests/coinbase.rs, pkgs/primitives/tests/data.rs, pkgs/primitives/tests/spend.rs
Updates test imports and assertions to use Checkable::check() instead of context-dependent validate() methods.
Payload and governance test updates
pkgs/primitives/tests/assetlock.rs, pkgs/primitives/tests/assetunlock.rs, pkgs/primitives/tests/cbtx.rs, pkgs/primitives/tests/mnhftx.rs, pkgs/primitives/tests/proregtx.rs, pkgs/primitives/tests/proupregtx.rs, pkgs/primitives/tests/prouprevtx.rs, pkgs/primitives/tests/proupservtx.rs, pkgs/primitives/tests/proposals.rs, pkgs/primitives/tests/qctx.rs
Updates test modules across all payload types and governance tests to use new Checkable trait assertions instead of validate() method calls.

CodeQL declaration slot type refactoring

Layer / File(s) Summary
Declaration rule slot refactoring
contrib/codeql/decl.ql
Updates outOfOrder predicate to operate on DeclSlot objects, comparing slot order via getOrder() methods and updating message formatting with toString() values.
Policy slot type and enumeration updates
contrib/codeql/lib/policy.qll
Introduces new TDeclSlot/DeclSlot representation with getOrder() and toString() methods, updates trait-to-slot mapping functions to return DeclSlot, and refactors itemEntry() predicate to use DeclSlot constructors.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main changes: introducing the Checkable trait, migrating validate() functions, removing DeploymentContext, and making checks stateless.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description is directly related to the changeset, explaining the removal of DeploymentContext, the introduction of the Checkable trait, and the migration of validate() methods.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Warning

This pull request may have conflicts, please coordinate with the authors of these pull requests.

Potential conflicts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkgs/primitives/src/transaction.rs (1)

81-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject impossible extra_payload states.

Line 75 only serializes extra_payload when has_extra_payload() is true, but check() never rejects payload bytes on spend or pre-v3 transactions. That lets an invalid in-memory transaction pass validation and then lose data on encode().

As per coding guidelines, make invalid states unrepresentable.

Suggested fix
 pub enum TxInvalid {
   EmptyInputs,
   EmptyOutputs,
   Oversize { size: usize },
   PayloadOversize { size: usize },
+  UnexpectedExtraPayload { size: usize },
   OutputTooLarge { index: usize, value: u64 },
   OutputTotalTooLarge { total: u64 },
   DuplicateInputs { outpoint: OutPoint },
   BadCoinbaseScriptLength { len: usize },
   NullPrevout { index: usize },
 }

 fn check(&self) -> Option<Self::Error> {
+  if !self.has_extra_payload() && !self.extra_payload.is_empty() {
+    return Some(TxInvalid::UnexpectedExtraPayload {
+      size: self.extra_payload.len(),
+    });
+  }
+
   let allows_empty_vin = matches!(
     self.tx_type,
     TxType::QuorumCommitment | TxType::MnhfSignal | TxType::AssetUnlock
   );

Also applies to: 159-162

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkgs/primitives/src/transaction.rs` around lines 81 - 149, The check()
implementation for Transaction must reject non-empty extra_payload when the
transaction type/flags indicate no extra payload (i.e., when has_extra_payload()
would be false) to prevent silent data loss on encode(); update
Transaction::check to return a TxInvalid (e.g., PayloadNotAllowed or reuse a
suitable variant) if self.extra_payload is non-empty but
self.has_extra_payload() is false, and mirror this validation where
encode/serialization logic assumes extra_payload is only present (the other
affected area around the serialization branch that checks has_extra_payload());
reference Transaction::check, Transaction::has_extra_payload, the extra_payload
field, and the encode/serialization branch so the invalid in-memory state is
rejected rather than trimmed during encode.

Source: Coding guidelines

pkgs/primitives/src/payload/cbtx.rs (1)

76-87: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate the version-gated option fields, not just version.

Lines 80-86 synthesize zero/default data when v2/v3 fields are missing, but check() only bounds version. A CoinbaseCommitment with version = 3 and None in those fields currently passes validation and re-encodes as a different payload. check() should require the fields selected by version, and reject extra fields on lower versions too.

As per coding guidelines, make invalid states unrepresentable.

Also applies to: 106-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkgs/primitives/src/payload/cbtx.rs` around lines 76 - 87, The version-gated
optional fields (merkle_root_quorums, best_cl_height_diff, best_cl_signature,
credit_pool_balance) must be validated in check() according to self.version and
rejected if missing for higher versions or present for lower versions; update
the CoinbaseCommitment::check() to: require merkle_root_quorums.is_some() when
version >= 2 and ensure it is None when version < 2; require
best_cl_height_diff, best_cl_signature, and credit_pool_balance to be Some when
version >= 3 and ensure they are None when version < 3. After that, remove the
encode-side syntheses (the unwrap_or_default/unwrap_or(0) usages in encode) and
encode the fields directly (or unwrap safely) because check() now guarantees
presence/absence; apply the same validation logic to the alternate block noted
at lines 106-116 as well.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkgs/primitives/src/gov.rs`:
- Around line 416-421: The validation currently lowercases each byte in
self.name (via .to_ascii_lowercase()) before checking membership against
PROPOSAL_NAME_CHARS, making the check case-insensitive; change the check in the
validation routine (the method implementing check() that returns
ProposalInvalid::NameInvalidChars) to test the raw bytes against
PROPOSAL_NAME_CHARS (remove the .to_ascii_lowercase() call) OR alternatively
enforce normalization at write-time (normalize the stored name to lowercase when
setting the name) so that check() validates against the documented
lowercase-only contract.

In `@pkgs/primitives/src/payload/proregtx.rs`:
- Around line 162-218: The check() implementation for ProRegTx must forbid
states that encode() would normalize: ensure version vs net_info are consistent
(require NetInfo::Extended when version >= PROTX_VERSION_EXT_ADDR and require
NetInfo::Legacy when version < PROTX_VERSION_EXT_ADDR), and validate platform
fields per mn_type (when MnType::Regular the platform_* fields must be
None/empty and when MnType::Evo the platform_* fields must be
present/non-empty), also keep the existing Evo/version guard around
PROTX_VERSION_BASIC_BLS; update ProRegTx::check() to return ProTxInvalid
variants for these mismatches so a successful check guarantees encode() will not
alter or drop fields (refer to check(), encode(), NetInfo::Extended/Legacy,
PROTX_VERSION_EXT_ADDR, PROTX_VERSION_BASIC_BLS, and MnType::Regular/Evo and the
platform_* fields).

In `@pkgs/primitives/src/payload/proupservtx.rs`:
- Around line 129-162: The check() implementation for ProUpServTx must enforce
the same wire-shape invariants that encode() assumes: update ProUpServTx::check
to validate (1) version vs NetInfo: require NetInfo::Extended when version >=
PROTX_VERSION_EXT_ADDR and require NetInfo::Legacy when version <
PROTX_VERSION_EXT_ADDR (replace the current is_extended != (version == ...)
logic with explicit range checks to match encode()), (2) platform fields: if
platform type is Regular or Full ensure platform_p2p/platform_http/any
platform-specific Option payloads match what encode() would serialize (i.e.,
reject Regular with Some(platform fields) if encode would drop them), (3)
Evo/version-dependent fields: for mn_type == MnType::Evo enforce version >=
PROTX_VERSION_BASIC_BLS and that Evo-only fields (operator_public,
voting_public, payout scripts, and any platform proto fields expected by encode)
are present/non-default where encode would expect them, and (4) legacy addr/port
presence when NetInfo::Legacy is required; return appropriate ProTxInvalid
variants on mismatch. Use the symbols ProUpServTx::check, encode(),
NetInfo::Extended, NetInfo::Legacy, PROTX_VERSION_EXT_ADDR,
PROTX_VERSION_BASIC_BLS, and mn_type to locate and implement these checks.
- Around line 132-152: The check() implementation in proupservtx.rs currently
allows MnType::Unknown(_) to pass through, bypassing BadMnType handling and
Evo/Regular-specific validation in check_sptx_netinfo; update check() to
explicitly reject MnType::Unknown(_) (return Some(ProTxInvalid::BadMnType {
mn_type: self.mn_type.clone() } or equivalent) early—preferably right after the
version checks and before the Evo-specific branch and net_info
validation—mirroring the behavior in ProRegTx::check() so unknown mn_type values
cannot proceed into check_sptx_netinfo or version logic.

In `@pkgs/primitives/src/payload/quorum.rs`:
- Around line 137-143: FinalCommitment is marked infallible but can contain a
Commitment where version/quorum_index are inconsistent because
Commitment::encode() writes quorum_index when Some(_) while Commitment::decode()
only reads it for versions 2/4; fix by adding validation of the
version/quorum_index invariant inside FinalCommitment::check (make type Error
non-Infallible and return an error when version==1 && quorum_index.is_some() or
when version ∉ {1,2,4} with quorum_index mismatches), or alternatively enforce
the invariant inside Commitment by preventing encode()/construction of
commitments with invalid version/quorum_index pairs so they never appear in
FinalCommitment; also update SpecialPayload::check() to stop assuming quorum
commitments cannot fail if you choose the fallible FinalCommitment path.

---

Outside diff comments:
In `@pkgs/primitives/src/payload/cbtx.rs`:
- Around line 76-87: The version-gated optional fields (merkle_root_quorums,
best_cl_height_diff, best_cl_signature, credit_pool_balance) must be validated
in check() according to self.version and rejected if missing for higher versions
or present for lower versions; update the CoinbaseCommitment::check() to:
require merkle_root_quorums.is_some() when version >= 2 and ensure it is None
when version < 2; require best_cl_height_diff, best_cl_signature, and
credit_pool_balance to be Some when version >= 3 and ensure they are None when
version < 3. After that, remove the encode-side syntheses (the
unwrap_or_default/unwrap_or(0) usages in encode) and encode the fields directly
(or unwrap safely) because check() now guarantees presence/absence; apply the
same validation logic to the alternate block noted at lines 106-116 as well.

In `@pkgs/primitives/src/transaction.rs`:
- Around line 81-149: The check() implementation for Transaction must reject
non-empty extra_payload when the transaction type/flags indicate no extra
payload (i.e., when has_extra_payload() would be false) to prevent silent data
loss on encode(); update Transaction::check to return a TxInvalid (e.g.,
PayloadNotAllowed or reuse a suitable variant) if self.extra_payload is
non-empty but self.has_extra_payload() is false, and mirror this validation
where encode/serialization logic assumes extra_payload is only present (the
other affected area around the serialization branch that checks
has_extra_payload()); reference Transaction::check,
Transaction::has_extra_payload, the extra_payload field, and the
encode/serialization branch so the invalid in-memory state is rejected rather
than trimmed during encode.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ab540649-5982-4069-a11c-862a5b1b893b

📥 Commits

Reviewing files that changed from the base of the PR and between efa6118 and d5ade42.

📒 Files selected for processing (32)
  • contrib/codeql/decl.ql
  • contrib/codeql/lib/policy.qll
  • pkgs/primitives/src/block.rs
  • pkgs/primitives/src/gov.rs
  • pkgs/primitives/src/lib.rs
  • pkgs/primitives/src/payload/assetlock.rs
  • pkgs/primitives/src/payload/assetunlock.rs
  • pkgs/primitives/src/payload/cbtx.rs
  • pkgs/primitives/src/payload/mnhftx.rs
  • pkgs/primitives/src/payload/mod.rs
  • pkgs/primitives/src/payload/proregtx.rs
  • pkgs/primitives/src/payload/proupregtx.rs
  • pkgs/primitives/src/payload/prouprevtx.rs
  • pkgs/primitives/src/payload/proupservtx.rs
  • pkgs/primitives/src/payload/quorum.rs
  • pkgs/primitives/src/transaction.rs
  • pkgs/primitives/src/validation.rs
  • pkgs/primitives/tests/assetlock.rs
  • pkgs/primitives/tests/assetunlock.rs
  • pkgs/primitives/tests/blocks.rs
  • pkgs/primitives/tests/cbtx.rs
  • pkgs/primitives/tests/coinbase.rs
  • pkgs/primitives/tests/data.rs
  • pkgs/primitives/tests/mnhftx.rs
  • pkgs/primitives/tests/proposals.rs
  • pkgs/primitives/tests/proregtx.rs
  • pkgs/primitives/tests/proupregtx.rs
  • pkgs/primitives/tests/prouprevtx.rs
  • pkgs/primitives/tests/proupservtx.rs
  • pkgs/primitives/tests/qctx.rs
  • pkgs/primitives/tests/spend.rs
  • pkgs/types/src/codec.rs
💤 Files with no reviewable changes (1)
  • pkgs/primitives/src/lib.rs

Comment on lines 416 to +421
if !self
.name
.bytes()
.all(|b| PROPOSAL_NAME_CHARS.contains(&b.to_ascii_lowercase()))
{
return Err(ProposalInvalid::NameInvalidChars);
return Some(ProposalInvalid::NameInvalidChars);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The proposal-name validator is accidentally case-insensitive.

Line 419 lowercases each byte before the membership test, so MyProposal passes even though Line 87 documents lowercase-only names and PROPOSAL_NAME_CHARS only contains lowercase bytes. Validate the raw bytes instead, or normalize the stored name before validation so check() matches the documented contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkgs/primitives/src/gov.rs` around lines 416 - 421, The validation currently
lowercases each byte in self.name (via .to_ascii_lowercase()) before checking
membership against PROPOSAL_NAME_CHARS, making the check case-insensitive;
change the check in the validation routine (the method implementing check() that
returns ProposalInvalid::NameInvalidChars) to test the raw bytes against
PROPOSAL_NAME_CHARS (remove the .to_ascii_lowercase() call) OR alternatively
enforce normalization at write-time (normalize the stored name to lowercase when
setting the name) so that check() validates against the documented
lowercase-only contract.

Comment thread pkgs/primitives/src/payload/proregtx.rs
Comment thread pkgs/primitives/src/payload/proupservtx.rs
Comment thread pkgs/primitives/src/payload/proupservtx.rs
Comment thread pkgs/primitives/src/payload/quorum.rs
@kwvg kwvg merged commit 96da4f9 into dashpay:develop Jun 9, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant