From eb6ab54c3647518e4171c74b47b71df6c9149fc0 Mon Sep 17 00:00:00 2001 From: Jenita Date: Mon, 17 Nov 2025 17:50:06 -0600 Subject: [PATCH 01/10] feat: track witness set for transaction Signed-off-by: Jenita --- database/models/datum.go | 12 ++++++++++ database/models/models.go | 4 ++++ database/models/redeemer.go | 43 ++++++++++++++++++++++++++++++++++ database/models/script.go | 38 ++++++++++++++++++++++++++++++ database/models/transaction.go | 20 +++++++++------- database/models/witness.go | 40 +++++++++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 database/models/redeemer.go create mode 100644 database/models/script.go create mode 100644 database/models/witness.go diff --git a/database/models/datum.go b/database/models/datum.go index ed61f18f..4f428ded 100644 --- a/database/models/datum.go +++ b/database/models/datum.go @@ -24,3 +24,15 @@ type Datum struct { func (Datum) TableName() string { return "datum" } + +// PlutusData represents a Plutus data value in the witness set +type PlutusData struct { + ID uint `gorm:"primaryKey"` + TransactionID uint `gorm:"index"` + Data []byte `gorm:"type:bytea"` + Transaction *Transaction +} + +func (PlutusData) TableName() string { + return "plutus_data" +} diff --git a/database/models/models.go b/database/models/models.go index 43f31494..a052f220 100644 --- a/database/models/models.go +++ b/database/models/models.go @@ -48,4 +48,8 @@ var MigrateModels = []any{ &Utxo{}, &VoteDelegation{}, &VoteRegistrationDelegation{}, + &Witness{}, + &Script{}, + &Redeemer{}, + &PlutusData{}, } diff --git a/database/models/redeemer.go b/database/models/redeemer.go new file mode 100644 index 00000000..a5b6f23e --- /dev/null +++ b/database/models/redeemer.go @@ -0,0 +1,43 @@ +// Copyright 2025 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +// RedeemerTag represents the tag for a redeemer (spend, mint, cert, reward, voting, proposing) +type RedeemerTag uint8 + +const ( + RedeemerTagSpend RedeemerTag = 0 + RedeemerTagMint RedeemerTag = 1 + RedeemerTagCert RedeemerTag = 2 + RedeemerTagReward RedeemerTag = 3 + RedeemerTagVoting RedeemerTag = 4 + RedeemerTagProposing RedeemerTag = 5 +) + +// Redeemer represents a redeemer in the witness set +type Redeemer struct { + ID uint `gorm:"primaryKey"` + TransactionID uint `gorm:"index"` + Tag uint8 `gorm:"index"` // RedeemerTag + Index uint32 `gorm:"index"` + Data []byte `gorm:"type:bytea"` // Plutus data + ExUnitsMemory uint64 + ExUnitsCPU uint64 + Transaction *Transaction +} + +func (Redeemer) TableName() string { + return "redeemer" +} diff --git a/database/models/script.go b/database/models/script.go new file mode 100644 index 00000000..fc1b28b0 --- /dev/null +++ b/database/models/script.go @@ -0,0 +1,38 @@ +// Copyright 2025 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +// ScriptType represents the type of script +type ScriptType uint8 + +const ( + ScriptTypeNative ScriptType = 0 + ScriptTypePlutusV1 ScriptType = 1 + ScriptTypePlutusV2 ScriptType = 2 + ScriptTypePlutusV3 ScriptType = 3 +) + +// Script represents a script entry in the witness set +type Script struct { + ID uint `gorm:"primaryKey"` + TransactionID uint `gorm:"index"` + Type uint8 `gorm:"index"` // ScriptType (0=Native, 1=PlutusV1, 2=PlutusV2, 3=PlutusV3) + ScriptData []byte `gorm:"type:bytea"` + Transaction *Transaction +} + +func (Script) TableName() string { + return "script" +} diff --git a/database/models/transaction.go b/database/models/transaction.go index 36f719e4..04aa0527 100644 --- a/database/models/transaction.go +++ b/database/models/transaction.go @@ -16,14 +16,18 @@ package models // Transaction represents a transaction record type Transaction struct { - Hash []byte `gorm:"uniqueIndex"` - BlockHash []byte `gorm:"index"` - Inputs []Utxo `gorm:"foreignKey:SpentAtTxId;references:Hash"` - Outputs []Utxo `gorm:"foreignKey:TransactionID;references:ID"` - ReferenceInputs []Utxo `gorm:"foreignKey:ReferencedByTxId;references:Hash"` - Collateral []Utxo `gorm:"foreignKey:CollateralByTxId;references:Hash"` - CollateralReturn *Utxo `gorm:"foreignKey:TransactionID;references:ID"` - ID uint `gorm:"primaryKey"` + Hash []byte `gorm:"uniqueIndex"` + BlockHash []byte `gorm:"index"` + Inputs []Utxo `gorm:"foreignKey:SpentAtTxId;references:Hash"` + Outputs []Utxo `gorm:"foreignKey:TransactionID;references:ID"` + ReferenceInputs []Utxo `gorm:"foreignKey:ReferencedByTxId;references:Hash"` + Collateral []Utxo `gorm:"foreignKey:CollateralByTxId;references:Hash"` + CollateralReturn *Utxo `gorm:"foreignKey:TransactionID;references:ID"` + Witnesses []Witness `gorm:"foreignKey:TransactionID;references:ID"` + Scripts []Script `gorm:"foreignKey:TransactionID;references:ID"` + Redeemers []Redeemer `gorm:"foreignKey:TransactionID;references:ID"` + PlutusDataList []PlutusData `gorm:"foreignKey:TransactionID;references:ID"` + ID uint `gorm:"primaryKey"` Type int BlockIndex uint32 Metadata []byte diff --git a/database/models/witness.go b/database/models/witness.go new file mode 100644 index 00000000..24c2a370 --- /dev/null +++ b/database/models/witness.go @@ -0,0 +1,40 @@ +// Copyright 2025 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +// WitnessType represents the type of witness +type WitnessType uint8 + +const ( + WitnessTypeVkey WitnessType = 0 + WitnessTypeBootstrap WitnessType = 1 +) + +// Witness represents a witness entry (Vkey or Bootstrap) +type Witness struct { + ID uint `gorm:"primaryKey"` + TransactionID uint `gorm:"index"` + Type uint8 `gorm:"index"` // WitnessType (0=Vkey, 1=Bootstrap) + Vkey []byte `gorm:"type:bytea"` + Signature []byte `gorm:"type:bytea"` + PublicKey []byte `gorm:"type:bytea"` // For Bootstrap + ChainCode []byte `gorm:"type:bytea"` // For Bootstrap + Attributes []byte `gorm:"type:bytea"` // For Bootstrap + Transaction *Transaction +} + +func (Witness) TableName() string { + return "witness" +} From 6d6530d682df88fd1357edac581ac24658d0bd9c Mon Sep 17 00:00:00 2001 From: Jenita Date: Wed, 19 Nov 2025 11:42:45 -0600 Subject: [PATCH 02/10] feat: track witness set for transaction Signed-off-by: Jenita --- database/models/models.go | 8 +- database/models/redeemer.go | 14 +-- database/models/script.go | 17 +-- database/models/transaction.go | 4 +- database/models/witness.go | 19 +-- .../plugin/metadata/sqlite/transaction.go | 109 ++++++++++++++++++ 6 files changed, 128 insertions(+), 43 deletions(-) diff --git a/database/models/models.go b/database/models/models.go index a052f220..882e3107 100644 --- a/database/models/models.go +++ b/database/models/models.go @@ -26,6 +26,7 @@ var MigrateModels = []any{ &DeregistrationDrep{}, &Drep{}, &Epoch{}, + &KeyWitness{}, &Pool{}, &PoolRegistration{}, &PoolRegistrationOwner{}, @@ -33,9 +34,12 @@ var MigrateModels = []any{ &PoolRetirement{}, &PParams{}, &PParamUpdate{}, + &PlutusData{}, &Registration{}, &RegistrationDrep{}, + &Redeemer{}, &ResignCommitteeCold{}, + &Script{}, &StakeDelegation{}, &StakeDeregistration{}, &StakeRegistration{}, @@ -48,8 +52,4 @@ var MigrateModels = []any{ &Utxo{}, &VoteDelegation{}, &VoteRegistrationDelegation{}, - &Witness{}, - &Script{}, - &Redeemer{}, - &PlutusData{}, } diff --git a/database/models/redeemer.go b/database/models/redeemer.go index a5b6f23e..dfff5ffa 100644 --- a/database/models/redeemer.go +++ b/database/models/redeemer.go @@ -14,23 +14,11 @@ package models -// RedeemerTag represents the tag for a redeemer (spend, mint, cert, reward, voting, proposing) -type RedeemerTag uint8 - -const ( - RedeemerTagSpend RedeemerTag = 0 - RedeemerTagMint RedeemerTag = 1 - RedeemerTagCert RedeemerTag = 2 - RedeemerTagReward RedeemerTag = 3 - RedeemerTagVoting RedeemerTag = 4 - RedeemerTagProposing RedeemerTag = 5 -) - // Redeemer represents a redeemer in the witness set type Redeemer struct { ID uint `gorm:"primaryKey"` TransactionID uint `gorm:"index"` - Tag uint8 `gorm:"index"` // RedeemerTag + Tag uint8 `gorm:"index"` // Redeemer tag Index uint32 `gorm:"index"` Data []byte `gorm:"type:bytea"` // Plutus data ExUnitsMemory uint64 diff --git a/database/models/script.go b/database/models/script.go index fc1b28b0..83443bd3 100644 --- a/database/models/script.go +++ b/database/models/script.go @@ -14,21 +14,16 @@ package models -// ScriptType represents the type of script -type ScriptType uint8 - -const ( - ScriptTypeNative ScriptType = 0 - ScriptTypePlutusV1 ScriptType = 1 - ScriptTypePlutusV2 ScriptType = 2 - ScriptTypePlutusV3 ScriptType = 3 -) - // Script represents a script entry in the witness set +// Type corresponds to ScriptRefType constants from gouroboros/ledger/common: +// 0=NativeScript (ScriptRefTypeNativeScript) +// 1=PlutusV1 (ScriptRefTypePlutusV1) +// 2=PlutusV2 (ScriptRefTypePlutusV2) +// 3=PlutusV3 (ScriptRefTypePlutusV3) type Script struct { ID uint `gorm:"primaryKey"` TransactionID uint `gorm:"index"` - Type uint8 `gorm:"index"` // ScriptType (0=Native, 1=PlutusV1, 2=PlutusV2, 3=PlutusV3) + Type uint8 `gorm:"index"` // Script type ScriptData []byte `gorm:"type:bytea"` Transaction *Transaction } diff --git a/database/models/transaction.go b/database/models/transaction.go index 04aa0527..5a487fed 100644 --- a/database/models/transaction.go +++ b/database/models/transaction.go @@ -23,10 +23,10 @@ type Transaction struct { ReferenceInputs []Utxo `gorm:"foreignKey:ReferencedByTxId;references:Hash"` Collateral []Utxo `gorm:"foreignKey:CollateralByTxId;references:Hash"` CollateralReturn *Utxo `gorm:"foreignKey:TransactionID;references:ID"` - Witnesses []Witness `gorm:"foreignKey:TransactionID;references:ID"` + KeyWitnesses []KeyWitness `gorm:"foreignKey:TransactionID;references:ID"` Scripts []Script `gorm:"foreignKey:TransactionID;references:ID"` Redeemers []Redeemer `gorm:"foreignKey:TransactionID;references:ID"` - PlutusDataList []PlutusData `gorm:"foreignKey:TransactionID;references:ID"` + PlutusData []PlutusData `gorm:"foreignKey:TransactionID;references:ID"` ID uint `gorm:"primaryKey"` Type int BlockIndex uint32 diff --git a/database/models/witness.go b/database/models/witness.go index 24c2a370..e48ebe57 100644 --- a/database/models/witness.go +++ b/database/models/witness.go @@ -14,19 +14,12 @@ package models -// WitnessType represents the type of witness -type WitnessType uint8 - -const ( - WitnessTypeVkey WitnessType = 0 - WitnessTypeBootstrap WitnessType = 1 -) - -// Witness represents a witness entry (Vkey or Bootstrap) -type Witness struct { +// KeyWitness represents a key witness entry (Vkey or Bootstrap) +// Type: 0 = VkeyWitness, 1 = BootstrapWitness +type KeyWitness struct { ID uint `gorm:"primaryKey"` TransactionID uint `gorm:"index"` - Type uint8 `gorm:"index"` // WitnessType (0=Vkey, 1=Bootstrap) + Type uint8 `gorm:"index"` // 0=Vkey, 1=Bootstrap Vkey []byte `gorm:"type:bytea"` Signature []byte `gorm:"type:bytea"` PublicKey []byte `gorm:"type:bytea"` // For Bootstrap @@ -35,6 +28,6 @@ type Witness struct { Transaction *Transaction } -func (Witness) TableName() string { - return "witness" +func (KeyWitness) TableName() string { + return "key_witness" } diff --git a/database/plugin/metadata/sqlite/transaction.go b/database/plugin/metadata/sqlite/transaction.go index 4bf37ac8..8b690858 100644 --- a/database/plugin/metadata/sqlite/transaction.go +++ b/database/plugin/metadata/sqlite/transaction.go @@ -277,6 +277,115 @@ func (d *MetadataStoreSqlite) SetTransaction( return result.Error } } + // Extract and save witness set data + if tx.Witnesses() != nil { + ws := tx.Witnesses() + + // Add Vkey Witnesses + for _, vkey := range ws.Vkey() { + keyWitness := models.KeyWitness{ + TransactionID: tmpTx.ID, + Type: 0, // VkeyWitness + Vkey: vkey.Vkey, + Signature: vkey.Signature, + } + if result := txn.Create(&keyWitness); result.Error != nil { + return fmt.Errorf("create vkey witness: %w", result.Error) + } + } + + // Add Bootstrap Witnesses + for _, bootstrap := range ws.Bootstrap() { + keyWitness := models.KeyWitness{ + TransactionID: tmpTx.ID, + Type: 1, // BootstrapWitness + PublicKey: bootstrap.PublicKey, + Signature: bootstrap.Signature, + ChainCode: bootstrap.ChainCode, + Attributes: bootstrap.Attributes, + } + if result := txn.Create(&keyWitness); result.Error != nil { + return fmt.Errorf("create bootstrap witness: %w", result.Error) + } + } + + // Add Native Scripts + for _, script := range ws.NativeScripts() { + scriptRecord := models.Script{ + TransactionID: tmpTx.ID, + Type: uint8(lcommon.ScriptRefTypeNativeScript), + ScriptData: script.Cbor(), + } + if result := txn.Create(&scriptRecord); result.Error != nil { + return fmt.Errorf("create native script: %w", result.Error) + } + } + + // Add PlutusV1 Scripts + for _, script := range ws.PlutusV1Scripts() { + scriptRecord := models.Script{ + TransactionID: tmpTx.ID, + Type: uint8(lcommon.ScriptRefTypePlutusV1), + ScriptData: script, + } + if result := txn.Create(&scriptRecord); result.Error != nil { + return fmt.Errorf("create plutus v1 script: %w", result.Error) + } + } + + // Add PlutusV2 Scripts + for _, script := range ws.PlutusV2Scripts() { + scriptRecord := models.Script{ + TransactionID: tmpTx.ID, + Type: uint8(lcommon.ScriptRefTypePlutusV2), + ScriptData: script, + } + if result := txn.Create(&scriptRecord); result.Error != nil { + return fmt.Errorf("create plutus v2 script: %w", result.Error) + } + } + + // Add PlutusV3 Scripts + for _, script := range ws.PlutusV3Scripts() { + scriptRecord := models.Script{ + TransactionID: tmpTx.ID, + Type: uint8(lcommon.ScriptRefTypePlutusV3), + ScriptData: script, + } + if result := txn.Create(&scriptRecord); result.Error != nil { + return fmt.Errorf("create plutus v3 script: %w", result.Error) + } + } + + // Add PlutusData (Datums) + for _, datum := range ws.PlutusData() { + plutusData := models.PlutusData{ + TransactionID: tmpTx.ID, + Data: datum.Cbor(), + } + if result := txn.Create(&plutusData); result.Error != nil { + return fmt.Errorf("create plutus data: %w", result.Error) + } + } + + // Add Redeemers + if ws.Redeemers() != nil { + for key, value := range ws.Redeemers().Iter() { + redeemer := models.Redeemer{ + TransactionID: tmpTx.ID, + Tag: uint8(key.Tag), + Index: key.Index, + Data: value.Data.Cbor(), + ExUnitsMemory: uint64(value.ExUnits.Memory), + ExUnitsCPU: uint64(value.ExUnits.Steps), + } + if result := txn.Create(&redeemer); result.Error != nil { + return fmt.Errorf("create redeemer: %w", result.Error) + } + } + } + } + // Avoid updating associations result = txn.Omit(clause.Associations).Save(&tmpTx) if result.Error != nil { From c3fa7a03602e6ca41de4e26088d036a494d9ec09 Mon Sep 17 00:00:00 2001 From: Jenita Date: Wed, 19 Nov 2025 12:04:06 -0600 Subject: [PATCH 03/10] feat: track witness set for transaction Signed-off-by: Jenita --- .../plugin/metadata/sqlite/transaction.go | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/database/plugin/metadata/sqlite/transaction.go b/database/plugin/metadata/sqlite/transaction.go index 8b690858..9708247e 100644 --- a/database/plugin/metadata/sqlite/transaction.go +++ b/database/plugin/metadata/sqlite/transaction.go @@ -89,6 +89,17 @@ func (d *MetadataStoreSqlite) SetTransaction( if result.Error != nil { return fmt.Errorf("create transaction: %w", result.Error) } + // If ID is still zero (conflict path with SQLite), fetch it by hash + if tmpTx.ID == 0 { + existingTx, err := d.GetTransactionByHash(txHash, txn) + if err != nil { + return fmt.Errorf("failed to fetch transaction ID after upsert: %w", err) + } + if existingTx == nil { + return fmt.Errorf("transaction not found after upsert: %x", txHash) + } + tmpTx.ID = existingTx.ID + } // Add Inputs to Transaction for _, input := range tx.Inputs() { inTxId := input.Id().Bytes() @@ -278,6 +289,21 @@ func (d *MetadataStoreSqlite) SetTransaction( } } // Extract and save witness set data + // Delete existing witness records to ensure idempotency on retry + if tmpTx.ID != 0 { + if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.KeyWitness{}); result.Error != nil { + return fmt.Errorf("delete existing key witnesses: %w", result.Error) + } + if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.Script{}); result.Error != nil { + return fmt.Errorf("delete existing scripts: %w", result.Error) + } + if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.Redeemer{}); result.Error != nil { + return fmt.Errorf("delete existing redeemers: %w", result.Error) + } + if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.PlutusData{}); result.Error != nil { + return fmt.Errorf("delete existing plutus data: %w", result.Error) + } + } if tx.Witnesses() != nil { ws := tx.Witnesses() From dab5d824dea31b15e631d75c020a1e349a7f89c6 Mon Sep 17 00:00:00 2001 From: Jenita Date: Wed, 19 Nov 2025 12:30:24 -0600 Subject: [PATCH 04/10] feat: track witness set for transaction Signed-off-by: Jenita --- database/plugin/metadata/sqlite/transaction.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/plugin/metadata/sqlite/transaction.go b/database/plugin/metadata/sqlite/transaction.go index 9708247e..38b87370 100644 --- a/database/plugin/metadata/sqlite/transaction.go +++ b/database/plugin/metadata/sqlite/transaction.go @@ -402,8 +402,8 @@ func (d *MetadataStoreSqlite) SetTransaction( Tag: uint8(key.Tag), Index: key.Index, Data: value.Data.Cbor(), - ExUnitsMemory: uint64(value.ExUnits.Memory), - ExUnitsCPU: uint64(value.ExUnits.Steps), + ExUnitsMemory: uint64(max(0, value.ExUnits.Memory)), //nolint:gosec + ExUnitsCPU: uint64(max(0, value.ExUnits.Steps)), //nolint:gosec } if result := txn.Create(&redeemer); result.Error != nil { return fmt.Errorf("create redeemer: %w", result.Error) From 6f5b11b53f28a65329b5ca37bfbd77f600049c41 Mon Sep 17 00:00:00 2001 From: Jenita Date: Mon, 24 Nov 2025 10:28:14 -0600 Subject: [PATCH 05/10] feat: track witness set for transaction Signed-off-by: Jenita --- database/models/models.go | 1 + database/models/redeemer.go | 2 +- database/models/script.go | 8 +- database/models/script_content.go | 30 ++++++++ database/models/witness.go | 21 +++-- database/plugin/metadata/sqlite/script.go | 58 ++++++++++++++ .../plugin/metadata/sqlite/transaction.go | 76 ++++++++++++++++--- database/plugin/metadata/store.go | 8 ++ 8 files changed, 185 insertions(+), 19 deletions(-) create mode 100644 database/models/script_content.go create mode 100644 database/plugin/metadata/sqlite/script.go diff --git a/database/models/models.go b/database/models/models.go index 882e3107..d9374be3 100644 --- a/database/models/models.go +++ b/database/models/models.go @@ -40,6 +40,7 @@ var MigrateModels = []any{ &Redeemer{}, &ResignCommitteeCold{}, &Script{}, + &ScriptContent{}, &StakeDelegation{}, &StakeDeregistration{}, &StakeRegistration{}, diff --git a/database/models/redeemer.go b/database/models/redeemer.go index dfff5ffa..b63f79fd 100644 --- a/database/models/redeemer.go +++ b/database/models/redeemer.go @@ -20,7 +20,7 @@ type Redeemer struct { TransactionID uint `gorm:"index"` Tag uint8 `gorm:"index"` // Redeemer tag Index uint32 `gorm:"index"` - Data []byte `gorm:"type:bytea"` // Plutus data + Data []byte // Plutus data ExUnitsMemory uint64 ExUnitsCPU uint64 Transaction *Transaction diff --git a/database/models/script.go b/database/models/script.go index 83443bd3..12ebea6b 100644 --- a/database/models/script.go +++ b/database/models/script.go @@ -14,17 +14,21 @@ package models -// Script represents a script entry in the witness set +// Script represents a reference to a script in the witness set // Type corresponds to ScriptRefType constants from gouroboros/ledger/common: // 0=NativeScript (ScriptRefTypeNativeScript) // 1=PlutusV1 (ScriptRefTypePlutusV1) // 2=PlutusV2 (ScriptRefTypePlutusV2) // 3=PlutusV3 (ScriptRefTypePlutusV3) +// +// To avoid storing duplicate script data for the same script used in multiple +// transactions, we store only the script hash here. The actual script content +// is stored separately in ScriptContent table, indexed by hash. type Script struct { ID uint `gorm:"primaryKey"` TransactionID uint `gorm:"index"` Type uint8 `gorm:"index"` // Script type - ScriptData []byte `gorm:"type:bytea"` + ScriptHash []byte `gorm:"index"` // Hash of the script Transaction *Transaction } diff --git a/database/models/script_content.go b/database/models/script_content.go new file mode 100644 index 00000000..aeb028e8 --- /dev/null +++ b/database/models/script_content.go @@ -0,0 +1,30 @@ +// Copyright 2025 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +// ScriptContent represents the content of a script, indexed by its hash +// This avoids storing duplicate script data when the same script appears +// in multiple transactions +type ScriptContent struct { + ID uint `gorm:"primaryKey"` + Hash []byte `gorm:"index;unique"` // Script hash + Type uint8 `gorm:"index"` // Script type + Content []byte // Script content + CreatedSlot uint64 // Slot when this script was first seen +} + +func (ScriptContent) TableName() string { + return "script_content" +} diff --git a/database/models/witness.go b/database/models/witness.go index e48ebe57..44b651c3 100644 --- a/database/models/witness.go +++ b/database/models/witness.go @@ -14,17 +14,24 @@ package models +const ( + // KeyWitnessTypeVkey represents a Vkey witness + KeyWitnessTypeVkey uint8 = iota + // KeyWitnessTypeBootstrap represents a Bootstrap witness + KeyWitnessTypeBootstrap +) + // KeyWitness represents a key witness entry (Vkey or Bootstrap) -// Type: 0 = VkeyWitness, 1 = BootstrapWitness +// Type: KeyWitnessTypeVkey = VkeyWitness, KeyWitnessTypeBootstrap = BootstrapWitness type KeyWitness struct { ID uint `gorm:"primaryKey"` TransactionID uint `gorm:"index"` - Type uint8 `gorm:"index"` // 0=Vkey, 1=Bootstrap - Vkey []byte `gorm:"type:bytea"` - Signature []byte `gorm:"type:bytea"` - PublicKey []byte `gorm:"type:bytea"` // For Bootstrap - ChainCode []byte `gorm:"type:bytea"` // For Bootstrap - Attributes []byte `gorm:"type:bytea"` // For Bootstrap + Type uint8 `gorm:"index"` // See KeyWitnessType* constants + Vkey []byte // Vkey witness key + Signature []byte // Witness signature + PublicKey []byte // For Bootstrap witness + ChainCode []byte // For Bootstrap witness + Attributes []byte // For Bootstrap witness Transaction *Transaction } diff --git a/database/plugin/metadata/sqlite/script.go b/database/plugin/metadata/sqlite/script.go new file mode 100644 index 00000000..a0e42b6c --- /dev/null +++ b/database/plugin/metadata/sqlite/script.go @@ -0,0 +1,58 @@ +// Copyright 2025 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlite + +import ( + "errors" + + "github.com/blinklabs-io/dingo/database/models" + lcommon "github.com/blinklabs-io/gouroboros/ledger/common" + "gorm.io/gorm" +) + +// GetScriptContent returns the script content by its hash +func (d *MetadataStoreSqlite) GetScriptContent( + hash lcommon.ScriptHash, + txn *gorm.DB, +) (*models.ScriptContent, error) { + ret := &models.ScriptContent{} + if txn == nil { + txn = d.DB() + } + result := txn.First(ret, "hash = ?", hash[:]) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, result.Error + } + return ret, nil +} + +// GetScriptsByTransaction returns all scripts (references) used in a particular transaction +func (d *MetadataStoreSqlite) GetScriptsByTransaction( + txID uint, + txn *gorm.DB, +) ([]models.Script, error) { + var scripts []models.Script + if txn == nil { + txn = d.DB() + } + result := txn.Where("transaction_id = ?", txID).Find(&scripts) + if result.Error != nil { + return nil, result.Error + } + return scripts, nil +} diff --git a/database/plugin/metadata/sqlite/transaction.go b/database/plugin/metadata/sqlite/transaction.go index 38b87370..8d1fcb90 100644 --- a/database/plugin/metadata/sqlite/transaction.go +++ b/database/plugin/metadata/sqlite/transaction.go @@ -89,7 +89,9 @@ func (d *MetadataStoreSqlite) SetTransaction( if result.Error != nil { return fmt.Errorf("create transaction: %w", result.Error) } - // If ID is still zero (conflict path with SQLite), fetch it by hash + // SQLite's ON CONFLICT clause doesn't return the ID of an existing row when + // the conflict path is taken (no insert occurs). We need to fetch the ID + // explicitly so we can associate witness records with the correct transaction. if tmpTx.ID == 0 { existingTx, err := d.GetTransactionByHash(txHash, txn) if err != nil { @@ -304,14 +306,14 @@ func (d *MetadataStoreSqlite) SetTransaction( return fmt.Errorf("delete existing plutus data: %w", result.Error) } } - if tx.Witnesses() != nil { - ws := tx.Witnesses() + ws := tx.Witnesses() + if ws != nil { // Add Vkey Witnesses for _, vkey := range ws.Vkey() { keyWitness := models.KeyWitness{ TransactionID: tmpTx.ID, - Type: 0, // VkeyWitness + Type: models.KeyWitnessTypeVkey, Vkey: vkey.Vkey, Signature: vkey.Signature, } @@ -324,7 +326,7 @@ func (d *MetadataStoreSqlite) SetTransaction( for _, bootstrap := range ws.Bootstrap() { keyWitness := models.KeyWitness{ TransactionID: tmpTx.ID, - Type: 1, // BootstrapWitness + Type: models.KeyWitnessTypeBootstrap, PublicKey: bootstrap.PublicKey, Signature: bootstrap.Signature, ChainCode: bootstrap.ChainCode, @@ -337,50 +339,106 @@ func (d *MetadataStoreSqlite) SetTransaction( // Add Native Scripts for _, script := range ws.NativeScripts() { + scriptHash := script.Hash() scriptRecord := models.Script{ TransactionID: tmpTx.ID, Type: uint8(lcommon.ScriptRefTypeNativeScript), - ScriptData: script.Cbor(), + ScriptHash: scriptHash.Bytes(), } if result := txn.Create(&scriptRecord); result.Error != nil { return fmt.Errorf("create native script: %w", result.Error) } + // Also store the script content separately to avoid duplicates + scriptContent := models.ScriptContent{ + Hash: scriptHash.Bytes(), + Type: uint8(lcommon.ScriptRefTypeNativeScript), + Content: script.Cbor(), + CreatedSlot: point.Slot, + } + if result := txn.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "hash"}}, + DoNothing: true, + }).Create(&scriptContent); result.Error != nil { + return fmt.Errorf("create native script content: %w", result.Error) + } } // Add PlutusV1 Scripts for _, script := range ws.PlutusV1Scripts() { + scriptHash := script.Hash() scriptRecord := models.Script{ TransactionID: tmpTx.ID, Type: uint8(lcommon.ScriptRefTypePlutusV1), - ScriptData: script, + ScriptHash: scriptHash.Bytes(), } if result := txn.Create(&scriptRecord); result.Error != nil { return fmt.Errorf("create plutus v1 script: %w", result.Error) } + // Also store the script content separately to avoid duplicates + scriptContent := models.ScriptContent{ + Hash: scriptHash.Bytes(), + Type: uint8(lcommon.ScriptRefTypePlutusV1), + Content: script.RawScriptBytes(), + CreatedSlot: point.Slot, + } + if result := txn.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "hash"}}, + DoNothing: true, + }).Create(&scriptContent); result.Error != nil { + return fmt.Errorf("create plutus v1 script content: %w", result.Error) + } } // Add PlutusV2 Scripts for _, script := range ws.PlutusV2Scripts() { + scriptHash := script.Hash() scriptRecord := models.Script{ TransactionID: tmpTx.ID, Type: uint8(lcommon.ScriptRefTypePlutusV2), - ScriptData: script, + ScriptHash: scriptHash.Bytes(), } if result := txn.Create(&scriptRecord); result.Error != nil { return fmt.Errorf("create plutus v2 script: %w", result.Error) } + // Also store the script content separately to avoid duplicates + scriptContent := models.ScriptContent{ + Hash: scriptHash.Bytes(), + Type: uint8(lcommon.ScriptRefTypePlutusV2), + Content: script.RawScriptBytes(), + CreatedSlot: point.Slot, + } + if result := txn.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "hash"}}, + DoNothing: true, + }).Create(&scriptContent); result.Error != nil { + return fmt.Errorf("create plutus v2 script content: %w", result.Error) + } } // Add PlutusV3 Scripts for _, script := range ws.PlutusV3Scripts() { + scriptHash := script.Hash() scriptRecord := models.Script{ TransactionID: tmpTx.ID, Type: uint8(lcommon.ScriptRefTypePlutusV3), - ScriptData: script, + ScriptHash: scriptHash.Bytes(), } if result := txn.Create(&scriptRecord); result.Error != nil { return fmt.Errorf("create plutus v3 script: %w", result.Error) } + // Also store the script content separately to avoid duplicates + scriptContent := models.ScriptContent{ + Hash: scriptHash.Bytes(), + Type: uint8(lcommon.ScriptRefTypePlutusV3), + Content: script.RawScriptBytes(), + CreatedSlot: point.Slot, + } + if result := txn.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "hash"}}, + DoNothing: true, + }).Create(&scriptContent); result.Error != nil { + return fmt.Errorf("create plutus v3 script content: %w", result.Error) + } } // Add PlutusData (Datums) diff --git a/database/plugin/metadata/store.go b/database/plugin/metadata/store.go index c5abf9a4..00738824 100644 --- a/database/plugin/metadata/store.go +++ b/database/plugin/metadata/store.go @@ -83,6 +83,14 @@ type MetadataStore interface { []byte, // hash *gorm.DB, ) (*models.Transaction, error) + GetScriptContent( + lcommon.ScriptHash, + *gorm.DB, + ) (*models.ScriptContent, error) + GetScriptsByTransaction( + uint, // txID + *gorm.DB, + ) ([]models.Script, error) SetBlockNonce( []byte, // blockHash From 04852d08480cd029fec3228aec676fa40c173118 Mon Sep 17 00:00:00 2001 From: Jenita Date: Mon, 24 Nov 2025 10:41:42 -0600 Subject: [PATCH 06/10] feat: track witness set for transaction Signed-off-by: Jenita --- database/plugin/metadata/sqlite/transaction.go | 1 - 1 file changed, 1 deletion(-) diff --git a/database/plugin/metadata/sqlite/transaction.go b/database/plugin/metadata/sqlite/transaction.go index 8d1fcb90..cfe0e93c 100644 --- a/database/plugin/metadata/sqlite/transaction.go +++ b/database/plugin/metadata/sqlite/transaction.go @@ -308,7 +308,6 @@ func (d *MetadataStoreSqlite) SetTransaction( } ws := tx.Witnesses() if ws != nil { - // Add Vkey Witnesses for _, vkey := range ws.Vkey() { keyWitness := models.KeyWitness{ From 3260f679752ddeb8fb4f4d241852271eda76d736 Mon Sep 17 00:00:00 2001 From: Jenita Date: Mon, 24 Nov 2025 18:08:25 -0600 Subject: [PATCH 07/10] feat: addressed review comments Signed-off-by: Jenita --- database/models/datum.go | 6 +- database/models/models.go | 2 +- database/models/script.go | 10 +- database/models/script_content.go | 8 +- database/models/transaction.go | 24 +-- database/models/witness.go | 4 +- database/plugin/metadata/sqlite/script.go | 24 +-- .../plugin/metadata/sqlite/transaction.go | 183 +++++++----------- database/plugin/metadata/store.go | 8 +- 9 files changed, 107 insertions(+), 162 deletions(-) diff --git a/database/models/datum.go b/database/models/datum.go index 4f428ded..67647706 100644 --- a/database/models/datum.go +++ b/database/models/datum.go @@ -27,9 +27,9 @@ func (Datum) TableName() string { // PlutusData represents a Plutus data value in the witness set type PlutusData struct { - ID uint `gorm:"primaryKey"` - TransactionID uint `gorm:"index"` - Data []byte `gorm:"type:bytea"` + ID uint `gorm:"primaryKey"` + TransactionID uint `gorm:"index"` + Data []byte Transaction *Transaction } diff --git a/database/models/models.go b/database/models/models.go index d9374be3..6ccb8956 100644 --- a/database/models/models.go +++ b/database/models/models.go @@ -40,7 +40,6 @@ var MigrateModels = []any{ &Redeemer{}, &ResignCommitteeCold{}, &Script{}, - &ScriptContent{}, &StakeDelegation{}, &StakeDeregistration{}, &StakeRegistration{}, @@ -53,4 +52,5 @@ var MigrateModels = []any{ &Utxo{}, &VoteDelegation{}, &VoteRegistrationDelegation{}, + &WitnessScripts{}, } diff --git a/database/models/script.go b/database/models/script.go index 12ebea6b..17e4a37f 100644 --- a/database/models/script.go +++ b/database/models/script.go @@ -14,7 +14,7 @@ package models -// Script represents a reference to a script in the witness set +// WitnessScripts represents a reference to a script in the witness set // Type corresponds to ScriptRefType constants from gouroboros/ledger/common: // 0=NativeScript (ScriptRefTypeNativeScript) // 1=PlutusV1 (ScriptRefTypePlutusV1) @@ -23,8 +23,8 @@ package models // // To avoid storing duplicate script data for the same script used in multiple // transactions, we store only the script hash here. The actual script content -// is stored separately in ScriptContent table, indexed by hash. -type Script struct { +// is stored separately in Script table, indexed by hash. +type WitnessScripts struct { ID uint `gorm:"primaryKey"` TransactionID uint `gorm:"index"` Type uint8 `gorm:"index"` // Script type @@ -32,6 +32,6 @@ type Script struct { Transaction *Transaction } -func (Script) TableName() string { - return "script" +func (WitnessScripts) TableName() string { + return "witness_scripts" } diff --git a/database/models/script_content.go b/database/models/script_content.go index aeb028e8..238ace62 100644 --- a/database/models/script_content.go +++ b/database/models/script_content.go @@ -14,10 +14,10 @@ package models -// ScriptContent represents the content of a script, indexed by its hash +// Script represents the content of a script, indexed by its hash // This avoids storing duplicate script data when the same script appears // in multiple transactions -type ScriptContent struct { +type Script struct { ID uint `gorm:"primaryKey"` Hash []byte `gorm:"index;unique"` // Script hash Type uint8 `gorm:"index"` // Script type @@ -25,6 +25,6 @@ type ScriptContent struct { CreatedSlot uint64 // Slot when this script was first seen } -func (ScriptContent) TableName() string { - return "script_content" +func (Script) TableName() string { + return "script" } diff --git a/database/models/transaction.go b/database/models/transaction.go index 5a487fed..4d1b6ea3 100644 --- a/database/models/transaction.go +++ b/database/models/transaction.go @@ -16,18 +16,18 @@ package models // Transaction represents a transaction record type Transaction struct { - Hash []byte `gorm:"uniqueIndex"` - BlockHash []byte `gorm:"index"` - Inputs []Utxo `gorm:"foreignKey:SpentAtTxId;references:Hash"` - Outputs []Utxo `gorm:"foreignKey:TransactionID;references:ID"` - ReferenceInputs []Utxo `gorm:"foreignKey:ReferencedByTxId;references:Hash"` - Collateral []Utxo `gorm:"foreignKey:CollateralByTxId;references:Hash"` - CollateralReturn *Utxo `gorm:"foreignKey:TransactionID;references:ID"` - KeyWitnesses []KeyWitness `gorm:"foreignKey:TransactionID;references:ID"` - Scripts []Script `gorm:"foreignKey:TransactionID;references:ID"` - Redeemers []Redeemer `gorm:"foreignKey:TransactionID;references:ID"` - PlutusData []PlutusData `gorm:"foreignKey:TransactionID;references:ID"` - ID uint `gorm:"primaryKey"` + Hash []byte `gorm:"uniqueIndex"` + BlockHash []byte `gorm:"index"` + Inputs []Utxo `gorm:"foreignKey:SpentAtTxId;references:Hash"` + Outputs []Utxo `gorm:"foreignKey:TransactionID;references:ID"` + ReferenceInputs []Utxo `gorm:"foreignKey:ReferencedByTxId;references:Hash"` + Collateral []Utxo `gorm:"foreignKey:CollateralByTxId;references:Hash"` + CollateralReturn *Utxo `gorm:"foreignKey:TransactionID;references:ID"` + KeyWitnesses []KeyWitness `gorm:"foreignKey:TransactionID;references:ID"` + WitnessScripts []WitnessScripts `gorm:"foreignKey:TransactionID;references:ID"` + Redeemers []Redeemer `gorm:"foreignKey:TransactionID;references:ID"` + PlutusData []PlutusData `gorm:"foreignKey:TransactionID;references:ID"` + ID uint `gorm:"primaryKey"` Type int BlockIndex uint32 Metadata []byte diff --git a/database/models/witness.go b/database/models/witness.go index 44b651c3..3788ed79 100644 --- a/database/models/witness.go +++ b/database/models/witness.go @@ -16,9 +16,9 @@ package models const ( // KeyWitnessTypeVkey represents a Vkey witness - KeyWitnessTypeVkey uint8 = iota + KeyWitnessTypeVkey uint8 = 0 // KeyWitnessTypeBootstrap represents a Bootstrap witness - KeyWitnessTypeBootstrap + KeyWitnessTypeBootstrap uint8 = 1 ) // KeyWitness represents a key witness entry (Vkey or Bootstrap) diff --git a/database/plugin/metadata/sqlite/script.go b/database/plugin/metadata/sqlite/script.go index a0e42b6c..92f162d2 100644 --- a/database/plugin/metadata/sqlite/script.go +++ b/database/plugin/metadata/sqlite/script.go @@ -22,12 +22,12 @@ import ( "gorm.io/gorm" ) -// GetScriptContent returns the script content by its hash -func (d *MetadataStoreSqlite) GetScriptContent( +// GetScript returns the script content by its hash +func (d *MetadataStoreSqlite) GetScript( hash lcommon.ScriptHash, txn *gorm.DB, -) (*models.ScriptContent, error) { - ret := &models.ScriptContent{} +) (*models.Script, error) { + ret := &models.Script{} if txn == nil { txn = d.DB() } @@ -40,19 +40,3 @@ func (d *MetadataStoreSqlite) GetScriptContent( } return ret, nil } - -// GetScriptsByTransaction returns all scripts (references) used in a particular transaction -func (d *MetadataStoreSqlite) GetScriptsByTransaction( - txID uint, - txn *gorm.DB, -) ([]models.Script, error) { - var scripts []models.Script - if txn == nil { - txn = d.DB() - } - result := txn.Where("transaction_id = ?", txID).Find(&scripts) - if result.Error != nil { - return nil, result.Error - } - return scripts, nil -} diff --git a/database/plugin/metadata/sqlite/transaction.go b/database/plugin/metadata/sqlite/transaction.go index cfe0e93c..4aa4ce2e 100644 --- a/database/plugin/metadata/sqlite/transaction.go +++ b/database/plugin/metadata/sqlite/transaction.go @@ -292,19 +292,17 @@ func (d *MetadataStoreSqlite) SetTransaction( } // Extract and save witness set data // Delete existing witness records to ensure idempotency on retry - if tmpTx.ID != 0 { - if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.KeyWitness{}); result.Error != nil { - return fmt.Errorf("delete existing key witnesses: %w", result.Error) - } - if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.Script{}); result.Error != nil { - return fmt.Errorf("delete existing scripts: %w", result.Error) - } - if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.Redeemer{}); result.Error != nil { - return fmt.Errorf("delete existing redeemers: %w", result.Error) - } - if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.PlutusData{}); result.Error != nil { - return fmt.Errorf("delete existing plutus data: %w", result.Error) - } + if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.KeyWitness{}); result.Error != nil { + return fmt.Errorf("delete existing key witnesses: %w", result.Error) + } + if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.WitnessScripts{}); result.Error != nil { + return fmt.Errorf("delete existing witness scripts: %w", result.Error) + } + if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.Redeemer{}); result.Error != nil { + return fmt.Errorf("delete existing redeemers: %w", result.Error) + } + if result := txn.Where("transaction_id = ?", tmpTx.ID).Delete(&models.PlutusData{}); result.Error != nil { + return fmt.Errorf("delete existing plutus data: %w", result.Error) } ws := tx.Witnesses() if ws != nil { @@ -336,108 +334,75 @@ func (d *MetadataStoreSqlite) SetTransaction( } } - // Add Native Scripts - for _, script := range ws.NativeScripts() { - scriptHash := script.Hash() - scriptRecord := models.Script{ - TransactionID: tmpTx.ID, - Type: uint8(lcommon.ScriptRefTypeNativeScript), - ScriptHash: scriptHash.Bytes(), - } - if result := txn.Create(&scriptRecord); result.Error != nil { - return fmt.Errorf("create native script: %w", result.Error) - } - // Also store the script content separately to avoid duplicates - scriptContent := models.ScriptContent{ - Hash: scriptHash.Bytes(), - Type: uint8(lcommon.ScriptRefTypeNativeScript), - Content: script.Cbor(), - CreatedSlot: point.Slot, - } - if result := txn.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "hash"}}, - DoNothing: true, - }).Create(&scriptContent); result.Error != nil { - return fmt.Errorf("create native script content: %w", result.Error) + // Helper function to process scripts - deduplicates the 4 script type blocks + processScripts := func(scriptType uint8, scripts interface{}) error { + // Common logic for creating script records + createScriptRecords := func(scriptHash lcommon.ScriptHash, content []byte) error { + witnessScript := models.WitnessScripts{ + TransactionID: tmpTx.ID, + Type: scriptType, + ScriptHash: scriptHash.Bytes(), + } + if result := txn.Create(&witnessScript); result.Error != nil { + return fmt.Errorf("create witness script: %w", result.Error) + } + scriptContent := models.Script{ + Hash: scriptHash.Bytes(), + Type: scriptType, + Content: content, + CreatedSlot: point.Slot, + } + if result := txn.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "hash"}}, + DoNothing: true, + }).Create(&scriptContent); result.Error != nil { + return fmt.Errorf("create script content: %w", result.Error) + } + return nil } - } - // Add PlutusV1 Scripts - for _, script := range ws.PlutusV1Scripts() { - scriptHash := script.Hash() - scriptRecord := models.Script{ - TransactionID: tmpTx.ID, - Type: uint8(lcommon.ScriptRefTypePlutusV1), - ScriptHash: scriptHash.Bytes(), - } - if result := txn.Create(&scriptRecord); result.Error != nil { - return fmt.Errorf("create plutus v1 script: %w", result.Error) - } - // Also store the script content separately to avoid duplicates - scriptContent := models.ScriptContent{ - Hash: scriptHash.Bytes(), - Type: uint8(lcommon.ScriptRefTypePlutusV1), - Content: script.RawScriptBytes(), - CreatedSlot: point.Slot, - } - if result := txn.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "hash"}}, - DoNothing: true, - }).Create(&scriptContent); result.Error != nil { - return fmt.Errorf("create plutus v1 script content: %w", result.Error) + // Type switch to handle different script types + switch s := scripts.(type) { + case []lcommon.NativeScript: + for _, script := range s { + if err := createScriptRecords(script.Hash(), script.Cbor()); err != nil { + return err + } + } + case []lcommon.PlutusV1Script: + for _, script := range s { + if err := createScriptRecords(script.Hash(), script.RawScriptBytes()); err != nil { + return err + } + } + case []lcommon.PlutusV2Script: + for _, script := range s { + if err := createScriptRecords(script.Hash(), script.RawScriptBytes()); err != nil { + return err + } + } + case []lcommon.PlutusV3Script: + for _, script := range s { + if err := createScriptRecords(script.Hash(), script.RawScriptBytes()); err != nil { + return err + } + } } + return nil } - // Add PlutusV2 Scripts - for _, script := range ws.PlutusV2Scripts() { - scriptHash := script.Hash() - scriptRecord := models.Script{ - TransactionID: tmpTx.ID, - Type: uint8(lcommon.ScriptRefTypePlutusV2), - ScriptHash: scriptHash.Bytes(), - } - if result := txn.Create(&scriptRecord); result.Error != nil { - return fmt.Errorf("create plutus v2 script: %w", result.Error) - } - // Also store the script content separately to avoid duplicates - scriptContent := models.ScriptContent{ - Hash: scriptHash.Bytes(), - Type: uint8(lcommon.ScriptRefTypePlutusV2), - Content: script.RawScriptBytes(), - CreatedSlot: point.Slot, - } - if result := txn.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "hash"}}, - DoNothing: true, - }).Create(&scriptContent); result.Error != nil { - return fmt.Errorf("create plutus v2 script content: %w", result.Error) - } + // Process all script types + if err := processScripts(uint8(lcommon.ScriptRefTypeNativeScript), ws.NativeScripts()); err != nil { + return err } - - // Add PlutusV3 Scripts - for _, script := range ws.PlutusV3Scripts() { - scriptHash := script.Hash() - scriptRecord := models.Script{ - TransactionID: tmpTx.ID, - Type: uint8(lcommon.ScriptRefTypePlutusV3), - ScriptHash: scriptHash.Bytes(), - } - if result := txn.Create(&scriptRecord); result.Error != nil { - return fmt.Errorf("create plutus v3 script: %w", result.Error) - } - // Also store the script content separately to avoid duplicates - scriptContent := models.ScriptContent{ - Hash: scriptHash.Bytes(), - Type: uint8(lcommon.ScriptRefTypePlutusV3), - Content: script.RawScriptBytes(), - CreatedSlot: point.Slot, - } - if result := txn.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "hash"}}, - DoNothing: true, - }).Create(&scriptContent); result.Error != nil { - return fmt.Errorf("create plutus v3 script content: %w", result.Error) - } + if err := processScripts(uint8(lcommon.ScriptRefTypePlutusV1), ws.PlutusV1Scripts()); err != nil { + return err + } + if err := processScripts(uint8(lcommon.ScriptRefTypePlutusV2), ws.PlutusV2Scripts()); err != nil { + return err + } + if err := processScripts(uint8(lcommon.ScriptRefTypePlutusV3), ws.PlutusV3Scripts()); err != nil { + return err } // Add PlutusData (Datums) diff --git a/database/plugin/metadata/store.go b/database/plugin/metadata/store.go index 00738824..b5e0a4b7 100644 --- a/database/plugin/metadata/store.go +++ b/database/plugin/metadata/store.go @@ -83,14 +83,10 @@ type MetadataStore interface { []byte, // hash *gorm.DB, ) (*models.Transaction, error) - GetScriptContent( + GetScript( lcommon.ScriptHash, *gorm.DB, - ) (*models.ScriptContent, error) - GetScriptsByTransaction( - uint, // txID - *gorm.DB, - ) ([]models.Script, error) + ) (*models.Script, error) SetBlockNonce( []byte, // blockHash From ffb53dcb99667e899d54ca73112191f986d1e8f7 Mon Sep 17 00:00:00 2001 From: Jenita Date: Mon, 24 Nov 2025 18:20:06 -0600 Subject: [PATCH 08/10] ci: retrigger nilaway check Signed-off-by: Jenita From d351aa8c52acb506e40145b0068800fa73cb9052 Mon Sep 17 00:00:00 2001 From: Jenita Date: Mon, 24 Nov 2025 18:21:18 -0600 Subject: [PATCH 09/10] ci: retrigger nilaway check Signed-off-by: Jenita From d8edaf6a53c69b0479ec29c02a4ca438ed0c1715 Mon Sep 17 00:00:00 2001 From: Jenita Date: Mon, 24 Nov 2025 18:48:26 -0600 Subject: [PATCH 10/10] feat: attempt to fix nilaway issue Signed-off-by: Jenita --- .github/workflows/nilaway.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nilaway.yml b/.github/workflows/nilaway.yml index ff736223..2d858e5b 100644 --- a/.github/workflows/nilaway.yml +++ b/.github/workflows/nilaway.yml @@ -14,6 +14,7 @@ jobs: nilaway: name: nilaway runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 https://github.com/actions/checkout/releases/tag/v5.0.0 - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 https://github.com/actions/setup-go/releases/tag/v6.0.0