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!: reserve the last 256 namespaces for protocol use (backport #2257) #2261

Merged
merged 7 commits into from
Aug 15, 2023
53 changes: 34 additions & 19 deletions pkg/namespace/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,35 +36,50 @@ var (
// NamespaceVersionZeroPrefix is the prefix of a namespace ID for version 0.
NamespaceVersionZeroPrefix = bytes.Repeat([]byte{0}, NamespaceVersionZeroPrefixSize)

// TxNamespace is the namespace reserved for transaction data.
TxNamespace = MustNewV0([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
// TxNamespace is the namespace reserved for ordinary Cosmos SDK transactions.
TxNamespace = primaryReservedNamespace(0x01)

// IntermediateStateRootsNamespace is the namespace reserved for
// intermediate state root data.
IntermediateStateRootsNamespace = MustNewV0([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 2})
IntermediateStateRootsNamespace = primaryReservedNamespace(0x02)

// PayForBlobNamespace is the namespace reserved for PayForBlobs transactions.
PayForBlobNamespace = MustNewV0([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 4})
PayForBlobNamespace = primaryReservedNamespace(0x04)

// ReservedPaddingNamespace is the namespace used for padding after all
// reserved namespaces. In practice this padding is after transactions
// (ordinary and PFBs) but before blobs.
ReservedPaddingNamespace = MustNewV0([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 255})
// PrimaryReservedPaddingNamespace is the namespace used for padding after all
// primary reserved namespaces.
PrimaryReservedPaddingNamespace = primaryReservedNamespace(0xFF)

// MaxReservedNamespace is lexicographically the largest namespace that is
// reserved for protocol use.
MaxReservedNamespace = MustNewV0([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 255})
// MaxPrimaryReservedNamespace is the highest primary reserved namespace.
// Namespaces lower than this are reserved for protocol use.
MaxPrimaryReservedNamespace = primaryReservedNamespace(0xFF)

// MinSecondaryReservedNamespace is the lowest secondary reserved namespace
// reserved for protocol use. Namespaces higher than this are reserved for
// protocol use.
MinSecondaryReservedNamespace = secondaryReservedNamespace(0x00)

// TailPaddingNamespace is the namespace reserved for tail padding. All data
// with this namespace will be ignored.
TailPaddingNamespace = Namespace{
Version: math.MaxUint8,
ID: append(bytes.Repeat([]byte{0xFF}, NamespaceIDSize-1), 0xFE),
}
TailPaddingNamespace = secondaryReservedNamespace(0xFE)

// ParitySharesNamespace is the namespace reserved for erasure coded data.
ParitySharesNamespace = Namespace{
Version: math.MaxUint8,
ID: bytes.Repeat([]byte{0xFF}, NamespaceIDSize),
}
ParitySharesNamespace = secondaryReservedNamespace(0xFF)

// SupportedBlobNamespaceVersions is a list of namespace versions that can be specified by a user for blobs.
SupportedBlobNamespaceVersions = []uint8{NamespaceVersionZero}
)

func primaryReservedNamespace(lastByte byte) Namespace {
return Namespace{
Version: NamespaceVersionZero,
ID: append(bytes.Repeat([]byte{0x00}, NamespaceIDSize-1), lastByte),
}
}

func secondaryReservedNamespace(lastByte byte) Namespace {
return Namespace{
Version: NamespaceVersionMax,
ID: append(bytes.Repeat([]byte{0xFF}, NamespaceIDSize-1), lastByte),
}
}
38 changes: 15 additions & 23 deletions pkg/namespace/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Namespace struct {

// New returns a new namespace with the provided version and id.
func New(version uint8, id []byte) (Namespace, error) {
err := validateVersion(version)
err := validateVersionSupported(version)
if err != nil {
return Namespace{}, err
}
Expand Down Expand Up @@ -83,25 +83,8 @@ func (n Namespace) Bytes() []byte {
return append([]byte{n.Version}, n.ID...)
}

// ValidateBlobNamespace returns an error if this namespace is not a valid blob namespace.
func (n Namespace) ValidateBlobNamespace() error {
if n.IsReserved() {
return fmt.Errorf("invalid blob namespace: %v cannot use a reserved namespace ID, want > %v", n.Bytes(), MaxReservedNamespace.Bytes())
}

if n.IsParityShares() {
return fmt.Errorf("invalid blob namespace: %v cannot use parity shares namespace ID", n.Bytes())
}

if n.IsTailPadding() {
return fmt.Errorf("invalid blob namespace: %v cannot use tail padding namespace ID", n.Bytes())
}

return nil
}

// validateVersion returns an error if the version is not supported.
func validateVersion(version uint8) error {
// validateVersionSupported returns an error if the version is not supported.
func validateVersionSupported(version uint8) error {
if version != NamespaceVersionZero && version != NamespaceVersionMax {
return fmt.Errorf("unsupported namespace version %v", version)
}
Expand All @@ -121,8 +104,17 @@ func validateID(version uint8, id []byte) error {
return nil
}

// IsReserved returns true if the namespace is reserved for protocol-use.
func (n Namespace) IsReserved() bool {
return bytes.Compare(n.Bytes(), MaxReservedNamespace.Bytes()) < 1
return n.IsPrimaryReserved() || n.IsSecondaryReserved()
}

func (n Namespace) IsPrimaryReserved() bool {
return n.IsLessOrEqualThan(MaxPrimaryReservedNamespace)
}

func (n Namespace) IsSecondaryReserved() bool {
return n.IsGreaterOrEqualThan(MinSecondaryReservedNamespace)
}

func (n Namespace) IsParityShares() bool {
Expand All @@ -133,8 +125,8 @@ func (n Namespace) IsTailPadding() bool {
return bytes.Equal(n.Bytes(), TailPaddingNamespace.Bytes())
}

func (n Namespace) IsReservedPadding() bool {
return bytes.Equal(n.Bytes(), ReservedPaddingNamespace.Bytes())
func (n Namespace) IsPrimaryReservedPadding() bool {
return bytes.Equal(n.Bytes(), PrimaryReservedPaddingNamespace.Bytes())
}

func (n Namespace) IsTx() bool {
Expand Down
89 changes: 89 additions & 0 deletions pkg/namespace/namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package namespace

import (
"bytes"
"math"
"reflect"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -195,3 +197,90 @@ func TestBytes(t *testing.T) {

assert.Equal(t, want, got)
}

func TestLeftPad(t *testing.T) {
tests := []struct {
input []byte
size int
expected []byte
}{
// input smaller than pad size
{[]byte{1, 2, 3}, 10, []byte{0, 0, 0, 0, 0, 0, 0, 1, 2, 3}},
{[]byte{1}, 5, []byte{0, 0, 0, 0, 1}},
{[]byte{1, 2}, 4, []byte{0, 0, 1, 2}},

// input equal to pad size
{[]byte{1, 2, 3}, 3, []byte{1, 2, 3}},
{[]byte{1, 2, 3, 4}, 4, []byte{1, 2, 3, 4}},

// input larger than pad size
{[]byte{1, 2, 3, 4, 5}, 4, []byte{1, 2, 3, 4, 5}},
{[]byte{1, 2, 3, 4, 5, 6, 7}, 3, []byte{1, 2, 3, 4, 5, 6, 7}},

// input size 0
{[]byte{}, 8, []byte{0, 0, 0, 0, 0, 0, 0, 0}},
{[]byte{}, 0, []byte{}},
}

for _, test := range tests {
result := leftPad(test.input, test.size)
assert.True(t, reflect.DeepEqual(result, test.expected))
}
}

func TestIsReserved(t *testing.T) {
type testCase struct {
ns Namespace
want bool
}
testCases := []testCase{
{
ns: MustNewV0(bytes.Repeat([]byte{1}, NamespaceVersionZeroIDSize)),
want: false,
},
{
ns: TxNamespace,
want: true,
},
{
ns: IntermediateStateRootsNamespace,
want: true,
},
{
ns: PayForBlobNamespace,
want: true,
},
{
ns: PrimaryReservedPaddingNamespace,
want: true,
},
{
ns: MaxPrimaryReservedNamespace,
want: true,
},
{
ns: MinSecondaryReservedNamespace,
want: true,
},
{
ns: TailPaddingNamespace,
want: true,
},
{
ns: ParitySharesNamespace,
want: true,
},
{
ns: Namespace{
Version: math.MaxUint8,
ID: append(bytes.Repeat([]byte{0xFF}, NamespaceIDSize-1), 1),
},
want: true,
},
}

for _, tc := range testCases {
got := tc.ns.IsReserved()
assert.Equal(t, tc.want, got)
}
}
21 changes: 17 additions & 4 deletions pkg/namespace/random_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package namespace

import (
tmrand "github.com/tendermint/tendermint/libs/rand"
"golang.org/x/exp/slices"
)

func RandomBlobNamespaceID() []byte {
Expand All @@ -22,11 +23,9 @@ func RandomBlobNamespaceWithPRG(prg *tmrand.Rand) Namespace {
for {
id := RandomBlobNamespaceIDWithPRG(prg)
namespace := MustNewV0(id)
err := namespace.ValidateBlobNamespace()
if err != nil {
continue
if isBlobNamespace(namespace) {
return namespace
}
return namespace
}
}

Expand All @@ -36,3 +35,17 @@ func RandomBlobNamespaces(rand *tmrand.Rand, count int) (namespaces []Namespace)
}
return namespaces
}

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

if !slices.Contains(SupportedBlobNamespaceVersions, ns.Version) {
return false
}

return true
}
21 changes: 11 additions & 10 deletions pkg/shares/padding.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import (

// NamespacePaddingShare returns a share that acts as padding. Namespace padding
// shares follow a blob so that the next blob may start at an index that
// conforms to blob share commitment rules. The ns parameter provided should
// be the namespace of the blob that precedes this padding in the data square.
func NamespacePaddingShare(ns appns.Namespace) (Share, error) {
b, err := NewBuilder(ns, appconsts.ShareVersionZero, true).Init()
// conforms to blob share commitment rules. The ns and shareVersion parameters
// provided should be the namespace and shareVersion of the blob that precedes
// this padding in the data square.
func NamespacePaddingShare(ns appns.Namespace, shareVersion uint8) (Share, error) {
b, err := NewBuilder(ns, shareVersion, true).Init()
if err != nil {
return Share{}, err
}
Expand All @@ -32,14 +33,14 @@ func NamespacePaddingShare(ns appns.Namespace) (Share, error) {
}

// NamespacePaddingShares returns n namespace padding shares.
func NamespacePaddingShares(ns appns.Namespace, n int) ([]Share, error) {
func NamespacePaddingShares(ns appns.Namespace, shareVersion uint8, n int) ([]Share, error) {
var err error
if n < 0 {
return nil, errors.New("n must be positive")
}
shares := make([]Share, n)
for i := 0; i < n; i++ {
shares[i], err = NamespacePaddingShare(ns)
shares[i], err = NamespacePaddingShare(ns, shareVersion)
if err != nil {
return shares, err
}
Expand All @@ -52,7 +53,7 @@ func NamespacePaddingShares(ns appns.Namespace, n int) ([]Share, error) {
// first blob can start at an index that conforms to non-interactive default
// rules.
func ReservedPaddingShare() Share {
share, err := NamespacePaddingShare(appns.ReservedPaddingNamespace)
share, err := NamespacePaddingShare(appns.PrimaryReservedPaddingNamespace, appconsts.ShareVersionZero)
if err != nil {
panic(err)
}
Expand All @@ -61,7 +62,7 @@ func ReservedPaddingShare() Share {

// ReservedPaddingShare returns n reserved padding shares.
func ReservedPaddingShares(n int) []Share {
shares, err := NamespacePaddingShares(appns.ReservedPaddingNamespace, n)
shares, err := NamespacePaddingShares(appns.PrimaryReservedPaddingNamespace, appconsts.ShareVersionZero, n)
if err != nil {
panic(err)
}
Expand All @@ -72,7 +73,7 @@ func ReservedPaddingShares(n int) []Share {
// square size. Tail padding shares follow the last blob share in the data
// square.
func TailPaddingShare() Share {
share, err := NamespacePaddingShare(appns.TailPaddingNamespace)
share, err := NamespacePaddingShare(appns.TailPaddingNamespace, appconsts.ShareVersionZero)
if err != nil {
panic(err)
}
Expand All @@ -81,7 +82,7 @@ func TailPaddingShare() Share {

// TailPaddingShares returns n tail padding shares.
func TailPaddingShares(n int) []Share {
shares, err := NamespacePaddingShares(appns.TailPaddingNamespace, n)
shares, err := NamespacePaddingShares(appns.TailPaddingNamespace, appconsts.ShareVersionZero, n)
if err != nil {
panic(err)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/shares/padding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var nsOnePadding, _ = zeroPadIfNecessary(

var reservedPadding, _ = zeroPadIfNecessary(
append(
appns.ReservedPaddingNamespace.Bytes(),
appns.PrimaryReservedPaddingNamespace.Bytes(),
[]byte{
1, // info byte
0, 0, 0, 0, // sequence len
Expand All @@ -40,13 +40,13 @@ var tailPadding, _ = zeroPadIfNecessary(
), appconsts.ShareSize)

func TestNamespacePaddingShare(t *testing.T) {
got, err := NamespacePaddingShare(ns1)
got, err := NamespacePaddingShare(ns1, appconsts.ShareVersionZero)
assert.NoError(t, err)
assert.Equal(t, nsOnePadding, got.ToBytes())
}

func TestNamespacePaddingShares(t *testing.T) {
shares, err := NamespacePaddingShares(ns1, 2)
shares, err := NamespacePaddingShares(ns1, appconsts.ShareVersionZero, 2)
assert.NoError(t, err)
for _, share := range shares {
assert.Equal(t, nsOnePadding, share.ToBytes())
Expand Down
6 changes: 3 additions & 3 deletions pkg/shares/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestParseShares(t *testing.T) {
// because it takes more than one share to store a sequence of 1000 bytes
tooLargeSequenceLen := generateRawShare(t, ns1, true, uint32(1000))

ns1Padding, err := NamespacePaddingShare(ns1)
ns1Padding, err := NamespacePaddingShare(ns1, appconsts.ShareVersionZero)
require.NoError(t, err)

type testCase struct {
Expand Down Expand Up @@ -162,11 +162,11 @@ func TestParseShares(t *testing.T) {
ignorePadding: false,
want: []ShareSequence{
{
Namespace: appns.ReservedPaddingNamespace,
Namespace: appns.PrimaryReservedPaddingNamespace,
Shares: []Share{ReservedPaddingShare()},
},
{
Namespace: appns.ReservedPaddingNamespace,
Namespace: appns.PrimaryReservedPaddingNamespace,
Shares: []Share{ReservedPaddingShare()},
},
},
Expand Down
Loading
Loading