Skip to content

Commit 11566a9

Browse files
committed
mssmt: add InsertMany method to full and compacted trees
This commit introduces the InsertMany method to both the FullTree and CompactedTree implementations of the MS-SMT. This method allows for the insertion of multiple leaf nodes in a single database transaction, improving efficiency when adding multiple leaves at once. The InsertMany method is added to the Tree interface and implemented in both FullTree and CompactedTree. The implementation includes sum overflow checks before each insertion and updates the root within the transaction for consistency. A new test case, TestInsertMany, is added to verify the functionality of the InsertMany method in both FullTree and CompactedTree. The test inserts a random set of leaves using InsertMany and verifies the resulting root and retrieved leaves. The Copy method in both FullTree and CompactedTree is updated to use InsertMany for efficiency when copying leaves to the target tree.
1 parent 6d3b699 commit 11566a9

File tree

4 files changed

+193
-17
lines changed

4 files changed

+193
-17
lines changed

mssmt/compacted_tree.go

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -543,15 +543,73 @@ func (t *CompactedTree) Copy(ctx context.Context, targetTree Tree) error {
543543
return err
544544
}
545545

546-
// Insert all found leaves into the target tree.
547-
for key, leaf := range leaves {
548-
// Use the target tree's Insert method.
549-
_, err := targetTree.Insert(ctx, key, leaf)
546+
// Insert all found leaves into the target tree using InsertMany for
547+
// efficiency.
548+
_, err = targetTree.InsertMany(ctx, leaves)
549+
if err != nil {
550+
return fmt.Errorf("error inserting leaves into "+
551+
"target tree: %w", err)
552+
}
553+
554+
return nil
555+
}
556+
557+
// InsertMany inserts multiple leaf nodes provided in the leaves map within a
558+
// single database transaction.
559+
func (t *CompactedTree) InsertMany(ctx context.Context,
560+
leaves map[[hashSize]byte]*LeafNode) (Tree, error) {
561+
562+
if len(leaves) == 0 {
563+
return t, nil
564+
}
565+
566+
dbErr := t.store.Update(ctx, func(tx TreeStoreUpdateTx) error {
567+
currentRoot, err := tx.RootNode()
550568
if err != nil {
551-
return fmt.Errorf("error inserting leaf with key %x "+
552-
"into target tree: %w", key, err)
569+
return err
553570
}
571+
rootBranch := currentRoot.(*BranchNode)
572+
573+
for key, leaf := range leaves {
574+
// Check for potential sum overflow before each
575+
// insertion.
576+
sumRoot := rootBranch.NodeSum()
577+
sumLeaf := leaf.NodeSum()
578+
err = CheckSumOverflowUint64(sumRoot, sumLeaf)
579+
if err != nil {
580+
return fmt.Errorf("compact tree leaf insert "+
581+
"sum overflow, root: %d, leaf: %d; %w",
582+
sumRoot, sumLeaf, err)
583+
}
584+
585+
// Insert the leaf using the internal helper.
586+
newRoot, err := t.insert(
587+
tx, &key, 0, rootBranch, leaf,
588+
)
589+
if err != nil {
590+
return fmt.Errorf("error inserting leaf "+
591+
"with key %x: %w", key, err)
592+
}
593+
rootBranch = newRoot
594+
595+
// Update the root within the transaction for
596+
// consistency, even though the insert logic passes the
597+
// root explicitly.
598+
err = tx.UpdateRoot(rootBranch)
599+
if err != nil {
600+
return fmt.Errorf("error updating root "+
601+
"during InsertMany: %w", err)
602+
}
603+
}
604+
605+
// The root is already updated by the last iteration of the
606+
// loop. No final update needed here, but returning nil error
607+
// signals success.
608+
return nil
609+
})
610+
if dbErr != nil {
611+
return nil, dbErr
554612
}
555613

556-
return nil
614+
return t, nil
557615
}

mssmt/interface.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ type Tree interface {
3131
// leaf.
3232
MerkleProof(ctx context.Context, key [hashSize]byte) (*Proof, error)
3333

34+
// InsertMany inserts multiple leaf nodes provided in the leaves map
35+
// within a single database transaction.
36+
InsertMany(ctx context.Context, leaves map[[hashSize]byte]*LeafNode) (
37+
Tree, error)
38+
3439
// Copy copies all the key-value pairs from the source tree into the
3540
// target tree. The target tree is assumed to be empty.
3641
Copy(ctx context.Context, targetTree Tree) error

mssmt/tree.go

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -423,20 +423,71 @@ func (t *FullTree) Copy(ctx context.Context, targetTree Tree) error {
423423
return err
424424
}
425425

426-
// Insert all found leaves into the target tree. We assume the target
427-
// tree handles batching or individual inserts efficiently.
428-
for key, leaf := range leaves {
429-
// Use the target tree's Insert method. We ignore the returned
430-
// tree as we are modifying the targetTree in place via its
431-
// store.
432-
_, err := targetTree.Insert(ctx, key, leaf)
426+
// Insert all found leaves into the target tree using InsertMany for
427+
// efficiency.
428+
_, err = targetTree.InsertMany(ctx, leaves)
429+
if err != nil {
430+
return fmt.Errorf("error inserting leaves into target tree: %w", err)
431+
}
432+
433+
return nil
434+
}
435+
436+
// InsertMany inserts multiple leaf nodes provided in the leaves map within a
437+
// single database transaction.
438+
func (t *FullTree) InsertMany(ctx context.Context,
439+
leaves map[[hashSize]byte]*LeafNode) (Tree, error) {
440+
441+
if len(leaves) == 0 {
442+
return t, nil
443+
}
444+
445+
err := t.store.Update(ctx, func(tx TreeStoreUpdateTx) error {
446+
currentRoot, err := tx.RootNode()
433447
if err != nil {
434-
return fmt.Errorf("error inserting leaf with key %x "+
435-
"into target tree: %w", key, err)
448+
return err
436449
}
450+
rootBranch := currentRoot.(*BranchNode)
451+
452+
for key, leaf := range leaves {
453+
// Check for potential sum overflow before each
454+
// insertion.
455+
sumRoot := rootBranch.NodeSum()
456+
sumLeaf := leaf.NodeSum()
457+
err = CheckSumOverflowUint64(sumRoot, sumLeaf)
458+
if err != nil {
459+
return fmt.Errorf("full tree leaf insert sum "+
460+
"overflow, root: %d, leaf: %d; %w",
461+
sumRoot, sumLeaf, err)
462+
}
463+
464+
// Insert the leaf using the internal helper.
465+
newRoot, err := t.insert(tx, &key, leaf)
466+
if err != nil {
467+
return fmt.Errorf("error inserting leaf "+
468+
"with key %x: %w", key, err)
469+
}
470+
rootBranch = newRoot
471+
472+
// Update the root within the transaction so subsequent
473+
// inserts in this batch read the correct state.
474+
err = tx.UpdateRoot(rootBranch)
475+
if err != nil {
476+
return fmt.Errorf("error updating root "+
477+
"during InsertMany: %w", err)
478+
}
479+
}
480+
481+
// The root is already updated by the last iteration of the
482+
// loop. No final update needed here, but returning nil error
483+
// signals success.
484+
return nil
485+
})
486+
if err != nil {
487+
return nil, err
437488
}
438489

439-
return nil
490+
return t, nil
440491
}
441492

442493
// VerifyMerkleProof determines whether a merkle proof for the leaf found at the

mssmt/tree_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,68 @@ func TestTreeCopy(t *testing.T) {
918918
}
919919
}
920920

921+
// TestInsertMany tests inserting multiple leaves using the InsertMany method.
922+
func TestInsertMany(t *testing.T) {
923+
t.Parallel()
924+
925+
leavesToInsert := randTree(50)
926+
leavesMap := make(map[[hashSize]byte]*mssmt.LeafNode)
927+
for _, item := range leavesToInsert {
928+
leavesMap[item.key] = item.leaf
929+
}
930+
931+
// Calculate expected root after individual insertions for comparison.
932+
tempStore := mssmt.NewDefaultStore()
933+
tempTree := mssmt.NewFullTree(tempStore)
934+
ctx := context.Background()
935+
for key, leaf := range leavesMap {
936+
_, err := tempTree.Insert(ctx, key, leaf)
937+
require.NoError(t, err)
938+
}
939+
expectedRoot, err := tempTree.Root(ctx)
940+
require.NoError(t, err)
941+
942+
runTest := func(t *testing.T, name string,
943+
makeTree func(mssmt.TreeStore) mssmt.Tree,
944+
makeStore makeTestTreeStoreFunc) {
945+
946+
t.Run(name, func(t *testing.T) {
947+
store, err := makeStore()
948+
require.NoError(t, err)
949+
tree := makeTree(store)
950+
951+
// Test inserting an empty map (should be a no-op).
952+
_, err = tree.InsertMany(ctx, make(map[[hashSize]byte]*mssmt.LeafNode))
953+
require.NoError(t, err)
954+
initialRoot, err := tree.Root(ctx)
955+
require.NoError(t, err)
956+
require.True(t, mssmt.IsEqualNode(mssmt.EmptyTree[0], initialRoot))
957+
958+
// Insert the leaves using InsertMany.
959+
_, err = tree.InsertMany(ctx, leavesMap)
960+
require.NoError(t, err)
961+
962+
// Verify the root.
963+
finalRoot, err := tree.Root(ctx)
964+
require.NoError(t, err)
965+
require.True(t, mssmt.IsEqualNode(expectedRoot, finalRoot))
966+
967+
// Verify each leaf can be retrieved.
968+
for key, expectedLeaf := range leavesMap {
969+
retrievedLeaf, err := tree.Get(ctx, key)
970+
require.NoError(t, err)
971+
require.Equal(t, expectedLeaf, retrievedLeaf)
972+
}
973+
})
974+
}
975+
976+
for storeName, makeStore := range genTestStores(t) {
977+
t.Run(storeName, func(t *testing.T) {
978+
runTest(t, "full SMT", makeFullTree, makeStore)
979+
runTest(t, "smol SMT", makeSmolTree, makeStore)
980+
})
981+
}
982+
}
921983

922984
// runBIPTestVector runs the tests in a single BIP test vector file.
923985
func runBIPTestVector(t *testing.T, testVectors *mssmt.TestVectors) {

0 commit comments

Comments
 (0)