Skip to content

Commit

Permalink
Merge branch 'main' into feat/add-trie-db
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-romano authored Nov 19, 2024
2 parents a6c3a9f + 0cfd720 commit 0ae936b
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 139 deletions.
37 changes: 16 additions & 21 deletions internal/merkle/mountain_ranges/mmr.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,36 @@ import (
"github.com/eigerco/strawberry/pkg/serialization/codec"
)

type MMR struct {
peaks []*crypto.Hash // nil represents empty peak (Ø)
}
type MMR struct{}

func New() *MMR {
return &MMR{peaks: []*crypto.Hash{}}
return &MMR{}
}

// Append (A function): A(r, l, H) ↦ P(r, l, 0, H)
func (m *MMR) Append(item []byte, hashFunc func([]byte) crypto.Hash) error {
hash := hashFunc(item)
m.peaks = placePeak(m.peaks, &hash, 0, hashFunc)
return nil
// Append (A function): A(r, l, H) ↦ P(r, l, 0, H) from equation (327)
func (m *MMR) Append(r []*crypto.Hash, l crypto.Hash, hashFunc func([]byte) crypto.Hash) []*crypto.Hash {
hash := hashFunc(l[:])
return placePeak(r, &hash, 0, hashFunc)
}

// placePeak implements P function from equation (327):
// P(r, l, n, H) ↦ { r ++ l if n ≥ |r|
// P(r, l, n, H) ↦ { r ++ l if n ≥ |r|
//
// { R(r, n, l) if n < |r| ∧ rₙ = Ø
// { R(r, n, l) if n < |r| ∧ rₙ = Ø
// { P(R(r, n, Ø), H(rₙ ~ l), n + 1, H) otherwise
func placePeak(peaks []*crypto.Hash, item *crypto.Hash, position int, hashFunc func([]byte) crypto.Hash) []*crypto.Hash {
if position >= len(peaks) {
// r ++ l case
return append(peaks, item)
}

if position < len(peaks) && peaks[position] == nil {
if peaks[position] == nil {
// R(r, n, l) case
return replacePeakAt(peaks, position, item)
}

combined := append(peaks[position][:], item[:]...)
// P(R(r, n, Ø), H(rₙ ~ l), n + 1, H) case
combined := append((*peaks[position])[:], (*item)[:]...)
hash := hashFunc(combined)
return placePeak(replacePeakAt(peaks, position, nil), &hash, position+1, hashFunc)
}
Expand All @@ -52,18 +52,13 @@ func replacePeakAt(peaks []*crypto.Hash, index int, value *crypto.Hash) []*crypt
}

// Encode implements E_M function from equation (328):
func (m *MMR) Encode() ([]byte, error) {
func (m *MMR) Encode(peaks []*crypto.Hash) ([]byte, error) {
jamCodec := codec.NewJamCodec()
serializer := serialization.NewSerializer(jamCodec)
encoded, err := serializer.Encode(m.peaks)

encoded, err := serializer.Encode(peaks)
if err != nil {
return nil, err
}
return encoded, nil
}

func (m *MMR) GetPeaks() []*crypto.Hash {
result := make([]*crypto.Hash, len(m.peaks))
copy(result, m.peaks)
return result
}
153 changes: 35 additions & 118 deletions internal/merkle/mountain_ranges/mmr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ package mountain_ranges

import (
"github.com/eigerco/strawberry/internal/crypto"
"github.com/eigerco/strawberry/internal/merkle/binary_tree/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func mockHashData(data []byte) crypto.Hash {
var h crypto.Hash
copy(h[:], data)
return h
}

func TestMMR(t *testing.T) {
tests := []struct {
name string
Expand All @@ -16,158 +22,69 @@ func TestMMR(t *testing.T) {
{
name: "empty",
toAppend: [][]byte{},
expectedPeaks: []*crypto.Hash{},
expectedPeaks: make([]*crypto.Hash, 0), // Initialize properly
},
{
name: "single_item",
toAppend: [][]byte{[]byte("1")},
expectedPeaks: func() []*crypto.Hash {
h := testutils.MockHashData([]byte("1"))
h := mockHashData([]byte("1"))
return []*crypto.Hash{&h}
}(),
},
{
name: "two_items",
toAppend: [][]byte{
[]byte("1"),
[]byte("2"),
},
name: "two_items",
toAppend: [][]byte{[]byte("1"), []byte("2")},
expectedPeaks: func() []*crypto.Hash {
h1 := testutils.MockHashData([]byte("1"))
h2 := testutils.MockHashData([]byte("2"))
h1 := mockHashData([]byte("1"))
h2 := mockHashData([]byte("2"))
combined := append(h1[:], h2[:]...)
hash := testutils.MockHashData(combined)
hash := mockHashData(combined)
return []*crypto.Hash{
nil,
&hash,
}
}(),
},
{
name: "three_items",
toAppend: [][]byte{
[]byte("1"),
[]byte("2"),
[]byte("3"),
},
name: "three_items",
toAppend: [][]byte{[]byte("1"), []byte("2"), []byte("3")},
expectedPeaks: func() []*crypto.Hash {
h1 := testutils.MockHashData([]byte("1"))
h2 := testutils.MockHashData([]byte("2"))
h1 := mockHashData([]byte("1"))
h2 := mockHashData([]byte("2"))
combined := append(h1[:], h2[:]...)
h12 := testutils.MockHashData(combined)
h3 := testutils.MockHashData([]byte("3"))
return []*crypto.Hash{
&h3,
&h12,
}
}(),
},
{
name: "four_items",
toAppend: [][]byte{
[]byte("1"),
[]byte("2"),
[]byte("3"),
[]byte("4"),
},
expectedPeaks: func() []*crypto.Hash {
h1 := testutils.MockHashData([]byte("1"))
h2 := testutils.MockHashData([]byte("2"))
combined12 := append(h1[:], h2[:]...)
h12 := testutils.MockHashData(combined12)
h3 := testutils.MockHashData([]byte("3"))
h4 := testutils.MockHashData([]byte("4"))
combined34 := append(h3[:], h4[:]...)
h34 := testutils.MockHashData(combined34)
combined := append(h12[:], h34[:]...)
hash := testutils.MockHashData(combined)
return []*crypto.Hash{
nil,
nil,
&hash,
}
h12 := mockHashData(combined)
h3 := mockHashData([]byte("3"))
return []*crypto.Hash{&h3, &h12}
}(),
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mmr := New()
peaks := make([]*crypto.Hash, 0)

// Append all items
for _, item := range tc.toAppend {
err := mmr.Append(item, testutils.MockHashData)
assert.NoError(t, err)
hash := mockHashData(item)
peaks = mmr.Append(peaks, hash, mockHashData)
}

// Check peaks
assert.Equal(t, tc.expectedPeaks, mmr.GetPeaks())
})
}
}

func TestEncode(t *testing.T) {
tests := []struct {
name string
toAppend [][]byte
}{
{
name: "empty",
toAppend: [][]byte{},
},
{
name: "multiple_items",
toAppend: [][]byte{
[]byte("1"),
[]byte("2"),
[]byte("3"),
[]byte("4"),
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mmr := New()
// Compare slices length
assert.Equal(t, len(tc.expectedPeaks), len(peaks))

for _, item := range tc.toAppend {
err := mmr.Append(item, testutils.MockHashData)
assert.NoError(t, err)
// Compare individual hash values
for i := range peaks {
if tc.expectedPeaks[i] == nil {
assert.Nil(t, peaks[i])
} else {
assert.Equal(t, *tc.expectedPeaks[i], *peaks[i])
}
}

encoded, err := mmr.Encode()

assert.NoError(t, err)
encoded, err := mmr.Encode(peaks)
require.NoError(t, err)
assert.NotNil(t, encoded)
})
}
}

func TestAppendIdempotent(t *testing.T) {
mmr1 := New()
mmr2 := New()

items := [][]byte{[]byte("1"), []byte("2"), []byte("3")}

// Append to first MMR in sequence
for _, item := range items {
err := mmr1.Append(item, testutils.MockHashData)
assert.NoError(t, err)
}

// Append same items to second MMR
for _, item := range items {
err := mmr2.Append(item, testutils.MockHashData)
assert.NoError(t, err)
}

// Both should have identical peaks
assert.Equal(t, mmr1.GetPeaks(), mmr2.GetPeaks())

// Both should encode to same value
encoded1, err := mmr1.Encode()
assert.NoError(t, err)
encoded2, err := mmr2.Encode()
assert.NoError(t, err)
assert.Equal(t, encoded1, encoded2)
}

0 comments on commit 0ae936b

Please sign in to comment.