Skip to content

Commit aefae1a

Browse files
committed
feat(database): unified certificate lookup table
Signed-off-by: Chris Gianelloni <[email protected]>
1 parent 0a2b68c commit aefae1a

File tree

3 files changed

+127
-30
lines changed

3 files changed

+127
-30
lines changed

database/database_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"time"
2020

2121
"github.com/blinklabs-io/dingo/database"
22+
"github.com/blinklabs-io/dingo/database/models"
2223
"gorm.io/gorm"
2324
)
2425

@@ -68,3 +69,28 @@ func TestInMemorySqliteMultipleTransaction(t *testing.T) {
6869
t.Fatalf("unexpected error: %s", err)
6970
}
7071
}
72+
73+
// TestUnifiedCertificateCreation tests that unified certificate records are created
74+
// when processing transactions with certificates
75+
func TestUnifiedCertificateCreation(t *testing.T) {
76+
db, err := database.New(dbConfig)
77+
if err != nil {
78+
t.Fatalf("unexpected error: %s", err)
79+
}
80+
81+
// Run auto-migration to ensure tables exist
82+
if err := db.Metadata().DB().AutoMigrate(models.MigrateModels...); err != nil {
83+
t.Fatalf("failed to auto-migrate: %v", err)
84+
}
85+
86+
// Check that the certs table exists by trying to query it
87+
var count int64
88+
if result := db.Metadata().DB().Model(&models.Certificate{}).Count(&count); result.Error != nil {
89+
t.Fatalf("failed to query certs table: %v", result.Error)
90+
}
91+
92+
// The count should be 0 initially (no certificates processed yet)
93+
if count != 0 {
94+
t.Errorf("expected 0 certificates initially, got %d", count)
95+
}
96+
}

database/models/certs.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@
1414

1515
package models
1616

17-
import "github.com/blinklabs-io/dingo/database/types"
18-
17+
// Certificate maps transaction certificates to their specialized table records.
18+
// Provides unified indexing across all certificate types without requiring joins.
19+
//
20+
// All certificate types now have dedicated specialized models. The CertificateID field
21+
// references the ID of the specific certificate record based on CertType.
1922
type Certificate struct {
20-
Cbor []byte `gorm:"-"`
21-
Pool []byte
22-
Credential []byte
23-
Drep []byte
24-
CertType uint `gorm:"index"`
25-
Epoch uint64
26-
Amount types.Uint64
27-
ID uint `gorm:"primaryKey"`
23+
BlockHash []byte `gorm:"index"`
24+
ID uint `gorm:"primaryKey"`
25+
TransactionID uint `gorm:"index,uniqueIndex:uniq_tx_cert;constraint:OnDelete:CASCADE"`
26+
CertificateID uint `gorm:"index"` // Polymorphic FK to certificate table based on CertType. Not DB-enforced.
27+
Slot uint64 `gorm:"index"`
28+
CertIndex uint `gorm:"column:cert_index;uniqueIndex:uniq_tx_cert"`
29+
CertType uint `gorm:"index"`
2830
}
2931

3032
func (Certificate) TableName() string {

database/plugin/metadata/sqlite/transaction.go

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,10 @@ func (d *MetadataStoreSqlite) SetTransaction(
207207
if tmpTx.ID == 0 {
208208
existingTx, err := d.GetTransactionByHash(txHash, txn)
209209
if err != nil {
210-
return fmt.Errorf("failed to fetch transaction ID after upsert: %w", err)
210+
return fmt.Errorf(
211+
"failed to fetch transaction ID after upsert: %w",
212+
err,
213+
)
211214
}
212215
if existingTx == nil {
213216
return fmt.Errorf("transaction not found after upsert: %x", txHash)
@@ -482,8 +485,12 @@ func (d *MetadataStoreSqlite) SetTransaction(
482485
Tag: uint8(key.Tag),
483486
Index: key.Index,
484487
Data: value.Data.Cbor(),
485-
ExUnitsMemory: uint64(max(0, value.ExUnits.Memory)), //nolint:gosec
486-
ExUnitsCPU: uint64(max(0, value.ExUnits.Steps)), //nolint:gosec
488+
ExUnitsMemory: uint64( //nolint:gosec
489+
max(0, value.ExUnits.Memory),
490+
),
491+
ExUnitsCPU: uint64( //nolint:gosec
492+
max(0, value.ExUnits.Steps),
493+
),
487494
}
488495
if result := txn.Create(&redeemer); result.Error != nil {
489496
return fmt.Errorf("create redeemer: %w", result.Error)
@@ -503,6 +510,67 @@ func (d *MetadataStoreSqlite) SetTransaction(
503510
if tx.IsValid() {
504511
certs := tx.Certificates()
505512
if len(certs) > 0 {
513+
// Create unified certificate records first
514+
certIDMap := make(map[int]uint)
515+
for i, cert := range certs {
516+
var certType uint
517+
switch cert.(type) {
518+
case *lcommon.PoolRegistrationCertificate:
519+
certType = uint(lcommon.CertificateTypePoolRegistration)
520+
case *lcommon.StakeRegistrationCertificate:
521+
certType = uint(lcommon.CertificateTypeStakeRegistration)
522+
case *lcommon.PoolRetirementCertificate:
523+
certType = uint(lcommon.CertificateTypePoolRetirement)
524+
case *lcommon.StakeDeregistrationCertificate:
525+
certType = uint(lcommon.CertificateTypeStakeDeregistration)
526+
case *lcommon.DeregistrationCertificate:
527+
certType = uint(lcommon.CertificateTypeDeregistration)
528+
case *lcommon.StakeDelegationCertificate:
529+
certType = uint(lcommon.CertificateTypeStakeDelegation)
530+
case *lcommon.StakeRegistrationDelegationCertificate:
531+
certType = uint(lcommon.CertificateTypeStakeRegistrationDelegation)
532+
case *lcommon.StakeVoteDelegationCertificate:
533+
certType = uint(lcommon.CertificateTypeStakeVoteDelegation)
534+
case *lcommon.RegistrationCertificate:
535+
certType = uint(lcommon.CertificateTypeRegistration)
536+
case *lcommon.RegistrationDrepCertificate:
537+
certType = uint(lcommon.CertificateTypeRegistrationDrep)
538+
case *lcommon.DeregistrationDrepCertificate:
539+
certType = uint(lcommon.CertificateTypeDeregistrationDrep)
540+
case *lcommon.UpdateDrepCertificate:
541+
certType = uint(lcommon.CertificateTypeUpdateDrep)
542+
case *lcommon.StakeVoteRegistrationDelegationCertificate:
543+
certType = uint(lcommon.CertificateTypeStakeVoteRegistrationDelegation)
544+
case *lcommon.VoteRegistrationDelegationCertificate:
545+
certType = uint(lcommon.CertificateTypeVoteRegistrationDelegation)
546+
case *lcommon.VoteDelegationCertificate:
547+
certType = uint(lcommon.CertificateTypeVoteDelegation)
548+
case *lcommon.AuthCommitteeHotCertificate:
549+
certType = uint(lcommon.CertificateTypeAuthCommitteeHot)
550+
case *lcommon.ResignCommitteeColdCertificate:
551+
certType = uint(lcommon.CertificateTypeResignCommitteeCold)
552+
case *lcommon.MoveInstantaneousRewardsCertificate:
553+
certType = uint(lcommon.CertificateTypeMoveInstantaneousRewards)
554+
default:
555+
d.logger.Warn("unknown certificate type", "type", fmt.Sprintf("%T", cert))
556+
continue
557+
}
558+
unifiedCert := models.Certificate{
559+
TransactionID: tmpTx.ID,
560+
CertIndex: uint(i), //nolint:gosec
561+
CertType: certType,
562+
Slot: point.Slot,
563+
BlockHash: point.Hash,
564+
CertificateID: 0, // Will be set to specialized record ID later if needed
565+
}
566+
if result := txn.Create(&unifiedCert); result.Error != nil {
567+
return fmt.Errorf(
568+
"create unified certificate: %w",
569+
result.Error,
570+
)
571+
}
572+
certIDMap[i] = unifiedCert.ID
573+
}
506574
for i, cert := range certs {
507575
deposit := uint64(0)
508576
if certDeposits != nil {
@@ -545,7 +613,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
545613
RewardAccount: c.RewardAccount[:],
546614
AddedSlot: point.Slot,
547615
DepositAmount: types.Uint64(deposit),
548-
CertificateID: uint(i), //nolint:gosec
616+
CertificateID: certIDMap[i],
549617
}
550618
if c.PoolMetadata != nil {
551619
tmpReg.MetadataUrl = c.PoolMetadata.Url
@@ -605,7 +673,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
605673
StakingKey: stakeKey,
606674
AddedSlot: point.Slot,
607675
DepositAmount: types.Uint64(deposit),
608-
CertificateID: uint(i), //nolint:gosec
676+
CertificateID: certIDMap[i],
609677
}
610678

611679
if err := saveAccountIfNew(tmpAccount, txn); err != nil {
@@ -633,10 +701,11 @@ func (d *MetadataStoreSqlite) SetTransaction(
633701
}
634702

635703
tmpItem := models.PoolRetirement{
636-
PoolKeyHash: c.PoolKeyHash[:],
637-
Epoch: c.Epoch,
638-
AddedSlot: point.Slot,
639-
PoolID: tmpPool.ID,
704+
PoolKeyHash: c.PoolKeyHash[:],
705+
Epoch: c.Epoch,
706+
AddedSlot: point.Slot,
707+
PoolID: tmpPool.ID,
708+
CertificateID: certIDMap[i],
640709
}
641710

642711
if err := saveCertRecord(&tmpItem, txn); err != nil {
@@ -667,7 +736,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
667736
tmpItem := models.StakeDeregistration{
668737
StakingKey: stakeKey,
669738
AddedSlot: point.Slot,
670-
CertificateID: uint(i), //nolint:gosec
739+
CertificateID: certIDMap[i],
671740
}
672741

673742
if err := saveAccountIfNew(tmpAccount, txn); err != nil {
@@ -702,7 +771,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
702771
tmpItem := models.Deregistration{
703772
StakingKey: stakeKey,
704773
AddedSlot: point.Slot,
705-
CertificateID: uint(i), //nolint:gosec
774+
CertificateID: certIDMap[i],
706775
Amount: types.Uint64(deposit),
707776
}
708777

@@ -726,7 +795,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
726795
StakingKey: stakeKey,
727796
PoolKeyHash: c.PoolKeyHash[:],
728797
AddedSlot: point.Slot,
729-
CertificateID: uint(i), //nolint:gosec
798+
CertificateID: certIDMap[i],
730799
}
731800

732801
if err := saveAccountIfNew(tmpAccount, txn); err != nil {
@@ -750,7 +819,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
750819
PoolKeyHash: c.PoolKeyHash[:],
751820
AddedSlot: point.Slot,
752821
DepositAmount: types.Uint64(deposit),
753-
CertificateID: uint(i), //nolint:gosec
822+
CertificateID: certIDMap[i],
754823
}
755824

756825
if err := saveAccountIfNew(tmpAccount, txn); err != nil {
@@ -775,7 +844,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
775844
PoolKeyHash: c.PoolKeyHash[:],
776845
Drep: c.Drep.Credential[:],
777846
AddedSlot: point.Slot,
778-
CertificateID: uint(i), //nolint:gosec
847+
CertificateID: certIDMap[i],
779848
}
780849

781850
if err := saveAccountIfNew(tmpAccount, txn); err != nil {
@@ -796,7 +865,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
796865
StakingKey: stakeKey,
797866
AddedSlot: point.Slot,
798867
DepositAmount: types.Uint64(deposit),
799-
CertificateID: uint(i), //nolint:gosec
868+
CertificateID: certIDMap[i],
800869
}
801870

802871
if err := saveAccountIfNew(tmpAccount, txn); err != nil {
@@ -842,7 +911,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
842911
tmpUpdate := models.UpdateDrep{
843912
Credential: drepCredential,
844913
AddedSlot: point.Slot,
845-
CertificateID: uint(i), //nolint:gosec
914+
CertificateID: certIDMap[i],
846915
}
847916
if c.Anchor != nil {
848917
tmpUpdate.AnchorUrl = c.Anchor.Url
@@ -868,7 +937,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
868937
Drep: c.Drep.Credential[:],
869938
AddedSlot: point.Slot,
870939
DepositAmount: types.Uint64(deposit),
871-
CertificateID: uint(i), //nolint:gosec
940+
CertificateID: certIDMap[i],
872941
}
873942

874943
if err := saveAccountIfNew(tmpAccount, txn); err != nil {
@@ -892,7 +961,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
892961
Drep: c.Drep.Credential[:],
893962
AddedSlot: point.Slot,
894963
DepositAmount: types.Uint64(deposit),
895-
CertificateID: uint(i), //nolint:gosec
964+
CertificateID: certIDMap[i],
896965
}
897966

898967
if err := saveAccountIfNew(tmpAccount, txn); err != nil {
@@ -915,7 +984,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
915984
StakingKey: stakeKey,
916985
Drep: c.Drep.Credential[:],
917986
AddedSlot: point.Slot,
918-
CertificateID: uint(i), //nolint:gosec
987+
CertificateID: certIDMap[i],
919988
}
920989

921990
if err := saveAccountIfNew(tmpAccount, txn); err != nil {
@@ -957,7 +1026,7 @@ func (d *MetadataStoreSqlite) SetTransaction(
9571026
tmpMIR := models.MoveInstantaneousRewards{
9581027
Pot: c.Reward.Source,
9591028
AddedSlot: point.Slot,
960-
CertificateID: uint(i), //nolint:gosec
1029+
CertificateID: certIDMap[i],
9611030
}
9621031

9631032
// Save the MIR record

0 commit comments

Comments
 (0)