Skip to content

Commit

Permalink
Merge pull request #5548 from oasisprotocol/peternose/feature/keyform…
Browse files Browse the repository at this point in the history
…at-uniqueness

go/common/keyformat: Ensure consensus and runtime prefixes are unique
  • Loading branch information
peternose authored Feb 5, 2024
2 parents f05a1de + 191d346 commit cd75e3b
Show file tree
Hide file tree
Showing 26 changed files with 139 additions and 109 deletions.
1 change: 1 addition & 0 deletions .changelog/5548.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/common/keyformat: Ensure prefixes are unique per namespace
3 changes: 1 addition & 2 deletions go/common/crypto/signature/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,9 @@ func NewContext(rawContext string, opts ...ContextOption) Context {
}

ctx := Context(rawContext)
if _, isRegistered := registeredContexts.Load(ctx); isRegistered {
if _, isRegistered := registeredContexts.LoadOrStore(ctx, &opt); isRegistered {
panic("signature: context already registered: '" + ctx + "'")
}
registeredContexts.Store(ctx, &opt)

return ctx
}
Expand Down
3 changes: 1 addition & 2 deletions go/common/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,9 @@ func New(module string, code uint32, msg string) error {
}

key := errorKey(module, code)
if prev, isRegistered := registeredErrors.Load(key); isRegistered {
if prev, isRegistered := registeredErrors.LoadOrStore(key, e); isRegistered {
panic(fmt.Errorf("error: already registered: %s (existing: %s)", key, prev))
}
registeredErrors.Store(key, e)

return e
}
Expand Down
3 changes: 1 addition & 2 deletions go/common/grpc/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,9 @@ func (sn ServiceName) NewMethod(name string, requestType interface{}) *MethodDes
requestType: requestType,
}

if _, isRegistered := registeredMethods.Load(md.FullName()); isRegistered {
if _, isRegistered := registeredMethods.LoadOrStore(md.FullName(), md); isRegistered {
panic(fmt.Errorf("service: method already registered: %s", name))
}
registeredMethods.Store(md.FullName(), md)

return md
}
Expand Down
34 changes: 34 additions & 0 deletions go/common/keyformat/key_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding"
"encoding/binary"
"fmt"
"sync"
)

// CustomFormat specifies a custom encoding format for a key element.
Expand Down Expand Up @@ -33,6 +34,39 @@ func (m *elementMeta) checkSize(index, size int) {
}
}

// Namespace is a tool for constructing unique key formats within a namespace.
type Namespace struct {
namespace string

mu sync.Mutex
prefixes map[byte]struct{}
}

// NewNamespace constructs a new key format namespace.
func NewNamespace(namespace string) *Namespace {
return &Namespace{
namespace: namespace,
prefixes: make(map[byte]struct{}),
}
}

// New constructs a new key format.
//
// The prefix must be unique; otherwise, this method will panic.
func (f *Namespace) New(prefix byte, layout ...interface{}) *KeyFormat {
func() {
f.mu.Lock()
defer f.mu.Unlock()

if _, ok := f.prefixes[prefix]; ok {
panic(fmt.Errorf("key format: already registered: namespace %s, prefix 0x%x", f.namespace, prefix))
}
f.prefixes[prefix] = struct{}{}
}()

return New(prefix, layout...)
}

// KeyFormat is a key formatting helper to be used together with key-value
// backends for constructing keys.
type KeyFormat struct {
Expand Down
9 changes: 9 additions & 0 deletions go/common/keyformat/key_format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,12 @@ func TestVariableSize(t *testing.T) {
require.EqualValues(t, vsElem, decVs3, "decoded variable-sized element should have the same value")
require.EqualValues(t, h, decH4, "decoded hash should have the same value")
}

func TestNamespace(t *testing.T) {
ns := NewNamespace("namespace")
ns.New('T', []byte{}, &hash.Hash{})

require.Panics(t, func() {
ns.New('T', []byte{}, &hash.Hash{})
}, "should panic if duplicate prefix is registered")
}
4 changes: 4 additions & 0 deletions go/consensus/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-core/go/common/errors"
"github.com/oasisprotocol/oasis-core/go/common/keyformat"
"github.com/oasisprotocol/oasis-core/go/common/node"
"github.com/oasisprotocol/oasis-core/go/common/pubsub"
"github.com/oasisprotocol/oasis-core/go/common/service"
Expand Down Expand Up @@ -65,6 +66,9 @@ var (
SystemMethods = map[transaction.MethodName]struct{}{
MethodMeta: {},
}

// KeyFormat is the namespace for the consensus state key formats.
KeyFormat = keyformat.NewNamespace("consensus")
)

// FeatureMask is the consensus backend feature bitmask.
Expand Down
3 changes: 1 addition & 2 deletions go/consensus/api/transaction/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,10 +338,9 @@ func (m MethodName) IsCritical() bool {
func NewMethodName(module, method string, bodyType interface{}) MethodName {
// Check for duplicate method names.
name := module + MethodSeparator + method
if _, isRegistered := registeredMethods.Load(name); isRegistered {
if _, isRegistered := registeredMethods.LoadOrStore(name, bodyType); isRegistered {
panic(fmt.Errorf("transaction: method already registered: %s", name))
}
registeredMethods.Store(name, bodyType)

return MethodName(name)
}
Expand Down
6 changes: 3 additions & 3 deletions go/consensus/cometbft/abci/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"errors"

"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/keyformat"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
"github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
consensusGenesis "github.com/oasisprotocol/oasis-core/go/consensus/genesis"
"github.com/oasisprotocol/oasis-core/go/storage/mkvs"
Expand All @@ -15,11 +15,11 @@ var (
// chainContextKeyFmt is the key format used for storing the chain context.
//
// Value is the chain context.
chainContextKeyFmt = keyformat.New(0xF0)
chainContextKeyFmt = consensus.KeyFormat.New(0xF0)
// parametersKeyFmt is the key format used for consensus parameters.
//
// Value is CBOR-serialized consensusGenesis.Parameters.
parametersKeyFmt = keyformat.New(0xF1)
parametersKeyFmt = consensus.KeyFormat.New(0xF1)
)

// ImmutableState is an immutable consensus backend state wrapper.
Expand Down
12 changes: 6 additions & 6 deletions go/consensus/cometbft/apps/beacon/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/keyformat"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
"github.com/oasisprotocol/oasis-core/go/storage/mkvs"
)
Expand All @@ -16,24 +16,24 @@ var (
// epochCurrentKeyFmt is the current epoch key format.
//
// Value is CBOR-serialized epoch time state.
epochCurrentKeyFmt = keyformat.New(0x40)
epochCurrentKeyFmt = consensus.KeyFormat.New(0x40)
// epochFutureKeyFmt is the future epoch key format.
//
// Value is CBOR-serialized epoch time state.
epochFutureKeyFmt = keyformat.New(0x41)
epochFutureKeyFmt = consensus.KeyFormat.New(0x41)
// epochPendingMockKeyFmt is the pending mock epoch key format.
//
// Value is CBOR-serialized epoch time.
epochPendingMockKeyFmt = keyformat.New(0x45)
epochPendingMockKeyFmt = consensus.KeyFormat.New(0x45)

// beaconKeyFmt is the random beacon key format.
//
// Value is raw random beacon.
beaconKeyFmt = keyformat.New(0x42)
beaconKeyFmt = consensus.KeyFormat.New(0x42)
// parametersKeyFmt is the key format used for consensus parameters.
//
// Value is CBOR-serialized beacon.ConsensusParameters.
parametersKeyFmt = keyformat.New(0x43)
parametersKeyFmt = consensus.KeyFormat.New(0x43)
)

// ImmutableState is the immutable beacon state wrapper.
Expand Down
12 changes: 0 additions & 12 deletions go/consensus/cometbft/apps/beacon/state/state_deprecated.go

This file was deleted.

4 changes: 2 additions & 2 deletions go/consensus/cometbft/apps/beacon/state/state_vrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import (
beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-core/go/common/keyformat"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
)

// vrfStateKeyFmt is the current VRF state key format.
var vrfStateKeyFmt = keyformat.New(0x46)
var vrfStateKeyFmt = consensus.KeyFormat.New(0x46)

func (s *ImmutableState) VRFState(ctx context.Context) (*beacon.VRFState, error) {
data, err := s.is.Get(ctx, vrfStateKeyFmt.Encode())
Expand Down
14 changes: 7 additions & 7 deletions go/consensus/cometbft/apps/governance/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/keyformat"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
"github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
governance "github.com/oasisprotocol/oasis-core/go/governance/api"
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
Expand All @@ -18,35 +18,35 @@ var (
// nextProposalIdentifierKeyFmt is the key format used for the storing the next proposal identifier.
//
// Value is a CBOR-serialized uint64.
nextProposalIdentifierKeyFmt = keyformat.New(0x80)
nextProposalIdentifierKeyFmt = consensus.KeyFormat.New(0x80)

// proposalsKeyFmt is the key format used for the storing existing proposals.
//
// Key format is: 0x81 <proposal-id (uint64)>.
// Value is a CBOR-serialized governance.Proposal.
proposalsKeyFmt = keyformat.New(0x81, uint64(0))
proposalsKeyFmt = consensus.KeyFormat.New(0x81, uint64(0))

// activeProposalsKeyFmt is the key format used for the storing active proposals.
//
// Key format is: 0x82 <closes-at-epoch (uint64)> <proposal-id (uint64)>.
activeProposalsKeyFmt = keyformat.New(0x82, uint64(0), uint64(0))
activeProposalsKeyFmt = consensus.KeyFormat.New(0x82, uint64(0), uint64(0))

// votesKeyFmt is the key format used for the storing existing votes for proposals.
//
// Key format is: 0x83 <proposal-id (uint64)> <voter-address (staking.Address)>.
// Value is a CBOR-serialized governance.Vote.
votesKeyFmt = keyformat.New(0x83, uint64(0), &staking.Address{})
votesKeyFmt = consensus.KeyFormat.New(0x83, uint64(0), &staking.Address{})

// pendingUpgradesKeyFmt is the key format used for the storing pending upgrades.
//
// Key format is: 0x84 <upgrade-epoch (uint64)> <proposal-id (uint64)>.
pendingUpgradesKeyFmt = keyformat.New(0x84, uint64(0), uint64(0))
pendingUpgradesKeyFmt = consensus.KeyFormat.New(0x84, uint64(0), uint64(0))

// parametersKeyFmt is the key format used for consensus parameters.
//
// Key format is: 0x85.
// Value is CBOR-serialized governance.ConsensusParameters.
parametersKeyFmt = keyformat.New(0x85)
parametersKeyFmt = consensus.KeyFormat.New(0x85)
)

// ImmutableState is the immutable consensus state wrapper.
Expand Down
9 changes: 5 additions & 4 deletions go/consensus/cometbft/apps/keymanager/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/keyformat"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
"github.com/oasisprotocol/oasis-core/go/keymanager/api"
"github.com/oasisprotocol/oasis-core/go/storage/mkvs"
Expand All @@ -16,19 +17,19 @@ var (
// statusKeyFmt is the key manager status key format.
//
// Value is CBOR-serialized key manager status.
statusKeyFmt = keyformat.New(0x70, keyformat.H(&common.Namespace{}))
statusKeyFmt = consensus.KeyFormat.New(0x70, keyformat.H(&common.Namespace{}))
// parametersKeyFmt is the key format used for consensus parameters.
//
// Value is CBOR-serialized keymanager.ConsensusParameters.
parametersKeyFmt = keyformat.New(0x71)
parametersKeyFmt = consensus.KeyFormat.New(0x71)
// masterSecretKeyFmt is the key manager master secret key format.
//
// Value is CBOR-serialized key manager signed encrypted master secret.
masterSecretKeyFmt = keyformat.New(0x72, keyformat.H(&common.Namespace{}))
masterSecretKeyFmt = consensus.KeyFormat.New(0x72, keyformat.H(&common.Namespace{}))
// ephemeralSecretKeyFmt is the key manager ephemeral secret key format.
//
// Value is CBOR-serialized key manager signed encrypted ephemeral secret.
ephemeralSecretKeyFmt = keyformat.New(0x73, keyformat.H(&common.Namespace{}))
ephemeralSecretKeyFmt = consensus.KeyFormat.New(0x73, keyformat.H(&common.Namespace{}))
)

// ImmutableState is the immutable key manager state wrapper.
Expand Down
21 changes: 11 additions & 10 deletions go/consensus/cometbft/apps/registry/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/oasisprotocol/oasis-core/go/common/entity"
"github.com/oasisprotocol/oasis-core/go/common/keyformat"
"github.com/oasisprotocol/oasis-core/go/common/node"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
"github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
tmcrypto "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/crypto"
Expand All @@ -29,20 +30,20 @@ var (
// signedEntityKeyFmt is the key format used for signed entities.
//
// Value is CBOR-serialized signed entity.
signedEntityKeyFmt = keyformat.New(0x10, keyformat.H(&signature.PublicKey{}))
signedEntityKeyFmt = consensus.KeyFormat.New(0x10, keyformat.H(&signature.PublicKey{}))
// signedNodeKeyFmt is the key format used for signed nodes.
//
// Value is CBOR-serialized signed node.
signedNodeKeyFmt = keyformat.New(0x11, keyformat.H(&signature.PublicKey{}))
signedNodeKeyFmt = consensus.KeyFormat.New(0x11, keyformat.H(&signature.PublicKey{}))
// signedNodeByEntityKeyFmt is the key format used for signed node by entity
// index.
//
// Value is empty.
signedNodeByEntityKeyFmt = keyformat.New(0x12, keyformat.H(&signature.PublicKey{}), keyformat.H(&signature.PublicKey{}))
signedNodeByEntityKeyFmt = consensus.KeyFormat.New(0x12, keyformat.H(&signature.PublicKey{}), keyformat.H(&signature.PublicKey{}))
// runtimeKeyFmt is the key format used for runtimes.
//
// Value is CBOR-serialized runtime.
runtimeKeyFmt = keyformat.New(0x13, keyformat.H(&common.Namespace{}))
runtimeKeyFmt = consensus.KeyFormat.New(0x13, keyformat.H(&common.Namespace{}))
// nodeByConsAddressKeyFmt is the key format used for the consensus address to
// node public key mapping.
//
Expand All @@ -51,30 +52,30 @@ var (
// evidence instead of the actual public key.
//
// Value is binary node public key.
nodeByConsAddressKeyFmt = keyformat.New(0x14, []byte{})
nodeByConsAddressKeyFmt = consensus.KeyFormat.New(0x14, []byte{})
// nodeStatusKeyFmt is the key format used for node statuses.
//
// Value is CBOR-serialized node status.
nodeStatusKeyFmt = keyformat.New(0x15, keyformat.H(&signature.PublicKey{}))
nodeStatusKeyFmt = consensus.KeyFormat.New(0x15, keyformat.H(&signature.PublicKey{}))
// parametersKeyFmt is the key format used for consensus parameters.
//
// Value is CBOR-serialized registry.ConsensusParameters.
parametersKeyFmt = keyformat.New(0x16)
parametersKeyFmt = consensus.KeyFormat.New(0x16)
// keyMapKeyFmt is the key format used for key-to-node-id map.
//
// This stores the consensus, P2P and TLS public keys to node ID mappings.
//
// Value is binary signature.PublicKey (node ID).
keyMapKeyFmt = keyformat.New(0x17, keyformat.H(&signature.PublicKey{}))
keyMapKeyFmt = consensus.KeyFormat.New(0x17, keyformat.H(&signature.PublicKey{}))
// suspendedRuntimeKeyFmt is the key format used for suspended runtimes.
//
// Value is CBOR-serialized runtime.
suspendedRuntimeKeyFmt = keyformat.New(0x18, keyformat.H(&common.Namespace{}))
suspendedRuntimeKeyFmt = consensus.KeyFormat.New(0x18, keyformat.H(&common.Namespace{}))
// runtimeByEntityKeyFmt is the key format used for runtime by entity
// index.
//
// Value is empty.
runtimeByEntityKeyFmt = keyformat.New(0x19, keyformat.H(&signature.PublicKey{}), keyformat.H(&common.Namespace{}))
runtimeByEntityKeyFmt = consensus.KeyFormat.New(0x19, keyformat.H(&signature.PublicKey{}), keyformat.H(&common.Namespace{}))
)

// ImmutableState is the immutable registry state wrapper.
Expand Down
7 changes: 0 additions & 7 deletions go/consensus/cometbft/apps/registry/state/state_deprecated.go

This file was deleted.

Loading

0 comments on commit cd75e3b

Please sign in to comment.