diff --git a/.changelog/2440.feature.2.md b/.changelog/2440.feature.2.md new file mode 100644 index 00000000000..da2b5e72b27 --- /dev/null +++ b/.changelog/2440.feature.2.md @@ -0,0 +1,9 @@ +go/worker/consensus: Add public consensus services worker + +A public consensus services worker enables any full consensus node to expose +light client services to other nodes that may need them (e.g., they are needed +to support light clients). + +The worker can be enabled using --worker.consensus.enabled and is disabled by +default. Enabling the public consensus services worker exposes the light +consensus client interface over publicly accessible gRPC. diff --git a/go/oasis-node/cmd/node/node.go b/go/oasis-node/cmd/node/node.go index 7c58ebe854f..fd48653f8a5 100644 --- a/go/oasis-node/cmd/node/node.go +++ b/go/oasis-node/cmd/node/node.go @@ -61,6 +61,7 @@ import ( "github.com/oasislabs/oasis-core/go/worker/compute/executor" "github.com/oasislabs/oasis-core/go/worker/compute/merge" "github.com/oasislabs/oasis-core/go/worker/compute/txnscheduler" + workerConsensus "github.com/oasislabs/oasis-core/go/worker/consensus" workerKeymanager "github.com/oasislabs/oasis-core/go/worker/keymanager" "github.com/oasislabs/oasis-core/go/worker/registration" workerSentry "github.com/oasislabs/oasis-core/go/worker/sentry" @@ -126,6 +127,7 @@ type Node struct { P2P *p2p.P2P RegistrationWorker *registration.Worker KeymanagerWorker *workerKeymanager.Worker + ConsensusWorker *workerConsensus.Worker } // Cleanup cleans up after the node has terminated. @@ -340,6 +342,13 @@ func (n *Node) initWorkers(logger *logging.Logger) error { } n.svcMgr.Register(n.TransactionSchedulerWorker) + // Initialize the public consensus services worker. + n.ConsensusWorker, err = workerConsensus.New(n.CommonWorker) + if err != nil { + return err + } + n.svcMgr.Register(n.ConsensusWorker) + return nil } @@ -384,8 +393,18 @@ func (n *Node) startWorkers(logger *logging.Logger) error { return err } + // Start the public consensus services worker. + if err := n.ConsensusWorker.Start(); err != nil { + return fmt.Errorf("consensus worker: %w", err) + } + // Only start the external gRPC server if any workers are enabled. - if n.StorageWorker.Enabled() || n.TransactionSchedulerWorker.Enabled() || n.MergeWorker.Enabled() || n.KeymanagerWorker.Enabled() { + if n.StorageWorker.Enabled() || + n.TransactionSchedulerWorker.Enabled() || + n.MergeWorker.Enabled() || + n.KeymanagerWorker.Enabled() || + n.ConsensusWorker.Enabled() { + if err := n.CommonWorker.Grpc.Start(); err != nil { logger.Error("failed to start external gRPC server", "err", err, @@ -793,6 +812,7 @@ func init() { workerCommon.Flags, workerStorage.Flags, workerSentry.Flags, + workerConsensus.Flags, crash.InitFlags(), } { Flags.AddFlagSet(v) diff --git a/go/oasis-test-runner/oasis/args.go b/go/oasis-test-runner/oasis/args.go index 0ee717542df..70784076d6c 100644 --- a/go/oasis-test-runner/oasis/args.go +++ b/go/oasis-test-runner/oasis/args.go @@ -30,6 +30,7 @@ import ( "github.com/oasislabs/oasis-core/go/worker/common/p2p" "github.com/oasislabs/oasis-core/go/worker/compute" "github.com/oasislabs/oasis-core/go/worker/compute/txnscheduler" + workerConsensus "github.com/oasislabs/oasis-core/go/worker/consensus" "github.com/oasislabs/oasis-core/go/worker/keymanager" "github.com/oasislabs/oasis-core/go/worker/registration" workerSentry "github.com/oasislabs/oasis-core/go/worker/sentry" @@ -377,6 +378,11 @@ func (args *argBuilder) workerTxnschedulerCheckTxEnabled() *argBuilder { return args } +func (args *argBuilder) workerConsensusEnabled() *argBuilder { + args.vec = append(args.vec, "--"+workerConsensus.CfgWorkerEnabled) + return args +} + func (args *argBuilder) iasUseGenesis() *argBuilder { args.vec = append(args.vec, "--ias.use_genesis") return args diff --git a/go/oasis-test-runner/oasis/fixture.go b/go/oasis-test-runner/oasis/fixture.go index c84851ba998..fc13ecab90c 100644 --- a/go/oasis-test-runner/oasis/fixture.go +++ b/go/oasis-test-runner/oasis/fixture.go @@ -133,6 +133,9 @@ type ConsensusFixture struct { // nolint: maligned // TendermintRecoverCorruptedWAL enables automatic recovery of corrupted Tendermint's WAL. TendermintRecoverCorruptedWAL bool `json:"tendermint_recover_corrupted_wal"` + + // EnableConsensusWorker enables the public consensus services worker. + EnableConsensusWorker bool `json:"enable_consensus_worker,omitempty"` } // TEEFixture is a TEE configuration fixture. diff --git a/go/oasis-test-runner/oasis/validator.go b/go/oasis-test-runner/oasis/validator.go index 67b2a2abe0c..90a7aa97698 100644 --- a/go/oasis-test-runner/oasis/validator.go +++ b/go/oasis-test-runner/oasis/validator.go @@ -25,6 +25,7 @@ type Validator struct { tmAddress string consensusPort uint16 + clientPort uint16 } // ValidatorCfg is the Oasis validator provisioning configuration. @@ -66,6 +67,11 @@ func (val *Validator) ExportsPath() string { return nodeExportsPath(val.dir) } +// ExternalGRPCAddress returns the address of the node's external gRPC server. +func (val *Validator) ExternalGRPCAddress() string { + return fmt.Sprintf("127.0.0.1:%d", val.clientPort) +} + // Start starts an Oasis node. func (val *Validator) Start() error { return val.startNode() @@ -91,6 +97,10 @@ func (val *Validator) startNode() error { } else { args = args.appendSeedNodes(val.net) } + if val.consensus.EnableConsensusWorker { + args = args.workerClientPort(val.clientPort). + workerConsensusEnabled() + } if len(val.net.validators) >= 1 && val == val.net.validators[0] { args = args.supplementarysanityEnabled() @@ -130,6 +140,7 @@ func (net *Network) NewValidator(cfg *ValidatorCfg) (*Validator, error) { entity: cfg.Entity, sentries: cfg.Sentries, consensusPort: net.nextNodePort, + clientPort: net.nextNodePort + 1, } val.doStartNode = val.startNode @@ -191,7 +202,7 @@ func (net *Network) NewValidator(cfg *ValidatorCfg) (*Validator, error) { } net.validators = append(net.validators, val) - net.nextNodePort++ + net.nextNodePort += 2 if err := net.AddLogWatcher(&val.Node); err != nil { net.logger.Error("failed to add log watcher", diff --git a/go/worker/consensus/worker.go b/go/worker/consensus/worker.go new file mode 100644 index 00000000000..1d2c88a64fb --- /dev/null +++ b/go/worker/consensus/worker.go @@ -0,0 +1,95 @@ +// Package consensus implements publicly accessible consensus services. +package consensus + +import ( + flag "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/oasislabs/oasis-core/go/common/logging" + consensus "github.com/oasislabs/oasis-core/go/consensus/api" + workerCommon "github.com/oasislabs/oasis-core/go/worker/common" +) + +const ( + // CfgWorkerEnabled enables the consensus services worker. + CfgWorkerEnabled = "worker.consensus.enabled" +) + +// Flags has the configuration flags. +var Flags = flag.NewFlagSet("", flag.ContinueOnError) + +// Worker is a worker providing publicly accessible consensus services. +// +// Currently this only exposes the consensus light client service. +type Worker struct { + enabled bool + + commonWorker *workerCommon.Worker + + quitCh chan struct{} + + logger *logging.Logger +} + +// Name returns the service name. +func (w *Worker) Name() string { + return "public consensus services worker" +} + +// Enabled returns if worker is enabled. +func (w *Worker) Enabled() bool { + return w.enabled +} + +// Start starts the worker. +func (w *Worker) Start() error { + if w.enabled { + w.logger.Info("starting public consensus services worker") + } + return nil +} + +// Stop halts the service. +func (w *Worker) Stop() { + close(w.quitCh) +} + +// Quit returns a channel that will be closed when the service terminates. +func (w *Worker) Quit() <-chan struct{} { + return w.quitCh +} + +// Cleanup performs the service specific post-termination cleanup. +func (w *Worker) Cleanup() { +} + +// New creates a new public consensus services worker. +func New(commonWorker *workerCommon.Worker) (*Worker, error) { + w := &Worker{ + enabled: Enabled(), + commonWorker: commonWorker, + quitCh: make(chan struct{}), + logger: logging.GetLogger("worker/consensus"), + } + + if w.enabled { + // Register the consensus light client service. + consensus.RegisterLightService(commonWorker.Grpc.Server(), commonWorker.Consensus) + + // TODO: We could introduce a role bit for nodes providing these public services so that you + // could easily discover suitable nodes to use. + } + + return w, nil +} + +// Enabled reads our enabled flag from viper. +func Enabled() bool { + return viper.GetBool(CfgWorkerEnabled) +} + +func init() { + Flags.Bool(CfgWorkerEnabled, false, "Enable public consensus services worker") + + _ = viper.BindPFlags(Flags) +}