Skip to content

Commit

Permalink
feat: add missed functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
vgonkivs committed Oct 7, 2024
1 parent a446cee commit 1359db0
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 10 deletions.
18 changes: 18 additions & 0 deletions share/blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package share
import (
"errors"
"fmt"
"slices"
"sort"

v1 "github.com/celestiaorg/go-square/v2/proto/blob/v1"
Expand Down Expand Up @@ -149,3 +150,20 @@ func SortBlobs(blobs []*Blob) {
return blobs[i].Compare(blobs[j]) < 0
})
}

// IsBlobNamespace returns a true if this namespace is a valid user-specifiable
// blob namespace.
func IsBlobNamespace(ns Namespace) bool {
if ns.IsReserved() {
return false
}

if !ns.IsUsableNamespace() {
return false
}

if !slices.Contains(SupportedBlobNamespaceVersions, ns.Version()) {
return false
}
return true
}
63 changes: 63 additions & 0 deletions share/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,35 @@ package share

import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
)

type Namespace struct {
data []byte
}

// MarshalJSON encodes namespace to the json encoded bytes.
func (n Namespace) MarshalJSON() ([]byte, error) {
return json.Marshal(n.data)
}

// MarshalJSON decodes json bytes to the namespace.
func (n *Namespace) UnmarshalJSON(data []byte) error {
var buf []byte
if err := json.Unmarshal(data, &buf); err != nil {
return err
}

ns, err := NewNamespaceFromBytes(buf)
if err != nil {
return err
}
*n = ns
return nil
}

// NewNamespace validates the provided version and id and returns a new namespace.
// This should be used for user specified namespaces.
func NewNamespace(version uint8, id []byte) (Namespace, error) {
Expand Down Expand Up @@ -92,6 +114,11 @@ func (n Namespace) ID() []byte {
return n.data[NamespaceVersionSize:]
}

// String stringifies the Namespace.
func (n Namespace) String() string {
return hex.EncodeToString(n.data)
}

// ValidateUserNamespace returns an error if the provided version is not
// supported or the provided id does not meet the requirements
// for the provided version. This should be used for validating
Expand All @@ -104,6 +131,17 @@ func ValidateUserNamespace(version uint8, id []byte) error {
return validateID(version, id)
}

// ValidateForData checks if the Namespace is of real/useful data.
func ValidateForData(n Namespace) error {
if err := ValidateUserNamespace(n.Version(), n.ID()); err != nil {
return err
}
if !n.IsUsableNamespace() {
return fmt.Errorf("invalid data namespace(%s): parity and tail padding namespace are forbidden", n)
}
return nil
}

// validateVersionSupported returns an error if the version is not supported.
func validateVersionSupported(version uint8) error {
if version != NamespaceVersionZero && version != NamespaceVersionMax {
Expand Down Expand Up @@ -203,6 +241,31 @@ func (n Namespace) Compare(n2 Namespace) int {
return bytes.Compare(n.data, n2.data)
}

// IsOutsideRange checks if the namespace is outside the min-max range of the given hashes.
func (n Namespace) IsOutsideRange(leftHash, rightHash []byte) bool {
if len(leftHash) < NamespaceSize || len(rightHash) < 2*NamespaceSize {
return false
}
return n.IsLessThan(Namespace{data: leftHash[:NamespaceSize]}) ||
!n.IsLessOrEqualThan(Namespace{data: rightHash[NamespaceSize : NamespaceSize*2]})
}

// IsAboveMax checks if the namespace is above the maximum namespace of the given hash.
func (n Namespace) IsAboveMax(hash []byte) bool {
if len(hash) < 2*NamespaceSize {
return false
}
return !n.IsLessOrEqualThan(Namespace{data: hash[NamespaceSize : NamespaceSize*2]})
}

// IsBelowMin checks if the target namespace is below the minimum namespace of the given hash.
func (n Namespace) IsBelowMin(hash []byte) bool {
if len(hash) < NamespaceSize {
return false
}
return n.IsLessThan(Namespace{data: hash[:NamespaceSize]})
}

// leftPad returns a new byte slice with the provided byte slice left-padded to the provided size.
// If the provided byte slice is already larger than the provided size, the original byte slice is returned.
func leftPad(b []byte, size int) []byte {
Expand Down
12 changes: 12 additions & 0 deletions share/namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,18 @@ func Test_compareMethods(t *testing.T) {
}
}

func TestMarshalNamespace(t *testing.T) {
ns := RandomNamespace()
b, err := ns.MarshalJSON()
require.NoError(t, err)

newNs := Namespace{}
err = newNs.UnmarshalJSON(b)
require.NoError(t, err)

require.Equal(t, ns, newNs)
}

func BenchmarkEqual(b *testing.B) {
n1 := RandomNamespace()
n2 := RandomNamespace()
Expand Down
54 changes: 44 additions & 10 deletions share/random_namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package share

import (
"crypto/rand"
"slices"
"encoding/binary"
"errors"
)

func RandomNamespace() Namespace {
Expand Down Expand Up @@ -38,22 +39,55 @@ func RandomBlobNamespace() Namespace {
for {
id := RandomBlobNamespaceID()
namespace := MustNewV0Namespace(id)
if isBlobNamespace(namespace) {
if IsBlobNamespace(namespace) {
return namespace
}
}
}

// isBlobNamespace returns an true if this namespace is a valid user-specifiable
// blob namespace.
func isBlobNamespace(ns Namespace) bool {
if ns.IsReserved() {
return false
// AddInt adds arbitrary int value to namespace, treating namespace as big-endian
// implementation of int
func AddInt(n Namespace, val int) (Namespace, error) {
if val == 0 {
return n, nil
}
// Convert the input integer to a byte slice and add it to result slice
result := make([]byte, NamespaceSize)
if val > 0 {
binary.BigEndian.PutUint64(result[NamespaceSize-8:], uint64(val))
} else {
binary.BigEndian.PutUint64(result[NamespaceSize-8:], uint64(-val))
}

// Perform addition byte by byte
var carry int
nn := n.Bytes()
for i := NamespaceSize - 1; i >= 0; i-- {
var sum int
if val > 0 {
sum = int(nn[i]) + int(result[i]) + carry
} else {
sum = int(nn[i]) - int(result[i]) + carry
}

switch {
case sum > 255:
carry = 1
sum -= 256
case sum < 0:
carry = -1
sum += 256
default:
carry = 0
}

result[i] = uint8(sum)
}

if !slices.Contains(SupportedBlobNamespaceVersions, ns.Version()) {
return false
// Handle any remaining carry
if carry != 0 {
return Namespace{}, errors.New("namespace overflow")
}

return true
return Namespace{data: result}, nil
}
69 changes: 69 additions & 0 deletions share/random_shares.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package share

import (
"bytes"
"crypto/rand"
"fmt"
"sort"
)

// RandShares generate 'total' amount of shares filled with random data.
func RandShares(total int) []Share {
if total&(total-1) != 0 {
panic(fmt.Errorf("total must be power of 2: %d", total))
}

shares := make([]Share, total)
for i := range shares {
shr := make([]byte, ShareSize)
copy(shr[:NamespaceSize], RandomNamespace().Bytes())
if _, err := rand.Read(shr[NamespaceSize:]); err != nil {
panic(err)
}

sh, err := NewShare(shr)
if err != nil {
panic(err)
}
if err = ValidateForData(sh.Namespace()); err != nil {
panic(err)
}

shares[i] = *sh
}
sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i].ToBytes(), shares[j].ToBytes()) < 0 })
return shares
}

// RandSharesWithNamespace is same the as RandShares, but sets same namespace for all shares.
func RandSharesWithNamespace(namespace Namespace, namespacedAmount, total int) []Share {
if total&(total-1) != 0 {
panic(fmt.Errorf("total must be power of 2: %d", total))
}

if namespacedAmount > total {
panic(fmt.Errorf("withNamespace must be less than total: %d", total))
}

shares := make([]Share, total)
for i := range shares {
shr := make([]byte, ShareSize)
if i < namespacedAmount {
copy(shr[:NamespaceSize], namespace.Bytes())
} else {
copy(shr[:NamespaceSize], RandomNamespace().Bytes())
}
_, err := rand.Read(shr[NamespaceSize:])
if err != nil {
panic(err)
}

sh, err := NewShare(shr)
if err != nil {
panic(err)
}
shares[i] = *sh
}
sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i].ToBytes(), shares[j].ToBytes()) < 0 })
return shares
}
15 changes: 15 additions & 0 deletions share/share.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package share
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
)

Expand All @@ -17,6 +18,20 @@ type Share struct {
data []byte
}

func (s Share) MarshalJSON() ([]byte, error) {
return json.Marshal(s.data)
}

func (s *Share) UnmarshalJSON(data []byte) error {
var buf []byte

if err := json.Unmarshal(data, &buf); err != nil {
return err
}
s.data = buf
return validateSize(s.data)
}

// NewShare creates a new share from the raw data, validating it's
// size and versioning
func NewShare(data []byte) (*Share, error) {
Expand Down
12 changes: 12 additions & 0 deletions share/share_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,15 @@ func TestShareToBytesAndFromBytes(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, shares, reconstructedShares)
}

func TestMarshalShare(t *testing.T) {
sh := RandShares(1)
b, err := sh[0].MarshalJSON()
require.NoError(t, err)

newShare := Share{}
err = newShare.UnmarshalJSON(b)
require.NoError(t, err)

require.Equal(t, sh[0], newShare)
}

0 comments on commit 1359db0

Please sign in to comment.