-
Notifications
You must be signed in to change notification settings - Fork 6
feat: implement unified certificate persistence system #998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughThis PR implements a unified certificate tracking system by introducing a central Certificate table with polymorphic references to type-specific certificate tables via CertificateID. It consolidates scattered certificate persistence methods from the database, drep, and account layers into SetTransaction, which now computes deposits upfront and coordinates storage. The Uint64 type is refactored from a simple alias to a struct wrapper. Account queries gain includeInactive filtering. The LedgerState adds goroutine lifecycle management with context cancellation. Configuration parameters for ledger catchup timing are introduced, and comprehensive test coverage is added for certificate persistence, account handling, and transaction flows. Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Possibly related PRs
Pre-merge checks and finishing touches✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
database/account.go(0 hunks)database/certificates.go(1 hunks)database/certs.go(0 hunks)database/drep.go(0 hunks)database/models/certificates.go(1 hunks)database/models/models.go(1 hunks)database/plugin/metadata/sqlite/account.go(0 hunks)database/plugin/metadata/sqlite/database.go(2 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/store.go(1 hunks)go.mod(3 hunks)ledger/certs.go(1 hunks)ledger/certs_test.go(1 hunks)ledger/delta.go(1 hunks)
💤 Files with no reviewable changes (5)
- database/certs.go
- database/plugin/metadata/sqlite/drep.go
- database/drep.go
- database/plugin/metadata/sqlite/account.go
- database/account.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/certs_test.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
ledger/certs_test.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/certs_test.go
🧬 Code graph analysis (5)
database/models/models.go (1)
database/models/certificates.go (2)
Certificates(20-26)Certificates(28-30)
database/certificates.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
ledger/certs_test.go (6)
database/plugin/metadata/sqlite/database.go (2)
New(256-328)MetadataStoreSqlite(247-253)chain/manager.go (2)
NewManager(46-61)ChainManager(37-44)ledger/state.go (2)
NewLedgerState(104-123)LedgerStateConfig(58-68)database/database.go (1)
Database(45-50)database/models/certificates.go (2)
Certificates(20-26)Certificates(28-30)database/models/pool.go (1)
Pool(26-38)
database/plugin/metadata/sqlite/database.go (7)
database/models/certificates.go (2)
Certificates(20-26)Certificates(28-30)database/models/account.go (20)
StakeRegistration(81-87)StakeRegistration(89-91)StakeDeregistration(70-75)StakeDeregistration(77-79)StakeDelegation(58-64)StakeDelegation(66-68)Registration(46-52)Registration(54-56)Deregistration(35-40)Deregistration(42-44)StakeRegistrationDelegation(93-100)StakeRegistrationDelegation(102-104)VoteDelegation(133-139)VoteDelegation(141-143)StakeVoteDelegation(106-113)StakeVoteDelegation(115-117)VoteRegistrationDelegation(145-152)VoteRegistrationDelegation(154-156)StakeVoteRegistrationDelegation(119-127)StakeVoteRegistrationDelegation(129-131)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(54-54)database/models/drep.go (7)
Drep(17-25)RegistrationDrep(43-51)RegistrationDrep(53-55)DeregistrationDrep(31-37)DeregistrationDrep(39-41)UpdateDrep(57-64)UpdateDrep(66-68)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(17-22)AuthCommitteeHot(24-26)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(17-23)ResignCommitteeCold(25-27)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(78-102)database/txn.go (1)
Txn(27-34)
⏰ 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). (4)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: lint
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
38f903c to
1f75a2d
Compare
There was a problem hiding this 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
♻️ Duplicate comments (5)
ledger/certs.go (1)
35-53: Populate deposit before calling SetCertificate.
depositstays zero for every certificate because this loop never sets it. The metadata/store path needs the real deposit value to persist stake registration/deregistration funding and refunds; sending zero regresses deposit accounting in the DB. Please restore the prior deposit calculation (the logic that consulted protocol parameters before this refactor) and pass that value intoSetCertificate, returning an error if the lookup fails.database/plugin/metadata/sqlite/database.go (4)
35-43: Populate certificates with the owning transaction.
insertCertalways writesTransactionID: 0, so every row incertificatesends up detached from the transaction it belongs to. We already havetxHashin scope—please look up themodels.Transactionrow once (inside SetCertificate) and use its ID when creating the mapping. Without that link the new unified table cannot satisfy the "track transaction certificates" requirement.Apply this diff to fix the issue:
+ var txRow models.Transaction + if err := txn.Where(&models.Transaction{Hash: txHash}).First(&txRow).Error; err != nil { + return fmt.Errorf("lookup transaction for certificate: %w", err) + } // Helper closure to insert certificates mapping row insertCert := func(certType uint, targetID uint) error { certRow := models.Certificates{ - TransactionID: 0, + TransactionID: txRow.ID, CertIndex: uint(blockIndex), CertType: certType, CertificateID: targetID,
76-88: Persist full pool registration payload.The pool registration branch no longer records margin, metadata (URL/hash), owners, or relays, all of which were persisted by the old
SetPoolRegistrationpath. Dropping them is a data-loss regression. Please restore the previous field population (including helper logic for owners/relays) before inserting the model so the unified method keeps parity with the legacy setters.
130-168: Do not drop the DRep payload.All vote-related certificates leave
models.*.Drepempty (or missing entirely), so every delegation/registration written through the new path loses the DRep selection. That breaks #878's goal of tracking certificate content. Please carry over the DRep serialization we had in the removedSetVoteDelegation,SetStakeVoteDelegation,SetVoteRegistrationDelegation, andSetStakeVoteRegistrationDelegationimplementations so the bytes written to the DB still identify the chosen DRep.
180-216: Restore anchor fields for DRep/committee certs.
ResignCommitteeColdCertificate,RegistrationDrepCertificate,DeregistrationDrepCertificate, andUpdateDrepCertificateall expose anchor data (URL/hash) that we currently discard. The legacy setters populatedAnchorUrl/AnchorHash; without that the new unified flow loses essential governance metadata. Please reapply the anchor assignments from the old code before inserting the models.
🧹 Nitpick comments (2)
ledger/delta.go (1)
101-103: Remove duplicate transaction index validation.This range check duplicates the validation already performed at lines 49-50. The transaction index is already verified before processing begins.
Apply this diff to remove the duplicate check:
// Certificates txHash := tr.Tx.Hash() txHashBytes := txHash.Bytes() - if tr.Index < 0 || tr.Index > 4294967295 { - return fmt.Errorf("transaction index out of range: %d", tr.Index) - } err = ls.ProcessTransactionCertificates(ledger/certs.go (1)
54-56: Remove redundant certificate index validation.The
idxvariable comes from arangeloop over thecertsslice (line 34), so it's always within valid bounds (0 to len(certs)-1). This explicit range check againstmath.MaxUint32is unnecessary.Apply this diff to remove the redundant check:
- if idx < 0 || idx > 4294967295 { - return fmt.Errorf("certificate index out of range: %d", idx) - } if err := ls.db.SetCertificate(cert, txHash, uint32(idx), blockPoint, deposit, amount, txn); err != nil {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
database/account.go(0 hunks)database/certificates.go(1 hunks)database/certs.go(0 hunks)database/drep.go(0 hunks)database/models/certificates.go(1 hunks)database/models/models.go(1 hunks)database/plugin/metadata/sqlite/account.go(0 hunks)database/plugin/metadata/sqlite/database.go(2 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/store.go(1 hunks)ledger/certs.go(1 hunks)ledger/delta.go(1 hunks)
💤 Files with no reviewable changes (5)
- database/certs.go
- database/plugin/metadata/sqlite/drep.go
- database/drep.go
- database/account.go
- database/plugin/metadata/sqlite/account.go
🚧 Files skipped from review as they are similar to previous changes (3)
- database/models/models.go
- database/models/certificates.go
- database/certificates.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/database.go
🧬 Code graph analysis (3)
ledger/delta.go (1)
database/models/certificates.go (2)
Certificates(20-26)Certificates(28-30)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(78-102)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/database.go (7)
database/models/certificates.go (2)
Certificates(20-26)Certificates(28-30)database/models/account.go (20)
StakeRegistration(81-87)StakeRegistration(89-91)StakeDeregistration(70-75)StakeDeregistration(77-79)StakeDelegation(58-64)StakeDelegation(66-68)Registration(46-52)Registration(54-56)Deregistration(35-40)Deregistration(42-44)StakeRegistrationDelegation(93-100)StakeRegistrationDelegation(102-104)VoteDelegation(133-139)VoteDelegation(141-143)StakeVoteDelegation(106-113)StakeVoteDelegation(115-117)VoteRegistrationDelegation(145-152)VoteRegistrationDelegation(154-156)StakeVoteRegistrationDelegation(119-127)StakeVoteRegistrationDelegation(129-131)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(54-54)database/models/drep.go (7)
Drep(17-25)RegistrationDrep(43-51)RegistrationDrep(53-55)DeregistrationDrep(31-37)DeregistrationDrep(39-41)UpdateDrep(57-64)UpdateDrep(66-68)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(17-22)AuthCommitteeHot(24-26)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(17-23)ResignCommitteeCold(25-27)
⏰ 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). (2)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (2)
database/plugin/metadata/sqlite/database.go (1)
262-268: LGTM: In-memory configuration aligns with shared-cache mode.The change to use
file::memory:?cache=sharedwithSkipDefaultTransaction: falseproperly enables concurrent transaction support for in-memory testing scenarios.database/plugin/metadata/store.go (1)
100-109: LGTM: Clean consolidation of certificate storage interface.The unified
SetCertificatemethod successfully replaces 16 specialized setters with a single, well-documented API. This simplifies the interface while providing all necessary parameters (certificate, transaction hash, index, block point, deposit, and amount) for complete certificate persistence.
1f75a2d to
d567984
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
database/models/certificates.go (2)
20-26: Consider adding temporal metadata for certificate queries.The Certificates mapping table currently lacks slot and block hash fields, which could make temporal queries (e.g., "certificates active at slot X") more expensive since they would require joining through the Transaction table. If certificate lifecycle queries by slot or block are anticipated, consider adding:
type Certificates struct { ID uint `gorm:"primaryKey"` TransactionID uint `gorm:"index"` + Slot uint64 `gorm:"index"` + BlockHash []byte CertIndex uint `gorm:"column:cert_index"` // Index of the certificate in the transaction CertType uint // Type of certificate (matches lcommon.Certificate type) CertificateID uint `gorm:"index"` // FK to the appropriate certificate table (e.g., certs, registration, etc.) }
25-25: Document the implicit foreign key relationship.
CertificateIDis described as a FK to various certificate tables, but GORM cannot enforce a foreign key constraint when the target table varies byCertType. Consider adding a comment clarifying this is a logical (not database-enforced) FK and that referential integrity must be maintained at the application layer.- CertificateID uint `gorm:"index"` // FK to the appropriate certificate table (e.g., certs, registration, etc.) + CertificateID uint `gorm:"index"` // Logical FK to the appropriate certificate table based on CertType (e.g., certs, pool_registration, etc.). Not enforced by DB constraints.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
database/account.go(0 hunks)database/certificates.go(1 hunks)database/drep.go(0 hunks)database/models/certificates.go(1 hunks)database/models/models.go(1 hunks)database/plugin/metadata/sqlite/account.go(0 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/sqlite/transaction.go(4 hunks)database/plugin/metadata/store.go(2 hunks)database/pparams.go(2 hunks)database/transaction.go(2 hunks)internal/config/config_test.go(1 hunks)ledger/certs.go(1 hunks)ledger/delta.go(2 hunks)
💤 Files with no reviewable changes (4)
- database/plugin/metadata/sqlite/drep.go
- database/plugin/metadata/sqlite/account.go
- database/account.go
- database/drep.go
✅ Files skipped from review due to trivial changes (1)
- internal/config/config_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- database/models/models.go
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/delta.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/database.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/plugin/metadata/store.go
🧬 Code graph analysis (5)
database/certificates.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(78-102)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/transaction.go (7)
database/models/certificates.go (2)
Certificates(20-26)Certificates(28-30)database/models/account.go (20)
StakeRegistration(81-87)StakeRegistration(89-91)StakeDeregistration(70-75)StakeDeregistration(77-79)StakeDelegation(58-64)StakeDelegation(66-68)Registration(46-52)Registration(54-56)Deregistration(35-40)Deregistration(42-44)StakeRegistrationDelegation(93-100)StakeRegistrationDelegation(102-104)VoteDelegation(133-139)VoteDelegation(141-143)StakeVoteDelegation(106-113)StakeVoteDelegation(115-117)VoteRegistrationDelegation(145-152)VoteRegistrationDelegation(154-156)StakeVoteRegistrationDelegation(119-127)StakeVoteRegistrationDelegation(129-131)database/models/pool.go (9)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (2)
Uint64(54-54)Rat(25-27)database/models/drep.go (7)
Drep(17-25)RegistrationDrep(43-51)RegistrationDrep(53-55)DeregistrationDrep(31-37)DeregistrationDrep(39-41)UpdateDrep(57-64)UpdateDrep(66-68)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(17-22)AuthCommitteeHot(24-26)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(17-23)ResignCommitteeCold(25-27)
database/plugin/metadata/sqlite/database.go (2)
database/database.go (1)
Config(35-42)database/plugin/log.go (1)
Logger(18-27)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/utxo.go (1)
Utxo(23-38)
⏰ 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). (4)
- GitHub Check: go-test (1.24.x, ubuntu-latest)
- GitHub Check: nilaway
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: Analyze (go)
🔇 Additional comments (11)
database/pparams.go (1)
32-45: Correct error propagation fix.Switching to
ppErrensures we actually surface metadata failures instead of silently returning decoded results.ledger/delta.go (1)
71-90: Deposits wiring looks solid.Calculating deposits up front and threading them through the persistence path keeps the invalid-transaction shortcut intact while enriching the valid path.
database/transaction.go (1)
47-54: Metadata call update matches new API.Forwarding
certificateDepositshere cleanly bridges the ledger layer to the metadata store.database/certificates.go (1)
21-38: Docs now reflect unified persistence.Nice to see the comments pointing callers at
SetTransaction; that prevents misuse of the old setters.ledger/certs.go (2)
26-50: LGTM: Deposit calculation correctly implemented.The deposit calculation logic properly:
- Iterates through certificates and computes deposits using the current era's deposit function
- Populates the deposits slice at each index (line 46)
- Provides clear error context including the certificate index
- Returns the complete deposits array for downstream storage
The past review concern about deposits staying zero has been addressed.
52-67: LGTM: Clear deprecation strategy.The deprecation approach is well-executed:
- Returns an explicit error rather than silently failing
- Error message directs developers to the replacement pattern (CalculateCertificateDeposits + SetTransaction)
- Function signature retained for backward compatibility during transition
database/plugin/metadata/sqlite/database.go (3)
84-85: Verify sync OFF is acceptable for your consistency requirements.The persistent database uses
_pragma=sync(OFF)which disables fsync, trading durability for performance. If the process crashes or the system loses power, recent commits may be lost. For a blockchain indexer that can resync from the network, this is often acceptable. However, if your use case requires strict durability guarantees or you lack easy resync capability, consider using_pragma=sync(NORMAL)instead.
175-189: LGTM: Proper cleanup in Close().The Close() method now stops the vacuum timer (lines 179-181) before closing the database connection, preventing potential goroutine leaks. Error wrapping provides clear context if obtaining the DB handle fails.
191-228: LGTM: Wrapper methods expose GORM operations.The wrapper methods (Create, DB, First, Order, Transaction, Where) provide a clean public API for database operations. The documentation comments clearly describe each method's purpose, and the Transaction() wrapper appropriately calls Begin() to start a new transaction.
database/plugin/metadata/store.go (2)
126-132: LGTM: SetTransaction signature aligns with new deposit calculation flow.The updated signature correctly:
- Adds
uint32transaction index for certificate position tracking within the block- Adds
[]uint64certificate deposits that match the return type fromLedgerState.CalculateCertificateDeposits- Maintains the
*gorm.DBtransaction parameter for atomic operationsThis change enables the ledger layer to calculate deposits once and pass them to the database layer for storage, avoiding redundant protocol parameter lookups.
143-145: LGTM: Type consolidation improves consistency.Changing from
ledger.Address/ledger.TransactionInputtolcommon.Address/lcommon.TransactionInputstandardizes the interface to use common ledger types throughout. This reduces import confusion and aligns with the broader codebase pattern of usinglcommon.*types at API boundaries.
d567984 to
051af00
Compare
There was a problem hiding this 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
♻️ Duplicate comments (1)
database/plugin/metadata/sqlite/transaction.go (1)
667-690: Specialized certificate tables still leak on retries
cleanupExistingCertificatesonly purges the mapping rows; the specialised tables keep the previous inserts. WhenSetTransactionreplays a block (which is expected on restart or rollback), every certificate is duplicated instake_registration,pool_registration, etc. Downstream queries that read those tables directly now see phantom entries, and storage bloat compounds on every retry. Please delete the underlying per-type rows before dropping the mappings, just like the removed setters did.func (d *MetadataStoreSqlite) cleanupExistingCertificates( transactionID uint, txn *gorm.DB, ) error { - // Delete from the certificates mapping table - // Note: This relies on application-level cleanup since we can't use FK cascades - // due to the polymorphic nature of CertificateID - result := txn.Where("transaction_id = ?", transactionID). - Delete(&models.Certificates{}) - if result.Error != nil { - return fmt.Errorf("delete certificates mapping: %w", result.Error) - } - - // Note: Individual certificate records in specialized tables (stake_registration, - // pool_registration, etc.) are not automatically cleaned up here since they don't - // have back-references to the certificates table. In practice, this is acceptable - // since the mapping table is the primary way to access certificates by transaction, - // and orphaned records in specialized tables don't affect correctness. - // - // If cleanup of specialized tables becomes necessary, it would require: - // 1. First querying certificates table for the certificate IDs and types - // 2. Then deleting from each specialized table based on the certificate type - // However, this adds significant complexity and may not be worth the storage overhead. - // - - return nil + var existing []models.Certificates + if err := txn. + Where("transaction_id = ?", transactionID). + Find(&existing).Error; err != nil { + return fmt.Errorf("lookup certificates for transaction %d: %w", transactionID, err) + } + + for _, certRow := range existing { + if err := d.deleteCertificateRecord(txn, certRow.CertType, certRow.CertificateID); err != nil { + return fmt.Errorf( + "delete certificate %d (type %d): %w", + certRow.CertificateID, + certRow.CertType, + err, + ) + } + } + + if err := txn. + Where("transaction_id = ?", transactionID). + Delete(&models.Certificates{}).Error; err != nil { + return fmt.Errorf("delete certificates mapping: %w", err) + } + + return nil } + +func (d *MetadataStoreSqlite) deleteCertificateRecord( + txn *gorm.DB, + certType uint, + certID uint, +) error { + switch certType { + case lcommon.CertificateTypeStakeRegistration: + return txn.Delete(&models.StakeRegistration{}, certID).Error + case lcommon.CertificateTypeStakeDeregistration: + return txn.Delete(&models.StakeDeregistration{}, certID).Error + case lcommon.CertificateTypeStakeDelegation: + return txn.Delete(&models.StakeDelegation{}, certID).Error + case lcommon.CertificateTypePoolRegistration: + return txn.Delete(&models.PoolRegistration{}, certID).Error + case lcommon.CertificateTypePoolRetirement: + return txn.Delete(&models.PoolRetirement{}, certID).Error + case lcommon.CertificateTypeRegistration: + return txn.Delete(&models.Registration{}, certID).Error + case lcommon.CertificateTypeDeregistration: + return txn.Delete(&models.Deregistration{}, certID).Error + case lcommon.CertificateTypeStakeRegistrationDelegation: + return txn.Delete(&models.StakeRegistrationDelegation{}, certID).Error + case lcommon.CertificateTypeVoteDelegation: + return txn.Delete(&models.VoteDelegation{}, certID).Error + case lcommon.CertificateTypeStakeVoteDelegation: + return txn.Delete(&models.StakeVoteDelegation{}, certID).Error + case lcommon.CertificateTypeVoteRegistrationDelegation: + return txn.Delete(&models.VoteRegistrationDelegation{}, certID).Error + case lcommon.CertificateTypeStakeVoteRegistrationDelegation: + return txn.Delete(&models.StakeVoteRegistrationDelegation{}, certID).Error + case lcommon.CertificateTypeAuthCommitteeHot: + return txn.Delete(&models.AuthCommitteeHot{}, certID).Error + case lcommon.CertificateTypeResignCommitteeCold: + return txn.Delete(&models.ResignCommitteeCold{}, certID).Error + case lcommon.CertificateTypeRegistrationDrep: + return txn.Delete(&models.RegistrationDrep{}, certID).Error + case lcommon.CertificateTypeDeregistrationDrep: + return txn.Delete(&models.DeregistrationDrep{}, certID).Error + case lcommon.CertificateTypeUpdateDrep: + return txn.Delete(&models.UpdateDrep{}, certID).Error + case lcommon.CertificateTypeGenesisKeyDelegation, + lcommon.CertificateTypeMoveInstantaneousRewards, + lcommon.CertificateTypeLeiosEb: + return txn.Delete(&models.Certificate{}, certID).Error + default: + return txn.Delete(&models.Certificate{}, certID).Error + } +}
🧹 Nitpick comments (1)
database/models/certificates.go (1)
24-30: Add indexes to enforce lookup & uniqueness.Right now
BlockHashisn’t indexed and(TransactionID, CertIndex)isn’t enforced unique. Block-level fetches will scan the whole table, and duplicate rows for the same certificate can slip in, defeating the mapping guarantee. Please add an index onBlockHashand a shared unique index acrossTransactionIDandCertIndex(e.g.gorm:"index;uniqueIndex:uniq_tx_cert"/gorm:"column:cert_index;uniqueIndex:uniq_tx_cert"). (gorm.io)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
database/account.go(0 hunks)database/certificates.go(1 hunks)database/drep.go(0 hunks)database/models/certificates.go(1 hunks)database/models/models.go(1 hunks)database/plugin/metadata/sqlite/account.go(0 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/sqlite/transaction.go(4 hunks)database/plugin/metadata/store.go(2 hunks)database/pparams.go(2 hunks)database/transaction.go(2 hunks)internal/config/config_test.go(1 hunks)ledger/certs.go(1 hunks)ledger/delta.go(2 hunks)
💤 Files with no reviewable changes (4)
- database/drep.go
- database/plugin/metadata/sqlite/account.go
- database/account.go
- database/plugin/metadata/sqlite/drep.go
🚧 Files skipped from review as they are similar to previous changes (2)
- internal/config/config_test.go
- database/transaction.go
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/delta.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/database.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/plugin/metadata/store.go
🧬 Code graph analysis (7)
database/models/models.go (1)
database/models/certificates.go (2)
Certificates(23-31)Certificates(34-36)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(78-102)database/txn.go (1)
Txn(27-34)
ledger/delta.go (1)
database/models/certificates.go (2)
Certificates(23-31)Certificates(34-36)
database/plugin/metadata/sqlite/database.go (2)
config.go (1)
Config(33-55)database/database.go (1)
Config(35-42)
database/plugin/metadata/sqlite/transaction.go (9)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(38-44)database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/certificates.go (2)
Certificates(23-31)Certificates(34-36)database/models/account.go (20)
StakeRegistration(81-87)StakeRegistration(89-91)StakeDeregistration(70-75)StakeDeregistration(77-79)StakeDelegation(58-64)StakeDelegation(66-68)Registration(46-52)Registration(54-56)Deregistration(35-40)Deregistration(42-44)StakeRegistrationDelegation(93-100)StakeRegistrationDelegation(102-104)VoteDelegation(133-139)VoteDelegation(141-143)StakeVoteDelegation(106-113)StakeVoteDelegation(115-117)VoteRegistrationDelegation(145-152)VoteRegistrationDelegation(154-156)StakeVoteRegistrationDelegation(119-127)StakeVoteRegistrationDelegation(129-131)database/models/pool.go (9)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (2)
Uint64(54-54)Rat(25-27)database/models/drep.go (7)
Drep(17-25)RegistrationDrep(43-51)RegistrationDrep(53-55)DeregistrationDrep(31-37)DeregistrationDrep(39-41)UpdateDrep(57-64)UpdateDrep(66-68)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(17-22)AuthCommitteeHot(24-26)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(17-23)ResignCommitteeCold(25-27)
database/certificates.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/utxo.go (1)
Utxo(23-38)
⏰ 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). (4)
- GitHub Check: nilaway
- GitHub Check: go-test (1.24.x, ubuntu-latest)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: Analyze (go)
051af00 to
b942939
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
database/account.go(0 hunks)database/certificates.go(1 hunks)database/drep.go(0 hunks)database/models/certificates.go(1 hunks)database/models/models.go(1 hunks)database/plugin/metadata/sqlite/account.go(0 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/sqlite/transaction.go(4 hunks)database/plugin/metadata/store.go(2 hunks)database/pparams.go(2 hunks)database/transaction.go(2 hunks)internal/config/config_test.go(1 hunks)ledger/certs.go(1 hunks)ledger/delta.go(2 hunks)
💤 Files with no reviewable changes (4)
- database/drep.go
- database/plugin/metadata/sqlite/account.go
- database/plugin/metadata/sqlite/drep.go
- database/account.go
🚧 Files skipped from review as they are similar to previous changes (3)
- database/pparams.go
- internal/config/config_test.go
- database/models/models.go
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/database.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/delta.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/plugin/metadata/store.go
🧬 Code graph analysis (5)
database/certificates.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(78-102)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/transaction.go (8)
database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/certificates.go (2)
Certificates(19-27)Certificates(30-32)database/models/account.go (20)
StakeRegistration(81-87)StakeRegistration(89-91)StakeDeregistration(70-75)StakeDeregistration(77-79)StakeDelegation(58-64)StakeDelegation(66-68)Registration(46-52)Registration(54-56)Deregistration(35-40)Deregistration(42-44)StakeRegistrationDelegation(93-100)StakeRegistrationDelegation(102-104)VoteDelegation(133-139)VoteDelegation(141-143)StakeVoteDelegation(106-113)StakeVoteDelegation(115-117)VoteRegistrationDelegation(145-152)VoteRegistrationDelegation(154-156)StakeVoteRegistrationDelegation(119-127)StakeVoteRegistrationDelegation(129-131)database/models/pool.go (9)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (2)
Uint64(54-54)Rat(25-27)database/models/drep.go (7)
Drep(17-25)RegistrationDrep(43-51)RegistrationDrep(53-55)DeregistrationDrep(31-37)DeregistrationDrep(39-41)UpdateDrep(57-64)UpdateDrep(66-68)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(17-22)AuthCommitteeHot(24-26)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(17-23)ResignCommitteeCold(25-27)
database/transaction.go (2)
database/database.go (1)
Database(45-50)database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/utxo.go (1)
Utxo(23-38)
⏰ 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). (4)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: nilaway
- GitHub Check: go-test (1.24.x, ubuntu-latest)
- GitHub Check: Analyze (go)
🔇 Additional comments (2)
database/plugin/metadata/store.go (2)
126-131: Unified SetTransaction signature looks solidPassing the
[]uint64deposit slice through the interface keeps the metadata layer in sync with the new ledger-side certificate deposit calculation while preserving existing call semantics (nil for “no deposits”). All good here.
143-145: Nice consistency on ledger/common typesSwitching to
lcommon.Addressandlcommon.TransactionInputmatches the current ledger/common usage elsewhere, so the interface stays coherent across layers.
b942939 to
4c8a343
Compare
There was a problem hiding this 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)
database/models/certificates.go (1)
19-27: Consider explicit foreign key constraint on TransactionID.The model properly implements the unified mapping table with a composite unique index on
(TransactionID, CertIndex). However, there's no explicit GORM foreign key tag onTransactionIDto reference thetransactiontable'sIDfield.While GORM's conventions may handle this implicitly, consider making the relationship explicit for clarity and to ensure referential integrity:
- TransactionID uint `gorm:"index;uniqueIndex:uniq_tx_cert"` + TransactionID uint `gorm:"index;uniqueIndex:uniq_tx_cert;constraint:OnDelete:CASCADE"`This would ensure that deleting a transaction cascades to remove its certificate mappings, maintaining referential integrity at the database level.
Based on learnings
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
database/account.go(0 hunks)database/certificates.go(1 hunks)database/drep.go(0 hunks)database/models/certificates.go(1 hunks)database/models/models.go(1 hunks)database/plugin/metadata/sqlite/account.go(0 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/store.go(2 hunks)database/pparams.go(2 hunks)database/transaction.go(2 hunks)internal/config/config_test.go(1 hunks)ledger/certs.go(1 hunks)ledger/delta.go(2 hunks)
💤 Files with no reviewable changes (4)
- database/plugin/metadata/sqlite/drep.go
- database/drep.go
- database/plugin/metadata/sqlite/account.go
- database/account.go
🚧 Files skipped from review as they are similar to previous changes (1)
- database/plugin/metadata/sqlite/database.go
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/delta.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/plugin/metadata/store.go
🧬 Code graph analysis (7)
database/models/models.go (1)
database/models/certificates.go (2)
Certificates(19-27)Certificates(30-32)
ledger/delta.go (1)
database/models/certificates.go (2)
Certificates(19-27)Certificates(30-32)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(78-102)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/transaction.go (9)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(38-44)database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/certificates.go (2)
Certificates(19-27)Certificates(30-32)database/models/account.go (12)
StakeRegistration(81-87)StakeRegistration(89-91)StakeDeregistration(70-75)StakeDeregistration(77-79)StakeDelegation(58-64)StakeDelegation(66-68)Registration(46-52)Registration(54-56)Deregistration(35-40)Deregistration(42-44)StakeRegistrationDelegation(93-100)StakeRegistrationDelegation(102-104)database/models/pool.go (9)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (2)
Uint64(54-54)Rat(25-27)database/models/drep.go (7)
Drep(17-25)RegistrationDrep(43-51)RegistrationDrep(53-55)DeregistrationDrep(31-37)DeregistrationDrep(39-41)UpdateDrep(57-64)UpdateDrep(66-68)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(17-22)AuthCommitteeHot(24-26)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(17-23)ResignCommitteeCold(25-27)
database/certificates.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/transaction.go (1)
database/database.go (1)
Database(45-50)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/utxo.go (1)
Utxo(23-38)
⏰ 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). (4)
- GitHub Check: go-test (1.24.x, ubuntu-latest)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (16)
internal/config/config_test.go (1)
1-13: License header aligns with compliance.No code behavior changes and the header matches our standard licensing block.
database/pparams.go (1)
33-54: Good catch on propagating metadata errors.Checking
ppErrin both branches ensures we no longer swallow failures fromGetPParams.database/models/models.go (1)
24-24: Migration list update looks correct.Including
&Certificates{}keeps the new unified table in sync with schema migrations.ledger/delta.go (1)
71-93: Nice integration of certificate deposit handling.Calculating deposits once per transaction and passing them into
SetTransactionkeeps the ledger and persistence layers aligned with the new unified model.database/certificates.go (1)
21-38: Comment updates improve clarity.Documenting that
SetTransactionnow owns certificate persistence helps future readers navigate the unified flow.database/transaction.go (1)
22-54: LGTM!The signature extension to include
certificateDepositsis clean and the forwarding to the metadata layer is correct. The updated comment accurately reflects that certificates are now persisted alongside the transaction.ledger/certs.go (2)
26-50: LGTM - Deposit calculation properly implemented.The function correctly computes deposits using protocol parameters via
currentEra.CertDepositFunc. The error context includes the certificate index, which aids debugging. This addresses the concern from the previous review about missing deposit calculation.
52-67: Let me conduct a more comprehensive search to ensure there are truly no callers:Based on the search results, I can now verify the concern. The grep search found no existing callers of
ProcessTransactionCertificatesin the codebase, which suggests the function may already be unused.However, the Go community convention is that functions must be kept for compatibility with existing programs. The current implementation returns a hard error, which contradicts Go's backward compatibility principles. An API feature being deprecated does not mean it is going to be removed in the future; on the contrary, Go 1 compatibility means the feature will be preserved in its deprecated form to keep existing programs running.
Since the search found no internal callers and the deprecation appears intentional, verify whether this PR is part of a major version bump or an intentional breaking change before merging.
database/plugin/metadata/sqlite/transaction.go (6)
97-102: LGTM - Transaction ID reload resolves prior concern.The transaction ID is now properly reloaded after the upsert when
tmpTx.ID == 0. This ensures that the certificate persistence logic (lines 300-335) operates with the correct primary key value, addressing the critical issue raised in the previous review.
300-335: LGTM - Certificate persistence flow is well-structured.The certificate handling logic properly:
- Validates that certificate and deposit counts match (lines 302-308)
- Cleans up existing certificates to prevent duplicates (lines 310-316)
- Persists each certificate with its deposit (lines 318-334)
All error handling includes proper context with wrapped errors for debugging. This resolves the duplicate certificate concern raised in the previous review.
340-413: LGTM - Well-structured helper with proper validation.The
storeCertificatehelper function is well-designed:
- Input validation prevents nil pointer issues (lines 349-354)
- The
getCertTypelocal function provides a clean type mapping (lines 356-401)- The
insertCertlocal function centralizes certificate mapping creation (lines 403-413)This design pattern makes the subsequent switch statement cases cleaner and more maintainable.
415-660: Comprehensive certificate type coverage.The switch statement handles all standard Cardano certificate types:
- Stake operations: registration, deregistration, delegation (lines 416-444)
- Pool operations: registration with relays/owners/metadata, retirement (lines 445-497)
- Conway-era certificates: registration, deregistration, stake delegation combos (lines 498-527)
- Vote/DRep operations: delegation, registration, deregistration, updates (lines 528-634)
- Committee operations: hot key auth, cold key resignation (lines 572-595)
- Special cases: Genesis delegation, MIR, Leios (lines 635-656)
Each case properly:
- Extracts relevant fields from the typed certificate
- Populates the specialized model with deposit where applicable
- Creates the specialized record
- Calls
insertCertto create the mapping entryThe default case (line 658) returns a descriptive error for unsupported types.
662-695: LGTM - Cleanup logic prevents duplicates effectively.The cleanup function properly:
- Queries existing certificate mappings for the transaction (lines 668-676)
- Deletes specialized certificate records via type-specific deletion (lines 677-686)
- Removes the mapping rows (lines 688-692)
The deletion order is correct (specialized records before mapping rows), and all errors include appropriate context for debugging.
697-749: LGTM - Type-aware deletion covers all certificate types.The deletion helper properly maps each certificate type to its specialized table. The switch exhaustively covers all certificate types defined in
getCertType(lines 356-401), and the fallback case (lines 746-747) handles any unknown types by attempting deletion from the genericCertificatetable.This ensures no orphaned certificate records remain after cleanup.
database/plugin/metadata/store.go (2)
126-132: LGTM - Interface signature aligns with unified persistence.The
SetTransactionsignature update to includecertificateDeposits []uint64properly reflects the new unified certificate persistence model. The comment on line 130 clearly documents the parameter's purpose and that it's calculated by the ledger layer.
143-145: LGTM - Type unification improves consistency.The parameter type changes from
ledger.*tolcommon.*(ledger/common) improve consistency across the interface. This aligns with the broader refactoring to use common types from the gouroboros library.
763416f to
ba98f6e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
database/plugin/metadata/store.go (1)
29-147: Remove orphaned certificate method implementations from pool.go.The interface breaking change is verified—SetPoolRegistration and SetPoolRetirement (along with ~13 others) have been removed from the MetadataStore interface. The migration to SetTransaction is complete with no callers of the removed methods remaining in the codebase. However, two orphaned implementations remain:
- SetPoolRegistration (database/plugin/metadata/sqlite/pool.go:141)
- SetPoolRetirement (database/plugin/metadata/sqlite/pool.go:214)
These are dead code and should be removed as cleanup. The internal migration is successful, though external packages depending on these methods would be affected by the breaking API change.
🧹 Nitpick comments (1)
database/models/certificates.go (1)
19-27: Consider additional composite indexes for common query patterns.The current indexes cover individual columns well, but common certificate query patterns might benefit from composite indexes:
(BlockHash, CertType)- for filtering certificates by block and type during rollback/chain reorganization(Slot, CertType)- for range queries filtering by certificate type(TransactionID, CertType)- for fetching specific certificate types within a transactionExample composite index for rollback scenarios:
type Certificates struct { ID uint `gorm:"primaryKey"` TransactionID uint `gorm:"index;uniqueIndex:uniq_tx_cert;constraint:OnDelete:CASCADE"` CertificateID uint `gorm:"index"` // Polymorphic FK to certificate table based on CertType. Not DB-enforced. Slot uint64 `gorm:"index"` CertIndex uint `gorm:"column:cert_index;uniqueIndex:uniq_tx_cert"` - CertType uint - BlockHash []byte `gorm:"index"` + CertType uint `gorm:"index:idx_block_cert,priority:2"` + BlockHash []byte `gorm:"index;index:idx_block_cert,priority:1"` }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
.gitignore(1 hunks)database/account.go(0 hunks)database/certificates.go(1 hunks)database/drep.go(0 hunks)database/models/certificates.go(1 hunks)database/models/models.go(1 hunks)database/plugin/metadata/sqlite/account.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/store.go(2 hunks)database/pparams.go(2 hunks)database/transaction.go(2 hunks)internal/config/config_test.go(1 hunks)ledger/certs.go(1 hunks)ledger/delta.go(2 hunks)
💤 Files with no reviewable changes (3)
- database/plugin/metadata/sqlite/drep.go
- database/drep.go
- database/account.go
🚧 Files skipped from review as they are similar to previous changes (2)
- database/certificates.go
- .gitignore
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/delta.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/models/certificates.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/store.godatabase/plugin/metadata/sqlite/database.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/plugin/metadata/store.go
🧬 Code graph analysis (6)
database/models/models.go (1)
database/models/certificates.go (2)
Certificates(19-27)Certificates(30-32)
ledger/delta.go (1)
database/models/certificates.go (2)
Certificates(19-27)Certificates(30-32)
database/transaction.go (2)
database/database.go (1)
Database(45-50)database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)
database/plugin/metadata/sqlite/transaction.go (9)
database/plugin/metadata/sqlite/database.go (2)
MetadataStoreSqlite(38-44)New(47-118)database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/certificates.go (2)
Certificates(19-27)Certificates(30-32)database/models/account.go (20)
StakeRegistration(81-87)StakeRegistration(89-91)StakeDeregistration(70-75)StakeDeregistration(77-79)StakeDelegation(58-64)StakeDelegation(66-68)Registration(46-52)Registration(54-56)Deregistration(35-40)Deregistration(42-44)StakeRegistrationDelegation(93-100)StakeRegistrationDelegation(102-104)VoteDelegation(133-139)VoteDelegation(141-143)StakeVoteDelegation(106-113)StakeVoteDelegation(115-117)VoteRegistrationDelegation(145-152)VoteRegistrationDelegation(154-156)StakeVoteRegistrationDelegation(119-127)StakeVoteRegistrationDelegation(129-131)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/types/types.go (2)
Uint64(54-54)Rat(25-27)database/models/drep.go (7)
Drep(17-25)RegistrationDrep(43-51)RegistrationDrep(53-55)DeregistrationDrep(31-37)DeregistrationDrep(39-41)UpdateDrep(57-64)UpdateDrep(66-68)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(17-22)AuthCommitteeHot(24-26)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(17-23)ResignCommitteeCold(25-27)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(78-102)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/utxo.go (1)
Utxo(23-38)
⏰ 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). (4)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: go-test (1.24.x, ubuntu-latest)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (6)
database/models/certificates.go (2)
29-32: LGTM!Standard GORM TableName implementation correctly maps the struct to the "certificates" table.
22-22: No action required—cleanup strategy is fully implemented.The concern about orphaned CertificateID references is already addressed. The
cleanupExistingCertificates()function (called during transaction processing) explicitly:
- Deletes all specialized certificate records by type
- Deletes all Certificates mapping records for the transaction in the same atomic transaction
This handles both transaction-level deletes (via CASCADE on TransactionID) and individual certificate removal. The CertificateID is intentionally not DB-enforced because it's polymorphic—a deliberate design trade-off documented in the model's inline comment.
database/plugin/metadata/sqlite/account.go (2)
58-61: Conflict resolution correctly updates all fields including Active.The
UpdateAll: trueclause ensures that when a conflict occurs on thestaking_keyunique constraint, all fields (including the newActivefield) are updated rather than just a subset. This is the correct behavior for maintaining account state consistency.
45-57: Verified: Active field exists in Account model.The
models.Accountstruct atdatabase/models/account.go:28includesActive boolwith a default value oftrue. The SetAccount method correctly assigns the active parameter to this field.database/plugin/metadata/store.go (2)
143-145: Type updates align with ledger package refactoring.The changes from
ledger.Addresstolcommon.Address(line 143) andledger.TransactionInputtolcommon.TransactionInput(line 145) appear to be part of a broader ledger package restructuring to use common types from gouroboros.
126-132: Optional certificate deposits are handled correctly in implementations.The SQLite implementation in
database/plugin/metadata/sqlite/transaction.go(lines 300-307) safely handles optional or empty certificate deposits. Certificate processing only executes when both certificates exist and deposits are provided (if len(certs) > 0 && len(certificateDeposits) > 0), with a length validation check to prevent count mismatches. No panic or crash scenarios exist when the deposits slice is nil or empty.
ba98f6e to
bd81d79
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
ledger/state_test.go (1)
699-723: Clarify intent for unused helper function.This helper function is marked as unused but appears complete and functional. If it's intended for future test validation, consider adding a comment explaining its purpose. Otherwise, if it's not needed, it should be removed to avoid code clutter.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
.gitignore(1 hunks)database/account.go(0 hunks)database/certificate.go(1 hunks)database/drep.go(0 hunks)database/models/certificate.go(1 hunks)database/models/certs.go(0 hunks)database/plugin/metadata/sqlite/account.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/store.go(2 hunks)database/pparams.go(2 hunks)database/transaction.go(2 hunks)internal/config/config_test.go(1 hunks)ledger/certs.go(1 hunks)ledger/delta.go(2 hunks)ledger/state_test.go(1 hunks)
💤 Files with no reviewable changes (4)
- database/plugin/metadata/sqlite/drep.go
- database/models/certs.go
- database/account.go
- database/drep.go
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
- database/pparams.go
- database/transaction.go
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/database.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/delta.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/plugin/metadata/store.go
🧬 Code graph analysis (4)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(78-102)database/txn.go (1)
Txn(27-34)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/transaction.go (9)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(38-44)database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/certificate.go (2)
Certificate(23-31)Certificate(34-36)database/models/account.go (20)
StakeRegistration(81-87)StakeRegistration(89-91)StakeDeregistration(70-75)StakeDeregistration(77-79)StakeDelegation(58-64)StakeDelegation(66-68)Registration(46-52)Registration(54-56)Deregistration(35-40)Deregistration(42-44)StakeRegistrationDelegation(93-100)StakeRegistrationDelegation(102-104)VoteDelegation(133-139)VoteDelegation(141-143)StakeVoteDelegation(106-113)StakeVoteDelegation(115-117)VoteRegistrationDelegation(145-152)VoteRegistrationDelegation(154-156)StakeVoteRegistrationDelegation(119-127)StakeVoteRegistrationDelegation(129-131)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/types/types.go (2)
Uint64(54-54)Rat(25-27)database/models/drep.go (7)
Drep(17-25)RegistrationDrep(43-51)RegistrationDrep(53-55)DeregistrationDrep(31-37)DeregistrationDrep(39-41)UpdateDrep(57-64)UpdateDrep(66-68)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(17-22)AuthCommitteeHot(24-26)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(17-23)ResignCommitteeCold(25-27)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/utxo.go (1)
Utxo(23-38)
⏰ 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). (3)
- GitHub Check: nilaway
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: Analyze (go)
🔇 Additional comments (19)
internal/config/config_test.go (1)
1-13: LGTM: License header addition.The Apache 2.0 license header has been properly added to the test file with no functional changes.
database/certificate.go (1)
21-39: LGTM: Clean architectural shift to unified certificate persistence.The comments clearly document that certificate persistence is now centralized through
SetTransaction, while appropriately retaining the getter methods for data access. This simplifies the API surface and aligns with the PR's objective of unified certificate handling.database/plugin/metadata/sqlite/database.go (3)
171-179: LGTM: Improved error wrapping.The error wrapping in the
Closemethod provides clearer context when database handle retrieval fails, improving debugging.
166-212: LGTM: Clear documentation improvements.The updated method comments provide clear descriptions of each method's purpose, improving code maintainability.
54-61: Verify intentionality of in-memory transaction behavior change.The change from
SkipDefaultTransaction: truetofalse(line 59) is confirmed as deliberate in commit bd81d79, but the commit message does not explain the rationale. This creates an inconsistency: in-memory databases now wrap operations in automatic transactions while persistent databases skip them (line 89:true).Confirm whether:
- This asymmetry is intentional
- Tests rely on automatic transaction wrapping for in-memory databases
- The performance implications have been evaluated
ledger/delta.go (2)
51-69: LGTM: Proper invalid transaction handling.Invalid transactions correctly pass
nilfor the deposits parameter toSetTransaction, which is appropriate since certificate processing should be skipped for invalid transactions.
71-94: LGTM: Clean certificate deposit handling.The certificate deposit calculation is properly separated from transaction persistence, with deposits computed only when certificates exist and errors appropriately wrapped with context. The deposits are then passed to
SetTransactionfor unified persistence.ledger/certs.go (2)
26-65: LGTM: Well-designed deposit calculation API.The
CalculateCertificateDepositsfunction provides a clean separation between deposit calculation and persistence. Storing only non-zero deposits in the map is an efficient optimization, and error messages include certificate index for better debugging.
67-82: LGTM: Clear deprecation with migration guidance.The deprecation of
ProcessTransactionCertificatesincludes a clear error message directing users to the new API (CalculateCertificateDepositsandSetTransaction), which aids in migration.database/plugin/metadata/sqlite/transaction.go (8)
31-34: LGTM: Appropriate placeholder for legacy certificates.Using the maximum uint value for
LegacyCertificateIDis a good choice to avoid conflicts with actual database-generated IDs, which typically start from 1.
102-107: LGTM: Critical fix for ON CONFLICT path.The transaction ID reload after upsert correctly handles the case where
ON CONFLICTupdates an existing row, ensuringtmpTx.IDis populated for subsequent certificate operations. This addresses the critical issue raised in previous reviews.
305-342: LGTM: Clean certificate processing flow.The certificate processing properly:
- Early returns when no certificates exist
- Cleans up existing certificates to prevent duplicates
- Processes each certificate with its deposit (defaulting to 0 if not in map)
- Provides clear error context for each step
The approach of defaulting to 0 for missing deposits is reasonable since not all certificate types require deposits.
347-421: LGTM: Well-structured certificate storage setup.The
storeCertificatefunction has:
- Proper nil checks for parameters
- Clean helper closures for type mapping and certificate insertion
- Clear separation of concerns
The helper functions are well-designed for reuse across all certificate types.
487-529: LGTM: Complete pool registration data persistence.The pool registration handler now properly persists all certificate data including margin, metadata (URL/hash), owners, and relays. This addresses the data-loss regression concern from previous reviews.
593-687: LGTM: Complete vote delegation data persistence.All vote-related certificate handlers (
VoteDelegation,StakeVoteDelegation,VoteRegistrationDelegation,StakeVoteRegistrationDelegation) now properly persist the DRep credential in both the specialized tables and account records, addressing the data-loss concern from previous reviews.
698-750: LGTM: Complete governance certificate data persistence.DRep and committee resignation certificates properly persist anchor data (URL and hash) when present, addressing the governance metadata concern from previous reviews.
768-856: LGTM: Comprehensive certificate cleanup.The cleanup functions properly:
- Query existing certificates for the transaction
- Delete specialized table records by type
- Delete the certificate mapping entries
- Handle legacy types (no specialized record to delete)
- Provide fallback for unknown types
This prevents duplicate certificates on transaction retry/reprocessing.
database/plugin/metadata/sqlite/account.go (1)
45-56: LGTM! Active flag properly integrated.The
activeparameter is correctly threaded through SetAccount and assigned to the Account model. The upsert logic remains intact with proper conflict handling.database/plugin/metadata/store.go (1)
126-132: All SetTransaction implementations and callers are correctly updated.Verification confirms:
- Interface signature matches sqlite implementation exactly (lines 126-131 vs sqlite/transaction.go:58)
- Database wrapper properly reorders parameters when delegating (database/transaction.go:48)
- Both call sites in ledger/delta.go pass all 5 required parameters: transaction, point, index, certificate deposits map (nil or computed), and database connection
- sqlite implementation correctly consumes the sparse deposits map with null-safe lookup (lines 318-325)
No issues found.
278b22d to
c4054fd
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this 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)
database/plugin/metadata/sqlite/utxo.go (1)
107-119: Change "payment_addr" to "payment_key" on line 108.The Utxo model defines a
PaymentKeyfield which GORM maps to thepayment_keycolumn. The code incorrectly referencespayment_addr, which does not exist in the database schema. This will cause the query to fail at runtime.if addr.PaymentKeyHash() != lcommon.NewBlake2b224(nil) { addrQuery = addrQuery.Or("payment_key = ?", addr.PaymentKeyHash().Bytes()) }
🧹 Nitpick comments (2)
database/models/leios_eb.go (1)
18-27: Consider renamingElectionIdtoElectionIDfor Go convention.Go convention capitalizes all letters in acronyms (ID, URL, HTTP, etc.). The field is named
ElectionIdbut should beElectionIDto match the pattern used for theIDfield and follow idiomatic Go style.Apply this diff:
type LeiosEb struct { ID uint `gorm:"primaryKey"` - ElectionId []byte `gorm:"index"` + ElectionID []byte `gorm:"index"` EndorserBlockHash []byte PersistentVotersData []byte // JSON-encoded persistent voters NonpersistentVotersData []byte // JSON-encoded non-persistent voters AggregateEligSigData []byte // JSON-encoded aggregate eligibility signature AggregateVoteSigData []byte // JSON-encoded aggregate vote signature AddedSlot uint64 `gorm:"index"` }database/models/move_instantaneous_rewards.go (1)
17-24: Consider a more semantic type for the Source field.The
Sourcefield usesuintto represent a binary choice (0=reserves, 1=treasury). While this works, consider using a boolean or a custom enum type for better type safety and clarity.For example:
- Source uint `gorm:"index"` // 0=reserves, 1=treasury + Source bool `gorm:"index"` // false=reserves, true=treasuryOr define a custom type:
type MIRSource uint const ( MIRSourceReserves MIRSource = 0 MIRSourceTreasury MIRSource = 1 )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
.gitignore(1 hunks)database/account.go(0 hunks)database/certificate.go(1 hunks)database/drep.go(0 hunks)database/models/certificate.go(1 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/plugin/metadata/sqlite/account.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(4 hunks)database/plugin/metadata/store.go(2 hunks)database/pparams.go(2 hunks)database/transaction.go(2 hunks)internal/config/config_test.go(1 hunks)ledger/certs.go(1 hunks)ledger/delta.go(2 hunks)ledger/state_test.go(1 hunks)
💤 Files with no reviewable changes (3)
- database/plugin/metadata/sqlite/drep.go
- database/account.go
- database/drep.go
🚧 Files skipped from review as they are similar to previous changes (2)
- ledger/state_test.go
- database/plugin/metadata/sqlite/transaction.go
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/database.godatabase/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/delta.go
🔇 Additional comments (16)
database/pparams.go (1)
32-54: LGTM! Error variable separation improves clarity.The refactor to use
ppErrfor parameter retrieval errors anderrfor decode errors makes the error flow more explicit and easier to follow. Both error paths are properly handled.database/plugin/metadata/sqlite/database.go (2)
54-61: Verify theSkipDefaultTransaction: falsechange for in-memory database.The in-memory database now uses
SkipDefaultTransaction: false(line 59) while the disk database usesSkipDefaultTransaction: true(line 89). This creates an inconsistency between the two paths. The AI summary notes this as a behavior change fromtruetofalse.Enabling default transactions in-memory may impact performance. Please confirm this change is intentional and aligns with the desired behavior for in-memory databases.
166-212: LGTM! Documentation and error handling improvements.The enhanced method documentation and error wrapping in
Close()improve code maintainability and debugging. ThepromRegistryparameter addition properly supports metrics integration.database/models/genesis_key_delegation.go (1)
17-29: LGTM! Clean model definition.The
GenesisKeyDelegationmodel is well-structured with appropriate GORM tags and follows Go naming conventions. The indexedGenesisHashandAddedSlotfields support efficient queries.ledger/certs.go (2)
26-65: Excellent refactor! Clear separation of concerns.The new
CalculateCertificateDepositsfunction properly separates deposit calculation from persistence, addressing the previous review concern. Key strengths:
- Comprehensive documentation listing all certificate types with deposits
- Proper error wrapping with certificate index context for debugging
- Optimization to store only non-zero deposits in the map
- Clean, focused implementation using the era's
CertDepositFunc
67-82: LGTM! Clear deprecation with helpful guidance.The deprecation of
ProcessTransactionCertificatesprovides clear direction to useCalculateCertificateDepositsandSetTransactioninstead. The error message properly guides developers to the new API.database/models/models.go (1)
18-54: LGTM! Migration list properly expanded.The additions of
GenesisKeyDelegation,LeiosEb, andMoveInstantaneousRewardsproperly register the new certificate-type models for database migration. The existingCertificatemodel (line 23) remains to provide the unified mapping table mentioned in the PR objectives.database/certificate.go (1)
21-39: LGTM! Clean architectural refactoring.The updated comment clearly documents the shift to centralized certificate persistence via
SetTransaction, while appropriately retaining the getter methods for data retrieval. This aligns well with the broader PR changes to unify certificate handling.database/plugin/metadata/sqlite/utxo.go (1)
225-246: LGTM! Type migration completed correctly.The parameter type update from
ledger.TransactionInputtolcommon.TransactionInputaligns with the interface definition and the broader type migration across the PR.ledger/delta.go (2)
51-69: LGTM! Invalid transaction handling is correct.Invalid transactions correctly skip certificate processing and pass
nilfor deposits, which makes sense since they don't process certificates. The earlycontinueprevents further processing as expected.
71-93: LGTM! Certificate deposit handling is well-structured.The deposit calculation flow is clean:
- Deposits are calculated only when certificates are present
- Proper error handling with formatted error messages
nildeposits passed for transactions without certificates- Unified
SetTransactioncall handles both casesdatabase/models/certificate.go (1)
17-43: LGTM! Well-documented polymorphic certificate model.The model design is sound:
- Clear documentation of the polymorphic relationship and sentinel value convention
- Appropriate indexes for query performance (Slot, CertType, BlockHash, CertificateID)
- Unique constraint on (TransactionID, CertIndex) prevents duplicate entries
- CASCADE delete maintains referential integrity
The comment correctly describes the
LegacyCertificateIDconvention as^uint(0)(maximum uint value), consistent with the implementation.database/plugin/metadata/sqlite/account.go (1)
45-72: LGTM! Account activation tracking added correctly.The addition of the
activeparameter and its assignment toAccount.Active(Line 56) enables proper tracking of account registration state. The upsert logic withOnConflict{UpdateAll: true}correctly handles updates to existing accounts.database/plugin/metadata/store.go (2)
126-132: LGTM! Clear interface definition with helpful documentation.The updated
SetTransactionsignature is well-documented:
- Line 130's comment clearly explains the sparse certificate deposits map
- Parameter types are consistent with the broader type migration to
lcommon.*- The signature correctly aligns with the call site in
database/transaction.go
143-145: LGTM! Type migration to lcommon. completed correctly.*The parameter types for UTXO methods now use
lcommon.Addressandlcommon.TransactionInput, consistent with the broader type migration across the codebase and matching the implementations.database/transaction.go (1)
22-54: Parameter order is correct—no issues found.Verification confirms the call at lines 48-54 correctly matches the interface definition. The parameters are passed in the expected order:
point(ocommon.Point)tx(lcommon.Transaction)idx(uint32)certificateDeposits(map[int]uint64)txn.Metadata()(*gorm.DB)This order matches both the interface definition in
database/plugin/metadata/store.go(lines 126-132) and the implementation indatabase/plugin/metadata/sqlite/transaction.go(lines 58-64).
c4054fd to
4fa99d3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
utxorpc/submit.go (1)
80-86: Simplify the error logging.The nested
fmt.Sprint(fmt.Errorf(...))is redundant—fmt.Sprinton an error simply calls itsError()method. Prefer the original direct approach or usefmt.Errorfdirectly.Apply this diff to simplify:
- fmt.Sprint( - fmt.Errorf( - "failed to decode transaction from CBOR: %w", - err, - ), - ), + fmt.Sprintf( + "failed to decode transaction from CBOR: %v", + err, + ),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
.gitignore(1 hunks)database/account.go(0 hunks)database/certificate.go(1 hunks)database/drep.go(0 hunks)database/models/account_test.go(9 hunks)database/models/certificate.go(1 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/plugin/metadata/sqlite/account.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(0 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(4 hunks)database/plugin/metadata/store.go(2 hunks)database/pparams.go(2 hunks)database/transaction.go(2 hunks)internal/config/config_test.go(1 hunks)ledger/certs.go(1 hunks)ledger/delta.go(2 hunks)ledger/state_test.go(0 hunks)utxorpc/submit.go(1 hunks)
💤 Files with no reviewable changes (4)
- database/drep.go
- ledger/state_test.go
- database/account.go
- database/plugin/metadata/sqlite/drep.go
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/config/config_test.go
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/delta.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/models/account_test.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/models/account_test.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/sqlite/database.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/plugin/metadata/store.go
🧬 Code graph analysis (6)
database/models/models.go (3)
database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(18-24)GenesisKeyDelegation(27-29)database/models/leios_eb.go (2)
LeiosEb(18-27)LeiosEb(30-32)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(18-24)MoveInstantaneousRewards(27-29)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(78-102)database/txn.go (1)
Txn(27-34)
database/transaction.go (2)
database/database.go (1)
Database(45-50)database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/transaction.go (12)
database/plugin/metadata/sqlite/database.go (2)
MetadataStoreSqlite(38-44)New(47-118)database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/certificate.go (2)
Certificate(25-33)Certificate(36-38)database/models/account.go (20)
StakeRegistration(106-112)StakeRegistration(114-116)StakeDeregistration(95-100)StakeDeregistration(102-104)StakeDelegation(83-89)StakeDelegation(91-93)Registration(71-77)Registration(79-81)Deregistration(59-65)Deregistration(67-69)StakeRegistrationDelegation(118-125)StakeRegistrationDelegation(127-129)VoteDelegation(158-164)VoteDelegation(166-168)StakeVoteDelegation(131-138)StakeVoteDelegation(140-142)VoteRegistrationDelegation(170-177)VoteRegistrationDelegation(179-181)StakeVoteRegistrationDelegation(144-152)StakeVoteRegistrationDelegation(154-156)database/models/pool.go (10)
Pool(26-38)ErrPoolNotFound(24-24)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/types/types.go (2)
Uint64(54-54)Rat(25-27)database/models/drep.go (7)
Drep(17-25)RegistrationDrep(43-51)RegistrationDrep(53-55)DeregistrationDrep(31-37)DeregistrationDrep(39-41)UpdateDrep(57-64)UpdateDrep(66-68)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(17-22)AuthCommitteeHot(24-26)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(17-23)ResignCommitteeCold(25-27)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(18-24)GenesisKeyDelegation(27-29)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(18-24)MoveInstantaneousRewards(27-29)database/models/leios_eb.go (2)
LeiosEb(18-27)LeiosEb(30-32)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(18-30)Transaction(32-34)database/models/utxo.go (1)
Utxo(23-38)
⏰ 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). (3)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: Analyze (go)
- GitHub Check: nilaway
🔇 Additional comments (26)
.gitignore (1)
33-35: LGTM!Standard IDE ignore entry for VS Code settings.
database/pparams.go (1)
33-44: LGTM!Renaming to
ppErrimproves clarity and avoids variable shadowing with the outererr.database/models/account_test.go (2)
27-96: LGTM!Excellent test coverage expansion with edge cases (empty, nil, single-byte, all-zeros, all-ones staking keys). The use of
hexDecodehelper and multiline formatting improves readability.
155-159: LGTM!Good addition of explicit error handling for
ConvertBitsin the round-trip validation.database/models/move_instantaneous_rewards.go (1)
1-29: LGTM!The
MoveInstantaneousRewardsmodel follows the established pattern with appropriate indexes onSourceandAddedSlotfor efficient queries, andRewardDataas a JSON-encoded byte field for flexible reward storage.database/plugin/metadata/sqlite/utxo.go (1)
22-22: LGTM!The import alias change from
ledgertolcommonimproves consistency with other files in the PR.database/models/leios_eb.go (1)
1-32: LGTM!The
LeiosEbmodel is well-structured with appropriate indexes onElectionIdandAddedSlot. Using JSON-encoded byte fields for voters and signature data provides flexibility for complex structures.database/plugin/metadata/sqlite/database.go (4)
35-51: LGTM!The enhanced documentation and updated constructor signature with
loggerandpromRegistryparameters improve observability and align with the broader PR's instrumentation goals.
120-133: LGTM!The
init()method properly configures GORM tracing and schedules database vacuum operations. The discard logger fallback ensures safe operation even without an explicit logger.
166-211: LGTM!The enhanced public method documentation improves API clarity for consumers of the metadata store.
56-60: Confirm the intentionality and performance trade-off for in-memory transaction wrapping.The change from
SkipDefaultTransaction: truetofalsefor in-memory databases is confirmed. Setting SkipDefaultTransaction to false wraps each write in a transaction—providing safer atomicity but incurring ~30%+ write throughput overhead compared to the production configuration.This creates an asymmetry where tests enable automatic transaction wrapping (slower but more consistent) while production disables it (faster). Verify this trade-off is intentional and document the rationale in code comments, then run your test suite to confirm the performance impact is acceptable for your test scenarios.
ledger/delta.go (1)
72-89: Appreciate the inline deposit wiring.Calculating deposits only when certificates exist and threading them through
SetTransactionkeeps the hot path lean while giving metadata everything it needs. Nicely tightened.database/models/genesis_key_delegation.go (1)
17-29: LGTM!The GenesisKeyDelegation model is well-structured with appropriate GORM tags for indexing and follows the repository's model conventions.
ledger/certs.go (2)
26-65: LGTM!The
CalculateCertificateDepositsfunction correctly computes deposits using protocol parameters and provides detailed error context with certificate indices. The optimization to store only non-zero deposits in the map is efficient.
67-82: Deprecation handled appropriately.The clear deprecation notice and error message guide users to the new workflow using
CalculateCertificateDepositsandSetTransaction.database/transaction.go (1)
22-55: LGTM!The
certificateDepositsparameter is properly threaded from the database layer to the metadata layer, aligning with the new unified certificate persistence workflow.database/plugin/metadata/sqlite/transaction.go (8)
31-34: LGTM!Using the maximum uint value as a sentinel for legacy certificate types is a reasonable approach that avoids conflicts with actual database IDs.
57-64: LGTM!The updated
SetTransactionsignature correctly accepts thecertificateDepositsparameter and matches the interface definition indatabase/plugin/metadata/store.go.
102-107: LGTM!The transaction ID reload after upsert correctly addresses the ON CONFLICT scenario, ensuring that
tmpTx.IDis populated before certificate persistence operations. This fixes the critical issue identified in previous reviews.
305-323: LGTM!The certificate validation correctly requires
certificateDepositsto be non-nil when certificates are present. The sparse map design (where only certificates with non-zero deposits have entries) aligns withCalculateCertificateDepositsbehavior, so a length check is appropriately omitted.
325-347: LGTM!The certificate persistence loop correctly handles the sparse deposit map by defaulting to zero and looking up each certificate's deposit. Good error context with certificate indices aids debugging.
352-899: LGTM!The
storeCertificatefunction comprehensively handles all certificate types with consistent patterns:
- Proper parameter validation
- Account/pool/DRep state updates where appropriate
- JSON serialization for complex data structures
- Consistent error handling with context
- Creation of mapping entries via
insertCert
901-934: LGTM!The
cleanupExistingCertificatesfunction correctly removes both specialized certificate records and mapping entries, preventing duplicates during transaction re-processing.
936-990: LGTM!The
deleteCertificateRecordfunction correctly routes deletions to specialized tables based on certificate type, with a graceful fallback for unknown types.database/plugin/metadata/store.go (2)
126-132: LGTM!The
SetTransactionsignature is correctly updated with thecertificateDepositsparameter, matching the implementation. The inline comment clearly documents the sparse map design (only certificates requiring deposits).
143-145: LGTM!The parameter types correctly use
lcommon.Addressandlcommon.TransactionInput, aligning with the canonical ledger/common types as per repository conventions. Based on learnings.
4fa99d3 to
ac27b63
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
ac27b63 to
6bfc74e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
database/plugin/metadata/sqlite/certs.go (2)
253-263: Add missing.Bytes()call forPoolKeyHashassignment.Line 256 assigns
c.PoolKeyHashdirectly without calling.Bytes(), while other delegation certificates (lines 136, 303) correctly usec.PoolKeyHash.Bytes(). The model expects[]byte, so this will fail to compile.Apply this diff:
model := &models.StakeRegistrationDelegation{ StakingKey: c.StakeCredential.Hash().Bytes(), - PoolKeyHash: c.PoolKeyHash, + PoolKeyHash: c.PoolKeyHash.Bytes(), DepositAmount: types.Uint64{Val: deposit}, AddedSlot: point.Slot, }
276-286: Add missing.Bytes()call forPoolKeyHashassignment.Line 279 assigns
c.PoolKeyHashdirectly without calling.Bytes(), inconsistent with lines 136 and 303. The model expects[]byte, so this will fail to compile.Apply this diff:
model := &models.StakeVoteDelegation{ StakingKey: c.StakeCredential.Hash().Bytes(), - PoolKeyHash: c.PoolKeyHash, + PoolKeyHash: c.PoolKeyHash.Bytes(), Drep: c.Drep.Credential, AddedSlot: point.Slot, }
🧹 Nitpick comments (5)
database/plugin/metadata/sqlite/utxo.go (1)
96-137: Migration tolcommontypes and address filtering looks consistentUsing
lcommon.Address/TransactionInputkeeps this API aligned with the rest of the lcommon migration, and the payment/staking key filters now targetpayment_key/staking_keycolumns that matchmodels.Utxo. The zero-hash guard avoids useless queries for addresses without key hashes, and the OR combination correctly handles addresses with both payment and stake parts. If you want to simplify, you could start fromaddrQuery(e.g.,addrQuery = addrQuery.Where("deleted_slot = 0")) instead of passingaddrQueryback intotxn.Where(...), but that’s purely cosmetic.Also applies to: 230-252
database/models/leios_eb.go (1)
21-37: Confirm whether LeiosEb uniqueness should be composite or per-columnBoth
ElectionIDandEndorserBlockHashuse the sameuniqueIndex:uniq_leios_ebtag, which in GORM creates a composite unique index(ElectionID, EndorserBlockHash). If the domain expects eitherElectionIDorEndorserBlockHashto be globally unique on its own, you’ll want separate unique indexes (e.g., omit the shared name or use two different names). If composite uniqueness is what you intended, this is fine as-is—just worth double‑checking.database/plugin/metadata/sqlite/transaction.go (1)
348-405: Certificate deposit validation is correct but consider a minor readability tweakThe logic that:
- defaults
depositto 0,- enforces presence of a
deposits[idx]entry only for the specificrequiresDepositcertificate types,matches the intended behavior after
CalculateCertificateDepositsstarted omitting zero‑deposit entries. The behavior is sound.You might consider renaming the loop index (e.g.,
certIdx) to avoid shadowing the outeridxparameter and make logs easier to reason about, but this is purely cosmetic.database/plugin/metadata/sqlite/drep.go (1)
17-77: DRep lookup and upsert behavior look correct
GetDrep’s pointer return with explicit not‑found handling andSetDrep’sON CONFLICT (drep_credential)upsert of slot/anchor/active provide a clear, idempotent DRep persistence path. If you want more diagnosability later, you could wrap theresult.ErrorfromCreatewith context (e.g.,"upsert drep"), but the current behavior is functionally sound.database/plugin/metadata/store.go (1)
126-132: Clarify the contract for thedepositsmap inSetTransaction.The new
depositsparameter and its inline comment are helpful, but the interface is now the canonical contract for all plugins. Consider tightening the documentation (either here or in a method-level comment) to spell out expectations such as:
- Whether
depositsmust be non‑nil whenTransaction.Certificates()is non‑empty- How missing keys vs. explicit
0values are interpreted- That indices must align exactly with the transaction’s certificate slice order
This will keep alternative MetadataStore implementations from subtly diverging from the current SQLite behavior.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (38)
database/account.go(0 hunks)database/certificate.go(1 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(6 hunks)database/plugin/metadata/sqlite/drep.go(3 hunks)database/plugin/metadata/sqlite/plugin.go(0 hunks)database/plugin/metadata/sqlite/pool.go(1 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(4 hunks)database/plugin/metadata/store.go(2 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)go.mod(3 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(3 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (3)
- database/account.go
- database/drep.go
- database/plugin/metadata/sqlite/plugin.go
✅ Files skipped from review due to trivial changes (1)
- database/models/certificate_test.go
🚧 Files skipped from review as they are similar to previous changes (13)
- database/models/asset.go
- internal/config/config_test.go
- database/models/move_instantaneous_rewards.go
- ledger/view.go
- database/models/models.go
- database/types/types_test.go
- database/models/auth_committee_hot.go
- go.mod
- ledger/state.go
- database/plugin/metadata/sqlite/certs_test.go
- database/models/account_test.go
- database/models/resign_committee_cold.go
- ledger/delta.go
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/models/transaction.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/models/transaction.godatabase/plugin/metadata/sqlite/transaction.godatabase/transaction.godatabase/models/genesis_key_delegation.godatabase/models/utxo.godatabase/plugin/metadata/sqlite/database.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/transaction.godatabase/models/utxo.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/sqlite/transaction.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/plugin/metadata/store.go
🔇 Additional comments (16)
internal/node/load.go (1)
173-199: LGTM! Well-structured cancellable wait pattern.The timeout and ticker-based polling approach ensures the ledger catches up before proceeding, with proper context cancellation and resource cleanup.
ledger/certs.go (1)
37-60: LGTM! Deposit calculation properly centralized.The function correctly computes deposits for all certificate types using the current era's parameters, includes zero-value deposits (as required), and provides helpful error context with certificate indices.
database/models/transaction.go (1)
26-29: LGTM! Associations correctly distinguish outputs from collateral return.The
where:clauses properly use snake_case column names and cleanly separate regular outputs from the collateral return output.database/models/genesis_key_delegation.go (1)
21-34: LGTM! Model properly enforces uniqueness for idempotent operations.The composite unique index on
GenesisHash,GenesisDelegateHash, andVrfKeyHashensures idempotency for genesis key delegations, which aligns with the OnConflict-based persistence logic.database/types/types.go (1)
59-86: LGTM! Uint64 struct correctly implements database serialization.The change from type alias to struct with
Valfield is consistent throughout the PR. TheValue()andScan()methods properly handle the struct field, and the type switch correctly handles bothstringand[]byteinputs.database/plugin/metadata/sqlite/pool.go (1)
89-90: LGTM! Correctly uses.Valaccessor for Uint64 struct.Consistent with the type change from
types.Uint64alias to struct withValfield.database/models/drep.go (1)
21-72: LGTM! Consistent field renaming across DRep models.The
CredentialtoDrepCredentialrename is applied uniformly, and index types are appropriate:uniqueIndexon the mainDreptable, andindexon the historical certificate tables.database/certificate.go (1)
21-39: Certificate persistence docs and getters align with the unified flowThe new comments make it clear that certificates are persisted via
SetTransaction, and the getters correctly delegate to the metadata layer while keeping existing signatures. This matches the unified certificate persistence design without introducing behavioral changes.database/models/pool.go (1)
44-60: Uniqueness constraints for pool registrations/retirements look well chosenMaking
PoolRegistration.CertificateIDuniquely indexed and using a composite unique index on(PoolKeyHash, Epoch)forPoolRetirementgives you idempotency per certificate and prevents duplicate retirements per pool/epoch while still allowing re-registrations. This matches the intended tracking semantics without reintroducing the earlier “one registration per pool” limitation.Also applies to: 91-98
database/models/utxo.go (1)
23-40: Utxo mapping totypes.Uint64is consistent with the UTXO/transaction modelWrapping
AddedSlotandAmountastypes.Uint64{Val: ...}matches the custom Uint64 type’s DB integration, and addingIsCollateralReturnas an indexed flag gives you a clean way to distinguish collateral-return outputs without affecting existing queries. TheTransactionID/SpentAtTxIdfields remain intact for the dual foreign‑key pattern inmodels.Transaction, so UTXO provenance should stay consistent. Based on learnings.Also applies to: 50-79
database/plugin/metadata/sqlite/database.go (1)
36-48: Vacuum lifecycle and Close() coordination look robust nowThe addition of
vacuumWG, thedataDir == "" || closedguard inrunVacuum, and the updatedClose()(markingclosed, stopping the timer, then waiting for in‑flight vacuums before closing the DB) collectively fix the prior “VACUUM on closed DB”/goroutine‑leak pattern. The in‑memory path and the lightweight wrapper methods with doc comments also make the store behavior clearer and safer for tests.Also applies to: 50-124, 126-184, 186-189, 191-210, 212-243
database/plugin/metadata/sqlite/transaction.go (1)
52-145: SetTransaction idempotency and deposits handling look consistentThe reworked
SetTransactionflow (hash-basedalreadyExistsguard,blockChangedhandling, and gating of UTXO vs certificate processing) plus thedepositsplumbing and required‑deposit validation form a coherent, idempotent path for new vs replayed transactions. I don’t see correctness issues in this control flow based on the current design.database/models/certificate.go (1)
25-164: CertificateType and unified Certificate model are well‑structuredThe
CertificateTypeenum, itsValue/Scanmethods, and theCertificateschema (including the(transaction_id, cert_index)unique index andCertTypefield) align cleanly with the unified certificate mapping design and should work correctly with GORM/SQLite. The conservative range check inScanis reasonable given the small, fixed certificate type set.database/models/account.go (1)
27-188: Account and certificate model indexing strategy matches the unified certificate designSwitching
Account.StakingKeyfromuniqueIndextoindex, addingAddedSlot/Activeindexes, and using composite uniques on(StakingKey, CertificateID)forDeregistration,StakeDeregistration, andStakeRegistrationcorrectly support:
- multiple historical certificates per stake key, and
- de‑duplication at the
(staking_key, certificate)level.The accompanying comment block accurately documents this constraint strategy, and it lines up with the new GetAccount/SetAccount behavior in the sqlite store.
database/plugin/metadata/sqlite/account.go (1)
24-69: Account retrieval and persistence align with the new Account schema
GetAccount’sWHERE staking_key = ? AND active = trueplusORDER BY added_slot DESCcorrectly targets the latest active account record per stake key, consistent with the indexed fields inmodels.Account.SetAccount’s creation of a newAccountrow withActiveandAddedSlotset by the caller matches that model and the broader move toward certificate‑driven account state.database/plugin/metadata/store.go (1)
143-145: UTXO methods now usinglcommontypes look correct and consistent.Switching
GetUtxosByAddresstolcommon.AddressandSetUtxoDeletedAtSlottolcommon.TransactionInputaligns the interface with the canonicalledger/commontypes, matching the established pattern elsewhere in the codebase. No further changes needed here.
952e932 to
b327165
Compare
There was a problem hiding this 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)
database/plugin/metadata/store.go (1)
29-147: Type mismatch at call sites: database/utxo.go uses incompatible ledger. types*The MetadataStore interface and sqlite implementations correctly use
lcommon.Addressandlcommon.TransactionInput, but database/utxo.go passes incompatible types:
- Line 111:
d.metadata.SetUtxoDeletedAtSlot(utxoId, ...)whereutxoId: ledger.TransactionInput→ expectslcommon.TransactionInput- Line 124:
d.metadata.GetUtxosByAddress(addr, ...)whereaddr: ledger.Address→ expectslcommon.Addressdatabase/utxo.go only imports
github.com/blinklabs-io/gouroboros/ledgerbut needslcommon "github.com/blinklabs-io/gouroboros/ledger/common". Either convert the parameters or update function signatures to uselcommon.*types consistently (as done in database/transaction.go, database/certificate.go, etc.).
♻️ Duplicate comments (2)
database/plugin/metadata/sqlite/certs.go (1)
63-498: Per-typeCertificateIDfields are still unset for many certificate models, breaking uniqueness and cleanup semantics
storeCertificatewiresunifiedCert.CertificateID = specificModelID, but only some per-type models set their ownCertificateIDtounifiedCert.ID(e.g.,StakeRegistration,StakeDeregistration,Deregistration,PoolRegistration). Many others with aCertificateIDcolumn never initialize it:
- Account-related:
StakeDelegation,Registration,StakeRegistrationDelegation,VoteDelegation,StakeVoteDelegation,VoteRegistrationDelegation,StakeVoteRegistrationDelegation.- Pool:
PoolRetirement.- DRep:
RegistrationDrep,DeregistrationDrep,UpdateDrep.- Committee/genesis:
AuthCommitteeHot,ResignCommitteeCold,GenesisKeyDelegation.- MIR/Leios:
MoveInstantaneousRewards,LeiosEb.Given the model definitions:
- Several tables have
CertificateIDwith auniqueIndex:*_certtag (e.g.,PoolRegistration,PoolRetirement,GenesisKeyDelegation,AuthCommitteeHot,MoveInstantaneousRewards,LeiosEb,ResignCommitteeCold, various DRep models).CertificateCleanupValidationincerts_test.goexplicitly simulates cleanup by deleting specialized rows viaWHERE certificate_id IN (?), implying cleanup relies on these fields.Leaving
CertificateIDat the zero value for these models leads to two concrete problems:
- Uniqueness collisions: any table where
CertificateIDparticipates in a single-column unique index will accept the first insert (certificate_id = 0) and then reject subsequent inserts, even when they represent distinct certificates.- Orphaned specialized rows on cleanup: bulk removal by
certificate_idwill not match rows whereCertificateIDwas never set, so specialized certificate data can be left behind when the unifiedcertificatesrow is deleted.The final comment in the function:
// The specific model already has the correct CertificateID set, so no need to update itis currently untrue for these types.
I recommend:
- For every per-type model that defines a
CertificateIDfield, set it tounifiedCert.IDwhen creating the model:model := &models.MoveInstantaneousRewards{ RewardData: rewardData, Source: c.Reward.Source, OtherPot: c.Reward.OtherPot, AddedSlot: types.Uint64{Val: point.Slot}, CertificateID: unifiedCert.ID, }(similarly for
LeiosEb, DRep types,AuthCommitteeHot,ResignCommitteeCold,GenesisKeyDelegation,PoolRetirement, delegation variants, etc.)
- Where you use
FirstOrCreate, keep the conflict key on the natural uniqueness (e.g.,(GenesisHash, VrfKeyHash)or(ColdCredential, HostCredential)), but also ensureCertificateIDis set on the resulting row; if needed, follow the pattern used in the stake registration case and apply an explicitUpdateswith the newCertificateIDafter creation.This will make uniqueness constraints behave as intended and ensure bulk certificate cleanup can reliably remove specialized rows by certificate ID.
#!/bin/bash # Verify all models with a CertificateID field and check whether storeCertificate sets it echo "=== Models with CertificateID field ===" rg -n 'CertificateID\s+uint' database/models -n -C2 echo -e "\n=== storeCertificate assignments to CertificateID ===" rg -n 'CertificateID:' database/plugin/metadata/sqlite/certs.go -n -C3 echo -e "\n=== Check bulk removal implementation uses certificate_id ===" rg -n 'bulkRemoveCertificateRecords' -n rg -n 'certificate_id IN' database/plugin/metadata/sqlite -n -C3database/plugin/metadata/sqlite/certs_test.go (1)
37-89: Invalid loop syntax:for i := range 3does not compileSeveral tests still use
for i := range 3:
TestPoolCertificateDuplicatePrevention(pool registration and retirement loops).TestStakeCertificateDuplicatePrevention(stake registration and deregistration loops).TestDrepCertificateReRegistration(registration, deregistration, and update loops).TestGenesisKeyDelegationCertificateIdempotent(outer loop).In Go,
rangecannot be used on an integer literal; these loops will not compile.Replace each with a counted loop, e.g.:
- // Insert pool registration record multiple times - for i := range 3 { + // Insert pool registration record multiple times + for i := 0; i < 3; i++ { regCert := models.PoolRegistration{ // ... } err := metadata.DB().Create(®Cert).Error // ... }Apply the same pattern to all other
for i := range 3usages in this file.Also applies to: 119-135, 164-209, 229-242, 259-267, 284-291, 329-368
🧹 Nitpick comments (1)
database/plugin/metadata/sqlite/transaction.go (1)
52-122: SetTransaction: idempotent UTXO writes, invalid-tx short-circuit, and deposit validation are well-structured; double‑check re-inclusion block metadataThis implementation hits the important behaviors:
- Uses a hash-based
alreadyExistsflag so that:
- First processing writes transaction, UTXO associations, and certificates.
- Replays skip UTXO work and (via the
alreadyExistschecks) also skip certificate re-processing, making the path idempotent.- For invalid transactions,
processCertificatesreturns immediately, so no certificate or deposit state is persisted, matching ledger semantics.- For new valid transactions with certificates,
depositsis required and per-cert deposits are enforced only for the expected registration types, while still preserving legitimate zero-deposit entries.One thing worth verifying: on re-inclusion with
blockChanged == trueyou updatetransaction.block_hash/block_indexand all certificate slots/added_slot, but thecertificates.BlockHashfield is left unchanged here. If any consumers rely oncertificates.block_hashto reflect the current canonical block (rather than the first inclusion), you may want to update that column alongsideslotin the re-inclusion path to avoid inconsistent block metadata betweentransactionandcertificates.Also applies to: 133-145, 343-422
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (38)
database/account.go(0 hunks)database/certificate.go(1 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(6 hunks)database/plugin/metadata/sqlite/drep.go(3 hunks)database/plugin/metadata/sqlite/pool.go(1 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(4 hunks)database/plugin/metadata/store.go(2 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)go.mod(3 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(3 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (3)
- database/account.go
- database/models/certs.go
- database/drep.go
✅ Files skipped from review due to trivial changes (1)
- database/models/account_test.go
🚧 Files skipped from review as they are similar to previous changes (10)
- database/models/asset.go
- database/models/certificate.go
- database/models/genesis_key_delegation.go
- database/models/models.go
- database/models/leios_eb.go
- database/types/types_test.go
- go.mod
- internal/config/config_test.go
- database/models/transaction.go
- database/types/types.go
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/sqlite/database.godatabase/plugin/metadata/sqlite/utxo.godatabase/transaction.godatabase/models/utxo.goledger/delta.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/sqlite/certs_test.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
ledger/state.godatabase/plugin/metadata/sqlite/certs_test.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
ledger/state.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
ledger/state.godatabase/plugin/metadata/sqlite/certs_test.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
ledger/state.godatabase/plugin/metadata/sqlite/certs_test.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/utxo.go
🧬 Code graph analysis (16)
database/models/move_instantaneous_rewards.go (1)
database/types/types.go (1)
Uint64(59-61)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(79-103)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(59-61)
database/models/certificate_test.go (1)
database/models/certificate.go (3)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeStakeDeregistration(35-37)
database/plugin/metadata/sqlite/transaction.go (4)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(59-61)database/models/utxo.go (1)
UtxoLedgerToModel(50-79)database/models/certificate.go (10)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeRegistrationDrep(74-76)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(146-154)Certificate(157-159)
ledger/state.go (1)
database/plugin/log.go (1)
Logger(18-27)
database/plugin/metadata/sqlite/drep.go (1)
database/models/drep.go (1)
Drep(21-29)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(268-276)
database/plugin/metadata/sqlite/certs_test.go (11)
database/plugin/metadata/sqlite/database.go (1)
New(51-124)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(59-61)database/models/account.go (7)
StakeRegistration(115-121)StakeRegistration(123-125)Deregistration(68-74)Deregistration(76-78)StakeDeregistration(104-109)StakeDeregistration(111-113)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(59-61)
database/models/account.go (2)
database/models/pool.go (1)
Pool(26-38)database/models/drep.go (1)
Drep(21-29)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/plugin/metadata/sqlite/certs.go (10)
database/models/certificate.go (3)
Certificate(146-154)Certificate(157-159)CertificateType(28-28)database/models/account.go (20)
StakeRegistration(115-121)StakeRegistration(123-125)StakeDeregistration(104-109)StakeDeregistration(111-113)StakeDelegation(92-98)StakeDelegation(100-102)Registration(80-86)Registration(88-90)Deregistration(68-74)Deregistration(76-78)StakeRegistrationDelegation(127-134)StakeRegistrationDelegation(136-138)VoteDelegation(167-173)VoteDelegation(175-177)StakeVoteDelegation(140-147)StakeVoteDelegation(149-151)VoteRegistrationDelegation(179-186)VoteRegistrationDelegation(188-190)StakeVoteRegistrationDelegation(153-161)StakeVoteRegistrationDelegation(163-165)database/types/types.go (2)
Uint64(59-61)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
database/models/auth_committee_hot.go (1)
database/types/types.go (1)
Uint64(59-61)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
⏰ 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). (3)
- GitHub Check: Analyze (go)
- GitHub Check: nilaway
- GitHub Check: docker (ubuntu-latest, amd64)
🔇 Additional comments (22)
internal/node/load.go (1)
173-199: LGTM: Robust wait mechanism with proper timeout and cleanup.The cancellable context with 5-minute timeout and periodic polling ensures the ledger catches up before proceeding. Proper cleanup with deferred cancel and ticker.Stop() prevents resource leaks.
database/models/certificate_test.go (1)
25-166: Excellent test coverage for CertificateType database marshaling.The tests comprehensively verify Value() and Scan() behavior with multiple input types, edge cases (zero, large values), error cases (invalid types, negative, overflow), and interface compliance. This ensures robust GORM integration.
database/models/pool.go (1)
44-98: Constraint design correctly supports pool lifecycle operations.The index strategy properly allows:
- Multiple registrations per pool (PoolKeyHash is indexed, not unique at line 48)
- One retirement per pool per epoch (composite unique at lines 92, 96)
- Certificate-level idempotency via CertificateID unique constraints (lines 55, 93)
This resolves the past review concern about preventing legitimate re-registrations.
database/models/utxo.go (1)
50-79: Proper migration to types.Uint64 struct with correct value wrapping.The UtxoLedgerToModel function correctly wraps slot and amount values in types.Uint64{Val: ...} structs (lines 58-59). The IsCollateralReturn field addition supports collateral tracking per the Babbage-era UTXO model.
database/models/account.go (1)
60-121: Well-designed constraint strategy balancing idempotency and historical tracking.The composite unique constraints on (StakingKey, CertificateID) for Deregistration, StakeDeregistration, and StakeRegistration (lines 69-71, 105-106, 116-117) properly prevent duplicate certificate processing while allowing legitimate re-registration cycles. Other certificate types use simple indexes for historical tracking.
The inline documentation (lines 60-67) clearly explains the design rationale. This addresses previous review concerns about preventing legitimate stake key lifecycle operations.
ledger/view.go (1)
111-112: Correct adaptation to types.Uint64 struct.Accessing pool.Pledge.Val and pool.Cost.Val properly reflects the migration from type alias to struct with Val field.
database/plugin/metadata/sqlite/pool.go (1)
89-90: Correct .Val field access for types.Uint64 migration.The changes properly adapt to the types.Uint64 struct by accessing cert.Pledge.Val and cert.Cost.Val.
database/models/move_instantaneous_rewards.go (1)
21-34: Correct constraint design allows multiple MIR certificates per slot.The uniqueness constraint on CertificateID alone (line 25) properly prevents duplicate certificate records while allowing multiple legitimate MIR certificates in the same slot from the same pot. Source and AddedSlot use simple indexes (lines 26, 28) for query performance without blocking valid scenarios.
This addresses the past review concern about over-strict composite uniqueness.
database/models/drep.go (1)
17-29: DRep credential/indexing layout now matches expected registration lifecycleUsing
uniqueIndexonDrep.DrepCredentialand plainindexon the registration/deregistration/update tables gives you a single canonical DRep row while still allowing multiple cert records over time, which avoids the earlier re-registration constraint issue. The use oftypes.Uint64for deposit amounts is consistent with the new typed-Uint64 handling.Also applies to: 35-55, 61-68
database/models/auth_committee_hot.go (1)
17-27: AuthCommitteeHot uniqueness and certificate linkage look consistent
ColdCredentialasuniqueplus a dedicated unique index onCertificateIDprovides clear 1:1 constraints for both the credential and backing certificate, and thetypes.Uint64AddedSlotaligns with the new typed-Uint64 pattern used elsewhere.ledger/state.go (1)
24-24: Certificate deposits and validation behavior are wired correctly into block processingPrecomputing
depositsonly for cert-bearing transactions and then callingdelta.addTransactionWithDeposits(tx, i, deposits)integrates the new deposits path cleanly. The updated validation logic now only swallows"value not conserved"errors for transactions with certificates and still aborts on all other validation failures, restoring strict validation while keeping the temporary certificate-deposit workaround.Also applies to: 845-895
database/plugin/metadata/sqlite/utxo.go (1)
21-24: UTxO address and input handling now correctly use ledger/common types and key hashes
GetUtxosByAddressnow derives payment/stake key hashes fromlcommon.Addressand only buildspayment_key/staking_keypredicates when the corresponding hash is non-zero, which aligns with the UTXO schema and avoids spurious queries. UpdatingSetUtxoDeletedAtSlotto acceptlcommon.TransactionInputkeeps the metadata store consistent with the rest of the ledger/common plumbing.Also applies to: 96-137, 230-252
database/certificate.go (1)
21-24: Certificate persistence docs and accessors align with the unified SetTransaction flowThe new comments make the “certs are persisted via SetTransaction” contract explicit, and the
GetPoolRegistrations/GetStakeRegistrationshelpers correctly delegate to the metadata store using the caller’s transaction context.Also applies to: 25-31, 33-39
database/models/resign_committee_cold.go (1)
17-30: ResignCommitteeCold uniqueness and slot typing look appropriateThe unique constraint on
ColdCredentialplus a dedicated unique index onCertificateIDmatches the expected “one resignation per cold credential and certificate” semantics, and switchingAddedSlottotypes.Uint64keeps it consistent with the new typed-Uint64 representation used in other committee models.ledger/certs.go (1)
23-59: Certificate deposit calculation and zero-value preservation look correctThe helper cleanly centralizes deposit computation via
CertDepositFunc, records an entry for every certificate index (including zero deposits), and returns precise error context per index. No issues from the ledger side.database/transaction.go (1)
22-63: Transactional wrapper and deposits plumb-through look soundThe named-error + deferred commit/rollback pattern correctly finalizes only internally-created transactions, and any error from blob writes or metadata persistence prevents commit. Deposits are cleanly threaded into the metadata path without changing UTXO semantics.
ledger/delta.go (1)
25-47: Deposit propagation and invalid-transaction handling in LedgerDelta.apply are consistentThe updated delta flow correctly:
- Skips deposit calculation for invalid transactions while still recording UTXO consumption.
- Reuses precomputed deposits when available and otherwise derives them via
CalculateCertificateDepositsonly for certificate-bearing, valid transactions.- Passes
nildeposits only when either the transaction is invalid or has no certificates, which aligns with the metadata layer’s checks.This keeps deposit semantics and idempotency intact end-to-end.
Also applies to: 61-63, 76-101
database/plugin/metadata/sqlite/transaction.go (1)
427-475: Certificate slot re-inclusion updates now cover the stake-related tables
updateCertificateSlotscorrectly:
- Updates
certificates.slotfor all rows tied to the transaction.- Propagates the new slot into
added_slotfor a comprehensive set of specialized certificate tables, including the previously-missing stake registration/deregistration/registration-delegation tables.This keeps timing information consistent across the unified mapping and specialized tables on re-inclusion.
database/plugin/metadata/sqlite/database.go (1)
36-48: Vacuum lifecycle and in-memory initialization look robustThe added
vacuumWG+closedchecks ensure that:
- VACUUM isn’t run for in-memory stores or after
Closeis initiated.- Any in-flight VACUUM completes before the DB handle is closed, preventing goroutine leaks and “VACUUM on closed DB” errors.
The in-memory path in
Newand the clarified Close/AutoMigrate wrappers are consistent with the rest of the metadata store.Also applies to: 50-124, 141-156, 191-210
database/plugin/metadata/sqlite/drep.go (1)
17-79: GetDrep/SetDrep DRep upsert flow looks correctThe updated
GetDrepandSetDrepimplementations correctly:
- Query by
drep_credential(matchingmodels.Drep.DrepCredential).- Return
(nil, nil)onErrRecordNotFound.- Use an
ON CONFLICT (drep_credential)upsert to maintain the latestadded_slot,anchor_url,anchor_hash, andactivefields.No issues from a correctness standpoint.
database/plugin/metadata/sqlite/account.go (1)
24-69: SetAccount is dead code and never called—the raised concern does not apply to any active code pathVerification shows:
SetAccountis defined but never invoked anywhere in the codebase (production or otherwise)- Account records are never created in any real execution flow
- Deactivation in tests uses direct
Save()on retrieved records, bypassingSetAccountentirelyGetAccountis used, but its paired write function does not exist in production codeThe concern about inserting deactivation records with
active=falseand causing stale reads is theoretically sound, but the code path does not exist—SetAccountis unreachable.Either remove the unused
GetAccount/SetAccountmethods or clarify if they are intended infrastructure for a feature not yet implemented.Likely an incorrect or invalid review comment.
database/plugin/metadata/sqlite/certs_test.go (1)
392-462: Test coverage for certificate persistence and idempotency is strongThe test suite successfully validates:
- Uniqueness and "re-registration allowed" behavior for pools, stake keys, DReps, and genesis key delegation.
- Correct population and uniqueness of
AuthCommitteeHot(includingCertificateID).- Cleanup behavior matching
bulkRemoveCertificateRecordsby deleting per-type rows viacertificate_id.- Validation of
types.Uint64round-trips in multiple models.- End-to-end behavior of
SetTransactionunder idempotent and re-inclusion scenarios, including deposit requirements and slot updates.- Data mapping for MIR, LeiosEb, and anchor-bearing certificates.
All 11 certificate-related tests pass without errors. This suite provides valuable regression testing for the new unified certificate persistence path.
b327165 to
8d1aaa4
Compare
There was a problem hiding this 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)
database/utxo.go (1)
128-139: PropagateIsCollateralReturnwhen materializing UTXOs by address
models.Utxonow has anIsCollateralReturnflag, butUtxosByAddressbuilds a fresh struct without copying it. Collateral-return UTXOs returned by address look like normal outputs (flag remains false), which can break consumers relying on that distinction.Include the flag in the struct literal:
tmpUtxo = models.Utxo{ ID: utxo.ID, TxId: utxo.TxId, OutputIdx: utxo.OutputIdx, AddedSlot: utxo.AddedSlot, DeletedSlot: utxo.DeletedSlot, PaymentKey: utxo.PaymentKey, StakingKey: utxo.StakingKey, Amount: utxo.Amount, Assets: utxo.Assets, + IsCollateralReturn: utxo.IsCollateralReturn, }
♻️ Duplicate comments (2)
database/plugin/metadata/sqlite/certs.go (1)
240-252: NormalizePoolKeyHashmapping across stake delegation certificate variantsIn the stake delegation-related cases,
PoolKeyHashis mapped inconsistently:
StakeDelegationCertificateandStakeVoteRegistrationDelegationCertificateusec.PoolKeyHash.Bytes().StakeRegistrationDelegationCertificateandStakeVoteDelegationCertificateassignPoolKeyHash: c.PoolKeyHashdirectly.Given the models declare
PoolKeyHash []byte, it would be good to confirm whetherc.PoolKeyHashis a hash object (needing.Bytes()) or already a[]bytefor those two variants. If all four certificates expose the same typed hash, standardizing on.Bytes()everywhere would avoid surprises; if the types differ intentionally, a short comment here explaining why some cases pass[]byteand others call.Bytes()would make this clearer.Also applies to: 265-277
database/plugin/metadata/sqlite/certs_test.go (1)
37-38: Fix invalid loop syntaxfor i := range 3(tests won’t compile)All of these loops:
for i := range 3 { // ... }are invalid Go;
rangedoes not operate on an integer. This will prevent the test file from compiling.Replace them with standard counted loops, e.g.:
- for i := range 3 { + for i := 0; i < 3; i++ {Apply this change to every occurrence (
TestPoolCertificateDuplicatePrevention,TestStakeCertificateDuplicatePrevention,TestDrepCertificateReRegistration, andTestGenesisKeyDelegationCertificateIdempotent).Also applies to: 74-75, 119-120, 165-166, 229-230, 259-260, 285-286, 329-330
🧹 Nitpick comments (5)
database/types/types.go (1)
40-56: Consider handlingNULLDB values explicitly inScanfor robustnessThe
Scanimplementations for bothRatandUint64are sound for non-null string/[]byte columns. If any columns using these types can beNULL, scanning will currently fail with a type error. You may want to treatNULLas “leave zero value” instead of erroring.Example adjustment:
func (r *Rat) Scan(val any) error { if r.Rat == nil { r.Rat = new(big.Rat) } - var v string + if val == nil { + // Keep zero-value rat when column is NULL. + return nil + } + var v string switch t := val.(type) { case string: v = t case []byte: v = string(t) default: return fmt.Errorf( "value was not expected type, wanted string or []byte, got %T", val, ) } @@ func (u *Uint64) Scan(val any) error { - var v string + if val == nil { + // Treat NULL as zero; adjust if a different semantic is desired. + u.Val = 0 + return nil + } + var v string switch t := val.(type) { case string: v = t case []byte: v = string(t) default: return fmt.Errorf( "value was not expected type, wanted string or []byte, got %T", val, ) } tmpUint, err := strconv.ParseUint(v, 10, 64) if err != nil { return err } u.Val = tmpUint return nil }Please double‑check against how these columns are defined in your migrations and what the SQL driver actually returns for the configured column types.
Also applies to: 58-85
database/certificate.go (1)
21-39: Guard certificate getters against nil transactionsThe new comment correctly describes that certificates are now persisted via
SetTransaction, and these getters just query metadata. However, both helpers calltxn.Metadata()without checking fortxn == nil, unlike other database methods that create a transaction when nil.To make these safer and consistent with the rest of the DB API, consider:
func (d *Database) GetPoolRegistrations( poolKeyHash lcommon.PoolKeyHash, txn *Txn, ) ([]lcommon.PoolRegistrationCertificate, error) { - return d.metadata.GetPoolRegistrations(poolKeyHash, txn.Metadata()) + if txn == nil { + txn = d.Transaction(false) + defer txn.Commit() //nolint:errcheck + } + return d.metadata.GetPoolRegistrations(poolKeyHash, txn.Metadata()) } @@ func (d *Database) GetStakeRegistrations( stakingKey []byte, txn *Txn, ) ([]lcommon.StakeRegistrationCertificate, error) { - return d.metadata.GetStakeRegistrations(stakingKey, txn.Metadata()) + if txn == nil { + txn = d.Transaction(false) + defer txn.Commit() //nolint:errcheck + } + return d.metadata.GetStakeRegistrations(stakingKey, txn.Metadata()) }If callers always supply a non-nil
txnhere, documenting that contract would also help.database/transaction.go (1)
22-64: SetTransaction lifecycle and deposits wiring are solid; avoid shadowingerrThe new SetTransaction correctly:
- Handles internally-created transactions with a single deferred commit/rollback decision, and
- Threads the
depositsmap through to metadata after writing produced UTXO blobs.To reduce confusion around the named return, consider avoiding
:=when settingerrinside the loop:- for _, utxo := range tx.Produced() { + for _, utxo := range tx.Produced() { // Add UTxO to blob DB key := UtxoBlobKey( utxo.Id.Id().Bytes(), utxo.Id.Index(), ) - err := txn.Blob().Set(key, utxo.Output.Cbor()) - if err != nil { + if err = txn.Blob().Set(key, utxo.Output.Cbor()); err != nil { return err } }Functionally this is equivalent, but it keeps all error handling clearly tied to the named
err.database/plugin/metadata/sqlite/account.go (1)
33-35: Clarify/enforce single-active-account invariant forSetAccount
GetAccountnow returns the most recent active account for a staking key, andSetAccountalways inserts a new row with the providedactiveflag. This works assuming other codepaths reliably mark previous accounts for the same key asActive = false; otherwise you can accumulate multiple active rows per staking key (even thoughGetAccountwill pick only the latest).If the intent is to guarantee a single active account per staking key, consider either:
- explicitly deactivating prior rows for that key in the same transaction that calls
SetAccount, or- enforcing it at the DB level with an additional partial unique index (e.g., on
(staking_key, active)withactive = true), if your SQLite setup allows it.Also applies to: 52-58
database/plugin/metadata/store.go (1)
126-132: Minor documentation clarification: specify deposit units and simplify default value explanation.The deposits map comment is detailed but could be improved:
Specify units: The comment doesn't indicate that deposit amounts are in lovelace (Cardano's base unit). Consider adding:
map[int]uint64, // deposits: certificate index -> deposit amount in lovelaceSimplify default behavior: The comment states "Missing keys default to 0. Non-deposit certificates should have 0 values" — this is somewhat redundant. Since missing keys already default to 0, you could simplify to: "Missing keys default to 0, so only include certificates with non-zero deposits."
This makes the caller's responsibility clearer: they only need to populate entries for certificates that actually have deposits.
Apply this diff to improve the comment:
SetTransaction( ocommon.Point, lcommon.Transaction, uint32, // transaction index within block - map[int]uint64, // deposits: certificate index -> deposit amount. Keys must align with transaction certificate slice order. Missing keys default to 0. Non-deposit certificates should have 0 values. + map[int]uint64, // deposits: certificate index -> deposit amount in lovelace. Keys must align with transaction certificate slice order. Missing keys default to 0. *gorm.DB, ) error
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (40)
database/account.go(0 hunks)database/certificate.go(1 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/blob/aws/database.go(1 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(6 hunks)database/plugin/metadata/sqlite/drep.go(3 hunks)database/plugin/metadata/sqlite/pool.go(1 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(4 hunks)database/plugin/metadata/store.go(2 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(3 hunks)go.mod(3 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(3 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (3)
- database/account.go
- database/drep.go
- database/models/certs.go
✅ Files skipped from review due to trivial changes (1)
- database/plugin/blob/aws/database.go
🚧 Files skipped from review as they are similar to previous changes (16)
- database/models/move_instantaneous_rewards.go
- database/plugin/metadata/sqlite/utxo.go
- database/models/models.go
- database/types/types_test.go
- database/models/genesis_key_delegation.go
- database/models/pool.go
- ledger/view.go
- database/models/leios_eb.go
- database/models/certificate_test.go
- go.mod
- database/models/certificate.go
- internal/node/load.go
- internal/config/config_test.go
- database/models/auth_committee_hot.go
- database/models/account_test.go
- database/models/drep.go
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/transaction.godatabase/utxo.godatabase/plugin/metadata/sqlite/transaction.goledger/delta.godatabase/models/transaction.godatabase/models/utxo.godatabase/plugin/metadata/sqlite/database.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/utxo.godatabase/models/transaction.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/utxo.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/sqlite/certs_test.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/utxo.godatabase/plugin/metadata/sqlite/certs_test.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/utxo.goledger/state.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/utxo.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/utxo.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/models/utxo.goledger/state.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/transaction.godatabase/models/utxo.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/plugin/metadata/sqlite/certs_test.godatabase/models/utxo.goledger/state.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/state.go
🧬 Code graph analysis (15)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(59-61)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(268-276)
database/plugin/metadata/sqlite/transaction.go (5)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-48)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(59-61)database/models/utxo.go (1)
UtxoLedgerToModel(50-79)database/models/certificate.go (10)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeRegistrationDrep(74-76)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(146-154)Certificate(157-159)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/models/transaction.go (2)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(59-61)
database/plugin/metadata/sqlite/certs_test.go (11)
database/plugin/metadata/sqlite/database.go (1)
New(51-124)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(59-61)database/models/account.go (7)
StakeRegistration(115-121)StakeRegistration(123-125)Deregistration(68-74)Deregistration(76-78)StakeDeregistration(104-109)StakeDeregistration(111-113)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(59-61)
ledger/state.go (1)
database/plugin/log.go (1)
Logger(18-27)
database/models/account.go (2)
database/models/pool.go (1)
Pool(26-38)database/models/drep.go (1)
Drep(21-29)
database/plugin/metadata/sqlite/drep.go (1)
database/models/drep.go (1)
Drep(21-29)
ledger/certs.go (1)
ledger/state.go (1)
LedgerState(79-103)
database/models/asset.go (1)
database/types/types.go (1)
Uint64(59-61)
database/plugin/metadata/sqlite/certs.go (11)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-48)database/models/certificate.go (3)
Certificate(146-154)Certificate(157-159)CertificateType(28-28)database/models/account.go (20)
StakeRegistration(115-121)StakeRegistration(123-125)StakeDeregistration(104-109)StakeDeregistration(111-113)StakeDelegation(92-98)StakeDelegation(100-102)Registration(80-86)Registration(88-90)Deregistration(68-74)Deregistration(76-78)StakeRegistrationDelegation(127-134)StakeRegistrationDelegation(136-138)VoteDelegation(167-173)VoteDelegation(175-177)StakeVoteDelegation(140-147)StakeVoteDelegation(149-151)VoteRegistrationDelegation(179-186)VoteRegistrationDelegation(188-190)StakeVoteRegistrationDelegation(153-161)StakeVoteRegistrationDelegation(163-165)database/types/types.go (2)
Uint64(59-61)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
⏰ 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). (3)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (16)
ledger/certs.go (1)
23-59: Certificate deposit calculation looks correct and preserves zero-value semanticsThe method cleanly delegates to
CertDepositFunc, records an entry for every certificate index (including zero deposits), and surfaces errors with useful context. This aligns well with the new unified certificate persistence flow.database/models/asset.go (1)
68-74: Struct-based Uint64 usage is correct hereSwitching to
types.Uint64{Val: amount}matches the new struct definition and keeps the previous amount semantics intact.database/models/resign_committee_cold.go (1)
17-30: ResignCommitteeCold model constraints look consistent with intended semanticsThe uniqueness on
ColdCredential, the unique index onCertificateID, and the switch totypes.Uint64forAddedSlotbring this model in line with the unified certificate mapping and other certificate models.database/plugin/metadata/sqlite/pool.go (1)
89-90: Uint64 → uint64 extraction for pool registrations is correctUsing
cert.Pledge.Valandcert.Cost.Valproperly unwraps the newtypes.Uint64fields when reconstructingPoolRegistrationCertificate.database/utxo.go (1)
24-25: Type migration tolcommoninputs/addresses looks consistentSwitching
UtxoConsumetolcommon.TransactionInputandUtxosByAddresstolcommon.Addressmatches the rest of the metadata/store interfaces in this PR and keeps DB APIs aligned with the ledger/common layer. I don’t see functional regressions here.Also applies to: 102-112
database/models/transaction.go (1)
17-36: Transaction associations and monetary types now look correctly wiredUsing
where:is_collateral_return = false/trueonOutputsandCollateralReturnaligns the GORM mappings with the newIsCollateralReturncolumn onUtxo, and revertingFee/TTLtotypes.Uint64keeps them consistent with other amount fields and existing DB encoding.ledger/state.go (1)
849-895: Certificate deposit precomputation and narrowed validation suppression look correctPrecomputing
depositsviaCalculateCertificateDepositsand threading them throughdelta.addTransactionWithDepositsintegrates cleanly with the new deposits flow. The validation logic now only swallows"value not conserved"errors for certificate-bearing transactions and treats all otherValidateTxFuncerrors as fatal, which restores strict validation while still working around the known deposit issue.ledger/delta.go (1)
25-48: Delta now models deposits correctly for valid vs. invalid transactionsStoring
DepositsinTransactionRecordand usingaddTransactionWithDepositslets you:
- Ignore deposits entirely for invalid transactions (always passing nil into
SetTransaction), and- Prefer precomputed deposits but fall back to
CalculateCertificateDepositsfor valid certificate-bearing txs when needed.This matches the intended ledger semantics and keeps the deposit flow centralized.
Also applies to: 49-99
database/models/utxo.go (1)
25-39: UTxO type wrapping and collateral-return flag look consistentSwitching
AddedSlot,DeletedSlot, andAmounttotypes.Uint64and wrapping them inUtxoLedgerToModelis consistent with the rest of the models, and theIsCollateralReturnflag lines up with thetransactionassociations for collateral returns. No issues spotted here.Also applies to: 58-59
database/plugin/metadata/sqlite/certs.go (1)
63-503: Unified certificate + per-type models implementation is coherentThe
storeCertificateflow (create unifiedCertificate, then per-type model, then back-linkCertificateID) now consistently populates all key fields (including deposits, anchors, MIR/Leios JSON payloads, and committee/DRep data) and wiresCertificateIDboth ways. This matches the schema in the variousmodels.*types and should satisfy the unified tracking requirement without reintroducing the uniqueness/linkage issues from earlier revisions.database/plugin/metadata/sqlite/transaction.go (1)
65-85: SetTransaction idempotency, deposits, and slot bumping look correctThe new
SetTransactionflow cleanly separates concerns:
- Upsert by hash with a precomputed
alreadyExistsflag andblockChangeddetection.- UTXO creation only on first insertion, with collateral return flagged via
IsCollateralReturn.- Certificate handling gated so invalid transactions and pure replays don’t create duplicates, while re-inclusion with a different block only bumps
slot/added_slotviaupdateCertificateSlots.- Deposits are enforced only for the certificate types that actually require them, while zero-deposit certs still pass with a default of 0.
The
updateCertificateSlotshelper also correctly targetscertificatesplus all specialized tables that carryadded_slot, keeping certificate timing consistent across a re-included transaction.Also applies to: 343-375, 427-475
database/plugin/metadata/sqlite/drep.go (1)
29-47: DRep get/upsert behavior is consistent with the model
GetDrepnow has the expected “(nil, nil) on not-found” behavior, andSetDrep’sON CONFLICT (drep_credential)upsert correctly drivesadded_slot, anchor fields, andactive. This matches themodels.Drepschema and should behave well under repeated updates.Also applies to: 58-77
database/plugin/metadata/sqlite/database.go (1)
36-48: SQLite store lifecycle and vacuum coordination are well-handledThe revised
New/init/runVacuum/Closeflow cleanly separates in-memory vs on-disk behavior, guards VACUUM ondataDir == ""andclosed, and usesvacuumWGplus the timer stop inCloseto avoid running VACUUM against a closed DB or leaking goroutines. The small wrapper/docs additions onAutoMigrate/DBhelpers are also straightforward.Also applies to: 50-68, 141-156, 191-205
database/models/account.go (1)
28-35: Certificate constraint strategy and Account indexing are consistentThe move to:
- plain
indexonAccount.StakingKeywith an indexedActiveflag andAddedSlot, and- composite uniques on
(StakingKey, CertificateID)only forDeregistration,StakeDeregistration, andStakeRegistration,matches the documented strategy and the way
storeCertificatenow populatesCertificateID. This allows multiple historical certs per stake key while still enforcing one row per certificate ID where needed, and keeps account lookups fast on(staking_key, active, added_slot).Also applies to: 60-67, 68-75, 104-121
database/plugin/metadata/store.go (2)
143-143: LGTM! Type consistency improvement.The parameter type change from
ledger.Addresstolcommon.Addressimproves consistency across the interface, where other ledger types (PoolKeyHash, Blake2b256, TransactionInput) also use thelcommonpackage. Past review verified that implementations have been updated to match.
145-145: LGTM! Consistent with the GetUtxosByAddress type change.The parameter type change from
ledger.TransactionInputtolcommon.TransactionInputmaintains interface consistency. Past review verified the implementation updates.
8d1aaa4 to
d70daec
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
database/plugin/metadata/sqlite/account.go (1)
52-82: SetAccount's FirstOrCreate pattern conflicts with multi-record account design—confirmed critical bug.GetAccount retrieves accounts with
Order("added_slot DESC").First(ret), indicating multiple records per staking key are expected, but SetAccount usesFirstOrCreatewhich retrieves the first (oldest) matching record.When multiple records exist for a staking key:
- GetAccount retrieves the most recent (by
added_slot)- SetAccount retrieves the oldest (by default ID order in FirstOrCreate)
This causes SetAccount to update the wrong historical record. For example, if a stake key has records from registration (slot 1000) and delegation (slot 2000), SetAccount would modify the slot 1000 record rather than the current slot 2000 record.
Additionally, the Account model has
gorm:"index"on StakingKey (non-unique), confirming the schema allows multiple records per staking key.To fix:
- If multi-record design is intended: Create new records rather than using FirstOrCreate, or query for the most recent record matching
staking_keybefore updating- If single-record design is intended: Add
gorm:"uniqueIndex"constraint on StakingKey and fix the misleading comment
♻️ Duplicate comments (1)
database/plugin/metadata/sqlite/certs_test.go (1)
37-38: Fix invalidfor i := range 3loops (compile-time error)All occurrences of:
for i := range 3 { ... }are invalid in Go (
rangecannot iterate over an integer). This file will not compile.Replace them with standard counted loops:
- for i := range 3 { + for i := 0; i < 3; i++ {Apply this change to all the 3-iteration loops in this file (pool registration/retirement, stake registration/deregistration, DRep registration/deregistration/update, and genesis key delegation tests).
Also applies to: 74-75, 119-120, 229-230, 259-260, 285-286, 329-330
🧹 Nitpick comments (6)
internal/node/load.go (1)
93-198: Async ledger start and cancellable catch-up loop look good; consider configurability and safety checks.The switch to
ls.Start()plus a timeout-based wait with a ticker is a nice improvement over a blind block. Two small follow-ups to consider:
- The
5*time.Minutetimeout and5*time.Secondpoll interval are hard-coded. Depending on network size and hardware, five minutes might be too short or overly conservative. Making these values configurable (or at least constants) would give you some operational flexibility.- The loop assumes
ls.Tip()always returns a non-nil value with a validPoint. IfTip()can ever transiently return a zero/empty value (e.g., before any block is processed), a defensive check (or a documented guarantee onTip()) would avoid surprises.These are polish items; the overall control flow and error handling look solid.
database/database_test.go (1)
22-24:TestUtxosByAddressCollateralReturnFlagdoesn’t exercise the realUtxosByAddresspath.Right now the test builds an
originalUtxo, manually copies every field (includingIsCollateralReturn) intocopiedUtxo, and then asserts they match. This only verifies the test’s own copy code, not the production logic that previously dropped the flag.To make this guard against regressions, consider:
- Creating an in-memory DB/metadata store, inserting a UTXO row with
IsCollateralReturn = true, and then- Calling the exported API that’s supposed to return UTXOs by address (e.g., the
database-levelUtxosByAddresswrapper), asserting that the returned slice contains the flag set on the corresponding element.That way, if the mapping logic changes or a new field gets missed again, the test will fail for the right reason instead of still passing due to in-test duplication.
Also applies to: 74-121
database/plugin/metadata/sqlite/utxo.go (1)
96-137: Address-based UTXO query logic is sound in intent; verify GORM subquery usage and consider simplifying.The new flow (derive payment/stake hashes from
lcommon.Address, ignore zero hashes, and early-return when both are empty) is a clear improvement and matches how addresses decompose into payment/stake parts.The one area that’s a bit subtle is:
if addrQuery == nil { return ret, nil } result := txn. Where("deleted_slot = 0"). Where(addrQuery). Find(&ret)Here
addrQueryis a*gorm.DBbuilt fromtxn. In GORM, passing a*gorm.DBintoWhereis treated as a subquery; the generated SQL typically ends up as... WHERE deleted_slot = 0 AND id IN (SELECT id FROM utxos WHERE ...)(or similar, based on primary key). That’s likely what you want, but it’s non-obvious and easy to get wrong if the schema or primary key changes.Two suggestions:
- Double-check the actual SQL generated (e.g., with
Debug()) to ensure it’s effectivelydeleted_slot = 0 AND (payment_key = ? OR staking_key = ?)and not something looser/tighter than intended.- For readability, consider building a single
query := txn.Model(&models.Utxo{}).Where("deleted_slot = 0")and adding explicitWhere/Orconditions with parentheses in the string, so future readers don’t have to reason about GORM’s subquery semantics.This doesn’t look immediately broken, but it’s subtle enough that a quick verification would be worthwhile.
database/types/types.go (1)
36-60: Clarify/extend Scan handling for driver numeric types and NULL semanticsThe new
Scanimplementations look coherent, but there is one subtle interoperability concern:
Rat.Scannow cleanly handlesnil,string, and[]byte— that’s fine for text-based columns.Uint64.Scannow:
- Treats
nilas zero, which is a behavioral choice that callers should be aware of.- Only accepts
string/[]byte, and errors for anything else.If any columns using
types.Uint64are backed by integer types (e.g., SQLite/PostgresINTEGERwhere the driver surfacesint64), those rows will now hit the default case and fail with"wanted string or []byte".Two follow-ups I’d recommend:
- Verify that all
Uint64-backed columns are mapped as text/char so that the driver actually returnsstringor[]byte.- If not guaranteed, consider extending
Scanto handle numeric types as well, e.g.:func (u *Uint64) Scan(val any) error { @@ - switch t := val.(type) { - case string: - v = t - case []byte: - v = string(t) - default: - return fmt.Errorf( - "value was not expected type, wanted string or []byte, got %T", - val, - ) - } + switch t := val.(type) { + case string: + v = t + case []byte: + v = string(t) + case int64: + u.Val = uint64(t) // #nosec G115 – application-level guarantee about range + return nil + default: + return fmt.Errorf( + "value was not expected type, wanted string, []byte, or int64, got %T", + val, + ) + }This would make the type more robust to how different drivers materialize integer columns, while preserving your current string/[]byte behavior and
nil→0 convention.Also applies to: 63-95
ledger/state.go (1)
850-895: Certificate deposit precomputation & validation gating look good; reconsider deposits usage on swallowed errorsThe new flow in
ledgerProcessBlockis much safer:
- Deposits are only precomputed for certificate TXs.
CalculateCertificateDepositsfailures abort block processing with context.- Validation errors are now only swallowed when the TX has certificates and the error message contains
"value not conserved"; all other errors abort, restoring strong validation guarantees.One subtle point to think through:
- In the
"value not conserved"case you log and continue, but you still pass the previously computeddepositsmap intodelta.addTransactionWithDeposits.- If that specific error is symptomatic of a disagreement between ledger validation and your deposit computation, you may want to explicitly discard deposits in this path to avoid persisting deposit data that the ledger just flagged as inconsistent, e.g.:
if err != nil { - // For transactions with certificates, deposits may not be properly accounted for in validation - // Only swallow "value not conserved" errors for certificate transactions - if len(tx.Certificates()) > 0 && - strings.Contains(err.Error(), "value not conserved") { - ls.config.Logger.Warn( - "TX with certificates failed deposit validation (known issue): " + err.Error(), - ) - } else { - return nil, fmt.Errorf("TX validation failure: %w", err) - } + // For transactions with certificates, deposits may not be properly accounted for in validation. + // Only swallow "value not conserved" errors for certificate transactions, and in that case + // skip recording deposits to avoid persisting potentially inconsistent values. + if len(tx.Certificates()) > 0 && + strings.Contains(err.Error(), "value not conserved") { + ls.config.Logger.Warn( + "TX with certificates failed deposit validation (known issue): "+err.Error(), + ) + deposits = nil + } else { + return nil, fmt.Errorf("TX validation failure: %w", err) + } }If your intent is explicitly to trust your own
CalculateCertificateDepositsresult even when the ledger complains, then the current behavior is fine — but it would be good to document that choice in a comment so future readers know this is intentional rather than an oversight. The string-match on"value not conserved"is also inherently brittle, so having that rationale spelled out will help if the underlying library ever changes its error text.database/plugin/metadata/sqlite/account_test.go (1)
128-185: Minor redundancy around transaction rollback in TestSetAccountWithTransactionThe test both defers
txn.Rollback()and callstxn.Rollback()explicitly before verifying the absence of the account. It’s harmless but redundant; consider dropping the deferred rollback and keeping the explicit call to simplify the control flow.- // Start a transaction - txn := metadataStore.DB().Begin() - require.NoError(t, txn.Error) - defer txn.Rollback() + // Start a transaction + txn := metadataStore.DB().Begin() + require.NoError(t, txn.Error)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (42)
database/account.go(1 hunks)database/certificate.go(1 hunks)database/database_test.go(2 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/blob/aws/database.go(1 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/account_test.go(1 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(3 hunks)database/plugin/metadata/sqlite/pool.go(1 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(4 hunks)database/plugin/metadata/store.go(3 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(4 hunks)go.mod(4 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(3 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (2)
- database/drep.go
- database/models/certs.go
✅ Files skipped from review due to trivial changes (3)
- database/plugin/blob/aws/database.go
- internal/config/config_test.go
- database/models/certificate_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
- database/models/models.go
- database/models/transaction.go
- database/utxo.go
- database/types/types_test.go
- database/models/asset.go
- database/models/move_instantaneous_rewards.go
- go.mod
- database/models/drep.go
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/database_test.godatabase/models/utxo.goledger/state.godatabase/plugin/metadata/sqlite/certs_test.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/database_test.godatabase/models/utxo.goledger/state.godatabase/plugin/metadata/sqlite/certs_test.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/database_test.godatabase/models/utxo.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/sqlite/certs_test.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/database_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/database_test.goledger/state.godatabase/plugin/metadata/sqlite/certs_test.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/models/utxo.godatabase/models/genesis_key_delegation.goledger/delta.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/sqlite/database.godatabase/transaction.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/utxo.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
ledger/state.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/state.go
🧬 Code graph analysis (23)
database/models/leios_eb.go (1)
database/types/types.go (1)
Uint64(63-65)
database/database_test.go (2)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(63-65)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(63-65)
database/models/genesis_key_delegation.go (1)
database/types/types.go (1)
Uint64(63-65)
ledger/state.go (1)
database/plugin/log.go (1)
Logger(18-27)
database/plugin/metadata/sqlite/certs.go (11)
database/models/certificate.go (4)
CertificateTypeStakeRegistration(32-34)Certificate(146-154)Certificate(157-159)CertificateType(28-28)database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-49)database/models/account.go (20)
StakeRegistration(115-121)StakeRegistration(123-125)StakeDeregistration(104-109)StakeDeregistration(111-113)StakeDelegation(92-98)StakeDelegation(100-102)Registration(80-86)Registration(88-90)Deregistration(68-74)Deregistration(76-78)StakeRegistrationDelegation(127-134)StakeRegistrationDelegation(136-138)VoteDelegation(167-173)VoteDelegation(175-177)StakeVoteDelegation(140-147)StakeVoteDelegation(149-151)VoteRegistrationDelegation(179-186)VoteRegistrationDelegation(188-190)StakeVoteRegistrationDelegation(153-161)StakeVoteRegistrationDelegation(163-165)database/types/types.go (2)
Uint64(63-65)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/plugin/metadata/sqlite/transaction.go (4)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(63-65)database/models/utxo.go (1)
UtxoLedgerToModel(50-79)database/models/certificate.go (10)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeRegistrationDrep(74-76)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(146-154)Certificate(157-159)
database/plugin/metadata/sqlite/certs_test.go (11)
database/plugin/metadata/sqlite/database.go (1)
New(52-125)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(63-65)database/models/account.go (7)
StakeRegistration(115-121)StakeRegistration(123-125)Deregistration(68-74)Deregistration(76-78)StakeDeregistration(104-109)StakeDeregistration(111-113)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (3)
Certificate(146-154)Certificate(157-159)CertificateTypeStakeRegistration(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
database/plugin/metadata/sqlite/database.go (1)
database/plugin/log.go (1)
Logger(18-27)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(63-65)
database/plugin/metadata/sqlite/drep.go (1)
database/models/drep.go (1)
Drep(21-29)
ledger/view.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
database/models/auth_committee_hot.go (1)
database/types/types.go (1)
Uint64(63-65)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(269-277)
database/plugin/metadata/sqlite/pool.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
database/models/account.go (2)
database/models/pool.go (1)
Pool(26-38)database/models/drep.go (1)
Drep(21-29)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(79-103)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
database/account.go (3)
database/models/account.go (1)
Account(27-35)database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/account_test.go (8)
database/plugin/metadata/sqlite/database.go (2)
New(52-125)MetadataStoreSqlite(39-49)database/plugin/metadata/store.go (1)
New(151-157)database/models/pool.go (1)
Pool(26-38)database/models/drep.go (1)
Drep(21-29)database/models/account.go (3)
Account(27-35)StakeRegistration(115-121)StakeRegistration(123-125)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(63-65)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)
database/plugin/metadata/sqlite/account.go (1)
database/models/account.go (1)
Account(27-35)
⏰ 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). (8)
- GitHub Check: lint
- GitHub Check: go-test (1.25.x, macos-latest)
- GitHub Check: go-test (1.24.x, ubuntu-latest)
- GitHub Check: go-test (1.24.x, macos-latest)
- GitHub Check: go-test (1.25.x, ubuntu-latest)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (28)
database/models/account_test.go (1)
27-34: Structured test table with explicitwantErrMsg/expectedHRPimproves clarity.Reordering the table to surface
wantErrMsgandexpectedHRPand then using them selectively in the assertions makes the tests easier to reason about while keeping behavior unchanged. The gating onwantErrandvalidateAddris correct and avoids accidental HRP checks on error cases.Also applies to: 98-167
database/models/leios_eb.go (1)
21-37: LeiosEb model and indexes look consistent with certificate mapping design.The composite unique index on
(ElectionID, EndorserBlockHash)plus a separate unique index onCertificateIDgives you stable identity from both the Leios view and the unified certificate view, andAddedSlot types.Uint64matches the rest of the schema. No issues spotted.database/models/resign_committee_cold.go (1)
17-30: ResignCommitteeCold uniqueness constraints are now explicit and consistent.Making
ColdCredentialunique and taggingCertificateIDwith its own unique index cleanly enforces “one resign per cold credential, one record per certificate” at the DB level. The shift totypes.Uint64forAddedSlotalso keeps this model in line with the rest of the certificate models.database/plugin/metadata/sqlite/pool.go (1)
80-91: Pool registration field mappings correctly track the new types.Using
uint(...)forCertTypeand readingPledge/Costfrom.Valis the right adaptation to the updated types in the models. The rest of the mapping logic is unchanged and still coherent.database/plugin/metadata/sqlite/utxo.go (1)
232-252: Switch tolcommon.TransactionInputfor delete marker aligns with ledger/common usage.Updating
SetUtxoDeletedAtSlotto acceptlcommon.TransactionInputand then usingutxoId.Id().Bytes()/utxoId.Index()keeps the metadata store consistent with the rest of the codebase’s move toward ledger/common types. Thedeleted_slotupdate query itself is unchanged and remains straightforward.Just ensure at call sites that
utxoId.Id()is always non-nil/valid before invoking this method so you don’t risk panics when dereferencing or callingBytes().database/models/pool.go (1)
55-60: Pool registration/retirement uniqueness aligns with certificate-centric persistenceThe updated GORM tags look consistent with the unified certificate model:
PoolRegistration.CertificateIDasuniqueIndex:uniq_pool_registration_certgives you a clean 1:1 mapping from registration certificate → row, which is ideal for idempotent upserts.PoolRetirementnow has:
PoolKeyHashandEpochparticipating inuniqueIndex:uniq_pool_retirement, allowing multiple retirements per pool across epochs but preventing duplicate retirements for the same (pool, epoch) pair.CertificateIDwith its own unique index, again giving a 1:1 certificate→row mapping.This preserves support for re-registrations and multiple retirements while enforcing the right idempotency guarantees at the certificate level.
Also applies to: 91-98
database/transaction.go (1)
22-63: Consolidated SetTransaction lifecycle and deposits propagation look solidThis implementation is much cleaner:
- The named return + single deferred closure ensures internally created transactions are either fully committed on success or rolled back on any error, without risking a commit-after-rollback sequence.
- UTXO blob writes short-circuit on failure and correctly propagate the error into the deferred rollback path.
depositsis threaded straight through tometadata.SetTransaction, matching the new ledger/delta deposit handling.Behavior for caller-supplied
txnis preserved (no implicit commit/rollback), so this is a safe evolution of the API.ledger/view.go (1)
103-113: PoolCurrentState correctly adapts to new Uint64 and CertType representationsThe adjustments here look correct:
- Using
uint(lcommon.CertificateTypePoolRegistration)matches the updated CertType field type onlcommon.PoolRegistrationCertificate.- Reading
PledgeandCostfrompool.Pledge.Val/pool.Cost.Valis the right adaptation to the newtypes.Uint64struct, and preserves the previous numeric behavior (defaulting to 0 when the pool was not found and a zero-valuemodels.Pool{}is used).No further changes needed in this method.
database/plugin/metadata/sqlite/account_test.go (2)
28-126: SetAccount tests nicely verify update semantics and inactive filteringThis test exercises first‑write, in‑place update (including Active flip), uniqueness of the account row, and the includeInactive flag on GetAccount; behavior matches the Account model and unified account semantics.
187-285: End-to-end DeleteCertificate test is thorough and well-scopedManually creating the Transaction, unified Certificate, and StakeRegistration rows, then asserting both their removal and idempotent behavior on a second DeleteCertificate call, gives strong coverage of the new unified certificate deletion path.
database/certificate.go (1)
21-47: Getter helpers for pool/stake registrations are simple and consistentThe auto‑transaction pattern when
txn == nilplus direct delegation to the metadata layer keeps the public API easy to use while preserving transactional control for advanced callers; no issues spotted.database/models/auth_committee_hot.go (1)
17-27: AuthCommitteeHot model updates align with unified certificate mappingMoving AddedSlot to
types.Uint64, addingCertificateID, and enforcing uniqueness onColdCredentialbring this model in line with the rest of the certificate‑backed schema; looks coherent with the unifiedCertificatemapping.database/models/certificate.go (1)
1-164: CertificateType Valuer/Scanner and unified Certificate model look correctThe CertificateType constants mirror the ledger types, the Valuer/Scanner implementations defensively handle common integer/[]byte shapes with overflow and basic range checks, and the unified Certificate struct (TransactionID + cert_index unique pair, polymorphic CertificateID, indexed CertType) matches the intended polymorphic certificate mapping.
Please ensure your existing tests cover round‑tripping
CertTypevalues through the actual DB driver(s) you use so that all driver return types encountered in practice are exercised.database/models/genesis_key_delegation.go (1)
21-34: GenesisKeyDelegation model correctly encodes uniqueness and 1:1 certificate mappingThe composite
uniq_genesis_delegationunique index plus the unique index onCertificateIDcapture both idempotent insertion and the one‑to‑one relationship to the unifiedcertificatesrow; usingtypes.Uint64for AddedSlot keeps it consistent with other certificate models.If you rely on
OnConflictclauses for this model in the sqlite persistence layer, double‑check that the conflict target matches these unique index columns/names so idempotency behaves as intended.ledger/certs.go (1)
37-59: Certificate deposit calculation is consistent and preserves zero-value entriesThe loop correctly defers deposit semantics to
CertDepositFunc, records an entry for every certificate index (including zeros), and surfaces per-index errors with context, which matches howSetTransactionexpects to consume the map. No issues from this implementation.database/plugin/metadata/sqlite/certs.go (1)
52-60: Stake registration getter change looks correctUsing
CertType: uint(lcommon.CertificateTypeStakeRegistration)when reconstructingStakeRegistrationCertificateinstances brings the returned value in line with the ledger type metadata and avoids zero-type ambiguities.database/plugin/metadata/sqlite/database.go (1)
127-141: Lifecycle, vacuum, and certificate-cleanup scheduling look soundThe revised
init,runVacuum,scheduleDailyVacuum,runCertificateCleanup,scheduleCertificateCleanup, andCloseimplementations correctly coordinate timers with theclosedflag andtimerMutex, and thevacuumWGensures no vacuum runs against a closed DB. The certificate cleanup hook is safely a guarded no-op for now and won’t race Close.Also applies to: 144-159, 189-241, 248-264
database/models/utxo.go (1)
23-40: UTxO model and conversion align with new transaction associationsSwitching
AddedSlot/Amounttotypes.Uint64and addingIsCollateralReturncleanly support theTransaction.OutputsvsCollateralReturnassociations, andUtxoLedgerToModelpopulates the typed fields as expected, leaving the collateral-return flag to the caller. No issues here.Also applies to: 50-60
database/plugin/metadata/sqlite/transaction.go (1)
52-145: SetTransaction’s idempotency and deposit plumbing are coherentThe revised
SetTransactioncorrectly:
- Detects existing transactions by hash up front and reloads the ID post-upsert.
- Avoids re-processing UTXOs for already-seen transactions.
- Skips certificate handling for invalid transactions.
- Uses the
depositsmap only for new, valid transactions, while allowing zero-value deposits and guarding only the certificate types that must carry a deposit.This matches the intended ledger flow and the new tests around re-inclusion and deposit presence.
Also applies to: 343-425
ledger/delta.go (1)
25-29: LedgerDelta now threads deposits into persistence correctlyThe new
TransactionRecord.Depositsfield andaddTransactionWithDeposits/applylogic cleanly separate:
- Invalid transactions: consume UTxOs, record the tx with nil deposits, and skip certificate effects.
- Valid transactions: reuse precomputed deposits when supplied or compute them once via
CalculateCertificateDepositswhen certificates are present, then pass them through toSetTransaction.This matches the intended two-phase validation semantics and the new database API.
Also applies to: 37-47, 49-89
database/plugin/metadata/sqlite/drep.go (1)
25-47: DRep lookup and upsert behavior are now consistent with the model
GetDrepcorrectly queries bydrep_credentialand uses(*models.Drep, error)with(nil, nil)for not-found, whileSetDrepupserts ondrep_credentialand updates slot/anchor/active fields atomically. This aligns with themodels.Drepdefinition and simplifies caller logic.Also applies to: 49-79
database/models/account.go (2)
28-35: Account model index changes support multi-record design correctly.The transition from
uniqueIndextoindexonStakingKeyenables multiple account records per stake key over time, while the new indexes onAddedSlot,CertificateID, andActivesupport the query patterns for retrieving current/historical state.
60-67: Excellent documentation of certificate constraint strategy.The comment block clearly articulates the design rationale: composite unique constraints for deregistration types prevent duplicate certificate storage, while simple indexes for other types enable historical tracking across multiple certificates.
database/account.go (1)
22-51: Clean delegation pattern preserves backward compatibility.The refactoring of
GetAccountto delegate toGetAccountWithInactivewithincludeInactive=falsemaintains existing behavior while providing the flexibility to query inactive accounts when needed.database/plugin/metadata/sqlite/account.go (1)
36-48: GetAccount query logic correctly implements most-recent-record retrieval.The conditional filtering by
activeand ordering byadded_slot DESCproperly retrieves the most recent account record, with optional filtering to active accounts only.database/plugin/metadata/store.go (3)
56-60: GetAccount signature correctly reflects includeInactive support.The added
boolparameter forincludeInactiveproperly exposes the capability to query both active and inactive accounts, consistent with the implementation insqlite/account.go.
127-133: SetTransaction signature properly includes deposits for certificate processing.The updated signature adds the transaction index (
uint32) and deposits map (map[int]uint64), enabling proper certificate deposit tracking. The comment clearly documents the deposits map semantics: keys align with certificate slice order and missing keys default to 0.
144-146: Parameter types correctly use lcommon canonical types.The updated parameter types (
lcommon.Address,lcommon.TransactionInput) align with the canonical ledger/common types used throughout the codebase, ensuring consistent type usage across the interface.
| // updateCertificateSlots updates the slot for all certificates associated with a transaction | ||
| func (d *MetadataStoreSqlite) updateCertificateSlots( | ||
| txID uint, | ||
| newSlot uint64, | ||
| txn *gorm.DB, | ||
| ) error { | ||
| if txn == nil { | ||
| txn = d.DB() | ||
| } | ||
|
|
||
| // Update slot in certificates table | ||
| if err := txn.Model(&models.Certificate{}). | ||
| Where("transaction_id = ?", txID). | ||
| Update("slot", newSlot).Error; err != nil { | ||
| return fmt.Errorf("update certificate slot: %w", err) | ||
| } | ||
|
|
||
| // Update added_slot in specialized certificate tables | ||
| // These tables have certificate_id that references the certificates.id | ||
| certTables := []string{ | ||
| "pool_registration", | ||
| "pool_retirement", | ||
| "genesis_key_delegations", | ||
| "move_instantaneous_rewards", | ||
| "resign_committee_cold", | ||
| "auth_committee_hot", | ||
| "registration", | ||
| "deregistration", | ||
| "stake_registration", | ||
| "stake_deregistration", | ||
| "stake_delegation", | ||
| "stake_registration_delegation", | ||
| "vote_delegation", | ||
| "stake_vote_delegation", | ||
| "vote_registration_delegation", | ||
| "stake_vote_registration_delegation", | ||
| "registration_drep", | ||
| "deregistration_drep", | ||
| "update_drep", | ||
| } | ||
|
|
||
| for _, table := range certTables { | ||
| // Update added_slot where certificate_id is in the certificates for this transaction | ||
| if err := txn.Table(table). | ||
| Where("certificate_id IN (SELECT id FROM certificates WHERE transaction_id = ?)", txID). | ||
| Update("added_slot", newSlot).Error; err != nil { | ||
| return fmt.Errorf("update %s added_slot: %w", table, err) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Include LeiosEb in re-inclusion slot updates
updateCertificateSlots updates added_slot for most specialized certificate tables, but the certTables slice omits the leios_ebs table even though models.LeiosEb has an AddedSlot indexed field and participates in the unified certificate mapping.
As a result, when a transaction is re-included in a different block (and blockChanged is true), certificates.slot is updated, but LeiosEb rows keep the old added_slot, leaving inconsistent timing for that certificate type.
Consider adding it:
certTables := []string{
"pool_registration",
"pool_retirement",
"genesis_key_delegations",
"move_instantaneous_rewards",
"resign_committee_cold",
"auth_committee_hot",
"registration",
"deregistration",
+ "leios_ebs",
"stake_registration",
"stake_deregistration",
"stake_delegation",
"stake_registration_delegation",
"vote_delegation",to keep LeiosEb added_slot in sync with other certificates on re-inclusion.
🤖 Prompt for AI Agents
In database/plugin/metadata/sqlite/transaction.go around lines 427 to 475, the
certTables slice used to update added_slot for specialized certificate tables
omits the leios_ebs table, so LeiosEb rows keep old added_slot on transaction
re-inclusion; fix by adding "leios_ebs" to the certTables slice so the loop also
executes the UPDATE for that table and keeps LeiosEb.added_slot in sync with
certificates.slot.
d70daec to
a14738d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ledger/state.go (1)
647-688: Epoch rollover errors are silently ignored due toerrshadowingInside the
needsEpochRolloverblock:var err error ... txn := ls.db.Transaction(true) err := txn.Do(func(txn *database.Txn) error { ... }) ls.Unlock() if err != nil { ... }The
err :=creates a new innererrscoped only to that block; theif err != nilthat follows uses the outererr, which is still its previous value (likelynil). Any failure fromtxn.Dowill therefore be dropped, and the function continues as if the epoch rollover succeeded.Use assignment instead of declaration so the outer
erris updated:- txn := ls.db.Transaction(true) - err := txn.Do(func(txn *database.Txn) error { + txn := ls.db.Transaction(true) + err = txn.Do(func(txn *database.Txn) error { ... }) ls.Unlock() if err != nil { ls.config.Logger.Error( "failed to process epoch rollover: " + err.Error(), ) return }(or equivalently
if err := txn.Do(...); err != nil { ... }with logging and early return kept inside that block).
♻️ Duplicate comments (2)
database/models/auth_committee_hot.go (1)
21-26: Confirm uniqueness migration safety and CertificateID population forAuthCommitteeHot.Changing
ColdCredentialtogorm:"unique"and adding a unique index onCertificateID(uniq_auth_committee_hot_cert) hardens constraints, but both can break AutoMigrate if there is existing data:
- When
CertificateIDis added, all existing rows will have the same default value (typically0), so creating a unique index can fail unless the table is empty or you backfill distinct values first.- The
CertificateIDfield now must be populated consistently from the unified certificates flow (e.g., in thestoreAuthCommitteeHot/certificate persistence path), otherwise new inserts/updates may hit unique‑index violations or store meaningless0values.Please double‑check:
- That any existing
auth_committee_hotdata is either absent or migrated so the new unique index can be created safely.- That the code creating
AuthCommitteeHotrecords always setsCertificateIDto the corresponding unified certificate record ID, and that there is a plan for backfilling older rows if they exist.database/plugin/metadata/sqlite/certs_test.go (1)
36-58: Fix invalidfor i := range 3loops – file won’t compileSeveral tests use
for i := range 3 { ... }, butrangecannot be used on an integer literal in Go; this is a compile-time error. You need standard counted loops.Update all these loops (pool, stake, DRep, genesis tests, etc.) to:
- for i := range 3 { + for i := 0; i < 3; i++ { ... }Apply this pattern to every
for i := range 3occurrence in this file.Also applies to: 73-89, 118-135, 228-243, 259-267, 285-292, 329-368
🧹 Nitpick comments (5)
database/plugin/metadata/sqlite/account_test.go (1)
28-126: Good coverage of account upsert and inactive behavior; consider factoring setup.This test exercises the full lifecycle (create, update, deactivate, includeInactive flag) and even checks the underlying
models.Accountrow, which is great for catching regressions in upsert logic and theincludeInactivesemantics.If you add more metadata tests, consider extracting the DB/metadataStore setup (
database.New+ cast to*sqlite.MetadataStoreSqlite) into a small helper to reduce duplication across tests, but it’s not required here.database/models/certificate.go (1)
25-139:CertificateTypeValue/Scan pair is robust; consider minor input/range tweaks.The
CertificateTypeimplementation correctly:
- Stores as an
int64viaValue()(with the G115 suppression justified by the small constant range), and- Handles common database representations in
Scan(int64,int,uint,uint64with overflow checks, and[]bytedecimal strings), plus a sanity range check.Two optional refinements you could consider (non‑blocking):
- Accept
stringvalues inScanas well, in case a driver hands back text instead of[]byte.- Replace the hard‑coded
val > 1000bound with something derived from the maximum knownlcommon.CertificateType*value (or simply drop the upper bound and rely on the set of valid constants elsewhere), to avoid surprises if new certificate types are added with larger numeric codes.database/plugin/metadata/sqlite/database.go (1)
36-49: Background vacuum/cleanup lifecycle is safe; mirror WG pattern when cleanup gains workThe vacuum path now correctly guards on
dataDir == "" || closed, tracks in‑flight runs withvacuumWG, and blocksClose()until they finish, which addresses earlier races with VACUUM on a closed DB. The certificate‑cleanup timer is also properly gated byclosedand stopped inClose().Once
runCertificateCleanupis implemented with real work, consider mirroring thevacuumWGpattern there too so any long‑running cleanup cannot outlive the store or race with shutdown.Also applies to: 127-141, 144-187, 189-241, 248-264
database/plugin/metadata/sqlite/certs.go (2)
64-244: Unified storeCertificate correctly populates per‑type models and deposit fieldsThe new
storeCertificateimplementation looks solid:
- Creates a unified
models.Certificaterow up front, then per‑type rows withCertificateIDset to the unified ID, and finally backfillsunifiedCert.CertificateIDwith the per‑type primary key. That symmetric linkage matches howupdateCertificateSlotsandDeleteCertificateexpect to join.- Pool registration now restores the full payload (margin, metadata URL/hash, owners, relays, pledge, cost, deposit, slot) and attaches owners/relays to both the registration and pool via
PoolRegistrationID/PoolID.- MIR and Leios certificates now persist all semantic data via JSON blobs and identifiers, instead of just a slot.
- DRep and committee certs once again carry anchors where present, and all specialized models receive a non‑zero
CertificateID, avoiding the previous uniqueness and orphaning issues.- Deregistration certificates now use
c.Amountinstead of the deposit map, which correctly reflects the on‑chain deregistration amount; the>= 0guard is defensive but could be simplified once the underlying type guarantees non‑negative values.The follow‑up
updateAccountStatecentralizes stake/vote account updates and looks consistent with Conway semantics for staking key activation, (pool, drep) delegation, and legacy registration/deregistration.Also applies to: 283-513
655-811: DeleteCertificate matches the new CertificateID wiring; consider coverage guard for future types
DeleteCertificatenow:
- Locates the unified
models.Certificateby(transaction_id, cert_index).- Deletes the corresponding specialized row by filtering on
certificate_id = cert.ID, which is consistent with the new “specialized.CertificateID → certificates.id” mapping established instoreCertificate.- Then removes the unified certificate row itself, cleanly tearing down both sides for all explicitly handled
CertificateType*variants.This aligns with the new schema and should avoid the earlier orphaning bugs. As a future‑proofing measure, you might add a small test or assert that every
models.CertificateType*is covered by the switch so new certificate kinds can’t silently fall into the default branch and leak specialized rows.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (43)
database/account.go(1 hunks)database/certificate.go(1 hunks)database/database_test.go(2 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/blob/aws/database.go(1 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/account_test.go(1 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(3 hunks)database/plugin/metadata/sqlite/pool.go(1 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(3 hunks)database/plugin/metadata/store.go(3 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(4 hunks)go.mod(4 hunks)internal/config/config.go(2 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(11 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (2)
- database/drep.go
- database/models/certs.go
🚧 Files skipped from review as they are similar to previous changes (7)
- database/models/asset.go
- internal/config/config_test.go
- database/models/genesis_key_delegation.go
- database/types/types_test.go
- database/models/drep.go
- database/models/account_test.go
- database/models/account.go
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/utxo.godatabase/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/store.godatabase/models/utxo.godatabase/models/transaction.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/utxo.godatabase/plugin/metadata/sqlite/utxo.goledger/delta.godatabase/plugin/metadata/sqlite/transaction.godatabase/database_test.godatabase/plugin/metadata/store.godatabase/transaction.godatabase/models/utxo.godatabase/plugin/metadata/sqlite/database.godatabase/models/transaction.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/database_test.godatabase/plugin/metadata/store.godatabase/models/utxo.goledger/state.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/plugin/metadata/sqlite/transaction.godatabase/database_test.godatabase/plugin/metadata/store.godatabase/models/utxo.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/database_test.goledger/state.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/utxo.godatabase/plugin/metadata/store.goledger/state.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/utxo.godatabase/database_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/utxo.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/utxo.godatabase/models/utxo.godatabase/models/transaction.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/database_test.godatabase/plugin/metadata/store.godatabase/models/utxo.goledger/state.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.
Applied to files:
ledger/state.go
🧬 Code graph analysis (25)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/pool.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
ledger/view.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
database/models/models.go (3)
database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)
database/plugin/metadata/sqlite/utxo.go (1)
database/models/utxo.go (1)
Utxo(24-40)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/certs_test.go (11)
database/plugin/metadata/sqlite/database.go (1)
New(52-125)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(66-68)database/models/account.go (7)
StakeRegistration(115-121)StakeRegistration(123-125)Deregistration(68-74)Deregistration(76-78)StakeDeregistration(104-109)StakeDeregistration(111-113)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(79-107)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)
database/models/leios_eb.go (1)
database/types/types.go (1)
Uint64(66-68)
database/models/certificate_test.go (1)
database/models/certificate.go (3)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeStakeDeregistration(35-37)
database/models/auth_committee_hot.go (1)
database/types/types.go (1)
Uint64(66-68)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/plugin/metadata/sqlite/transaction.go (5)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-49)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/utxo.go (1)
UtxoLedgerToModel(50-79)database/models/certificate.go (9)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(146-154)Certificate(157-159)
database/database_test.go (5)
database/plugin/metadata/sqlite/database.go (1)
New(52-125)database/database.go (1)
New(118-154)database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/plugin/metadata/sqlite/drep.go (1)
database/models/drep.go (1)
Drep(21-29)
database/models/move_instantaneous_rewards.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
database/plugin/metadata/sqlite/certs.go (11)
database/models/certificate.go (4)
CertificateTypeStakeRegistration(32-34)Certificate(146-154)Certificate(157-159)CertificateType(28-28)database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-49)database/models/account.go (18)
StakeRegistration(115-121)StakeRegistration(123-125)StakeDeregistration(104-109)StakeDeregistration(111-113)StakeDelegation(92-98)StakeDelegation(100-102)Registration(80-86)Registration(88-90)Deregistration(68-74)Deregistration(76-78)StakeRegistrationDelegation(127-134)StakeRegistrationDelegation(136-138)VoteDelegation(167-173)VoteDelegation(175-177)StakeVoteDelegation(140-147)StakeVoteDelegation(149-151)VoteRegistrationDelegation(179-186)VoteRegistrationDelegation(188-190)database/types/types.go (2)
Uint64(66-68)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(269-277)
internal/config/config.go (1)
internal/node/load.go (3)
LedgerCatchupTimeout(36-36)LedgerCatchupPollInterval(38-38)LedgerCatchupStallTimeout(40-40)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/account.go (1)
database/models/account.go (1)
Account(27-35)
database/plugin/metadata/sqlite/account_test.go (5)
database/plugin/metadata/sqlite/database.go (2)
New(52-125)MetadataStoreSqlite(39-49)database/models/account.go (3)
Account(27-35)StakeRegistration(115-121)StakeRegistration(123-125)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)
database/account.go (3)
database/models/account.go (1)
Account(27-35)database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/models/transaction.go (1)
database/models/utxo.go (1)
Utxo(24-40)
⏰ 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). (3)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
- GitHub Check: docker (ubuntu-latest, amd64)
🔇 Additional comments (40)
database/plugin/blob/aws/database.go (1)
69-71: LGTM!The multi-line formatting of the error message improves readability. No functional change.
internal/config/config.go (3)
22-22: LGTM!The
timeimport is necessary for the new duration-based configuration fields.
46-68: LGTM!The new duration fields are properly integrated with appropriate YAML tags and consistent formatting.
70-92: LGTM!The default values align with the constants defined in
internal/node/load.goand follow a sensible progression (5s poll interval, 5m initial timeout, 10m stall timeout).internal/node/load.go (2)
101-104: LGTM!The updated error message accurately reflects the asynchronous ledger startup pattern.
197-244: Consider clarifying the stall detection semantics.The current stall detection resets whenever any progress is made (line 223-226). This means extremely slow progress (e.g., 1 slot per 9 minutes) would never trigger the stall timeout. If this is the intended behavior (detect complete stalls only, not slow progress), the implementation is correct. Otherwise, consider whether a minimum progress rate should also be enforced.
The nolint comment on line 197 mentions "for-select pattern" but this is actually an infinite loop that exits via return statements within the select case. Consider updating the comment for clarity:
- for { //nolint:staticcheck // Correct use of for-select pattern for ticker-based polling + for { //nolint:staticcheck // Infinite loop exits via return when catchup completes or stallsledger/view.go (1)
104-112: LGTM! Type conversions align with unified certificate persistence.The type conversions for CertType, Pledge, and Cost properly adapt the Pool model fields to the ledger certificate interface. The cast to
uintfor CertType and accessing.Valfor Pledge/Cost are consistent with the broader refactor to typed wrappers.database/account.go (1)
22-51: LGTM! Clean delegation pattern for account retrieval.The new
GetAccountWithInactivemethod adds flexibility while maintaining backward compatibility. The delegation inGetAccountwithincludeInactive=falsepreserves existing behavior for current callers.database/models/resign_committee_cold.go (1)
17-30: LGTM! Model properly integrated with unified certificate system.The changes correctly implement the certificate persistence pattern:
- Unique constraint on
ColdCredentialprevents duplicate resignationsCertificateIDwith unique index links to the unified certificate mappingAddedSlottype migration totypes.Uint64is consistent across modelsdatabase/models/models.go (1)
29-31: LGTM! New certificate models registered for migration.The three new certificate models (
GenesisKeyDelegation,LeiosEb,MoveInstantaneousRewards) are correctly added to the migration list, enabling proper schema creation for the expanded certificate type coverage.database/database_test.go (1)
76-176: LGTM! Comprehensive test coverage for IsCollateralReturn flag.This test thoroughly validates that the
IsCollateralReturnflag is preserved through:
- Database persistence and metadata retrieval
- UTXO copying operations
The test structure is well-organized with clear setup, execution, and assertions.
go.mod (2)
14-14: LGTM! Dependency update to support new certificate types.The update to
gouroboros v0.140.0is necessary to support the new certificate types (Genesis, Leios, MIR) introduced in this PR.
24-24: LGTM! Standard testing library addition.Adding
testify v1.11.1provides assertion utilities for the new test suite, particularly for the comprehensive certificate tests.database/models/move_instantaneous_rewards.go (1)
21-34: LGTM! MIR certificate model properly structured.The model correctly implements the certificate persistence pattern:
CertificateIDwith unique index ensures 1:1 mapping to the unified certificate tableSourceandAddedSlotare indexed for efficient lookupsAddedSlotusestypes.Uint64consistent with other certificate modelsRewardDataas JSON blob accommodates variable reward structuresdatabase/utxo.go (3)
24-24: LGTM! Consistent import alias for ledger common types.The change from
ledgertolcommonalias improves clarity and consistency with other files in the codebase.
102-112: LGTM! Signature updated to use lcommon types.The
UtxoConsumesignature correctly useslcommon.TransactionInput, aligning with the standardized type usage across the codebase.
114-147: LGTM! Complete UTXO mapping includes IsCollateralReturn.The
UtxosByAddressmethod properly:
- Uses
lcommon.Addressparameter type- Maps all UTXO fields including the new
IsCollateralReturnflag (line 139)- Maintains consistency with the test coverage in
database_test.godatabase/models/leios_eb.go (1)
21-37: LGTM! Well-structured Leios certificate model.The LeiosEb model follows established patterns with appropriate unique indexes: the composite index on ElectionID+EndorserBlockHash prevents duplicate endorser blocks, and the separate CertificateID unique index ensures proper one-to-one mapping with the certificates table.
database/plugin/metadata/sqlite/pool.go (1)
82-90: LGTM! Correctly adapted for types.Uint64 struct.The changes properly access the
Valfield of thetypes.Uint64struct for Pledge and Cost, and correctly cast CertType to uint for the certificate construction.database/models/transaction.go (1)
26-29: LGTM! Correct filtering of outputs and collateral return.The
whereclauses now properly use the snake_case column nameis_collateral_returnto discriminate between regular outputs and collateral return UTXOs, addressing the previous critical issue.database/certificate.go (1)
21-46: LGTM! Clean autonomous transaction pattern.The lifecycle comment clearly documents the new persistence model, and the autonomous transaction creation in the Get methods provides convenient read-only access without requiring explicit transaction management from callers.
database/models/certificate_test.go (2)
25-166: LGTM! Comprehensive test coverage for CertificateType.The test suite thoroughly covers Value/Scan behavior with various input types, error cases, and interface conformance verification. Well-structured and follows testing best practices.
124-135: Range validation in CertificateType.Scan() is correctly implemented and consistent with test expectations.The verification confirms that
Scan()at line 134 ofdatabase/models/certificate.govalidates the rangeval < 0 || val > 1000, which matches the test cases expecting errors for 1001 and uint overflow conditions. The implementation handles both range validation and overflow checks as intended.database/types/types.go (1)
65-101: LGTM! Systematic Uint64 refactor from primitive to struct.The change from
type Uint64 uint64tostruct { Val uint64 }is systematic and complete across the PR. The updated Value/Scan methods properly handle the Val field and maintain string-based database serialization. All usage sites in the PR have been updated to access the Val field.database/models/pool.go (1)
44-98: LGTM! Properly configured unique indexes for pool certificates.The unique index configuration correctly allows pool re-registrations (no unique constraint on PoolKeyHash alone) while preventing duplicate retirements at the same epoch via the composite
(PoolKeyHash, Epoch)unique index. The separate CertificateID unique indexes ensure proper one-to-one certificate mapping.database/plugin/metadata/sqlite/utxo.go (1)
96-136: LGTM! Clearer and more maintainable UTXO address query.The refactored
GetUtxosByAddressuses explicit conditionals for different key combinations, making the logic easier to understand and maintain. The early return for addresses with no valid keys avoids unnecessary queries.database/plugin/metadata/sqlite/account_test.go (2)
128-184: TransactionalSetAccountbehavior test looks solid.The pattern of doing
Begin(), callingSetAccountwith the transaction, asserting visibility viaGetAccountin the same txn, and then rolling back to verify nothing is persisted through the default handle cleanly validates thatSetAccountrespects the passed*gorm.DBwithout auto‑committing.
186-284:DeleteCertificatetest nicely validates unified ↔ specialized mapping and cascade.Manually wiring
models.Transaction, a unifiedmodels.Certificate, and the correspondingmodels.StakeRegistration, then:
- Updating
unifiedCert.CertificateIDto the specialized record ID,- Calling
DeleteCertificate(txID, certIndex, txn), and- Asserting both the certificate and stake‑registration counts drop to zero,
gives strong confidence that the delete path correctly traverses the unified certificate mapping and cleans up specialized rows, while also confirming that deleting a non‑existent certificate is a no‑op.
database/plugin/metadata/store.go (1)
56-60: Interface changes for accounts, transactions, and UTXOs look consistent with ledger/common usage.
- Extending
GetAccountwithincludeInactivematches how you’re using it in the new tests and keeps the default (includeInactive=false) behavior intact.- The
SetTransactionsignature (point,lcommon.Transaction,idx,map[int]uint64deposits,*gorm.DB) lines up with the database wrapper and the ledger/delta deposits flow.- Switching
GetUtxosByAddressandSetUtxoDeletedAtSlottolcommon.Address/lcommon.TransactionInputmatches the prior guidance to standardize on ledger/common types in this layer, avoiding alias/interface mismatch issues. Based on learnings.Just ensure all concrete implementations (currently sqlite) and any mock/test implementations have been updated to these exact signatures;
go buildwill catch anything that was missed.Also applies to: 127-147
database/transaction.go (1)
22-63: Deferred commit/rollback logic looks correct; verify UTXO writes for invalid transactions.The refactor to a named
errreturn plus the deferred closure that callsRollbackon error andCommiton success fixes the previous “commit after rollback” risk for internally created transactions and keeps the API straightforward for callers that pass an existing*Txn.One behavior worth double‑checking: the
for _, utxo := range tx.Produced()loop runs unconditionally, so any transaction that reaches this method will have its produced outputs written to the blob store, regardless of validity. If callers can pass invalid transactions here (as suggested by the invalid‑tx path in the ledger/delta flow), please confirm that:
- Either
tx.Produced()already accounts for validity (i.e., returns only outputs that should be persisted), or- Consumers do not treat the blob store as the canonical ledger UTXO set.
If invalid transactions should not have their outputs written, consider guarding the loop with a validity check or documenting the intended semantics in the function comment.
database/models/certificate.go (1)
141-164: UnifiedCertificatemodel and indexing strategy look appropriate.The
Certificatestruct cleanly models:
- A unique
(TransactionID, CertIndex)pair withuniq_tx_cert, and- A polymorphic
CertificateIDpointing to the specialized per‑type model, whileTransactionIDhasOnDelete:CASCADEto clear certificates when a transaction is removed.This matches the unified certificate‑mapping design and should make it straightforward to join from transactions to certificates and then to the specialized tables without relying on complex joins or ambiguous keys.
ledger/certs.go (1)
23-59: Certificate deposit calculator is correct and side‑effect freeThe helper now consistently records deposits for every certificate index (including legitimate zero‑deposit entries) and wraps per‑index failures clearly. This matches the new
SetTransactiondeposit usage and avoids the prior “missing vs zero” ambiguity.database/models/utxo.go (1)
25-39: UTxO model and mapper align with new collateral‑return semanticsSwitching slot/amount to
types.Uint64and introducingIsCollateralReturnwith an index matches the updatedTransactionassociations and the new collateral‑return handling inSetTransaction. The mapper correctly setsAddedSlotandAmountviatypes.Uint64.Also applies to: 58-59
database/plugin/metadata/sqlite/transaction.go (2)
52-145: SetTransaction idempotency and deposit handling look correctThe reordered flow—checking existence by hash before upsert, reloading the transaction ID after
ON CONFLICT, and skipping UTXO/certificate processing on replays—gives a sane idempotent behavior for reprocessing and re‑inclusion. The certificate section correctly:
- Skips certs on invalid transactions and when none are present.
- Requires a deposits map only for new transactions with certificates.
- Treats missing entries as an error only for the known deposit‑carrying certificate types, while defaulting others to 0.
Assuming callers always invoke this inside a DB transaction and roll back on error, the write‑then‑validate pattern remains safe.
Also applies to: 343-375
427-477: Certificate slot update on re‑inclusion is consistent with unified mapping
updateCertificateSlotsupdatescertificates.slotand thenadded_slotacross all specialized tables, includingleios_eb, by joining oncertificate_id→certificates.id. This matches the new symmetricCertificate/per‑typeCertificateIDlinkage and should keep timing fields coherent when a transaction is re‑included in a different block.database/plugin/metadata/sqlite/drep.go (1)
26-47: DRep lookup/upsert flow looks consistent with the model schemaUsing
drep_credentialas the key with a pointer-returningGetDrep(nil on not‑found) and a singleSetDrepupsert viaON CONFLICTondrep_credentialmatchesmodels.Drepand gives clear "latest state wins" semantics for URL/hash/active/slot. I don’t see correctness issues here.Also applies to: 50-79
ledger/delta.go (1)
25-29: Certificate deposit handling in delta.apply aligns with validity semanticsStoring precomputed deposits on
TransactionRecordand, inapply, (a) forcing invalid transactions to usedeposits == nilwhile still consuming UTxOs, and (b) only calculating deposits for valid cert‑bearing transactions (or using precomputed ones) matches the intended “no deposits for invalid txs” rule and avoids recomputation where the caller already provided a map. This logic looks correct and side‑effect free.Also applies to: 37-47, 61-88, 90-99
ledger/state.go (1)
885-936: Precomputing certificate deposits before validation is consistent with the new delta flowThe new
ledgerProcessBlocklogic calculatesdepositsonce per cert‑bearing transaction and threads them viadelta.addTransactionWithDeposits;delta.applythen uses these only for valid transactions and passesnilfor invalid ones. This avoids redundant deposit calculations and ensures the store layer always gets a coherent per‑certificate deposit map. The behavior here looks correct.database/plugin/metadata/sqlite/account.go (2)
26-49: GetAccount’sincludeInactiveflag and ordering look reasonableThe refactored
GetAccountcleanly supports both “only active” and “include inactive” queries via theincludeInactiveflag and centralizes the logic aroundstaking_key, returningnilon not‑found. The extraORDER BY added_slot DESCis consistent with “latest state” semantics and harmless even with the uniquestaking_keyconstraint.
52-82: SetAccount’s find-or-create + bulk update pattern is appropriateUsing
FirstOrCreatekeyed byStakingKeyand then a singleUpdatescall forpool,drep,added_slot, andactivegives a clear account-centric upsert that respects an optional transaction DB handle. This should play nicely with the unified certificate/account flows; I don’t see correctness or race issues here.
a14738d to
93d81a8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
database/types/types_test.go (1)
75-81: Fix incorrect test assertion.Line 79 compares
tmpScanner(the scanned struct) againsttestDef.expectedValue(a string like "123"), which will always fail. It should compare againsttestDef.origValueto verify round-trip fidelity.Apply this diff:
if !reflect.DeepEqual(tmpScanner, testDef.origValue) { t.Fatalf( "did not get expected value after Scan(): got %#v, expected %#v", tmpScanner, - testDef.expectedValue, + testDef.origValue, ) }
♻️ Duplicate comments (3)
database/plugin/metadata/sqlite/certs_test.go (1)
37-89: Fixfor i := range 3loops – file will not compileAll the counted loops using
for i := range 3are invalid Go and will prevent this test file from compiling (e.g., inTestPoolCertificateDuplicatePrevention,TestStakeCertificateDuplicatePrevention,TestDrepCertificateReRegistration,TestGenesisKeyDelegationCertificateIdempotent, etc.).Replace each with a standard counted loop:
- for i := range 3 { + for i := 0; i < 3; i++ {Apply consistently to every occurrence in this file.
Also applies to: 119-179, 229-243, 259-291, 329-368
internal/node/load.go (1)
34-41: Clean up legacy timeout constant and use the shared default
LedgerCatchupTimeoutis marked deprecated and isn’t referenced; plus the stall timeout fallback still hardcodes10 * time.Minuteeven thoughLedgerCatchupStallTimeoutexists. It would be clearer to drop the unused legacy constant and useLedgerCatchupStallTimeoutfor the fallback to keep all defaults in one place.Also applies to: 186-189
ledger/state.go (1)
635-643: Still a race: closingreadChainResultChfrom the consumer can panic if producer sends concurrently
ledgerProcessBlocksclosesreadChainResultChonls.ctx.Done(), butledgerReadChainstill owns sending to this channel and only checksls.ctx.Done()at the top of the outer loop. There is a window where:
ledgerProcessBlockssees context cancellation, closesreadChainResultCh, and returns, whileledgerReadChainhas already passed itsselectonls.ctx.Done()and later executesresultCh <- result.This will panic with “send on closed channel”. Only one side should ever close the channel—typically the producer—or you can avoid closing it entirely and rely solely on
ctx.Done()for shutdown.Suggestion: remove
close(readChainResultCh)from the consumer and either:
- Don’t close the channel at all (both goroutines exit on
ctx.Done()), or- Move
defer close(resultCh)intoledgerReadChainand let the consumer stop whenok == falseon receive.#!/bin/bash # Verify there is no remaining consumer-side close on readChainResultCh rg -n "readChainResultCh" ledger/state.go -n -C3Also applies to: 655-661, 696-717
🧹 Nitpick comments (6)
database/plugin/metadata/sqlite/pool.go (1)
53-75: Pool registration mapping looks correct; confirm pool key hashing
- Mapping of
Pledge/Costvia.Valfromtypes.Uint64and settingCertTypefromlcommon.CertificateTypePoolRegistrationare consistent with the updated models.- One thing to double‑check:
GetPoolRegistrationsfilters onpool_key_hash = lcommon.Blake2b224(pkh).Bytes(), whereasGetPoolusespkh.Bytes()directly. Iflcommon.PoolKeyHashalready represents a Blake2b224 hash, this could be double‑hashing and prevent lookups from matching persisted rows.Please confirm whether
pkhis pre‑hashed or raw here and alignGetPool/GetPoolRegistrationsaccordingly if needed.Also applies to: 80-95
database/database_test.go (1)
76-176: Solid verification of IsCollateralReturn propagation; consider using db.UtxosByAddress directlyThis test does a good job of asserting that
GetUtxosByAddressreturns a row withIsCollateralReturnset and that the flag survives a struct‑to‑struct copy.If you want even stronger coverage against future refactors, you could also exercise the exported
db.UtxosByAddresshelper (if available) instead of manually reconstructingcopiedUtxo, so that the test breaks if the database‑level mapping ever forgets to copyIsCollateralReturn.database/plugin/metadata/sqlite/utxo.go (1)
21-24: GetUtxosByAddress query is structurally sound; clarify OR vs AND semantics
- The refactor to
lcommon.Address, explicitdeleted_slot = 0, and filtering onpayment_key/staking_keycolumns matches themodels.Utxoschema and resolves the previouspayment_addrcolumn mismatch.- The zeroHash/
PaymentKeyHash()–StakeKeyHash()checks look reasonable, assuming the address implementation returns a zero Blake2b224 when a credential is absent.One thing to double‑check: when both credentials are present you currently query with:
query = query.Where("payment_key = ? OR staking_key = ?", pkh.Bytes(), skh.Bytes())If the intent is “UTxOs belonging to this exact address,” an
ANDon both columns would be stricter and avoid picking up UTxOs that share only the payment key or only the staking key if those are ever reused with different counterparts. If, instead, you explicitly want “either credential matches,” the current behavior is correct but worth documenting.
SetUtxoDeletedAtSlot’s switch tolcommon.TransactionInputis consistent with the rest of the ledger‑facing API.Also applies to: 96-137, 230-252
database/plugin/metadata/sqlite/certs.go (1)
64-513: Unified certificate storage is much healthier; be clear about DeleteCertificate vs account stateThe new
storeCertificate/updateAccountState/DeleteCertificatetrio looks substantially better:
- Per-type models now set
CertificateIDto the unified row ID, and the unifiedCertificate.CertificateIDis updated with the specialized PK, so the polymorphic mapping and uniqueness constraints line up.- Pool registrations, MIR, Leios, and DRep/committee certs now carry their full semantic payload (margin, metadata, owners/relays, reward maps, voter/signature data, anchors, etc.), avoiding the earlier data-loss regressions.
- Deregistration correctly records the actual on-chain amount rather than the (always-zero) deposit, and DRep/vote certs now persist their DRep credentials.
DeleteCertificateconsistently deletes specialized rows bycertificate_id = cert.IDand then removes the unified record, which matches the new linkage model. Just be aware that it intentionally doesn’t touchaccountstate; if you ever intend to drive rollbacks via DeleteCertificate, you’ll likely want a complementary mechanism to recompute or adjustSetAccountoutputs as well.Overall, this file is in good shape.
Also applies to: 515-653, 655-811
database/plugin/metadata/sqlite/database.go (1)
36-49: Metadata store background lifecycle looks solid; coordinate future cert cleanup with CloseThe refactor of
MetadataStoreSqlite’s background work is well thought out:
runVacuumnow respectsdataDir == ""andclosed, and usesvacuumWGsoClosecan safely wait for in-flight vacuums before closing the DB.- Daily vacuum and the new 6‑hour certificate cleanup scheduler both honor the
closedflag and avoid rescheduling once shutdown begins.Closecorrectly stops both timers, marks the store closed, waits onvacuumWG, and only then closes the underlying DB handle.Given
runCertificateCleanupis currently a no-op with logging, this is safe. When you later introduce real cleanup logic that hits the DB, it would be worth mirroring thevacuumWGpattern (or a dedicated WaitGroup) around that work as well soClosecan wait for cleanup tasks just as it does for vacuum.Also applies to: 127-141, 144-159, 189-241, 248-264
database/plugin/metadata/sqlite/account.go (1)
36-41: Remove unnecessaryOrder("added_slot DESC")in GetAccount function.The
Accountmodel has auniqueIndexconstraint onStakingKey, so only one record can exist per staking key. Ordering byadded_slot DESCbefore callingFirst()adds unnecessary query overhead with no semantic effect.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (44)
blockfetch.go(1 hunks)database/account.go(1 hunks)database/certificate.go(1 hunks)database/database_test.go(2 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/blob/aws/database.go(1 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/account_test.go(1 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(3 hunks)database/plugin/metadata/sqlite/pool.go(1 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(3 hunks)database/plugin/metadata/store.go(3 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(4 hunks)go.mod(4 hunks)internal/config/config.go(2 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(11 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (2)
- database/drep.go
- database/models/certs.go
🚧 Files skipped from review as they are similar to previous changes (12)
- internal/config/config_test.go
- database/models/certificate_test.go
- database/models/asset.go
- database/models/drep.go
- database/models/certificate.go
- database/plugin/blob/aws/database.go
- database/utxo.go
- database/models/auth_committee_hot.go
- go.mod
- database/models/genesis_key_delegation.go
- ledger/view.go
- database/models/models.go
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/transaction.godatabase/models/utxo.godatabase/plugin/metadata/sqlite/transaction.godatabase/database_test.godatabase/plugin/metadata/sqlite/utxo.goledger/delta.godatabase/models/transaction.godatabase/plugin/metadata/sqlite/database.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/utxo.godatabase/models/transaction.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/models/utxo.godatabase/database_test.goledger/state.godatabase/plugin/metadata/sqlite/certs_test.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/transaction.godatabase/database_test.godatabase/plugin/metadata/sqlite/certs_test.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/models/utxo.godatabase/database_test.goledger/state.godatabase/plugin/metadata/sqlite/certs_test.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/utxo.godatabase/models/transaction.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/database_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/database_test.goledger/state.godatabase/plugin/metadata/sqlite/certs_test.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
ledger/state.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.
Applied to files:
ledger/state.go
🧬 Code graph analysis (24)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(269-277)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/pool.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
internal/config/config.go (2)
internal/node/load.go (3)
LedgerCatchupTimeout(36-36)LedgerCatchupPollInterval(38-38)LedgerCatchupStallTimeout(40-40)config.go (1)
Config(33-55)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(79-107)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)
database/models/move_instantaneous_rewards.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/account_test.go (6)
database/plugin/metadata/sqlite/database.go (2)
New(52-125)MetadataStoreSqlite(39-49)database/plugin/metadata/store.go (1)
New(151-157)database/models/account.go (3)
Account(27-35)StakeRegistration(115-121)StakeRegistration(123-125)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/transaction.go (5)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-49)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/utxo.go (1)
UtxoLedgerToModel(50-79)database/models/certificate.go (10)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeRegistrationDrep(74-76)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(146-154)Certificate(157-159)
database/database_test.go (3)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/plugin/metadata/sqlite/utxo.go (1)
database/models/utxo.go (1)
Utxo(24-40)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/plugin/metadata/sqlite/drep.go (1)
database/models/drep.go (1)
Drep(21-29)
ledger/state.go (1)
database/plugin/log.go (1)
Logger(18-27)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(66-68)
database/models/transaction.go (2)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)
database/types/types_test.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/certs.go (11)
database/models/certificate.go (7)
CertificateTypeStakeRegistration(32-34)Certificate(146-154)Certificate(157-159)CertificateType(28-28)CertificateTypeStakeDeregistration(35-37)CertificateTypeMoveInstantaneousRewards(86-88)CertificateTypeLeiosEb(89-91)database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-49)database/models/account.go (20)
StakeRegistration(115-121)StakeRegistration(123-125)StakeDeregistration(104-109)StakeDeregistration(111-113)StakeDelegation(92-98)StakeDelegation(100-102)Registration(80-86)Registration(88-90)Deregistration(68-74)Deregistration(76-78)StakeRegistrationDelegation(127-134)StakeRegistrationDelegation(136-138)VoteDelegation(167-173)VoteDelegation(175-177)StakeVoteDelegation(140-147)StakeVoteDelegation(149-151)VoteRegistrationDelegation(179-186)VoteRegistrationDelegation(188-190)StakeVoteRegistrationDelegation(153-161)StakeVoteRegistrationDelegation(163-165)database/types/types.go (2)
Uint64(66-68)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
database/models/leios_eb.go (1)
database/types/types.go (1)
Uint64(66-68)
database/account.go (3)
database/models/account.go (1)
Account(27-35)database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/certs_test.go (11)
database/plugin/metadata/sqlite/database.go (1)
New(52-125)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(66-68)database/models/account.go (7)
StakeRegistration(115-121)StakeRegistration(123-125)Deregistration(68-74)Deregistration(76-78)StakeDeregistration(104-109)StakeDeregistration(111-113)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (2)
Certificate(146-154)Certificate(157-159)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
database/models/pool.go (1)
database/models/epoch.go (2)
Epoch(17-27)Epoch(29-31)
database/plugin/metadata/sqlite/account.go (1)
database/models/account.go (1)
Account(27-35)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
⏰ 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). (4)
- GitHub Check: nilaway
- GitHub Check: go-test (1.24.x, macos-latest)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: Analyze (go)
🔇 Additional comments (34)
database/models/account_test.go (1)
26-96: LGTM!The test struct reorganization improves clarity by grouping expected validation fields (
wantErrMsg,expectedHRP) before the input field (stakingKey). The test coverage is comprehensive, including edge cases for empty, nil, single-byte, and boundary values.database/models/move_instantaneous_rewards.go (1)
21-29: LGTM!The previous over-restrictive composite unique index has been correctly addressed. The model now uses a unique constraint only on
CertificateID(line 25), whileSourceandAddedSlothave non-unique indexes for query performance. This allows multiple MIR certificates with the same source/slot combination to be stored independently.database/certificate.go (2)
21-23: LGTM!The comment clearly documents the architectural decision to handle certificate persistence through
SetTransaction, aligning with the unified certificate persistence system described in the PR objectives.
30-33: LGTM!The auto-transaction creation pattern is correct and consistent with the pattern used in
GetStakeRegistrationsbelow. The deferredCommit()with error suppression is appropriate for read-only transactions.database/types/types.go (3)
40-63: LGTM!The enhanced
Scan()method properly handles nil values (preserving zero-value Rat) and supports multiple input types (string, []byte, int64) with appropriate error messages. The early return for int64 (line 51-52) is an efficient optimization.
65-68: Approved: Uint64 type refactor from alias to struct.Changing
Uint64from a type alias to a struct wrapper is a breaking change, but it enables proper nil/NULL handling in database contexts and aligns with how GORM handles custom types. The AI summary confirms this change has been propagated throughout the codebase.
74-101: Assumption verified: Application-level guarantee is sound but relies on upstream ledger validation.The int64→uint64 cast at line 87 assumes non-negative values because
Uint64fields (AddedSlot, Amount, DepositAmount, etc.) are always initialized with data from the blockchain ledger layer (e.g.,types.Uint64{Val: utxo.Output.Amount()},types.Uint64{Val: c.Pledge}). These values are pre-validated by the ledger before reaching the database layer. However, the Scan() method itself contains no defensive validation. The approach is acceptable but could be more explicit: either add a runtime check for negative values or document that the safety guarantee depends entirely on trustworthy upstream validation.database/types/types_test.go (1)
82-92: LGTM!The additional test path for scanning
[]byteinput ensures theScan()method correctly handles both string and byte slice representations, which is important for database driver compatibility.database/models/pool.go (1)
44-60: LGTM!The
PoolRegistrationmodel correctly addresses the past review concern. The unique constraint is now only onCertificateID(line 55), whilePoolKeyHashuses a non-unique index (line 48), allowing pools to submit multiple registration certificates over time to update their parameters.database/models/leios_eb.go (1)
21-32: Code constraint is correct; no issues found.The web search confirms that the Leios protocol guarantees exactly one certificate per (election-ID + endorser-block hash) pair. Leios produces a single certificate for an endorser block once the voting quorum is reached, which directly aligns with the composite unique index in the code. The constraint is necessary and correctly specified.
ledger/delta.go (1)
25-35: Deposit wiring and invalid‑tx behavior look correct now
TransactionRecord.DepositsplusaddTransactionWithDepositsgive a clear way to thread precomputed deposits intoapply, and the precedence over on‑the‑flyCalculateCertificateDepositsis explicit.- The invalid‑transaction branch now skips deposit calculation and passes
nilintoSetTransaction, addressing the prior issue of persisting deposits for script‑invalid txs.- For valid txs, you guarantee a non‑nil deposits map whenever
Certificates()is non‑empty (either viatr.DepositsorCalculateCertificateDeposits), which matches the semantics exercised in the new SetTransaction tests.No changes requested here.
Also applies to: 37-47, 54-88, 90-101
database/plugin/metadata/sqlite/account_test.go (3)
28-126: Good coverage of SetAccount lifecycle (active/inactive + includeInactive)This test exercises first‑time creation, update/inactivation, exclusion from the default GetAccount path, and retrieval with
includeInactive=true, plus verifying the same DB row is reused. The directmodels.Accountassertions viametadataStore.DB()give strong guarantees that SetAccount’s upsert semantics match expectations.
128-184: Transactional SetAccount behavior is well‑validatedUsing an explicit GORM transaction to verify visibility within the txn and absence after rollback is a nice sanity check on SetAccount’s use of the provided
txn. No issues spotted.
186-284: DeleteCertificate cascade behavior is exercised thoroughlyManually wiring up
models.Transaction, unifiedmodels.Certificate, andmodels.StakeRegistration, then callingDeleteCertificateand asserting both mapping and specialized row removal (and that repeated deletes are no‑ops) gives good end‑to‑end coverage of the cascade behavior. Looks solid.database/plugin/metadata/sqlite/certs_test.go (4)
718-836: Idempotent SetTransaction behavior is exercised correctly
TestTransactionCertificateIdempotencynow goes throughmetadata.SetTransactionwith a real CBOR transaction, checking:
- first inclusion creates certificates,
- re‑calling with the same point is idempotent, and
- re‑inclusion at a different point updates the
Slotwithout duplicating rows.This is exactly the sort of regression guard you want around the new unified certificate persistence.
838-1036: Table‑driven processing tests cover key SetTransaction edge casesThe scenarios in
TestCertificateProcessingDataIntegrity(missing deposits erroring, idempotent re‑processing with same/different block, and slot updates) give good coverage of the new “deposits required for certs” contract. The finalexpectedSlotcheck keeps behavior tight across future refactors. No changes requested.
1038-1094: Certificate Slot persistence test aligns with SetTransaction semantics
TestCertificateSlotFieldvalidates that allmodels.Certificaterows end up withSlotequal to the block’s slot after SetTransaction, which is the key invariant for later cleanup and querying. Looks correct and complements the idempotency tests.
1141-1398: Specialized certificate model mapping tests are straightforward and usefulThe MIR, LeiosEb, and anchor‑bearing certificate tests directly assert field‑for‑field round‑tripping through the GORM models. Given these are relatively new tables, these explicit checks are helpful to catch tag or field regressions. No issues spotted.
database/models/transaction.go (1)
17-19: Collateral return association now correctly distinguished from regular outputsUsing
where:is_collateral_return = falseforOutputsandwhere:is_collateral_return = trueforCollateralReturnmatches theIsCollateralReturncolumn onmodels.Utxoand fixes the earlier ambiguity where both associations shared the same FK without a discriminator. Fee/TTL staying astypes.Uint64also keeps DB behavior consistent with other monetary fields.Also applies to: 25-36
internal/config/config.go (1)
17-23: Ledger catch‑up configuration fields are correctly added and defaultedThe three new duration fields on
Config(timeout, poll interval, stall timeout) with their 5m/5s/10m defaults align with the node’s hard‑coded constants and follow the existing YAML/envconfig tagging style. Looks good.Also applies to: 46-68, 70-92
database/models/resign_committee_cold.go (1)
21-29: ResignCommitteeCold uniqueness and slot typing look consistent
ColdCredentialuniqueness plusCertificateIDunique index give the expected 1:1 mapping, and switchingAddedSlottotypes.Uint64aligns with other models. No issues spotted here.database/transaction.go (1)
22-62: Transactional handling and error propagation in SetTransaction look solidThe switch to a named error with a single deferred commit/rollback plus early returns on blob/metadata errors makes this path much safer and clearer. The UTXO write loop and metadata call are straightforward and consistent with the dual-store design.
database/models/utxo.go (1)
23-40: UTxO typing and collateral-return flag changes are consistentUsing
types.Uint64for slot/amount plus theIsCollateralReturnflag matches the updated Transaction associations and other models.UtxoLedgerToModelcorrectly populates the wrapped values, so persistence and queries should remain coherent.Also applies to: 50-60
database/plugin/metadata/sqlite/transaction.go (1)
65-83: Idempotent, deposit-aware SetTransaction is well-structured; double-check assumptions for existing rowsThe new flow looks coherent:
alreadyExistsgates UTxO and certificate work, with re-inclusion detected viablockChanged.- Transaction upsert on hash plus the ID reload handles ON CONFLICT correctly.
- Certificate handling is clean: invalid tx short-circuit, deposit map required only for new tx with certs, and deposit presence enforced only for known deposit-bearing types, using the index-aligned
depositsmap.updateCertificateSlotscorrectly propagates the new slot intocertificatesand all of the specialized tables, includingleios_eb.Two behavioral assumptions are worth keeping in mind:
- If a transaction row somehow exists without its certificates (e.g., from legacy or partially failed code before this refactor), the
alreadyExistsguard will skip certificate processing entirely, and those certs will never be backfilled.- On re-inclusion with different block info, only certificate slots are updated; UTxO
added_slot/deleted_slotremain at their original values. If you ever rely on those slots to reflect the final inclusion point after a reorg, you may want a similar update pass for UTxOs keyed bytx_id/spent_at_tx_id.If those edge cases are acceptable for your migration story and reorg handling, the current implementation is in good shape.
Also applies to: 98-145, 343-376, 377-422, 427-476
ledger/certs.go (1)
23-60: Certificate deposit calculation helper is correct and side-effect free
CalculateCertificateDepositscleanly delegates to the era’sCertDepositFunc, annotates errors with the certificate index, and always records an entry (even when the deposit is zero), which is exactly what the metadata layer needs to distinguish “zero” from “missing”. No issues here.database/plugin/metadata/sqlite/drep.go (2)
25-47: GetDrep pointer return + not-found handling look goodReturning
(*models.Drep, nil)with(nil, nil)for not-found is idiomatic, and usingdrep_credentialpluserrors.Is(..., gorm.ErrRecordNotFound)matches the model and avoids leaking ORM-specific sentinel errors to callers.
49-79: Upsert-by-credential semantics in SetDrep are consistentConstructing
models.DrepwithDrepCredentialand usingOnConflictondrep_credentialto updateadded_slot,anchor_url,anchor_hash, andactivegives a clean, idempotent “current DRep state by credential” write-path and matches the GORM tags inmodels.Drep.ledger/state.go (2)
103-107: Goroutine lifecycle management via context + WaitGroup is solidInitializing
ctx/cancelinNewLedgerState, wrapping background processing (ledgerProcessBlocksand its reader) withwg.Add/Done, and usingClosetocancel()thenwg.Wait()provides a clear, leak-free lifecycle for ledger goroutines.Also applies to: 127-129, 176-180, 209-217
885-903: Certificate deposits precomputation and strict validation behavior look correctPrecomputing
depositsonly whenlen(tx.Certificates()) > 0, propagating any error fromCalculateCertificateDeposits, then passing the map intodelta.addTransactionWithDepositsmatches the new unified certificate/deposit model. TighteningValidateTxFunchandling toreturn nil, fmt.Errorf("TX validation failure: %w", err)ensures invalid transactions abort block processing instead of being silently indexed.Also applies to: 905-926, 919-920
database/models/account.go (2)
27-35: Account metadata fields and indexing strategy make senseAdding
AddedSlot,CertificateID, and an indexedActiveflag onAccountprovides enough metadata to track when/why the account last changed while still enforcing a single current row perStakingKeyvia the unique index.
60-67: Composite unique constraints on certificate models align with the documented strategyThe explanation comment and the
uniqueIndex:uniq_*_certtags on(StakingKey, CertificateID)forDeregistration,StakeDeregistration, andStakeRegistrationcorrectly enforce “one row per certificate per staking key” while allowing multiple distinct certificates per key. Using only plain indexes for other certificate types preserves full historical tracking for those tables.Also applies to: 68-74, 104-109, 115-121
database/account.go (1)
21-27: GetAccountWithInactive helper cleanly extends behavior without breaking callersHaving
GetAccountdelegate toGetAccountWithInactive(..., false, ...)preserves existing semantics, while the new helper cleanly wires throughincludeInactivetometadata.GetAccountand safely manages a read-only transaction when the caller doesn’t provide one.Also applies to: 29-51
database/plugin/metadata/store.go (1)
56-61: MetadataStore interface updates are correctly implemented in the sqlite backendAll interface changes have matching implementations:
GetAccountwithincludeInactiveparameter (account.go:26-29)SetTransactionwith deposits map (transaction.go:53-56)GetUtxosByAddressandSetUtxoDeletedAtSlotusing ledger/common types (utxo.go:97-100, 231-234)Tests confirm proper usage of the new signatures across all functions.
database/plugin/metadata/sqlite/account.go (1)
71-79: Clarify zero-value handling in Updates.GORM's
Updateswith a map skips zero values by default. Ifpkhordreparenilor empty byte slices, they won't be updated, preserving the existing values in the database.If the intent is to explicitly clear these fields (set them to
nil), consider usingSelectto force update specific fields or use struct-based updates:if err := db.Model(account).Select("Pool", "Drep", "AddedSlot", "Active").Updates(models.Account{ Pool: pkh, Drep: drep, AddedSlot: slot, Active: active, }).Error; err != nil { return fmt.Errorf("failed to update account: %w", err) }If preserving existing values when
nilis passed is the desired behavior, consider documenting this in a comment.
93d81a8 to
5196a3f
Compare
There was a problem hiding this 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)
database/plugin/metadata/sqlite/account.go (1)
52-82: SetAccount must accept and set CertificateID to track certificate origins.The refactored
SetAccountmethod omits theCertificateIDfield despite the Account model having this field with a database index (models.Account line ~30). Other certificate-related models—StakeRegistration, StakeDelegation, VoteDelegation, etc.—correctly setCertificateIDduring creation (certs.go lines 94, 106, 119, etc.), but Account records updated viaSetAccountdo not.Required changes:
- Add
certificateID uintparameter toSetAccountsignature (line 52)- Add
"certificate_id": certificateIDto the updates map (line 71-76)- Update all
SetAccountcalls inStoreCertificate(certs.go lines 524-634) to pass the unified Certificate IDThis ensures Account records properly track their certificate origins, consistent with other certificate-related models.
♻️ Duplicate comments (1)
database/plugin/metadata/sqlite/certs_test.go (1)
40-40: Fix invalidfor i := range 3loops; tests won’t compile as written.
rangein Go cannot iterate over an integer literal; all thesefor i := range 3loops are compile-time errors. Use a counted loop instead.Suggested fix:
- // Insert pool registration record multiple times - for i := range 3 { + // Insert pool registration record multiple times + for i := 0; i < 3; i++ { @@ - // Insert pool retirement record multiple times - for i := range 3 { + // Insert pool retirement record multiple times + for i := 0; i < 3; i++ { @@ - // Insert stake registration record multiple times with different certificate IDs - for i := range 3 { + // Insert stake registration record multiple times with different certificate IDs + for i := 0; i < 3; i++ { @@ - // Insert stake deregistration record multiple times with different certificate IDs - for i := range 3 { + // Insert stake deregistration record multiple times with different certificate IDs + for i := 0; i < 3; i++ { @@ - // Insert DRep registration record multiple times (should all succeed now) - for i := range 3 { + // Insert DRep registration record multiple times (should all succeed now) + for i := 0; i < 3; i++ { @@ - // Insert DRep deregistration record multiple times (should all succeed) - for i := range 3 { + // Insert DRep deregistration record multiple times (should all succeed) + for i := 0; i < 3; i++ { @@ - // Insert DRep update record multiple times (should all succeed) - for i := range 3 { + // Insert DRep update record multiple times (should all succeed) + for i := 0; i < 3; i++ { @@ - for i := range 3 { + for i := 0; i < 3; i++ {Make the same substitution anywhere else
for i := range 3appears in this file.Also applies to: 77-77, 128-128, 175-175, 239-239, 269-269, 294-294, 338-338
🧹 Nitpick comments (13)
database/plugin/metadata/sqlite/certs_test.go (2)
1410-1487:TestDeleteCertificateTypeCoveragecurrently only asserts “no panic”, not switch exhaustiveness.Despite the comment, this test will happily pass even if
DeleteCertificatereturns an “unhandled type” error for a knownCertificateType, because it just logs any error. If you actually want coverage that forces the switch to handle all known types, you’ll need to treat unexpected errors as failures (and, ideally, haveDeleteCertificatereturn a specific sentinel error for unknown types that the test can assert on).For now this is a nice smoke test, but it doesn’t guard against newly added cert types being forgotten in the delete switch.
727-1103: End-to-end SetTransaction tests are valuable; consider de-duplicating shared CBOR/fixture setup.All three transaction/certificate tests use the same large CBOR blob and very similar decode/setup logic. Functionally this is fine and the assertions around idempotency, re-inclusion, and
Slotupdates look good; if this file grows further, you may want a small helper to centralize the “decode transaction from CBOR” + deposits map construction to keep future changes to the fixture in one place.database/plugin/metadata/sqlite/utxo.go (1)
22-24: GetUtxosByAddress refactor and type switch look correct and fix prior column mismatch
- Using
lcommon.Address/lcommon.TransactionInputand comparingPaymentKeyHash/StakeKeyHashagainst a zero Blake2b224 mirrors existing patterns and is consistent withmodels.Utxowhich exposesPaymentKey/StakingKeycolumns (sopayment_key/staking_keyin SQL). This resolves the earlierpayment_addrvsPaymentKeymismatch from prior reviews. Based on learnings.- The early return when both key hashes are zero is a reasonable safeguard given the schema only indexes key credentials; if you ever need to support script‑only addresses here, you’ll need to extend the UTXO model/query rather than loosening this guard.
Also applies to: 96-133
database/models/utxo.go (1)
23-40: UTxO model Uint64 migration and helper look consistent with the rest of the DB layer
- Moving
AddedSlot,DeletedSlot, andAmounttotypes.Uint64and constructing them inUtxoLedgerToModelviatypes.Uint64{Val: ...}matches how other models (e.g.,Asset.Amount) use the wrapper type and keeps schema handling uniform. Based on learnings.- The new
IsCollateralReturnflag is wired into the model and indexed; as long as the transaction‑persistence path sets this when mapping collateral‑return outputs intomodels.Utxo, the associations onmodels.Transaction(OutputsvsCollateralReturn) will behave as intended.Also applies to: 50-79
database/plugin/metadata/sqlite/account_test.go (2)
28-126: Account tests accurately cover upsert, active flag, and transactional semantics
TestSetAccountvalidates initial creation, update of pool/drep/slot, deactivation, and thatGetAccountrespects theincludeInactiveflag while the underlying DB row and ID remain stable.TestSetAccountWithTransactioncorrectly asserts that changes are visible within the provided GORM transaction and disappear after rollback, confirming thatSetAccounthonors the caller‑suppliedtxn.
Ifmodels.Account.AddedSlotis ever migrated totypes.Uint64like other slot fields, you’ll just need to adjust theassert.Equalchecks to compare.Val.Also applies to: 128-184
186-284: DeleteCertificate test gives good coverage of unified certificate → stake_registration cascade
- Manually seeding
models.Transaction, unifiedmodels.Certificate, andmodels.StakeRegistration, then callingDeleteCertificateand asserting both the certificate and stake registration rows are gone exercises the new unified certificate mapping and cascade behavior well.- The additional “delete non‑existent certificate” case ensures the API is idempotent and safe to call in cleanup paths.
If deposit/slot fields onStakeRegistrationare later migrated totypes.Uint64, the struct literals here should be updated accordingly, but the overall test structure looks solid.database/plugin/metadata/sqlite/pool.go (1)
53-90: Pool registration query and amount extraction are aligned with the models
- Filtering on
pool_key_hash = ?withpkh.Bytes()in both transactional and non‑transactional paths lines up withmodels.PoolRegistration.PoolKeyHash []byte, removing the prior mismatch.- Using
cert.Pledge.Val/cert.Cost.Valmatches the migration of those columns totypes.Uint64and returns plain uint amounts as expected by the ledger/common types.
TheCertType: uint(lcommon.CertificateTypePoolRegistration)assignment assumesPoolRegistrationCertificate.CertTypeis a uint; if upstream changes that field back to a strong enum, you may need to swap this for the enum value directly.internal/node/load.go (1)
99-103: Async ledger start plus progress-based catchup loop address prior stall issues
- Starting the ledger via
ls.Start()and then using a ticker to pollls.Tip()until the ledger slot reaches the immutable tip decouples loading from processing and avoids blocking inside the ledger implementation.- The new loop:
- treats prolonged
tip.Point.Slot == 0as a stall (withzeroSlotSeenAt), and- treats lack of slot advancement beyond the last seen slot for longer than
stallTimeoutas a stall,
both of which preventLoadfrom hanging indefinitely if the ledger never makes progress.- Logging of progress and completion provides good observability for long syncs.
Also applies to: 189-258
database/utxo.go (1)
24-24: lcommon type alignment and extended UTXO mapping look goodSwitching
UtxoConsume/UtxosByAddressto uselcommontypes and explicitly copyingIsCollateralReturn(plus the other core fields) keeps the DB API consistent with the ledger layer and preserves the new collateral-return flag end‑to‑end. The logic in both methods is sound.If you want a tiny optimization, you could preallocate the result slice in
UtxosByAddress:ret := make([]models.Utxo, 0, len(utxos))to avoid potential reallocations.
Also applies to: 103-112, 115-140
database/plugin/metadata/sqlite/certs.go (1)
64-513: Unified certificate storage and payload mapping look consistent and completeThe new
storeCertificateflow (unified row first, then per‑type row linked viaCertificateID, then back‑linkingCertificate.CertificateID) is coherent and matches the model definitions:
- Pool registrations now include margin, metadata URL/hash, owners, and relays, so
GetPoolRegistrationsand related readers won’t see partial data.- MIR and Leios certificates persist their full semantic payloads as JSON blobs (rewards map, voters, aggregated signatures), which restores the previously missing information.
- Stake/vote/DRep/committee certs all set
CertificateIDon their specialized models to the unified cert ID, aligning with the delete and slot‑update paths.- The deregistration amount is sourced from
c.Amountrather than the deposits map, which fixes the earlier “always zero” regression.Given how large this switch is, extracting small helpers per family (stake, pool, vote, DRep, MIR/Leios) could reduce cognitive load, but that’s purely structural and can be deferred.
ledger/certs.go (1)
23-59: Deposit calculation helper is simple and correct
CalculateCertificateDepositsnow computes deposits for each certificate index viacurrentEra.CertDepositFuncand always records an entry (including zeros), which removes the earlier ambiguity around missing vs zero deposits. Error wrapping with the index is also helpful for debugging.If you ever expect eras without a
CertDepositFuncbut with certificates, a defensive nil check before calling it would avoid a panic, but that’s optional given current era semantics.database/plugin/metadata/sqlite/transaction.go (1)
52-425: Idempotent SetTransaction with deposit-aware certificate persistence looks solidThe reworked
SetTransactionflow addresses the earlier issues:
- Idempotency is based on a pre‑upsert hash check (
alreadyExists) and anON CONFLICT(hash)upsert, so the first insert still processes certificates while replays skip or just adjust slots.- UTXO ingestion/consumption runs only when
alreadyExists == false, preventing double‑spend bookkeeping on replays.- The
processCertificatessection cleanly handles:
- Invalid txs (no cert persistence),
- Re‑inclusion with changed block info (
updateCertificateSlotsthen return),- New txs with certs (enforce non‑nil
deposits, per‑index deposit lookup, and strict checks for the known deposit‑requiring cert types before delegating tostoreCertificate).Control flow via the
processCertificateslabel is a bit dense but logically correct. If you touch this again, you might consider splitting the certificate handling into a small dedicated helper to reduce the size/complexity ofSetTransaction.database/plugin/metadata/sqlite/database.go (1)
36-50: Background VACUUM and certificate-cleanup lifecycle are correctly synchronized with CloseThe
MetadataStoreSqlitechanges introduce a solid pattern:
scheduleDailyVacuum/runVacuumandscheduleCertificateCleanup/runCertificateCleanupall respectd.closedand usetimerMutexplusvacuumWG/certificateCleanupWGso no background job runs against a closed store.Closemarksclosed, stops both timers, waits for in‑flight jobs to finish via the WaitGroups, and then closes the underlying DB handle, which prevents the previous “VACUUM after Close” issue and avoids goroutine leaks.- In-memory mode (
dataDir == "") is correctly excluded from VACUUM via the guard inrunVacuum.The certificate cleanup is currently a logged no‑op with clear TODO comments, which is a reasonable staging point for adding real cleanup logic later.
Also applies to: 52-126, 128-188, 190-245, 252-278, 280-311
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (44)
blockfetch.go(1 hunks)database/account.go(1 hunks)database/certificate.go(1 hunks)database/database_test.go(2 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/blob/aws/database.go(1 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/account_test.go(1 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(3 hunks)database/plugin/metadata/sqlite/pool.go(2 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(3 hunks)database/plugin/metadata/store.go(3 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(4 hunks)go.mod(4 hunks)internal/config/config.go(2 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(12 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (2)
- database/models/certs.go
- database/drep.go
🚧 Files skipped from review as they are similar to previous changes (14)
- database/types/types_test.go
- database/models/asset.go
- database/certificate.go
- blockfetch.go
- database/models/certificate_test.go
- database/models/models.go
- database/models/auth_committee_hot.go
- database/models/certificate.go
- database/models/pool.go
- internal/config/config_test.go
- database/models/leios_eb.go
- database/models/drep.go
- database/models/genesis_key_delegation.go
- database/database_test.go
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/models/utxo.godatabase/utxo.godatabase/plugin/metadata/store.godatabase/models/transaction.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/models/utxo.godatabase/plugin/metadata/sqlite/transaction.goledger/delta.godatabase/transaction.godatabase/utxo.godatabase/plugin/metadata/store.godatabase/plugin/metadata/sqlite/database.godatabase/models/transaction.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/utxo.godatabase/plugin/metadata/store.goledger/state.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/sqlite/certs_test.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/utxo.godatabase/plugin/metadata/store.goledger/state.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/utxo.godatabase/utxo.godatabase/models/transaction.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/plugin/metadata/sqlite/certs_test.godatabase/utxo.goledger/state.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/utxo.godatabase/plugin/metadata/store.goledger/state.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/utxo.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/utxo.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.
Applied to files:
ledger/state.go
🧬 Code graph analysis (21)
database/plugin/metadata/sqlite/utxo.go (1)
database/models/utxo.go (1)
Utxo(24-40)
internal/config/config.go (1)
internal/node/load.go (2)
LedgerCatchupPollInterval(36-36)LedgerCatchupStallTimeout(38-38)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(79-107)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
ledger/view.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
database/plugin/metadata/sqlite/certs.go (11)
database/models/certificate.go (23)
CertificateTypeStakeRegistration(32-34)Certificate(157-165)Certificate(168-170)CertificateType(28-28)CertificateTypeStakeDeregistration(35-37)CertificateTypeStakeDelegation(38-40)CertificateTypePoolRegistration(41-43)CertificateTypePoolRetirement(44-46)CertificateTypeRegistration(47-49)CertificateTypeDeregistration(50-52)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteDelegation(56-58)CertificateTypeStakeVoteDelegation(59-61)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)CertificateTypeAuthCommitteeHot(68-70)CertificateTypeResignCommitteeCold(71-73)CertificateTypeRegistrationDrep(74-76)CertificateTypeDeregistrationDrep(77-79)CertificateTypeUpdateDrep(80-82)CertificateTypeGenesisKeyDelegation(83-85)CertificateTypeMoveInstantaneousRewards(86-88)CertificateTypeLeiosEb(89-91)database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-50)database/models/account.go (20)
StakeRegistration(115-121)StakeRegistration(123-125)StakeDeregistration(104-109)StakeDeregistration(111-113)StakeDelegation(92-98)StakeDelegation(100-102)Registration(80-86)Registration(88-90)Deregistration(68-74)Deregistration(76-78)StakeRegistrationDelegation(127-134)StakeRegistrationDelegation(136-138)VoteDelegation(167-173)VoteDelegation(175-177)StakeVoteDelegation(140-147)StakeVoteDelegation(149-151)VoteRegistrationDelegation(179-186)VoteRegistrationDelegation(188-190)StakeVoteRegistrationDelegation(153-161)StakeVoteRegistrationDelegation(163-165)database/types/types.go (2)
Uint64(66-68)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
database/models/move_instantaneous_rewards.go (1)
database/types/types.go (1)
Uint64(66-68)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/transaction.go (5)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-50)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/utxo.go (1)
UtxoLedgerToModel(50-79)database/models/certificate.go (10)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeRegistrationDrep(74-76)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(157-165)Certificate(168-170)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(66-68)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(269-277)
database/plugin/metadata/sqlite/drep.go (1)
database/models/drep.go (1)
Drep(21-29)
database/plugin/metadata/sqlite/pool.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
database/plugin/metadata/sqlite/certs_test.go (11)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(66-68)database/models/account.go (7)
StakeRegistration(115-121)StakeRegistration(123-125)Deregistration(68-74)Deregistration(76-78)StakeDeregistration(104-109)StakeDeregistration(111-113)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (3)
Certificate(157-165)Certificate(168-170)CertificateType(28-28)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
database/plugin/metadata/sqlite/account.go (1)
database/models/account.go (1)
Account(27-35)
ledger/state.go (2)
database/txn.go (1)
Txn(27-34)database/plugin/log.go (1)
Logger(18-27)
database/plugin/metadata/sqlite/account_test.go (5)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/account.go (3)
Account(27-35)StakeRegistration(115-121)StakeRegistration(123-125)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
database/plugin/metadata/sqlite/database.go (1)
database/plugin/log.go (1)
Logger(18-27)
database/account.go (3)
database/models/account.go (1)
Account(27-35)database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/models/transaction.go (2)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)
⏰ 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). (3)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (29)
database/plugin/blob/aws/database.go (1)
69-71: LGTM! Minor formatting improvement.The multi-line formatting of the error construction improves readability without any functional change.
go.mod (1)
14-14: Dependency bumps and new test tooling look consistent; ensure module graph is tidy.The gouroboros bump and addition of testify + its indirects match the broader ledger/cert/test changes. Please just confirm
go.sumis in sync (e.g., via a localgo mod tidy) and that there are no unintended version drifts elsewhere in the repo.Also applies to: 24-24, 97-97, 158-158
database/models/move_instantaneous_rewards.go (1)
21-34: MIR model matches unified certs design and avoids prior uniqueness pitfall.Single-unique
CertificateIDplus indexedSourceandAddedSlotallow multiple MIR certs per slot/pot while keeping lookup efficient. Struct fields and tags are consistent with other new cert models; nothing blocking here.database/models/transaction.go (1)
17-19: Outputs/CollateralReturn filters and Uint64 usage look correct.The
where:is_collateral_return = …filters now align with theUtxo.IsCollateralReturncolumn name, and revertingFee/TTLtotypes.Uint64keeps DB encoding consistent with other monetary fields. This struct layout looks sound.Also applies to: 25-36
database/types/types.go (2)
36-63: Rat.Scan improvements safely broaden input handling.Handling
nil,string,[]byte, andint64while keeping the zero-value on NULL is reasonable and matches typical SQL driver behavior. No issues spotted.
65-101: Uint64 struct Scan/Value semantics are coherent with existing storage scheme.Switching
Uint64to a struct withValplus string-basedValue()and tolerantScan()(nil → 0, string/[]byte/int64 inputs) preserves prior on-disk representation while making usage in models explicit. This looks correct and well-scoped.database/models/account_test.go (1)
26-96: Test table fields now properly support both HRP and error-message assertions.Bringing
wantErrMsgandexpectedHRPinto the table and using them selectively for error vs. success cases makes the tests clearer and more extensible; the current cases cover empty/nil keys and multiple valid edge patterns well.ledger/view.go (1)
92-167: PoolCurrentState mapping aligns with new pledge/cost and cert typing.Casting
CertTypefrom the ledger enum touintand pullingPledge/Costviapool.Pledge.Val/pool.Cost.Valmatches the new model types and keeps the ledger-facing certificate consistent. The latest-registration and latest-retirement selection logic remains unchanged and correct.internal/config/config.go (1)
17-23: New ledger catchup tuning knobs are wired correctly with sane defaults.The added duration fields (timeout, poll interval, stall timeout) have appropriate YAML/env tags and default values that match the node load constants, so they should be easy to override without surprising behavior.
Also applies to: 46-68, 70-92
database/plugin/metadata/sqlite/utxo.go (1)
230-247: SetUtxoDeletedAtSlot signature change is consistent with lcommon migrationSwitching the
utxoIdparameter tolcommon.TransactionInputwhile still usingId().Bytes()andIndex()in the WHERE clause preserves behavior and keeps this API aligned with other lcommon‑based call sites.database/transaction.go (1)
22-63: SetTransaction lifecycle and deposits wiring look solid
- The named return + deferred commit/rollback for internally created transactions removes the prior risk of calling
Commit()after aRollback()and centralizes error handling cleanly. Based on learnings.- Always iterating
tx.Produced()and writing UTXO blobs is appropriate given BabbageProduced()semantics (only collateral return for invalid tx, full outputs for valid ones), and threadingdepositsstraight through tometadata.SetTransactionkeeps the persistence API cohesive.ledger/delta.go (2)
25-46: TransactionRecord deposits field and helper are straightforwardAdding
Deposits map[int]uint64toTransactionRecordand usingaddTransactionWithDepositsto append{Tx, Index, Deposits}cleanly threads per‑certificate deposit data through the delta without complicating the main apply loop.
49-99: Deposit handling for invalid vs valid transactions now matches ledger rules
- In the invalid‑transaction branch you now skip any deposit calculation and pass a nil
depositsmap intoSetTransaction, while still consuming UTXOs; this aligns with Cardano semantics where phase‑2 invalid txs don’t apply certificate actions or deposits but do affect collateral. Based on learnings.- For valid txs, preferring
tr.Depositswhen supplied and otherwise callingCalculateCertificateDepositsontr.Tx.Certificates()gives you flexibility to precompute deposits upstream without changing persistence logic.database/models/resign_committee_cold.go (1)
17-30: ResignCommitteeCold uniqueness and Uint64 migration are coherentThe model now clearly enforces:
- a single resignation per
ColdCredentialviagorm:"unique", and- a 1:1 link to the unified certificate row via
CertificateID’s unique index,while migrating
AddedSlottotypes.Uint64in line with other certificate‑related models. The in‑code comments match the actual constraints.internal/node/load.go (1)
34-39: Configurable poll/stall constants and fallbacks improve maintainabilityDefining
LedgerCatchupPollIntervalandLedgerCatchupStallTimeoutand then using them as fallbacks when the corresponding config values are zero avoids hard‑coded literals and makes the catch‑up behavior easy to tune without touching code.Also applies to: 179-187
database/plugin/metadata/sqlite/certs.go (2)
515-653: Account-state updates for certificate effects are well covered
updateAccountStatecleanly maps certificate types toSetAccountcalls:
- Stake registration/deregistration and all stake/vote delegation variants correctly toggle
Activeand wirePool/Drepas expected.- Legacy registration/deregistration types are handled alongside the newer Conway variants.
- Pool and DRep‑only certificates intentionally no-op, which matches their lack of direct staking key.
This looks aligned with the models in
database/models/account.goand the unified certificate storage.
655-808: I need to search more broadly for call sites and understand the relationship with account state management.Now I need to search the codebase for the broader context around
updateAccountStateandDeleteCertificateusage.Now let me search directly in the codebase for
updateAccountStateand broaderDeleteCertificateusage:Based on my verification through search and web resources, the original search showed that
DeleteCertificateis currently only used in test cases (account_test.go and certs_test.go), with no visible production call sites in the repository. The reviewer's architectural concern about account state rollback is conceptually valid—when certificates are deleted, any state changes they caused should theoretically be reversed. However, the practical impact depends entirely on how and when this function is actually used in the codebase.The cascading delete itself appears to be correctly implemented using
cert.IDto match the unified certificate record to its specialized type tables. The concern raised by the reviewer is a valid design consideration rather than a bug in the current code shown.Review the usage context of DeleteCertificate to clarify whether account state must be managed during certificate deletion
The cascading delete correctly uses the unified certificate ID across all related tables. The architectural consideration about
updateAccountStateeffects is valid but requires clarification on actual usage:
- Verify whether
DeleteCertificateis invoked only during straight replays (where effects should be reapplied) or also for true removal scenarios (where effects should be reversed)- If used for removal scenarios, add a comment documenting that callers must handle any
Account/DRepstate reconciliation- If used only during replays, document this constraint and consider whether the comment is premature documentation of a hypothetical concern
Currently visible usage is test-only; ensure this function's integration path is clear before deployment.
database/plugin/metadata/sqlite/transaction.go (1)
427-478: Slot propagation across certificate tables is comprehensive
updateCertificateSlotsupdates theslotcolumn incertificatesand then propagatesadded_slotacross all specialized tables via a subquery ontransaction_id. ThecertTableslist correctly includes all new certificate tables (includingleios_eb) and matches the schema indatabase/models.Looks good for keeping certificate timing consistent on transaction re‑inclusion.
ledger/state.go (2)
103-107: Context-based lifecycle and goroutine tracking are well wiredIntroducing
ctx,cancel, andwgintoLedgerStateand:
- Starting
ledgerProcessBlocksunderStart()withwg.Add/Done,- Spawning
ledgerReadChainfrom withinledgerProcessBlockswith its ownwgaccounting,- Respecting
ls.ctx.Done()in both loops, and- Having
Close()callcancel()andwg.Wait(),gives you a clean, race‑free shutdown story. Moving the
close(resultCh)responsibility intoledgerReadChainalso removes the earlier “send on closed channel” hazard.Also applies to: 127-129, 132-182, 542-645
890-925: Certificate deposit calculation and validation integration look correctWithin
ledgerProcessBlock:
- Deposits are computed only when
len(tx.Certificates()) > 0viaCalculateCertificateDeposits, and any error there aborts block processing with good context.- Validation is then run (when
shouldValidateand aValidateTxFuncexist), and all validation errors are now fatal, addressing the prior over‑broad suppression.- The call to
delta.addTransactionWithDeposits(tx, i, deposits)passesnilfor non‑certificate transactions and the populated map for certificate transactions, which matches the delta’s new API.This aligns the ledger path with the store’s expectations about the deposits map.
database/plugin/metadata/sqlite/drep.go (1)
18-47: DRep lookup and upsert behavior align with the model schema
GetDrepnow queries bydrep_credentialand returns(*models.Drep, nil)on success,nil, nilon not‑found, and a real error otherwise, which is a clean API for callers.SetDrepusesOnConflict(Columns: drep_credential)to upsertadded_slot,anchor_url,anchor_hash, andactive, and correctly initializesDrepCredentialin the model.This matches the
models.Drepdefinition and should behave well for both initial registration and subsequent updates.Also applies to: 49-78
database/models/account.go (1)
32-35: Composite unique constraints now match the unified certificate storage design
- Indexing
Account.AddedSlot,Account.CertificateID, andAccount.Activealigns with how account state is queried and filtered.- Switching
Deregistration,StakeDeregistration, andStakeRegistrationto composite uniques on(StakingKey, CertificateID)fixes the earlier “one row per staking key” limitation while still preventing duplicate entries for the same certificate mapping.- The explanatory comment block documents the strategy and makes the intent clear.
Given
storeCertificatenow sets these models’CertificateIDfields to the unified certificate ID, the schema and persistence logic are in sync.Also applies to: 60-67, 68-74, 104-109, 115-121
database/account.go (2)
22-27: LGTM: Clean delegation pattern maintains backward compatibility.The refactoring to delegate
GetAccounttoGetAccountWithInactivewithincludeInactive=falsepreserves existing behavior while enabling the new functionality through the extended method.
29-51: LGTM: Well-implemented extension for inactive account retrieval.The new method correctly:
- Handles transaction lifecycle (nil check and deferred commit)
- Propagates the
includeInactiveparameter to the metadata layer- Preserves existing error handling patterns including the
ErrAccountNotFoundmappingdatabase/plugin/metadata/sqlite/account.go (1)
26-49: LGTM: Correct conditional query logic for inactive accounts.The implementation correctly:
- Builds the base query filtering by
staking_key- Conditionally adds
active = truefilter only whenincludeInactiveis false- Preserves the existing error handling pattern (returns nil for
ErrRecordNotFound)database/plugin/metadata/store.go (4)
56-60: LGTM: Interface extension for inactive account retrieval is well-documented.The addition of the
includeInactiveboolean parameter is clearly commented and consistent with the implementations reviewed in the SQLite plugin.
127-133: LGTM: SetTransaction signature change is well-documented.The updated signature adds critical parameters for unified certificate persistence:
uint32transaction index enables proper block-level orderingmap[int]uint64deposits map with comprehensive inline documentation explaining the key-value semantics and default behaviorThe deposits map comment clearly states the alignment requirement with transaction certificate slice order and the default-to-zero behavior for missing keys.
144-144: LGTM: Type change to canonical lcommon.Address improves consistency.The parameter type change from
ledger.Addresstolcommon.Addressaligns with the canonical types used throughout the ledger packages, as confirmed in the past review verification.
146-146: LGTM: Type change to canonical lcommon.TransactionInput improves consistency.The parameter type change from
ledger.TransactionInputtolcommon.TransactionInputmaintains consistency with the type standardization applied across the interface.
|
Hey @coderabbitai range over int is definitely valid Go. Stop claiming that it is not. It's been in Go since 1.22 and we use it everywhere and use a minimum Go 1.23 (usually 1.24 or higher) everywhere. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 9 @wolf31o2: You're absolutely right, and I apologize for the incorrect feedback. Range over int is indeed valid Go syntax since Go 1.22, and your repository is using Go 1.24.0 as confirmed in your go.mod. I should not have flagged this as invalid syntax. Thank you for the clarification. I've noted this to ensure I don't make this mistake again in future reviews of your code. ✏️ Learnings added
|
1648d9f to
2c4ba00
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
database/plugin/metadata/store.go (1)
144-146: UTXO methods now useledger/commontypes, matching canonical ledger usage.Changing
GetUtxosByAddressto takelcommon.AddressandSetUtxoDeletedAtSlotto takelcommon.TransactionInputaligns the interface with theledger/commonpackage, which is the canonical type family for ledger-facing code in this repo. This resolves the earlier mismatch noted between interface and SQLite implementation; just ensure all implementations and callers consistently import and passledger/commontypes.Based on learnings.
🧹 Nitpick comments (9)
database/plugin/metadata/sqlite/drep.go (1)
32-44: Document the (nil, nil) return pattern for not-found DReps.
GetDrepreturns(nil, nil)when a DRep is not found (lines 34, 41), rather than returning an error. This pattern can be confusing for callers who may interpret(nil, nil)as "success with no result" rather than "not found."Consider either:
- Returning a sentinel error like
models.ErrDrepNotFound(similar tomodels.ErrAccountNotFoundelsewhere)- Adding a doc comment explicitly stating that
(nil, nil)means "not found"database/plugin/metadata/sqlite/database.go (1)
191-218: Track or document the no-op certificate cleanup placeholder.The
runCertificateCleanup()method is a TODO placeholder that performs no actual work (lines 202-207 list intended functionality). This is scheduled to run every 6 hours (line 232), but currently only logs a debug message.Consider:
- Opening a tracking issue for implementing the cleanup logic, or
- Adding a doc comment stating this is intentionally a no-op until specific cleanup requirements are identified
Do you want me to help draft an issue description or update the documentation?
database/plugin/metadata/sqlite/certs_test.go (1)
44-132: Consider using per‑test TempDir data dirs for stronger isolationSeveral of the earlier tests (
TestPoolCertificateDuplicatePrevention,TestStakeCertificateDuplicatePrevention,TestDrepCertificateReRegistration, etc.) construct databases withDataDir: "", while the later transaction/idempotency tests uset.TempDir()for an isolated DB file per test.For consistency and to avoid any subtle cross‑test interference via a shared default path, consider switching these to use
DataDir: t.TempDir()as well. Behavior is currently correct, this would just harden isolation.Also applies to: 134-242, 244-339
database/database_test.go (1)
21-27: Strengthen the test by exercising the actual UtxosByAddress copy pathThe metadata part of the test is solid: inserting a UTXO with
IsCollateralReturn = trueand asserting it survivesGetUtxosByAddressgives good coverage of the query/mapping layer.For the “database-level copying logic” section, you’re manually reconstructing the
models.Utxoinstead of going through the real helper that production code uses to copy from metadata results. That means this test could keep passing even if the actual copy path forgets to propagateIsCollateralReturn.Two small refinements:
- Prefer calling the real helper (e.g., the
UtxosByAddressAPI that wrapsGetUtxosByAddress) rather than re-implementing the copy in the test, so you’re validating the production mapping end-to-end.- Since you already copy
AddedSlotandDeletedSlot, consider asserting them too to catch regressions on those fields.Also applies to: 76-176
database/models/certificate.go (1)
25-101: CertificateType enum and Scan/Value implementation are solidThe
CertificateTypeconstants mirror the ledger/common types, and theValue/Scanmethods correctly handle the usual DB driver variants (ints, uints, byte slices, strings) with overflow and range checks. This should integrate cleanly with GORM and prevent invalid enum values from being loaded.If you ever add new certificate kinds, consider deriving
maxCertificateTypeValuefrom the highest defined constant (or validating against a small allowed set) to avoid the “1000” magic number drifting out of sync.database/plugin/metadata/sqlite/certs.go (1)
666-823: Cascading certificate deletion matches the new mapping model
DeleteCertificatecorrectly:
- looks up the unified
models.Certificateby(transaction_id, cert_index),- deletes the corresponding specialized row(s) by
certificate_id = cert.IDfor each supportedCertType, and- then deletes the unified certificate row itself,
while treating “not found” as a no-op and returning a specific error for unknown types (useful in tests). For higher robustness, you may eventually want to wrap the specialized delete + unified delete in an explicit DB transaction when
txn == nil, but functionally this implementation aligns with the unified certificate schema.database/plugin/metadata/sqlite/transaction.go (2)
18-29: Idempotent transaction + certificate persistence flow looks correctThe reworked
SetTransaction:
- determines
alreadyExistsby hash before the upsert,- uses an
ON CONFLICT (hash)upsert to keep block hash/index up to date and then reloadstmpTx.IDwhen necessary,- only performs UTXO and association writes on the first insertion,
- cleanly short-circuits certificate processing for invalid transactions and txs without certificates,
- treats reprocessing of the same tx+block as a no-op, and
- for new cert-bearing txs, requires a deposits map, defaults missing entries to 0, and enforces presence only for deposit-requiring cert types before delegating to
storeCertificate.This matches the new
CalculateCertificateDepositscontract and gives you idempotent behavior without reintroducing the earlier “missing deposit vs zero deposit” ambiguity.If you touch this again, consider replacing the
goto processCertificateswith a structured branch to make the control flow slightly easier to follow.Also applies to: 52-145, 214-341, 343-425
427-479: Certificate slot re-inclusion updates are consistent; confirm table naming
updateCertificateSlotscorrectly updates:
- the
slotcolumn incertificatesfor the giventransaction_id, and- each specialized table’s
added_slotwherecertificate_idjoins back to those certificates,including LeiosEb via the
"leios_eb"entry. The overall re-inclusion behavior (only whenalreadyExists && blockChanged) lines up with the SetTransaction logic.The only thing worth a quick local check is that each string in
certTablesmatches the actual GORM table name for its model (particularly"leios_eb"vs any pluralization), so that all specialized rows get theiradded_slotupdated as intended.database/plugin/metadata/sqlite/account.go (1)
52-84: Clarify whetheradded_slotshould be first-seen or last-updated.
SetAccountusesFirstOrCreateand then always updatesadded_slotto the currentslot, even when the account already exists. Ifadded_slotis intended to mean “creation/first registration slot”, you likely want to set it only when the row is newly created (e.g., checkresult.RowsAffectedoraccount.ID). If instead it’s meant as “last updated by certificate at this slot”, current behavior is fine, but consider adding a brief comment to document that intent.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (44)
blockfetch.go(1 hunks)database/account.go(1 hunks)database/certificate.go(1 hunks)database/database_test.go(2 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/blob/aws/database.go(1 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/account_test.go(1 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(3 hunks)database/plugin/metadata/sqlite/pool.go(2 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(3 hunks)database/plugin/metadata/store.go(3 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(4 hunks)go.mod(4 hunks)internal/config/config.go(2 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(12 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (2)
- database/drep.go
- database/models/certs.go
✅ Files skipped from review due to trivial changes (1)
- internal/config/config_test.go
🚧 Files skipped from review as they are similar to previous changes (11)
- blockfetch.go
- database/models/certificate_test.go
- internal/config/config.go
- database/plugin/blob/aws/database.go
- database/models/models.go
- database/models/genesis_key_delegation.go
- database/models/leios_eb.go
- database/models/move_instantaneous_rewards.go
- database/models/account_test.go
- database/plugin/metadata/sqlite/account_test.go
- database/models/drep.go
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/utxo.goledger/delta.godatabase/models/transaction.godatabase/plugin/metadata/sqlite/database.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/store.godatabase/transaction.godatabase/utxo.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/utxo.godatabase/models/transaction.godatabase/plugin/metadata/store.godatabase/utxo.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/database_test.godatabase/models/transaction.goledger/state.godatabase/plugin/metadata/store.godatabase/utxo.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/database_test.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/store.godatabase/utxo.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/database_test.goledger/state.godatabase/plugin/metadata/store.godatabase/utxo.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/utxo.godatabase/models/transaction.godatabase/utxo.go
📚 Learning: 2025-11-20T02:07:07.043Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 0
File: :0-0
Timestamp: 2025-11-20T02:07:07.043Z
Learning: In the blinklabs-io/dingo repository, range over int is valid Go syntax. The repository uses Go 1.24.0 (minimum Go 1.23), and range over int has been supported since Go 1.22. This syntax should not be flagged as invalid.
Applied to files:
database/plugin/metadata/sqlite/certs_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/plugin/metadata/sqlite/certs_test.goledger/state.godatabase/utxo.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/database_test.godatabase/utxo.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
ledger/state.godatabase/plugin/metadata/store.godatabase/utxo.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/utxo.go
🔇 Additional comments (35)
go.mod (3)
97-97: Transitive dependencies from testify look appropriate.The go-spew and go-difflib indirect dependencies with specific commit hashes are expected transitive dependencies of testify and are commonly used for enhanced test output and diffing. These versions appear consistent with testify v1.11.1.
Also applies to: 97-97, 158-158, 158-158
24-24: v1.11.1 is stable, current, and free of known security vulnerabilities.v1.11.1 is the latest stable version of github.com/stretchr/testify (released Aug 27, 2025), and no known security issues or CVEs have been publicly reported for this version. The dependency is safe to include.
14-14: Verified: gouroboros v0.140.0 is an appropriate stable release for this PR.v0.140.0 (released 18 Nov 2025) includes fixed multiple ledger CBOR/serialization and certificate-related bugs including certificate shapes, certificate deposit validation, and correct utxorpc cert type. These certificate-specific improvements directly support the PR's unified certificate persistence system.
database/types/types.go (2)
36-63: LGTM: Rat.Scan properly handles multiple input types.The updated Scan method correctly handles nil (preserving zero-value), string, []byte, and int64 inputs with appropriate conversions.
86-88: Verify that negative int64 values cannot reach the Uint64.Scan method; clarify the intent of the application-level guarantee.The cast from
int64touint64at line 87 will silently wrap negative values without runtime validation. The#nosec G115comment asserts an "application-level guarantee about range," but verification could not locate explicit upstream validation logic or constraints that prevent negative values.Based on domain context (Cardano slot numbers are inherently non-negative), the assumption is reasonable. However, confirm:
- All callers of
Uint64.Scan()pass only non-negative int64 values or have upstream validation- Database schema or ORM layer does not allow negative values to reach this code path
- The intent is to document and accept this implicit assumption rather than add runtime checks
If the guarantee is implicit to the blockchain domain, document it in a code comment (e.g., "Slot numbers from Cardano are always non-negative"). If runtime safety is preferred, add a check before the cast.
database/models/asset.go (1)
73-73: LGTM: Asset.Amount correctly uses new Uint64 struct literal.The change from
types.Uint64(amount)totypes.Uint64{Val: amount}properly aligns with the updated Uint64 type definition.database/types/types_test.go (1)
34-34: LGTM: Test properly updated for struct-based Uint64 and extended coverage.The test now uses the struct literal form and adds verification for
[]byteinput, ensuring Scan handles multiple input types correctly.Also applies to: 82-92
ledger/view.go (1)
104-104: LGTM: Pool certificate construction aligns with updated type signatures.The changes to use
uint(lcommon.CertificateTypePoolRegistration),pool.Pledge.Val, andpool.Cost.Valcorrectly reflect the updated Uint64 struct shape and certificate type handling.Also applies to: 111-112
ledger/delta.go (2)
54-74: LGTM: Invalid transactions correctly skip deposit calculation.Per Cardano ledger rules, invalid (phase-2 validation failure) transactions do not apply certificate effects or deposits. The code now correctly:
- Processes consumed UTxOs for collateral
- Sets
depositstonil- Passes
nildeposits toSetTransaction()- Stops further processing
This addresses the critical issue flagged in previous reviews.
76-100: LGTM: Valid transactions properly calculate and persist deposits.For valid transactions, deposits are:
- Reused from
tr.Depositsif pre-calculated, or- Calculated via
CalculateCertificateDeposits()when certificates are present- Passed to
SetTransaction()before protocol parameter updatesThis ensures deposit state is correctly maintained for all certificate types.
database/plugin/metadata/sqlite/drep.go (1)
58-79: LGTM: SetDrep uses unified upsert with OnConflict.The consolidated upsert approach using
OnConflictwithDoUpdateson specific fields (added_slot,anchor_url,anchor_hash,active) properly handles both insert and update cases in a single operation.database/models/account.go (2)
60-67: LGTM: Clear documentation of composite unique constraint strategy.The comment effectively explains why certain certificate models use composite unique constraints
(StakingKey, CertificateID)while others use simple indexes. This design correctly allows:
- Single deregistration/stake registration per certificate ID (preventing duplicates)
- Multiple delegations/registrations per staking key across different certificates (preserving history)
69-71: LGTM: Composite unique constraints enable proper certificate lifecycle.The
uniqueIndexon(StakingKey, CertificateID)forDeregistration,StakeDeregistration, andStakeRegistrationcorrectly addresses the previous critical issue where simpleuniqueconstraints onStakingKeyprevented legitimate re-registrations and re-delegations.Per past review comments,
CertificateIDis now properly populated during certificate persistence (addressed in commit 1a0053f).Also applies to: 105-106, 116-117
database/plugin/metadata/sqlite/database.go (1)
145-160: LGTM: Proper lifecycle management for background operations.The wait group pattern correctly ensures:
runVacuum()incrementsvacuumWGwhile the store is known to be open (lines 152-154)Close()stops both timers and waits for in-flight operations to complete (lines 256-270)- Operations check
d.closedbefore starting work (lines 147, 193)This addresses the previous critical issue where vacuum could run after close.
Also applies to: 260-270
database/models/pool.go (1)
55-60: CertificateID uniqueness tags align with pool re-registration semanticsUsing per-table unique indexes on
CertificateIDwhile keepingPoolKeyHash/Epochas non-unique indexes correctly allows multiple registrations/retirements for the same pool while enforcing 1:1 mapping per certificate row. This is consistent with the new duplicate‑prevention/re‑registration tests.Also applies to: 91-98
database/transaction.go (1)
22-63: SetTransaction transactional control and error propagation look correctThe new signature and deferred rollback/commit logic cleanly separate caller‑managed vs internally created transactions and ensure blob writes and metadata persistence are rolled back together on error. No commit‑after‑rollback path remains, and UTXO persistence + metadata.SetTransaction are handled atomically for the internal case.
database/certificate.go (1)
21-47: Certificate read helpers and documentation are consistent with the new write pathThe top‑level comment correctly documents that certificates are persisted via SetTransaction, and the updated GetPoolRegistrations/GetStakeRegistrations behavior (auto‑creating and committing a read transaction only when txn is nil) is a sensible, non‑intrusive convenience for callers.
database/plugin/metadata/sqlite/certs_test.go (1)
63-84: Loop style usingfor i := range 3is fine for this repoThe
for i := range 3loops used across these tests are valid and idiomatic given this project’s minimum Go version (≥1.23, using 1.24 in go.mod). No change needed here; the loops clearly express fixed small iteration counts.Based on learnings
Also applies to: 100-117, 151-167, 197-212, 261-275, 291-300, 316-324, 360-401
database/plugin/metadata/sqlite/utxo.go (1)
96-137: UTXO address/query refactor and input type change look soundThe new GetUtxosByAddress implementation is clearer: it always filters
deleted_slot = 0, then conditionally applies payment/staking key predicates based on what the address carries, and short‑circuits when neither key is present. The switch tolcommon.Address/lcommon.TransactionInputmatches the rest of the ledger/common usage in this PR without changing observable behavior.Also applies to: 230-252
database/models/transaction.go (1)
17-19: Outputs/CollateralReturn associations now correctly discriminate collateral-return UTXOsUsing
where:is_collateral_return = false/trueon theTransactionID→IDFK keeps the existing dual-FK UTXO pattern while ensuring theCollateralReturnrelation doesn’t randomly bind to a regular output. The import ofdatabase/typesforFee/TTLis also consistent with other monetary/slot-like fields. This addresses the earlier mis-association and casing issues without introducing new ones.Based on learnings
Also applies to: 26-36
database/models/auth_committee_hot.go (1)
17-27: AuthCommitteeHot schema aligns with unified certificate modelThe unique constraint on
ColdCredential, the unique index onCertificateID, and the shift ofAddedSlottotypes.Uint64all line up with the unified certificate + slot handling used elsewhere in the models. AssumingCertificateIDis now populated in the certificate persistence path, this struct definition looks consistent and ready for use; the only thing to keep in mind operationally is that the new uniqueness constraints can cause migrations to fail if any historical duplicates exist.database/models/resign_committee_cold.go (1)
17-30: ResignCommitteeCold uniqueness and slot typing look consistentThe clarified comment plus
gorm:"unique"onColdCredentialand a unique index onCertificateIDmake the intended 1:1 relationships explicit and enforceable at the DB layer, and movingAddedSlottotypes.Uint64matches the rest of the certificate-related models. Schema-wise this is coherent with the unified certificate design and should be safe as long as existing data doesn’t violate the new uniqueness constraints.database/plugin/metadata/sqlite/pool.go (1)
61-69: Pool registration fetch and mapping now match stored representationsSwitching the query to
pool_key_hash = ?withpkh.Bytes()avoids re-hashing the pool key when looking up registrations and should line up with how the key hash is stored onmodels.PoolRegistration. Likewise, settingCertTypeviauint(lcommon.CertificateTypePoolRegistration)and sourcingPledge/Costfromcert.Pledge.Val/cert.Cost.Valcorrectly reflects the move totypes.Uint64in the models and keeps the ledger-facing certificate struct using plain numeric values.Also applies to: 82-91
database/models/utxo.go (1)
25-39: Uint64 wrapper migration and collateral-return flag look consistentThe transition of
AddedSlot,DeletedSlot, andAmounttotypes.Uint64plus the addition ofIsCollateralReturnintegrates cleanly withUtxoLedgerToModel, and indexing/GORM tags remain coherent with prior UTXO usage. No issues spotted here.Also applies to: 58-60
internal/node/load.go (1)
34-39: Catch-up timeout logic correctly handles initialization and stallsThe new poll/stall defaults and progress-based wait loop correctly (a) special-case a tip stuck at slot 0 with its own timeout, (b) reset timers on forward progress, and (c) exit cleanly once
immutableTip.Slotis reached or a stall is detected. The behavior looks sound and avoids the previous “hang forever at slot 0” issue.Also applies to: 179-257
database/models/certificate.go (1)
152-175: Unified Certificate model matches the new persistence strategyThe
Certificatestruct (includingCertTypeandCertificateID) aligns with the unified-plus-specialized mapping used in the sqlite metadata layer, andTableName()/Type()give a clean public surface for querying. The commentary now correctly documents thatCertificateIDpoints at the per-type record.ledger/certs.go (1)
23-59: Centralized certificate deposit computation behaves as intended
CalculateCertificateDepositscleanly delegates to the era’sCertDepositFunc, returns a per-index map, and preserves zero-valued deposits instead of omitting them, which is important for downstream validation. Error reporting includes the certificate index, which will help diagnose protocol-parameter issues.ledger/state.go (2)
18-19: Context/WaitGroup orchestration and channel handling look correctThe introduction of
ctx,cancel, andwgonLedgerState, plus:
- starting
ledgerProcessBlocksandledgerReadChainunder the sharedwg,- checking
ls.ctx.Done()in both loops, and- having
ledgerReadChainbe the sole closer ofresultCh,eliminates the earlier close‑race on the channel and gives a clean, leak‑free shutdown path via
Close(). The concurrency and lifecycle wiring here looks solid.Also applies to: 80-107, 127-130, 176-180, 209-216, 542-644, 634-717
885-935: Precomputing certificate deposits and restoring strict TX validation are appropriateFor each transaction, deposits are now computed once via
CalculateCertificateDepositswhen certificates are present, and then passed intodelta.addTransactionWithDeposits, keeping deposit logic centralized. Validation has been returned to a fail‑fast behavior (ValidateTxFuncerrors now abort block processing instead of being logged and ignored), which matches expected ledger semantics. The interaction betweenshouldValidate, deposits (nil vs non‑nil), and delta application looks consistent.database/utxo.go (1)
24-26: Common-types alignment and collateral-return propagation are correctUpdating
UtxoConsume/UtxosByAddressto uselcommon.TransactionInputandlcommon.Address, and threadingIsCollateralReturnthrough the UTXO mapping while preserving the existing fields, keeps the DB API consistent with the newer ledger/common types and exposes the collateral-return flag to callers without changing prior behavior.Also applies to: 102-112, 114-147
database/plugin/metadata/sqlite/certs.go (1)
29-62: Unified certificate storage and account-state updates are comprehensively wired
storeCertificatenow:
- creates a unified
models.Certificaterow per ledger certificate,- writes fully-populated per-type rows (including pool margin/metadata, MIR reward payloads, Leios voter/signature JSON, anchors, etc.), and
- links both directions (
specialized.CertificateID = unified.ID,Certificate.CertificateID = specialized.ID),and
updateAccountStateconsistently maps all staking-key–driven certificate types ontoSetAccounttransitions (pool delegation, DRep delegation, activation/deactivation) while correctly no-oping for pool/DRep-only certs.This resolves the earlier gaps around under-populated models, missing
CertificateIDfields, and incomplete account updates.Also applies to: 64-513, 515-664
database/account.go (2)
21-27: GetAccount delegation to GetAccountWithInactive is clean and keeps behavior centralized.Forwarding through
GetAccountWithInactive(stakeKey, false, txn)is a straightforward way to preserve the existing API while adding the new flag; no issues here.
29-51: Confirm callers handleErrAccountNotFoundinstead of a nil account.
GetAccountWithInactivenow converts anilresult frommetadata.GetAccountintomodels.ErrAccountNotFound. If any existing callers relied on(nil, nil)or checked forgorm.ErrRecordNotFound, they need to be updated to look forErrAccountNotFound. Please confirm tests cover both the “found” and “not found” cases under the new behavior.database/plugin/metadata/sqlite/account.go (1)
25-49: includeInactive query behavior in GetAccount looks correct.The query builder adds
active = trueonly whenincludeInactiveis false and otherwise returns any matching account by staking key. Returning(nil, nil)onErrRecordNotFoundcleanly delegates “not found” semantics to the outer layer. This is consistent and looks good.database/plugin/metadata/store.go (1)
56-61: Interface changes for GetAccount and SetTransaction are consistent; verify all implementations/callers are updated.
GetAccountnow takes anincludeInactiveflag, matching the newdatabase.Account.GetAccountWithInactiveand the SQLite implementation.SetTransactionnow has signature(ocommon.Point, lcommon.Transaction, uint32, map[int]uint64, *gorm.DB), centralizing certificate deposit handling via thedepositsmap.Please double-check any non-SQLite
MetadataStoreimplementations (if any) and all call sites to ensure they passincludeInactiveand thedepositsmap correctly, and that deposit map indices align with the transaction’sCertificates()slice order (with missing keys effectively treated as zero).Also applies to: 127-133
2c4ba00 to
8f01e40
Compare
There was a problem hiding this 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)
database/models/utxo.go (1)
50-79: SetIsCollateralReturnon collateral return UTXOs ingested viautxo.goThe helper
UtxoLedgerToModeldoes not setIsCollateralReturn, relying on callers to set it explicitly. Whiledatabase/plugin/metadata/sqlite/transaction.go:103–104correctly handles this for collateral returns,database/plugin/metadata/sqlite/utxo.go:216creates UTXO models without checking or setting the flag. This causes collateral return UTXOs ingested through this path to be marked as regular UTXOs (IsCollateralReturn=false), resulting in incomplete query results.Either derive
IsCollateralReturnfrom the ledger UTXO type (e.g., via a dedicated interface), or update the caller atutxo.go:216to check if the UTXO is a collateral return and set the flag accordingly.
🧹 Nitpick comments (8)
database/certificate.go (1)
21-35: Auto-creating read-only transactions for getters looks saneThe new doc comment and the
txn == nilbranches in both getters make the API easier to use and avoid nil dereferences while keeping explicit transaction control for callers that need it. IgnoringCommiterrors is consistent with treating these as read-only metadata lookups; if you ever see flaky reads in this path, consider at least logging commit failures for the auto-created transaction.Also applies to: 37-47
database/plugin/metadata/sqlite/certs_test.go (1)
497-749: CertificatePersistenceValidation subtests cover key invariantsThe grouped subtests validate:
StakeDeregistrationcomposite uniqueness on(staking_key, certificate_id).- Manual cleanup behavior for specialized and mapping tables (using
certificate_idand mapping IDs).- Account
Activeflag transitions.types.Uint64persistence/round‑trip on stake registration and deregistration, including within an explicit gorm transaction.The only minor nit is that the “end‑to‑end conversion through store functions” comment in the last subtest slightly overstates things—you’re still constructing
StakeRegistrationdirectly rather than calling the higher‑level store—though the DB‑level conversion check is still useful.database/plugin/metadata/sqlite/account_test.go (1)
28-287: Account and DeleteCertificate tests provide good coverage of new behaviorThese tests nicely exercise:
SetAccountcreate/update semantics, including theincludeInactiveflag.- Transaction scoping for
SetAccountandGetAccountwhen using an explicit GORM transaction.DeleteCertificatecascading removal of both the unified certificate row and the linkedStakeRegistrationrow, plus the non-existent delete case.One possible extension (optional): add a focused test that drives
SetAccountvia the real certificate flow (non‑zerocertificateID) and asserts that account rows are correctly linked to the originating certificate, to lock in the new unified-cert mapping.database/database_test.go (1)
76-176: Consider exercisingDatabase.UtxosByAddressdirectly for the copy-path checkThe test does a good job validating that:
GetUtxosByAddressreturns the UTXO withIsCollateralReturn == true, and- a field‑by‑field copy preserves that flag.
For the second part, you might tighten the contract by calling the real
database.UtxosByAddresshelper instead of manually constructingcopiedUtxo. That way the test will fail if the production mapping ever forgets to propagateIsCollateralReturnor other fields.database/models/certificate.go (1)
94-150: CertificateType Value/Scan implementation looks solid; max range is the only minor couplingValue/Scan correctly satisfy the driver.Valuer/sql.Scanner contracts, handle the common DB-returned types, and guard against overflow and obviously invalid values. The only small coupling is
maxCertificateTypeValue = 1000; if new certificate types are added with higher numeric IDs, you’ll need to bump this constant, but that’s an acceptable trade-off for early validation.database/plugin/metadata/sqlite/certs.go (2)
64-513: Unified certificate storage and account-state updates look correct and completeThe
storeCertificateflow now cleanly implements the unified mapping:
- Unified
models.Certificaterow created first, withCertTypeandCertIndexset.- Each specialized model is inserted with
CertificateID: unifiedCert.ID, and all previously-missed fields (pool margin/metadata/owners/relays, anchors for committee/DRep, MIR payload, Leios payload, etc.) are now populated.- After insert,
unifiedCert.CertificateIDis updated to the specialized row’sID, giving a symmetric link that matches the models’ uniqueness constraints.updateAccountStateonly touches staking/DRep-related accounts and correctly wires pool and DRep targets from the certificate, leaving non-account certificates as no-ops.The Deregistration certificate path now uses
c.Amountinstead of the deposit map, which fixes the earlier loss of deregistration amounts; clamping negative amounts to zero is defensively safe, though you could choose to treat a negative value as an error if you ever want to surface upstream inconsistencies.Also applies to: 515-664
666-823: DeleteCertificate now deletes specialized rows by unified certificate ID; consider pool owner/relay cleanupDeleting specialized records via
certificate_id = cert.IDmatches howstoreCertificatepopulates per-typeCertificateIDfields, so cascading deletes now align with the unified mapping table. The exhaustive switch overCertTypeplus an explicit error for unknown types makes future additions easier to catch in tests.If you ever decide that
PoolRegistrationOwner/PoolRegistrationRelayrows should be physically removed when a pool registration certificate is deleted (rather than kept as historical orphans), you’ll need an extra delete step or DB-level cascade on those associations.database/plugin/metadata/sqlite/transaction.go (1)
343-424: Re‑inclusion only updates certificate slots, not BlockHashOn re‑inclusion (
alreadyExists && blockChanged), onlycertificates.slotand specializedadded_slotfields are updated;certificates.block_hashremains pointing at the old block. If any consumers rely onCertificate.BlockHash(rather than joining viaTransactionID) to attribute certificates to blocks, this can leave stale block references after a reorg.If block‑level queries will ever use
certificates.block_hash, consider extendingupdateCertificateSlotsto also update the block hash (e.g., acceptnewBlockHash []byteand update bothslotandblock_hashin the same helper), or document thatblock_hashon the mapping is not re‑inclusion aware.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (44)
blockfetch.go(1 hunks)database/account.go(1 hunks)database/certificate.go(1 hunks)database/database_test.go(2 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/blob/aws/database.go(1 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/account_test.go(1 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(2 hunks)database/plugin/metadata/sqlite/pool.go(2 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(3 hunks)database/plugin/metadata/store.go(3 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(4 hunks)go.mod(4 hunks)internal/config/config.go(2 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(12 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (2)
- database/drep.go
- database/models/certs.go
🚧 Files skipped from review as they are similar to previous changes (7)
- internal/config/config_test.go
- blockfetch.go
- database/models/certificate_test.go
- internal/config/config.go
- ledger/view.go
- go.mod
- database/models/leios_eb.go
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/database_test.goledger/state.godatabase/plugin/metadata/sqlite/certs_test.godatabase/models/utxo.godatabase/models/transaction.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/database_test.godatabase/plugin/metadata/sqlite/certs_test.godatabase/models/utxo.godatabase/plugin/metadata/sqlite/transaction.godatabase/models/transaction.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/database_test.goledger/state.godatabase/plugin/metadata/sqlite/certs_test.godatabase/models/utxo.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/database_test.goledger/state.godatabase/plugin/metadata/sqlite/certs_test.godatabase/utxo.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/database_test.godatabase/utxo.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/transaction.godatabase/plugin/metadata/sqlite/utxo.godatabase/models/utxo.godatabase/models/genesis_key_delegation.godatabase/plugin/metadata/sqlite/database.godatabase/plugin/metadata/sqlite/transaction.goledger/delta.godatabase/models/transaction.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/models/utxo.godatabase/models/transaction.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
ledger/state.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.
Applied to files:
ledger/state.go
📚 Learning: 2025-11-20T02:07:07.043Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 0
File: :0-0
Timestamp: 2025-11-20T02:07:07.043Z
Learning: In the blinklabs-io/dingo repository, range over int is valid Go syntax. The repository uses Go 1.24.0 (minimum Go 1.23), and range over int has been supported since Go 1.22. This syntax should not be flagged as invalid.
Applied to files:
database/plugin/metadata/sqlite/certs_test.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/utxo.godatabase/models/transaction.godatabase/utxo.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/utxo.go
🧬 Code graph analysis (25)
database/database_test.go (4)
database/plugin/metadata/sqlite/database.go (1)
New(53-126)database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/models/models.go (3)
database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(269-277)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(79-107)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
database/models/move_instantaneous_rewards.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/utxo.go (1)
database/models/utxo.go (1)
Utxo(24-40)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/certs_test.go (11)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(66-68)database/models/account.go (7)
StakeRegistration(115-121)StakeRegistration(123-125)Deregistration(68-74)Deregistration(76-78)StakeDeregistration(104-109)StakeDeregistration(111-113)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (5)
Certificate(157-165)Certificate(168-170)CertificateTypeStakeRegistration(32-34)CertificateType(28-28)CertificateTypeLeiosEb(89-91)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
database/plugin/metadata/sqlite/account_test.go (5)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/account.go (3)
Account(27-35)StakeRegistration(115-121)StakeRegistration(123-125)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
database/plugin/metadata/sqlite/drep.go (1)
database/models/drep.go (1)
Drep(21-29)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(66-68)
database/models/genesis_key_delegation.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/certs.go (11)
database/models/certificate.go (5)
CertificateTypeStakeRegistration(32-34)Certificate(157-165)Certificate(168-170)CertificateType(28-28)CertificateTypeLeiosEb(89-91)database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-50)database/models/account.go (20)
StakeRegistration(115-121)StakeRegistration(123-125)StakeDeregistration(104-109)StakeDeregistration(111-113)StakeDelegation(92-98)StakeDelegation(100-102)Registration(80-86)Registration(88-90)Deregistration(68-74)Deregistration(76-78)StakeRegistrationDelegation(127-134)StakeRegistrationDelegation(136-138)VoteDelegation(167-173)VoteDelegation(175-177)StakeVoteDelegation(140-147)StakeVoteDelegation(149-151)VoteRegistrationDelegation(179-186)VoteRegistrationDelegation(188-190)StakeVoteRegistrationDelegation(153-161)StakeVoteRegistrationDelegation(163-165)database/types/types.go (2)
Uint64(66-68)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(66-68)
database/models/asset.go (1)
database/types/types.go (1)
Uint64(66-68)
database/types/types_test.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/pool.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
database/models/auth_committee_hot.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/database.go (1)
database/plugin/log.go (1)
Logger(18-27)
database/plugin/metadata/sqlite/transaction.go (5)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-50)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/utxo.go (1)
UtxoLedgerToModel(50-79)database/models/certificate.go (10)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeRegistrationDrep(74-76)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(157-165)Certificate(168-170)
database/account.go (1)
database/models/account.go (1)
Account(27-35)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/plugin/metadata/sqlite/account.go (1)
database/models/account.go (1)
Account(27-35)
database/models/transaction.go (2)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
⏰ 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). (3)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (49)
database/plugin/blob/aws/database.go (1)
69-71: LGTM!The multi-line formatting improves readability. No functional change.
database/models/account_test.go (1)
29-30: LGTM!Field reordering within the test struct improves organization. No functional change.
database/models/transaction.go (1)
17-19: LGTM!The changes correctly filter outputs and collateral returns using the
IsCollateralReturncolumn with proper snake_case naming in thewhere:clauses. Field reordering and import formatting are cosmetic improvements.Also applies to: 26-26, 29-29, 34-35
ledger/delta.go (1)
25-29: LGTM!The deposits handling is correctly implemented:
- Invalid transactions pass
nildeposits (aligned with Cardano ledger rules)- Valid transactions calculate deposits only when certificates are present
- Clean integration with the unified certificate persistence system
Also applies to: 37-47, 61-100
ledger/state.go (3)
104-106: LGTM - Clean lifecycle management!The goroutine lifecycle management is properly implemented with context cancellation and waitgroup synchronization. This enables graceful shutdown of background processing.
Also applies to: 127-128, 176-180, 210-217
542-543: LGTM - Correct channel closing pattern!The channel is now closed only by the producer (
ledgerReadChainviadefer close(resultCh)), while the consumer checksctx.Done()for cancellation. This eliminates the "send on closed channel" race condition.Also applies to: 636-644, 656-661, 697-717
890-925: LGTM - Correct deposit pre-calculation!Deposits are calculated before validation when certificates are present, and validation errors are properly returned (not swallowed). This aligns with the unified certificate persistence approach.
internal/node/load.go (1)
34-39: LGTM - Robust catchup implementation!The ticker-based catchup loop with progress tracking and stall detection is well-implemented:
- Uses defined constants for timeouts
- Handles slot 0 initialization with timeout (prevents infinite hangs)
- Tracks progress and detects stalls correctly
- Clean cancellation handling
All past review concerns have been addressed.
Also applies to: 179-259
database/models/asset.go (1)
73-73: LGTM!Correctly uses the new
types.Uint64struct syntax, consistent with the type system changes across the codebase.database/types/types_test.go (1)
34-34: LGTM - Correct type usage and enhanced test coverage!The struct literal syntax aligns with the new
types.Uint64definition, and the additional[]bytescanning test improves coverage.Also applies to: 82-92
database/models/pool.go (1)
55-55: CertificateID‑scoped unique indexes align with unified certificate mappingMaking
CertificateIDunique for bothPoolRegistrationandPoolRetirementgives you a clean 1:1 relation between each unified certificate row and its per-type row, while leavingPoolKeyHash/Epochas non-unique indexes. That matches Cardano semantics and avoids the earlier constraint issues around re-registrations and multiple retirements.Also applies to: 91-98
database/models/models.go (1)
29-31: New certificate-related models correctly wired into migrationsRegistering
GenesisKeyDelegation,LeiosEb, andMoveInstantaneousRewardsinMigrateModelsensures their tables and indexes are created alongside existing certificate/pool tables. Ordering here looks compatible with the rest of the model set.database/types/types.go (2)
36-63: Rat.Scan improvements handle NULL and int64 cleanlyExtending
Rat.Scanto toleratenilandint64while keeping string/[]byte support makes it more robust against different driver behaviors, and the “keep zero-value rat when column is NULL” behavior is clearly documented. This looks correct and side‑effect free.
65-101: Uint64 struct + Scan/Value implementation looks consistent with intended DB semanticsWrapping
uint64in aUint64{Val}struct with explicitValue/Scan:
- Gives you a single place to encode/decode DB values.
- Handles
NULLdeterministically as zero (and documents that choice).- Accepts the common DB representations: string, []byte, int64.
Assuming the “NULL means 0” decision matches your domain expectations for slots/amounts, this implementation looks solid.
database/models/move_instantaneous_rewards.go (1)
1-34: MoveInstantaneousRewards model matches unified MIR storage designThe MIR model cleanly captures the certificate payload (JSON reward map, source pot, other-pot amount, added slot) and uses a unique index on
CertificateIDfor 1:1 mapping with the unified certificates table. Usingtypes.Uint64forAddedSlotkeeps it consistent with the new Uint64 wrapper, and dropping any composite uniqueness on Source/OtherPot/AddedSlot avoids the prior collision issue.database/plugin/metadata/sqlite/certs_test.go (11)
20-42: Helper setup for CBOR and deposits looks solid
testTransactionCBORHex,decodeTestTransaction, andgetTestDepositsgive a clean, reusable basis for all the SetTransaction-based tests and correctly guard decode errors viarequire.NoError. No issues here.
44-132: Pool registration/retirement tests align with updated uniqueness semanticsThe pool tests now deliberately allow multiple registrations and retirements for the same pool (different
CertificateID/ slots) and assert on counts instead of expecting uniqueness failures. This matches the currentPoolRegistration/PoolRetirementschema where uniqueness is per-certificate, not per key/epoch, and avoids the previous false positives from reusing structs.
134-242: Stake registration/deregistration uniqueness tests correctly target composite indexThese tests now:
- Insert three rows per staking key with distinct
CertificateIDvalues and assert all succeed.- Then assert that reusing the same
(staking_key, certificate_id)pair fails for bothStakeRegistrationandDeregistration.That cleanly exercises the
uniq_*_certcomposite unique indexes and avoids coupling to primary-key behavior.
244-339: DRep re-registration flows are properly validatedThe DRep tests confirm that:
- Multiple
RegistrationDrep,DeregistrationDrep, andUpdateDreprows can exist for the sameDrepCredential.- There are no uniqueness constraints blocking legitimate re-registration/update flows.
This directly guards the regression that was previously called out around over‑strict uniqueness on DRep credential fields.
341-423: Genesis key delegation idempotency test mirrors store behaviorThe
TestGenesisKeyDelegationCertificateIdempotenttest accurately reproduces theOnConflict(...).DoNothinginsert pattern and verifies:
- Only the first insert allocates an ID.
- Subsequent inserts succeed but all lookups resolve to the same row/ID.
- Exactly one row exists for the
(genesis_hash, genesis_delegate_hash, vrf_key_hash)triple.Good coverage of the intended idempotent semantics.
425-495: AuthCommitteeHot test now validates CertificateID usage and uniqueness
TestAuthCommitteeHotCertificateIdPopulationproves that:
CertificateIDis populated on creation and round‑trips correctly from the DB.- The
ColdCredentialuniqueness constraint prevents a second auth‑hot row for the same cold credential.This directly addresses the earlier concern that
CertificateIDmight be dead/unused in this model.
751-859: SetTransaction idempotency and re‑inclusion behavior well covered
TestTransactionCertificateIdempotencynow drives everything throughmetadata.SetTransactionwith:
- An initial inclusion.
- A second call with the same point (strict idempotency on count and slot).
- A third call with a different point (re‑inclusion), asserting stable counts and updated
Slotfor all certificates.This is exactly the sort of black‑box coverage needed for the unified certificate persistence path.
861-1051: Table‑driven SetTransaction tests exercise error, idempotent, and re‑inclusion paths
TestCertificateProcessingDataIntegrity’s table‑driven cases now:
- Check the failure path when deposits are omitted (including message text).
- Verify one‑shot processing when called once.
- Verify idempotency when called twice with the same point.
- Verify that a second call with a different point either leaves counts unchanged on failure or also updates slots on success.
The final
expectedSlotlogic, gated onreprocessingSucceeded, correctly matches the comments and avoids the earlier “acceptable failure” vs assertion mismatch.
1053-1096: Certificate Slot field test is straightforward and correct
TestCertificateSlotFieldis a clean sanity check that certificates persisted viaSetTransactionall carry the block slot from thePoint. It complements the more complex idempotency tests without duplicating logic.
1098-1400: Deposit and data‑mapping tests for DRep/MIR/Leios/anchor-bearing certs are appropriateThe remaining tests verify:
DeregistrationDrep.DepositAmountround‑trips astypes.Uint64.MoveInstantaneousRewards,LeiosEb,ResignCommitteeCold,RegistrationDrep, andUpdateDreppersist and retrieve their JSON/blob and anchor fields exactly.These are good low‑level guards around the new certificate‑adjacent models’ field mappings.
1402-1525: DeleteCertificate type coverage test effectively guards switch completeness
TestDeleteCertificateTypeCoverageensures:
- All known
CertificateTypeconstants can be inserted and deleted viaDeleteCertificatewithout error.- An unknown type (999) yields a clear, type‑specific error message.
This gives a practical safety net that the DeleteCertificate switch is kept in sync as new certificate types are added. Just remember to extend
knownTypeswhenever a newCertificateType*constant appears.database/models/drep.go (1)
17-72: DRep model/tag adjustments now match intended lifecycle semantics
Drep.DrepCredentialremainsuniqueIndex, giving a single authoritative DRep row per credential.RegistrationDrep,DeregistrationDrep, andUpdateDrepnow use plainindextags, allowing multiple historical records per credential (as exercised byTestDrepCertificateReRegistration).- Deposit fields use
types.Uint64consistently.This layout matches the Cardano DRep lifecycle and the new certificate persistence tests.
database/plugin/metadata/sqlite/utxo.go (2)
21-24: GetUtxosByAddress refactor matches Utxo schema and filters correctlyThe refactored
GetUtxosByAddress:
- Always filters on
deleted_slot = 0.- Uses
payment_key/staking_keycolumn names that matchmodels.Utxo’sPaymentKey/StakingKeyfields.- Distinguishes four cases (both PKH+SKH, PKH only, SKH only, neither) and returns an empty slice when there are no usable keys.
This addresses the prior field‑name mismatch and makes the selection logic much clearer.
Also applies to: 96-137
230-252: SetUtxoDeletedAtSlot signature and query are consistent with model indicesSwitching to
lcommon.TransactionInputand querying bytx_id/output_idxis consistent with how UTXOs are keyed and howUtxoBlobKeyis constructed. The update is scoped to thedeleted_slotfield only, so it’s safe for concurrent readers relying on other indices.database/models/auth_committee_hot.go (1)
17-27: AuthCommitteeHot model now fits unified certificate + slot conventions
ColdCredentialisunique, preventing multiple hot entries per cold credential.CertificateIDprovides a unique linkage into the unified certificates table.AddedSlotnow usestypes.Uint64and is indexed, aligning with other certificate‑adjacent models.TestAuthCommitteeHotCertificateIdPopulationexercises these fields, so the struct is no longer at risk of being “write‑only”.Looks consistent with the rest of the certificate persistence design.
database/transaction.go (1)
22-63: SetTransaction transaction management and deposit threading look correct
- When
txnis nil, a newTxnis created and a single deferred closure decides betweenRollbackandCommitbased on the namederr, eliminating the earlier “commit after rollback” risk.- Produced UTXOs are written to the blob store first, with immediate failure propagation.
d.metadata.SetTransactionis then invoked with the newdepositsmap andtxn.Metadata(), keeping blob and metadata changes within the same transaction when one is created here.- Callers that pass their own
*Txnremain responsible for commit/rollback.This aligns well with the new certificate/deposit semantics and previous review feedback.
database/models/resign_committee_cold.go (1)
21-29: ResignCommitteeCold constraints and types look consistent with unified cert mappingColdCredential uniqueness plus a unique index on CertificateID give you both “one resignation per cold credential” and a 1:1 link from the unified certificates table. Using
types.Uint64for AddedSlot is consistent with the rest of the schema and the new Scanner/Valuer-based uint64 wrapper.database/models/genesis_key_delegation.go (1)
21-34: GenesisKeyDelegation indexing matches intended idempotent insertsThe composite
uniqueIndex:uniq_genesis_delegationover genesis hash, delegate hash, and VRF key plus a separate unique index on CertificateID gives you clear idempotency and a 1:1 link from the unified certificates table. AddedSlot usingtypes.Uint64keeps the schema aligned with other certificate models.database/models/utxo.go (1)
23-40: Utxo schema updates align with Uint64 wrapper and collateral-return trackingSwitching AddedSlot/DeletedSlot/Amount to
types.Uint64and adding an indexedIsCollateralReturnflag are consistent with the rest of the schema (e.g., assets) and give you explicit support for collateral-return outputs while keeping queryability via indices.database/models/certificate.go (1)
152-175: Unified Certificate model matches the new persistence designThe
Certificatestruct (with(TransactionID, CertIndex)unique index andCertTypediscriminator) lines up withstoreCertificate/DeleteCertificateusage and the polymorphicCertificateIDpattern. The mapping table should give you efficient lookups without forcing heavy joins, and the comments now accurately describe the linkage.database/plugin/metadata/sqlite/pool.go (1)
61-91: Pool registration lookups and reconstruction are now consistent with stored dataUsing
pkh.Bytes()in both transactional and non-transactional branches matches howPoolKeyHashis written viac.Operator.Bytes(), and mappingPledge/Costfromcert.Pledge.Val/cert.Cost.Valcorrectly unwraps the model’stypes.Uint64fields back to the ledger types. The explicituint(lcommon.CertificateTypePoolRegistration)cast also alignsCertTypewith the field’s concrete type.database/plugin/metadata/sqlite/certs.go (1)
29-62: Stake registration CertType cast matches the new enum usageSetting
CertType: uint(lcommon.CertificateTypeStakeRegistration)is consistent with the field’s concrete type onlcommon.StakeRegistrationCertificate, while the rest of the mapping (staking key only) preserves the previous behavior.database/utxo.go (1)
24-26: UTxO APIs now align with ledger/common types and preserve collateral-return flagsSwitching
UtxoConsume/UtxosByAddressto uselcommon.TransactionInputandlcommon.Addresskeeps the DB API consistent with the rest of the ledger stack, and copyingIsCollateralReturnthrough into the returnedmodels.Utxoensures the new flag survives all the way out of the metadata layer. The CBOR loading path is unchanged.Also applies to: 102-112, 115-147
ledger/certs.go (1)
23-60: Certificate deposit calculation matches the documented behavior
CalculateCertificateDepositsnow delegates per-certificate logic tols.currentEra.CertDepositFuncand records an entry for every certificate index (including zero-value deposits), which aligns with the comment and lets downstream persistence distinguish “no deposit” from “missing entry”. The indexed error wrapping also makes it clear which certificate caused a failure; just ensureCertDepositFuncis defined for all eras you support.database/plugin/metadata/sqlite/transaction.go (2)
52-145: SetTransaction idempotency and deposit handling look soundThe new
SetTransactionflow correctly distinguishes first-seen vs replayed transactions using the pre-insert hash count, avoids re‑creating UTXOs whenalreadyExists, and gates certificate processing/deposit validation to the “new transaction” case. The earlytx.IsValid()/len(certs) == 0checks and the per‑typerequiresDepositguard give a clear, predictable contract fordepositswithout reintroducing the earlier zero‑deposit regressions. TheON CONFLICTupsert plusIDreload covers the “update existing row” path cleanly.No changes requested here.
427-476: Verify LeiosEb table name used inupdateCertificateSlotsThe
certTablesslice includes"leios_eb". That needs to match the actual table name formodels.LeiosEb(either viaTableName()or GORM’s default pluralization). If the table is named"leios_ebs"instead, Leios EB certificates won’t get theiradded_slotupdated on re‑inclusion.Please double‑check the
LeiosEbmodel’sTableNameand align this string if needed.database/plugin/metadata/sqlite/drep.go (1)
25-77: DRep lookup and upsert behavior are consistent and robustReturning
(*models.Drep, error)with(nil, nil)for not‑found and usingerrors.Isaroundgorm.ErrRecordNotFoundis a clean API.SetDrep’sON CONFLICT (drep_credential)upsert correctly keepsAddedSlot, anchor fields, andActivein sync without losing data.Looks good as implemented.
database/models/account.go (1)
27-121: Composite certificate constraints align with intended lifecycleIndexing
Account.AddedSlot,CertificateID, andActive, and moving Deregistration/Stake(Der|Reg)istration to composite uniques on(StakingKey, CertificateID)match the described strategy: one row per certificate per staking key while allowing multiple historical certs per key. Other certificate tables remaining non‑unique onStakingKeyalso fits the “event history” role.Assuming migrations mirror these tags, this looks correct and resolves the earlier “one cert per staking key” constraint problem without losing history.
database/account.go (1)
21-50: GetAccountWithInactive nicely generalizes the lookup APIDelegating
GetAccounttoGetAccountWithInactive(..., false, ...)keeps the existing behavior while centralizing the include‑inactive logic. PassingincludeInactivethrough tometadata.GetAccountand normalizing “not found” tomodels.ErrAccountNotFoundmakes the higher‑level API predictable.No changes needed here.
database/plugin/metadata/sqlite/database.go (1)
36-311: Background vacuum/cleanup lifecycle is well‑coordinatedThe combination of
closed+timerMutex, per‑taskWaitGroups, and the guards inrunVacuum/runCertificateCleanupensures:
- No new timers are scheduled after
Close()- In‑flight vacuum/cleanup runs complete before the DB handle is closed
- In‑memory stores skip VACUUM via the
dataDir == ""checkThe certificate cleanup helper is intentionally a no‑op scaffold right now, but its scheduling and shutdown semantics are solid and ready for real logic when you add it.
database/plugin/metadata/sqlite/account.go (2)
25-49: GetAccount: includeInactive flag and query construction look correctThe conditional
active = truefilter matches the newincludeInactivesemantics, and the nil-txnhandling cleanly falls back tod.DB()while still returningnil, nilon not-found. No issues from a correctness standpoint.
51-86: SetAccount: find-or-create plus consolidated updates align with account modelUsing
FirstOrCreatekeyed onStakingKeyand then a singleUpdatescall forpool,drep,added_slot,active, andcertificate_idis consistent withmodels.Accountand the “last updated by certificate” semantics. Transaction handling via the optionaltxnparameter is also correct.database/plugin/metadata/store.go (2)
56-60: Account and transaction API extensions are coherent with the new certificate flowAdding
includeInactivetoGetAccountand extendingSetTransactionwith an explicit transaction index and deposits map gives callers the controls they need for unified certificate persistence while keeping the interface compact. Assuming implementations and call sites are updated (as indicated elsewhere in the PR), this looks solid.Also applies to: 127-133
143-147: UTXO helper signature changes align with the updated metadata modelThe updated
GetUtxosByAddressandSetUtxoDeletedAtSlotsignatures are consistent with the rest of the PR’s UTXO/model changes and keep the UTXO helpers focused and clear. No additional concerns here.
8f01e40 to
86dd722
Compare
There was a problem hiding this 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)
database/models/account.go (1)
27-58: Account.String() HRP is hard-coded to mainnet value; incorrect for testnets.Cardano testnets use "stake_test" HRP while mainnet uses "stake". The
Account.String()method hard-codes HRP to"stake", which will produce incorrect bech32 addresses when the application runs against testnet networks (preview, preprod). TheAccountstruct has no access to network configuration to determine the correct HRP at runtime.Fix: Either (1) add a network-aware parameter to
String()or (2) move bech32 encoding to a service layer that has access to network config. Alternatively, document thatString()is intended for mainnet-only and create a separate method for testnet encoding. Tests currently verify only"stake"HRP; add testnet variants if multi-network support is intended.
♻️ Duplicate comments (1)
blockfetch.go (1)
41-42: Clarify recv queue size comment vs literal.The comment says “2x our block batch size” but the configured value is 512. If 512 is now the intentional size (and #1020 will handle any batch size changes), consider updating the comment to reflect the actual relationship, or vice versa, to avoid future confusion.
🧹 Nitpick comments (7)
database/models/resign_committee_cold.go (1)
28-28: Remove the composite index name fromCertificateID.The
CertificateIDfield usesuniqueIndex:uniq_resign_committee_cold_cert, which suggests a composite unique constraint, but no other field shares this index name. Since bothCertificateIDandColdCredentialrequire separate unique constraints (1:1 relationship with certificates table and preventing duplicate resignations, respectively), the named index is misleading.Apply this diff to use a simple unique constraint:
- CertificateID uint `gorm:"uniqueIndex:uniq_resign_committee_cold_cert"` + CertificateID uint `gorm:"unique"`Alternatively, if a composite unique constraint on
(ColdCredential, CertificateID)is intended for a different business reason, add the same index name toColdCredential:- ColdCredential []byte `gorm:"unique"` // Unique constraint to prevent duplicate resignations + ColdCredential []byte `gorm:"uniqueIndex:uniq_resign_committee_cold_cert"` // Unique constraint to prevent duplicate resignationsdatabase/plugin/metadata/sqlite/certs_test.go (2)
341-423: Genesis key delegation idempotency test mirrors store behavior but could call the real API.Reusing the exact
OnConflictclause from the store to assert “one logical record despite repeated inserts” is accurate, but it does duplicate implementation details. If you later change the conflict target or strategy in the store, this test could silently drift. Consider, in a follow-up, adding an integration-style test that drives the same scenario via the production path (e.g., through whatever function ultimately persists genesis key delegation certificates) so behavior is verified even if the internal clause changes.If you refactor the store, please double-check both the store and this test still agree on which columns form the conflict target.
751-1051: Idempotency and re-inclusion tests are strong; you might also assert deposit effects.
TestTransactionCertificateIdempotencyandTestCertificateProcessingDataIntegritydo a good job checking thatSetTransactionis idempotent, doesn’t create duplicate certificate rows, and updatesSloton re-inclusion when the second call succeeds. If you want even more protection around the new deposits plumbing, you could add a small assertion that deposit-bearing specialized rows (e.g., stake registrations) have theirDepositAmountpopulated as expected afterSetTransaction, using the same CBOR transaction fixture.Verifying one or two representative deposit-bearing cert types against
getTestDeposits()would catch regressions in howdeposits map[int]uint64is threaded intoSetTransactionand the SQLite store.database/plugin/metadata/sqlite/account_test.go (1)
311-370: Optional: avoid rolling back transactions that you explicitly commit in testsIn
TestSetAccountWithCertificateLinkage,txnis committed and also deferred for rollback. GORM tolerates this, but it’s slightly misleading when reading the test. Consider either dropping thedefer txn.Rollback()in tests that always commit, or wrapping it in adeferthat only rolls back on error to better reflect the intended control flow.ledger/state.go (1)
80-107: Cancel context on fatal errors inledgerProcessBlocksto avoid leakingledgerReadChainThe new
ctx/cancel/wgwiring plus havingledgerReadChaincloseresultChis a solid improvement for normal Start/Close lifecycles. However, whenledgerProcessBlocksreturns early due to errors (e.g., epoch rollover failure orledgerProcessBlockerror),ls.ctxis not canceled, soledgerReadChainkeeps running and will eventually block onresultCh <- resultwith no receiver. Consider callingls.cancel()(or otherwise canceling the context) before these early returns so the reader goroutine and any other ctx-aware code shut down cleanly on fatal errors.Also applies to: 127-129, 132-182, 542-563, 636-644, 656-660, 695-717
database/plugin/metadata/sqlite/drep.go (1)
25-47: DRep lookup/upsert behavior is clear; confirmCertificateIDusageThe updated
GetDrep/SetDreppair looks good:
GetDrepnow returns(*models.Drep, error)and uses(nil, nil)for not-found, which is easier for callers to distinguish.SetDrepcleanly upserts ondrep_credential, updatingadded_slot, anchor fields, andactive.Given that
models.Drepstill has aCertificateIDfield, please confirm whether it’s intentionally decoupled from this helper now (e.g., managed only via certificate persistence), or whetherSetDrepshould also track the latest certificate ID; if the former, a brief comment or eventual field removal would help avoid confusion.Also applies to: 49-77
database/plugin/metadata/sqlite/transaction.go (1)
140-143: Consider refactoring to eliminate goto.The
gotoat line 142 skips UTXO processing for existing transactions. While this works correctly, consider refactoring to a helper function or early return pattern for improved readability.For example, extract UTXO processing into a helper:
// If transaction already existed, skip UTXO processing if !alreadyExists { if err := d.processUtxos(&tmpTx, tx, point, txHash, txn); err != nil { return err } } // Continue to certificate processing...
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (44)
blockfetch.go(1 hunks)database/account.go(1 hunks)database/certificate.go(1 hunks)database/database_test.go(2 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(1 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/blob/aws/database.go(1 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/account_test.go(1 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(2 hunks)database/plugin/metadata/sqlite/pool.go(2 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(4 hunks)database/plugin/metadata/store.go(3 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(4 hunks)go.mod(4 hunks)internal/config/config.go(2 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(12 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (2)
- database/models/certs.go
- database/drep.go
✅ Files skipped from review due to trivial changes (1)
- internal/config/config_test.go
🚧 Files skipped from review as they are similar to previous changes (12)
- database/models/pool.go
- database/types/types_test.go
- database/models/move_instantaneous_rewards.go
- database/models/leios_eb.go
- database/models/asset.go
- database/models/certificate.go
- database/models/genesis_key_delegation.go
- database/models/auth_committee_hot.go
- database/models/certificate_test.go
- database/models/drep.go
- database/types/types.go
- database/models/account_test.go
🧰 Additional context used
🧠 Learnings (14)
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
blockfetch.godatabase/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/utxo.godatabase/database_test.godatabase/models/transaction.godatabase/models/utxo.goledger/state.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
blockfetch.godatabase/plugin/metadata/sqlite/certs_test.godatabase/plugin/metadata/sqlite/transaction.godatabase/utxo.godatabase/database_test.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/utxo.godatabase/models/transaction.godatabase/models/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/sqlite/transaction.godatabase/transaction.godatabase/utxo.godatabase/models/transaction.goledger/delta.godatabase/models/utxo.godatabase/plugin/metadata/sqlite/database.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/utxo.godatabase/database_test.godatabase/models/utxo.goledger/state.godatabase/plugin/metadata/store.go
📚 Learning: 2025-11-20T02:07:07.043Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 0
File: :0-0
Timestamp: 2025-11-20T02:07:07.043Z
Learning: In the blinklabs-io/dingo repository, range over int is valid Go syntax. The repository uses Go 1.24.0 (minimum Go 1.23), and range over int has been supported since Go 1.22. This syntax should not be flagged as invalid.
Applied to files:
database/plugin/metadata/sqlite/certs_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/plugin/metadata/sqlite/certs_test.godatabase/utxo.godatabase/database_test.goledger/state.go
📚 Learning: 2025-11-20T19:58:38.844Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 0
File: :0-0
Timestamp: 2025-11-20T19:58:38.844Z
Learning: In Go, test dependencies (such as github.com/stretchr/testify) are correctly marked as direct dependencies in go.mod, not as indirect dependencies, even when they are only used in test files. This is a standard practice in Go's module system.
Applied to files:
go.mod
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
database/utxo.goledger/state.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/utxo.godatabase/database_test.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/utxo.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/utxo.godatabase/models/transaction.godatabase/models/utxo.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.
Applied to files:
ledger/state.go
🧬 Code graph analysis (23)
database/models/models.go (3)
database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(79-107)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
database/plugin/metadata/sqlite/utxo.go (1)
database/models/utxo.go (2)
Utxo(24-40)UtxoLedgerToModel(50-81)
database/plugin/metadata/sqlite/certs_test.go (11)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(66-68)database/models/account.go (7)
StakeRegistration(115-121)StakeRegistration(123-125)Deregistration(68-74)Deregistration(76-78)StakeDeregistration(104-109)StakeDeregistration(111-113)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (5)
Certificate(157-165)Certificate(168-170)CertificateTypeStakeRegistration(32-34)CertificateType(28-28)CertificateTypeLeiosEb(89-91)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/certs.go (10)
database/models/certificate.go (6)
CertificateTypeStakeRegistration(32-34)Certificate(157-165)Certificate(168-170)CertificateType(28-28)CertificateTypeStakeDeregistration(35-37)CertificateTypeLeiosEb(89-91)database/models/account.go (20)
StakeRegistration(115-121)StakeRegistration(123-125)StakeDeregistration(104-109)StakeDeregistration(111-113)StakeDelegation(92-98)StakeDelegation(100-102)Registration(80-86)Registration(88-90)Deregistration(68-74)Deregistration(76-78)StakeRegistrationDelegation(127-134)StakeRegistrationDelegation(136-138)VoteDelegation(167-173)VoteDelegation(175-177)StakeVoteDelegation(140-147)StakeVoteDelegation(149-151)VoteRegistrationDelegation(179-186)VoteRegistrationDelegation(188-190)StakeVoteRegistrationDelegation(153-161)StakeVoteRegistrationDelegation(163-165)database/types/types.go (2)
Uint64(66-68)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
database/plugin/metadata/sqlite/pool.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
database/plugin/metadata/sqlite/account_test.go (5)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/account.go (3)
Account(27-35)StakeRegistration(115-121)StakeRegistration(123-125)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/transaction.go (4)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/utxo.go (1)
UtxoLedgerToModel(50-81)database/models/certificate.go (10)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeRegistrationDrep(74-76)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(157-165)Certificate(168-170)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(269-277)
database/database_test.go (3)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
internal/config/config.go (2)
internal/node/load.go (2)
LedgerCatchupPollInterval(36-36)LedgerCatchupStallTimeout(38-38)config.go (1)
Config(33-55)
database/plugin/metadata/sqlite/drep.go (1)
database/models/drep.go (1)
Drep(21-29)
database/models/transaction.go (2)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(66-68)
ledger/view.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
database/plugin/metadata/sqlite/database.go (1)
database/plugin/log.go (1)
Logger(18-27)
ledger/state.go (1)
database/txn.go (1)
Txn(27-34)
database/account.go (3)
database/models/account.go (1)
Account(27-35)database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/account.go (1)
database/models/account.go (1)
Account(27-35)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
⏰ 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). (3)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (43)
database/models/models.go (1)
29-31: LGTM! Correct addition of new certificate models to migration list.The three new certificate models (
GenesisKeyDelegation,LeiosEb,MoveInstantaneousRewards) are properly added to enable database migrations. The placement correctly maintains dependency ordering—all three models reference theCertificatemodel (line 23) via theirCertificateIDfields, and they appear after it in the migration list.database/models/resign_committee_cold.go (4)
17-19: LGTM: Import addition is necessary.The import of
database/typesis required for thetypes.Uint64type used in theAddedSlotfield.
21-22: LGTM: Clear documentation.The struct comment clearly explains the purpose and the rationale for the unique constraint on
ColdCredential.
25-25: LGTM: Unique constraint enforces business logic.The unique constraint on
ColdCredentialcorrectly prevents duplicate resignation certificates for the same cold credential.
29-29: LGTM: Type change aligns with refactoring.The migration from
uint64totypes.Uint64is consistent with the broader refactoring to use wrapped types. The index onAddedSlotis appropriate for query performance.go.mod (3)
188-188: Security vulnerability fix confirmed for golang.org/x/text.Line 188 shows
golang.org/x/text v0.31.0, which addresses the known security vulnerabilities (CVE-2021-38561, CVE-2022-32149) that were present in v0.30.0. The upgrade is appropriate and necessary.
24-24: Testify addition correctly marked as direct dependency.The addition of
github.com/stretchr/testify v1.11.1as a direct requirement (line 24) aligns with Go best practices for test-related dependencies and supports the 19 new certificate test cases mentioned in the PR summary. The transitive dependencies (go-spewandgo-difflibat lines 97 and 158) are appropriately marked as indirect.
14-14: I need to search for the specific GitHub repository to find the release information. Let me query for the blinklabs-io/gouroboros project releases.Let me search more specifically for the v0.140.0 release.
Based on my web search, I cannot locate any public releases for gouroboros v0.139.0 or v0.140.0. The latest available version appears to be v0.129.0 (released July 21, 2025). Without access to release notes or changelog entries for the claimed versions, I cannot verify the assertions about certificate types, ledger protocol updates, or API changes mentioned in your review comment.
Verify the existence and contents of gouroboros v0.140.0 release or confirm the version numbers in go.mod.
The versions v0.139.0 and v0.140.0 do not appear to have public releases on GitHub or pkg.go.dev. Confirm whether these are unreleased development versions, pre-release tags, or if different version numbers should be used. If the upgrade is to an actual released version, manually verify the changelog to confirm alignment with the PR objectives around certificate handling (stake, pool, DRep, vote, committee, genesis, MIR, Leios), parameter type changes (uint64 → ocommon.Point), and ledger protocol updates.
database/plugin/blob/aws/database.go (1)
66-72: No behavior change in New; formatting looks fine.The tightened validation and multi-line
errors.New(...)preserve behavior while improving readability. Nothing to change here.ledger/view.go (1)
92-113: Pool registration view mapping matches new model types.Using
pool.Pledge.Val/pool.Cost.Valand castingCertificateTypePoolRegistrationtouintkeepsPoolRegistrationCertificatealigned with the updatedtypes.Uint64/CertificateType modeling. This looks consistent withmodels.Poolandmodels.CertificateTypePoolRegistration.database/models/transaction.go (1)
17-36: Collateral-return associations now correctly filtered.Using
where:is_collateral_return = false/trueonOutputsandCollateralReturnmatches theIsCollateralReturnboolean column onUtxoand keeps the dual-FK pattern intact. This should fix earlier “no such column” issues and cleanly separates regular outputs from the collateral return row.If you want extra assurance, you can run a quick migration/SQL dump to confirm the generated column name is indeed
is_collateral_returnand that preloadingOutputs/CollateralReturnemits valid SQL under your GORM/SQLite versions.database/models/account.go (1)
60-121: Composite unique constraints for stake certs match the documented strategy.The
(StakingKey, CertificateID)composite uniques onDeregistration,StakeDeregistration, andStakeRegistrationalign with the comment and allow multiple lifecycle events per staking key while preventing duplicate rows for the same certificate ID. This is a good balance between correctness and historical tracking.If you haven’t already, it’s worth running a quick migration against a test DB and verifying the generated index names/definitions match expectations (
uniq_*over both columns).database/certificate.go (1)
21-47: Helper transactions for certificate reads look good.Letting
GetPoolRegistrations/GetStakeRegistrationsauto-create a metadata transaction whentxnis nil makes these safe convenience APIs while still allowing callers to pass their ownTxnfor batching. The new comment clarifying thatSetTransactionis the write-path for certificates keeps the surface area coherent.database/plugin/metadata/sqlite/certs_test.go (2)
44-242: Certificate uniqueness tests now exercise the right constraints.The pool, stake registration, and deregistration tests now create a fresh model per iteration and vary
CertificateID, so they truly validate the(StakingKey/PoolKeyHash, CertificateID)uniqueness instead of tripping on primary-key reuse. This gives good coverage of the revised composite-constraint design.
1402-1525: DeleteCertificate coverage test gives good safety net for switch completeness.Enumerating all current
CertificateType*constants and verifyingDeleteCertificatedoesn’t error for them (plus expecting a specific error for an unknown type) is a nice guard against missing cases in the delete switch. Just keep in mind you’ll need to updateknownTypeswhenever new certificate types are added so this continues to reflect the full set.database/transaction.go (1)
22-63: SetTransaction’s unified blob/metadata transaction handling looks correct—confirm Transaction mode flag.The refactored
SetTransactionnow:
- Uses a named
errplus a single deferred closure to rollback on error or commit on success when it creates theTxnitself.- Writes all
tx.Produced()UTxOs into the blob store before delegating tometadata.SetTransaction.- Shares the same
Txnfor blob and metadata operations, which is the right shape for atomic persistence.The only small thing to double‑check is the
d.Transaction(false)call: in other code,falseis used for read‑style operations. If that parameter controls read/write mode, ensurefalseis indeed appropriate for write paths like this; otherwise, consider switching to the write-enabled mode.If you want to be sure, a quick grep of
Transaction(call sites and the implementation intxn.gowill confirm the intended meaning of the boolean flag.database/plugin/metadata/sqlite/utxo.go (1)
22-23: UTXO address lookup and type alignment look solid
GetUtxosByAddressnow cleanly derivespayment_key/staking_keypredicates fromlcommon.Addresswith a zero-hash guard and always enforcesdeleted_slot = 0, which matches themodels.Utxoschema.AddUtxosandSetUtxoDeletedAtSlotcorrectly adapt toUtxoLedgerToModel(..., isCollateralReturn bool)andlcommon.TransactionInputwithout changing behavior.Also applies to: 96-133, 216-247
database/database_test.go (1)
76-179: Good coverage ofIsCollateralReturnthrough metadata UTXO lookupThe test wires address components and stored key hashes consistently, then verifies that
GetUtxosByAddressreturns exactly one matching UTXO withIsCollateralReturnand all core fields preserved, plus a second copy step to guard against flag loss during mapping. This should catch regressions both in the SQL predicate and in future copy logic around the new flag andtypes.Uint64fields.database/plugin/metadata/sqlite/pool.go (1)
53-91: Pool registration lookup and value decoding are consistent with model typesUsing
pkh.Bytes()in thepool_key_hashWHERE clause matches the stored[]bytekey, and unwrappingcert.Pledge.Val/cert.Cost.Valcorrectly bridges fromtypes.Uint64in the model to the ledger certificate struct. The explicitCertTypevalue also keeps the mapping unambiguous.internal/config/config.go (1)
17-23: Ledger catchup timing config is wired cleanlyExposing
LedgerCatchupTimeout,LedgerCatchupPollInterval, andLedgerCatchupStallTimeoutastime.Durationfields with sensible defaults inglobalConfigaligns with howLoadininternal/node/load.goreads them and lets YAML/env overrides tune behavior without changing call sites.Also applies to: 46-68, 70-92
internal/node/load.go (1)
34-39: Catchup loop and stall detection are robust and configurableStarting ledger asynchronously via
ls.Start()and then using a ticker-driven loop with configurableLedgerCatchupPollInterval/LedgerCatchupStallTimeoutgives a clear, bounded wait forls.Tip()to reachimmutableTip.Slot. The explicit handling oftip.Point.Slot == 0withzeroSlotSeenAtavoids the previous “stuck at slot 0” hang, and thelastSlot/lastProgressTimetracking cleanly detects lack of progress before failing.Also applies to: 86-102, 179-257
ledger/delta.go (1)
25-29: Deposit handling now matches ledger semantics and supports precomputed mapsExtending
TransactionRecordwithDepositsand introducingaddTransactionWithDepositslets callers supply precomputed certificate deposits while still falling back toCalculateCertificateDepositswhen needed. Inapply, invalid transactions no longer compute or persist deposits and record withnildeposits, while valid ones correctly attach deposits only when certificates are present. This aligns with Cardano’s “no deposits for invalid txs” rule and keeps SetTransaction’s API consistent.Also applies to: 37-46, 49-89, 90-99
database/plugin/metadata/sqlite/account_test.go (1)
28-128: Comprehensive coverage for account lifecycle and certificate linkageThese tests exercise the key behaviors of the updated metadata layer: account creation/update and inactive handling, transactional visibility and rollback, certificate deletion cascading into
stake_registration, and linking accounts to originating certificates. The direct assertions against the underlying sqlite store and models provide good confidence that the new APIs and schema wiring behave as intended.Also applies to: 130-187, 189-287, 289-402
database/models/utxo.go (1)
23-40: UTxO model updates fortypes.Uint64and collateral-return flag are consistentSwitching
AddedSlot,DeletedSlot, andAmounttotypes.Uint64and wrapping assignments inUtxoLedgerToModelwithtypes.Uint64{Val: ...}matches the established pattern for typed amounts and slots. The newIsCollateralReturnfield is correctly threaded via theisCollateralReturnparameter and is exercised by the new tests that query by address and assert the flag survives mapping. Based on learningsAlso applies to: 50-63
database/utxo.go (1)
24-24: UTXO API now consistently uses common-layer types and preserves collateral-return flagSwitching
UtxoConsume/UtxosByAddresstolcommon.TransactionInput/lcommon.Addressand wiring throughIsCollateralReturninto the returnedmodels.Utxoslice is consistent with the rest of the PR and keeps the existing CBOR loading behavior intact. No issues spotted here.Also applies to: 102-112, 114-140
ledger/certs.go (1)
23-25: Certificate-deposit calculation looks correct and preserves zero entries
CalculateCertificateDepositsuses the era’sCertDepositFuncwithls.currentPParams, records an entry for every certificate index (including legitimate zero deposits), and returns a clear, indexed error on failure. This aligns with the documented Conway-era deposit behavior and fixes the prior “missing zero deposit” issue.Also applies to: 37-60
ledger/state.go (1)
885-904: Precomputing certificate deposits and threading them into delta looks correctComputing
depositsonce per transaction viaCalculateCertificateDepositsand passing them intodelta.addTransactionWithDeposits(withnilfor non-certificate TXs) cleanly separates deposit calculation from validation and persistence. Erroring out on deposit-calculation failures is appropriate, and the behavior for empty/zero-deposit maps is well defined.Also applies to: 905-926
database/plugin/metadata/sqlite/certs.go (2)
64-513: Unified certificate persistence and account-state updates look consistent
storeCertificatenow follows a clean pattern: create the unifiedcertificatesrow, insert the type-specific model withCertificateID: unifiedCert.ID, then backfillCertificate.CertificateIDwith the specific model’s primary key and invokeupdateAccountStateusing the unified ID. Per-type mappings (including pool margin/metadata, MIR data, Leios EB payloads, and delegation/DRep variants) are populated with the expected fields, and the SetAccount calls inupdateAccountStatemirror the staking/voting semantics. This resolves the earlier uniqueness and under-population issues and looks solid overall.
666-823: I need to check how migrations are defined and whether CASCADE constraints are configured. Let me search for the migration/schema setup:Now let me search for the PoolRegistration struct definition to see if CASCADE constraints are defined:
Let me now search the codebase directly for the PoolRegistration struct definition and GORM constraint tags:
Based on my analysis of the web search results and the code output, I can now assess the review comment. Let me search for the actual PoolRegistration model definition in the codebase with full tag information:
Based on my thorough verification using web search, I can now provide a comprehensive assessment. The review comment raises a legitimate concern about cascading deletes for related records. GORM AutoMigrate creates database foreign key constraints automatically and creates missing foreign keys, constraints, columns and indexes. However, every new connection to a sqlite database needs to explicitly turn on foreign key enforcement for that connection. Additionally, you can setup OnUpdate, OnDelete constraints with tag constraint, and it will be created when migrating with GORM.
The review comment is accurate in identifying that:
- The
DeleteCertificatefunction deletesPoolRegistrationrecords viacertificate_id- Related tables like
PoolRegistrationOwner,PoolRegistrationRelay, and potentiallyMoveInstantaneousRewardspayloads depend on these records- Without explicit
ON DELETE CASCADEconstraints at the database level (or manual deletes), orphaned records may remainVerify whether CASCADE constraints are defined in PoolRegistration and related model relationships
Check the full PoolRegistration model definition for constraint tags on the
OwnersandRelaysrelationship fields. If neitherconstraint:OnDelete:CASCADEtags are present in the model nor explicit deletes are performed inDeleteCertificate, this represents a data integrity risk.Additionally, confirm that PRAGMA foreign_keys is enabled at database connection initialization to ensure that any CASCADE constraints actually function in SQLite (which disables foreign key enforcement by default).
database/plugin/metadata/sqlite/transaction.go (5)
18-18: LGTM: Import and signature changes support new functionality.The
bytesimport enables the re-inclusion check, and the updatedSetTransactionsignature properly adds the deposits map parameter with clear documentation.Also applies to: 53-59
65-83: LGTM: Idempotency check correctly positioned.The existence check now runs before the upsert, allowing certificate processing to proceed on first insert. The re-inclusion detection properly handles transactions moving between blocks.
114-137: LGTM: Transaction ID properly hydrated after upsert.The ID reload at lines 132-137 ensures
tmpTx.IDis populated even when the ON CONFLICT path is taken, addressing the critical issue identified in past reviews.
426-483: LGTM: Certificate slot updates properly handle re-inclusion.The
updateCertificateSlotshelper correctly updates both the unifiedcertificatestable and all specialized certificate tables, includingleios_ebwhich was previously missing.
377-421: Now let me search for more specific information about Conway era certificate types and deposits:Now let me verify the exact struct names in the account model to map them to certificate types:
Based on my verification using web searches and protocol documentation, the deposit-requiring certificate types listed in the code are complete and accurate. Registration certificates require deposits matching ppKeyDepositL in protocol parameters. The combined registration+delegation certificates (StakeRegistrationDelegation, VoteRegistrationDelegation, StakeVoteRegistrationDelegation) all require deposits. DRep registration requires a 500 ada refundable deposit. Pool registration also requires deposits per the protocol. Notably, delegation-only certificates and deregistration certificates do not require deposits (deregistrations return deposits instead).
The list of deposit-requiring certificate types is complete and correctly validated.
database/account.go (1)
22-51: LGTM: Clean delegation pattern for inactive account support.The
GetAccountmethod now delegates toGetAccountWithInactivewithincludeInactive=false, maintaining backward compatibility while adding the flexibility to include inactive accounts when needed. The error handling correctly returnsErrAccountNotFoundwhen the account is nil.database/plugin/metadata/sqlite/database.go (3)
36-50: LGTM: Struct fields properly support lifecycle management.The new fields (
timerCertificateCleanup,vacuumWG,certificateCleanupWG,dataDir, etc.) enable proper tracking and coordination of background operations and clean shutdown.
190-246: LGTM: Certificate cleanup infrastructure in place with clear placeholder.The
runCertificateCleanupandscheduleCertificateCleanupfunctions establish the scheduling infrastructure with clear TODO comments for future implementation. The 6-hour cleanup interval is reasonable.
253-279: LGTM: Close properly orchestrates shutdown.The
Closemethod now correctly stops both timers and waits for in-flight vacuum and certificate cleanup operations to complete before closing the database handle. This addresses the critical issue from past reviews about background goroutine leaks.database/plugin/metadata/sqlite/account.go (2)
26-49: LGTM: GetAccount properly supports inactive account filtering.The conditional query construction at lines 36-41 correctly filters by
active = trueonly whenincludeInactiveis false, providing the flexibility needed for different query scenarios.
52-86: LGTM: SetAccount refactored to cleaner pattern.The rewrite uses
FirstOrCreatefollowed byUpdates, which is more straightforward than the previousOnConflictapproach. Thecertificate_idfield is properly included in the updates map, and the comment at lines 72-73 helpfully clarifies theadded_slotsemantics.database/plugin/metadata/store.go (3)
56-60: LGTM: GetAccount interface extended for inactive account support.The
includeInactivebool parameter properly extends the interface to support retrieving inactive accounts when needed.
127-133: LGTM: SetTransaction interface updated with deposits parameter.The updated signature adds the
depositsmap parameter with clear documentation explaining that it maps certificate index to deposit amount. The parameter order change (pointfirst) improves consistency with other methods.
144-146: LGTM: UTXO method signatures use canonical ledger types.The type changes from
ledger.*tolcommon.*align with the repository's convention of usingledger/commonas the canonical type package, consistent with learnings from previous reviews.
86dd722 to
d748f01
Compare
There was a problem hiding this 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)
database/models/drep.go (1)
17-68: Addcolumn:credentialtag to all DRepDrepCredentialfields or create schema migrationThe field rename from
Credential→DrepCredentialwithout acolumn:tag will cause GORM's AutoMigrate to create a newdrep_credentialcolumn while leaving the oldcredentialcolumn untouched. Existing data will become inaccessible.All four affected models in
database/models/drep.goare registered inMigrateModelsand will be auto-migrated:
Drep(line 22)DeregistrationDrep(line 36)RegistrationDrep(line 48)UpdateDrep(line 62)Add
gorm:"column:credential;..."to pin the column name, or create a proper database migration to rename the column and update indexes.
🧹 Nitpick comments (12)
internal/config/config.go (1)
22-92: Ledger catchup duration config looks good; watch for duplicated defaultsThe new
time.Durationfields with YAML/envconfig tags are appropriate, and the defaults (5m/5s/10m) look sane and consistent withinternal/node/load.go. One minor maintenance nit: these literal defaults now exist in both this file andinternal/node/load.go; if they ever change, you’ll need to remember to update both. If possible, consider centralizing the defaults (e.g., constants in one place) to avoid drift.database/types/types_test.go (1)
33-92: Nice added coverage for []byte Scan; consider strengthening assertionsThe extra
Scan([]byte(...))checks are useful and align with the updatedUint64struct. Note thattmpScannerandtestDef.origValueare the same pointer, soreflect.DeepEqual(tmpScanner, testDef.origValue)only guarantees pointer identity, not that the underlying numeric value is as expected. If you want stricter tests (optional), you could type‑switch ontestDef.origValue(e.g.,*types.Uint64,*types.Rat) and assert on the underlying fields after the []byte Scan.database/models/move_instantaneous_rewards.go (1)
21-29: MoveInstantaneousRewards model fits unified certificate schemaThe model wiring (unique
CertificateID, indexedSource/AddedSlot, free-formRewardData) aligns with the rest of the certificate tables and the unified mapping design. If you later want uniform handling of all value-like amounts, you could consider switchingOtherPottotypes.Uint64for consistency with deposits, but it’s not required for correctness right now.database/certificate.go (1)
21-47: Auto-transaction behavior for registration queries is reasonableDocumenting that certificates are persisted via
SetTransactionand havingGetPoolRegistrations/GetStakeRegistrationslazily open a read-onlyTxnwhentxn == nilkeeps call sites simple and still allows callers to supply an existing transaction when needed. Ignoring the commit error on a read-only transaction is acceptable here given the usage.database/plugin/metadata/sqlite/certs_test.go (2)
425-749: AuthCommitteeHot and certificate validation tests give good coverage of edge casesHighlights:
TestAuthCommitteeHotCertificateIdPopulationvalidates both initialCertificateIDpopulation and the uniqueness ofColdCredential.TestCertificatePersistenceValidationcovers:
- StakeDeregistration uniqueness on
(staking_key, certificate_id).- Manual cleanup behavior mirroring
bulkRemoveCertificateRecordsand cert deletion.- Account activation/deactivation state changes.
- Round‑trip behavior of
types.Uint64on both registration deposits and deregistration amounts, including transactional usage.These tests collectively exercise the key invariants around uniqueness, cleanup, and type wrappers without re‑implementing too much store logic.
1053-1669: Slot, mapping, DeleteCertificate coverage, and DRep SetDrep tests are comprehensiveThis tail section of the file:
- Confirms
Certificate.Slotis correctly persisted viaSetTransaction.- Verifies
DeregistrationDrep.DepositAmountandtypes.Uint64behave as expected.- Checks specialized data mapping for MIR, LeiosEb, and all anchor‑bearing certs (ResignCommitteeCold, RegistrationDrep, UpdateDrep).
- Enforces that every known
CertificateTypeis accepted byDeleteCertificate, and that unknown types return a clear “unknown certificate type …” error.- Validates
SetDrepboth on initial creation and on update (ensuring the singleDreprow is updated rather than duplicated, with slot, anchor, active flag, andCertificateIDupdated correctly).All of this is well targeted at the new unified certificate model and should catch most regressions in the certificate‑related metadata layer.
database/database_test.go (1)
76-179: Consider driving this throughDatabase.UtxosByAddressinstead of reimplementing the copyThe test correctly:
- Inserts a UTXO with
IsCollateralReturn = true.- Asserts that
GetUtxosByAddressreturns it with the flag and key/amount fields intact.- Manually copies fields into a new
models.Utxoand verifies the flag survives the copy.Functionally this all works, but the manual copy at the bottom is duplicating what
Database.UtxosByAddressdoes today. To guard against future drift, you might prefer to calldb.UtxosByAddress(testAddr)directly and assert on that result instead of recreating the mapping inline.database/models/certificate.go (1)
25-150: CertificateType scanning/range guard is solid; consider making the max value more obviously future‑proofThe
CertificateTypewrapper andScanimplementation look robust: you handle all common driver return types (int64,uint64,[]byte,string), check for overflow before casting, and enforce a small integer range. Functionally this is in good shape.Two minor maintainability thoughts:
maxCertificateTypeValue = 1000is a reasonable safety net, but it’s not clearly tied to the actual maximum inlcommon. You might want to either:
- Add a short comment explicitly stating “current highest value in lcommon is X; bump this if new types are added”, or
- Move the limit next to the constant definitions and base it on the largest known
lcommon.CertificateType*value so it’s harder to forget during future upgrades.
Scan(nil)sets*ct = 0. That’s fine if0is either a valid “unknown/zero” type or an unused sentinel. If0is not representable from the ledger, it might be safer to treatnilas an error or a clearly documented sentinel so callers don’t accidentally treat it as a real certificate type.database/plugin/metadata/sqlite/database.go (1)
190-246: Certificate cleanup scheduler is a good hook; consider early guards when you add real logicThe
runCertificateCleanup/scheduleCertificateCleanuppair mirrors the vacuum pattern nicely (timer +certificateCleanupWG+closedguard), and is safe as a no‑op placeholder.When you eventually implement the TODOs here, it would be worth:
- Mirroring
runVacuum’sdataDir == ""check if the cleanup will touch on‑disk state only (to avoid unnecessary work for purely in‑memory/testing setups).- Ensuring any heavier cleanup work runs in short‑lived transactions or with appropriate context timeouts so these background goroutines can’t get stuck indefinitely on DB I/O.
Nothing to change now; just considerations for when you flesh out the cleanup logic.
database/plugin/metadata/sqlite/drep.go (2)
25-47: GetDrep behavior and not‑found semantics look good; minor duplication you could trim
GetDrep’s contract of returning(nil, nil)when a DRep isn’t found is clear and matches the comment. The use of"drep_credential = ?"aligns with the unique index onDrepCredential.You have a small amount of duplication between the
txn != nilandtxn == nilbranches. If you want to tighten this up later, you could follow theSetDreppattern:db := txn if db == nil { db = d.DB() } result := db.First(ret, "drep_credential = ?", cred)and keep the error handling once.
49-85: SetDrep upsert correctly captures anchor + certificate linkage; check slot ordering assumptionsThe
SetDrepupsert viaOnConflict{Columns: drep_credential}looks appropriate:
- New records get
DrepCredential,AddedSlot,AnchorUrl,AnchorHash,Active, andCertificateIDpopulated in line withmodels.Drep.- Existing records are updated with the latest values on conflict, keeping the DRep row in sync with the most recent certificate affecting it.
One thing to double‑check at the call sites: this assumes calls arrive in ledger order. If
SetDrepcan be invoked out of order in terms ofslot(e.g., during backfills or recovery), you may want to add a guard in theDoUpdatesclause (or at the caller) so an older certificate cannot overwrite a newerAddedSlotand associated fields.database/plugin/metadata/sqlite/account.go (1)
51-86: SetAccount upsert behavior looks correct; relies on callers to pass the desired final stateThe new
SetAccountimplementation:
- Uses
FirstOrCreatekeyed onStakingKey, ensuring a single account row per staking key.- Then updates
pool,drep,added_slot,active, andcertificate_idin oneUpdatescall, which matches the fields onmodels.Account.This is a clean consolidation of account updates. Just keep in mind that it always overwrites all those fields with the values provided; if some certificate types should leave pool or DRep unchanged, the corresponding callers need to pass through the current values rather than zero them out.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (46)
blockfetch.go(1 hunks)database/account.go(1 hunks)database/certificate.go(1 hunks)database/database_test.go(2 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(4 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/blob/aws/database.go(1 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/account_test.go(1 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(2 hunks)database/plugin/metadata/sqlite/plugin.go(0 hunks)database/plugin/metadata/sqlite/pool.go(2 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(4 hunks)database/plugin/metadata/store.go(3 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(4 hunks)go.mod(4 hunks)internal/config/config.go(2 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/certs_test.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(14 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (3)
- database/plugin/metadata/sqlite/plugin.go
- database/models/certs.go
- database/drep.go
✅ Files skipped from review due to trivial changes (1)
- database/plugin/blob/aws/database.go
🚧 Files skipped from review as they are similar to previous changes (10)
- database/models/genesis_key_delegation.go
- database/models/pool.go
- database/models/asset.go
- internal/config/config_test.go
- go.mod
- ledger/view.go
- blockfetch.go
- database/models/auth_committee_hot.go
- database/models/leios_eb.go
- database/models/certificate_test.go
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/database_test.godatabase/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/sqlite/certs_test.goledger/certs_test.goledger/state.godatabase/models/utxo.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/database_test.godatabase/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/sqlite/certs_test.goledger/certs_test.godatabase/models/utxo.godatabase/utxo.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/database_test.godatabase/plugin/metadata/sqlite/utxo.godatabase/plugin/metadata/sqlite/certs_test.goledger/certs_test.goledger/state.godatabase/models/utxo.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/database_test.godatabase/utxo.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/database_test.godatabase/plugin/metadata/sqlite/certs_test.goledger/certs_test.goledger/state.godatabase/utxo.go
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/database_test.godatabase/plugin/metadata/sqlite/utxo.godatabase/models/transaction.godatabase/models/utxo.godatabase/utxo.godatabase/transaction.godatabase/plugin/metadata/sqlite/transaction.goledger/delta.godatabase/plugin/metadata/sqlite/database.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/plugin/metadata/sqlite/utxo.godatabase/models/transaction.godatabase/models/utxo.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-11-20T02:07:07.043Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 0
File: :0-0
Timestamp: 2025-11-20T02:07:07.043Z
Learning: In the blinklabs-io/dingo repository, range over int is valid Go syntax. The repository uses Go 1.24.0 (minimum Go 1.23), and range over int has been supported since Go 1.22. This syntax should not be flagged as invalid.
Applied to files:
database/plugin/metadata/sqlite/certs_test.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/transaction.godatabase/models/utxo.godatabase/utxo.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/certs_test.goledger/state.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
ledger/state.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/utxo.go
🧬 Code graph analysis (25)
database/database_test.go (3)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/types/types_test.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/utxo.go (1)
database/models/utxo.go (2)
Utxo(24-40)UtxoLedgerToModel(50-81)
internal/config/config.go (1)
internal/node/load.go (2)
LedgerCatchupPollInterval(36-36)LedgerCatchupStallTimeout(38-38)
database/plugin/metadata/sqlite/certs_test.go (11)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/types/types.go (1)
Uint64(66-68)database/models/account.go (7)
StakeRegistration(127-133)StakeRegistration(135-137)Deregistration(80-86)Deregistration(88-90)StakeDeregistration(116-121)StakeDeregistration(123-125)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (23)
Certificate(157-165)Certificate(168-170)CertificateTypeStakeRegistration(32-34)CertificateType(28-28)CertificateTypeStakeDeregistration(35-37)CertificateTypeStakeDelegation(38-40)CertificateTypePoolRegistration(41-43)CertificateTypePoolRetirement(44-46)CertificateTypeRegistration(47-49)CertificateTypeDeregistration(50-52)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteDelegation(56-58)CertificateTypeStakeVoteDelegation(59-61)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)CertificateTypeAuthCommitteeHot(68-70)CertificateTypeResignCommitteeCold(71-73)CertificateTypeRegistrationDrep(74-76)CertificateTypeDeregistrationDrep(77-79)CertificateTypeUpdateDrep(80-82)CertificateTypeGenesisKeyDelegation(83-85)CertificateTypeMoveInstantaneousRewards(86-88)CertificateTypeLeiosEb(89-91)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
database/models/transaction.go (2)
database/models/utxo.go (1)
Utxo(24-40)database/types/types.go (1)
Uint64(66-68)
database/models/move_instantaneous_rewards.go (1)
database/types/types.go (1)
Uint64(66-68)
ledger/certs_test.go (2)
ledger/state.go (1)
LedgerState(79-107)ledger/eras/conway.go (1)
ConwayEraDesc(33-45)
database/models/models.go (3)
database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(66-68)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(79-107)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
database/plugin/metadata/sqlite/certs.go (10)
database/models/certificate.go (4)
CertificateTypeStakeRegistration(32-34)Certificate(157-165)Certificate(168-170)CertificateType(28-28)database/models/account.go (20)
StakeRegistration(127-133)StakeRegistration(135-137)StakeDeregistration(116-121)StakeDeregistration(123-125)StakeDelegation(104-110)StakeDelegation(112-114)Registration(92-98)Registration(100-102)Deregistration(80-86)Deregistration(88-90)StakeRegistrationDelegation(139-146)StakeRegistrationDelegation(148-150)VoteDelegation(179-185)VoteDelegation(187-189)StakeVoteDelegation(152-159)StakeVoteDelegation(161-163)VoteRegistrationDelegation(191-198)VoteRegistrationDelegation(200-202)StakeVoteRegistrationDelegation(165-173)StakeVoteRegistrationDelegation(175-177)database/types/types.go (2)
Uint64(66-68)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
ledger/state.go (3)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/txn.go (1)
Txn(27-34)database/plugin/log.go (1)
Logger(18-27)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(66-68)
database/models/account_test.go (1)
database/models/account.go (1)
Account(27-35)
database/plugin/metadata/sqlite/account_test.go (5)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/account.go (3)
Account(27-35)StakeRegistration(127-133)StakeRegistration(135-137)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(269-277)
database/account.go (3)
database/models/account.go (1)
Account(27-35)database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/drep.go (1)
database/models/drep.go (1)
Drep(21-29)
database/plugin/metadata/sqlite/pool.go (1)
database/models/certificate.go (1)
CertificateTypePoolRegistration(41-43)
database/plugin/metadata/sqlite/transaction.go (5)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-50)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/utxo.go (1)
UtxoLedgerToModel(50-81)database/models/certificate.go (10)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeRegistrationDrep(74-76)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(157-165)Certificate(168-170)
database/plugin/metadata/sqlite/account.go (1)
database/models/account.go (1)
Account(27-35)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
⏰ 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). (3)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (35)
database/transaction.go (1)
22-62: SetTransaction transactional flow and error propagation look solidThe refactor to use a named
errwith a single deferred commit/rollback for internally createdTxninstances makes the transaction lifecycle much safer and clearer. UTXO blob writes short‑circuit on error, andmetadata.SetTransactionis wired into the same flow with proper error propagation. Behavior when a caller passes a non‑nilTxnis preserved.database/models/models.go (1)
29-31: MigrateModels registration for new certificate-related tables looks correctRegistering
GenesisKeyDelegation,LeiosEb, andMoveInstantaneousRewardsinMigrateModelsis consistent with their definitions and placement relative toCertificateshould be fine for migrations.database/types/types.go (2)
36-63: Rat.Scan nil and type handling look correctThe updated
Scancorrectly:
- Initializes
r.Ratwhen needed.- Treats
NULLas a no-op zero value.- Accepts
string,[]byte, andint64and rejects everything else with a clear error.This is a reasonable and explicit set of accepted database representations for
NUMERIC/text-based rationals.
65-101: Uint64 wrapper Scan/Value implementation is soundThe new
Uint64struct with:
Value()returning a base‑10 string, andScan()handlingNULL,string,[]byte, andint64(with a guarded cast),gives predictable behavior for metadata columns while keeping on-disk representation compatible with existing string/int encodings. The NULL→zero behavior is clearly documented and matches the new tests.
database/plugin/metadata/sqlite/certs_test.go (4)
20-42: Test helpers for CBOR decoding and deposits are well factored
decodeTestTransactionandgetTestDepositscentralize shared fixture setup and keep the downstream tests focused on behavior instead of mechanics. This matches the intent of driving everything through realgledger.Transactioninstances.
44-242: Pool and stake duplicate-prevention tests correctly exercise updated constraintsThe pool and stake tests now:
- Build a fresh cert struct inside each loop iteration with unique
CertificateIDs.- Use
for i := range 3to get three iterations, which is valid for the Go version this repo targets (so the file compiles as intended).- Verify that re-registrations and multiple retirements/deregistrations for the same key are allowed, while duplicate
(staking_key, certificate_id)pairs are rejected.This accurately reflects the revised schema semantics around composite uniqueness and re-registration behavior.
244-423: DRep re‑registration and genesis delegation idempotency tests match schema designThe DRep tests confirm:
- Multiple
RegistrationDrep/DeregistrationDrep/UpdateDreprows can exist per credential (no over‑strict uniqueness on the credential itself).- Counts for each table match expectations after re‑registration/update loops.
The genesis key delegation test mirrors the store’s
ON CONFLICT DO NOTHINGbehavior and asserts that repeated inserts hit the same row (ID), plus that only a single row exists for a given (genesis_hash, delegate, vrf) triple. This neatly guards the intended idempotent semantics.
751-1051: SetTransaction idempotency and data‑integrity tests align with unified certificate flow
TestTransactionCertificateIdempotencyandTestCertificateProcessingDataIntegritynow:
- Drive everything through
metadata.SetTransactionwith a real decoded transaction and deposit map.- Assert non‑zero certificate creation on first insert, full idempotency when called again with the same block point, and stable certificate count when re‑included at a different point.
- Verify slot updates on successful re‑inclusion, while explicitly allowing the “already processed” error path as long as no extra certs are created (with
expectedSlotconditioned on whether the second call actually succeeded).This gives strong coverage of the subtle re‑org/idempotency behavior without duplicating the production logic.
database/models/account_test.go (1)
26-235: Excellent test coverage for network-aware address formatting.The test suite comprehensively covers mainnet/testnet HRP handling, various staking key lengths (28-byte, 32-byte), and edge cases (empty, nil, single-byte, all-zeros, all-ones). The round-trip validation via bech32 decode ensures encoding correctness.
database/models/transaction.go (1)
17-36: LGTM! Collateral return filtering correctly implemented.The
where:is_collateral_returnclauses correctly filter regular outputs from the collateral return using the snake_case column name. This addresses the previous review concerns about distinguishing collateral returns from regular outputs.database/models/resign_committee_cold.go (1)
17-30: Well-structured constraints for resignation tracking.The unique constraint on
ColdCredentialcorrectly prevents duplicate resignations for the same cold credential. TheCertificateIDwithuniqueIndexproperly links to the unified certificate table, and thetypes.Uint64usage forAddedSlotis consistent with other models.database/plugin/metadata/sqlite/utxo.go (1)
96-137: Improved query logic with explicit conditions.The refactored
GetUtxosByAddresswith explicit zero-hash checks and conditional where clauses is clearer and more maintainable than the previous implementation. The early return for no valid keys prevents unnecessary database queries.database/plugin/metadata/sqlite/pool.go (1)
75-95: Correct usage of types.Uint64 wrapper fields.The
.Valaccess forPledgeandCostcorrectly unwraps thetypes.Uint64wrapper, and theuint(lcommon.CertificateTypePoolRegistration)cast aligns with the updatedCertTypefield type.ledger/certs_test.go (1)
27-299: Comprehensive test coverage for certificate deposit calculation.The test suite thoroughly validates deposit calculation across all Cardano certificate types, including:
- Registration certificates with deposits (stake, pool, DRep)
- Delegation certificates with zero deposits
- Combined registration+delegation certificates
- Edge case with zero protocol parameters
The integration-style test at line 174 provides end-to-end validation of the complete certificate type set.
database/models/account.go (2)
41-70: Well-implemented network-aware address formatting.The
StringWithNetworkmethod correctly handles mainnet ("stake") vs testnet ("stake_test") HRP, with proper error handling for empty staking keys and encoding failures. The delegation fromString()toStringWithNetwork(false)preserves backward compatibility.
72-202: Clear certificate constraint strategy documentation.The comment block at lines 72-78 clearly articulates the design: composite unique constraints on
(StakingKey, CertificateID)for registration/deregistration certificates prevent duplicates while allowing re-registration, and simple indexes on other certificate types enable historical tracking. This addresses the legitimate certificate lifecycle concerns from previous reviews.ledger/delta.go (2)
25-47: Correct deposit threading through transaction records.The
Depositsfield inTransactionRecordand the renamedaddTransactionWithDepositsmethod correctly thread deposit information through the transaction processing pipeline, enabling proper tracking of certificate-related deposits.
49-119: Properly handles deposits for valid vs invalid transactions.The logic correctly implements Cardano's ledger rules:
- Invalid transactions (lines 54-74): No deposit calculation; only collateral is processed
- Valid transactions (lines 76-100): Deposits are calculated from certificates or used from pre-calculated values
This aligns with the phase-2 validation failure behavior where certificates are not applied for invalid transactions.
internal/node/load.go (1)
34-39: Catch‑up constants and progress‑based stall detection look solidUsing exported defaults plus a ticker with
lastProgressTimeand explicit slot‑0 handling gives you bounded wait, clear logs, and no more “stuck at slot 0 forever” risk. The exit and error paths are consistent and don’t leak goroutines.Also applies to: 179-257
database/utxo.go (1)
24-24: UTxO API alignment withlcommonand collateral-return flag propagation look correctSwitching to
lcommon.TransactionInput/lcommon.Addresshere and explicitly copyingIsCollateralReturninto the returnedmodels.Utxoslice keeps the database API consistent with the ledger/common types and preserves the collateral‑return flag end‑to‑end.Also applies to: 102-112, 115-140
ledger/certs.go (1)
23-60: Centralized certificate‑deposit calculation is correct and preserves zero valuesThis helper cleanly delegates to the era’s
CertDepositFunc, records every certificate index (including legitimate zero deposits), and wraps errors with the offending index, which is exactly what the downstream transaction/certificate persistence needs.ledger/state.go (1)
17-19: Context-based lifecycle and channel ownership fixes look correctThe addition of
ctx/cancel/wg, producer‑onlyclose(resultCh), and ctx checks in bothledgerReadChainandledgerProcessBlocksfixes the earlier channel‑closure race and gives you clean shutdown on errors or Close(). The deposit pre‑computation plusaddTransactionWithDepositsintegration inledgerProcessBlockis also straightforward and consistent with the new certificate/deposit persistence flow.Also applies to: 80-107, 127-129, 176-181, 542-644, 656-719, 838-845, 865-939
database/models/utxo.go (1)
23-40: UTxO model updates and collateral-return flag wiring are consistentExtending
UtxowithIsCollateralReturnand movingAddedSlot/DeletedSlot/Amounttotypes.Uint64, then setting those viaUtxoLedgerToModel(…, isCollateralReturn)matches the GORM associations onTransactionand cleanly separates normal outputs from collateral return.Also applies to: 50-63
database/plugin/metadata/sqlite/account_test.go (1)
1-401: Account and certificate linkage tests thoroughly exercise the new behaviorThe four tests nicely cover SetAccount’s active/inactive semantics (with and without explicit transactions), DeleteCertificate’s cascade into specialized tables, and the account↔certificate linkage via
CertificateID. Using the in‑memory metadata store keeps them fast and focused.database/plugin/metadata/sqlite/certs.go (1)
17-62: Unified certificate storage, account-state updates, and cascade delete are coherent
storeCertificatecorrectly establishes the mapping row first, then populates each specialized model with the right fields (including margin, anchors, MIR and Leios JSON payloads) and links both directions viaCertificateID.updateAccountStaterestricts SetAccount calls to staking-key certs, andDeleteCertificatenow deletes specialized rows by the unified certificate ID before removing the mapping, which matches the schema and the new tests.Also applies to: 64-874
database/plugin/metadata/sqlite/transaction.go (2)
52-151: Idempotent transaction upsert and deposit-aware certificate persistence are well structuredThe new
SetTransactionflow uses a hash-based upsert, cleanly distinguishes “already exists” vs new inserts, and only processes UTXOs once.processTransactionCertificatescorrectly:
- skips non-cert and invalid txs,
- treats re-inclusion by updating certificate slots/block hashes without duplicating rows, and
- enforces that deposit-requiring certificate types have entries in the
depositsmap while still allowing legitimate zero-deposit certs.This matches the new ledger-side
CalculateCertificateDepositsbehavior and achieves the unified certificate persistence goal without reintroducing duplicate records.Also applies to: 361-451
453-507: Certificate slot/block-hash re-inclusion updates, including LeiosEb, look correct
updateCertificateSlotsupdates both the unifiedcertificatesrows and all specialized tables’added_slot(now includingleios_eb) via a subquery oncertificates.id, which keeps all certificate rows consistent when a transaction is re-included in a different block.database/models/certificate.go (1)
152-175: Unified Certificate mapping model and indexing look correctThe
Certificatestruct layout and GORM tags make sense for the unified certificate table:
TransactionID+cert_indexas a unique composite index gives you a stable per‑transaction certificate ordering.constraint:OnDelete:CASCADEonTransactionIDensures mappings are cleaned up with their owning transaction.CertType+CertificateIDgive you a simple polymorphic link into the specialized certificate tables without forcing DB‑level polymorphism.No functional or modeling issues from what’s shown here.
database/plugin/metadata/sqlite/database.go (2)
36-50: Vacuum scheduling and shutdown lifecycle look correctThe added state (
timerVacuum,timerMutex,vacuumWG,closed,dataDir) plus the newrunVacuum/scheduleDailyVacuumflow look sound:
runVacuumskips work whendataDir == ""orclosed, and usesvacuumWGsoClose()can reliably wait for any in‑flight VACUUM before closing the DB.scheduleDailyVacuumcancels any previous timer, checksclosed, and reschedules viatime.AfterFuncwith a simple self‑rescheduling closure.This addresses the earlier concern about VACUUM running after
Close()and avoids running it against in‑memory stores entirely.Also applies to: 128-188
253-312: Close now correctly stops background work before closing the DB
Closenow:
- Sets
closed = trueundertimerMutexand stops bothtimerVacuumandtimerCertificateCleanup.- Waits for
vacuumWGandcertificateCleanupWGto drain before actually closing the underlying DB handle.That sequencing ensures no background VACUUM or cleanup run can outlive the store or race against
db.Close(). The thin wrappers (Create,DB,First,Order,Transaction,Where) are straightforward pass‑throughs and look fine.database/account.go (1)
21-51: GetAccountWithInactive nicely extends behavior without breaking callersThe split between:
GetAccount→GetAccountWithInactive(..., false, ...), andGetAccountWithInactivehandling txn creation, delegating tometadata.GetAccount, and normalizingniltomodels.ErrAccountNotFoundkeeps existing behavior intact while adding an explicit way to query inactive accounts. The transaction management (
if txn == nil { txn = d.Transaction(false); defer txn.Commit() }) is consistent with other database methods in this codebase.database/plugin/metadata/sqlite/account.go (1)
25-49: Account lookup correctly honors includeInactive flag and not‑found behavior
GetAccount’s use of:
staking_key = ?as the primary key (matching the unique index onStakingKey), and- a conditional
active = truefilter only whenincludeInactiveis false,gives you the expected semantics for active‑only vs all‑accounts queries. Returning
(nil, nil)onErrRecordNotFoundlines up with howdatabase.GetAccountWithInactivetranslates that intoErrAccountNotFound.database/plugin/metadata/store.go (3)
56-60: LGTM! Clear and straightforward parameter addition.The
includeInactiveflag provides a clean way to filter inactive accounts. The parameter name is self-documenting, and the addition maintains interface clarity.
127-133: Excellent refactor with clear documentation.The signature changes strengthen the interface by:
- Using
ocommon.Pointfor richer chain location context (vs. raw slot)- Adding the
Transactionparameter for direct access to certificate data- Providing a well-documented
depositsmap with a clear contractThe comment explicitly states the requirements: "Keys must align with transaction certificate slice order. Missing keys default to 0." This prevents ambiguity and helps implementers understand the expected behavior.
144-146: Consistent use of canonical ledger/common types.The updated signatures correctly use
lcommon.Addressandlcommon.TransactionInput, establishing consistency with the gouroboros ledger/common package as the canonical type source. This prevents type mismatches and aligns with the repository's pattern.
| if isTestnet { | ||
| hrp = "stake_test" | ||
| } | ||
| encoded, err := bech32.Encode(hrp, convData) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use gOuroboros's common.Address for this
| CertificateTypeLeiosEb CertificateType = CertificateType( | ||
| lcommon.CertificateTypeLeiosEb, | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to define a special type for certificate types, we should really do this in gOuroboros and make it consistent throughout.
| } | ||
| *ct = CertificateType(val) | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need all of this special DB handling for a uint type?
blockfetch.go
Outdated
| blockfetch.WithBlockTimeout(2 * time.Second), | ||
| // Set the recv queue size to 2x our block batch size | ||
| blockfetch.WithRecvQueueSize(1000), | ||
| blockfetch.WithRecvQueueSize(512), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this being changed? This was set to its current value very specifically
- Add SetCertificate method handling all Cardano certificate types - Switch interface from slot to ocommon.Point for consistency - Add comprehensive test suite with 19 test cases - Remove deprecated certificate setter methods - Include certificate mapping model and migration updates Signed-off-by: GitHub Copilot <[email protected]> Signed-off-by: Chris Gianelloni <[email protected]>
d748f01 to
a82866f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
database/plugin/metadata/sqlite/certs_test.go (1)
425-495: Consider adding an integration test that exercises AuthCommitteeHot via the actual store path
TestAuthCommitteeHotCertificateIdPopulationcurrently createsmodels.AuthCommitteeHotrecords directly through GORM and validatesCertificateIDpersistence and the uniqueness ofColdCredential. That’s valuable, but it doesn’t hit whatever higher‑level persistence path (e.g.,SetCertificate/SetTransaction) is responsible for wiring upCertificateIDin production.To fully close the loop on the earlier concern that
CertificateIDmight be left unset, consider adding a test that:
- Builds a minimal transaction containing an auth‑committee‑hot certificate.
- Calls the real
SetTransaction/ metadata store entry point.- Asserts that the resulting
AuthCommitteeHotrow has the expected non‑zeroCertificateIDand that the uniqueness constraint behaves as intended under that path.This would ensure the model, tags, and store logic all agree.
🧹 Nitpick comments (5)
database/models/certificate_test.go (1)
69-142: StrengthenScanerror‑path assertions to guard against partial writesThe
TestCertificateType_Scantable covers a good range of input shapes and error cases, but for the error cases (invalid type,negative value,too large value,uint overflow) the test only checks thatScanreturns an error and doesn’t assert anything about the final value ofct. To catch accidental partial assignments inScan, consider also asserting thatctremains at its zero value (or some explicitly expected value) whenwantErris true.database/plugin/metadata/sqlite/certs_test.go (1)
1402-1525:DeleteCertificatecoverage relies on a hand‑maintained constant list
TestDeleteCertificateTypeCoverageis valuable in that it asserts:
- Each listed
CertificateTypecan be round‑tripped throughDeleteCertificatewithout error.- An explicit unknown type produces the expected “unknown certificate type …” error.
One thing to be aware of is that the
knownTypesslice is manual: if newCertificateTypeconstants are added inmodels, this test will not fail until you also updateknownTypes. The comment currently suggests automatic coverage (“If new certificate types are added, this test will fail…”), which isn’t strictly true.Consider either:
- Updating the comment to note that
knownTypesmust be kept in sync with new constants, or- Adding a small helper (e.g., a central
AllCertificateTypes()list inmodels) that both the switch and this test consume to avoid drift.database/plugin/metadata/sqlite/account_test.go (1)
28-128: Nice coverage ofSetAccountstate and transaction semanticsThese two tests thoroughly exercise the new
SetAccountbehavior: active→inactive transitions withincludeInactivefiltering, and transaction-scoped visibility/rollback. They should catch most regressions in account upserts. If you want to trim repetition later, consider a small helper for the in‑memory DB/metadata store setup, but not required for this PR.Also applies to: 130-187
ledger/state.go (1)
80-107: Goroutine lifecycle management via context andWaitGroupis solidAdding
ctx,cancel, andwgtoLedgerState, wiring them inNewLedgerState,Start, andClose, gives you explicit ownership of the long‑running block processing goroutine and its child readers. One small follow‑up you might consider is stoppingtimerCleanupConsumedUtxosinCloseas well, so the periodic cleanup callback doesn’t try to touch a closed DB after shutdown.Also applies to: 109-129, 132-182, 209-218
database/plugin/metadata/sqlite/database.go (1)
190-219: Certificate cleanup is a no-op placeholder.The
runCertificateCleanupfunction is currently a placeholder with TODO comments (lines 203-208) but is scheduled to run every 6 hours. While the infrastructure is in place, the actual cleanup logic is not implemented yet.This is acceptable for now since the PR objectives focus on certificate persistence, not cleanup. Consider creating a follow-up issue to track the cleanup implementation if it's needed for production use.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (44)
database/account.go(1 hunks)database/certificate.go(1 hunks)database/database_test.go(2 hunks)database/drep.go(0 hunks)database/models/account.go(4 hunks)database/models/account_test.go(4 hunks)database/models/asset.go(1 hunks)database/models/auth_committee_hot.go(1 hunks)database/models/certificate.go(1 hunks)database/models/certificate_test.go(1 hunks)database/models/certs.go(0 hunks)database/models/drep.go(2 hunks)database/models/genesis_key_delegation.go(1 hunks)database/models/leios_eb.go(1 hunks)database/models/models.go(1 hunks)database/models/move_instantaneous_rewards.go(1 hunks)database/models/pool.go(2 hunks)database/models/resign_committee_cold.go(1 hunks)database/models/transaction.go(1 hunks)database/models/utxo.go(2 hunks)database/plugin/metadata/sqlite/account.go(2 hunks)database/plugin/metadata/sqlite/account_test.go(1 hunks)database/plugin/metadata/sqlite/certs.go(2 hunks)database/plugin/metadata/sqlite/certs_test.go(1 hunks)database/plugin/metadata/sqlite/database.go(4 hunks)database/plugin/metadata/sqlite/drep.go(2 hunks)database/plugin/metadata/sqlite/plugin.go(0 hunks)database/plugin/metadata/sqlite/pool.go(2 hunks)database/plugin/metadata/sqlite/transaction.go(5 hunks)database/plugin/metadata/sqlite/utxo.go(4 hunks)database/plugin/metadata/store.go(3 hunks)database/transaction.go(1 hunks)database/types/types.go(2 hunks)database/types/types_test.go(2 hunks)database/utxo.go(4 hunks)go.mod(3 hunks)internal/config/config.go(3 hunks)internal/config/config_test.go(1 hunks)internal/node/load.go(3 hunks)ledger/certs.go(1 hunks)ledger/certs_test.go(1 hunks)ledger/delta.go(4 hunks)ledger/state.go(14 hunks)ledger/view.go(1 hunks)
💤 Files with no reviewable changes (3)
- database/plugin/metadata/sqlite/plugin.go
- database/drep.go
- database/models/certs.go
🚧 Files skipped from review as they are similar to previous changes (11)
- internal/config/config_test.go
- database/plugin/metadata/sqlite/utxo.go
- database/models/genesis_key_delegation.go
- database/models/models.go
- database/models/drep.go
- database/models/asset.go
- go.mod
- database/models/transaction.go
- database/models/move_instantaneous_rewards.go
- database/database_test.go
- ledger/certs_test.go
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-10-24T21:56:15.978Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 975
File: database/models/transaction.go:21-26
Timestamp: 2025-10-24T21:56:15.978Z
Learning: In database/models/transaction.go, the Transaction struct uses a dual foreign-key pattern for UTXO associations: Outputs []Utxo uses TransactionID→ID (where UTXOs were created), and Inputs []Utxo uses SpentAtTxId→Hash (where UTXOs were consumed). Explicit GORM tags `gorm:"foreignKey:SpentAtTxId;references:Hash"` for Inputs and `gorm:"foreignKey:TransactionID;references:ID"` for Outputs prevent association conflicts and preserve UTXO provenance.
Applied to files:
database/models/utxo.godatabase/utxo.godatabase/transaction.goledger/delta.godatabase/plugin/metadata/store.godatabase/plugin/metadata/sqlite/transaction.godatabase/plugin/metadata/sqlite/database.go
📚 Learning: 2025-10-26T14:12:53.587Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 977
File: database/plugin/metadata/sqlite/transaction.go:71-81
Timestamp: 2025-10-26T14:12:53.587Z
Learning: In Babbage-era transactions (gouroboros ledger), the Produced() method behavior depends on transaction validity: for valid transactions (IsValid() == true), Produced() returns all transaction outputs; for invalid transactions (IsValid() == false), Produced() returns only the collateral return UTXO (if non-nil) with index len(t.Outputs()). The collateral return Output is set to t.CollateralReturn() in the returned Utxo struct.
Applied to files:
database/models/utxo.godatabase/utxo.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: In utxorpc code paths, always operate on github.com/blinklabs-io/gouroboros/ledger/common.Block. Use database/models.Block only as the storage type and call models.Block.Decode() to obtain a ledger/common.Block before further processing.
Applied to files:
database/models/utxo.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: In utxorpc/sync_test.go, tests must not pass database/models.Block into chain.Chain.AddBlock. They should call models.Block.Decode() and pass the resulting github.com/blinklabs-io/gouroboros/ledger/common.Block to AddBlock.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/certs_test.goledger/state.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: In utxorpc/sync_test.go, tests should not construct database/models.Block directly for sync server use. Instead, build a models.Block only as a container for CBOR and call models.Block.Decode() to obtain a github.com/blinklabs-io/gouroboros/ledger/common.Block (or the repository’s ledger.Block interface), and use that decoded value in the test.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/certs_test.godatabase/utxo.godatabase/plugin/metadata/store.godatabase/plugin/metadata/sqlite/transaction.go
📚 Learning: 2025-10-22T20:13:35.972Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T20:13:35.972Z
Learning: Unit tests under utxorpc should assert that models.Block.Decode() returns a github.com/blinklabs-io/gouroboros/ledger/common.Block, not rely on models.Block directly in protocol/ledger-facing logic.
Applied to files:
database/models/utxo.godatabase/plugin/metadata/sqlite/certs_test.goledger/state.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-11-20T02:07:07.073Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 0
File: :0-0
Timestamp: 2025-11-20T02:07:07.073Z
Learning: In the blinklabs-io/dingo repository, range over int is valid Go syntax. The repository uses Go 1.24.0 (minimum Go 1.23), and range over int has been supported since Go 1.22. This syntax should not be flagged as invalid.
Applied to files:
database/plugin/metadata/sqlite/certs_test.go
📚 Learning: 2025-10-22T21:29:26.924Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T21:29:26.924Z
Learning: For tests under ledger/, prefer constructing a minimal ledger Block (MockBlock mirroring chain/chain_test.go) and pass it to Chain.AddBlock, rather than using database/models.Block or relying on models.Block.Decode().
Applied to files:
database/plugin/metadata/sqlite/certs_test.goledger/state.godatabase/utxo.go
📚 Learning: 2025-10-22T20:13:35.164Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:56-56
Timestamp: 2025-10-22T20:13:35.164Z
Learning: In tests under ledger/state_test.go, PrimaryChain.AddBlock expects a "github.com/blinklabs-io/gouroboros/ledger/common".Block; use models.Block.Decode() to obtain that type before calling AddBlock.
Applied to files:
ledger/state.godatabase/utxo.godatabase/plugin/metadata/store.go
📚 Learning: 2025-10-22T20:19:25.277Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:434-434
Timestamp: 2025-10-22T20:19:25.277Z
Learning: In this repo’s tests, use models.Block.Decode() to obtain a ledger.Block before calling chain.Chain.AddBlock, since AddBlock expects a ledger.Block, while LedgerState.GetBlock returns a database/models.Block.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T20:18:45.125Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: ledger/state_test.go:248-248
Timestamp: 2025-10-22T20:18:45.125Z
Learning: In tests, Chain.AddBlock must be given a gouroboros ledger.Block (not database/models.Block). Use a lightweight MockBlock (as in chain/chain_test.go) or call models.Block.Decode() when starting from stored rows.
Applied to files:
ledger/state.go
📚 Learning: 2025-10-22T21:28:48.396Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:339-339
Timestamp: 2025-10-22T21:28:48.396Z
Learning: utxorpc tests must pass github.com/blinklabs-io/gouroboros/ledger/common.Block to chain.Chain.AddBlock. They should create database/models.Block only as a storage row, then call models.Block.Decode() to obtain a ledger/common.Block for AddBlock and any ledger/protocol-facing logic.
Applied to files:
database/utxo.go
📚 Learning: 2025-10-22T20:19:26.360Z
Learnt from: wolf31o2
Repo: blinklabs-io/dingo PR: 971
File: utxorpc/sync_test.go:91-91
Timestamp: 2025-10-22T20:19:26.360Z
Learning: chain.Chain.AddBlock expects a github.com/blinklabs-io/gouroboros/ledger.Block. In tests, construct a database/models.Block only as a container and call its Decode() to obtain the gouroboros ledger.Block (which also satisfies github.com/blinklabs-io/gouroboros/ledger/common.Block) before inserting into the chain.
Applied to files:
database/utxo.go
🧬 Code graph analysis (22)
database/models/leios_eb.go (1)
database/types/types.go (1)
Uint64(66-68)
database/certificate.go (2)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/models/utxo.go (2)
database/models/asset.go (2)
Asset(24-32)Asset(34-36)database/types/types.go (1)
Uint64(66-68)
ledger/certs.go (2)
ledger/state.go (1)
LedgerState(79-107)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
database/models/auth_committee_hot.go (1)
database/types/types.go (1)
Uint64(66-68)
database/types/types_test.go (1)
database/types/types.go (2)
Uint64(66-68)Rat(25-27)
database/plugin/metadata/sqlite/certs.go (10)
database/models/certificate.go (23)
Certificate(157-165)Certificate(168-170)CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeStakeDeregistration(35-37)CertificateTypeStakeDelegation(38-40)CertificateTypePoolRegistration(41-43)CertificateTypePoolRetirement(44-46)CertificateTypeRegistration(47-49)CertificateTypeDeregistration(50-52)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteDelegation(56-58)CertificateTypeStakeVoteDelegation(59-61)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)CertificateTypeAuthCommitteeHot(68-70)CertificateTypeResignCommitteeCold(71-73)CertificateTypeRegistrationDrep(74-76)CertificateTypeDeregistrationDrep(77-79)CertificateTypeUpdateDrep(80-82)CertificateTypeGenesisKeyDelegation(83-85)CertificateTypeMoveInstantaneousRewards(86-88)CertificateTypeLeiosEb(89-91)database/models/account.go (20)
StakeRegistration(127-133)StakeRegistration(135-137)StakeDeregistration(116-121)StakeDeregistration(123-125)StakeDelegation(104-110)StakeDelegation(112-114)Registration(92-98)Registration(100-102)Deregistration(80-86)Deregistration(88-90)StakeRegistrationDelegation(139-146)StakeRegistrationDelegation(148-150)VoteDelegation(179-185)VoteDelegation(187-189)StakeVoteDelegation(152-159)StakeVoteDelegation(161-163)VoteRegistrationDelegation(191-198)VoteRegistrationDelegation(200-202)StakeVoteRegistrationDelegation(165-173)StakeVoteRegistrationDelegation(175-177)database/types/types.go (2)
Uint64(66-68)Rat(25-27)database/models/pool.go (9)
Pool(26-38)PoolRegistration(44-60)PoolRegistration(62-64)PoolRegistrationOwner(66-71)PoolRegistrationOwner(73-75)PoolRegistrationRelay(77-85)PoolRegistrationRelay(87-89)PoolRetirement(91-98)PoolRetirement(100-102)database/models/drep.go (7)
Drep(21-29)RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)
database/plugin/metadata/sqlite/certs_test.go (10)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/pool.go (5)
PoolRegistration(44-60)PoolRegistration(62-64)PoolRetirement(91-98)PoolRetirement(100-102)Pool(26-38)database/models/account.go (7)
StakeRegistration(127-133)StakeRegistration(135-137)Deregistration(80-86)Deregistration(88-90)StakeDeregistration(116-121)StakeDeregistration(123-125)Account(27-35)database/models/drep.go (7)
RegistrationDrep(47-55)RegistrationDrep(57-59)DeregistrationDrep(35-41)DeregistrationDrep(43-45)UpdateDrep(61-68)UpdateDrep(70-72)Drep(21-29)database/models/genesis_key_delegation.go (2)
GenesisKeyDelegation(22-29)GenesisKeyDelegation(32-34)database/models/auth_committee_hot.go (2)
AuthCommitteeHot(21-27)AuthCommitteeHot(29-31)database/models/certificate.go (17)
Certificate(157-165)Certificate(168-170)CertificateTypeStakeRegistration(32-34)CertificateType(28-28)CertificateTypeStakeDeregistration(35-37)CertificateTypePoolRegistration(41-43)CertificateTypePoolRetirement(44-46)CertificateTypeRegistration(47-49)CertificateTypeDeregistration(50-52)CertificateTypeAuthCommitteeHot(68-70)CertificateTypeResignCommitteeCold(71-73)CertificateTypeRegistrationDrep(74-76)CertificateTypeDeregistrationDrep(77-79)CertificateTypeUpdateDrep(80-82)CertificateTypeGenesisKeyDelegation(83-85)CertificateTypeMoveInstantaneousRewards(86-88)CertificateTypeLeiosEb(89-91)database/models/move_instantaneous_rewards.go (2)
MoveInstantaneousRewards(22-29)MoveInstantaneousRewards(32-34)database/models/leios_eb.go (2)
LeiosEb(22-32)LeiosEb(35-37)database/models/resign_committee_cold.go (2)
ResignCommitteeCold(23-30)ResignCommitteeCold(32-34)
internal/config/config.go (1)
internal/node/load.go (2)
LedgerCatchupPollInterval(36-36)LedgerCatchupStallTimeout(38-38)
database/models/account_test.go (1)
database/models/account.go (1)
Account(27-35)
database/transaction.go (3)
database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)database/utxo.go (1)
UtxoBlobKey(269-277)
ledger/delta.go (1)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)
internal/node/load.go (1)
internal/config/config.go (2)
DefaultLedgerCatchupPollInterval(37-37)DefaultLedgerCatchupStallTimeout(38-38)
database/models/resign_committee_cold.go (1)
database/types/types.go (1)
Uint64(66-68)
database/plugin/metadata/sqlite/account_test.go (7)
database/plugin/metadata/sqlite/database.go (2)
New(53-126)MetadataStoreSqlite(39-50)database/models/pool.go (1)
Pool(26-38)database/models/drep.go (1)
Drep(21-29)database/models/account.go (3)
Account(27-35)StakeRegistration(127-133)StakeRegistration(135-137)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/certificate.go (2)
Certificate(157-165)Certificate(168-170)
database/plugin/metadata/store.go (2)
database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/models/utxo.go (1)
Utxo(24-40)
database/plugin/metadata/sqlite/drep.go (2)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-50)database/models/drep.go (1)
Drep(21-29)
database/plugin/metadata/sqlite/account.go (1)
database/models/account.go (1)
Account(27-35)
database/models/certificate_test.go (1)
database/models/certificate.go (3)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeStakeDeregistration(35-37)
database/account.go (3)
database/models/account.go (1)
Account(27-35)database/database.go (1)
Database(45-50)database/txn.go (1)
Txn(27-34)
database/plugin/metadata/sqlite/transaction.go (5)
database/plugin/metadata/sqlite/database.go (1)
MetadataStoreSqlite(39-50)database/models/transaction.go (2)
Transaction(22-36)Transaction(38-40)database/types/types.go (1)
Uint64(66-68)database/models/utxo.go (1)
UtxoLedgerToModel(50-81)database/models/certificate.go (10)
CertificateType(28-28)CertificateTypeStakeRegistration(32-34)CertificateTypeRegistration(47-49)CertificateTypePoolRegistration(41-43)CertificateTypeRegistrationDrep(74-76)CertificateTypeStakeRegistrationDelegation(53-55)CertificateTypeVoteRegistrationDelegation(62-64)CertificateTypeStakeVoteRegistrationDelegation(65-67)Certificate(157-165)Certificate(168-170)
database/plugin/metadata/sqlite/database.go (1)
database/plugin/log.go (1)
Logger(18-27)
⏰ 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). (3)
- GitHub Check: docker (ubuntu-latest, amd64)
- GitHub Check: nilaway
- GitHub Check: Analyze (go)
🔇 Additional comments (48)
internal/node/load.go (3)
34-39: LGTM!The constants properly expose the config defaults for use as fallbacks in the catchup loop.
99-102: LGTM!The error message update correctly reflects the asynchronous start semantics.
179-258: LGTM!The progress-based catchup loop correctly implements:
- Configurable poll interval and stall timeout with proper fallbacks
- Stuck-at-slot-0 detection and timeout (addresses past review concern)
- Progress tracking with stall detection
- Clean exit paths for success and timeout scenarios
All past review comments have been addressed in this implementation.
internal/config/config.go (3)
72-74: LGTM!The Config fields follow the established pattern with appropriate YAML tags and directives.
96-98: LGTM!The globalConfig initialization correctly sets the new fields to their default constants, following the standard initialization pattern.
34-39: The constantDefaultLedgerCatchupTimeoutis actively used and there is no issue.The verification shows that
DefaultLedgerCatchupTimeoutis used at line 96 ininternal/config/config.goto initialize theLedgerCatchupTimeoutfield in the Config struct initialization. This is legitimate usage within the same file, which is the appropriate pattern for package-level default constants.Likely an incorrect or invalid review comment.
ledger/view.go (1)
111-112: LGTM!The field assignments correctly access
.Valfrom thetypes.Uint64struct fields, consistent with the Uint64 type migration from alias to struct throughout this PR.database/plugin/metadata/sqlite/drep.go (3)
18-23: LGTM!Import changes align with the refactored code:
errorsis now needed forErrRecordNotFoundchecks, and unused imports have been removed.
49-85: LGTM!The SetDrep refactoring correctly:
- Adds the
certificateIDparameter to support unified certificate tracking- Updates the
DrepCredentialfield usage- Configures
OnConflictto upsert all fields includingcertificate_idThe implementation properly handles both transactional and non-transactional paths.
25-47: GetDrep's (nil, nil) pattern is consistent with similar Get methods.Verification confirms GetDrep follows the standard pattern used by six other similar pointer-returning Get methods in the sqlite store (GetUTXO, GetTransaction, GetPool, GetAccount, GetDatum). All return
(nil, nil)when a record is not found. While some unrelated methods use different patterns (e.g., GetTip returns a zero-initialized value, GetCommitTimestamp returns a scalar), this is due to differing return types, not inconsistency.database/types/types_test.go (2)
33-35: LGTM!The Uint64 construction correctly uses the struct literal syntax
types.Uint64{Val: 123}, consistent with the type migration from alias to struct.
82-108: Excellent test coverage!The extended test validates that both
Uint64andRatcorrectly restore their underlying values when scanning from[]byte, using type-appropriate comparison methods (.Valequality for Uint64,.Cmp()for Rat).database/models/utxo.go (3)
35-39: LGTM!The field type changes and addition of
IsCollateralReturnare consistent with:
- The Uint64 struct migration across the PR
- Support for tracking collateral return UTXOs (per PR objectives and test coverage in
database/database_test.go)
50-54: LGTM!The addition of the
isCollateralReturnparameter enables callers to specify whether a UTXO is a collateral return, which is then persisted in the model.
56-63: LGTM!Field assignments correctly wrap values in the
types.Uint64struct and properly set theIsCollateralReturnfield from the parameter.database/models/pool.go (2)
55-55: LGTM!The unique index on
CertificateIDcorrectly enforces that each certificate appears at most once in thepool_registrationtable, supporting idempotent certificate persistence in the unified system.
93-93: LGTM!The unique index on
CertificateIDcorrectly enforces that each retirement certificate appears at most once in thepool_retirementtable, mirroring the pattern used for pool registrations.database/types/types.go (3)
40-43: LGTM!The nil handling for
Rat.Scanis appropriate and well-documented, preserving a zero-value rat for NULL database columns.
44-58: LGTM!The type switch comprehensively handles the common database driver value types (
string,[]byte,int64), with appropriate fallback handling forint64.
66-72: LGTM!The Uint64 type change from alias to struct is implemented correctly, with
Value()properly accessing theValfield for serialization.database/models/leios_eb.go (2)
21-32: LGTM!The LeiosEb model follows established patterns from other certificate models in this PR:
- Composite unique index on the business key (
ElectionID+EndorserBlockHash)- Separate unique index on
CertificateIDfor certificate system integration[]bytefields for JSON-encoded complex data structures (voters, signatures)The indexing strategy correctly balances uniqueness enforcement with queryability.
34-37: LGTM!The
TableName()method correctly specifies the database table name.database/certificate.go (3)
21-23: LGTM!The comment clearly documents the new unified certificate persistence strategy, clarifying that certificates are now persisted via
SetTransactionwith deposits calculated by the ledger layer.
25-35: LGTM!The auto-transaction pattern improves caller convenience by handling the common case where no transaction is provided. The
//nolint:errcheckon the deferredCommit()is acceptable for read-only transactions where commit failures don't affect data integrity.
37-47: LGTM!The auto-transaction pattern is consistently applied, matching the implementation in
GetPoolRegistrations.database/models/auth_committee_hot.go (1)
21-27: AuthCommitteeHot model changes look consistent with unified certificate design
ColdCredentialas a single‑column unique plus a separate unique index onCertificateID, andAddedSlotusingtypes.Uint64, align with the other certificate models and the new unified certificate mapping. I don’t see issues with the tags or field types here.database/models/account_test.go (1)
26-235: ExpandedAccountstring tests correctly cover mainnet/testnet HRPsThe refactored table and new cases for 28‑ and 32‑byte staking keys, edge cases (single byte, all‑zeros, all‑ones), and the
isTestnet/expectedHRPwiring give good coverage ofStringWithNetwork. The downstream checks using bech32 decoding and round‑tripping the payload still look coherent, and keeping the separateString()tests for idempotence and format on mainnet makes sense.database/models/resign_committee_cold.go (1)
21-30: ResignCommitteeCold uniqueness and type changes look appropriateThe explicit comment plus
gorm:"unique"onColdCredentialand a dedicated unique index onCertificateID, together withAddedSlotswitched totypes.Uint64, line up with the unified certificate mapping and with how other certificate‑backed models are structured. No issues from a modeling or migration perspective.database/plugin/metadata/sqlite/pool.go (1)
53-91: GetPoolRegistrations query and type mappings match the updated modelsUsing
pkh.Bytes()directly in thepool_key_hashfilter (for both transactional and non‑transactional paths) is consistent with how the pool key hash is stored and avoids any double‑hashing. MappingPledgeandCostviacert.Pledge.Val/cert.Cost.Valand castingCertTypetouintalso align with the newtypes.Uint64model fields and the updated certificate type representation. No functional issues stand out here.ledger/delta.go (1)
25-35: Deposit handling inLedgerDelta.applynow correctly separates valid/invalid transactionsStoring deposits on
TransactionRecordand, inapply, skipping deposit calculation entirely for!tr.Tx.IsValid()while still consuming UTxOs, then using either precomputedtr.DepositsorCalculateCertificateDepositsonly for valid transactions, matches the intended Cardano semantics and removes the earlier bug where invalid transactions could get certificate deposits recorded. The updatedSetTransactioncalls with thedepositsmap look consistent with that design.Also applies to: 37-89
database/plugin/metadata/sqlite/certs_test.go (10)
24-42: CBOR decode and deposit helpers are clear and reusable
decodeTestTransactionandgetTestDepositsgive a nice, centralized way to drive multiple tests off the same real transaction payload and deposit map. The helper use ofgledger.DetermineTransactionTypeandNewTransactionFromCborkeeps individual tests focused on persistence behavior rather than CBOR plumbing.
44-339: Pool/stake/DRep duplicate‑handling tests align with the new uniqueness semanticsThe pool, stake, and DRep tests correctly mirror the GORM tag semantics:
- Pool registration/retirement tests confirm that multiple rows per pool (and per epoch for retirements) are allowed, since uniqueness is on
CertificateIDonly.- Stake registration/deregistration tests verify the composite uniqueness on
(staking_key, certificate_id)by allowing distinct certificate IDs and rejecting duplicates.- DRep registration/deregistration/update tests ensure re‑registration and multiple updates are permitted for the same credential.
Using
for i := range 3with varyingCertificateID/AddedSlotvalues gives clear, repeatable coverage without false positives from primary‑key reuse.
341-423: GenesisKeyDelegation idempotency test matches ON CONFLICT behavior
TestGenesisKeyDelegationCertificateIdempotentaccurately reproduces the store’sON CONFLICT (genesis_hash, genesis_delegate_hash, vrf_key_hash) DO NOTHINGbehavior and verifies that repeated inserts for the same triple always resolve to the same row ID. This is a good guardrail against regressions in idempotent handling for genesis key delegation.
497-749: Certificate persistence and Uint64 conversion tests give good end‑to‑end coverageThe grouped subtests in
TestCertificatePersistenceValidationand the laterUint64ConversionValidationexercise:
- Uniqueness on
StakeDeregistration(staking_key, certificate_id).- Manual bulk cleanup semantics for specialized tables plus the unified
Certificatemapping (mirroringbulkRemoveCertificateRecords).- Account state transitions (active → inactive).
- Storage and retrieval of
types.Uint64fields (DepositAmount,Amount) for both stake registration and deregistration.These tests significantly reduce the risk of subtle migration or type‑conversion bugs across the unified certificate schema.
751-859: Transaction re‑inclusion/idempotency behavior is well covered
TestTransactionCertificateIdempotencydrives the realMetadataStore.SetTransactionwith a decoded CBOR transaction and deposit map, and checks:
- Certificates are created on first inclusion with
Slotmatching the block point.- A second call with the same block point doesn’t change the certificate count (idempotent).
- A third call with a different block point preserves the count but updates all certificate
Slotvalues.This is a solid end‑to‑end check that the unified certificate persistence is idempotent and correctly tracks re‑inclusion after a reorg.
861-1051: Flexible re‑processing test captures both idempotent and “already processed” behaviors
TestCertificateProcessingDataIntegritydoes a good job of covering:
- Required deposits for certificate‑bearing transactions (negative case when
provideDepositsis false).- Single‑shot processing with deposits.
- Re‑processing with same or different block points, checking that certificate counts never increase and that
Slotis updated only when a second call actually succeeds on a different block.The
reprocessingSucceededflag and finalexpectedSlotlogic correctly reconcile the comment that a failing second call is acceptable as long as it doesn’t create duplicates, while still asserting slot updates when re‑inclusion succeeds.
1053-1096: Slot persistence test nicely isolates theCertificate.Slotfield
TestCertificateSlotFieldis a concise sanity check that, given a real transaction and deposits, all storedmodels.Certificaterows have theirSlotfield set to the block point’s slot. That’s a helpful focused guard in addition to the broader idempotency tests.
1098-1141: DeregistrationDrep deposit persistence is verified end‑to‑end
TestDeregistrationDrepDepositAmountPersistencedirectly exercisesDeregistrationDrepwith atypes.Uint64DepositAmountand confirms that the rawValsurvives round‑trip through SQLite. This aligns with the broader use oftypes.Uint64across the certificate models.
1143-1400: MIR, LeiosEb, and anchor-bearing certificate mapping tests are thoroughThe
TestCertificateDataMappingValidationsubtests validate:
- JSON payload and numeric fields for
MoveInstantaneousRewards.- All primary byte fields for
LeiosEb(IDs, voter data, signatures).- Anchor URL/hash propagation for
ResignCommitteeCold,RegistrationDrep, andUpdateDrep.These direct model‑level checks cover the new certificate‑specific tables well and should catch schema/tag regressions for these more specialized certificate types.
1527-1669: DRepSetDreptests effectively validate creation and update semanticsThe two DRep tests:
- Confirm that
SetDrepcreates a generalDreprecord with the expected credential, anchor info, slot, active flag, andCertificateID.- Verify that calling
SetDrepagain for the same credential updates the existing record in place (slot, anchor URL/hash, active flag, andCertificateID), while keeping only a single row.This is a good end‑to‑end verification of the DRep aggregation behavior on top of the per‑certificate tables.
database/transaction.go (1)
22-63: ReworkedSetTransactiontransaction management fixes the rollback/commit hazardChanging
SetTransactionto use a namederrreturn with a single deferred closure that chooses betweenRollbackandCommitonly when it creates theTxnitself cleanly resolves the previous risk of committing after a rollback. The UTXO blob writes now propagate errors immediately, and the finalmetadata.SetTransactioncall includes thedepositsmap as intended. The behavior for caller‑suppliedtxn(no auto‑commit/rollback) is preserved.database/utxo.go (1)
24-25: UTxO API alignment withledger/commonand collateral-return flag looks goodUsing
lcommon.TransactionInput/lcommon.Addresshere matches the updated metadata store interface, and theUtxosByAddressmapper now correctly includesIsCollateralReturnwhile preserving all existing fields. No functional issues spotted.Also applies to: 102-112, 114-140
ledger/certs.go (1)
23-60: Well-factored, side-effect-free certificate deposit calculation
CalculateCertificateDepositscleanly centralizes deposit computation, always records entries (including zeros), and surfaces errors with the certificate index for debugging. This aligns with the new deposits-aware flow inledger/state.go/ledger/delta.goand addresses earlier concerns about distinguishing missing vs. zero deposits.database/models/account.go (2)
32-35: Bech32 helpers are clear and network-awareDelegating
String()toStringWithNetwork(false)and gating HRP selection onisTestnetmakes the API explicit while keeping the common mainnet case simple. Empty-key and encoding failure paths are handled cleanly.Also applies to: 41-69
72-79: Composite certificate constraints now match the intended lifecycle semanticsThe documented strategy and tags on
Deregistration,StakeDeregistration, andStakeRegistration(compositeuniqueIndexon(StakingKey, CertificateID)) prevent duplicate processing per unified certificate while still allowing multiple historical records across other models (Registration,StakeDelegation, etc.). This resolves the earlier “one cert per staking key” issue without sacrificing history for non-terminal certs.Also applies to: 80-86, 116-121, 127-133
database/plugin/metadata/sqlite/account_test.go (1)
189-287: Certificate delete/linkage tests validate the unified mapping design
TestDeleteCertificateandTestSetAccountWithCertificateLinkagecorrectly model the unified certificate row plus specificStakeRegistration, then assert:
- Cascading removal of both unified and specific rows via
DeleteCertificate.- Proper wiring of
Account.CertificateIDwhen a non-zero certificate ID is passed intoSetAccount, including reverse lookup bycertificate_id.This gives strong signal that the composite constraints and new SetAccount/SetCertificate flows are behaving as intended.
Also applies to: 289-401
ledger/state.go (2)
542-632: Channel + context handling betweenledgerReadChainandledgerProcessBlockslooks race-free nowMoving
close(resultCh)intoledgerReadChain(withdefer), adding thectx.Done()checks in both producer and consumer, and removing receiver-side closure eliminates the prior “send on closed channel” risk. The small buffer onreadChainResultChis also a reasonable choice to decouple the iterator loop from transient processing delays.Also applies to: 634-645, 656-661, 697-719, 838-845
887-907: Deposit precomputation and strict validation error handling inledgerProcessBlockPrecomputing per-certificate deposits via
CalculateCertificateDepositsand passing them intoaddTransactionWithDepositskeeps deposit logic centralized and ensures the delta layer has all information upfront. At the same time,ValidateTxFuncerrors are now propagated directly (TX validation failure: %w), restoring the expected “invalid TX aborts block processing” behavior. This combination matches the unified certificate/deposit design and avoids silent validation failures.Also applies to: 908-923, 927-936
| func (u *Uint64) Scan(val any) error { | ||
| v, ok := val.(string) | ||
| if !ok { | ||
| if val == nil { | ||
| // Treat NULL as zero; adjust if a different semantic is desired. | ||
| u.Val = 0 | ||
| return nil | ||
| } | ||
| var v string | ||
| switch t := val.(type) { | ||
| case string: | ||
| v = t | ||
| case []byte: | ||
| v = string(t) | ||
| case int64: | ||
| u.Val = uint64(t) // #nosec G115 – application-level guarantee about range | ||
| return nil | ||
| default: | ||
| return fmt.Errorf( | ||
| "value was not expected type, wanted string, got %T", | ||
| "value was not expected type, wanted string, []byte, or int64, got %T", | ||
| val, | ||
| ) | ||
| } | ||
| tmpUint, err := strconv.ParseUint(v, 10, 64) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| *u = Uint64(tmpUint) | ||
| u.Val = tmpUint | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify the int64 → uint64 cast safety assumption.
Line 87 casts int64 to uint64 with #nosec G115 and claims "application-level guarantee about range," but no bounds checking is present. If a database driver returns a negative int64 value, this cast will produce an incorrect large uint64.
Options to address this:
- Add bounds checking (recommended):
case int64:
+ if t < 0 {
+ return fmt.Errorf("cannot convert negative int64 %d to uint64", t)
+ }
u.Val = uint64(t) // #nosec G115- Document the guarantee: If the application truly guarantees non-negative values at the database driver level, document this in the function comment.
Run the following to check if similar patterns exist elsewhere:
🏁 Script executed:
#!/bin/bash
# Description: Check for similar int64→uint64 casts and their handling
# Find other int64 to uint64 conversions
echo "=== int64 to uint64 conversions ==="
rg -n "uint64\(.*int64" database/ --type=go -C 2
# Check if there are validation patterns
echo "=== Negative value checks before uint conversion ==="
rg -n "< 0.*uint64|negative.*uint" database/ --type=go -C 2Length of output: 88
🏁 Script executed:
#!/bin/bash
# Verify file location and content
echo "=== Locating types.go files ==="
fd "types.go" --type f
echo -e "\n=== Content of database/types/types.go (lines 74-100) ==="
if [ -f "database/types/types.go" ]; then
sed -n '74,100p' "database/types/types.go"
else
echo "File not found at database/types/types.go"
fi
echo -e "\n=== Search for all int64 and uint64 in types.go ==="
if [ -f "database/types/types.go" ]; then
rg -n "int64|uint64" "database/types/types.go" -C 1
fi
echo -e "\n=== Check all Scan method implementations ==="
rg -n "func.*Scan" --type=go -A 15 | head -100Length of output: 5664
🏁 Script executed:
#!/bin/bash
# Check if tests cover negative int64 input to Uint64.Scan
echo "=== Scan tests in types_test.go ==="
rg -n "Scan" "database/types/types_test.go" -A 10 -B 2
echo -e "\n=== Look for negative test cases ==="
rg -n "negative|-[0-9]|< *0" "database/types/types_test.go"Length of output: 2231
Add bounds checking to prevent silent wraparound on negative int64 values.
Line 87 in database/types/types.go casts int64 directly to uint64 without validation. The #nosec G115 comment claims "application-level guarantee about range," but:
- Tests (lines 27–109 in
types_test.go) do not cover negativeint64input - Similar code in
database/models/certificate.go:113–117explicitly validatesuintoverflow before conversion - A negative
int64from a database driver would silently wrap to a large positiveuint64
Add bounds checking:
case int64:
+ if t < 0 {
+ return fmt.Errorf("cannot convert negative int64 %d to uint64", t)
+ }
u.Val = uint64(t) // #nosec G115Also add a test case covering negative int64 input in database/types/types_test.go.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (u *Uint64) Scan(val any) error { | |
| v, ok := val.(string) | |
| if !ok { | |
| if val == nil { | |
| // Treat NULL as zero; adjust if a different semantic is desired. | |
| u.Val = 0 | |
| return nil | |
| } | |
| var v string | |
| switch t := val.(type) { | |
| case string: | |
| v = t | |
| case []byte: | |
| v = string(t) | |
| case int64: | |
| u.Val = uint64(t) // #nosec G115 – application-level guarantee about range | |
| return nil | |
| default: | |
| return fmt.Errorf( | |
| "value was not expected type, wanted string, got %T", | |
| "value was not expected type, wanted string, []byte, or int64, got %T", | |
| val, | |
| ) | |
| } | |
| tmpUint, err := strconv.ParseUint(v, 10, 64) | |
| if err != nil { | |
| return err | |
| } | |
| *u = Uint64(tmpUint) | |
| u.Val = tmpUint | |
| return nil | |
| func (u *Uint64) Scan(val any) error { | |
| if val == nil { | |
| // Treat NULL as zero; adjust if a different semantic is desired. | |
| u.Val = 0 | |
| return nil | |
| } | |
| var v string | |
| switch t := val.(type) { | |
| case string: | |
| v = t | |
| case []byte: | |
| v = string(t) | |
| case int64: | |
| if t < 0 { | |
| return fmt.Errorf("cannot convert negative int64 %d to uint64", t) | |
| } | |
| u.Val = uint64(t) // #nosec G115 – application-level guarantee about range | |
| return nil | |
| default: | |
| return fmt.Errorf( | |
| "value was not expected type, wanted string, []byte, or int64, got %T", | |
| val, | |
| ) | |
| } | |
| tmpUint, err := strconv.ParseUint(v, 10, 64) | |
| if err != nil { | |
| return err | |
| } | |
| u.Val = tmpUint | |
| return nil |
🤖 Prompt for AI Agents
In database/types/types.go around lines 74–100, the Scan method casts int64 to
uint64 without checking for negative values which would silently wrap; modify
the int64 case to validate the value is non-negative before converting (return
an error if negative or out of expected range), mirror the explicit overflow
validation pattern used in database/models/certificate.go, and keep the existing
#nosec only if a proper range check is performed; additionally add a unit test
in database/types/types_test.go covering a negative int64 input to assert Scan
returns an error.
Implements a unified certificate persistence system to handle all Cardano certificate types through a single interface.
Changes
Testing
Closes #878
Summary by CodeRabbit
New Features
Refactor
Chores
✏️ Tip: You can customize this high-level summary in your review settings.