-
Notifications
You must be signed in to change notification settings - Fork 932
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
share: introduce Namespace type (#2367)
Mostly copied from the app's Namespace type with multiple modifications. In preparation for #2301. We want a more straightforward Namespace type from what the app provides: * Repsents 1:1 mapping of the serialized form of Namespace [version byte + namespace id] * So that it does not allocate on the hot path when getting data by namespace * and had simpler json serialization
- Loading branch information
Showing
3 changed files
with
236 additions
and
0 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,151 @@ | ||
package share | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"fmt" | ||
|
||
appns "github.com/celestiaorg/celestia-app/pkg/namespace" | ||
"github.com/celestiaorg/nmt/namespace" | ||
) | ||
|
||
// Various reserved namespaces. | ||
var ( | ||
MaxReservedNamespace = Namespace(appns.MaxReservedNamespace.Bytes()) | ||
ParitySharesNamespace = Namespace(appns.ParitySharesNamespace.Bytes()) | ||
TailPaddingNamespace = Namespace(appns.TailPaddingNamespace.Bytes()) | ||
ReservedPaddingNamespace = Namespace(appns.ReservedPaddingNamespace.Bytes()) | ||
TxNamespace = Namespace(appns.TxNamespace.Bytes()) | ||
PayForBlobNamespace = Namespace(appns.PayForBlobNamespace.Bytes()) | ||
) | ||
|
||
// Namespace represents namespace of a Share. | ||
// Consists of version byte and namespace ID. | ||
type Namespace []byte | ||
|
||
// NamespaceFromBytes converts bytes into Namespace and validates it. | ||
func NamespaceFromBytes(b []byte) (Namespace, error) { | ||
n := Namespace(b) | ||
return n, n.Validate() | ||
} | ||
|
||
// Version reports version of the Namespace. | ||
func (n Namespace) Version() byte { | ||
return n[appns.NamespaceVersionSize-1] | ||
} | ||
|
||
// ID reports ID of the Namespace. | ||
func (n Namespace) ID() namespace.ID { | ||
return namespace.ID(n[appns.NamespaceVersionSize:]) | ||
} | ||
|
||
// ToNMT converts the whole Namespace(both Version and ID parts) into NMT's namespace.ID | ||
// NOTE: Once https://github.com/celestiaorg/nmt/issues/206 is closed Namespace should become NNT's | ||
// type. | ||
func (n Namespace) ToNMT() namespace.ID { | ||
return namespace.ID(n) | ||
} | ||
|
||
// ToAppNamespace converts the Namespace to App's definition of Namespace. | ||
// TODO: Unify types between node and app | ||
func (n Namespace) ToAppNamespace() appns.Namespace { | ||
return appns.Namespace{Version: n.Version(), ID: n.ID()} | ||
} | ||
|
||
// Len reports the total length of the namespace. | ||
func (n Namespace) Len() int { | ||
return len(n) | ||
} | ||
|
||
// String stringifies the Namespace. | ||
func (n Namespace) String() string { | ||
return hex.EncodeToString(n) | ||
} | ||
|
||
// Equals compares two Namespaces. | ||
func (n Namespace) Equals(target Namespace) bool { | ||
return bytes.Equal(n, target) | ||
} | ||
|
||
// Validate checks if the namespace is correct. | ||
func (n Namespace) Validate() error { | ||
if n.Len() != NamespaceSize { | ||
return fmt.Errorf("invalid namespace length: expected %d, got %d", NamespaceSize, n.Len()) | ||
} | ||
if n.Version() != appns.NamespaceVersionZero && n.Version() != appns.NamespaceVersionMax { | ||
return fmt.Errorf("invalid namespace version %v", n.Version()) | ||
} | ||
if len(n.ID()) != appns.NamespaceIDSize { | ||
return fmt.Errorf("invalid namespace id length: expected %d, got %d", appns.NamespaceIDSize, n.ID().Size()) | ||
} | ||
if n.Version() == appns.NamespaceVersionZero && !bytes.HasPrefix(n.ID(), appns.NamespaceVersionZeroPrefix) { | ||
return fmt.Errorf("invalid namespace id: expect %d leading zeroes", len(appns.NamespaceVersionZeroPrefix)) | ||
} | ||
return nil | ||
} | ||
|
||
// ValidateDataNamespace checks if the Namespace contains real/useful data. | ||
func (n Namespace) ValidateDataNamespace() error { | ||
if err := n.Validate(); err != nil { | ||
return err | ||
} | ||
if n.Equals(ParitySharesNamespace) || n.Equals(TailPaddingNamespace) { | ||
return fmt.Errorf("invalid data namespace(%s): parity and tail padding namespace are forbidden", n) | ||
} | ||
return nil | ||
} | ||
|
||
// ValidateBlobNamespace checks if the Namespace is valid blob namespace. | ||
func (n Namespace) ValidateBlobNamespace() error { | ||
if err := n.ValidateDataNamespace(); err != nil { | ||
return err | ||
} | ||
if bytes.Compare(n, MaxReservedNamespace) < 1 { | ||
return fmt.Errorf("invalid blob namespace(%s): reserved namespaces are forbidden", n) | ||
} | ||
return nil | ||
} | ||
|
||
// IsAboveMax checks if the namespace is above the maximum namespace of the given hash. | ||
func (n Namespace) IsAboveMax(nodeHash []byte) bool { | ||
return !n.IsLessOrEqual(nodeHash[n.Len() : n.Len()*2]) | ||
} | ||
|
||
// IsBelowMin checks if the target namespace is below the minimum namespace of the given hash. | ||
func (n Namespace) IsBelowMin(nodeHash []byte) bool { | ||
return n.IsLess(nodeHash[:n.Len()]) | ||
} | ||
|
||
// IsOutsideRange checks if the namespace is outside the min-max range of the given hashes. | ||
func (n Namespace) IsOutsideRange(leftNodeHash, rightNodeHash []byte) bool { | ||
return n.IsBelowMin(leftNodeHash) || n.IsAboveMax(rightNodeHash) | ||
} | ||
|
||
// Repeat copies the Namespace t times. | ||
func (n Namespace) Repeat(t int) []Namespace { | ||
ns := make([]Namespace, t) | ||
for i := 0; i < t; i++ { | ||
ns[i] = n | ||
} | ||
return ns | ||
} | ||
|
||
// IsLess reports if the Namespace is less than the target. | ||
func (n Namespace) IsLess(target Namespace) bool { | ||
return bytes.Compare(n, target) == -1 | ||
} | ||
|
||
// IsLessOrEqual reports if the Namespace is less than the target. | ||
func (n Namespace) IsLessOrEqual(target Namespace) bool { | ||
return bytes.Compare(n, target) < 1 | ||
} | ||
|
||
// IsGreater reports if the Namespace is greater than the target. | ||
func (n Namespace) IsGreater(target Namespace) bool { | ||
return bytes.Compare(n, target) == 1 | ||
} | ||
|
||
// IsGreaterOrEqualThan reports if the Namespace is greater or equal than the target. | ||
func (n Namespace) IsGreaterOrEqualThan(target Namespace) bool { | ||
return bytes.Compare(n, target) > -1 | ||
} |
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,84 @@ | ||
package share | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
appns "github.com/celestiaorg/celestia-app/pkg/namespace" | ||
) | ||
|
||
var ( | ||
validID = append( | ||
appns.NamespaceVersionZeroPrefix, | ||
bytes.Repeat([]byte{1}, appns.NamespaceVersionZeroIDSize)..., | ||
) | ||
tooShortID = append(appns.NamespaceVersionZeroPrefix, []byte{1}...) | ||
tooLongID = append(appns.NamespaceVersionZeroPrefix, bytes.Repeat([]byte{1}, NamespaceSize)...) | ||
invalidPrefixID = bytes.Repeat([]byte{1}, NamespaceSize) | ||
) | ||
|
||
func TestFrom(t *testing.T) { | ||
type testCase struct { | ||
name string | ||
bytes []byte | ||
wantErr bool | ||
want Namespace | ||
} | ||
validNamespace := []byte{} | ||
validNamespace = append(validNamespace, appns.NamespaceVersionZero) | ||
validNamespace = append(validNamespace, appns.NamespaceVersionZeroPrefix...) | ||
validNamespace = append(validNamespace, bytes.Repeat([]byte{0x1}, appns.NamespaceVersionZeroIDSize)...) | ||
parityNamespace := bytes.Repeat([]byte{0xFF}, NamespaceSize) | ||
|
||
testCases := []testCase{ | ||
{ | ||
name: "valid namespace", | ||
bytes: validNamespace, | ||
wantErr: false, | ||
want: append([]byte{appns.NamespaceVersionZero}, validID...), | ||
}, | ||
{ | ||
name: "parity namespace", | ||
bytes: parityNamespace, | ||
wantErr: false, | ||
want: append([]byte{appns.NamespaceVersionMax}, bytes.Repeat([]byte{0xFF}, appns.NamespaceIDSize)...), | ||
}, | ||
{ | ||
name: "unsupported version", | ||
bytes: append([]byte{1}, append( | ||
appns.NamespaceVersionZeroPrefix, | ||
bytes.Repeat([]byte{1}, NamespaceSize-len(appns.NamespaceVersionZeroPrefix))..., | ||
)...), | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "unsupported id: too short", | ||
bytes: append([]byte{appns.NamespaceVersionZero}, tooShortID...), | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "unsupported id: too long", | ||
bytes: append([]byte{appns.NamespaceVersionZero}, tooLongID...), | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "unsupported id: invalid prefix", | ||
bytes: append([]byte{appns.NamespaceVersionZero}, invalidPrefixID...), | ||
wantErr: true, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
got, err := NamespaceFromBytes(tc.bytes) | ||
if tc.wantErr { | ||
assert.Error(t, err) | ||
return | ||
} | ||
assert.NoError(t, err) | ||
assert.Equal(t, tc.want, got) | ||
}) | ||
} | ||
} |
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