@@ -27,10 +27,12 @@ const (
27
27
28
28
type IndexerService interface {
29
29
GetCommitmentTxInfo (ctx context.Context , txid string ) (* CommitmentTxResp , error )
30
+ GetCommitmentTxLeaves (ctx context.Context , txid string , page * Page ) (* CommitmentTxLeavesResp , error )
30
31
GetVtxoTree (ctx context.Context , batchOutpoint Outpoint , page * Page ) (* VtxoTreeResp , error )
32
+ GetVtxoTreeLeaves (ctx context.Context , batchOutpoint Outpoint , page * Page ) (* VtxoTreeLeavesResp , error )
31
33
GetForfeitTxs (ctx context.Context , batchOutpoint Outpoint , page * Page ) (* ForfeitTxsResp , error )
32
34
GetConnectors (ctx context.Context , batchOutpoint Outpoint , page * Page ) (* ConnectorResp , error )
33
- GetSpendableVtxos (ctx context.Context , pubkey string , page * Page ) (* SpendableVtxosResp , error )
35
+ GetVtxos (ctx context.Context , pubkey string , spendableOnly , spendOnly bool , page * Page ) (* SpendableVtxosResp , error )
34
36
GetTransactionHistory (ctx context.Context , pubkey string , start , end int64 , page * Page ) (* TxHistoryResp , error )
35
37
GetVtxoChain (ctx context.Context , vtxoKey Outpoint , page * Page ) (* VtxoChainResp , error )
36
38
GetVirtualTxs (ctx context.Context , txids []string , page * Page ) (* VirtualTxsResp , error )
@@ -64,18 +66,34 @@ func (i *indexerService) GetCommitmentTxInfo(
64
66
batches := make (map [VOut ]Batch )
65
67
// TODO: currently commitment tx has only one batch, in future multiple batches will be supported
66
68
batches [0 ] = Batch {
67
- TotalBatchAmount : roundStats .TotalBatchAmount ,
68
- TotalForfeitAmount : roundStats .TotalForfeitAmount ,
69
- TotalInputVtxos : roundStats .TotalInputVtxos ,
70
- TotalOutputVtxos : roundStats .TotalOutputVtxos ,
71
- ExpiresAt : roundStats .ExpiresAt ,
72
- Swept : roundStats .Swept ,
69
+ TotalBatchAmount : roundStats .TotalBatchAmount ,
70
+ TotalOutputVtxos : roundStats .TotalOutputVtxos ,
71
+ ExpiresAt : roundStats .ExpiresAt ,
72
+ Swept : roundStats .Swept ,
73
73
}
74
74
75
75
return & CommitmentTxResp {
76
- StartedAt : roundStats .Started ,
77
- EndAt : roundStats .Ended ,
78
- Batches : batches ,
76
+ StartedAt : roundStats .Started ,
77
+ EndAt : roundStats .Ended ,
78
+ Batches : batches ,
79
+ TotalInputAmount : roundStats .TotalForfeitAmount ,
80
+ TotalInputtVtxos : roundStats .TotalInputVtxos ,
81
+ TotalOutputVtxos : roundStats .TotalOutputVtxos ,
82
+ TotalOutputAmount : roundStats .TotalBatchAmount ,
83
+ }, nil
84
+ }
85
+
86
+ func (i * indexerService ) GetCommitmentTxLeaves (ctx context.Context , txid string , page * Page ) (* CommitmentTxLeavesResp , error ) {
87
+ leaves , err := i .repoManager .Vtxos ().GetLeafVtxosForRound (ctx , txid )
88
+ if err != nil {
89
+ return nil , err
90
+ }
91
+
92
+ paginatedLeaves , pageResp := paginate (leaves , page , maxPageSizeVtxoTree )
93
+
94
+ return & CommitmentTxLeavesResp {
95
+ Leaves : paginatedLeaves ,
96
+ Page : pageResp ,
79
97
}, nil
80
98
}
81
99
@@ -93,6 +111,20 @@ func (i *indexerService) GetVtxoTree(ctx context.Context, batchOutpoint Outpoint
93
111
}, nil
94
112
}
95
113
114
+ func (i * indexerService ) GetVtxoTreeLeaves (ctx context.Context , outpoint Outpoint , page * Page ) (* VtxoTreeLeavesResp , error ) {
115
+ leaves , err := i .repoManager .Vtxos ().GetLeafVtxosForRound (ctx , outpoint .Txid )
116
+ if err != nil {
117
+ return nil , err
118
+ }
119
+
120
+ paginatedLeaves , pageResp := paginate (leaves , page , maxPageSizeVtxoTree )
121
+
122
+ return & VtxoTreeLeavesResp {
123
+ Leaves : paginatedLeaves ,
124
+ Page : pageResp ,
125
+ }, nil
126
+ }
127
+
96
128
func (i * indexerService ) GetForfeitTxs (ctx context.Context , batchOutpoint Outpoint , page * Page ) (* ForfeitTxsResp , error ) {
97
129
forfeitTxs , err := i .repoManager .Rounds ().GetRoundForfeitTxs (ctx , batchOutpoint .Txid ) //TODO batch thing
98
130
if err != nil {
@@ -127,12 +159,25 @@ func (i *indexerService) GetConnectors(ctx context.Context, batchOutpoint Outpoi
127
159
}, nil
128
160
}
129
161
130
- func (i * indexerService ) GetSpendableVtxos (ctx context.Context , pubkey string , page * Page ) (* SpendableVtxosResp , error ) {
131
- vtxos , err := i .repoManager .Vtxos ().GetSpendableVtxosWithPubKey (ctx , pubkey )
162
+ func (i * indexerService ) GetVtxos (
163
+ ctx context.Context , pubkey string , spendableOnly , spentOnly bool , page * Page ,
164
+ ) (* SpendableVtxosResp , error ) {
165
+ if spendableOnly && spentOnly {
166
+ return nil , fmt .Errorf ("spendable and spent only can't be true at the same time" )
167
+ }
168
+ spendable , spent , err := i .repoManager .Vtxos ().GetAllVtxosWithPubKey (ctx , pubkey )
132
169
if err != nil {
133
170
return nil , err
134
171
}
135
172
173
+ vtxos := append (spendable , spent ... )
174
+ if spendableOnly {
175
+ vtxos = spendable
176
+ }
177
+ if spentOnly {
178
+ vtxos = spent
179
+ }
180
+
136
181
spendableVtxosPaged , pageResp := paginate (vtxos , page , maxPageSizeSpendableVtxos )
137
182
138
183
return & SpendableVtxosResp {
@@ -149,7 +194,19 @@ func (i *indexerService) GetTransactionHistory(
149
194
return nil , err
150
195
}
151
196
152
- txs , err := vtxosToTxs (spendable , spent )
197
+ var roundTxids map [string ]any
198
+ if len (spent ) > 0 {
199
+ txids := make ([]string , 0 , len (spent ))
200
+ for _ , vtxo := range spent {
201
+ txids = append (txids , vtxo .SpentBy )
202
+ }
203
+ roundTxids , err = i .repoManager .Rounds ().GetExistingRounds (ctx , txids )
204
+ if err != nil {
205
+ return nil , err
206
+ }
207
+ }
208
+
209
+ txs , err := vtxosToTxs (spendable , spent , roundTxids )
153
210
if err != nil {
154
211
return nil , err
155
212
}
@@ -158,8 +215,8 @@ func (i *indexerService) GetTransactionHistory(
158
215
txsPaged , pageResp := paginate (txs , page , maxPageSizeTxHistory )
159
216
160
217
return & TxHistoryResp {
161
- Records : txsPaged ,
162
- Pagination : pageResp ,
218
+ Records : txsPaged ,
219
+ Page : pageResp ,
163
220
}, nil
164
221
}
165
222
@@ -188,20 +245,24 @@ func filterByDate(txs []TxHistoryRecord, start, end int64) []TxHistoryRecord {
188
245
return filteredTxs
189
246
}
190
247
191
- type vtxoKeyWithCreatedAt struct {
192
- domain.VtxoKey
193
- CreatedAt int64
194
- }
195
-
196
248
func (i * indexerService ) GetVtxoChain (ctx context.Context , vtxoKey Outpoint , page * Page ) (* VtxoChainResp , error ) {
197
249
chainMap := make (map [vtxoKeyWithCreatedAt ]ChainWithExpiry )
198
250
199
251
outpoint := domain.VtxoKey {
200
252
Txid : vtxoKey .Txid ,
201
253
VOut : vtxoKey .Vout ,
202
254
}
255
+ vtxos , err := i .repoManager .Vtxos ().GetVtxos (ctx , []domain.VtxoKey {outpoint })
256
+ if err != nil {
257
+ return nil , err
258
+ }
203
259
204
- if err := i .buildChain (ctx , outpoint , chainMap , true ); err != nil {
260
+ if len (vtxos ) == 0 {
261
+ return nil , fmt .Errorf ("vtxo not found for outpoint: %v" , outpoint )
262
+ }
263
+ vtxo := vtxos [0 ]
264
+
265
+ if err := i .buildChain (ctx , vtxo , chainMap ); err != nil {
205
266
return nil , err
206
267
}
207
268
@@ -211,44 +272,36 @@ func (i *indexerService) GetVtxoChain(ctx context.Context, vtxoKey Outpoint, pag
211
272
}
212
273
213
274
sort .Slice (chainSlice , func (i , j int ) bool {
214
- return chainSlice [i ].CreatedAt < chainSlice [j ].CreatedAt
275
+ return chainSlice [i ].CreatedAt > chainSlice [j ].CreatedAt
215
276
})
216
277
217
278
pagedChainSlice , pageResp := paginate (chainSlice , page , maxPageSizeVtxoChain )
218
279
219
- txMap := make (map [ string ]ChainWithExpiry )
280
+ chain := make ([ ]ChainWithExpiry , 0 , len ( pagedChainSlice ) )
220
281
for _ , vtxo := range pagedChainSlice {
221
- txMap [ vtxo . Txid ] = chainMap [vtxo ]
282
+ chain = append ( chain , chainMap [vtxo ])
222
283
}
223
284
224
285
return & VtxoChainResp {
225
- Transactions : txMap ,
226
- Page : pageResp ,
286
+ Chain : chain ,
287
+ Page : pageResp ,
288
+ RootCommitmentTxid : vtxo .RoundTxid ,
289
+ Depth : getMaxDepth (chainMap ),
227
290
}, nil
228
291
}
229
292
230
293
func (i * indexerService ) buildChain (
231
294
ctx context.Context ,
232
- outpoint domain.VtxoKey ,
295
+ vtxo domain.Vtxo ,
233
296
chain map [vtxoKeyWithCreatedAt ]ChainWithExpiry ,
234
- isFirst bool ,
235
297
) error {
236
- vtxos , err := i .repoManager .Vtxos ().GetVtxos (ctx , []domain.VtxoKey {outpoint })
237
- if err != nil {
238
- return err
239
- }
240
-
241
- if isFirst && len (vtxos ) == 0 {
242
- return fmt .Errorf ("vtxo not found for outpoint: %v" , outpoint )
243
- }
244
-
245
- vtxo := vtxos [0 ]
246
298
key := vtxoKeyWithCreatedAt {
247
- VtxoKey : outpoint ,
299
+ VtxoKey : vtxo . VtxoKey ,
248
300
CreatedAt : vtxo .CreatedAt ,
249
301
}
250
302
if _ , ok := chain [key ]; ! ok {
251
303
chain [key ] = ChainWithExpiry {
304
+ Txid : vtxo .VtxoKey .Txid ,
252
305
Txs : make ([]ChainTx , 0 ),
253
306
ExpiresAt : vtxo .ExpireAt ,
254
307
}
@@ -264,6 +317,7 @@ func (i *indexerService) buildChain(
264
317
Type : "commitment" ,
265
318
})
266
319
chain [key ] = ChainWithExpiry {
320
+ Txid : vtxo .VtxoKey .Txid ,
267
321
Txs : txs ,
268
322
ExpiresAt : chain [key ].ExpiresAt ,
269
323
}
@@ -282,15 +336,24 @@ func (i *indexerService) buildChain(
282
336
Type : "virtual" ,
283
337
})
284
338
chain [key ] = ChainWithExpiry {
339
+ Txid : chain [key ].Txid ,
285
340
Txs : txs ,
286
341
ExpiresAt : chain [key ].ExpiresAt ,
287
342
}
288
343
parentOutpoint := domain.VtxoKey {
289
344
Txid : in .PreviousOutPoint .Hash .String (),
290
345
VOut : in .PreviousOutPoint .Index ,
291
346
}
347
+ vtxos , err := i .repoManager .Vtxos ().GetVtxos (ctx , []domain.VtxoKey {parentOutpoint })
348
+ if err != nil {
349
+ return err
350
+ }
292
351
293
- if err := i .buildChain (ctx , parentOutpoint , chain , false ); err != nil {
352
+ if len (vtxos ) == 0 {
353
+ return fmt .Errorf ("vtxo not found for outpoint: %v" , parentOutpoint )
354
+ }
355
+
356
+ if err := i .buildChain (ctx , vtxos [0 ], chain ); err != nil {
294
357
return err
295
358
}
296
359
}
@@ -319,7 +382,7 @@ func (i *indexerService) GetSweptCommitmentTx(ctx context.Context, txid string)
319
382
return & SweptCommitmentTxResp {}, nil
320
383
}
321
384
322
- func paginate [T any ](items []T , params * Page , maxSize int ) ([]T , PageResp ) {
385
+ func paginate [T any ](items []T , params * Page , maxSize int32 ) ([]T , PageResp ) {
323
386
if params == nil {
324
387
return items , PageResp {}
325
388
}
@@ -330,8 +393,8 @@ func paginate[T any](items []T, params *Page, maxSize int) ([]T, PageResp) {
330
393
params .PageNum = 1
331
394
}
332
395
333
- totalCount := len (items )
334
- totalPages := int (math .Ceil (float64 (totalCount ) / float64 (params .PageSize )))
396
+ totalCount := int32 ( len (items ) )
397
+ totalPages := int32 (math .Ceil (float64 (totalCount ) / float64 (params .PageSize )))
335
398
next := min (params .PageNum + 1 , totalPages )
336
399
337
400
resp := PageResp {
@@ -374,7 +437,7 @@ func flattenNodes(t [][]tree.Node) []Node {
374
437
return result
375
438
}
376
439
377
- func vtxosToTxs (spendable , spent []domain.Vtxo ) ([]TxHistoryRecord , error ) {
440
+ func vtxosToTxs (spendable , spent []domain.Vtxo , roundTxids map [ string ] any ) ([]TxHistoryRecord , error ) {
378
441
txs := make ([]TxHistoryRecord , 0 )
379
442
380
443
// Receivals
@@ -399,10 +462,14 @@ func vtxosToTxs(spendable, spent []domain.Vtxo) ([]TxHistoryRecord, error) {
399
462
commitmentTxid := vtxo .RoundTxid
400
463
virtualTxid := ""
401
464
settled := ! vtxo .IsPending ()
465
+ settledBy := ""
402
466
if vtxo .IsPending () {
403
467
virtualTxid = vtxo .Txid
404
468
commitmentTxid = ""
405
469
settled = vtxo .SpentBy != ""
470
+ if _ , ok := roundTxids [vtxo .SpentBy ]; settled && ok {
471
+ settledBy = vtxo .SpentBy
472
+ }
406
473
}
407
474
408
475
txs = append (txs , TxHistoryRecord {
@@ -412,6 +479,7 @@ func vtxosToTxs(spendable, spent []domain.Vtxo) ([]TxHistoryRecord, error) {
412
479
Type : TxReceived ,
413
480
CreatedAt : time .Unix (vtxo .CreatedAt , 0 ),
414
481
Settled : settled ,
482
+ SettledBy : settledBy ,
415
483
})
416
484
}
417
485
@@ -524,3 +592,49 @@ func getVtxo(usedVtxos []domain.Vtxo, spentByVtxos []domain.Vtxo) domain.Vtxo {
524
592
}
525
593
return domain.Vtxo {}
526
594
}
595
+
596
+ type vtxoKeyWithCreatedAt struct {
597
+ domain.VtxoKey
598
+ CreatedAt int64
599
+ }
600
+
601
+ func getMaxDepth (chainMap map [vtxoKeyWithCreatedAt ]ChainWithExpiry ) int32 {
602
+ memo := make (map [string ]int32 )
603
+
604
+ // Create a lookup from txid to ChainWithExpiry
605
+ txidToChain := make (map [string ]ChainWithExpiry )
606
+ for _ , chain := range chainMap {
607
+ txidToChain [chain .Txid ] = chain
608
+ }
609
+
610
+ // DFS function to get depth from a given txid
611
+ var dfs func (string ) int32
612
+ dfs = func (txid string ) int32 {
613
+ if val , ok := memo [txid ]; ok {
614
+ return val
615
+ }
616
+ chain := txidToChain [txid ]
617
+ if len (chain .Txs ) == 1 && chain .Txs [0 ].Type == "commitment" {
618
+ memo [txid ] = 1
619
+ return 1
620
+ }
621
+ maxDepth := int32 (0 )
622
+ for _ , child := range chain .Txs {
623
+ depth := dfs (child .Txid )
624
+ maxDepth = max (depth , maxDepth )
625
+ }
626
+ memo [txid ] = maxDepth + 1
627
+ return memo [txid ]
628
+ }
629
+
630
+ // Compute max depth starting from all root txids in the map
631
+ max := int32 (0 )
632
+ for _ , chain := range chainMap {
633
+ depth := dfs (chain .Txid )
634
+ if depth > max {
635
+ max = depth
636
+ }
637
+ }
638
+
639
+ return max
640
+ }
0 commit comments