@@ -17,6 +17,7 @@ package sqlite
1717import (
1818 "errors"
1919 "fmt"
20+ "strings"
2021
2122 "github.com/blinklabs-io/dingo/database/models"
2223 lcommon "github.com/blinklabs-io/gouroboros/ledger/common"
@@ -34,7 +35,9 @@ func (d *MetadataStoreSqlite) GetTransactionByHash(
3435 if txn == nil {
3536 txn = d .DB ()
3637 }
37- result := txn .First (ret , "hash = ?" , hash )
38+ result := txn .
39+ Preload (clause .Associations ).
40+ First (ret , "hash = ?" , hash )
3841 if result .Error != nil {
3942 if errors .Is (result .Error , gorm .ErrRecordNotFound ) {
4043 return nil , nil
@@ -51,40 +54,195 @@ func (d *MetadataStoreSqlite) SetTransaction(
5154 idx uint32 ,
5255 txn * gorm.DB ,
5356) error {
57+ txHash := tx .Hash ().Bytes ()
5458 if txn == nil {
5559 txn = d .DB ()
5660 }
5761 tmpTx := & models.Transaction {
58- Hash : tx . Hash (). Bytes () ,
62+ Hash : txHash ,
5963 Type : tx .Type (),
6064 BlockHash : point .Hash ,
6165 BlockIndex : idx ,
6266 }
63- if tx .IsValid () {
64- for _ , utxo := range tx .Produced () {
65- tmpTx . Outputs = append (
66- tmpTx . Outputs ,
67- models . UtxoLedgerToModel ( utxo , point . Slot ),
68- )
67+ collateralReturn := tx .CollateralReturn ()
68+ for _ , utxo := range tx .Produced () {
69+ if collateralReturn != nil && utxo . Output == collateralReturn {
70+ utxo := models . UtxoLedgerToModel ( utxo , point . Slot )
71+ tmpTx . CollateralReturn = & utxo
72+ continue
6973 }
74+ tmpTx .Outputs = append (
75+ tmpTx .Outputs ,
76+ models .UtxoLedgerToModel (utxo , point .Slot ),
77+ )
7078 }
71- result := txn .Create (& tmpTx )
79+ result := txn .Clauses (clause.OnConflict {
80+ Columns : []clause.Column {{Name : "hash" }}, // unique txn hash
81+ DoUpdates : clause .AssignmentColumns (
82+ []string {"block_hash" , "block_index" },
83+ ),
84+ }).Create (& tmpTx )
7285 if result .Error != nil {
7386 return fmt .Errorf ("create transaction: %w" , result .Error )
7487 }
75- // Explicitly create produced outputs with TransactionID set
76- if tx .IsValid () && len (tmpTx .Outputs ) > 0 {
77- for o := range tmpTx .Outputs {
78- tmpTx .Outputs [o ].TransactionID = & tmpTx .ID
88+ // Add Inputs to Transaction
89+ for _ , input := range tx .Inputs () {
90+ inTxId := input .Id ().Bytes ()
91+ inIdx := input .Index ()
92+ utxo , err := d .GetUtxo (inTxId , inIdx , txn )
93+ if err != nil {
94+ return fmt .Errorf (
95+ "failed to fetch input %x#%d: %w" ,
96+ inTxId ,
97+ inIdx ,
98+ err ,
99+ )
79100 }
80- result = txn .Clauses (clause.OnConflict {
81- UpdateAll : true ,
82- }).Create (& tmpTx .Outputs )
83- if result .Error != nil {
84- return fmt .Errorf ("create outputs: %w" , result .Error )
101+ if utxo == nil {
102+ d .logger .Warn (
103+ "Skipping missing input UTxO" ,
104+ "hash" ,
105+ input .Id ().String (),
106+ "index" ,
107+ inIdx ,
108+ )
109+ continue
110+ }
111+ tmpTx .Inputs = append (
112+ tmpTx .Inputs ,
113+ * utxo ,
114+ )
115+ }
116+ // Add Collateral to Transaction
117+ if len (tx .Collateral ()) > 0 {
118+ var caseClauses []string
119+ var whereConditions []string
120+ var caseArgs []any
121+ var whereArgs []any
122+
123+ for _ , input := range tx .Collateral () {
124+ inTxId := input .Id ().Bytes ()
125+ inIdx := input .Index ()
126+ utxo , err := d .GetUtxo (inTxId , inIdx , txn )
127+ if err != nil {
128+ return fmt .Errorf (
129+ "failed to fetch input %x#%d: %w" ,
130+ inTxId ,
131+ inIdx ,
132+ err ,
133+ )
134+ }
135+ if utxo == nil {
136+ d .logger .Warn (
137+ "Skipping missing collateral UTxO" ,
138+ "hash" ,
139+ input .Id ().String (),
140+ "index" ,
141+ inIdx ,
142+ )
143+ continue
144+ }
145+ // Found the Utxo, add it to the SQL UPDATE list
146+ // First, add it to the CASE statement so it's selected
147+ caseClauses = append (
148+ caseClauses ,
149+ "WHEN tx_id = ? AND output_idx = ? THEN ?" ,
150+ )
151+ caseArgs = append (caseArgs , inTxId , inIdx , txHash )
152+ // Also add it to the WHERE clause in the SQL UPDATE
153+ whereConditions = append (
154+ whereConditions ,
155+ "(tx_id = ? AND output_idx = ?)" ,
156+ )
157+ whereArgs = append (whereArgs , inTxId , inIdx )
158+ // Add it to the Transaction
159+ tmpTx .Collateral = append (
160+ tmpTx .Collateral ,
161+ * utxo ,
162+ )
163+ }
164+ // Update reference where this Utxo was used as collateral in a Transaction
165+ if len (caseClauses ) > 0 {
166+ args := append (caseArgs , whereArgs ... )
167+ sql := fmt .Sprintf (
168+ "UPDATE utxo SET collateral_by_tx_id = CASE %s ELSE collateral_by_tx_id END WHERE %s" ,
169+ strings .Join (caseClauses , " " ),
170+ strings .Join (whereConditions , " OR " ),
171+ )
172+ result = txn .Exec (sql , args ... )
173+ if result .Error != nil {
174+ return fmt .Errorf ("batch update collateral: %w" , result .Error )
175+ }
85176 }
86177 }
87- for i , input := range tx .Consumed () {
178+ // Add ReferenceInputs to Transaction
179+ if len (tx .ReferenceInputs ()) > 0 {
180+ var caseClauses []string
181+ var whereConditions []string
182+ var caseArgs []any
183+ var whereArgs []any
184+
185+ for _ , input := range tx .ReferenceInputs () {
186+ inTxId := input .Id ().Bytes ()
187+ inIdx := input .Index ()
188+ utxo , err := d .GetUtxo (inTxId , inIdx , txn )
189+ if err != nil {
190+ return fmt .Errorf (
191+ "failed to fetch input %x#%d: %w" ,
192+ inTxId ,
193+ inIdx ,
194+ err ,
195+ )
196+ }
197+ if utxo == nil {
198+ d .logger .Warn (
199+ "Skipping missing reference input UTxO" ,
200+ "hash" ,
201+ input .Id ().String (),
202+ "index" ,
203+ inIdx ,
204+ )
205+ continue
206+ }
207+ // Found the Utxo, add it to the SQL UPDATE list
208+ // First, add it to the CASE statement so it's selected
209+ caseClauses = append (
210+ caseClauses ,
211+ "WHEN tx_id = ? AND output_idx = ? THEN ?" ,
212+ )
213+ caseArgs = append (caseArgs , inTxId , inIdx , txHash )
214+ // Also add it to the WHERE clause in the SQL UPDATE
215+ whereConditions = append (
216+ whereConditions ,
217+ "(tx_id = ? AND output_idx = ?)" ,
218+ )
219+ whereArgs = append (whereArgs , inTxId , inIdx )
220+ // Add it to the Transaction
221+ tmpTx .ReferenceInputs = append (
222+ tmpTx .ReferenceInputs ,
223+ * utxo ,
224+ )
225+ }
226+ // Update reference where this Utxo was used as a reference input in a Transaction
227+ if len (caseClauses ) > 0 {
228+ args := append (caseArgs , whereArgs ... )
229+ sql := fmt .Sprintf (
230+ "UPDATE utxo SET referenced_by_tx_id = CASE %s ELSE referenced_by_tx_id END WHERE %s" ,
231+ strings .Join (caseClauses , " " ),
232+ strings .Join (whereConditions , " OR " ),
233+ )
234+ result = txn .Exec (sql , args ... )
235+ if result .Error != nil {
236+ return fmt .Errorf (
237+ "batch update reference inputs: %w" ,
238+ result .Error ,
239+ )
240+ }
241+ }
242+ }
243+
244+ // Consume input UTxOs
245+ for _ , input := range tx .Consumed () {
88246 inTxId := input .Id ().Bytes ()
89247 inIdx := input .Index ()
90248 utxo , err := d .GetUtxo (inTxId , inIdx , txn )
@@ -97,27 +255,26 @@ func (d *MetadataStoreSqlite) SetTransaction(
97255 )
98256 }
99257 if utxo == nil {
100- return fmt .Errorf ("input UTxO not found: %x#%d" , inTxId , inIdx )
258+ d .logger .Warn (
259+ fmt .Sprintf ("input UTxO not found: %x#%d" , inTxId , inIdx ),
260+ )
261+ continue
262+ // return fmt.Errorf("input UTxO not found: %x#%d", inTxId, inIdx)
101263 }
102264 // Update existing UTxOs
103265 result = txn .Model (& models.Utxo {}).
104266 Where ("tx_id = ? AND output_idx = ?" , inTxId , inIdx ).
267+ Where ("spent_at_tx_id IS NULL OR spent_at_tx_id = ?" , txHash ).
105268 Updates (map [string ]any {
106269 "deleted_slot" : point .Slot ,
107- "spent_at_tx_id" : tx . Hash (). Bytes () ,
270+ "spent_at_tx_id" : txHash ,
108271 })
109272 if result .Error != nil {
110273 return result .Error
111274 }
112- // Explicitly set consumed inputs with TransactionID set
113- tmpTx .Inputs = append (
114- tmpTx .Inputs ,
115- * utxo ,
116- )
117- tmpTx .Inputs [i ].TransactionID = & utxo .ID
118275 }
119276 // Avoid updating associations
120- result = txn .Omit (clause .Associations ).Save (& tmpTx . Inputs )
277+ result = txn .Omit (clause .Associations ).Save (& tmpTx )
121278 if result .Error != nil {
122279 return result .Error
123280 }
0 commit comments