Skip to content

Commit 66f8106

Browse files
committed
tapd: add post migration step for adding burns to table
We only added the burn table in the most recent release. But users might have burned assets before, which wouldn't be shown when listing burns (only with is_burn=true when listing assets). This migration retroactively inserts burned assets into the burn table that aren't there yet.
1 parent 663749b commit 66f8106

File tree

3 files changed

+281
-1
lines changed

3 files changed

+281
-1
lines changed

tapdb/migrations_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,3 +670,27 @@ func TestMigration33(t *testing.T) {
670670
),
671671
)
672672
}
673+
674+
// TestMigration37 tests that the Golang based post-migration check for the
675+
// asset burn insertion works as expected.
676+
func TestMigration37(t *testing.T) {
677+
ctx := context.Background()
678+
679+
db := NewTestDBWithVersion(t, 36)
680+
681+
// We need to insert some test data that will be affected by the
682+
// migration number 37.
683+
InsertTestdata(t, db.BaseDB, "migrations_test_00037_dummy_data.sql")
684+
685+
// And now that we have test data inserted, we can migrate to the latest
686+
// version.
687+
err := db.ExecuteMigrations(TargetLatest, WithPostStepCallbacks(
688+
makePostStepCallbacks(db, postMigrationChecks),
689+
))
690+
require.NoError(t, err)
691+
692+
burns, err := db.QueryBurns(ctx, QueryBurnsFilters{})
693+
require.NoError(t, err)
694+
695+
require.Len(t, burns, 5)
696+
}

tapdb/post_migration_checks.go

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/btcsuite/btcd/btcec/v2/schnorr"
10+
"github.com/btcsuite/btcd/chaincfg/chainhash"
1011
"github.com/golang-migrate/migrate/v4"
1112
"github.com/golang-migrate/migrate/v4/database"
1213
"github.com/lightninglabs/taproot-assets/asset"
@@ -20,6 +21,11 @@ const (
2021
// Migration33ScriptKeyType is the version of the migration that
2122
// introduces the script key type.
2223
Migration33ScriptKeyType = 33
24+
25+
// Migration37InsertAssetBurns is the version of the migration that
26+
// inserts the asset burns into the specific asset burns table by
27+
// querying all assets and detecting burns from their witnesses.
28+
Migration37InsertAssetBurns = 37
2329
)
2430

2531
// postMigrationCheck is a function type for a function that performs a
@@ -32,7 +38,8 @@ var (
3238
// applied. These functions are used to perform additional checks on the
3339
// database state that are not fully expressible in SQL.
3440
postMigrationChecks = map[uint]postMigrationCheck{
35-
Migration33ScriptKeyType: determineAndAssignScriptKeyType,
41+
Migration33ScriptKeyType: determineAndAssignScriptKeyType,
42+
Migration37InsertAssetBurns: insertAssetBurns,
3643
}
3744
)
3845

@@ -216,3 +223,97 @@ func determineAndAssignScriptKeyType(ctx context.Context,
216223

217224
return nil
218225
}
226+
227+
// insertAssetBurns queries all assets and detects burns from their witnesses,
228+
// then inserts the asset burns into the specific asset burns table.
229+
func insertAssetBurns(ctx context.Context, q sqlc.Querier) error {
230+
defaultClock := clock.NewDefaultClock()
231+
232+
log.Debugf("Detecting script key types")
233+
234+
// We start by fetching all assets, even the spent ones. We then collect
235+
// a list of the burn keys from the assets (because burn keys can only
236+
// be calculated from the asset's witness).
237+
assetFilter := QueryAssetFilters{
238+
Now: sql.NullTime{
239+
Time: defaultClock.Now().UTC(),
240+
Valid: true,
241+
},
242+
}
243+
dbAssets, assetWitnesses, err := fetchAssetsWithWitness(
244+
ctx, q, assetFilter,
245+
)
246+
if err != nil {
247+
return fmt.Errorf("error fetching assets: %w", err)
248+
}
249+
250+
chainAssets, err := dbAssetsToChainAssets(
251+
dbAssets, assetWitnesses, defaultClock,
252+
)
253+
if err != nil {
254+
return fmt.Errorf("error converting assets: %w", err)
255+
}
256+
257+
burnAssets := fn.Filter(chainAssets, func(a *asset.ChainAsset) bool {
258+
return a.IsBurn()
259+
})
260+
261+
burnsInTable, err := q.QueryBurns(ctx, sqlc.QueryBurnsParams{})
262+
if err != nil {
263+
return err
264+
}
265+
266+
burnsMatch := func(b sqlc.QueryBurnsRow, a *asset.ChainAsset) bool {
267+
txMatch := (chainhash.Hash(b.AnchorTxid)) == a.AnchorTx.TxHash()
268+
assetIDMatch := (asset.ID(b.AssetID)) == a.ID()
269+
amountMatch := uint64(b.Amount) == a.Amount
270+
return txMatch && assetIDMatch && amountMatch
271+
}
272+
burnAssetsNotInTable := fn.Filter(
273+
burnAssets, func(a *asset.ChainAsset) bool {
274+
return fn.NotAny(
275+
burnsInTable, func(b sqlc.QueryBurnsRow) bool {
276+
return burnsMatch(b, a)
277+
},
278+
)
279+
},
280+
)
281+
282+
log.Debugf("Found %d asset burns not in burn table, adding them now",
283+
len(burnAssetsNotInTable))
284+
for _, burnAsset := range burnAssetsNotInTable {
285+
assetTransfers, err := q.QueryAssetTransfers(ctx, TransferQuery{
286+
AnchorTxHash: fn.ByteSlice(burnAsset.AnchorTx.TxHash()),
287+
})
288+
if err != nil {
289+
return fmt.Errorf("unable to query asset transfers: %w",
290+
err)
291+
}
292+
if len(assetTransfers) != 1 {
293+
log.Warnf("Found %d asset transfers for burn asset "+
294+
"%s, expected 1: %v", len(assetTransfers),
295+
burnAsset.ID(), assetTransfers)
296+
297+
continue
298+
}
299+
assetTransfer := assetTransfers[0]
300+
301+
var groupKeyBytes []byte
302+
if burnAsset.GroupKey != nil {
303+
gk := burnAsset.GroupKey.GroupPubKey
304+
groupKeyBytes = gk.SerializeCompressed()
305+
}
306+
307+
_, err = q.InsertBurn(ctx, sqlc.InsertBurnParams{
308+
TransferID: assetTransfer.ID,
309+
AssetID: fn.ByteSlice(burnAsset.ID()),
310+
GroupKey: groupKeyBytes,
311+
Amount: int64(burnAsset.Amount),
312+
})
313+
if err != nil {
314+
return fmt.Errorf("error inserting burn: %w", err)
315+
}
316+
}
317+
318+
return nil
319+
}

0 commit comments

Comments
 (0)