Skip to content

feat!(share/shwap): implement get range request over shwap #4156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 56 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
5317b61
feat!(share/proto): define proto
vgonkivs Mar 3, 2025
d815b23
feat(share/shwap): add proof type
vgonkivs Mar 3, 2025
3d2e116
feat(share/shwap): add range namespace data with id
vgonkivs Mar 3, 2025
9542c57
feat(share/p2p/bitswap): add range namespace data block
vgonkivs Mar 3, 2025
89d4478
refacor!(nodebuilde/share): update api
vgonkivs Mar 3, 2025
64ba201
swamp(share): add GetRange test
vgonkivs Mar 3, 2025
53ecfcf
chore(share/getters): udpate getters
vgonkivs Mar 3, 2025
811875a
update proto
vgonkivs Mar 7, 2025
6cc8cd2
rework get namespace data
vgonkivs Mar 7, 2025
f07fced
chore(share/accessor): update accessor
vgonkivs Mar 3, 2025
d631cb9
feat: implement validation for RangeNamespaceData
vgonkivs Mar 7, 2025
77e99ce
fix lint
vgonkivs Mar 7, 2025
4093946
fixes
vgonkivs Mar 7, 2025
8071ca3
remove root from Proof, refactor Verify functions
gupadhyaya Apr 28, 2025
802f812
store only incomplete proofs
gupadhyaya May 2, 2025
aaa437c
some fixes
gupadhyaya May 5, 2025
622a5dd
cleanup
gupadhyaya May 5, 2025
8eb170c
update nmt, fixes to proof
gupadhyaya May 6, 2025
dcf90fa
fix conditions for incomplete rows
gupadhyaya May 6, 2025
1cc998e
move extend methods out of axis_half to resolve cyclic dependencies
gupadhyaya May 7, 2025
587ae04
verify without row roots
gupadhyaya May 13, 2025
824c3b2
minor fixes
gupadhyaya May 13, 2025
09bee8f
odsSize based on extended shares
gupadhyaya May 13, 2025
d4c6887
Merge remote-tracking branch 'origin/main' into implement_get_range
gupadhyaya May 14, 2025
b0dec58
fix imports and build failures after the latest merge from main
gupadhyaya May 14, 2025
de80fd8
formatting
gupadhyaya May 14, 2025
73ad28e
fix imports
gupadhyaya May 15, 2025
a57ba4b
Merge branch 'main' into implement_get_range
gupadhyaya May 15, 2025
49fce9a
revert
gupadhyaya May 20, 2025
47254e2
clean up
gupadhyaya May 20, 2025
6a81691
improve condition readability, pass axis roots to range namespace dat…
gupadhyaya May 20, 2025
f95e147
use compute root with basic validation
gupadhyaya May 20, 2025
d71c54a
update proofs of complete rows
gupadhyaya May 20, 2025
8407c2b
fix failing tests
gupadhyaya May 20, 2025
2a16175
Merge remote-tracking branch 'origin/main' into implement_get_range
gupadhyaya May 20, 2025
4c6dd90
golangci fix
gupadhyaya May 20, 2025
180e6fe
fixes to prevent bad data
gupadhyaya May 23, 2025
eaf6967
further increase timeout
gupadhyaya May 23, 2025
92d843e
Merge branch 'main' into implement_get_range
gupadhyaya May 23, 2025
4845c4a
non-empty proof check for unmarshal
gupadhyaya May 23, 2025
86c991c
update to nmt v0.24.0
gupadhyaya May 23, 2025
ce8aaa0
minor fix
gupadhyaya May 23, 2025
c1fe8bc
reuse NamespaceDataID
gupadhyaya May 26, 2025
b4edcbb
add more tests for range namespace data id
gupadhyaya May 27, 2025
443ebcc
improve godoc and check empty for earlier termination
gupadhyaya May 27, 2025
240504b
shareProof to sharesProof
gupadhyaya May 27, 2025
e4617b4
enhance TestRangeNamespaceData_FetchRoundtrip
gupadhyaya May 27, 2025
ca13b5c
add more test cases
gupadhyaya May 27, 2025
acebf3e
add verify to rndid, improve godoc
gupadhyaya May 27, 2025
1ad7c11
renaming, fixing comment
gupadhyaya May 27, 2025
addb7c2
Merge branch 'main' into implement_get_range
gupadhyaya May 27, 2025
cb8c423
clarify godoc for RangeNamespaceData and Proof
gupadhyaya May 28, 2025
17a6dd8
add nil check, IsEmpty everywhere, improve godoc for module interface…
gupadhyaya Jun 2, 2025
f3d72c2
err var, consolidate checks, revert generateSharesProofs
gupadhyaya Jun 3, 2025
57b0f0a
use well-defined error, comment fix
gupadhyaya Jun 3, 2025
e69163f
Merge branch 'main' into implement_get_range
gupadhyaya Jun 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions nodebuilder/share/mocks/api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 21 additions & 32 deletions nodebuilder/share/share.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,17 @@ package share
import (
"context"

"github.com/tendermint/tendermint/types"

libshare "github.com/celestiaorg/go-square/v2/share"
"github.com/celestiaorg/rsmt2d"

"github.com/celestiaorg/celestia-node/header"
headerServ "github.com/celestiaorg/celestia-node/nodebuilder/header"
"github.com/celestiaorg/celestia-node/share"
"github.com/celestiaorg/celestia-node/share/eds"
"github.com/celestiaorg/celestia-node/share/shwap"
)

var _ Module = (*API)(nil)

// GetRangeResult wraps the return value of the GetRange endpoint
// because Json-RPC doesn't support more than two return values.
type GetRangeResult struct {
Shares []libshare.Share
Proof *types.ShareProof
}

// Module provides access to any data square or block share on the network.
//
// All Get methods provided on Module follow the following flow:
Expand Down Expand Up @@ -58,7 +48,9 @@ type Module interface {
ctx context.Context, height uint64, namespace libshare.Namespace,
) (shwap.NamespaceData, error)
// GetRange gets a list of shares and their corresponding proof.
GetRange(ctx context.Context, height uint64, start, end int) (*GetRangeResult, error)
GetRange(
ctx context.Context, ns libshare.Namespace, height uint64, from, to shwap.SampleCoords, proofsOnly bool,
) (shwap.RangeNamespaceData, error)
}

// API is a wrapper around Module for the RPC.
Expand Down Expand Up @@ -91,9 +83,11 @@ type API struct {
) (shwap.NamespaceData, error) `perm:"read"`
GetRange func(
ctx context.Context,
ns libshare.Namespace,
height uint64,
start, end int,
) (*GetRangeResult, error) `perm:"read"`
from, to shwap.SampleCoords,
proofsOnly bool,
) (shwap.RangeNamespaceData, error) `perm:"read"`
}
}

Expand All @@ -119,8 +113,10 @@ func (api *API) GetRow(ctx context.Context, height uint64, rowIdx int) (shwap.Ro
return api.Internal.GetRow(ctx, height, rowIdx)
}

func (api *API) GetRange(ctx context.Context, height uint64, start, end int) (*GetRangeResult, error) {
return api.Internal.GetRange(ctx, height, start, end)
func (api *API) GetRange(
ctx context.Context, ns libshare.Namespace, height uint64, from, to shwap.SampleCoords, proofsOnly bool,
) (shwap.RangeNamespaceData, error) {
return api.Internal.GetRange(ctx, ns, height, from, to, proofsOnly)
}

func (api *API) GetNamespaceData(
Expand Down Expand Up @@ -175,25 +171,18 @@ func (m module) SharesAvailable(ctx context.Context, height uint64) error {
return m.avail.SharesAvailable(ctx, header)
}

func (m module) GetRange(ctx context.Context, height uint64, start, end int) (*GetRangeResult, error) {
extendedDataSquare, err := m.GetEDS(ctx, height)
if err != nil {
return nil, err
}

proof, err := eds.ProveShares(extendedDataSquare, start, end)
if err != nil {
return nil, err
}

shares, err := libshare.FromBytes(extendedDataSquare.FlattenedODS()[start:end])
func (m module) GetRange(
ctx context.Context,
ns libshare.Namespace,
height uint64,
from, to shwap.SampleCoords,
proofsOnly bool,
) (shwap.RangeNamespaceData, error) {
header, err := m.hs.GetByHeight(ctx, height)
if err != nil {
return nil, err
return shwap.RangeNamespaceData{}, err
}
return &GetRangeResult{
Shares: shares,
Proof: proof,
}, nil
return m.getter.GetRangeNamespaceData(ctx, header, ns, from, to, proofsOnly)
}

func (m module) GetNamespaceData(
Expand Down
32 changes: 31 additions & 1 deletion nodebuilder/tests/share_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
)

func TestShareModule(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Minute)
t.Cleanup(cancel)
sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Second*1))
blobSize := 128
Expand All @@ -44,6 +44,7 @@ func TestShareModule(t *testing.T) {
require.NoError(t, fullNode.Start(ctx))

addrsFull, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(fullNode.Host))

require.NoError(t, err)

lightCfg := sw.DefaultTestConfig(node.Light)
Expand Down Expand Up @@ -207,7 +208,36 @@ func TestShareModule(t *testing.T) {
require.NoError(t, err)
// compare commitments
require.Equal(t, nodeBlob[0].Commitment, blb[0].Commitment)
}
},
},
{
name: "GetRangeNamespaceData",
doFn: func(t *testing.T) {
dah := hdr.DAH
blobLength, err := sampledBlob.Length()
require.NoError(t, err)
from, to, err := shwap.RangeCoordsFromIdx(sampledBlob.Index(), blobLength, len(dah.RowRoots))
require.NoError(t, err)
for _, client := range clients {
rng, err := client.Share.GetRange(ctx, nodeBlob[0].Namespace(), height, from, to, false)
require.NoError(t, err)
err = rng.Verify(nodeBlob[0].Namespace(), from, to, dah.Hash())
require.NoError(t, err)

shrs := rng.Flatten()
blbs, err := libshare.ParseBlobs(shrs)
require.NoError(t, err)

parsedBlob, err := blob.ToNodeBlobs(blbs...)
require.NoError(t, err)
require.Equal(t, nodeBlob[0].Commitment, parsedBlob[0].Commitment)

rngProofsOnly, err := client.Share.GetRange(ctx, nodeBlob[0].Namespace(), height, from, to, true)
require.NoError(t, err)
assert.Empty(t, rngProofsOnly.Shares)
err = rngProofsOnly.VerifyShares(rng.Shares, nodeBlob[0].Namespace(), from, to, dah.Hash())
require.NoError(t, err)
}
},
},
Expand Down
10 changes: 10 additions & 0 deletions share/availability/light/availability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,16 @@ func (g successGetter) GetNamespaceData(
panic("not implemented")
}

func (g successGetter) GetRangeNamespaceData(
_ context.Context,
_ *header.ExtendedHeader,
_ libshare.Namespace,
_, _ shwap.SampleCoords,
_ bool,
) (shwap.RangeNamespaceData, error) {
panic("not implemented")
}

func TestPruneAll(t *testing.T) {
const size = 8
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
Expand Down
7 changes: 7 additions & 0 deletions share/eds/accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ type Accessor interface {
RowNamespaceData(ctx context.Context, namespace libshare.Namespace, rowIdx int) (shwap.RowNamespaceData, error)
// Shares returns data (ODS) shares extracted from the Accessor.
Shares(ctx context.Context) ([]libshare.Share, error)
// RangeNamespaceData returns data(ODS) shares along with their proofs from the requested range
// from the Accessor. Response might have only proofs.
RangeNamespaceData(
ctx context.Context,
ns libshare.Namespace,
from, to shwap.SampleCoords,
) (shwap.RangeNamespaceData, error)
}

// AccessorStreamer is an interface that groups Accessor and Streamer interfaces.
Expand Down
11 changes: 11 additions & 0 deletions share/eds/close_once.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ func (c *closeOnce) Shares(ctx context.Context) ([]libshare.Share, error) {
return c.f.Shares(ctx)
}

func (c *closeOnce) RangeNamespaceData(
ctx context.Context,
ns libshare.Namespace,
from, to shwap.SampleCoords,
) (shwap.RangeNamespaceData, error) {
if c.closed.Load() {
return shwap.RangeNamespaceData{}, errAccessorClosed
}
return c.f.RangeNamespaceData(ctx, ns, from, to)
}

func (c *closeOnce) Reader() (io.Reader, error) {
if c.closed.Load() {
return nil, errAccessorClosed
Expand Down
8 changes: 8 additions & 0 deletions share/eds/close_once_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ func (s *stubEdsAccessorCloser) RowNamespaceData(
return shwap.RowNamespaceData{}, nil
}

func (s *stubEdsAccessorCloser) RangeNamespaceData(
_ context.Context,
_ libshare.Namespace,
_, _ shwap.SampleCoords,
) (shwap.RangeNamespaceData, error) {
return shwap.RangeNamespaceData{}, nil
}

func (s *stubEdsAccessorCloser) Shares(context.Context) ([]libshare.Share, error) {
return nil, nil
}
Expand Down
59 changes: 58 additions & 1 deletion share/eds/proofs_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ func (c *proofsCache) RowNamespaceData(
if err != nil {
return shwap.RowNamespaceData{}, fmt.Errorf("shares by namespace %s for row %v: %w", namespace.String(), rowIdx, err)
}

return shwap.RowNamespaceData{
Shares: row,
Proof: proof,
Expand Down Expand Up @@ -255,6 +254,64 @@ func (c *proofsCache) Shares(ctx context.Context) ([]libshare.Share, error) {
return shares, nil
}

// RangeNamespaceData tries to find all complete rows in cache. For all incomplete rows,
// it uses the inner accessor to build the namespace data
func (c *proofsCache) RangeNamespaceData(
ctx context.Context,
ns libshare.Namespace,
from, to shwap.SampleCoords,
) (shwap.RangeNamespaceData, error) {
odsSize := c.Size(ctx) / 2

roots, err := c.AxisRoots(ctx)
if err != nil {
return shwap.RangeNamespaceData{}, fmt.Errorf("accessing axis roots: %w", err)
}
rngdata := shwap.RangeNamespaceData{
Start: from.Row,
Shares: make([][]libshare.Share, 0, to.Row-from.Row+1),
Proof: make([]*shwap.Proof, 0, to.Row-from.Row+1),
}

// iterate over each row in the range [from.Row; to.Row].
// All complete rows(from.Col = 0 && to.Col = odsSize-1) is
// requested using `RowNamespaceData` that uses cache.
// Other cases are handled using `RangeNamespaceData` as these rows are incomplete.
for row := from.Row; row <= to.Row; row++ {
startCol := from.Col
endCol := to.Col
if row != to.Row {
endCol = odsSize - 1
}

if startCol == 0 && endCol == odsSize-1 {
// request full row using RowNamespaceData
rowData, err := c.RowNamespaceData(ctx, ns, row)
if err != nil {
return shwap.RangeNamespaceData{}, err
}
rngdata.Shares = append(rngdata.Shares, rowData.Shares)
rngdata.Proof = append(rngdata.Proof, shwap.NewProof(row, rowData.Proof, roots))
continue
}

// Otherwise, fetch the partial range
data, err := c.inner.RangeNamespaceData(
ctx, ns,
shwap.SampleCoords{Row: row, Col: startCol},
shwap.SampleCoords{Row: row, Col: endCol},
)
if err != nil {
return shwap.RangeNamespaceData{}, err
}
rngdata.Shares = append(rngdata.Shares, data.Shares[0])
rngdata.Proof = append(rngdata.Proof, data.Proof[0])
// Reset column for subsequent rows
from.Col = 0
}
return rngdata, nil
}

func (c *proofsCache) Reader() (io.Reader, error) {
odsSize := c.Size(context.TODO()) / 2
reader := NewShareReader(odsSize, c.getShare)
Expand Down
2 changes: 1 addition & 1 deletion share/eds/proofs_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestCache(t *testing.T) {
ODSSize := 16
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
t.Cleanup(cancel)

newAccessor := func(tb testing.TB, inner *rsmt2d.ExtendedDataSquare) Accessor {
Expand Down
23 changes: 23 additions & 0 deletions share/eds/rsmt2d.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,29 @@ func (eds *Rsmt2D) RowNamespaceData(
return shwap.RowNamespaceDataFromShares(sh, namespace, rowIdx)
}

// RangeNamespaceData builds a namespace range from the given coordinates and the length of the
// range.
func (eds *Rsmt2D) RangeNamespaceData(
ctx context.Context,
ns libshare.Namespace,
from, to shwap.SampleCoords,
) (shwap.RangeNamespaceData, error) {
rawShares := make([][]libshare.Share, 0, to.Row-from.Row+1)
for row := from.Row; row <= to.Row; row++ {
rawShare := eds.Row(uint(row))
sh, err := libshare.FromBytes(rawShare)
if err != nil {
return shwap.RangeNamespaceData{}, err
}
rawShares = append(rawShares, sh)
}
roots, err := eds.AxisRoots(ctx)
if err != nil {
return shwap.RangeNamespaceData{}, err
}
return shwap.RangedNamespaceDataFromShares(rawShares, ns, roots, from, to)
}

// Shares returns data (ODS) shares extracted from the EDS. It returns new copy of the shares each
// time.
func (eds *Rsmt2D) Shares(_ context.Context) ([]libshare.Share, error) {
Expand Down
Loading
Loading