github.com/celestiaorg/celestia-node@v0.15.0-beta.1/blob/blob.go (about) 1 package blob 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 9 tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 10 11 "github.com/celestiaorg/celestia-app/pkg/appconsts" 12 "github.com/celestiaorg/celestia-app/pkg/shares" 13 "github.com/celestiaorg/celestia-app/x/blob/types" 14 "github.com/celestiaorg/nmt" 15 16 "github.com/celestiaorg/celestia-node/share" 17 ) 18 19 // Commitment is a Merkle Root of the subtree built from shares of the Blob. 20 // It is computed by splitting the blob into shares and building the Merkle subtree to be included 21 // after Submit. 22 type Commitment []byte 23 24 func (com Commitment) String() string { 25 return string(com) 26 } 27 28 // Equal ensures that commitments are the same 29 func (com Commitment) Equal(c Commitment) bool { 30 return bytes.Equal(com, c) 31 } 32 33 // Proof is a collection of nmt.Proofs that verifies the inclusion of the data. 34 type Proof []*nmt.Proof 35 36 func (p Proof) Len() int { return len(p) } 37 38 func (p Proof) MarshalJSON() ([]byte, error) { 39 proofs := make([]string, 0, len(p)) 40 for _, proof := range p { 41 proofBytes, err := proof.MarshalJSON() 42 if err != nil { 43 return nil, err 44 } 45 proofs = append(proofs, string(proofBytes)) 46 } 47 return json.Marshal(proofs) 48 } 49 50 func (p *Proof) UnmarshalJSON(b []byte) error { 51 var proofs []string 52 if err := json.Unmarshal(b, &proofs); err != nil { 53 return err 54 } 55 for _, proof := range proofs { 56 var nmtProof nmt.Proof 57 if err := nmtProof.UnmarshalJSON([]byte(proof)); err != nil { 58 return err 59 } 60 *p = append(*p, &nmtProof) 61 } 62 return nil 63 } 64 65 // equal is a temporary method that compares two proofs. 66 // should be removed in BlobService V1. 67 func (p Proof) equal(input Proof) error { 68 if p.Len() != input.Len() { 69 return ErrInvalidProof 70 } 71 72 for i, proof := range p { 73 pNodes := proof.Nodes() 74 inputNodes := input[i].Nodes() 75 for i, node := range pNodes { 76 if !bytes.Equal(node, inputNodes[i]) { 77 return ErrInvalidProof 78 } 79 } 80 81 if proof.Start() != input[i].Start() || proof.End() != input[i].End() { 82 return ErrInvalidProof 83 } 84 85 if !bytes.Equal(proof.LeafHash(), input[i].LeafHash()) { 86 return ErrInvalidProof 87 } 88 89 } 90 return nil 91 } 92 93 // Blob represents any application-specific binary data that anyone can submit to Celestia. 94 type Blob struct { 95 types.Blob `json:"blob"` 96 97 Commitment Commitment `json:"commitment"` 98 99 // the celestia-node's namespace type 100 // this is to avoid converting to and from app's type 101 namespace share.Namespace 102 } 103 104 // NewBlobV0 constructs a new blob from the provided Namespace and data. 105 // The blob will be formatted as v0 shares. 106 func NewBlobV0(namespace share.Namespace, data []byte) (*Blob, error) { 107 return NewBlob(appconsts.ShareVersionZero, namespace, data) 108 } 109 110 // NewBlob constructs a new blob from the provided Namespace, data and share version. 111 func NewBlob(shareVersion uint8, namespace share.Namespace, data []byte) (*Blob, error) { 112 if len(data) == 0 || len(data) > appconsts.DefaultMaxBytes { 113 return nil, fmt.Errorf("blob data must be > 0 && <= %d, but it was %d bytes", appconsts.DefaultMaxBytes, len(data)) 114 } 115 if err := namespace.ValidateForBlob(); err != nil { 116 return nil, err 117 } 118 119 blob := tmproto.Blob{ 120 NamespaceId: namespace.ID(), 121 Data: data, 122 ShareVersion: uint32(shareVersion), 123 NamespaceVersion: uint32(namespace.Version()), 124 } 125 126 com, err := types.CreateCommitment(&blob) 127 if err != nil { 128 return nil, err 129 } 130 return &Blob{Blob: blob, Commitment: com, namespace: namespace}, nil 131 } 132 133 // Namespace returns blob's namespace. 134 func (b *Blob) Namespace() share.Namespace { 135 return b.namespace 136 } 137 138 type jsonBlob struct { 139 Namespace share.Namespace `json:"namespace"` 140 Data []byte `json:"data"` 141 ShareVersion uint32 `json:"share_version"` 142 Commitment Commitment `json:"commitment"` 143 } 144 145 func (b *Blob) MarshalJSON() ([]byte, error) { 146 blob := &jsonBlob{ 147 Namespace: b.Namespace(), 148 Data: b.Data, 149 ShareVersion: b.ShareVersion, 150 Commitment: b.Commitment, 151 } 152 return json.Marshal(blob) 153 } 154 155 func (b *Blob) UnmarshalJSON(data []byte) error { 156 var blob jsonBlob 157 err := json.Unmarshal(data, &blob) 158 if err != nil { 159 return err 160 } 161 162 b.Blob.NamespaceVersion = uint32(blob.Namespace.Version()) 163 b.Blob.NamespaceId = blob.Namespace.ID() 164 b.Blob.Data = blob.Data 165 b.Blob.ShareVersion = blob.ShareVersion 166 b.Commitment = blob.Commitment 167 b.namespace = blob.Namespace 168 return nil 169 } 170 171 // buildBlobsIfExist takes shares and tries building the Blobs from them. 172 // It will build blobs either until appShares will be empty or the first incomplete blob will 173 // appear, so in this specific case it will return all built blobs + remaining shares. 174 func buildBlobsIfExist(appShares []shares.Share) ([]*Blob, []shares.Share, error) { 175 if len(appShares) == 0 { 176 return nil, nil, errors.New("empty shares received") 177 } 178 blobs := make([]*Blob, 0, len(appShares)) 179 for { 180 length, err := appShares[0].SequenceLen() 181 if err != nil { 182 return nil, nil, err 183 } 184 185 amount := shares.SparseSharesNeeded(length) 186 if amount > len(appShares) { 187 return blobs, appShares, nil 188 } 189 190 b, err := parseShares(appShares[:amount]) 191 if err != nil { 192 return nil, nil, err 193 } 194 195 // only 1 blob will be created bc we passed the exact amount of shares 196 blobs = append(blobs, b[0]) 197 198 if amount == len(appShares) { 199 return blobs, nil, nil 200 } 201 appShares = appShares[amount:] 202 } 203 }