Skip to content
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

feat!: create native blob struct #74

Merged
merged 11 commits into from
Jun 19, 2024
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 author of the blob)
cmwaters marked this conversation as resolved.
Show resolved Hide resolved
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.Bytes(),
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 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
Loading