diff --git a/nodebuilder/da/da.go b/nodebuilder/da/da.go index 64ae806e20..dc6cb51cbe 100644 --- a/nodebuilder/da/da.go +++ b/nodebuilder/da/da.go @@ -43,3 +43,7 @@ func (api *API) Commit(ctx context.Context, blobs []da.Blob, ns da.Namespace) ([ func (api *API) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, ns da.Namespace) ([]bool, error) { return api.Internal.Validate(ctx, ids, proofs, ns) } + +func (api *API) Submit(ctx context.Context, blobs []da.Blob, gasPrice float64, ns da.Namespace) ([]da.ID, []da.Proof, error) { + return api.Internal.Submit(ctx, blobs, gasPrice, ns) +} diff --git a/nodebuilder/da/service.go b/nodebuilder/da/service.go index 1e437b26e3..56a59e8829 100644 --- a/nodebuilder/da/service.go +++ b/nodebuilder/da/service.go @@ -45,7 +45,7 @@ func (s *Service) MaxBlobSize(context.Context) (uint64, error) { func (s *Service) Get(ctx context.Context, ids []da.ID, ns da.Namespace) ([]da.Blob, error) { blobs := make([]da.Blob, 0, len(ids)) for _, id := range ids { - height, commitment := splitID(id) + height, commitment := SplitID(id) log.Debugw("getting blob", "height", height, "commitment", commitment, "namespace", share.Namespace(ns)) currentBlob, err := s.blobServ.Get(ctx, height, ns, commitment) log.Debugw("got blob", "height", height, "commitment", commitment, "namespace", share.Namespace(ns)) @@ -70,7 +70,7 @@ func (s *Service) GetIDs(ctx context.Context, height uint64, namespace da.Namesp return nil, err } for _, b := range blobs { - ids = append(ids, makeID(height, b.Commitment)) + ids = append(ids, MakeID(height, b.Commitment)) } return ids, nil } @@ -102,7 +102,7 @@ func (s *Service) Submit( ids := make([]da.ID, len(daBlobs)) proofs := make([]da.Proof, len(daBlobs)) for i, commitment := range commitments { - ids[i] = makeID(height, commitment) + ids[i] = MakeID(height, commitment) proof, err := s.blobServ.GetProof(ctx, height, namespace, commitment) if err != nil { return nil, nil, err @@ -158,7 +158,7 @@ func (s *Service) Validate( proofs = append(proofs, proof) } for i, id := range ids { - height, commitment := splitID(id) + height, commitment := SplitID(id) // TODO(tzdybal): for some reason, if proof doesn't match commitment, API returns (false, "blob: // invalid proof") but analysis of the code in celestia-node implies this should never happen - // maybe it's caused by openrpc? there is no way of gently handling errors here, but returned @@ -169,14 +169,14 @@ func (s *Service) Validate( return included, nil } -func makeID(height uint64, commitment da.Commitment) da.ID { +func MakeID(height uint64, commitment da.Commitment) da.ID { id := make([]byte, heightLen+len(commitment)) binary.LittleEndian.PutUint64(id, height) copy(id[heightLen:], commitment) return id } -func splitID(id da.ID) (uint64, da.Commitment) { +func SplitID(id da.ID) (uint64, da.Commitment) { if len(id) <= heightLen { return 0, nil } diff --git a/nodebuilder/node.go b/nodebuilder/node.go index d5d0ab2016..b16a376cc1 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -22,6 +22,7 @@ import ( "github.com/celestiaorg/celestia-node/api/gateway" "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/blob" + "github.com/celestiaorg/celestia-node/nodebuilder/da" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -71,6 +72,7 @@ type Node struct { BlobServ blob.Module // not optional DASer das.Module // not optional AdminServ node.Module // not optional + DAMod da.Module // not optional // start and stop control ref internal fx.App lifecycle funcs to be called from Start and Stop start, stop lifecycleFunc diff --git a/nodebuilder/tests/da_test.go b/nodebuilder/tests/da_test.go new file mode 100644 index 0000000000..6ecfb1299e --- /dev/null +++ b/nodebuilder/tests/da_test.go @@ -0,0 +1,134 @@ +//go:build da || integration + +package tests + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/blob/blobtest" + "github.com/celestiaorg/celestia-node/nodebuilder/da" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" + "github.com/celestiaorg/celestia-node/share" +) + +func TestDaModule(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) + t.Cleanup(cancel) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Second*1)) + + namespace, err := share.NewBlobNamespaceV0([]byte("namespace")) + require.NoError(t, err) + + appBlobs0, err := blobtest.GenerateV0Blobs([]int{8, 4}, true) + require.NoError(t, err) + appBlobs1, err := blobtest.GenerateV0Blobs([]int{4}, false) + require.NoError(t, err) + blobs := make([]*blob.Blob, 0, len(appBlobs0)+len(appBlobs1)) + daBlobs := make([][]byte, 0, len(appBlobs0)+len(appBlobs1)) + + for _, b := range append(appBlobs0, appBlobs1...) { + blob, err := blob.NewBlob(b.ShareVersion, append([]byte{b.NamespaceVersion}, namespace...), b.Data) + require.NoError(t, err) + blobs = append(blobs, blob) + daBlobs = append(daBlobs, blob.Data) + } + + require.NoError(t, err) + bridge := sw.NewBridgeNode() + require.NoError(t, bridge.Start(ctx)) + + addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) + require.NoError(t, err) + + fullCfg := sw.DefaultTestConfig(node.Full) + fullCfg.Header.TrustedPeers = append(fullCfg.Header.TrustedPeers, addrs[0].String()) + fullNode := sw.NewNodeWithConfig(node.Full, fullCfg) + require.NoError(t, fullNode.Start(ctx)) + + addrsFull, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(fullNode.Host)) + require.NoError(t, err) + + lightCfg := sw.DefaultTestConfig(node.Light) + lightCfg.Header.TrustedPeers = append(lightCfg.Header.TrustedPeers, addrsFull[0].String()) + lightNode := sw.NewNodeWithConfig(node.Light, lightCfg) + require.NoError(t, lightNode.Start(ctx)) + + fullClient := getAdminClient(ctx, fullNode, t) + lightClient := getAdminClient(ctx, lightNode, t) + + ids, proofs, err := fullClient.DA.Submit(ctx, daBlobs, -1, namespace) + require.NoError(t, err) + + var test = []struct { + name string + doFn func(t *testing.T) + }{ + { + name: "MaxBlobSize", + doFn: func(t *testing.T) { + mbs, err := fullClient.DA.MaxBlobSize(ctx) + require.NoError(t, err) + require.Equal(t, mbs, uint64(appconsts.DefaultMaxBytes)) + }, + }, + { + name: "Validate", + doFn: func(t *testing.T) { + valid, err := fullClient.DA.Validate(ctx, ids, proofs, namespace) + require.NoError(t, err) + for _, v := range valid { + require.True(t, v) + } + }, + }, + { + name: "GetIDs", + doFn: func(t *testing.T) { + height, _ := da.SplitID(ids[0]) + ids2, err := fullClient.DA.GetIDs(ctx, height, namespace) + require.NoError(t, err) + require.EqualValues(t, ids, ids2) + }, + }, + { + name: "Get", + doFn: func(t *testing.T) { + fetched, err := lightClient.DA.Get(ctx, ids, namespace) + require.NoError(t, err) + require.Len(t, fetched, len(ids)) + for i := range fetched { + require.True(t, bytes.Equal(fetched[i], daBlobs[i])) + } + }, + }, + { + name: "Commit", + doFn: func(t *testing.T) { + fetched, err := fullClient.DA.Commit(ctx, ids, namespace) + require.NoError(t, err) + require.Len(t, fetched, len(ids)) + for i := range fetched { + _, commitment := da.SplitID(ids[i]) + require.EqualValues(t, fetched[i], commitment) + } + }, + }, + } + + for _, tt := range test { + tt := tt + t.Run(tt.name, func(t *testing.T) { + tt.doFn(t) + }) + } +}