Skip to content

Conversation

@wolf31o2
Copy link
Member

@wolf31o2 wolf31o2 commented Nov 17, 2025

Closes #1250

Summary by CodeRabbit

  • New Features

    • Added explicit validation for certificate deposit amounts and a clear error type for non-positive amounts.
  • Bug Fixes

    • Replaced fixed-deposit logic with amount-based validation across certificate handling and updated tests to match.
  • Refactor

    • Standardized certificate type representation for more consistent encoding and handling across the ledger.

@wolf31o2 wolf31o2 requested a review from a team as a code owner November 17, 2025 23:01
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 2025

📝 Walkthrough

Walkthrough

The diff introduces a new exported CertificateType (uint) and retypes certificate constants and uses to that type. Many test fixtures across eras were updated to cast certificate Type/CertType fields to uint(...). Conway validation logic was changed to validate and consume explicit certificate Amount values (replacing previous deposit fields) and to return a new InvalidCertificateDepositError when amounts are non-positive. A new exported error type InvalidCertificateDepositError was added in ledger/shelley/errors.go.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

  • Heterogeneous changes: type-system refactor, validation logic additions with new error paths, and broad test updates.
  • Files/areas needing extra attention:
    • ledger/common/certs.go: new CertificateType, constant typing, and CBOR unmarshalling changes.
    • ledger/conway/rules.go: amount-based deposit/refund logic, type assertions, and new error returns.
    • ledger/shelley/errors.go: new exported error type and its Error() implementation.
    • Cross-era tests (ledger/*/rules_test.go, ledger/common/common_test.go): many literal/type-cast updates that must match the public API and marshaling behavior.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes unnecessary type system changes in ledger/common/certs.go that introduce a new CertificateType enum and update all certificate constants, which are out of scope for the deposit validation fix. Remove the CertificateType enum introduction and constant type changes from ledger/common/certs.go. Keep only the deposit validation logic changes needed to fix issue #1250.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(ledger): certificate deposit validation' directly and concisely describes the main change, which is fixing certificate deposit validation logic to properly account for deposits when checking transaction value conservation.
Linked Issues check ✅ Passed The PR implements certificate deposit validation fixes across multiple certificate types (stake registration, pool registration, DRep registration, etc.) by replacing fixed deposit constants with dynamic amount-based validation and introducing InvalidCertificateDepositError, directly addressing issue #1250's requirement to properly account for certificate deposits.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/deposit-validation

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 501fde2 and e86696b.

📒 Files selected for processing (10)
  • ledger/allegra/rules_test.go (2 hunks)
  • ledger/alonzo/rules_test.go (2 hunks)
  • ledger/babbage/rules_test.go (2 hunks)
  • ledger/common/certs.go (3 hunks)
  • ledger/common/common_test.go (1 hunks)
  • ledger/conway/rules.go (2 hunks)
  • ledger/conway/rules_test.go (2 hunks)
  • ledger/mary/rules_test.go (2 hunks)
  • ledger/shelley/errors.go (1 hunks)
  • ledger/shelley/rules_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • ledger/shelley/errors.go
  • ledger/conway/rules.go
  • ledger/babbage/rules_test.go
🧰 Additional context used
🧬 Code graph analysis (7)
ledger/alonzo/rules_test.go (1)
ledger/common/certs.go (2)
  • CertificateTypeStakeRegistration (32-32)
  • CertificateTypeStakeDeregistration (33-33)
ledger/conway/rules_test.go (1)
ledger/common/certs.go (2)
  • CertificateTypeStakeRegistration (32-32)
  • CertificateTypeStakeDeregistration (33-33)
ledger/allegra/rules_test.go (1)
ledger/common/certs.go (2)
  • CertificateTypeStakeRegistration (32-32)
  • CertificateTypeStakeDeregistration (33-33)
ledger/shelley/rules_test.go (1)
ledger/common/certs.go (2)
  • CertificateTypeStakeRegistration (32-32)
  • CertificateTypeStakeDeregistration (33-33)
ledger/common/certs.go (1)
ledger/compat.go (1)
  • Certificate (64-64)
ledger/common/common_test.go (1)
ledger/common/certs.go (37)
  • CertificateTypeStakeRegistration (32-32)
  • StakeDeregistrationCertificate (256-261)
  • CertificateTypeStakeDeregistration (33-33)
  • StakeDelegationCertificate (294-300)
  • CertificateTypeStakeDelegation (34-34)
  • PoolRegistrationCertificate (435-448)
  • CertificateTypePoolRegistration (35-35)
  • PoolRetirementCertificate (617-623)
  • CertificateTypePoolRetirement (36-36)
  • GenesisKeyDelegationCertificate (653-660)
  • CertificateTypeGenesisKeyDelegation (37-37)
  • MoveInstantaneousRewardsCertificate (733-738)
  • CertificateTypeMoveInstantaneousRewards (38-38)
  • RegistrationCertificate (787-793)
  • CertificateTypeRegistration (39-39)
  • DeregistrationCertificate (828-834)
  • CertificateTypeDeregistration (40-40)
  • VoteDelegationCertificate (869-875)
  • CertificateTypeVoteDelegation (41-41)
  • StakeVoteDelegationCertificate (915-922)
  • CertificateTypeStakeVoteDelegation (42-42)
  • StakeRegistrationDelegationCertificate (964-971)
  • CertificateTypeStakeRegistrationDelegation (43-43)
  • VoteRegistrationDelegationCertificate (1007-1014)
  • CertificateTypeVoteRegistrationDelegation (44-44)
  • StakeVoteRegistrationDelegationCertificate (1055-1063)
  • CertificateTypeStakeVoteRegistrationDelegation (45-45)
  • AuthCommitteeHotCertificate (1116-1122)
  • CertificateTypeAuthCommitteeHot (46-46)
  • ResignCommitteeColdCertificate (1162-1168)
  • CertificateTypeResignCommitteeCold (47-47)
  • RegistrationDrepCertificate (1211-1218)
  • CertificateTypeRegistrationDrep (48-48)
  • DeregistrationDrepCertificate (1262-1268)
  • CertificateTypeDeregistrationDrep (49-49)
  • UpdateDrepCertificate (1303-1309)
  • CertificateTypeUpdateDrep (50-50)
ledger/mary/rules_test.go (1)
ledger/common/certs.go (2)
  • CertificateTypeStakeRegistration (32-32)
  • CertificateTypeStakeDeregistration (33-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (10)
ledger/common/certs.go (4)

29-52: LGTM - Type safety improvement

The introduction of CertificateType uint and retyping all certificate constants improves type safety while maintaining backward compatibility. The public API remains uint-based (via Certificate.Type() uint and CertificateWrapper.Type uint), so this is a non-breaking internal improvement.


65-67: LGTM - Defensive validation

Good defensive programming to validate non-negative certificate types before the switch statement.


69-69: LGTM - Type-safe switch

Switching on CertificateType(certType) provides better type checking and is consistent with the newly typed constants.


1383-1383: LGTM - Required cast

The explicit cast to uint is necessary since CertificateTypeLeiosEb is now typed as CertificateType while the method signature returns uint. This maintains API compatibility.

ledger/alonzo/rules_test.go (1)

563-563: LGTM - Test data updated for typed constants

The uint() casts are necessary because CertificateWrapper.Type is uint while the constants are now typed as CertificateType. These mechanical changes correctly align the test data with the new type system.

Also applies to: 590-590

ledger/allegra/rules_test.go (1)

554-554: LGTM - Test data updated for typed constants

Mechanical updates to align test data with the new CertificateType type system. The uint() casts correctly handle the type conversion for CertificateWrapper.Type field.

Also applies to: 581-581

ledger/conway/rules_test.go (1)

562-562: LGTM - Test data updated for typed constants

Consistent mechanical updates to test certificate data, correctly using uint() casts for the typed constants.

Also applies to: 589-589

ledger/shelley/rules_test.go (1)

553-553: LGTM - Test data updated for typed constants

Mechanical updates align test data with the new type system, correctly casting typed constants to uint for the CertificateWrapper.Type field.

Also applies to: 580-580

ledger/mary/rules_test.go (1)

561-561: LGTM - Test data updated for typed constants

Consistent with other test file updates, correctly using uint() casts for typed certificate constants.

Also applies to: 588-588

ledger/common/common_test.go (1)

756-874: LGTM - Comprehensive test updates for all certificate types

All test cases have been systematically updated to use uint(CertificateTypeX) casts for both the CertType fields and expected values. This comprehensive update covers all 19 certificate types and maintains consistency with the new type system.


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.

@wolf31o2 wolf31o2 force-pushed the fix/deposit-validation branch 2 times, most recently from 6072656 to 1e1dadb Compare November 18, 2025 03:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
ledger/shelley/errors.go (1)

147-158: New InvalidCertificateDepositError type is sound; consider a stronger CertificateType type alias

The struct and Error() implementation are correct and consistent with the existing error patterns in this file, and the formatted message is clear. If you already have a dedicated certificate type enum/alias elsewhere in the ledger (rather than plain uint), wiring that in here for CertificateType would tighten compile-time checks and avoid accidental mismatches; otherwise this is fine to keep as-is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6072656 and 1e1dadb.

📒 Files selected for processing (8)
  • ledger/allegra/rules_test.go (1 hunks)
  • ledger/alonzo/rules_test.go (1 hunks)
  • ledger/babbage/rules_test.go (1 hunks)
  • ledger/conway/rules.go (2 hunks)
  • ledger/conway/rules_test.go (1 hunks)
  • ledger/mary/rules_test.go (1 hunks)
  • ledger/shelley/errors.go (1 hunks)
  • ledger/shelley/rules_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • ledger/conway/rules_test.go
  • ledger/mary/rules_test.go
  • ledger/allegra/rules_test.go
  • ledger/babbage/rules_test.go
  • ledger/conway/rules.go
  • ledger/alonzo/rules_test.go
  • ledger/shelley/rules_test.go

@wolf31o2 wolf31o2 force-pushed the fix/deposit-validation branch from 1e1dadb to 501fde2 Compare November 18, 2025 03:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
ledger/common/certs.go (1)

61-68: Resolve G115 int→uint conversion warning in UnmarshalCBOR

gosec is flagging the implicit int -> uint conversion in switch CertificateType(certType) (line 66). Even if CBOR IDs are guaranteed non‑negative, the unchecked cast is what’s breaking the pipeline.

You can both harden the code and quiet G115 by validating the sign once and converting to a uint variable that you then reuse:

 func (c *CertificateWrapper) UnmarshalCBOR(data []byte) error {
   // Determine cert type
-  certType, err := cbor.DecodeIdFromList(data)
+  certType, err := cbor.DecodeIdFromList(data)
   if err != nil {
     return err
   }
-  var tmpCert Certificate
-  switch CertificateType(certType) {
+  if certType < 0 {
+    return fmt.Errorf("negative certificate type: %d", certType)
+  }
+  certTypeUint := uint(certType) // #nosec G115 -- validated as non-negative
+  var tmpCert Certificate
+  switch CertificateType(certTypeUint) {
@@
-  // certType is known within uint range
-  c.Type = uint(certType) // #nosec G115
+  // certType validated and cached as uint
+  c.Type = certTypeUint
   c.Certificate = tmpCert
   return nil
 }

This preserves behavior, adds a defensive check for malformed CBOR, and should keep gosec happy by ensuring the only int→uint cast is guarded and explicitly annotated.

Also applies to: 115-116

🧹 Nitpick comments (3)
ledger/common/certs.go (1)

29-51: CertificateType alias and typed constants look good; consider propagating type usage later

Defining type CertificateType uint and explicitly typing the certificate constants improves clarity and enables stronger typing for things like InvalidCertificateDepositError. Right now, most struct fields (e.g., CertificateWrapper.Type, CertType on individual certs) remain plain uint, so you don't fully benefit from the new alias yet. Not urgent, but over time it may be worth migrating those fields to CertificateType for additional type safety.

ledger/conway/rules.go (1)

255-270: Conway deposit/refund handling looks solid overall; double‑check DeregistrationCertificate and zero‑amount assumptions

The updated UtxoValidateValueNotConservedUtxo logic now:

  • Treats deposits correctly on the produced side for:

    • *common.PoolRegistrationCertificate (using PoolDeposit when not already registered),
    • *common.RegistrationCertificate,
    • *common.RegistrationDrepCertificate,
    • *common.StakeRegistrationCertificate (using KeyDeposit),
    • *common.StakeRegistrationDelegationCertificate,
    • *common.StakeVoteRegistrationDelegationCertificate,
    • *common.VoteRegistrationDelegationCertificate,
      with InvalidCertificateDepositError returned on non‑positive Amount for the Amount‑based types.
  • Treats refunds on the consumed side for:

    • *common.StakeDeregistrationCertificate (KeyDeposit),
    • *common.DeregistrationCertificate (KeyDeposit),
    • *common.DeregistrationDrepCertificate (Amount, with the same non‑positive guard).

The pattern of checking Amount <= 0 before casting to uint64 is good: it both enforces a sane ledger invariant and avoids negative→unsigned wraparound.

Two things worth explicitly confirming against the Conway ledger spec:

  1. DeregistrationCertificate refund amount
    common.DeregistrationCertificate has an Amount int64 field, but here you still refund using tmpPparams.KeyDeposit and ignore tmpCert.Amount, while the corresponding RegistrationCertificate deposit is purely Amount‑based. If registrations can have deposit Amounts that differ from KeyDeposit, this asymmetry could still skew the consumed/produced balance. If, conversely, the spec guarantees Amount is either unused or always equal to KeyDeposit for this cert, the current logic is fine—but it would be good to confirm.

  2. Zero‑deposit certificates
    For all the Amount‑driven certs (registration and DRep/delegation variants), you treat Amount <= 0 as invalid. That matches the intent described in the PR, but it does bake in the assumption that “zero deposit” variants are always illegal. If the spec ever allows an Amount == 0 case (e.g., for future parameter changes), these transactions would now be rejected up‑front by InvalidCertificateDepositError.

Functionally, this change is a big step toward correct accounting of certificate deposits/refunds in Conway; I’d just recommend double‑checking those two assumptions to avoid subtle future mismatches.

Also applies to: 279-330

ledger/common/common_test.go (1)

756-875: Certificate type casting and expectations look correct

The updated cases consistently set CertType: uint(CertificateTypeX) and assert expected: uint(CertificateTypeX), which matches the structs’ CertType uint fields and the Certificate.Type() uint contract. The mapping across all the defined certificate types looks complete and correct.

If you want to future‑proof this, you could add a small meta‑test that asserts this table covers every CertificateType* constant so new types can’t be added without updating this test, but that’s optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1dadb and 501fde2.

📒 Files selected for processing (10)
  • ledger/allegra/rules_test.go (2 hunks)
  • ledger/alonzo/rules_test.go (2 hunks)
  • ledger/babbage/rules_test.go (2 hunks)
  • ledger/common/certs.go (3 hunks)
  • ledger/common/common_test.go (1 hunks)
  • ledger/conway/rules.go (2 hunks)
  • ledger/conway/rules_test.go (2 hunks)
  • ledger/mary/rules_test.go (2 hunks)
  • ledger/shelley/errors.go (1 hunks)
  • ledger/shelley/rules_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • ledger/conway/rules_test.go
  • ledger/shelley/rules_test.go
  • ledger/allegra/rules_test.go
  • ledger/mary/rules_test.go
🧰 Additional context used
🧬 Code graph analysis (5)
ledger/alonzo/rules_test.go (1)
ledger/common/certs.go (2)
  • CertificateTypeStakeRegistration (32-32)
  • CertificateTypeStakeDeregistration (33-33)
ledger/conway/rules.go (2)
ledger/common/certs.go (7)
  • DeregistrationCertificate (825-831)
  • DeregistrationDrepCertificate (1259-1265)
  • CertificateType (29-29)
  • RegistrationDrepCertificate (1208-1215)
  • StakeRegistrationDelegationCertificate (961-968)
  • StakeVoteRegistrationDelegationCertificate (1052-1060)
  • VoteRegistrationDelegationCertificate (1004-1011)
ledger/shelley/errors.go (1)
  • InvalidCertificateDepositError (147-150)
ledger/common/common_test.go (1)
ledger/common/certs.go (25)
  • CertificateTypeStakeRegistration (32-32)
  • StakeDeregistrationCertificate (253-258)
  • CertificateTypeStakeDeregistration (33-33)
  • CertificateTypeStakeDelegation (34-34)
  • PoolRegistrationCertificate (432-445)
  • CertificateTypePoolRegistration (35-35)
  • PoolRetirementCertificate (614-620)
  • CertificateTypePoolRetirement (36-36)
  • CertificateTypeGenesisKeyDelegation (37-37)
  • CertificateTypeMoveInstantaneousRewards (38-38)
  • RegistrationCertificate (784-790)
  • CertificateTypeRegistration (39-39)
  • DeregistrationCertificate (825-831)
  • CertificateTypeDeregistration (40-40)
  • CertificateTypeVoteDelegation (41-41)
  • CertificateTypeStakeVoteDelegation (42-42)
  • CertificateTypeStakeRegistrationDelegation (43-43)
  • CertificateTypeVoteRegistrationDelegation (44-44)
  • CertificateTypeStakeVoteRegistrationDelegation (45-45)
  • CertificateTypeAuthCommitteeHot (46-46)
  • CertificateTypeResignCommitteeCold (47-47)
  • CertificateTypeRegistrationDrep (48-48)
  • CertificateTypeDeregistrationDrep (49-49)
  • UpdateDrepCertificate (1300-1306)
  • CertificateTypeUpdateDrep (50-50)
ledger/shelley/errors.go (1)
ledger/common/certs.go (1)
  • CertificateType (29-29)
ledger/babbage/rules_test.go (1)
ledger/common/certs.go (2)
  • CertificateTypeStakeRegistration (32-32)
  • CertificateTypeStakeDeregistration (33-33)
🪛 GitHub Actions: golangci-lint
ledger/common/certs.go

[error] 66-66: golangci-lint (gosec) G115: integer overflow conversion int -> uint detected at line 66. Command: golangci-lint run

🪛 GitHub Check: lint
ledger/common/certs.go

[failure] 66-66:
G115: integer overflow conversion int -> uint (gosec)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (4)
ledger/common/certs.go (1)

1379-1381: LeiosEbCertificate.Type now correctly uses the typed constant

Returning uint(CertificateTypeLeiosEb) keeps the public Type() uint signature while tying the value back to the strongly‑typed constant. This is a clean way to keep compatibility with existing callers.

ledger/alonzo/rules_test.go (1)

561-595: Test updates correctly follow the new CertificateType representation

Casting common.CertificateTypeStakeRegistration / CertificateTypeStakeDeregistration to uint for CertificateWrapper.Type keeps the tests consistent with the new CertificateType alias and the uint-typed field, without changing semantics.

ledger/shelley/errors.go (1)

146-158: New InvalidCertificateDepositError cleanly surfaces bad certificate deposits

The InvalidCertificateDepositError type is well‑shaped for the new Conway validation: it carries both the typed certificate kind and the offending Amount, and the Error() message is consistent with the other error types in this file. This should make diagnosing malformed certificate deposits straightforward.

ledger/babbage/rules_test.go (1)

561-595: Babbage value‑conservation tests correctly updated for CertificateType

Using uint(common.CertificateTypeStakeRegistration) / StakeDeregistration for CertificateWrapper.Type brings these tests in line with the new CertificateType alias and the underlying uint field type, matching the pattern used in other era tests.

Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
@wolf31o2 wolf31o2 force-pushed the fix/deposit-validation branch from 501fde2 to e86696b Compare November 18, 2025 04:03
@wolf31o2 wolf31o2 merged commit 47e67cc into main Nov 18, 2025
10 checks passed
@wolf31o2 wolf31o2 deleted the fix/deposit-validation branch November 18, 2025 14:30
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.

Deposit validation fails for certificate transactions with 'value not conserved' error

3 participants