Skip to content

Commit

Permalink
Merge pull request #335 from lightninglabs/universe_root_deletion
Browse files Browse the repository at this point in the history
WIP: Universe root deletion RPC
  • Loading branch information
guggero authored Jun 20, 2023
2 parents 012554c + 168772a commit 1f8dd8a
Show file tree
Hide file tree
Showing 28 changed files with 1,288 additions and 447 deletions.
41 changes: 41 additions & 0 deletions cmd/tapcli/universe.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var universeCommands = []cli.Command{
Category: "Universe",
Subcommands: []cli.Command{
universeRootsCommand,
universeDeleteRootCommand,
universeLeavesCommand,
universeKeysCommand,
universeProofCommand,
Expand Down Expand Up @@ -139,6 +140,46 @@ func universeRoots(ctx *cli.Context) error {
return nil
}

var universeDeleteRootCommand = cli.Command{
Name: "delete",
ShortName: "d",
Description: "Delete a known asset universe root",
Usage: "delete a known asset universe root",
Flags: []cli.Flag{
cli.StringFlag{
Name: assetIDName,
Usage: "the asset ID of the universe to delete",
},
cli.StringFlag{
Name: groupKeyName,
Usage: "the group key of the universe to delete",
},
},
Action: deleteUniverseRoot,
}

func deleteUniverseRoot(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getUniverseClient(ctx)
defer cleanUp()

universeID, err := parseUniverseID(ctx, true)
if err != nil {
return err
}

rootReq := &universerpc.DeleteRootQuery{
Id: universeID,
}

_, err = client.DeleteAssetRoot(ctxc, rootReq)
if err != nil {
return err
}

return nil
}

var universeKeysCommand = cli.Command{
Name: "keys",
ShortName: "k",
Expand Down
42 changes: 42 additions & 0 deletions itest/universe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,48 @@ func testUniverseSync(t *harnessTest) {
firstAssetFromUni := firstAssetUniProof.AssetLeaf.Asset
firstAssetFromUni.PrevWitnesses = nil
assertAsset(t.t, rpcSimpleAssets[0], firstAssetFromUni)

// Now we'll delete a universe root on Bob's node, and then re-sync it.
_, err = bob.DeleteAssetRoot(ctxt, &unirpc.DeleteRootQuery{
Id: &unirpc.ID{
Id: &unirpc.ID_AssetId{
AssetId: firstAssetID,
},
},
})
require.NoError(t.t, err)

universeRootsBob, err = bob.AssetRoots(
ctxt, &unirpc.AssetRootRequest{},
)
require.NoError(t.t, err)

// Bob should be missing one universe root from the total, which is
// exactly the root we deleted.
require.Len(t.t, universeRootsBob.UniverseRoots, totalAssets-1)
firstAssetUniID := hex.EncodeToString(firstAssetID)
_, ok := universeRootsBob.UniverseRoots[firstAssetUniID]
require.False(t.t, ok)

syncDiff, err = bob.SyncUniverse(ctxt, &unirpc.SyncRequest{
UniverseHost: t.tapd.rpcHost(),
SyncMode: unirpc.UniverseSyncMode_SYNC_ISSUANCE_ONLY,
})
require.NoError(t.t, err)

// The diff from resyncing Bob to the main universe node should be
// for one universe with one asset.
require.Len(t.t, syncDiff.SyncedUniverses, 1)
resyncedUniverse := syncDiff.SyncedUniverses[0]
require.True(t.t, resyncedUniverse.OldAssetRoot.MssmtRoot == nil)
require.Len(t.t, resyncedUniverse.NewAssetLeaves, 1)

// After re-sync, both universes should match again.
universeRootsBob, err = bob.AssetRoots(
ctxt, &unirpc.AssetRootRequest{},
)
require.NoError(t.t, err)
assertUniverseRootsEqual(t.t, universeRoots, universeRootsBob)
}

// testUniverseREST tests that we're able to properly query the universe state
Expand Down
14 changes: 14 additions & 0 deletions mssmt/compacted_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,20 @@ func (t *CompactedTree) Delete(ctx context.Context, key [hashSize]byte) (
return t, nil
}

// DeleteRoot deletes the root node of the MS-SMT.
func (t *CompactedTree) DeleteRoot(ctx context.Context) error {
return t.store.Update(ctx, func(tx TreeStoreUpdateTx) error {
return tx.DeleteRoot()
})
}

// DeleteAllNodes deletes all nodes in the MS-SMT.
func (t *CompactedTree) DeleteAllNodes(ctx context.Context) error {
return t.store.Update(ctx, func(tx TreeStoreUpdateTx) error {
return tx.DeleteAllNodes()
})
}

// Get returns the leaf node found at the given key within the MS-SMT.
func (t *CompactedTree) Get(ctx context.Context, key [hashSize]byte) (
*LeafNode, error) {
Expand Down
6 changes: 6 additions & 0 deletions mssmt/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ type Tree interface {
// MS-SMT.
Delete(ctx context.Context, key [hashSize]byte) (Tree, error)

// DeleteRoot deletes the root node of the MS-SMT.
DeleteRoot(ctx context.Context) error

// DeleteAllNodes deletes all non-root nodes within the MS-SMT.
DeleteAllNodes(ctx context.Context) error

// Get returns the leaf node found at the given key within the MS-SMT.
Get(ctx context.Context, key [hashSize]byte) (*LeafNode, error)

Expand Down
48 changes: 46 additions & 2 deletions mssmt/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"fmt"
"sync"

"golang.org/x/exp/maps"
)

// TreeStore represents a generic database interface to update or view a
Expand Down Expand Up @@ -61,6 +63,12 @@ type TreeStoreUpdateTx interface {
// DeleteCompactedLeaf deletes a compacted leaf keyed by the given
// NodeHash.
DeleteCompactedLeaf(NodeHash) error

// DeleteRoot deletes the root node of the MS-SMT.
DeleteRoot() error

// DeleteAllNodes deletes all nodes in the MS-SMT.
DeleteAllNodes() error
}

// TreeStoreDriver represents a concrete driver of the main TreeStore
Expand Down Expand Up @@ -244,6 +252,32 @@ func (d *DefaultStore) DeleteCompactedLeaf(key NodeHash) error {
return nil
}

// DeleteRoot deletes the root node of the MS-SMT.
func (d *DefaultStore) DeleteRoot() error {
d.root = nil
d.cntDeletes++

return nil
}

// DeleteAllNodes deletes all nodes in the MS-SMT.
func (d *DefaultStore) DeleteAllNodes() error {
// Delete leaves, then compacted leaves, then branches.
leafCount := len(d.leaves)
maps.Clear(d.leaves)
d.cntDeletes += leafCount

compactedLeafCount := len(d.compactedLeaves)
maps.Clear(d.compactedLeaves)
d.cntDeletes += compactedLeafCount

branchCount := len(d.branches)
maps.Clear(d.branches)
d.cntDeletes += branchCount

return nil
}

// GetChildren returns the left and right child of the node keyed by the given
// NodeHash.
func (d *DefaultStore) GetChildren(height int, key NodeHash) (
Expand All @@ -257,16 +291,26 @@ func (d *DefaultStore) GetChildren(height int, key NodeHash) (
d.cntReads++
return branch
}

if leaf, ok := d.compactedLeaves[key]; ok {
d.cntReads++
return leaf
}

d.cntReads++
return d.leaves[key]
if leaf, ok := d.leaves[key]; ok {
d.cntReads++
return leaf
}

return EmptyTree[height]
}

node := getNode(uint(height), key)

if key != EmptyTree[height].NodeHash() && node == EmptyTree[height] {
return nil, nil, fmt.Errorf("node not found")
}

switch node := node.(type) {
case *BranchNode:
return getNode(uint(height)+1, node.Left.NodeHash()),
Expand Down
14 changes: 14 additions & 0 deletions mssmt/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,20 @@ func (t *FullTree) Delete(ctx context.Context, key [hashSize]byte) (
return t, nil
}

// DeleteRoot deletes the root node of the MS-SMT.
func (t *FullTree) DeleteRoot(ctx context.Context) error {
return t.store.Update(ctx, func(tx TreeStoreUpdateTx) error {
return tx.DeleteRoot()
})
}

// DeleteAllNodes deletes all nodes in the MS-SMT.
func (t *FullTree) DeleteAllNodes(ctx context.Context) error {
return t.store.Update(ctx, func(tx TreeStoreUpdateTx) error {
return tx.DeleteAllNodes()
})
}

// Get returns the leaf node found at the given key within the MS-SMT.
func (t *FullTree) Get(ctx context.Context, key [hashSize]byte) (
*LeafNode, error) {
Expand Down
34 changes: 34 additions & 0 deletions mssmt/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ func TestDeletion(t *testing.T) {
testDeletion(t,
leaves, mssmt.NewFullTree(store),
)
testBatchDeletion(t,
leaves, mssmt.NewFullTree(store),
)
})

t.Run("smol SMT", func(t *testing.T) {
Expand All @@ -433,6 +436,9 @@ func TestDeletion(t *testing.T) {
testDeletion(t,
leaves, mssmt.NewCompactedTree(store),
)
testBatchDeletion(t,
leaves, mssmt.NewCompactedTree(store),
)
})
})
}
Expand Down Expand Up @@ -465,6 +471,34 @@ func testDeletion(t *testing.T, leaves []treeLeaf, tree mssmt.Tree) {
require.True(t, mssmt.IsEqualNode(mssmt.EmptyTree[0], treeRoot))
}

func testBatchDeletion(t *testing.T, leaves []treeLeaf, tree mssmt.Tree) {
ctx := context.TODO()
for _, item := range leaves {
_, err := tree.Insert(ctx, item.key, item.leaf)
require.NoError(t, err)
}

treeRoot, err := tree.Root(ctx)
require.NoError(t, err)
require.NotEqual(t, mssmt.EmptyTree[0], treeRoot)

err = tree.DeleteAllNodes(ctx)
require.NoError(t, err)

for _, item := range leaves {
emptyLeaf, err := tree.Get(ctx, item.key)
require.Nil(t, emptyLeaf)
require.ErrorContains(t, err, "node not found")
}

err = tree.DeleteRoot(ctx)
require.NoError(t, err)

treeRoot, err = tree.Root(ctx)
require.NoError(t, err)
require.Equal(t, mssmt.EmptyTree[0], treeRoot)
}

func assertEqualProofAfterCompression(t *testing.T, proof *mssmt.Proof) {
t.Helper()

Expand Down
4 changes: 4 additions & 0 deletions perms/perms.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ var (
Entity: "universe",
Action: "read",
}},
"/universerpc.Universe/DeleteAssetRoot": {{
Entity: "universe",
Action: "write",
}},
"/universerpc.Universe/AssetLeafKeys": {{
Entity: "universe",
Action: "read",
Expand Down
20 changes: 20 additions & 0 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2370,6 +2370,26 @@ func (r *rpcServer) QueryAssetRoots(ctx context.Context,
}, nil
}

// DeleteAssetRoot attempts to locate the current Universe root for a specific
// asset, and deletes the associated Universe tree if found.
func (r *rpcServer) DeleteAssetRoot(ctx context.Context,
req *unirpc.DeleteRootQuery) (*unirpc.DeleteRootResponse, error) {

universeID, err := unmarshalUniID(req.Id)
if err != nil {
return nil, err
}

rpcsLog.Debugf("Deleting asset root for %v", spew.Sdump(universeID))

_, err = r.cfg.BaseUniverse.DeleteRoot(ctx, universeID)
if err != nil {
return nil, err
}

return &unirpc.DeleteRootResponse{}, nil
}

func marshalLeafKey(leafKey universe.BaseKey) *unirpc.AssetKey {
return &unirpc.AssetKey{
Outpoint: &unirpc.AssetKey_OpStr{
Expand Down
18 changes: 18 additions & 0 deletions tapdb/mssmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ type TreeStore interface {
// leaf) from the store.
DeleteNode(ctx context.Context, n DelNode) (int64, error)

// DeleteAllNodes deletes all nodes from the store.
DeleteAllNodes(ctx context.Context, namespace string) (int64, error)

// DeleteRoot deletes a root node from the store.
DeleteRoot(ctx context.Context, namespace string) (int64, error)

// FetchRootNode fetches the root node for the specified namespace.
FetchRootNode(ctx context.Context,
namespace string) (sqlc.MssmtNode, error)
Expand Down Expand Up @@ -217,6 +223,18 @@ func (t *taprootAssetTreeStoreTx) InsertCompactedLeaf(
return nil
}

// DeleteRoot deletes the root node of the MS-SMT.
func (t *taprootAssetTreeStoreTx) DeleteRoot() error {
_, err := t.dbTx.DeleteRoot(t.ctx, t.namespace)
return err
}

// DeleteRoot deletes all nodes, including branch nodes, of the MS-SMT.
func (t *taprootAssetTreeStoreTx) DeleteAllNodes() error {
_, err := t.dbTx.DeleteAllNodes(t.ctx, t.namespace)
return err
}

// DeleteBranch deletes the branch node keyed by the given NodeHash.
func (t *taprootAssetTreeStoreTx) DeleteBranch(hashKey mssmt.NodeHash) error {
_, err := t.dbTx.DeleteNode(t.ctx, DelNode{
Expand Down
Loading

0 comments on commit 1f8dd8a

Please sign in to comment.