Skip to content

Commit 2e91cb5

Browse files
authored
Merge pull request #1612 from lightninglabs/insert-burns
tapdb: retroactively insert old asset burns into burn table
2 parents 49dd668 + 66f8106 commit 2e91cb5

11 files changed

+358
-18
lines changed

tapdb/assets_store.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3312,14 +3312,11 @@ func (a *AssetStore) LogAnchorTxConfirm(ctx context.Context,
33123312
// in the transfer.
33133313
for _, b := range burns {
33143314
_, err = q.InsertBurn(ctx, sqlc.InsertBurnParams{
3315-
TransferID: int32(assetTransfer.ID),
3316-
Note: sql.NullString{
3317-
String: b.Note,
3318-
Valid: b.Note != "",
3319-
},
3320-
AssetID: b.AssetID,
3321-
GroupKey: b.GroupKey,
3322-
Amount: int64(b.Amount),
3315+
TransferID: assetTransfer.ID,
3316+
Note: sqlStr(b.Note),
3317+
AssetID: b.AssetID,
3318+
GroupKey: b.GroupKey,
3319+
Amount: int64(b.Amount),
33233320
})
33243321
if err != nil {
33253322
return fmt.Errorf("failed to insert burn in "+

tapdb/assets_store_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2865,7 +2865,7 @@ func TestQueryAssetBurns(t *testing.T) {
28652865
assetID := inputAsset.ID()
28662866

28672867
_, err = assetsStore.db.InsertBurn(ctx, sqlc.InsertBurnParams{
2868-
TransferID: int32(assetTransfers[0].ID),
2868+
TransferID: assetTransfers[0].ID,
28692869
Note: sql.NullString{
28702870
String: "burn",
28712871
Valid: true,
@@ -2883,7 +2883,7 @@ func TestQueryAssetBurns(t *testing.T) {
28832883
require.Len(t, burns, 1)
28842884

28852885
_, err = assetsStore.db.InsertBurn(ctx, sqlc.InsertBurnParams{
2886-
TransferID: int32(assetTransfers[0].ID),
2886+
TransferID: assetTransfers[0].ID,
28872887
Note: sql.NullString{
28882888
String: "burn",
28892889
Valid: true,

tapdb/migrations.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const (
2424
// daemon.
2525
//
2626
// NOTE: This MUST be updated when a new migration is added.
27-
LatestMigrationVersion = 36
27+
LatestMigrationVersion = 37
2828
)
2929

3030
// DatabaseBackend is an interface that contains all methods our different

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+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- We roll back the transfer ID type change from BIGINT to INTEGER.
2+
3+
CREATE TABLE IF NOT EXISTS asset_burn_transfers_corrected (
4+
-- The auto-incrementing integer that identifies this burn transfer.
5+
burn_id INTEGER PRIMARY KEY,
6+
7+
-- A reference to the primary key of the transfer that includes this burn.
8+
transfer_id INTEGER NOT NULL REFERENCES asset_transfers(id),
9+
10+
-- A note that may contain user defined metadata.
11+
note TEXT,
12+
13+
-- The asset id of the burnt asset.
14+
asset_id BLOB NOT NULL REFERENCES genesis_assets(asset_id),
15+
16+
-- The group key of the group the burnt asset belonged to.
17+
group_key BLOB REFERENCES asset_groups(tweaked_group_key),
18+
19+
-- The amount of the asset that was burned.
20+
amount BIGINT NOT NULL
21+
);
22+
23+
INSERT INTO asset_burn_transfers_corrected (
24+
burn_id, transfer_id, note, asset_id, group_key, amount
25+
)
26+
SELECT burn_id, transfer_id, note, asset_id, group_key, amount
27+
FROM asset_burn_transfers;
28+
29+
DROP TABLE asset_burn_transfers;
30+
31+
ALTER TABLE asset_burn_transfers_corrected RENAME TO asset_burn_transfers;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-- We need to modify the transfer ID to be a BIGINT instead of INTEGER,
2+
-- otherwise at some point things will break unexpectedly.
3+
4+
CREATE TABLE IF NOT EXISTS asset_burn_transfers_corrected (
5+
-- The auto-incrementing integer that identifies this burn transfer.
6+
burn_id INTEGER PRIMARY KEY,
7+
8+
-- A reference to the primary key of the transfer that includes this burn.
9+
transfer_id BIGINT NOT NULL REFERENCES asset_transfers(id),
10+
11+
-- A note that may contain user defined metadata.
12+
note TEXT,
13+
14+
-- The asset id of the burnt asset.
15+
asset_id BLOB NOT NULL REFERENCES genesis_assets(asset_id),
16+
17+
-- The group key of the group the burnt asset belonged to.
18+
group_key BLOB REFERENCES asset_groups(tweaked_group_key),
19+
20+
-- The amount of the asset that was burned.
21+
amount BIGINT NOT NULL
22+
);
23+
24+
INSERT INTO asset_burn_transfers_corrected (
25+
burn_id, transfer_id, note, asset_id, group_key, amount
26+
)
27+
SELECT burn_id, transfer_id, note, asset_id, group_key, amount
28+
FROM asset_burn_transfers;
29+
30+
DROP TABLE asset_burn_transfers;
31+
32+
ALTER TABLE asset_burn_transfers_corrected RENAME TO asset_burn_transfers;

tapdb/sqlc/models.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tapdb/sqlc/schemas/generated_schema.sql

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ CREATE TABLE addrs (
9696
proof_courier_addr BLOB NOT NULL
9797
);
9898

99-
CREATE TABLE asset_burn_transfers (
99+
CREATE TABLE "asset_burn_transfers" (
100100
-- The auto-incrementing integer that identifies this burn transfer.
101-
burn_id INTEGER PRIMARY KEY,
101+
burn_id INTEGER PRIMARY KEY,
102102

103103
-- A reference to the primary key of the transfer that includes this burn.
104-
transfer_id INTEGER NOT NULL REFERENCES asset_transfers(id),
105-
104+
transfer_id BIGINT NOT NULL REFERENCES asset_transfers(id),
105+
106106
-- A note that may contain user defined metadata.
107107
note TEXT,
108108

tapdb/sqlc/transfers.sql.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)