Skip to content

Commit

Permalink
Merge pull request #4916 from oasisprotocol/peternose/feature/freshne…
Browse files Browse the repository at this point in the history
…ss-proofs

registry: Add ProveFreshness consensus layer transaction
  • Loading branch information
peternose authored Sep 1, 2022
2 parents 2e768ce + 7a87fdb commit 0807389
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .changelog/4916.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
registry: Add ProveFreshness consensus layer transaction

Introducing new transaction that accepts a fixed-size binary blob of 32 bytes
and always succeeds without doing any processing or state changes. Transaction
is needed for client node TEE freshness verification and enabled via
freshness_proofs parameter located in tee_features consensus parameter group.
4 changes: 4 additions & 0 deletions go/common/node/tee.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import "github.com/oasisprotocol/oasis-core/go/common/sgx/quote"
type TEEFeatures struct {
// SGX contains the supported TEE features for Intel SGX.
SGX TEEFeaturesSGX `json:"sgx"`

// FreshnessProofs is a feature flag specifying whether ProveFreshness transactions are
// supported and processed, or ignored and handled as non-existing transactions.
FreshnessProofs bool `json:"freshness_proofs"`
}

// TEEFeaturesSGX are the supported Intel SGX-specific TEE features.
Expand Down
21 changes: 18 additions & 3 deletions go/consensus/tendermint/apps/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,24 +94,25 @@ func (app *registryApplication) ExecuteTx(ctx *api.Context, tx *transaction.Tran
if err := cbor.Unmarshal(tx.Body, &sigEnt); err != nil {
return err
}

return app.registerEntity(ctx, state, &sigEnt)

case registry.MethodDeregisterEntity:
return app.deregisterEntity(ctx, state)

case registry.MethodRegisterNode:
var sigNode node.MultiSignedNode
if err := cbor.Unmarshal(tx.Body, &sigNode); err != nil {
return err
}

return app.registerNode(ctx, state, &sigNode)

case registry.MethodUnfreezeNode:
var unfreeze registry.UnfreezeNode
if err := cbor.Unmarshal(tx.Body, &unfreeze); err != nil {
return err
}

return app.unfreezeNode(ctx, state, &unfreeze)

case registry.MethodRegisterRuntime:
var rt registry.Runtime
if err := cbor.Unmarshal(tx.Body, &rt); err != nil {
Expand All @@ -121,6 +122,20 @@ func (app *registryApplication) ExecuteTx(ctx *api.Context, tx *transaction.Tran
return err
}
return nil

case registry.MethodProveFreshness:
var blob [32]byte
if err := cbor.Unmarshal(tx.Body, &blob); err != nil {
ctx.Logger().Error("ExecuteTx: failed to unmarshal blob for freshness proof",
"err", err,
)
return registry.ErrInvalidArgument
}
if err := app.proveFreshness(ctx, state, blob); err != nil {
return err
}
return nil

default:
return registry.ErrInvalidArgument
}
Expand Down
30 changes: 30 additions & 0 deletions go/consensus/tendermint/apps/registry/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -787,3 +787,33 @@ func (app *registryApplication) registerRuntime( // nolint: gocyclo

return rt, nil
}

func (app *registryApplication) proveFreshness(
ctx *api.Context,
state *registryState.MutableState,
blob [32]byte,
) error {
params, err := state.ConsensusParameters(ctx)
if err != nil {
ctx.Logger().Error("ProveFreshness: failed to fetch consensus parameters",
"err", err,
)
return err
}

// Handle as a non-existing transaction to avoid breaking consensus before the activation
// proposal passes.
if params.TEEFeatures == nil || !params.TEEFeatures.FreshnessProofs {
return registry.ErrInvalidArgument
}

// Charge gas for this transaction.
if err = ctx.Gas().UseGas(1, registry.GasOpProveFreshness, params.GasCosts); err != nil {
return err
}

// Intentionally ignoring the blob and not doing any processing or state changes as this method
// should always succeed.

return nil
}
46 changes: 46 additions & 0 deletions go/consensus/tendermint/apps/registry/transactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,49 @@ func TestRegisterNode(t *testing.T) {
})
}
}

func TestProofFreshness(t *testing.T) {
require := requirePkg.New(t)

now := time.Unix(1580461674, 0)
cfg := abciAPI.MockApplicationStateConfig{}
appState := abciAPI.NewMockApplicationState(&cfg)
ctx := appState.NewContext(abciAPI.ContextEndBlock, now)
defer ctx.Close()

var md abciAPI.NoopMessageDispatcher
app := registryApplication{appState, &md}
state := registryState.NewMutableState(ctx.State())

setTEEFeaturesFn := func(TEEFeatures *node.TEEFeatures) {
err := state.SetConsensusParameters(ctx, &registry.ConsensusParameters{
TEEFeatures: TEEFeatures,
})
require.NoError(err, "registry.SetConsensusParameters")
}

var blob [32]byte

t.Run("happy path", func(t *testing.T) {
setTEEFeaturesFn(&node.TEEFeatures{FreshnessProofs: true})

err := app.proveFreshness(ctx, state, blob)
require.NoError(err, "freshness proofs should succeed")
})

t.Run("not enabled", func(t *testing.T) {
// Freshness proofs disabled.
setTEEFeaturesFn(&node.TEEFeatures{FreshnessProofs: false})

err := app.proveFreshness(ctx, state, blob)
require.Error(err, "freshness proofs should not be enabled")
require.Equal(registry.ErrInvalidArgument, err)

// No TEE features.
setTEEFeaturesFn(nil)

err = app.proveFreshness(ctx, state, blob)
require.Error(err, "freshness proofs should not be enabled")
require.Equal(registry.ErrInvalidArgument, err)
})
}
6 changes: 6 additions & 0 deletions go/consensus/tendermint/tests/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ func NewTestNodeGenesisProvider(identity *identity.Identity, ent *entity.Entity,
registry.GovernanceEntity: true,
registry.GovernanceRuntime: true,
},
TEEFeatures: &node.TEEFeatures{
SGX: node.TEEFeaturesSGX{
PCS: true,
},
FreshnessProofs: true,
},
},
},
Scheduler: scheduler.Genesis{
Expand Down
16 changes: 12 additions & 4 deletions go/oasis-node/cmd/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const (
cfgRegistryDebugBypassStake = "registry.debug.bypass_stake" // nolint: gosec
cfgRegistryEnableRuntimeGovernanceModels = "registry.enable_runtime_governance_models"
CfgRegistryTEEFeaturesSGXPCS = "registry.tee_features.sgx.pcs"
CfgRegistryTEEFeaturesFreshnessProofs = "registry.tee_features.freshness_proofs"

// Scheduler config flags.
cfgSchedulerMinValidators = "scheduler.min_validators"
Expand Down Expand Up @@ -345,11 +346,17 @@ func AppendRegistryState(doc *genesis.Document, entities, runtimes, nodes []stri
}

if viper.GetBool(CfgRegistryTEEFeaturesSGXPCS) {
regSt.Parameters.TEEFeatures = &node.TEEFeatures{
SGX: node.TEEFeaturesSGX{
PCS: true,
},
if regSt.Parameters.TEEFeatures == nil {
regSt.Parameters.TEEFeatures = &node.TEEFeatures{}
}
regSt.Parameters.TEEFeatures.SGX = node.TEEFeaturesSGX{PCS: true}
}

if viper.GetBool(CfgRegistryTEEFeaturesFreshnessProofs) {
if regSt.Parameters.TEEFeatures == nil {
regSt.Parameters.TEEFeatures = &node.TEEFeatures{}
}
regSt.Parameters.TEEFeatures.FreshnessProofs = true
}

for _, gmStr := range viper.GetStringSlice(cfgRegistryEnableRuntimeGovernanceModels) {
Expand Down Expand Up @@ -776,6 +783,7 @@ func init() {
initGenesisFlags.Bool(cfgRegistryDebugBypassStake, false, "bypass all stake checks and operations (UNSAFE)")
initGenesisFlags.StringSlice(cfgRegistryEnableRuntimeGovernanceModels, []string{"entity"}, "set of enabled runtime governance models")
initGenesisFlags.Bool(CfgRegistryTEEFeaturesSGXPCS, true, "enable PCS support for SGX TEEs")
initGenesisFlags.Bool(CfgRegistryTEEFeaturesFreshnessProofs, true, "enable freshness proofs")
_ = initGenesisFlags.MarkHidden(cfgRegistryDebugAllowUnroutableAddresses)
_ = initGenesisFlags.MarkHidden(CfgRegistryDebugAllowTestRuntimes)
_ = initGenesisFlags.MarkHidden(cfgRegistryDebugBypassStake)
Expand Down
6 changes: 6 additions & 0 deletions go/oasis-test-runner/scenario/e2e/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ func (n *upgradeTeePcsChecker) PostUpgradeFn(ctx context.Context, ctrl *oasis.Co
if !registryParams.TEEFeatures.SGX.PCS {
return fmt.Errorf("PCS SGX TEE feature is disabled")
}
if !registryParams.TEEFeatures.FreshnessProofs {
return fmt.Errorf("freshness proofs TEE feature is disabled")
}
if registryParams.GasCosts[registry.GasOpProveFreshness] != registry.DefaultGasCosts[registry.GasOpProveFreshness] {
return fmt.Errorf("default gas cost for freshness proofs is not set")
}

return nil
}
Expand Down
11 changes: 11 additions & 0 deletions go/registry/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ var (
MethodUnfreezeNode = transaction.NewMethodName(ModuleName, "UnfreezeNode", UnfreezeNode{})
// MethodRegisterRuntime is the method name for registering runtimes.
MethodRegisterRuntime = transaction.NewMethodName(ModuleName, "RegisterRuntime", Runtime{})
// MethodProveFreshness is the method name for freshness proofs.
MethodProveFreshness = transaction.NewMethodName(ModuleName, "ProveFreshness", Runtime{})

// Methods is the list of all methods supported by the registry backend.
Methods = []transaction.MethodName{
Expand All @@ -138,6 +140,7 @@ var (
MethodRegisterNode,
MethodUnfreezeNode,
MethodRegisterRuntime,
MethodProveFreshness,
}

// RuntimesRequiredRoles are the Node roles that require runtimes.
Expand Down Expand Up @@ -286,6 +289,11 @@ func NewRegisterRuntimeTx(nonce uint64, fee *transaction.Fee, rt *Runtime) *tran
return transaction.NewTransaction(nonce, fee, MethodRegisterRuntime, rt)
}

// NewProveFreshnessTx creates a new prove freshness transaction.
func NewProveFreshnessTx(nonce uint64, fee *transaction.Fee, blob [32]byte) *transaction.Transaction {
return transaction.NewTransaction(nonce, fee, MethodProveFreshness, blob)
}

// EntityEvent is the event that is returned via WatchEntities to signify
// entity registration changes and updates.
type EntityEvent struct {
Expand Down Expand Up @@ -1451,6 +1459,8 @@ const (
// GasOpUpdateKeyManager is the gas operation identifier for key manager
// policy updates costs.
GasOpUpdateKeyManager transaction.Op = "update_keymanager"
// GasOpProveFreshness is the gas operation identifier for freshness proofs.
GasOpProveFreshness transaction.Op = "prove_freshness"
)

// XXX: Define reasonable default gas costs.
Expand All @@ -1464,6 +1474,7 @@ var DefaultGasCosts = transaction.Costs{
GasOpRegisterRuntime: 1000,
GasOpRuntimeEpochMaintenance: 1000,
GasOpUpdateKeyManager: 1000,
GasOpProveFreshness: 1000,
}

const (
Expand Down
21 changes: 21 additions & 0 deletions go/registry/tests/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ func RegistryImplementationTests(t *testing.T, backend api.Backend, consensus co
})

testRegistryEntityNodes(t, backend, consensus, runtimeID, runtimeEWID, validatorEntityID)

t.Run("FreshnessProofs", func(t *testing.T) {
testFreshnessProofs(t, consensus)
})
}

// Add node's ID to node list if it's not already on it.
Expand Down Expand Up @@ -941,6 +945,18 @@ func testRegistryRuntime(t *testing.T, backend api.Backend, consensus consensusA
return rtMapByName["WithoutKM"].ID, rtMapByName["EntityWhitelist"].ID
}

func testFreshnessProofs(t *testing.T, consensus consensusAPI.Backend) {
require := require.New(t)

// Generate one entity used for the test case.
entities, err := NewTestEntities(entityNodeSeed, 1)
require.NoError(err, "new test entities should be generated")

// Test freshness proofs.
err = entities[0].ProveFreshness(consensus)
require.NoError(err, "freshness proofs should succeed")
}

// EnsureRegistryClean enforces that the registry is in a clean state before running the registry tests.
func EnsureRegistryClean(t *testing.T, backend api.Backend) {
registeredEntities, err := backend.GetEntities(context.Background(), consensusAPI.HeightLatest)
Expand Down Expand Up @@ -980,6 +996,11 @@ func (ent *TestEntity) Deregister(consensus consensusAPI.Backend) error {
return consensusAPI.SignAndSubmitTx(context.Background(), consensus, ent.Signer, api.NewDeregisterEntityTx(0, nil))
}

// ProveFreshness attempts to prove freshness with zero value blob.
func (ent *TestEntity) ProveFreshness(consensus consensusAPI.Backend) error {
return consensusAPI.SignAndSubmitTx(context.Background(), consensus, ent.Signer, api.NewProveFreshnessTx(0, nil, [32]byte{}))
}

// TestNode is a testing Node and some common pre-generated/signed blobs
// useful for testing.
type TestNode struct {
Expand Down
5 changes: 5 additions & 0 deletions go/upgrade/migrations/consensus_tee_pcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/oasisprotocol/oasis-core/go/common/node"
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/api"
registryState "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/apps/registry/state"
registry "github.com/oasisprotocol/oasis-core/go/registry/api"
)

const (
Expand Down Expand Up @@ -40,8 +41,12 @@ func (th *teePcsHandler) ConsensusUpgrade(ctx *Context, privateCtx interface{})
SGX: node.TEEFeaturesSGX{
PCS: true,
},
FreshnessProofs: true,
}

// Configure the default gas cost for freshness proofs.
params.GasCosts[registry.GasOpProveFreshness] = registry.DefaultGasCosts[registry.GasOpProveFreshness]

if err = state.SetConsensusParameters(abciCtx, params); err != nil {
return fmt.Errorf("failed to update registry consensus parameters: %w", err)
}
Expand Down

0 comments on commit 0807389

Please sign in to comment.