diff --git a/analyzer/consensus/consensus.go b/analyzer/consensus/consensus.go index e55889f5a..97cd95c9e 100644 --- a/analyzer/consensus/consensus.go +++ b/analyzer/consensus/consensus.go @@ -821,7 +821,7 @@ func (m *processor) queueNodeEvents(batch *storage.QueryBatch, data *registryDat nodeEvent.ConsensusID.String(), strings.Join(nodeEvent.ConsensusAddresses, ","), // TODO: store as array nodeEvent.VRFPubKey, - strings.Join(nodeEvent.Roles, ","), // TODO: store as array + nodeEvent.Roles, nodeEvent.SoftwareVersion, 0, ) @@ -830,9 +830,8 @@ func (m *processor) queueNodeEvents(batch *storage.QueryBatch, data *registryDat // Update the node's runtime associations by deleting // previous node records and inserting new ones. batch.Queue(queries.ConsensusRuntimeNodesDelete, nodeEvent.NodeID.String()) - for _, runtimeID := range nodeEvent.RuntimeIDs { - // XXX: Include other fields here if needed in the future. - batch.Queue(queries.ConsensusRuntimeNodesUpsert, runtimeID.String(), nodeEvent.NodeID.String()) + for _, rt := range nodeEvent.Runtimes { + batch.Queue(queries.ConsensusRuntimeNodesUpsert, rt.ID.String(), nodeEvent.NodeID.String(), rt.Version, rt.RawCapabilities, rt.ExtraInfo) } } else { // An existing node is expired. diff --git a/analyzer/consensus/genesis.go b/analyzer/consensus/genesis.go index d04fd0440..185208ec3 100644 --- a/analyzer/consensus/genesis.go +++ b/analyzer/consensus/genesis.go @@ -5,6 +5,7 @@ package consensus import ( "encoding/json" "sort" + "strings" "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/entity" @@ -122,7 +123,7 @@ func (mg *GenesisProcessor) addRegistryBackendMigrations(batch *storage.QueryBat node.Consensus.ID.String(), nil, nil, - node.Roles.String(), + strings.Split(node.Roles.String(), ","), nil, nil, ) @@ -132,6 +133,9 @@ func (mg *GenesisProcessor) addRegistryBackendMigrations(batch *storage.QueryBat batch.Queue(queries.ConsensusRuntimeNodesUpsert, runtime.ID.String(), node.ID.String(), + runtime.Version.String(), + cbor.Marshal(runtime.Capabilities), + runtime.ExtraInfo, ) } } diff --git a/analyzer/queries/queries.go b/analyzer/queries/queries.go index caad4530a..199ff09b0 100644 --- a/analyzer/queries/queries.go +++ b/analyzer/queries/queries.go @@ -400,8 +400,12 @@ var ( voting_power = excluded.voting_power` ConsensusRuntimeNodesUpsert = ` - INSERT INTO chain.runtime_nodes (runtime_id, node_id) VALUES ($1, $2) - ON CONFLICT (runtime_id, node_id) DO NOTHING` + INSERT INTO chain.runtime_nodes (runtime_id, node_id, version, capabilities, extra_info) VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (runtime_id, node_id) DO UPDATE + SET + version = excluded.version, + capabilities = excluded.capabilities, + extra_info = excluded.extra_info` ConsensusRuntimeNodesDelete = ` DELETE FROM chain.runtime_nodes WHERE node_id = $1` diff --git a/storage/migrations/00_consensus.up.sql b/storage/migrations/00_consensus.up.sql index 7f0c764ea..ff4408975 100644 --- a/storage/migrations/00_consensus.up.sql +++ b/storage/migrations/00_consensus.up.sql @@ -28,6 +28,9 @@ CREATE TYPE public.runtime AS ENUM ('emerald', 'sapphire', 'cipher', 'pontusx_de CREATE TYPE public.call_format AS ENUM ('encrypted/x25519-deoxysii'); CREATE TYPE public.sourcify_level AS ENUM ('partial', 'full'); +-- Added in 51_node_roles.up.sql. +-- CREATE TYPE public.node_role AS ENUM ('compute', 'observer', 'key-manager', 'validator', 'consensus-rpc', 'storage-rpc'); + -- Block Data CREATE TABLE chain.blocks ( @@ -207,6 +210,8 @@ CREATE TABLE chain.nodes vrf_pubkey TEXT, roles TEXT, + -- roles node_role[] NOT NULL DEFAULT '{}'::node_role[]; -- Added in 51_node_roles.up.sql. + software_version TEXT, -- Voting power should only be nonzero for consensus validator nodes. diff --git a/storage/migrations/51_node_roles.up.sql b/storage/migrations/51_node_roles.up.sql new file mode 100644 index 000000000..23bace40f --- /dev/null +++ b/storage/migrations/51_node_roles.up.sql @@ -0,0 +1,64 @@ +BEGIN; + +CREATE TYPE public.node_role AS ENUM ('compute', 'observer', 'key-manager', 'validator', 'consensus-rpc', 'storage-rpc'); + +ALTER TABLE chain.nodes + ADD COLUMN IF NOT EXISTS roles_new node_role[] NOT NULL DEFAULT '{}'::node_role[]; + +-- Migrate existing comma-separated string roles to the new enum array column. +WITH + split AS ( + SELECT + id, + regexp_split_to_table(COALESCE(roles, ''), '\s*,\s*') AS role_name + FROM + chain.nodes + WHERE + roles IS NOT NULL + AND roles <> '' + ), + filtered AS ( + SELECT + id, + role_name + FROM + split + WHERE + role_name IN ( + 'validator', + 'compute', + 'key-manager', + 'storage-rpc', + 'observer', + 'consensus-rpc' + ) + ), + dedup AS ( + SELECT DISTINCT + id, + role_name::node_role AS val + FROM + filtered + ), + agg AS ( + SELECT + id, + array_agg(val ORDER BY val) AS arr + FROM + dedup + GROUP BY + id + ) +UPDATE + chain.nodes AS n +SET + roles_new = a.arr +FROM + agg AS a +WHERE + n.id = a.id; + +ALTER TABLE chain.nodes DROP COLUMN roles; +ALTER TABLE chain.nodes RENAME COLUMN roles_new TO roles; + +COMMIT; diff --git a/storage/migrations/52_runtime_nodes.up.sql b/storage/migrations/52_runtime_nodes.up.sql new file mode 100644 index 000000000..73440bd2d --- /dev/null +++ b/storage/migrations/52_runtime_nodes.up.sql @@ -0,0 +1,8 @@ +BEGIN; + +ALTER TABLE chain.runtime_nodes + ADD COLUMN IF NOT EXISTS version TEXT, + ADD COLUMN IF NOT EXISTS capabilities BYTEA, + ADD COLUMN IF NOT EXISTS extra_info BYTEA; + +COMMIT; diff --git a/storage/oasis/nodeapi/api.go b/storage/oasis/nodeapi/api.go index 9213a43a6..81e803254 100644 --- a/storage/oasis/nodeapi/api.go +++ b/storage/oasis/nodeapi/api.go @@ -193,15 +193,24 @@ type RuntimeStartedEvent struct { type RuntimeSuspendedEvent struct { RuntimeID coreCommon.Namespace } + +type NodeRuntime struct { + ID coreCommon.Namespace + Version string + RawCapabilities []byte // CBOR-encoded capabilities struct. + ExtraInfo []byte +} + type ( - Node node.Node + Node node.Node + NodeUnfrozenEvent registry.NodeUnfrozenEvent EntityEvent registry.EntityEvent NodeEvent struct { NodeID signature.PublicKey EntityID signature.PublicKey Expiration uint64 // Epoch in which the node expires. - RuntimeIDs []coreCommon.Namespace + Runtimes []*NodeRuntime VRFPubKey *signature.PublicKey TLSAddresses []string // TCP addresses of the node's TLS-enabled gRPC endpoint. TLSPubKey signature.PublicKey diff --git a/storage/oasis/nodeapi/cobalt/convert.go b/storage/oasis/nodeapi/cobalt/convert.go index 352226924..d7f1f1efa 100644 --- a/storage/oasis/nodeapi/cobalt/convert.go +++ b/storage/oasis/nodeapi/cobalt/convert.go @@ -9,7 +9,6 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/quantity" // nexus-internal data types. - coreCommon "github.com/oasisprotocol/oasis-core/go/common" registry "github.com/oasisprotocol/nexus/coreapi/v22.2.11/registry/api" "github.com/oasisprotocol/nexus/coreapi/v22.2.11/roothash/api/message" @@ -280,10 +279,6 @@ func convertRegistryEvent(e registryCobalt.Event) nodeapi.Event { if e.NodeEvent.Node.VRF != nil { vrfID = &e.NodeEvent.Node.VRF.ID } - runtimeIDs := make([]coreCommon.Namespace, len(e.NodeEvent.Node.Runtimes)) - for i, r := range e.NodeEvent.Node.Runtimes { - runtimeIDs[i] = r.ID - } tlsAddresses := make([]string, len(e.NodeEvent.Node.TLS.Addresses)) for i, a := range e.NodeEvent.Node.TLS.Addresses { tlsAddresses[i] = a.String() @@ -292,6 +287,16 @@ func convertRegistryEvent(e registryCobalt.Event) nodeapi.Event { for i, a := range e.NodeEvent.Node.P2P.Addresses { p2pAddresses[i] = a.String() } + runtimes := make([]*nodeapi.NodeRuntime, len(e.NodeEvent.Node.Runtimes)) + for i, r := range e.NodeEvent.Node.Runtimes { + capabilities := cbor.Marshal(r.Capabilities) + runtimes[i] = &nodeapi.NodeRuntime{ + ID: r.ID, + Version: r.Version.String(), + RawCapabilities: capabilities, + ExtraInfo: r.ExtraInfo, + } + } consensusAddresses := make([]string, len(e.NodeEvent.Node.Consensus.Addresses)) for i, a := range e.NodeEvent.Node.Consensus.Addresses { consensusAddresses[i] = a.String() @@ -307,7 +312,7 @@ func convertRegistryEvent(e registryCobalt.Event) nodeapi.Event { TLSNextPubKey: e.NodeEvent.Node.TLS.NextPubKey, P2PID: e.NodeEvent.Node.P2P.ID, P2PAddresses: p2pAddresses, - RuntimeIDs: runtimeIDs, + Runtimes: runtimes, ConsensusID: e.NodeEvent.Node.Consensus.ID, ConsensusAddresses: consensusAddresses, IsRegistration: e.NodeEvent.IsRegistration, diff --git a/storage/oasis/nodeapi/damask/convert.go b/storage/oasis/nodeapi/damask/convert.go index ac9c037c0..02a1ba3a7 100644 --- a/storage/oasis/nodeapi/damask/convert.go +++ b/storage/oasis/nodeapi/damask/convert.go @@ -4,7 +4,7 @@ import ( "strings" // nexus-internal data types. - coreCommon "github.com/oasisprotocol/oasis-core/go/common" + "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" "github.com/oasisprotocol/oasis-core/go/common/quantity" @@ -144,10 +144,6 @@ func convertRegistryEvent(e registryDamask.Event) nodeapi.Event { if e.NodeEvent.Node.VRF != nil { vrfID = &e.NodeEvent.Node.VRF.ID } - runtimeIDs := make([]coreCommon.Namespace, len(e.NodeEvent.Node.Runtimes)) - for i, r := range e.NodeEvent.Node.Runtimes { - runtimeIDs[i] = r.ID - } tlsAddresses := make([]string, len(e.NodeEvent.Node.TLS.Addresses)) for i, a := range e.NodeEvent.Node.TLS.Addresses { tlsAddresses[i] = a.String() @@ -156,6 +152,16 @@ func convertRegistryEvent(e registryDamask.Event) nodeapi.Event { for i, a := range e.NodeEvent.Node.P2P.Addresses { p2pAddresses[i] = a.String() } + runtimes := make([]*nodeapi.NodeRuntime, len(e.NodeEvent.Node.Runtimes)) + for i, r := range e.NodeEvent.Node.Runtimes { + capabilities := cbor.Marshal(r.Capabilities) + runtimes[i] = &nodeapi.NodeRuntime{ + ID: r.ID, + Version: r.Version.String(), + RawCapabilities: capabilities, + ExtraInfo: r.ExtraInfo, + } + } consensusAddresses := make([]string, len(e.NodeEvent.Node.Consensus.Addresses)) for i, a := range e.NodeEvent.Node.Consensus.Addresses { consensusAddresses[i] = a.String() @@ -171,7 +177,7 @@ func convertRegistryEvent(e registryDamask.Event) nodeapi.Event { TLSNextPubKey: e.NodeEvent.Node.TLS.NextPubKey, P2PID: e.NodeEvent.Node.P2P.ID, P2PAddresses: p2pAddresses, - RuntimeIDs: runtimeIDs, + Runtimes: runtimes, ConsensusID: e.NodeEvent.Node.Consensus.ID, ConsensusAddresses: consensusAddresses, IsRegistration: e.NodeEvent.IsRegistration, diff --git a/storage/oasis/nodeapi/eden/convert.go b/storage/oasis/nodeapi/eden/convert.go index 4ebf9f608..5739a0032 100644 --- a/storage/oasis/nodeapi/eden/convert.go +++ b/storage/oasis/nodeapi/eden/convert.go @@ -6,8 +6,6 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" - coreCommon "github.com/oasisprotocol/oasis-core/go/common" - "github.com/oasisprotocol/nexus/coreapi/v22.2.11/common/node" governance "github.com/oasisprotocol/nexus/coreapi/v22.2.11/governance/api" registry "github.com/oasisprotocol/nexus/coreapi/v22.2.11/registry/api" @@ -316,14 +314,20 @@ func convertRegistryEvent(e registryEden.Event) nodeapi.Event { case e.NodeEvent != nil: var vrfID *signature.PublicKey vrfID = &e.NodeEvent.Node.VRF.ID - runtimeIDs := make([]coreCommon.Namespace, len(e.NodeEvent.Node.Runtimes)) - for i, r := range e.NodeEvent.Node.Runtimes { - runtimeIDs[i] = r.ID - } p2pAddresses := make([]string, len(e.NodeEvent.Node.P2P.Addresses)) for i, a := range e.NodeEvent.Node.P2P.Addresses { p2pAddresses[i] = a.String() } + runtimes := make([]*nodeapi.NodeRuntime, len(e.NodeEvent.Node.Runtimes)) + for i, r := range e.NodeEvent.Node.Runtimes { + capabilities := cbor.Marshal(r.Capabilities) + runtimes[i] = &nodeapi.NodeRuntime{ + ID: r.ID, + Version: r.Version.String(), + RawCapabilities: capabilities, + ExtraInfo: r.ExtraInfo, + } + } consensusAddresses := make([]string, len(e.NodeEvent.Node.Consensus.Addresses)) for i, a := range e.NodeEvent.Node.Consensus.Addresses { consensusAddresses[i] = a.String() @@ -339,7 +343,7 @@ func convertRegistryEvent(e registryEden.Event) nodeapi.Event { TLSNextPubKey: signature.PublicKey{}, // Not used any more in Eden. P2PID: e.NodeEvent.Node.P2P.ID, P2PAddresses: p2pAddresses, - RuntimeIDs: runtimeIDs, + Runtimes: runtimes, ConsensusID: e.NodeEvent.Node.Consensus.ID, ConsensusAddresses: consensusAddresses, IsRegistration: e.NodeEvent.IsRegistration,