Skip to content

Commit

Permalink
Merge branch 'refs/heads/shwap' into update-file-interface
Browse files Browse the repository at this point in the history
# Conflicts:
#	share/share.go
#	share/shwap/namespaced_data.go
#	share/shwap/row.go
  • Loading branch information
walldiss committed May 24, 2024
2 parents 968d155 + 4ff30e8 commit 7d3f5a7
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 11 deletions.
48 changes: 48 additions & 0 deletions share/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package share

import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"

appns "github.com/celestiaorg/celestia-app/pkg/namespace"
Expand Down Expand Up @@ -182,3 +184,49 @@ func (n Namespace) IsGreater(target Namespace) bool {
func (n Namespace) IsGreaterOrEqualThan(target Namespace) bool {
return bytes.Compare(n, target) > -1
}

// AddInt adds arbitrary int value to namespace, treating namespace as big-endian
// implementation of int
func (n Namespace) AddInt(val int) (Namespace, error) {
if val == 0 {
return n, nil
}
// Convert the input integer to a byte slice and add it to result slice
result := make([]byte, len(n))
if val > 0 {
binary.BigEndian.PutUint64(result[len(n)-8:], uint64(val))
} else {
binary.BigEndian.PutUint64(result[len(n)-8:], uint64(-val))
}

// Perform addition byte by byte
var carry int
for i := len(n) - 1; i >= 0; i-- {
var sum int
if val > 0 {
sum = int(n[i]) + int(result[i]) + carry
} else {
sum = int(n[i]) - int(result[i]) + carry
}

switch {
case sum > 255:
carry = 1
sum -= 256
case sum < 0:
carry = -1
sum += 256
default:
carry = 0
}

result[i] = uint8(sum)
}

// Handle any remaining carry
if carry != 0 {
return nil, errors.New("namespace overflow")
}

return result, nil
}
15 changes: 6 additions & 9 deletions share/shwap/namespaced_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,17 @@ type RowNamespaceData struct {

// Verify checks the integrity of the NamespacedData against a provided root and namespace.
func (ns NamespacedData) Verify(root *share.Root, namespace share.Namespace) error {
var originalRoots [][]byte
for _, rowRoot := range root.RowRoots {
if !namespace.IsOutsideRange(rowRoot, rowRoot) {
originalRoots = append(originalRoots, rowRoot)
}
}
rowRoots, _, _ := share.FilterRootByNamespace(root, namespace)

if len(originalRoots) != len(ns) {
return fmt.Errorf("expected %d rows, found %d rows", len(originalRoots), len(ns))
if len(rowRoots) != len(ns) {
return fmt.Errorf("expected %d rows, found %d rows", len(rowRoots), len(ns))
}

for i, row := range ns {
if row.Proof == nil || len(row.Shares) == 0 {
return fmt.Errorf("row %d is missing proofs or shares", i)
}
if !row.VerifyInclusion(originalRoots[i], namespace) {
if !row.VerifyInclusion(rowRoots[i], namespace) {
return fmt.Errorf("failed to verify row %d", i)
}
}
Expand Down Expand Up @@ -156,6 +151,8 @@ func NamespacedRowFromShares(shares []share.Share, namespace share.Namespace, ro
}
}
if count == 0 {
// FIXME: This should return Non-inclusion proofs instead. Need support in app wrapper to generate
// absence proofs.
return RowNamespaceData{}, fmt.Errorf("no shares found in the namespace for row %d", rowIndex)
}

Expand Down
104 changes: 104 additions & 0 deletions share/shwap/namespaced_data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package shwap

import (
"bytes"
"slices"
"testing"

"github.com/stretchr/testify/require"

"github.com/celestiaorg/celestia-node/share"
"github.com/celestiaorg/celestia-node/share/eds/edstest"
"github.com/celestiaorg/celestia-node/share/sharetest"
)

func TestNamespacedRowFromShares(t *testing.T) {
const odsSize = 8

minNamespace, err := share.NewBlobNamespaceV0(slices.Concat(bytes.Repeat([]byte{0}, 8), []byte{1, 0}))
require.NoError(t, err)
err = minNamespace.ValidateForData()
require.NoError(t, err)

for namespacedAmount := 1; namespacedAmount < odsSize; namespacedAmount++ {
shares := sharetest.RandSharesWithNamespace(t, minNamespace, namespacedAmount, odsSize)
parity, err := share.DefaultRSMT2DCodec().Encode(shares)
require.NoError(t, err)
extended := slices.Concat(shares, parity)

nr, err := NamespacedRowFromShares(extended, minNamespace, 0)
require.NoError(t, err)
require.Equal(t, namespacedAmount, len(nr.Shares))
}
}

func TestNamespacedRowFromSharesNonIncluded(t *testing.T) {
// TODO: this will fail until absence proof support is added
t.Skip()

const odsSize = 8
// Test absent namespace
shares := sharetest.RandShares(t, odsSize)
absentNs, err := share.GetNamespace(shares[0]).AddInt(1)
require.NoError(t, err)

parity, err := share.DefaultRSMT2DCodec().Encode(shares)
require.NoError(t, err)
extended := slices.Concat(shares, parity)

nr, err := NamespacedRowFromShares(extended, absentNs, 0)
require.NoError(t, err)
require.Len(t, nr.Shares, 0)
require.True(t, nr.Proof.IsOfAbsence())
}

func TestNamespacedSharesFromEDS(t *testing.T) {
const odsSize = 8
sharesAmount := odsSize * odsSize
namespace := sharetest.RandV0Namespace()
for amount := 1; amount < sharesAmount; amount++ {
eds, root := edstest.RandEDSWithNamespace(t, namespace, amount, odsSize)
nd, err := NewNamespacedSharesFromEDS(eds, namespace)
require.NoError(t, err)
require.True(t, len(nd) > 0)
require.Len(t, nd.Flatten(), amount)

err = nd.Verify(root, namespace)
require.NoError(t, err)
}
}

func TestValidateNamespacedRow(t *testing.T) {
const odsSize = 8
sharesAmount := odsSize * odsSize
namespace := sharetest.RandV0Namespace()
for amount := 1; amount < sharesAmount; amount++ {
eds, root := edstest.RandEDSWithNamespace(t, namespace, amount, odsSize)
nd, err := NewNamespacedSharesFromEDS(eds, namespace)
require.NoError(t, err)
require.True(t, len(nd) > 0)

_, from, to := share.FilterRootByNamespace(root, namespace)
require.Len(t, nd, to-from)
idx := from
for _, row := range nd {
err = row.Validate(root, namespace, idx)
require.NoError(t, err)
idx++
}
}
}

func TestNamespacedRowProtoEncoding(t *testing.T) {
const odsSize = 8
namespace := sharetest.RandV0Namespace()
eds, _ := edstest.RandEDSWithNamespace(t, namespace, odsSize, odsSize)
nd, err := NewNamespacedSharesFromEDS(eds, namespace)
require.NoError(t, err)
require.True(t, len(nd) > 0)

expected := nd[0]
pb := expected.ToProto()
ndOut := NamespacedRowFromProto(pb)
require.Equal(t, expected, ndOut)
}
4 changes: 2 additions & 2 deletions share/shwap/row.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ func RowFromProto(r *pb.Row) Row {
}
}

// NewRowFromEDS constructs a new Row from an Extended Data Square based on the specified index and
// RowFromEDS constructs a new Row from an Extended Data Square based on the specified index and
// side.
func NewRowFromEDS(square *rsmt2d.ExtendedDataSquare, idx int, side RowSide) Row {
func RowFromEDS(square *rsmt2d.ExtendedDataSquare, idx int, side RowSide) Row {
sqrLn := int(square.Width())
shares := square.Row(uint(idx))
var halfShares []share.Share
Expand Down
114 changes: 114 additions & 0 deletions share/shwap/row_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package shwap

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/celestiaorg/celestia-node/share"
"github.com/celestiaorg/celestia-node/share/eds/edstest"
)

func TestNewRowFromEDS(t *testing.T) {
const odsSize = 8
eds := edstest.RandEDS(t, odsSize)

for rowIdx := 0; rowIdx < odsSize*2; rowIdx++ {
for _, side := range []RowSide{Left, Right} {
row := RowFromEDS(eds, rowIdx, side)

want := eds.Row(uint(rowIdx))
shares, err := row.Shares()
require.NoError(t, err)
require.Equal(t, want, shares)

var half []share.Share
if side == Right {
half = want[odsSize:]
} else {
half = want[:odsSize]
}
require.Equal(t, half, row.halfShares)
require.Equal(t, side, row.side)
}
}
}

func TestRowValidate(t *testing.T) {
const odsSize = 8
eds := edstest.RandEDS(t, odsSize)
root, err := share.NewRoot(eds)
require.NoError(t, err)

for rowIdx := 0; rowIdx < odsSize*2; rowIdx++ {
for _, side := range []RowSide{Left, Right} {
row := RowFromEDS(eds, rowIdx, side)

err := row.VerifyRoot(root, rowIdx)
require.NoError(t, err)
err = row.Validate(root, rowIdx)
require.NoError(t, err)
}
}
}

func TestRowValidateNegativeCases(t *testing.T) {
eds := edstest.RandEDS(t, 8) // Generate a random Extended Data Square of size 8
root, err := share.NewRoot(eds)
require.NoError(t, err)
row := RowFromEDS(eds, 0, Left)

// Test with incorrect side specification
invalidSideRow := Row{halfShares: row.halfShares, side: RowSide(999)}
err = invalidSideRow.Validate(root, 0)
require.Error(t, err, "should error on invalid row side")

// Test with invalid shares (more shares than expected)
incorrectShares := make([]share.Share, (eds.Width()/2)+1) // Adding an extra share
for i := range incorrectShares {
incorrectShares[i] = eds.GetCell(uint(i), 0)
}
invalidRow := Row{halfShares: incorrectShares, side: Left}
err = invalidRow.Validate(root, 0)
require.Error(t, err, "should error on incorrect number of shares")

// Test with empty shares
emptyRow := Row{halfShares: []share.Share{}, side: Left}
err = emptyRow.Validate(root, 0)
require.Error(t, err, "should error on empty halfShares")

// Doesn't match root. Corrupt root hash
root.RowRoots[0][len(root.RowRoots[0])-1] ^= 0xFF
err = row.Validate(root, 0)
require.Error(t, err, "should error on invalid root hash")
}

func TestRowProtoEncoding(t *testing.T) {
const odsSize = 8
eds := edstest.RandEDS(t, odsSize)

for rowIdx := 0; rowIdx < odsSize*2; rowIdx++ {
for _, side := range []RowSide{Left, Right} {
row := RowFromEDS(eds, rowIdx, side)

pb := row.ToProto()
rowOut := RowFromProto(pb)
require.Equal(t, row, rowOut)
}
}
}

// BenchmarkRowValidate benchmarks the performance of row validation.
// BenchmarkRowValidate-10 9591 121802 ns/op
func BenchmarkRowValidate(b *testing.B) {
const odsSize = 32
eds := edstest.RandEDS(b, odsSize)
root, err := share.NewRoot(eds)
require.NoError(b, err)
row := RowFromEDS(eds, 0, Left)

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = row.Validate(root, 0)
}
}
Loading

0 comments on commit 7d3f5a7

Please sign in to comment.