Skip to content

Commit 20fa381

Browse files
committed
tapdb: refactor and unit test asset creation skip
Extracts the asset creation skip logic into a shouldSkipAssetCreation function and adds unit tests for it.
1 parent 87d3f11 commit 20fa381

File tree

2 files changed

+158
-26
lines changed

2 files changed

+158
-26
lines changed

tapdb/assets_store.go

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3158,31 +3158,6 @@ func (a *AssetStore) LogAnchorTxConfirm(ctx context.Context,
31583158
return fmt.Errorf("unable to decode script "+
31593159
"key: %w", err)
31603160
}
3161-
scriptPubKey := fullScriptKey.PubKey
3162-
3163-
isNumsKey := scriptPubKey.IsEqual(asset.NUMSPubKey)
3164-
isTombstone := isNumsKey &&
3165-
out.Amount == 0 &&
3166-
out.OutputType == int16(tappsbt.TypeSplitRoot)
3167-
isBurn := !isNumsKey && len(witnessData) > 0 &&
3168-
asset.IsBurnKey(scriptPubKey, witnessData[0])
3169-
isKnown := fullScriptKey.Type != asset.ScriptKeyUnknown
3170-
isRemotePedersen := fullScriptKey.Type ==
3171-
asset.ScriptKeyUniquePedersen &&
3172-
!out.ScriptKeyLocal
3173-
skipAssetCreation := !isTombstone && !isBurn &&
3174-
!out.ScriptKeyLocal &&
3175-
(!isKnown || isRemotePedersen)
3176-
3177-
log.Tracef("Skip asset creation for "+
3178-
"output %d?: %v, position=%v, scriptKey=%x, "+
3179-
"isTombstone=%v, isBurn=%v, "+
3180-
"scriptKeyLocal=%v, scriptKeyKnown=%v, "+
3181-
"isRemotePedersen=%v", idx, skipAssetCreation,
3182-
out.Position,
3183-
scriptPubKey.SerializeCompressed(), isTombstone,
3184-
isBurn, out.ScriptKeyLocal, isKnown,
3185-
isRemotePedersen)
31863161

31873162
// If this is an outbound transfer (meaning that our
31883163
// node doesn't control the script key, and it isn't a
@@ -3191,6 +3166,9 @@ func (a *AssetStore) LogAnchorTxConfirm(ctx context.Context,
31913166
// leaving the node. The same goes for outputs that are
31923167
// only used to anchor passive assets, which are handled
31933168
// separately.
3169+
skipAssetCreation, markSpent := shouldSkipAssetCreation(
3170+
out, fullScriptKey, witnessData,
3171+
)
31943172
if skipAssetCreation {
31953173
continue
31963174
}
@@ -3236,7 +3214,7 @@ func (a *AssetStore) LogAnchorTxConfirm(ctx context.Context,
32363214
SplitCommitmentRootHash: out.SplitCommitmentRootHash,
32373215
SplitCommitmentRootValue: out.SplitCommitmentRootValue,
32383216
SpentAssetID: templateID,
3239-
Spent: isTombstone || isBurn,
3217+
Spent: markSpent,
32403218
AssetVersion: out.AssetVersion,
32413219
}
32423220
newAssetID, err := q.ApplyPendingOutput(ctx, params)
@@ -3258,6 +3236,7 @@ func (a *AssetStore) LogAnchorTxConfirm(ctx context.Context,
32583236
"witnesses: %w", err)
32593237
}
32603238

3239+
scriptPubKey := fullScriptKey.PubKey
32613240
outKey := tapfreighter.NewOutputIdentifier(
32623241
outProofAsset.ID(), inclusionProof.OutputIndex,
32633242
*scriptPubKey,
@@ -3355,6 +3334,55 @@ func (a *AssetStore) LogAnchorTxConfirm(ctx context.Context,
33553334
return nil
33563335
}
33573336

3337+
// shouldSkipAssetCreation determines whether we should skip creating an asset
3338+
// in our local database for a given transfer output. This is based on the
3339+
// script key type, the amount, and whether the output is controlled by the
3340+
// node or not. If we skip asset creation, we also return a second boolean that
3341+
// indicates whether the output should be displayed as being spent (because
3342+
// tombstone and burns cannot be used any further and should be displayed as
3343+
// such).
3344+
func shouldSkipAssetCreation(out TransferOutputRow,
3345+
scriptKey asset.ScriptKey, witnessData []asset.Witness) (bool, bool) {
3346+
3347+
scriptPubKey := scriptKey.PubKey
3348+
3349+
scriptKeyType := asset.ScriptKeyUnknown
3350+
if scriptKey.TweakedScriptKey != nil {
3351+
scriptKeyType = scriptKey.TweakedScriptKey.Type
3352+
}
3353+
3354+
isNumsKey := scriptPubKey.IsEqual(asset.NUMSPubKey)
3355+
isTombstone := isNumsKey && out.Amount == 0 &&
3356+
out.OutputType == int16(tappsbt.TypeSplitRoot)
3357+
isBurn := !isNumsKey && len(witnessData) > 0 &&
3358+
asset.IsBurnKey(scriptPubKey, witnessData[0])
3359+
isKnown := scriptKeyType != asset.ScriptKeyUnknown
3360+
isRemotePedersen := scriptKeyType == asset.ScriptKeyUniquePedersen &&
3361+
!out.ScriptKeyLocal
3362+
isKnownLocal := isKnown && !isRemotePedersen
3363+
3364+
// We _do_ create assets for the following asset types:
3365+
// 1. Tombstones (to later garbage collect the anchor).
3366+
// 2. Burns (to later garbage collect the anchor).
3367+
// 3. Outputs that are controlled by the node (local script key).
3368+
// 4. Outputs that are known to the node, and don't belong to a remote
3369+
// node.
3370+
// The following boolean expression is the inverse of the above, meaning
3371+
// that we skip asset creation for outputs that _are not_ any of the
3372+
// above.
3373+
skipAssetCreation := !isTombstone && !isBurn && !out.ScriptKeyLocal &&
3374+
!isKnownLocal
3375+
3376+
log.Tracef("Skip asset creation for output %d?: %v, scriptKey=%x, "+
3377+
"isTombstone=%v, isBurn=%v, scriptKeyLocal=%v, "+
3378+
"scriptKeyKnown=%v, isRemotePedersen=%v", out.Position,
3379+
skipAssetCreation, scriptPubKey.SerializeCompressed(),
3380+
isTombstone, isBurn, out.ScriptKeyLocal, isKnown,
3381+
isRemotePedersen)
3382+
3383+
return skipAssetCreation, isTombstone || isBurn
3384+
}
3385+
33583386
// reAnchorPassiveAssets re-anchors all passive assets that were anchored by
33593387
// the given transfer output.
33603388
func (a *AssetStore) reAnchorPassiveAssets(ctx context.Context,

tapdb/assets_store_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/lightninglabs/taproot-assets/proof"
2525
"github.com/lightninglabs/taproot-assets/tapdb/sqlc"
2626
"github.com/lightninglabs/taproot-assets/tapfreighter"
27+
"github.com/lightninglabs/taproot-assets/tappsbt"
2728
"github.com/lightninglabs/taproot-assets/tapscript"
2829
"github.com/lightningnetwork/lnd/input"
2930
"github.com/lightningnetwork/lnd/keychain"
@@ -3143,3 +3144,106 @@ func TestQueryAssetBalancesCustomChannelFunding(t *testing.T) {
31433144
}
31443145
require.Equal(t, assetDesc[0].amt, balanceByGroupSum)
31453146
}
3147+
3148+
// TestShouldSkipAssetCreation tests the behavior of the shouldSkipAssetCreation
3149+
// function for various asset transfer output scenarios.
3150+
func TestShouldSkipAssetCreation(t *testing.T) {
3151+
t.Parallel()
3152+
3153+
prevID := asset.PrevID{
3154+
OutPoint: test.RandOp(t),
3155+
ID: asset.RandID(t),
3156+
ScriptKey: asset.RandSerializedKey(t),
3157+
}
3158+
3159+
testCases := []struct {
3160+
name string
3161+
output TransferOutputRow
3162+
scriptKey asset.ScriptKey
3163+
witnessData []asset.Witness
3164+
expectedSkip bool
3165+
expectedSpent bool
3166+
}{
3167+
{
3168+
name: "tombstone asset",
3169+
output: TransferOutputRow{
3170+
Amount: 0,
3171+
OutputType: int16(tappsbt.TypeSplitRoot),
3172+
},
3173+
scriptKey: asset.NUMSScriptKey,
3174+
witnessData: nil,
3175+
expectedSkip: false,
3176+
expectedSpent: true,
3177+
},
3178+
{
3179+
name: "burn asset",
3180+
output: TransferOutputRow{
3181+
Amount: 100,
3182+
},
3183+
scriptKey: asset.ScriptKey{
3184+
PubKey: asset.DeriveBurnKey(prevID),
3185+
},
3186+
witnessData: []asset.Witness{{
3187+
PrevID: &prevID,
3188+
}},
3189+
expectedSkip: false,
3190+
expectedSpent: true,
3191+
},
3192+
{
3193+
name: "local script key asset",
3194+
output: TransferOutputRow{
3195+
Amount: 100,
3196+
ScriptKeyLocal: true,
3197+
},
3198+
scriptKey: asset.ScriptKey{
3199+
PubKey: test.RandPubKey(t),
3200+
},
3201+
witnessData: nil,
3202+
expectedSkip: false,
3203+
expectedSpent: false,
3204+
},
3205+
{
3206+
name: "remote pedersen asset",
3207+
output: TransferOutputRow{
3208+
Amount: 100,
3209+
},
3210+
scriptKey: asset.ScriptKey{
3211+
TweakedScriptKey: &asset.TweakedScriptKey{
3212+
Type: asset.ScriptKeyUniquePedersen,
3213+
},
3214+
PubKey: test.RandPubKey(t),
3215+
},
3216+
witnessData: nil,
3217+
expectedSkip: true,
3218+
expectedSpent: false,
3219+
},
3220+
{
3221+
name: "local pedersen asset",
3222+
output: TransferOutputRow{
3223+
Amount: 100,
3224+
ScriptKeyLocal: true,
3225+
},
3226+
scriptKey: asset.ScriptKey{
3227+
TweakedScriptKey: &asset.TweakedScriptKey{
3228+
Type: asset.ScriptKeyUniquePedersen,
3229+
},
3230+
PubKey: test.RandPubKey(t),
3231+
},
3232+
witnessData: nil,
3233+
expectedSkip: false,
3234+
expectedSpent: false,
3235+
},
3236+
}
3237+
3238+
// Run each test case.
3239+
for _, tc := range testCases {
3240+
t.Run(tc.name, func(t *testing.T) {
3241+
skip, spent := shouldSkipAssetCreation(
3242+
tc.output, tc.scriptKey, tc.witnessData,
3243+
)
3244+
3245+
require.Equal(t, tc.expectedSkip, skip)
3246+
require.Equal(t, tc.expectedSpent, spent)
3247+
})
3248+
}
3249+
}

0 commit comments

Comments
 (0)