Skip to content

Commit bd5d0b4

Browse files
committed
tapdb: add FetchSupplyLeavesByHeight to SupplyTreeStore
In this commit, we add a new method to the SupplyTreeStore that is able to read out the leaves of a supply tree based on a start and end height. This will be useful for writing the new syncing state machine and the sub-system that serves the supply tree syncer.
1 parent 375fc5b commit bd5d0b4

File tree

2 files changed

+316
-1
lines changed

2 files changed

+316
-1
lines changed

tapdb/supply_tree.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,3 +596,111 @@ func (s *SupplyTreeStore) ApplySupplyUpdates(ctx context.Context,
596596
return finalRoot, nil
597597
}
598598

599+
// SupplyUpdate is a struct that holds a supply update event and its block
600+
// height.
601+
type SupplyUpdate struct {
602+
supplycommit.SupplyUpdateEvent
603+
BlockHeight uint32
604+
}
605+
606+
// FetchSupplyLeavesByHeight fetches all supply leaves for a given asset
607+
// specifier within a given block height range.
608+
func (s *SupplyTreeStore) FetchSupplyLeavesByHeight(ctx context.Context,
609+
spec asset.Specifier, startHeight, endHeight uint32) ([]SupplyUpdate, error) {
610+
611+
groupKey, err := spec.UnwrapGroupKeyOrErr()
612+
if err != nil {
613+
return nil, fmt.Errorf("group key must be "+
614+
"specified for supply tree: %w", err)
615+
}
616+
617+
var updates []SupplyUpdate
618+
619+
readTx := NewBaseUniverseReadTx()
620+
dbErr := s.db.ExecTx(ctx, &readTx, func(db BaseUniverseStore) error {
621+
for _, treeType := range []supplycommit.SupplySubTree{
622+
supplycommit.MintTreeType, supplycommit.BurnTreeType,
623+
supplycommit.IgnoreTreeType,
624+
} {
625+
namespace := subTreeNamespace(groupKey, treeType)
626+
627+
leaves, err := db.QuerySupplyLeavesByHeight(
628+
ctx, sqlc.QuerySupplyLeavesByHeightParams{
629+
Namespace: namespace,
630+
StartHeight: sqlInt32(startHeight),
631+
EndHeight: sqlInt32(endHeight),
632+
},
633+
)
634+
if err != nil {
635+
if errors.Is(err, sql.ErrNoRows) {
636+
continue
637+
}
638+
639+
return fmt.Errorf("failed to query "+
640+
"supply leaves: %w", err)
641+
}
642+
643+
for _, leaf := range leaves {
644+
var event supplycommit.SupplyUpdateEvent
645+
switch treeType {
646+
case supplycommit.MintTreeType:
647+
var mintEvent supplycommit.NewMintEvent
648+
err = mintEvent.Decode(
649+
bytes.NewReader(
650+
leaf.SupplyLeafBytes,
651+
),
652+
)
653+
if err != nil {
654+
return fmt.Errorf("failed "+
655+
"to decode mint "+
656+
"event: %w", err)
657+
}
658+
659+
event = &mintEvent
660+
661+
case supplycommit.BurnTreeType:
662+
var burnEvent supplycommit.NewBurnEvent
663+
err = burnEvent.Decode(
664+
bytes.NewReader(
665+
leaf.SupplyLeafBytes,
666+
),
667+
)
668+
if err != nil {
669+
return fmt.Errorf("failed "+
670+
"to decode burn "+
671+
"event: %w", err)
672+
}
673+
674+
event = &burnEvent
675+
676+
case supplycommit.IgnoreTreeType:
677+
var ignoreEvent supplycommit.NewIgnoreEvent
678+
err = ignoreEvent.Decode(
679+
bytes.NewReader(
680+
leaf.SupplyLeafBytes,
681+
),
682+
)
683+
if err != nil {
684+
return fmt.Errorf("failed "+
685+
"to decode ignore "+
686+
"event: %w", err)
687+
}
688+
event = &ignoreEvent
689+
}
690+
691+
updates = append(updates, SupplyUpdate{
692+
SupplyUpdateEvent: event,
693+
BlockHeight: extractSqlInt32[uint32](
694+
leaf.BlockHeight,
695+
),
696+
})
697+
}
698+
}
699+
return nil
700+
})
701+
if dbErr != nil {
702+
return nil, dbErr
703+
}
704+
705+
return updates, nil
706+
}

tapdb/supply_tree_test.go

Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ func randIgnoreTupleGen(t *rapid.T,
248248
ScriptKey: asset.ToSerialized(scriptKey.PubKey),
249249
OutPoint: op,
250250
},
251-
Amount: 100,
251+
Amount: 100,
252+
BlockHeight: rapid.Uint32Range(1, 1000).Draw(t, "block_height"),
252253
}
253254

254255
// Create a signature for the ignore tuple.
@@ -367,6 +368,101 @@ func setupSupplyTreeTestForProps(t *testing.T) (*SupplyTreeStore,
367368
return supplyStore, spec, eventGen
368369
}
369370

371+
// createMintEventWithHeight creates a mint event with a specific block height.
372+
func createMintEventWithHeight(t *testing.T, groupKey *btcec.PublicKey,
373+
height uint32) *supplycommit.NewMintEvent {
374+
375+
mintAsset := asset.RandAsset(t, asset.Normal)
376+
mintAsset.GroupKey = &asset.GroupKey{GroupPubKey: *groupKey}
377+
mintAsset.GroupKey.Witness = mintAsset.PrevWitnesses[0].TxWitness
378+
379+
mintProof := randProof(t, mintAsset)
380+
mintProof.BlockHeight = height
381+
mintProof.GroupKeyReveal = asset.NewGroupKeyRevealV0(
382+
asset.ToSerialized(groupKey), nil,
383+
)
384+
385+
var proofBuf bytes.Buffer
386+
require.NoError(t, mintProof.Encode(&proofBuf))
387+
388+
mintLeaf := universe.Leaf{
389+
GenesisWithGroup: universe.GenesisWithGroup{
390+
Genesis: mintAsset.Genesis,
391+
GroupKey: mintAsset.GroupKey,
392+
},
393+
Asset: &mintProof.Asset,
394+
Amt: mintProof.Asset.Amount,
395+
RawProof: proofBuf.Bytes(),
396+
}
397+
398+
mintKey := universe.AssetLeafKey{
399+
BaseLeafKey: universe.BaseLeafKey{
400+
OutPoint: mintProof.OutPoint(),
401+
ScriptKey: &mintProof.Asset.ScriptKey,
402+
},
403+
AssetID: mintProof.Asset.ID(),
404+
}
405+
406+
return &supplycommit.NewMintEvent{
407+
LeafKey: mintKey,
408+
IssuanceProof: mintLeaf,
409+
}
410+
}
411+
412+
// createBurnEventWithHeight creates a burn event with a specific block height.
413+
func createBurnEventWithHeight(t *testing.T, baseGenesis asset.Genesis,
414+
groupKey *asset.GroupKey, db BatchedUniverseTree,
415+
height uint32) *supplycommit.NewBurnEvent {
416+
417+
burnAsset := createBurnAsset(t)
418+
burnAsset.Genesis = baseGenesis
419+
burnAsset.GroupKey = groupKey
420+
421+
burnProof := randProof(t, burnAsset)
422+
burnProof.BlockHeight = height
423+
burnProof.GenesisReveal = &baseGenesis
424+
425+
// Ensure genesis exists for this burn leaf in the DB.
426+
ctx := context.Background()
427+
genesisPointID, err := upsertGenesisPoint(
428+
ctx, db, burnAsset.Genesis.FirstPrevOut,
429+
)
430+
require.NoError(t, err)
431+
_, err = upsertGenesis(
432+
ctx, db, genesisPointID, burnAsset.Genesis,
433+
)
434+
require.NoError(t, err)
435+
436+
burnLeaf := &universe.BurnLeaf{
437+
UniverseKey: universe.AssetLeafKey{
438+
BaseLeafKey: universe.BaseLeafKey{
439+
OutPoint: burnProof.OutPoint(),
440+
ScriptKey: &burnProof.Asset.ScriptKey,
441+
},
442+
AssetID: burnProof.Asset.ID(),
443+
},
444+
BurnProof: burnProof,
445+
}
446+
447+
return &supplycommit.NewBurnEvent{
448+
BurnLeaf: *burnLeaf,
449+
}
450+
}
451+
452+
// createIgnoreEventWithHeight creates an ignore event with a specific block
453+
// height.
454+
func createIgnoreEventWithHeight(t *testing.T, baseAssetID asset.ID,
455+
db BatchedUniverseTree, height uint32) *supplycommit.NewIgnoreEvent {
456+
457+
signedTuple := randIgnoreTuple(t, db)
458+
signedTuple.IgnoreTuple.Val.ID = baseAssetID
459+
signedTuple.IgnoreTuple.Val.BlockHeight = height
460+
461+
return &supplycommit.NewIgnoreEvent{
462+
SignedIgnoreTuple: signedTuple,
463+
}
464+
}
465+
370466
// TestSupplyTreeStoreApplySupplyUpdates tests that the ApplySupplyUpdates meets
371467
// a series of key invariant via property based testing.
372468
func TestSupplyTreeStoreApplySupplyUpdates(t *testing.T) {
@@ -544,3 +640,114 @@ func TestSupplyTreeStoreApplySupplyUpdates(t *testing.T) {
544640
)
545641
require.NoError(t, err)
546642
}
643+
644+
// TestSupplyTreeStoreFetchSupplyLeavesByHeight tests the
645+
// FetchSupplyLeavesByHeight method.
646+
func TestSupplyTreeStoreFetchSupplyLeavesByHeight(t *testing.T) {
647+
t.Parallel()
648+
649+
supplyStore, spec, _ := setupSupplyTreeTestForProps(t)
650+
ctxb := context.Background()
651+
dbTxer := supplyStore.db.(BatchedUniverseTree)
652+
653+
groupKey, err := spec.UnwrapGroupKeyOrErr()
654+
require.NoError(t, err)
655+
assetID := spec.UnwrapIdToPtr()
656+
657+
fullGroupKey := &asset.GroupKey{
658+
GroupPubKey: *groupKey,
659+
}
660+
661+
// Create events with specific block heights, we'll use these heights
662+
// below to ensure that the new leaf height is properly set/read all the
663+
// way down the call stack.
664+
mintEvent100 := createMintEventWithHeight(t, groupKey, 100)
665+
burnEvent200 := createBurnEventWithHeight(
666+
t, asset.RandGenesis(t, asset.Normal), fullGroupKey, dbTxer,
667+
200,
668+
)
669+
ignoreEvent300 := createIgnoreEventWithHeight(t, *assetID, dbTxer, 300)
670+
mintEvent400 := createMintEventWithHeight(t, groupKey, 400)
671+
672+
updates := []supplycommit.SupplyUpdateEvent{
673+
mintEvent100, burnEvent200, ignoreEvent300, mintEvent400,
674+
}
675+
676+
// Apply updates.
677+
_, err = supplyStore.ApplySupplyUpdates(ctxb, spec, updates)
678+
require.NoError(t, err)
679+
680+
testCases := []struct {
681+
name string
682+
startHeight uint32
683+
endHeight uint32
684+
expectedCount int
685+
expectedHeights []uint32
686+
}{
687+
{
688+
name: "range including first",
689+
startHeight: 0,
690+
endHeight: 150,
691+
expectedCount: 1,
692+
expectedHeights: []uint32{100},
693+
},
694+
{
695+
name: "range including second",
696+
startHeight: 150,
697+
endHeight: 250,
698+
expectedCount: 1,
699+
expectedHeights: []uint32{200},
700+
},
701+
{
702+
name: "range including all",
703+
startHeight: 0,
704+
endHeight: 500,
705+
expectedCount: 4,
706+
expectedHeights: []uint32{100, 200, 300, 400},
707+
},
708+
{
709+
name: "exact range",
710+
startHeight: 100,
711+
endHeight: 400,
712+
expectedCount: 4,
713+
expectedHeights: []uint32{100, 200, 300, 400},
714+
},
715+
{
716+
name: "inner range",
717+
startHeight: 101,
718+
endHeight: 399,
719+
expectedCount: 2,
720+
expectedHeights: []uint32{200, 300},
721+
},
722+
{
723+
name: "range after all",
724+
startHeight: 501,
725+
endHeight: 1000,
726+
expectedCount: 0,
727+
expectedHeights: nil,
728+
},
729+
{
730+
name: "range before all",
731+
startHeight: 0,
732+
endHeight: 99,
733+
expectedCount: 0,
734+
expectedHeights: nil,
735+
},
736+
}
737+
738+
for _, tc := range testCases {
739+
t.Run(tc.name, func(t *testing.T) {
740+
leaves, err := supplyStore.FetchSupplyLeavesByHeight(
741+
ctxb, spec, tc.startHeight, tc.endHeight,
742+
)
743+
require.NoError(t, err)
744+
require.Len(t, leaves, tc.expectedCount)
745+
746+
var heights []uint32
747+
for _, leaf := range leaves {
748+
heights = append(heights, leaf.BlockHeight)
749+
}
750+
require.ElementsMatch(t, tc.expectedHeights, heights)
751+
})
752+
}
753+
}

0 commit comments

Comments
 (0)