From 46d8fbabb24713521efd9b7f4ab6ce382df711d1 Mon Sep 17 00:00:00 2001 From: Sneh Koul Date: Wed, 27 Aug 2025 17:16:35 -0400 Subject: [PATCH 1/5] Decentralized Sequencing: View Store Implementation --- espresso/view-store/view_store_binary_tree.go | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 espresso/view-store/view_store_binary_tree.go diff --git a/espresso/view-store/view_store_binary_tree.go b/espresso/view-store/view_store_binary_tree.go new file mode 100644 index 00000000000..df3dd6ae969 --- /dev/null +++ b/espresso/view-store/view_store_binary_tree.go @@ -0,0 +1,86 @@ +package view_store + +import ( + "github.com/ethereum/go-ethereum/common" +) + +type View struct { + viewNumber uint64 + builderCommitment string + stateHash common.Hash +} + +// ViewStoreBinaryTree is a binary tree +// storing the state hashes of the nitro state +// at a given view number and payload commitment +// TODO: should we store this in the database? +type ViewStoreBinaryTree struct { + view View + Left *ViewStoreBinaryTree + Right *ViewStoreBinaryTree +} + +func Insert(root *ViewStoreBinaryTree, viewNumber uint64, builderCommitment string, stateHash common.Hash) *ViewStoreBinaryTree { + if root == nil { + return &ViewStoreBinaryTree{ + view: View{ + viewNumber: viewNumber, + builderCommitment: builderCommitment, + stateHash: stateHash, + }, + } + } + if viewNumber < root.view.viewNumber { + root.Left = Insert(root.Left, viewNumber, builderCommitment, stateHash) + } else if viewNumber > root.view.viewNumber { + root.Right = Insert(root.Right, viewNumber, builderCommitment, stateHash) + } + + // This means that view numbers is equal to the root's view number + // Payload commitment might be different so we insert based on that now + if builderCommitment < root.view.builderCommitment { + root.Left = Insert(root.Left, viewNumber, builderCommitment, stateHash) + } else if builderCommitment > root.view.builderCommitment { + root.Right = Insert(root.Right, viewNumber, builderCommitment, stateHash) + } + + // This means that the view numbers are equal and the payload commitment is equal + return root +} + +func Search(root *ViewStoreBinaryTree, viewNumber uint64, builderCommitment string) *View { + if root == nil { + return nil + } + if viewNumber < root.view.viewNumber { + return Search(root.Left, viewNumber, builderCommitment) + } else if viewNumber > root.view.viewNumber { + return Search(root.Right, viewNumber, builderCommitment) + } + if builderCommitment == root.view.builderCommitment { + return &root.view + } else if builderCommitment < root.view.builderCommitment { + return Search(root.Left, viewNumber, builderCommitment) + } else if builderCommitment > root.view.builderCommitment { + return Search(root.Right, viewNumber, builderCommitment) + } + return nil +} + +func Delete(root *ViewStoreBinaryTree, viewNumber uint64) *ViewStoreBinaryTree { + if root == nil { + return nil + } + // Find the node where this view number is located first and then delete any nodes + // which have the view number equal or less than the node's view number + if viewNumber < root.view.viewNumber { + root.Left = Delete(root.Left, viewNumber) + } else if viewNumber > root.view.viewNumber { + root.Right = Delete(root.Right, viewNumber) + } + + if root.Left == nil && root.Right == nil { + return nil + } + return root +} From bb37ddb9c8e428cfb79979bb6e21e38dcc7433d0 Mon Sep 17 00:00:00 2001 From: Sneh Koul Date: Thu, 28 Aug 2025 15:51:13 -0400 Subject: [PATCH 2/5] add tests --- espresso/view-store/view_store_binary_tree.go | 46 +-- .../view-store/view_store_binary_tree_test.go | 279 ++++++++++++++++++ 2 files changed, 308 insertions(+), 17 deletions(-) create mode 100644 espresso/view-store/view_store_binary_tree_test.go diff --git a/espresso/view-store/view_store_binary_tree.go b/espresso/view-store/view_store_binary_tree.go index df3dd6ae969..da020786456 100644 --- a/espresso/view-store/view_store_binary_tree.go +++ b/espresso/view-store/view_store_binary_tree.go @@ -4,6 +4,7 @@ import ( "github.com/ethereum/go-ethereum/common" ) +// TODO: should we store this in the database? type View struct { viewNumber uint64 builderCommitment string @@ -13,13 +14,17 @@ type View struct { // ViewStoreBinaryTree is a binary tree // storing the state hashes of the nitro state // at a given view number and payload commitment -// TODO: should we store this in the database? type ViewStoreBinaryTree struct { view View Left *ViewStoreBinaryTree Right *ViewStoreBinaryTree } +/* +Insert inserts a view into the view store binary tree, on the left side of the tree all the views +with a view number and builder commitment less than the root's view number are stored. On the right side +all the views with a view number and builder commitment greater than the root's view number are stored. +*/ func Insert(root *ViewStoreBinaryTree, viewNumber uint64, builderCommitment string, stateHash common.Hash) *ViewStoreBinaryTree { if root == nil { return &ViewStoreBinaryTree{ @@ -32,23 +37,30 @@ func Insert(root *ViewStoreBinaryTree, viewNumber uint64, builderCommitment stri } if viewNumber < root.view.viewNumber { root.Left = Insert(root.Left, viewNumber, builderCommitment, stateHash) + return root } else if viewNumber > root.view.viewNumber { root.Right = Insert(root.Right, viewNumber, builderCommitment, stateHash) + return root } // This means that view numbers is equal to the root's view number - // Payload commitment might be different so we insert based on that now + // Builder commitment might be different so we insert based on that now if builderCommitment < root.view.builderCommitment { root.Left = Insert(root.Left, viewNumber, builderCommitment, stateHash) + return root } else if builderCommitment > root.view.builderCommitment { root.Right = Insert(root.Right, viewNumber, builderCommitment, stateHash) + return root } - // This means that the view numbers are equal and the payload commitment is equal + // This means that the view numbers are equal and the builder commitment is equal return root } -func Search(root *ViewStoreBinaryTree, viewNumber uint64, builderCommitment string) *View { +/* +Search searches for a view with a given view number and builder commitment +*/ +func Search(root *ViewStoreBinaryTree, viewNumber uint64, builderCommitment string) *ViewStoreBinaryTree { if root == nil { return nil } @@ -58,29 +70,29 @@ func Search(root *ViewStoreBinaryTree, viewNumber uint64, builderCommitment stri return Search(root.Right, viewNumber, builderCommitment) } if builderCommitment == root.view.builderCommitment { - return &root.view + return root } else if builderCommitment < root.view.builderCommitment { return Search(root.Left, viewNumber, builderCommitment) } else if builderCommitment > root.view.builderCommitment { return Search(root.Right, viewNumber, builderCommitment) } - return nil + return root } -func Delete(root *ViewStoreBinaryTree, viewNumber uint64) *ViewStoreBinaryTree { +// Delete removes all nodes with viewNumber <= cutoff. +func Delete(root *ViewStoreBinaryTree, cutoff uint64) *ViewStoreBinaryTree { if root == nil { return nil } - // Find the node where this view number is located first and then delete any nodes - // which have the view number equal or less than the node's view number - if viewNumber < root.view.viewNumber { - root.Left = Delete(root.Left, viewNumber) - } else if viewNumber > root.view.viewNumber { - root.Right = Delete(root.Right, viewNumber) - } - if root.Left == nil && root.Right == nil { - return nil + if root.view.viewNumber > cutoff { + // Keep this node; only the left subtree can contain nodes to delete. + root.Left = Delete(root.Left, cutoff) + return root } - return root + + // root.view.viewNumber <= cutoff: + // Drop this node and its entire left subtree. + // Continue deleting in the right subtree (may contain ties == cutoff). + return Delete(root.Right, cutoff) } diff --git a/espresso/view-store/view_store_binary_tree_test.go b/espresso/view-store/view_store_binary_tree_test.go new file mode 100644 index 00000000000..e3724418eeb --- /dev/null +++ b/espresso/view-store/view_store_binary_tree_test.go @@ -0,0 +1,279 @@ +package view_store + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func mkHash(s string) common.Hash { return common.BytesToHash([]byte(s)) } + +func MakeInitialTree(t *testing.T) *ViewStoreBinaryTree { + root := Insert(nil, 5, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_", mkHash("1")) + root = Insert(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_", mkHash("2")) + root = Insert(root, 7, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgC_", mkHash("3")) + return root +} + +func TestEspressoViewStoreInsert(t *testing.T) { + t.Run("Insert view on the left side of the tree", func(t *testing.T) { + root := MakeInitialTree(t) + + // Now insert a view which has a view number which is less than the root's view number + root = Insert(root, 0, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_", mkHash("0")) + + // Search for view number 2 + viewStoreForViewNumvber2 := Search(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_") + viewStoreForViewNumvber0 := Search(root, 0, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_") + + // Check that left child has view number 0 + if viewStoreForViewNumvber2.Left.view.viewNumber != 0 { + t.Errorf("Expected left child's view number to be 0, got %d", viewStoreForViewNumvber2.Left.view.viewNumber) + } + + if viewStoreForViewNumvber0.view.viewNumber != 0 { + t.Errorf("Expected root's view number to be 0, got %d", viewStoreForViewNumvber0.view.viewNumber) + } + if viewStoreForViewNumvber0.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected root's builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumvber0.view.builderCommitment) + } + + }) + + t.Run("Insert view on the right side of the tree", func(t *testing.T) { + root := MakeInitialTree(t) + + // Now insert a view which has a view number which is less than the root's view number + root = Insert(root, 9, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_", mkHash("9")) + + // Now check that its parent should be view number 7 + viewStoreForViewNumber7 := Search(root, 7, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgC_") + + // Check that the right side of view store 7 is the inserted view + if viewStoreForViewNumber7.Right.view.viewNumber != 9 { + t.Errorf("Expected right side of view store 7 to be 9, got %d", viewStoreForViewNumber7.Right.view.viewNumber) + } + if viewStoreForViewNumber7.Right.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected right side of view store 7 to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber7.Right.view.builderCommitment) + } + + }) + + t.Run("Insert a view which has the same view number but higher builder commitment", func(t *testing.T) { + root := MakeInitialTree(t) + + // Now insert a view which has the same view number but higher builder commitment + root = Insert(root, 5, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_", mkHash("4")) + + // Now check that its parent view should be 5 view store with lower builder commitment + viewStoreForViewNumber5HigherBuilderCommitment := Search(root, 5, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_") + viewStoreFor7ViewNumber := Search(root, 7, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgC_") + + // Check that the lower builder commitment view store 5 exists on the left side of view store 7 + if viewStoreFor7ViewNumber.Left.view.viewNumber != 5 { + t.Errorf("Expected left side of view store for view number 7 to have view number 5, got %d", viewStoreFor7ViewNumber.Left.view.viewNumber) + } + if viewStoreFor7ViewNumber.Left.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected left side of view store for view number 7 to have builder commitment BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber5HigherBuilderCommitment.Left.view.builderCommitment) + } + }) + + t.Run("Insert a view which has the same view number but lower builder commitment", func(t *testing.T) { + root := MakeInitialTree(t) + + // Now insert a view which has the same view number but lower builder commitment + root = Insert(root, 5, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_", mkHash("4")) + + viewStoreFor2ViewNumber := Search(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_") + + // Check that the lower builder commitment view store 5 exists on the left side of view store 7 + if viewStoreFor2ViewNumber.Right.view.viewNumber != 5 { + t.Errorf("Expected right side of view store for view number 2 to have view number 5, got %d", viewStoreFor2ViewNumber.Left.view.viewNumber) + } + if viewStoreFor2ViewNumber.Right.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_" { + t.Errorf("Expected right side of view store for view number 2 to have builder commitment BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_, got %s", viewStoreFor2ViewNumber.Left.view.builderCommitment) + } + }) + +} + +func TestEspressoViewStoreSearch(t *testing.T) { + t.Run("Search view on the left side of the tree", func(t *testing.T) { + root := MakeInitialTree(t) + + // Now insert a view which has a view number which is less than the root's view number + root = Insert(root, 0, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_", mkHash("0")) + + // Search for view number 2 + viewStoreForViewNumber0 := Search(root, 0, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_") + + if viewStoreForViewNumber0.view.viewNumber != 0 { + t.Errorf("Expected root's view number to be 0, got %d", viewStoreForViewNumber0.view.viewNumber) + } + if viewStoreForViewNumber0.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected root's builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber0.view.builderCommitment) + } + }) + + t.Run("Search view on the right side of the tree", func(t *testing.T) { + root := MakeInitialTree(t) + + root = Insert(root, 10, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_", mkHash("2")) + // Search for view number 2 + viewStoreForViewNumber2 := Search(root, 10, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_") + + if viewStoreForViewNumber2.view.viewNumber != 10 { + t.Errorf("Expected right child's view number to be 10, got %d", viewStoreForViewNumber2.Right.view.viewNumber) + } + if viewStoreForViewNumber2.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_" { + t.Errorf("Expected right child's builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_, got %s", viewStoreForViewNumber2.Right.view.builderCommitment) + } + }) + + t.Run("search for a view number which is equal to one of the view numbers but has a greater builder commitment", func(t *testing.T) { + root := MakeInitialTree(t) + + // Insert a view which has a view number which is equal to the root's view number + // but has a greater builder commitment + root = Insert(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_", mkHash("4")) + + // Search for view number 2 + viewStoreForViewNumber2 := Search(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_") + + if viewStoreForViewNumber2.view.viewNumber != 2 { + t.Errorf("Expected view number to be 2, got %d", viewStoreForViewNumber2.Right.view.viewNumber) + } + if viewStoreForViewNumber2.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber2.Right.view.builderCommitment) + } + }) + + t.Run("search for a view number which is equal to one of the view numbers but has a lower builder commitment", func(t *testing.T) { + root := MakeInitialTree(t) + + // Insert a view which has a view number which is equal to the root's view number + // but has a lower builder commitment + root = Insert(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_", mkHash("4")) + + // Search for view number 2 + viewStoreForViewNumber2 := Search(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_") + + if viewStoreForViewNumber2.view.viewNumber != 2 { + t.Errorf("Expected view number to be 2, got %d", viewStoreForViewNumber2.view.viewNumber) + } + if viewStoreForViewNumber2.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_" { + t.Errorf("Expected builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_, got %s", viewStoreForViewNumber2.view.builderCommitment) + } + }) + + t.Run("search for a view number which doesnt exist", func(t *testing.T) { + root := MakeInitialTree(t) + + // Search for view number 2 + viewStore := Search(root, 3, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_") + + if viewStore != nil { + t.Errorf("Expected view store to be nil, got %v", viewStore) + } + }) + + t.Run("search for a view which has a given view number but builder commitment which doesnt exist", func(t *testing.T) { + root := MakeInitialTree(t) + + // Search for view number 2 + viewStore := Search(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgK_") + + if viewStore != nil { + t.Errorf("Expected view store to be nil, got %v", viewStore) + } + }) +} + +func TestViewStoreDelete(t *testing.T) { + t.Run("Delete a view which has a view number less than the root's view number", func(t *testing.T) { + root := MakeInitialTree(t) + + // Delete view number 0 + root = Delete(root, 2) + + // Check that now no element exits on the left side of the tree + if root.Left != nil { + t.Errorf("Expected left child to be nil, got %v", root.Left) + } + }) + + t.Run("Delete a view which has a view number greater than the root's view number", func(t *testing.T) { + root := MakeInitialTree(t) + + // Delete view number 0 + root = Delete(root, 7) + + // It should have deleted the whole tree + if root != nil { + t.Errorf("Expected root to be nil, got %v", root) + } + + root = MakeInitialTree(t) + + // Insert a view number 10 and 8 + root = Insert(root, 10, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_", mkHash("2")) + root = Insert(root, 8, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_", mkHash("2")) + + // Delete view number 7 + root = Delete(root, 7) + + // root view number should be 10 + if root.view.viewNumber != 10 { + t.Errorf("Expected root view number to be 10, got %d", root.view.viewNumber) + } + + // Search and there should be no view with view number 7 + viewStore := Search(root, 7, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_") + if viewStore != nil { + t.Errorf("Expected view store to be nil, got %v", viewStore) + } + }) + + t.Run("Delete a view which has a view number equal to one of the views but higher builder commitment", func(t *testing.T) { + root := MakeInitialTree(t) + + // Insert a view number 2 but with a higher builder commitment + root = Insert(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_", mkHash("4")) + // Delete view number 0 + root = Delete(root, 2) + + // There should now be no left child of the root + if root.Left != nil { + t.Errorf("Expected left child to be nil, got %v", root.Left) + } + }) + + t.Run("Delete a view which has a view number equal to one of the views but lower builder commitment", func(t *testing.T) { + root := MakeInitialTree(t) + + // Insert a view number 2 but with a lower builder commitment + root = Insert(root, 7, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_", mkHash("4")) + // Delete view number 0 + root = Delete(root, 7) + + // now the whole tree should be deleted + if root != nil { + t.Errorf("Expected root to be nil, got %v", root) + } + + root = MakeInitialTree(t) + // Insert a view number 10 and 8 + root = Insert(root, 10, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_", mkHash("2")) + root = Insert(root, 8, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_", mkHash("2")) + + // Insert a lower builder commitment for view 8 and delete view number 8 + root = Insert(root, 8, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_", mkHash("2")) + root = Delete(root, 8) + + // root view number should be 10 + if root.view.viewNumber != 10 { + t.Errorf("Expected root view number to be 10, got %d", root.view.viewNumber) + } + }) + +} From 5928bca838182356846793692a3b4203d07cbad6 Mon Sep 17 00:00:00 2001 From: Sneh Koul Date: Thu, 28 Aug 2025 15:58:38 -0400 Subject: [PATCH 3/5] cleanup --- espresso/view-store/view_store_binary_tree.go | 1 - 1 file changed, 1 deletion(-) diff --git a/espresso/view-store/view_store_binary_tree.go b/espresso/view-store/view_store_binary_tree.go index da020786456..f077e8949fd 100644 --- a/espresso/view-store/view_store_binary_tree.go +++ b/espresso/view-store/view_store_binary_tree.go @@ -4,7 +4,6 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// TODO: should we store this in the database? type View struct { viewNumber uint64 builderCommitment string From 1d14ad22e987666637bd60fffb815d4fa79d71c8 Mon Sep 17 00:00:00 2001 From: Sneh Koul Date: Wed, 3 Sep 2025 16:55:58 -0400 Subject: [PATCH 4/5] Processing nitro state from consensus events --- espresso/hotshot-listener/hotshot_listener.go | 206 +++++++++++++++--- .../submitter/polling_espresso_submitter.go | 4 + espresso/utils/utils.go | 99 +++++++++ espresso/view-store/view_store_binary_tree.go | 24 +- .../view-store/view_store_binary_tree_test.go | 76 +++---- system_tests/hotshot_listener_test.go | 10 +- 6 files changed, 334 insertions(+), 85 deletions(-) create mode 100644 espresso/utils/utils.go diff --git a/espresso/hotshot-listener/hotshot_listener.go b/espresso/hotshot-listener/hotshot_listener.go index 5af6c107d38..b8363d6a2a9 100644 --- a/espresso/hotshot-listener/hotshot_listener.go +++ b/espresso/hotshot-listener/hotshot_listener.go @@ -5,15 +5,24 @@ import ( "fmt" "math/big" "strconv" + "time" "github.com/EspressoSystems/espresso-network/sdks/go/types" "github.com/gorilla/websocket" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" - + "github.com/ethereum/go-ethereum/rlp" + + "github.com/offchainlabs/nitro/arbos" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/espresso/utils" + view_store "github.com/offchainlabs/nitro/espresso/view-store" + "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/solgen/go/espressogen" "github.com/offchainlabs/nitro/util/stopwaiter" ) @@ -26,13 +35,16 @@ type HotshotListener struct { stopwaiter.StopWaiter hotshotUrl string rollupSequencerManager *espressogen.IEspressoRollupSequencerManager - quorumViewNumberBuilderCommitment map[string]big.Int - daViewNumberBuilderCommitment map[string]bool + quorumViewNumberBuilderCommitment map[string]*big.Int + daViewNumberBuilderCommitment map[string]*types.BlockPayload sequencerAddress string conn *websocket.Conn + chainId uint32 + execution *gethexec.ExecutionEngine + root *view_store.ViewStoreBinaryTree } -func NewHotshotListener(hotshotUrl string, rollupSequencerManagerContract string, l1Client *ethclient.Client, sequencerAddress string) (*HotshotListener, error) { +func NewHotshotListener(hotshotUrl string, rollupSequencerManagerContract string, l1Client *ethclient.Client, sequencerAddress string, chainId uint32, execution *gethexec.ExecutionEngine) (*HotshotListener, error) { if hotshotUrl == "" { return nil, fmt.Errorf("hotshot url is empty, please provide a valid url") @@ -55,13 +67,18 @@ func NewHotshotListener(hotshotUrl string, rollupSequencerManagerContract string return nil, err } + // Create a new view store binary tree + // Create a new rollup sequencer manager contract instance return &HotshotListener{ hotshotUrl: hotshotUrl + HotshotListenerEndpoint, rollupSequencerManager: rollupSequencerManager, - quorumViewNumberBuilderCommitment: make(map[string]big.Int), - daViewNumberBuilderCommitment: make(map[string]bool), + quorumViewNumberBuilderCommitment: make(map[string]*big.Int), + daViewNumberBuilderCommitment: make(map[string]*types.BlockPayload), sequencerAddress: sequencerAddress, + chainId: chainId, + execution: execution, + root: nil, }, nil } @@ -108,7 +125,7 @@ func (listener *HotshotListener) processQuorumProposalEvent(quorumProposalWrappe l1FinalizedBlockNumberForView := quorumProposalWrapper.QuorumProposalDataWrapper.Data.Proposal.BlockHeader.Fields.L1Finalized.Number l1FinalizedBlockNumberBigInt := big.NewInt(int64(l1FinalizedBlockNumberForView)) // Store the finalized L1 block number in the map - listener.quorumViewNumberBuilderCommitment[key] = *l1FinalizedBlockNumberBigInt + listener.quorumViewNumberBuilderCommitment[key] = l1FinalizedBlockNumberBigInt // Check if a da commitment exists for the key relative to // this quorum proposal view number and builder commitment @@ -119,24 +136,20 @@ func (listener *HotshotListener) processQuorumProposalEvent(quorumProposalWrappe log.Info("processing builder commitment and view number", "viewNumber", viewNumber, "builderCommitment", builderCommitment) // Get the sequencer address for the next view - nextView := viewNumber + 1 - // Note: Its important to use l1 finalized block number here because we want the GetCurrentSequencer to - // always return the same sequencer address for the same view number - sequencerAddressForNextView, err := listener.rollupSequencerManager.GetCurrentSequencer(&bind.CallOpts{ - BlockNumber: l1FinalizedBlockNumberBigInt, - }, big.NewInt(int64(nextView))) + // #nosec G115 + err := listener.processHotshotCurrentView(listener.daViewNumberBuilderCommitment[key], uint64(viewNumber), builderCommitment) if err != nil { - log.Error("failed to get current sequencer", "err", err) + log.Error("failed to process current view", "err", err) return err } - if sequencerAddressForNextView.Hex() == listener.sequencerAddress { - log.Info("next view is this node's view", "nextView", nextView, "sequencerAddress", listener.sequencerAddress) - // TODO: Processing will be implemented in the next PR + // #nosec G115 + err = listener.processHotshotNextView(l1FinalizedBlockNumberBigInt, uint64(viewNumber)) + if err != nil { + log.Error("failed to process next view", "err", err) + return err } - // TODO: Processing will be implemented in the next PR - // Delete the quorum and da proposal keys from the map // so that map doesnt take a lot of space in memory delete(listener.quorumViewNumberBuilderCommitment, key) @@ -171,7 +184,7 @@ func (listener *HotshotListener) processDaProposalEvent(daProposalWrapper *types key := viewNumberString + builderCommitmentString // Now store the key and check if a quorum proposal exists for the given builder commitment - listener.daViewNumberBuilderCommitment[key] = true + listener.daViewNumberBuilderCommitment[key] = blockPayload // Check if a da commitment exists for this key // relative to this DA proposal view number and builder commitment if _, ok := listener.quorumViewNumberBuilderCommitment[key]; !ok { @@ -185,25 +198,23 @@ func (listener *HotshotListener) processDaProposalEvent(daProposalWrapper *types // Get L1 block number from the quorum proposal map l1FinalizedBlockNumberForView := listener.quorumViewNumberBuilderCommitment[key] - nextView := viewNumber + 1 - // Note: Its important to use l1 finalized block number here because we want the GetCurrentSequencer to - // always return the same sequencer address for the same view number - sequencerAddressForNextView, err := listener.rollupSequencerManager.GetCurrentSequencer(&bind.CallOpts{ - BlockNumber: &l1FinalizedBlockNumberForView, - }, big.NewInt(int64(nextView))) + if l1FinalizedBlockNumberForView == nil { + log.Error("l1 finalized block number for view is nil") + return nil + } + + // #nosec G115 + err = listener.processHotshotCurrentView(listener.daViewNumberBuilderCommitment[key], uint64(viewNumber), builderCommitmentString) if err != nil { - log.Error("failed to get sequencer address for next view", "err", err) + log.Error("failed to process current view", "err", err) return err } - - // Check if the sequencer address is the same address of this node - if sequencerAddressForNextView.Hex() != listener.sequencerAddress { - log.Info("next view is not this node's view") - // TODO: Processing will be implemented in the next PR + err = listener.processHotshotNextView(l1FinalizedBlockNumberForView, uint64(viewNumber)) + if err != nil { + log.Error("failed to process next view", "err", err) + return err } - // TODO: Processing will be implemented in the next PR - // Delate the quorum and da proposal keys from the map // so that map doesnt take a lot of space in memory delete(listener.quorumViewNumberBuilderCommitment, key) @@ -220,8 +231,135 @@ func (listener *HotshotListener) processDecideEvent(decide *types.Decide) error builderCommitment := leafChain.Leaf.BlockHeader.Fields.BuilderCommitment log.Info("processing leaf chain", "leafChain", leafChain, "builderCommitment", builderCommitment, "viewNumber", viewNumber) // TODO: Processing will be implemented in the next PR + } + return nil +} +func (listener *HotshotListener) processHotshotNextView(l1FinalizedBlockNumberBigInt *big.Int, viewNumber uint64) error { + nextView := viewNumber + 1 + + // Note: Its important to use l1 finalized block number here because we want the GetCurrentSequencer to + // always return the same sequencer address for the same view number + sequencerAddressForNextView, err := listener.rollupSequencerManager.GetCurrentSequencer(&bind.CallOpts{ + BlockNumber: l1FinalizedBlockNumberBigInt, + }, big.NewInt(int64(nextView))) + if err != nil { + log.Error("failed to get current sequencer", "err", err) + return err + } + + if sequencerAddressForNextView.Hex() != listener.sequencerAddress { + // TODO: Processing will be implemented in the next PR + return nil } + log.Info("next view is this node's view", "nextView", nextView, "sequencerAddress", listener.sequencerAddress) + // TODO: Processing will be implemented in the next PR + return nil +} + +func (listener *HotshotListener) processHotshotCurrentView(blockPayload *types.BlockPayload, viewNumber uint64, builderCommitment string) error { + log.Info("received hotshot view event") + // Decode the block payload + encodedTransactions := blockPayload.RawPayload + + nsRange, err := utils.DecodeNSTable(blockPayload.NsTable.Bytes, listener.chainId) + if err != nil { + return err + } + + if nsRange == nil { + return err + } + + transactionsPayload, err := utils.DecodeTransactionsPayload(encodedTransactions, *nsRange) + if err != nil { + return err + } + + for _, tx := range transactionsPayload { + _, _, indices, messages, err := arbutil.ParseHotShotPayload(tx.Payload) + if err != nil { + return err + } + if len(messages) == 0 { + return nil + } + + err = listener.processMessages(messages, indices) + if err != nil { + log.Error("failed to process messages", "err", err) + return err + } + } + + // Now add the state hash to the view store + lastBlockHeader := listener.execution.Bc().CurrentBlock().Hash() + + // Update the view store + viewStoreBinaryTree := view_store.Insert(listener.root, viewNumber, builderCommitment, lastBlockHeader) + listener.root = viewStoreBinaryTree + log.Info("view store updated", "viewStore", viewStoreBinaryTree) + return nil +} + +func (listener *HotshotListener) processMessages(messages [][]byte, indices []uint64) error { + + // Now we need to convert the transaction payload to a nitro block and submit it to the sequencer + currentBlockNumber := listener.execution.Bc().CurrentBlock().Number.Uint64() + + for i, message := range messages { + var messageWithMetadata arbostypes.MessageWithMetadata + err := rlp.DecodeBytes(message, &messageWithMetadata) + if err != nil { + log.Warn("failed to decode message", "err", err) + // Instead of returnning an error, we should just skip this message + continue + } + + if indices[i] <= currentBlockNumber { + log.Warn("message index is less than current message pos, skipping", "messageIndex", indices[i], "currentMessagePos", currentBlockNumber) + continue + } + err = listener.createBlock(&messageWithMetadata) + if err != nil { + log.Error("unable to create block", "err", err) + return err + } + } + + // At the end store the state hash in th view store + + return nil +} + +func (listener *HotshotListener) createBlock(msg *arbostypes.MessageWithMetadata) error { + + lastBlockHeader := listener.execution.Bc().CurrentBlock() + + statedb, err := listener.execution.Bc().StateAt(lastBlockHeader.Root) + if err != nil { + log.Error("failed to get state at last block header", "err", err) + return err + } + + startTime := time.Now() + block, receipts, err := arbos.ProduceBlock(msg.Message, msg.DelayedMessagesRead, lastBlockHeader, statedb, listener.execution.Bc(), false, core.MessageReplayMode) + + if err != nil || block == nil { + log.Error("Failed to produce block", "err", err) + return err + } + + blockCalcTime := time.Since(startTime) + + log.Info("Produced block", "block", block.Hash(), "blockNumber", block.Number(), "receipts", len(receipts)) + + err = listener.execution.AppendBlock(block, statedb, receipts, blockCalcTime) + if err != nil { + log.Error("Failed to append block", "err", err) + return err + } + return nil } diff --git a/espresso/submitter/polling_espresso_submitter.go b/espresso/submitter/polling_espresso_submitter.go index fb654e08068..6b5d3e1f795 100644 --- a/espresso/submitter/polling_espresso_submitter.go +++ b/espresso/submitter/polling_espresso_submitter.go @@ -352,6 +352,7 @@ func (s *PollingEspressoSubmitter) ResubmitEspressoTransactions(ctx context.Cont Payload: tx.Payload, Namespace: s.chainID, }) + if err != nil { return nil, err } @@ -425,6 +426,9 @@ func (s *PollingEspressoSubmitter) submitEspressoTransactions(ctx context.Contex Namespace: s.chainID, }) + log.Info("Submitted payload is", "payload", payload) + log.Info("Submitted namespace is", "namespace", s.chainID) + if err != nil { return fmt.Errorf("failed to submit transaction to espresso: %w", err) } diff --git a/espresso/utils/utils.go b/espresso/utils/utils.go new file mode 100644 index 00000000000..414c080d955 --- /dev/null +++ b/espresso/utils/utils.go @@ -0,0 +1,99 @@ +package utils + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + + "github.com/EspressoSystems/espresso-network/sdks/go/types" +) + +const ( + NUM_NSS_BYTE_LEN = 4 // num entries field + NS_ID_BYTE_LEN = 4 // ns_id is u32 + OFFSET_BYTE_LEN = 4 // offset is u32 + ENTRY_BYTE_LEN = NS_ID_BYTE_LEN + OFFSET_BYTE_LEN + LEN_SIZE = 4 +) + +type NSRange struct { + NsId uint32 + Start uint32 + End uint32 +} + +func DecodeTransactionsPayload(encoded []byte, nsRange NSRange) ([]types.Transaction, error) { + + if len(encoded) < int(nsRange.End) { + return nil, fmt.Errorf("encoded payload is smaller than the end of the ns range") + } + nsEncodedPayload := encoded[nsRange.Start:nsRange.End] + r := bytes.NewReader(nsEncodedPayload) + + // Step 1: num_txs + numTxns := make([]uint8, 4) + if _, err := io.ReadFull(r, numTxns); err != nil { + return nil, fmt.Errorf("read num_txs: %w", err) + } + numTxs := binary.LittleEndian.Uint32(numTxns) + + // Step 2: offsets + offsets := make([]uint32, numTxs) + for i := range offsets { + if err := binary.Read(r, binary.LittleEndian, &offsets[i]); err != nil { + return nil, fmt.Errorf("read offset[%d]: %w", i, err) + } + } + + // Step 3: tx_bodies = remaining bytes + txBodies, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("read tx_bodies: %w", err) + } + + // Step 4: reconstruct transactions + var txs []types.Transaction + start := uint32(0) + for i := 0; i < int(numTxs); i++ { + end := offsets[i] + txBytes := txBodies[start:end] + + txs = append(txs, types.Transaction{ + Payload: txBytes, + }) + start = end + } + + return txs, nil +} + +func DecodeNSTable(encoded []byte, namespaceId uint32) (*NSRange, error) { + if len(encoded) < NUM_NSS_BYTE_LEN { + return nil, fmt.Errorf("ns table too short: %d bytes", len(encoded)) + } + + numEntries := binary.LittleEndian.Uint32(encoded[:NUM_NSS_BYTE_LEN]) + + off := NUM_NSS_BYTE_LEN + + start := uint32(0) + + for i := uint32(0); i < numEntries; i++ { + nsId := binary.LittleEndian.Uint32(encoded[off : off+NS_ID_BYTE_LEN]) + off += NS_ID_BYTE_LEN + offset := binary.LittleEndian.Uint32(encoded[off : off+OFFSET_BYTE_LEN]) + off += OFFSET_BYTE_LEN + + if namespaceId == nsId { + return &NSRange{ + NsId: namespaceId, + End: offset, + Start: start, + }, nil + } + start = offset + } + + return nil, fmt.Errorf("ns table not found") +} diff --git a/espresso/view-store/view_store_binary_tree.go b/espresso/view-store/view_store_binary_tree.go index f077e8949fd..25790643cc5 100644 --- a/espresso/view-store/view_store_binary_tree.go +++ b/espresso/view-store/view_store_binary_tree.go @@ -14,7 +14,7 @@ type View struct { // storing the state hashes of the nitro state // at a given view number and payload commitment type ViewStoreBinaryTree struct { - view View + View View Left *ViewStoreBinaryTree Right *ViewStoreBinaryTree } @@ -27,27 +27,27 @@ all the views with a view number and builder commitment greater than the root's func Insert(root *ViewStoreBinaryTree, viewNumber uint64, builderCommitment string, stateHash common.Hash) *ViewStoreBinaryTree { if root == nil { return &ViewStoreBinaryTree{ - view: View{ + View: View{ viewNumber: viewNumber, builderCommitment: builderCommitment, stateHash: stateHash, }, } } - if viewNumber < root.view.viewNumber { + if viewNumber < root.View.viewNumber { root.Left = Insert(root.Left, viewNumber, builderCommitment, stateHash) return root - } else if viewNumber > root.view.viewNumber { + } else if viewNumber > root.View.viewNumber { root.Right = Insert(root.Right, viewNumber, builderCommitment, stateHash) return root } // This means that view numbers is equal to the root's view number // Builder commitment might be different so we insert based on that now - if builderCommitment < root.view.builderCommitment { + if builderCommitment < root.View.builderCommitment { root.Left = Insert(root.Left, viewNumber, builderCommitment, stateHash) return root - } else if builderCommitment > root.view.builderCommitment { + } else if builderCommitment > root.View.builderCommitment { root.Right = Insert(root.Right, viewNumber, builderCommitment, stateHash) return root } @@ -63,16 +63,16 @@ func Search(root *ViewStoreBinaryTree, viewNumber uint64, builderCommitment stri if root == nil { return nil } - if viewNumber < root.view.viewNumber { + if viewNumber < root.View.viewNumber { return Search(root.Left, viewNumber, builderCommitment) - } else if viewNumber > root.view.viewNumber { + } else if viewNumber > root.View.viewNumber { return Search(root.Right, viewNumber, builderCommitment) } - if builderCommitment == root.view.builderCommitment { + if builderCommitment == root.View.builderCommitment { return root - } else if builderCommitment < root.view.builderCommitment { + } else if builderCommitment < root.View.builderCommitment { return Search(root.Left, viewNumber, builderCommitment) - } else if builderCommitment > root.view.builderCommitment { + } else if builderCommitment > root.View.builderCommitment { return Search(root.Right, viewNumber, builderCommitment) } return root @@ -84,7 +84,7 @@ func Delete(root *ViewStoreBinaryTree, cutoff uint64) *ViewStoreBinaryTree { return nil } - if root.view.viewNumber > cutoff { + if root.View.viewNumber > cutoff { // Keep this node; only the left subtree can contain nodes to delete. root.Left = Delete(root.Left, cutoff) return root diff --git a/espresso/view-store/view_store_binary_tree_test.go b/espresso/view-store/view_store_binary_tree_test.go index e3724418eeb..efa00e3b2dd 100644 --- a/espresso/view-store/view_store_binary_tree_test.go +++ b/espresso/view-store/view_store_binary_tree_test.go @@ -27,15 +27,15 @@ func TestEspressoViewStoreInsert(t *testing.T) { viewStoreForViewNumvber0 := Search(root, 0, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_") // Check that left child has view number 0 - if viewStoreForViewNumvber2.Left.view.viewNumber != 0 { - t.Errorf("Expected left child's view number to be 0, got %d", viewStoreForViewNumvber2.Left.view.viewNumber) + if viewStoreForViewNumvber2.Left.View.viewNumber != 0 { + t.Errorf("Expected left child's view number to be 0, got %d", viewStoreForViewNumvber2.Left.View.viewNumber) } - if viewStoreForViewNumvber0.view.viewNumber != 0 { - t.Errorf("Expected root's view number to be 0, got %d", viewStoreForViewNumvber0.view.viewNumber) + if viewStoreForViewNumvber0.View.viewNumber != 0 { + t.Errorf("Expected root's view number to be 0, got %d", viewStoreForViewNumvber0.View.viewNumber) } - if viewStoreForViewNumvber0.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { - t.Errorf("Expected root's builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumvber0.view.builderCommitment) + if viewStoreForViewNumvber0.View.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected root's builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumvber0.View.builderCommitment) } }) @@ -50,11 +50,11 @@ func TestEspressoViewStoreInsert(t *testing.T) { viewStoreForViewNumber7 := Search(root, 7, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgC_") // Check that the right side of view store 7 is the inserted view - if viewStoreForViewNumber7.Right.view.viewNumber != 9 { - t.Errorf("Expected right side of view store 7 to be 9, got %d", viewStoreForViewNumber7.Right.view.viewNumber) + if viewStoreForViewNumber7.Right.View.viewNumber != 9 { + t.Errorf("Expected right side of view store 7 to be 9, got %d", viewStoreForViewNumber7.Right.View.viewNumber) } - if viewStoreForViewNumber7.Right.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { - t.Errorf("Expected right side of view store 7 to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber7.Right.view.builderCommitment) + if viewStoreForViewNumber7.Right.View.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected right side of view store 7 to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber7.Right.View.builderCommitment) } }) @@ -70,11 +70,11 @@ func TestEspressoViewStoreInsert(t *testing.T) { viewStoreFor7ViewNumber := Search(root, 7, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgC_") // Check that the lower builder commitment view store 5 exists on the left side of view store 7 - if viewStoreFor7ViewNumber.Left.view.viewNumber != 5 { - t.Errorf("Expected left side of view store for view number 7 to have view number 5, got %d", viewStoreFor7ViewNumber.Left.view.viewNumber) + if viewStoreFor7ViewNumber.Left.View.viewNumber != 5 { + t.Errorf("Expected left side of view store for view number 7 to have view number 5, got %d", viewStoreFor7ViewNumber.Left.View.viewNumber) } - if viewStoreFor7ViewNumber.Left.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { - t.Errorf("Expected left side of view store for view number 7 to have builder commitment BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber5HigherBuilderCommitment.Left.view.builderCommitment) + if viewStoreFor7ViewNumber.Left.View.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected left side of view store for view number 7 to have builder commitment BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber5HigherBuilderCommitment.Left.View.builderCommitment) } }) @@ -87,11 +87,11 @@ func TestEspressoViewStoreInsert(t *testing.T) { viewStoreFor2ViewNumber := Search(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_") // Check that the lower builder commitment view store 5 exists on the left side of view store 7 - if viewStoreFor2ViewNumber.Right.view.viewNumber != 5 { - t.Errorf("Expected right side of view store for view number 2 to have view number 5, got %d", viewStoreFor2ViewNumber.Left.view.viewNumber) + if viewStoreFor2ViewNumber.Right.View.viewNumber != 5 { + t.Errorf("Expected right side of view store for view number 2 to have view number 5, got %d", viewStoreFor2ViewNumber.Left.View.viewNumber) } - if viewStoreFor2ViewNumber.Right.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_" { - t.Errorf("Expected right side of view store for view number 2 to have builder commitment BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_, got %s", viewStoreFor2ViewNumber.Left.view.builderCommitment) + if viewStoreFor2ViewNumber.Right.View.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_" { + t.Errorf("Expected right side of view store for view number 2 to have builder commitment BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_, got %s", viewStoreFor2ViewNumber.Left.View.builderCommitment) } }) @@ -107,11 +107,11 @@ func TestEspressoViewStoreSearch(t *testing.T) { // Search for view number 2 viewStoreForViewNumber0 := Search(root, 0, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_") - if viewStoreForViewNumber0.view.viewNumber != 0 { - t.Errorf("Expected root's view number to be 0, got %d", viewStoreForViewNumber0.view.viewNumber) + if viewStoreForViewNumber0.View.viewNumber != 0 { + t.Errorf("Expected root's view number to be 0, got %d", viewStoreForViewNumber0.View.viewNumber) } - if viewStoreForViewNumber0.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { - t.Errorf("Expected root's builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber0.view.builderCommitment) + if viewStoreForViewNumber0.View.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected root's builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber0.View.builderCommitment) } }) @@ -122,11 +122,11 @@ func TestEspressoViewStoreSearch(t *testing.T) { // Search for view number 2 viewStoreForViewNumber2 := Search(root, 10, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_") - if viewStoreForViewNumber2.view.viewNumber != 10 { - t.Errorf("Expected right child's view number to be 10, got %d", viewStoreForViewNumber2.Right.view.viewNumber) + if viewStoreForViewNumber2.View.viewNumber != 10 { + t.Errorf("Expected right child's view number to be 10, got %d", viewStoreForViewNumber2.Right.View.viewNumber) } - if viewStoreForViewNumber2.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_" { - t.Errorf("Expected right child's builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_, got %s", viewStoreForViewNumber2.Right.view.builderCommitment) + if viewStoreForViewNumber2.View.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_" { + t.Errorf("Expected right child's builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgB_, got %s", viewStoreForViewNumber2.Right.View.builderCommitment) } }) @@ -140,11 +140,11 @@ func TestEspressoViewStoreSearch(t *testing.T) { // Search for view number 2 viewStoreForViewNumber2 := Search(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_") - if viewStoreForViewNumber2.view.viewNumber != 2 { - t.Errorf("Expected view number to be 2, got %d", viewStoreForViewNumber2.Right.view.viewNumber) + if viewStoreForViewNumber2.View.viewNumber != 2 { + t.Errorf("Expected view number to be 2, got %d", viewStoreForViewNumber2.Right.View.viewNumber) } - if viewStoreForViewNumber2.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { - t.Errorf("Expected builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber2.Right.view.builderCommitment) + if viewStoreForViewNumber2.View.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_" { + t.Errorf("Expected builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgF_, got %s", viewStoreForViewNumber2.Right.View.builderCommitment) } }) @@ -158,11 +158,11 @@ func TestEspressoViewStoreSearch(t *testing.T) { // Search for view number 2 viewStoreForViewNumber2 := Search(root, 2, "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_") - if viewStoreForViewNumber2.view.viewNumber != 2 { - t.Errorf("Expected view number to be 2, got %d", viewStoreForViewNumber2.view.viewNumber) + if viewStoreForViewNumber2.View.viewNumber != 2 { + t.Errorf("Expected view number to be 2, got %d", viewStoreForViewNumber2.View.viewNumber) } - if viewStoreForViewNumber2.view.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_" { - t.Errorf("Expected builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_, got %s", viewStoreForViewNumber2.view.builderCommitment) + if viewStoreForViewNumber2.View.builderCommitment != "BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_" { + t.Errorf("Expected builder commitment to be BUILDER_COMMITMENT~tEvs0rxqOiMCvfe2R0omNNaphSlUiEDrb2q0IZpRcgA_, got %s", viewStoreForViewNumber2.View.builderCommitment) } }) @@ -223,8 +223,8 @@ func TestViewStoreDelete(t *testing.T) { root = Delete(root, 7) // root view number should be 10 - if root.view.viewNumber != 10 { - t.Errorf("Expected root view number to be 10, got %d", root.view.viewNumber) + if root.View.viewNumber != 10 { + t.Errorf("Expected root view number to be 10, got %d", root.View.viewNumber) } // Search and there should be no view with view number 7 @@ -271,8 +271,8 @@ func TestViewStoreDelete(t *testing.T) { root = Delete(root, 8) // root view number should be 10 - if root.view.viewNumber != 10 { - t.Errorf("Expected root view number to be 10, got %d", root.view.viewNumber) + if root.View.viewNumber != 10 { + t.Errorf("Expected root view number to be 10, got %d", root.View.viewNumber) } }) diff --git a/system_tests/hotshot_listener_test.go b/system_tests/hotshot_listener_test.go index 25ba78a69e6..0a9cf36193a 100644 --- a/system_tests/hotshot_listener_test.go +++ b/system_tests/hotshot_listener_test.go @@ -43,7 +43,8 @@ func TestEspressoHotshotListener(t *testing.T) { rollupSequencerManagerAddress := builder.L1Info.GetAddress("RollupSequencerManager") // Get Sequencer Address sequencerAddress := builder.L1Info.GetAddress("Sequencer") - listener, err := hotshot_listener.NewHotshotListener(SEQUENCER_API_WEBSOCKERT_URL, rollupSequencerManagerAddress.Hex(), builder.L1.Client, sequencerAddress.Hex()) + // TODO: fix this + listener, err := hotshot_listener.NewHotshotListener(SEQUENCER_API_WEBSOCKERT_URL, rollupSequencerManagerAddress.Hex(), builder.L1.Client, sequencerAddress.Hex(), uint32(builder.L2Info.Signer.ChainID().Uint64()), builder.L2.ExecNode.ExecEngine) if err != nil { t.Fatal(err) } @@ -54,6 +55,13 @@ func TestEspressoHotshotListener(t *testing.T) { t.Fatal(err) } + err = checkTransferTxOnL2(t, ctx, builder.L2, "User14", builder.L2Info) + Require(t, err) + err = checkTransferTxOnL2(t, ctx, builder.L2, "User15", builder.L2Info) + Require(t, err) + err = checkTransferTxOnL2(t, ctx, builder.L2, "User16", builder.L2Info) + Require(t, err) + // Note: These are rudimentary tests to check if the initial basic functionality // of the listener works. These tests will be modified in the future to include more processing checks From d33e22a3e883797b98c3eb0801c25678db65f6a9 Mon Sep 17 00:00:00 2001 From: Sneh Koul Date: Wed, 3 Sep 2025 18:10:13 -0400 Subject: [PATCH 5/5] fix decoding --- espresso/utils/utils.go | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/espresso/utils/utils.go b/espresso/utils/utils.go index 414c080d955..c61855fbd0c 100644 --- a/espresso/utils/utils.go +++ b/espresso/utils/utils.go @@ -7,6 +7,7 @@ import ( "io" "github.com/EspressoSystems/espresso-network/sdks/go/types" + "github.com/ethereum/go-ethereum/log" ) const ( @@ -15,6 +16,7 @@ const ( OFFSET_BYTE_LEN = 4 // offset is u32 ENTRY_BYTE_LEN = NS_ID_BYTE_LEN + OFFSET_BYTE_LEN LEN_SIZE = 4 + NUM_TXS_BYTE_LEN = 4 ) type NSRange struct { @@ -29,23 +31,27 @@ func DecodeTransactionsPayload(encoded []byte, nsRange NSRange) ([]types.Transac return nil, fmt.Errorf("encoded payload is smaller than the end of the ns range") } nsEncodedPayload := encoded[nsRange.Start:nsRange.End] + r := bytes.NewReader(nsEncodedPayload) // Step 1: num_txs - numTxns := make([]uint8, 4) - if _, err := io.ReadFull(r, numTxns); err != nil { - return nil, fmt.Errorf("read num_txs: %w", err) + var numTxs uint32 + err := binary.Read(r, binary.LittleEndian, &numTxs) + if err != nil { + return nil, fmt.Errorf("read num txs: %w", err) } - numTxs := binary.LittleEndian.Uint32(numTxns) // Step 2: offsets offsets := make([]uint32, numTxs) for i := range offsets { if err := binary.Read(r, binary.LittleEndian, &offsets[i]); err != nil { - return nil, fmt.Errorf("read offset[%d]: %w", i, err) + return nil, fmt.Errorf("read offsets: %w", err) } } + log.Info("num txs of decode transactions", "numTxs", numTxs) + log.Info("offsets of decode transactions", "offsets", offsets) + // Step 3: tx_bodies = remaining bytes txBodies, err := io.ReadAll(r) if err != nil { @@ -56,15 +62,18 @@ func DecodeTransactionsPayload(encoded []byte, nsRange NSRange) ([]types.Transac var txs []types.Transaction start := uint32(0) for i := 0; i < int(numTxs); i++ { - end := offsets[i] + end := start + offsets[i] txBytes := txBodies[start:end] - + log.Info("start of decode transactions", "start", start) + log.Info("end of decode transactions", "end", end) + log.Info("txBytes of decode transactions", "txBytes", txBytes) txs = append(txs, types.Transaction{ Payload: txBytes, }) start = end } + log.Info("transaction after decoding", "txs", txs) return txs, nil } @@ -85,15 +94,25 @@ func DecodeNSTable(encoded []byte, namespaceId uint32) (*NSRange, error) { offset := binary.LittleEndian.Uint32(encoded[off : off+OFFSET_BYTE_LEN]) off += OFFSET_BYTE_LEN + end := start + offset + if namespaceId == nsId { + log.Info("nsId of ns table", "nsId", nsId) + log.Info("offset of ns table", "offset", offset) + log.Info("end of ns table", "end", end) + log.Info("start of ns table", "start", start) return &NSRange{ NsId: namespaceId, - End: offset, + End: end, Start: start, }, nil } - start = offset + start = end } return nil, fmt.Errorf("ns table not found") } + +// 0...250 +// 250..350 +//