forked from rollkit/rollkit
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from notional-labs/v0.11.9-notional
Add celestia da for rollkit.
- Loading branch information
Showing
5 changed files
with
395 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package celestia | ||
|
||
import ( | ||
"context" | ||
"crypto/rand" | ||
"encoding/binary" | ||
"log/slog" | ||
"strings" | ||
|
||
"github.com/celestiaorg/nmt" | ||
openrpc "github.com/rollkit/celestia-openrpc" | ||
"github.com/rollkit/celestia-openrpc/types/appconsts" | ||
"github.com/rollkit/celestia-openrpc/types/blob" | ||
"github.com/rollkit/celestia-openrpc/types/share" | ||
"github.com/rollkit/go-da" | ||
) | ||
|
||
func RandomNamespace() share.Namespace { | ||
bytes := make([]byte, 4) // 4 bytes to produce 8 hex characters | ||
_, err := rand.Read(bytes) | ||
if err != nil { | ||
panic(err) | ||
} | ||
ns, err := share.NewBlobNamespaceV0(bytes) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return ns | ||
} | ||
|
||
type CelestiaDA struct { | ||
logger *slog.Logger | ||
context context.Context | ||
client *openrpc.Client | ||
namespace share.Namespace | ||
} | ||
|
||
func NewCelestiaDA(ctx context.Context, client *openrpc.Client) *CelestiaDA { | ||
return &CelestiaDA{ | ||
context: ctx, | ||
client: client, | ||
namespace: RandomNamespace(), | ||
logger: slog.Default(), | ||
} | ||
} | ||
|
||
var _ da.DA = &CelestiaDA{} | ||
|
||
// Commit implements da.DA. | ||
func (c *CelestiaDA) Commit(blobs [][]byte) ([][]byte, error) { | ||
c.logger.Info("CelestiaDA Commit", "blobs len", len(blobs)) | ||
_, commitments, err := c.blobsAndCommitments(blobs) | ||
return commitments, err | ||
} | ||
|
||
// Get implements da.DA. | ||
func (c *CelestiaDA) Get(ids [][]byte) ([][]byte, error) { | ||
c.logger.Info("CelestiaDA Get", "ids len", len(ids)) | ||
var blobs []da.Blob | ||
for _, id := range ids { | ||
height, commitment := splitID(id) | ||
blob, err := c.client.Blob.Get(c.context, height, c.namespace, commitment) | ||
if err != nil { | ||
return nil, err | ||
} | ||
blobs = append(blobs, blob.Data) | ||
} | ||
return blobs, nil | ||
} | ||
|
||
// GetIDs implements da.DA. | ||
func (c *CelestiaDA) GetIDs(height uint64) ([][]byte, error) { | ||
c.logger.Info("CelestiaDA GetIDs", "height", height) | ||
var ids []da.ID | ||
blobs, err := c.client.Blob.GetAll(c.context, height, []share.Namespace{c.namespace}) | ||
if err != nil { | ||
if strings.Contains(err.Error(), blob.ErrBlobNotFound.Error()) { | ||
return nil, nil | ||
} | ||
return nil, err | ||
} | ||
for _, b := range blobs { | ||
ids = append(ids, makeID(height, b.Commitment)) | ||
} | ||
return ids, nil | ||
} | ||
|
||
// MaxBlobSize implements da.DA. | ||
func (c *CelestiaDA) MaxBlobSize() (uint64, error) { | ||
c.logger.Info("CelestiaDA MaxBlobSize", "result", appconsts.DefaultMaxBytes) | ||
return appconsts.DefaultMaxBytes, nil | ||
} | ||
|
||
// Submit implements da.DA. | ||
func (c *CelestiaDA) Submit(daBlobs [][]byte) ([][]byte, [][]byte, error) { | ||
c.logger.Info("CelestiaDA Submit", "daBlobs len", len(daBlobs)) | ||
blobs, commitments, err := c.blobsAndCommitments(daBlobs) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
height, err := c.client.Blob.Submit(c.context, blobs, openrpc.DefaultSubmitOptions()) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
ids := make([]da.ID, len(daBlobs)) | ||
proofs := make([]da.Proof, len(daBlobs)) | ||
for i, commitment := range commitments { | ||
ids[i] = makeID(height, commitment) | ||
proof, err := c.client.Blob.GetProof(c.context, height, c.namespace, commitment) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
proofs[i], err = (*proof)[0].MarshalJSON() | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
} | ||
return ids, proofs, nil | ||
} | ||
|
||
// Validate implements da.DA. | ||
func (c *CelestiaDA) Validate(ids [][]byte, proofs [][]byte) ([]bool, error) { | ||
c.logger.Info("CelestiaDA Validate", "ids len", len(ids)) | ||
var included []bool | ||
var bProofs []*blob.Proof | ||
for _, p := range proofs { | ||
nmtProof := &nmt.Proof{} | ||
if err := nmtProof.UnmarshalJSON(p); err != nil { | ||
return nil, err | ||
} | ||
proof := &blob.Proof{nmtProof} | ||
bProofs = append(bProofs, proof) | ||
} | ||
for i, id := range ids { | ||
height, commitment := splitID(id) | ||
isIncluded, _ := c.client.Blob.Included(c.context, height, c.namespace, bProofs[i], commitment) | ||
included = append(included, isIncluded) | ||
} | ||
return included, nil | ||
} | ||
|
||
func (c *CelestiaDA) blobsAndCommitments(daBlobs []da.Blob) ([]*blob.Blob, []da.Commitment, error) { | ||
var blobs []*blob.Blob | ||
var commitments []da.Commitment | ||
for _, daBlob := range daBlobs { | ||
b, err := blob.NewBlobV0(c.namespace, daBlob) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
blobs = append(blobs, b) | ||
|
||
commitment, err := blob.CreateCommitment(b) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
commitments = append(commitments, commitment) | ||
} | ||
return blobs, commitments, nil | ||
} | ||
|
||
// heightLen is a length (in bytes) of serialized height. | ||
// | ||
// This is 8 as uint64 consist of 8 bytes. | ||
const heightLen = 8 | ||
|
||
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) { | ||
if len(id) <= heightLen { | ||
return 0, nil | ||
} | ||
return binary.LittleEndian.Uint64(id[:heightLen]), id[heightLen:] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package celestia_test | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ory/dockertest/v3" | ||
openrpc "github.com/rollkit/celestia-openrpc" | ||
"github.com/rollkit/go-da/test" | ||
"github.com/rollkit/rollkit/da/celestia" | ||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type TestSuite struct { | ||
suite.Suite | ||
|
||
pool *dockertest.Pool | ||
resource *dockertest.Resource | ||
|
||
token string | ||
} | ||
|
||
func (t *TestSuite) SetupSuite() { | ||
pool, err := dockertest.NewPool("") | ||
if err != nil { | ||
t.Failf("Could not construct docker pool", "error: %v\n", err) | ||
} | ||
t.pool = pool | ||
|
||
// uses pool to try to connect to Docker | ||
err = pool.Client.Ping() | ||
if err != nil { | ||
t.Failf("Could not connect to Docker", "error: %v\n", err) | ||
} | ||
|
||
// pulls an image, creates a container based on it and runs it | ||
resource, err := pool.Run("ghcr.io/rollkit/local-celestia-devnet", "latest", []string{}) | ||
if err != nil { | ||
t.Failf("Could not start resource", "error: %v\n", err) | ||
} | ||
t.resource = resource | ||
|
||
// // exponential backoff-retry, because the application in the container might not be ready to accept connections yet | ||
// pool.MaxWait = 60 * time.Second | ||
// if err := pool.Retry(func() error { | ||
// resp, err := http.Get(fmt.Sprintf("http://localhost:%s/balance", resource.GetPort("26659/tcp"))) | ||
// if err != nil { | ||
// return err | ||
// } | ||
// bz, err := io.ReadAll(resp.Body) | ||
// _ = resp.Body.Close() | ||
// if err != nil { | ||
// return err | ||
// } | ||
// if strings.Contains(string(bz), "error") { | ||
// return errors.New(string(bz)) | ||
// } | ||
// return nil | ||
// }); err != nil { | ||
// log.Fatalf("Could not start local-celestia-devnet: %s", err) | ||
// } | ||
|
||
opts := dockertest.ExecOptions{} | ||
buf := new(bytes.Buffer) | ||
opts.StdOut = buf | ||
opts.StdErr = buf | ||
_, err = resource.Exec([]string{"/bin/celestia", "bridge", "auth", "admin", "--node.store", "/home/celestia/bridge"}, opts) | ||
if err != nil { | ||
t.Failf("Could not execute command", "error: %v\n", err) | ||
} | ||
|
||
t.token = buf.String() | ||
} | ||
|
||
func (t *TestSuite) TearDownSuite() { | ||
if err := t.pool.Purge(t.resource); err != nil { | ||
t.Failf("failed to purge docker resource", "error: %v\n", err) | ||
} | ||
} | ||
|
||
func TestIntegrationTestSuite(t *testing.T) { | ||
suite.Run(t, new(TestSuite)) | ||
} | ||
|
||
func (t *TestSuite) TestCelestiaDA() { | ||
client, err := openrpc.NewClient(context.Background(), t.getRPCAddress(), t.token) | ||
t.Require().NoError(err) | ||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) | ||
defer cancel() | ||
da := celestia.NewCelestiaDA(ctx, client) | ||
test.RunDATestSuite(t.T(), da) | ||
} | ||
|
||
func (t *TestSuite) getRPCAddress() string { | ||
return fmt.Sprintf("http://localhost:%s", t.resource.GetPort("26658/tcp")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.