Skip to content

Commit 3b29b09

Browse files
Merge pull request #6497 from oasisprotocol/martin/trivial/observer-e2e-test
oasis-core/go/oasis-test-runner: Add observer fixture and e2e test
2 parents 51c6778 + 9b99580 commit 3b29b09

12 files changed

Lines changed: 492 additions & 17 deletions

File tree

.buildkite/code.pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ steps:
262262
--scenario e2e/runtime/rofl
263263
--scenario e2e/runtime/trust-root/.+
264264
--scenario e2e/runtime/keymanager-.+
265+
--scenario e2e/runtime/observer
265266
env:
266267
# Unsafe flags needed as the trust-root test rebuilds the enclave with embedded trust root data.
267268
OASIS_UNSAFE_SKIP_AVR_VERIFY: "1"

.changelog/6497.trivial.md

Whitespace-only changes.

go/oasis-net-runner/fixtures/default.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ func newDefaultFixture() (*oasis.NetworkFixture, error) {
143143
Kind: registry.KindKeyManager,
144144
Entity: 0,
145145
Keymanager: -1,
146-
AdmissionPolicy: registry.RuntimeAdmissionPolicy{
147-
AnyNode: &registry.AnyNodeRuntimeAdmissionPolicy{},
146+
AdmissionPolicy: oasis.RuntimeAdmissionPolicyFixture{
147+
AnyNode: true,
148148
},
149149
GovernanceModel: registry.GovernanceEntity,
150150
Deployments: []oasis.DeploymentCfg{
@@ -234,8 +234,8 @@ func newDefaultFixture() (*oasis.NetworkFixture, error) {
234234
BatchFlushTimeout: time.Second,
235235
ProposerTimeout: 2 * time.Second,
236236
},
237-
AdmissionPolicy: registry.RuntimeAdmissionPolicy{
238-
AnyNode: &registry.AnyNodeRuntimeAdmissionPolicy{},
237+
AdmissionPolicy: oasis.RuntimeAdmissionPolicyFixture{
238+
AnyNode: true,
239239
},
240240
GenesisRound: 0,
241241
GovernanceModel: registry.GovernanceEntity,

go/oasis-test-runner/oasis/fixture.go

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
88
"github.com/oasisprotocol/oasis-core/go/common"
99
"github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
10+
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
1011
"github.com/oasisprotocol/oasis-core/go/common/node"
1112
"github.com/oasisprotocol/oasis-core/go/common/sgx"
1213
"github.com/oasisprotocol/oasis-core/go/consensus/cometbft/config"
@@ -28,6 +29,7 @@ type NetworkFixture struct {
2829
Keymanagers []KeymanagerFixture `json:"keymanagers,omitempty"`
2930
KeymanagerPolicies []KeymanagerPolicyFixture `json:"keymanager_policies,omitempty"`
3031
ComputeWorkers []ComputeWorkerFixture `json:"compute_workers,omitempty"`
32+
Observers []ObserverFixture `json:"observers,omitempty"`
3133
Sentries []SentryFixture `json:"sentries,omitempty"`
3234
Clients []ClientFixture `json:"clients,omitempty"`
3335
StatelessClients []StatelessClientFixture `json:"stateless_clients,omitempty"`
@@ -107,6 +109,13 @@ func (f *NetworkFixture) Create(env *env.Env) (*Network, error) {
107109
}
108110
}
109111

112+
// Provision the observers.
113+
for _, fx := range f.Observers {
114+
if _, err = fx.Create(net); err != nil {
115+
return nil, err
116+
}
117+
}
118+
110119
// Provision the client nodes.
111120
for _, fx := range f.Clients {
112121
if _, err = fx.Create(net); err != nil {
@@ -241,7 +250,7 @@ type RuntimeFixture struct {
241250
TxnScheduler registry.TxnSchedulerParameters `json:"txn_scheduler"`
242251
Storage registry.StorageParameters `json:"storage"`
243252

244-
AdmissionPolicy registry.RuntimeAdmissionPolicy `json:"admission_policy"`
253+
AdmissionPolicy RuntimeAdmissionPolicyFixture `json:"admission_policy"`
245254
Constraints map[scheduler.CommitteeKind]map[scheduler.Role]registry.SchedulingConstraints `json:"constraints,omitempty"`
246255
Staking registry.RuntimeStakingParameters `json:"staking,omitempty"`
247256

@@ -259,6 +268,10 @@ func (f *RuntimeFixture) Create(netFixture *NetworkFixture, net *Network) (*Runt
259268
if err != nil {
260269
return nil, err
261270
}
271+
admissionPolicy, err := f.AdmissionPolicy.Resolve(net)
272+
if err != nil {
273+
return nil, err
274+
}
262275

263276
var km *Runtime
264277
if f.Keymanager != -1 {
@@ -282,7 +295,7 @@ func (f *RuntimeFixture) Create(netFixture *NetworkFixture, net *Network) (*Runt
282295
Executor: f.Executor,
283296
TxnScheduler: f.TxnScheduler,
284297
Storage: f.Storage,
285-
AdmissionPolicy: f.AdmissionPolicy,
298+
AdmissionPolicy: admissionPolicy,
286299
Staking: f.Staking,
287300
GenesisRound: f.GenesisRound,
288301
GenesisStateRoot: f.GenesisStateRoot,
@@ -294,6 +307,69 @@ func (f *RuntimeFixture) Create(netFixture *NetworkFixture, net *Network) (*Runt
294307
})
295308
}
296309

310+
type RuntimeAdmissionPolicyFixture struct {
311+
AnyNode bool `json:"any_node,omitempty"`
312+
EntityWhitelist *EntityWhitelistRuntimeAdmissionPolicyFixture `json:"entity_whitelist,omitempty"`
313+
PerRole map[node.RolesMask]PerRoleAdmissionPolicyFixture `json:"per_role,omitempty"`
314+
}
315+
316+
type EntityWhitelistRuntimeAdmissionPolicyFixture struct {
317+
Entities map[int]registry.EntityWhitelistConfig `json:"entities"`
318+
}
319+
320+
type PerRoleAdmissionPolicyFixture struct {
321+
EntityWhitelist *EntityWhitelistRoleAdmissionPolicyFixture `json:"entity_whitelist,omitempty"`
322+
}
323+
324+
type EntityWhitelistRoleAdmissionPolicyFixture struct {
325+
Entities map[int]registry.EntityWhitelistRoleConfig `json:"entities"`
326+
}
327+
328+
func (f RuntimeAdmissionPolicyFixture) Resolve(net *Network) (registry.RuntimeAdmissionPolicy, error) {
329+
var policy registry.RuntimeAdmissionPolicy
330+
331+
if f.AnyNode {
332+
policy.AnyNode = &registry.AnyNodeRuntimeAdmissionPolicy{}
333+
}
334+
335+
if f.EntityWhitelist != nil {
336+
whitelist := &registry.EntityWhitelistRuntimeAdmissionPolicy{
337+
Entities: make(map[signature.PublicKey]registry.EntityWhitelistConfig),
338+
}
339+
for idx, cfg := range f.EntityWhitelist.Entities {
340+
ent, err := resolveEntity(net, idx)
341+
if err != nil {
342+
return registry.RuntimeAdmissionPolicy{}, err
343+
}
344+
whitelist.Entities[ent.ID()] = cfg
345+
}
346+
policy.EntityWhitelist = whitelist
347+
}
348+
349+
if f.PerRole != nil {
350+
policy.PerRole = make(map[node.RolesMask]registry.PerRoleAdmissionPolicy)
351+
for role, prap := range f.PerRole {
352+
var perRolePolicy registry.PerRoleAdmissionPolicy
353+
if prap.EntityWhitelist != nil {
354+
whitelist := &registry.EntityWhitelistRoleAdmissionPolicy{
355+
Entities: make(map[signature.PublicKey]registry.EntityWhitelistRoleConfig),
356+
}
357+
for idx, cfg := range prap.EntityWhitelist.Entities {
358+
ent, err := resolveEntity(net, idx)
359+
if err != nil {
360+
return registry.RuntimeAdmissionPolicy{}, err
361+
}
362+
whitelist.Entities[ent.ID()] = cfg
363+
}
364+
perRolePolicy.EntityWhitelist = whitelist
365+
}
366+
policy.PerRole[role] = perRolePolicy
367+
}
368+
}
369+
370+
return policy, nil
371+
}
372+
297373
// KeymanagerPolicyFixture is a key manager policy fixture.
298374
type KeymanagerPolicyFixture struct {
299375
Runtime int `json:"runtime"`
@@ -458,6 +534,53 @@ func (f *ComputeWorkerFixture) Create(net *Network) (*Compute, error) {
458534
})
459535
}
460536

537+
// ObserverFixture is an observer node fixture.
538+
type ObserverFixture struct {
539+
NodeFixture
540+
541+
// Entity is the index of the entity the node will be provisioned with.
542+
Entity int `json:"entity"`
543+
544+
AllowErrorTermination bool `json:"allow_error_termination"`
545+
AllowEarlyTermination bool `json:"allow_early_termination"`
546+
547+
// Consensus contains configuration for the consensus backend.
548+
Consensus ConsensusFixture `json:"consensus"`
549+
550+
// Runtimes contains the indexes of the runtimes to enable.
551+
Runtimes []int `json:"runtimes,omitempty"`
552+
553+
// RuntimeProvisioner is the runtime provisioner configuration.
554+
RuntimeProvisioner runtimeConfig.RuntimeProvisioner `json:"runtime_provisioner"`
555+
556+
// RuntimeConfig contains the per-runtime node-local configuration.
557+
RuntimeConfig map[int]map[string]any `json:"runtime_config,omitempty"`
558+
}
559+
560+
// Create instantiates the observer node described by the fixture.
561+
func (f *ObserverFixture) Create(net *Network) (*Observer, error) {
562+
entity, err := resolveEntity(net, f.Entity)
563+
if err != nil {
564+
return nil, err
565+
}
566+
567+
return net.NewObserver(&ObserverCfg{
568+
NodeCfg: NodeCfg{
569+
Name: f.Name,
570+
Consensus: f.Consensus,
571+
AllowErrorTermination: f.AllowErrorTermination,
572+
AllowEarlyTermination: f.AllowEarlyTermination,
573+
NoAutoStart: f.NoAutoStart,
574+
SupplementarySanityInterval: f.Consensus.SupplementarySanityInterval,
575+
Entity: entity,
576+
ExtraArgs: f.ExtraArgs,
577+
},
578+
Runtimes: f.Runtimes,
579+
RuntimeProvisioner: f.RuntimeProvisioner,
580+
RuntimeConfig: f.RuntimeConfig,
581+
})
582+
}
583+
461584
// SeedFixture is a seed node fixture.
462585
type SeedFixture struct {
463586
NodeFixture

go/oasis-test-runner/oasis/network.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type Network struct {
5959
runtimes []*Runtime
6060
keymanagers []*Keymanager
6161
computeWorkers []*Compute
62+
observers []*Observer
6263
sentries []*Sentry
6364
clients []*Client
6465
statelessClients []*StatelessClient
@@ -229,6 +230,11 @@ func (net *Network) ComputeWorkers() []*Compute {
229230
return net.computeWorkers
230231
}
231232

233+
// Observers returns the observer nodes associated with the network.
234+
func (net *Network) Observers() []*Observer {
235+
return net.observers
236+
}
237+
232238
// Sentries returns the sentry nodes associated with the network.
233239
func (net *Network) Sentries() []*Sentry {
234240
return net.sentries
@@ -249,7 +255,7 @@ func (net *Network) Byzantine() []*Byzantine {
249255
return net.byzantine
250256
}
251257

252-
// Nodes returns all the validator, compute, storage, keymanager and client nodes associated with
258+
// Nodes returns all the validator, compute, observer, keymanager and client nodes associated with
253259
// the network.
254260
//
255261
// Seed, sentry, byzantine and IAS proxy nodes are omitted if they're only hosting these single features.
@@ -262,6 +268,9 @@ func (net *Network) Nodes() []*Node {
262268
for _, c := range net.ComputeWorkers() {
263269
nodes = append(nodes, c.Node)
264270
}
271+
for _, c := range net.Observers() {
272+
nodes = append(nodes, c.Node)
273+
}
265274
for _, k := range net.Keymanagers() {
266275
nodes = append(nodes, k.Node)
267276
}
@@ -349,6 +358,7 @@ func (net *Network) NumRegisterNodes() int {
349358
return len(net.validators) +
350359
len(net.keymanagers) +
351360
len(net.computeWorkers) +
361+
len(net.observers) +
352362
len(net.byzantine)
353363
}
354364

go/oasis-test-runner/oasis/oasis.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,7 @@ func (n *Node) setProvisionedIdentity(seed string) error {
458458
return err
459459
}
460460

461-
if n.entity != nil {
462-
// Client nodes may need a provisioned identity. They never need an entity, however.
461+
if n.entity != nil { // client nodes don't have entity id.
463462
if err := n.entity.addNode(nodeSigner); err != nil {
464463
return err
465464
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package oasis
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strconv"
7+
8+
"github.com/oasisprotocol/oasis-core/go/config"
9+
"github.com/oasisprotocol/oasis-core/go/runtime/bundle"
10+
runtimeConfig "github.com/oasisprotocol/oasis-core/go/runtime/config"
11+
)
12+
13+
const (
14+
observerIdentitySeedTemplate = "ekiden node observer %d"
15+
)
16+
17+
// Observer is an Oasis observer node.
18+
type Observer struct {
19+
*Node
20+
21+
consensusPort uint16
22+
p2pPort uint16
23+
24+
runtimes []int
25+
runtimeConfig map[int]map[string]any
26+
runtimeProvisioner runtimeConfig.RuntimeProvisioner
27+
}
28+
29+
// ObserverCfg is the Oasis observer node provisioning configuration.
30+
type ObserverCfg struct {
31+
NodeCfg
32+
33+
Runtimes []int
34+
RuntimeConfig map[int]map[string]any
35+
RuntimeProvisioner runtimeConfig.RuntimeProvisioner
36+
}
37+
38+
// UpdateRuntimes updates the observer node runtimes.
39+
func (o *Observer) UpdateRuntimes(runtimes []int) {
40+
o.runtimes = runtimes
41+
}
42+
43+
func (o *Observer) AddArgs(args *argBuilder) error {
44+
args.appendNetwork(o.net)
45+
46+
if o.entity.isDebugTestEntity {
47+
args.appendDebugTestEntity()
48+
}
49+
50+
for _, idx := range o.runtimes {
51+
v := o.net.runtimes[idx]
52+
// XXX: could support configurable binary idx if ever needed.
53+
o.addHostedRuntime(v, o.runtimeConfig[idx])
54+
}
55+
56+
return nil
57+
}
58+
59+
func (o *Observer) ModifyConfig() error {
60+
o.Config.Consensus.ListenAddress = allInterfacesAddr + ":" + strconv.Itoa(int(o.consensusPort))
61+
o.Config.Consensus.ExternalAddress = localhostAddr + ":" + strconv.Itoa(int(o.consensusPort))
62+
63+
if o.supplementarySanityInterval > 0 {
64+
o.Config.Consensus.SupplementarySanity.Enabled = true
65+
o.Config.Consensus.SupplementarySanity.Interval = o.supplementarySanityInterval
66+
}
67+
68+
o.Config.P2P.Port = o.p2pPort
69+
70+
if !o.entity.isDebugTestEntity {
71+
entityID, _ := o.entity.ID().MarshalText() // Cannot fail.
72+
o.Config.Registration.EntityID = string(entityID)
73+
}
74+
75+
o.Config.Mode = config.ModeClient
76+
o.Config.Runtime.Provisioner = o.runtimeProvisioner
77+
o.Config.Runtime.SGX.Loader = o.net.cfg.RuntimeSGXLoaderBinary
78+
o.Config.Runtime.AttestInterval = o.net.cfg.RuntimeAttestInterval
79+
80+
o.AddSeedNodesToConfig()
81+
82+
return nil
83+
}
84+
85+
// NewObserver provisions a new observer node and adds it to the network.
86+
func (net *Network) NewObserver(cfg *ObserverCfg) (*Observer, error) {
87+
observerName := fmt.Sprintf("observer-%d", len(net.observers))
88+
host, err := net.GetNamedNode(observerName, &cfg.NodeCfg)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
// Pre-provision the node identity so that we can identify the entity.
94+
err = host.setProvisionedIdentity(fmt.Sprintf(observerIdentitySeedTemplate, len(net.observers)))
95+
if err != nil {
96+
return nil, fmt.Errorf("oasis/observer: failed to provision node identity: %w", err)
97+
}
98+
99+
if cfg.RuntimeProvisioner == "" {
100+
cfg.RuntimeProvisioner = runtimeConfig.RuntimeProvisionerSandboxed
101+
}
102+
103+
observer := &Observer{
104+
Node: host,
105+
runtimes: cfg.Runtimes,
106+
runtimeProvisioner: cfg.RuntimeProvisioner,
107+
runtimeConfig: cfg.RuntimeConfig,
108+
consensusPort: host.getProvisionedPort(nodePortConsensus),
109+
p2pPort: host.getProvisionedPort(nodePortP2P),
110+
}
111+
112+
// Remove any exploded bundles on cleanup.
113+
net.env.AddOnCleanup(func() {
114+
_ = os.RemoveAll(bundle.ExplodedPath(observer.dir.String()))
115+
})
116+
117+
net.observers = append(net.observers, observer)
118+
host.features = append(host.features, observer)
119+
120+
return observer, nil
121+
}

0 commit comments

Comments
 (0)