Skip to content

Commit

Permalink
feat!: create native blob struct (#74)
Browse files Browse the repository at this point in the history
Co-authored-by: Rootul P <[email protected]>
  • Loading branch information
cmwaters and rootulp authored Jun 19, 2024
1 parent d75565f commit 54314fc
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 136 deletions.
108 changes: 74 additions & 34 deletions blob/blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package blob

import (
"bytes"
"errors"
"fmt"
"sort"
Expand All @@ -26,53 +25,83 @@ const ProtoIndexWrapperTypeID = "INDX"
// MaxShareVersion is the maximum value a share version can be. See: [shares.MaxShareVersion].
const MaxShareVersion = 127

// Blob (stands for binary large object) is a core type that represents data
// to be submitted to the Celestia network alongside an accompanying namespace
// and optional signer (for proving the signer of the blob)
type Blob struct {
namespace ns.Namespace
data []byte
shareVersion uint8
signer []byte
}

// New creates a new coretypes.Blob from the provided data after performing
// basic stateless checks over it.
func New(ns ns.Namespace, blob []byte, shareVersion uint8) *Blob {
func New(ns ns.Namespace, data []byte, shareVersion uint8, signer []byte) *Blob {
return &Blob{
NamespaceId: ns.ID(),
Data: blob,
ShareVersion: uint32(shareVersion),
NamespaceVersion: uint32(ns.Version()),
Signer: nil,
namespace: ns,
data: data,
shareVersion: shareVersion,
signer: signer,
}
}

// NewFromProto creates a Blob from the proto format and performs
// rudimentary validation checks on the structure
func NewFromProto(pb *BlobProto) (*Blob, error) {
if pb.ShareVersion > MaxShareVersion {
return nil, errors.New("share version can not be greater than MaxShareVersion")
}
if pb.NamespaceVersion > ns.NamespaceVersionMax {
return nil, errors.New("namespace version can not be greater than MaxNamespaceVersion")
}
if len(pb.Data) == 0 {
return nil, errors.New("blob data can not be empty")
}
ns, err := ns.New(uint8(pb.NamespaceVersion), pb.NamespaceId)
if err != nil {
return nil, fmt.Errorf("invalid namespace: %w", err)
}
return &Blob{
namespace: ns,
data: pb.Data,
shareVersion: uint8(pb.ShareVersion),
signer: pb.Signer,
}, nil
}

// Namespace returns the namespace of the blob
func (b *Blob) Namespace() (ns.Namespace, error) {
return ns.NewFromBytes(b.RawNamespace())
func (b *Blob) Namespace() ns.Namespace {
return b.namespace
}

// RawNamespace returns the namespace of the blob
func (b *Blob) RawNamespace() []byte {
namespace := make([]byte, ns.NamespaceSize)
namespace[ns.VersionIndex] = uint8(b.NamespaceVersion)
copy(namespace[ns.NamespaceVersionSize:], b.NamespaceId)
return namespace
// ShareVersion returns the share version of the blob
func (b *Blob) ShareVersion() uint8 {
return b.shareVersion
}

// Validate runs a stateless validity check on the form of the struct.
func (b *Blob) Validate() error {
if b == nil {
return errors.New("nil blob")
}
if len(b.NamespaceId) != ns.NamespaceIDSize {
return fmt.Errorf("namespace id must be %d bytes", ns.NamespaceIDSize)
}
if b.ShareVersion > MaxShareVersion {
return errors.New("share version can not be greater than MaxShareVersion")
}
if b.NamespaceVersion > ns.NamespaceVersionMax {
return errors.New("namespace version can not be greater than MaxNamespaceVersion")
}
if len(b.Data) == 0 {
return errors.New("blob data can not be empty")
// Signer returns the signer of the blob
func (b *Blob) Signer() []byte {
return b.signer
}

// Data returns the data of the blob
func (b *Blob) Data() []byte {
return b.data
}

func (b *Blob) ToProto() *BlobProto {
return &BlobProto{
NamespaceId: b.namespace.ID(),
NamespaceVersion: uint32(b.namespace.Version()),
ShareVersion: uint32(b.shareVersion),
Data: b.data,
Signer: b.signer,
}
return nil
}

func (b *Blob) Compare(other *Blob) int {
return bytes.Compare(b.RawNamespace(), other.RawNamespace())
return b.namespace.Compare(other.namespace)
}

// UnmarshalBlobTx attempts to unmarshal a transaction into blob transaction. If an
Expand Down Expand Up @@ -104,14 +133,25 @@ func UnmarshalBlobTx(tx []byte) (*BlobTx, bool) {
// NOTE: Any checks on the blobs or the transaction must be performed in the
// application
func MarshalBlobTx(tx []byte, blobs ...*Blob) ([]byte, error) {
if len(blobs) == 0 {
return nil, errors.New("at least one blob must be provided")
}
bTx := &BlobTx{
Tx: tx,
Blobs: blobs,
Blobs: blobsToProto(blobs),
TypeId: ProtoBlobTxTypeID,
}
return proto.Marshal(bTx)
}

func blobsToProto(blobs []*Blob) []*BlobProto {
pb := make([]*BlobProto, len(blobs))
for i, b := range blobs {
pb[i] = b.ToProto()
}
return pb
}

// Sort sorts the blobs by their namespace.
func Sort(blobs []*Blob) {
sort.SliceStable(blobs, func(i, j int) bool {
Expand Down
95 changes: 48 additions & 47 deletions blob/blob.pb.go

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

6 changes: 3 additions & 3 deletions blob/blob.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package pkg.blob;

option go_package = "github.com/celestiaorg/go-square/blob";

// Blob (named after binary large object) is a chunk of data submitted by a user
// BlobProto is the protobuf representation of a blob (binary large object)
// to be published to the Celestia blockchain. The data of a Blob is published
// to a namespace and is encoded into shares based on the format specified by
// share_version.
message Blob {
message BlobProto {
bytes namespace_id = 1;
bytes data = 2;
uint32 share_version = 3;
Expand All @@ -22,7 +22,7 @@ message Blob {
// using the relevant MsgPayForBlobs that is signed over in the encoded sdk.Tx.
message BlobTx {
bytes tx = 1;
repeated Blob blobs = 2;
repeated BlobProto blobs = 2;
string type_id = 3;
}

Expand Down
9 changes: 1 addition & 8 deletions inclusion/commitment.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,6 @@ type MerkleRootFn func([][]byte) []byte
// [data square layout rationale]: ../../specs/src/specs/data_square_layout.md
// [blob share commitment rules]: ../../specs/src/specs/data_square_layout.md#blob-share-commitment-rules
func CreateCommitment(blob *blob.Blob, merkleRootFn MerkleRootFn, subtreeRootThreshold int) ([]byte, error) {
if err := blob.Validate(); err != nil {
return nil, err
}
namespace, err := blob.Namespace()
if err != nil {
return nil, err
}

shares, err := sh.SplitBlobs(blob)
if err != nil {
return nil, err
Expand All @@ -46,6 +38,7 @@ func CreateCommitment(blob *blob.Blob, merkleRootFn MerkleRootFn, subtreeRootThr
cursor += treeSize
}

namespace := blob.Namespace()
// create the commitments by pushing each leaf set onto an NMT
subTreeRoots := make([][]byte, len(leafSets))
for i, set := range leafSets {
Expand Down
Loading

0 comments on commit 54314fc

Please sign in to comment.