diff --git a/cmd/node/main.go b/cmd/node/main.go index a3627ff..5bee79b 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -41,7 +41,7 @@ func main() { fmt.Printf("%s %s\n", programName, version.GetVersionString()) os.Exit(0) } - // Configure default logger + // Configure logger logLevel := slog.LevelInfo if globalFlags.debug { logLevel = slog.LevelDebug @@ -53,7 +53,7 @@ func main() { ) slog.SetDefault(logger) // Run node - if err := node.Run(); err != nil { + if err := node.Run(logger); err != nil { slog.Error(err.Error()) os.Exit(1) } diff --git a/config.go b/config.go new file mode 100644 index 0000000..159cd79 --- /dev/null +++ b/config.go @@ -0,0 +1,109 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +import ( + "fmt" + "io" + "log/slog" + + ouroboros "github.com/blinklabs-io/gouroboros" +) + +type Config struct { + logger *slog.Logger + listeners []ListenerConfig + network string + networkMagic uint32 + peerSharing bool + // TODO +} + +// configPopulateNetworkMagic uses the named network (if specified) to determine the network magic value (if not specified) +func (n *Node) configPopulateNetworkMagic() error { + if n.config.networkMagic == 0 && n.config.network != "" { + tmpCfg := n.config + tmpNetwork := ouroboros.NetworkByName(n.config.network) + if tmpNetwork == ouroboros.NetworkInvalid { + return fmt.Errorf("unknown network name: %s", n.config.network) + } + tmpCfg.networkMagic = tmpNetwork.NetworkMagic + n.config = tmpCfg + } + return nil +} + +func (n *Node) configValidate() error { + if n.config.networkMagic == 0 { + return fmt.Errorf("invalid network magic value: %d", n.config.networkMagic) + } + if len(n.config.listeners) == 0 { + return fmt.Errorf("no listeners defined") + } + return nil +} + +// ConfigOptionFunc is a type that represents functions that modify the Connection config +type ConfigOptionFunc func(*Config) + +// NewConfig creates a new node config with the specified options +func NewConfig(opts ...ConfigOptionFunc) Config { + c := Config{ + // Default logger will throw away logs + // We do this so we don't have to add guards around every log operation + logger: slog.New(slog.NewJSONHandler(io.Discard, nil)), + // TODO: add defaults + } + // Apply options + for _, opt := range opts { + opt(&c) + } + return c +} + +// WithLogger specifies the logger to use. This defaults to discarding log output +func WithLogger(logger *slog.Logger) ConfigOptionFunc { + return func(c *Config) { + c.logger = logger + } +} + +// WithListeners specifies the listener config(s) to use +func WithListeners(listeners ...ListenerConfig) ConfigOptionFunc { + return func(c *Config) { + c.listeners = append(c.listeners, listeners...) + } +} + +// WithNetwork specifies the named network to operate on. This will automatically set the appropriate network magic value +func WithNetwork(network string) ConfigOptionFunc { + return func(c *Config) { + c.network = network + } +} + +// WithNetworkMagic specifies the network magic value to use. This will override any named network specified +func WithNetworkMagic(networkMagic uint32) ConfigOptionFunc { + return func(c *Config) { + c.networkMagic = networkMagic + } +} + +// WithPeerSharing specifies whether to enable peer sharing. This is disabled by default +func WithPeerSharing(peerSharing bool) ConfigOptionFunc { + return func(c *Config) { + c.peerSharing = peerSharing + } +} diff --git a/go.mod b/go.mod index 7837422..d660b46 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,19 @@ module github.com/blinklabs-io/node go 1.21.5 -require github.com/spf13/cobra v1.8.0 +require ( + github.com/blinklabs-io/gouroboros v0.78.0 + github.com/spf13/cobra v1.8.0 +) require ( + github.com/fxamacker/cbor/v2 v2.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jinzhu/copier v0.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/utxorpc/go-codegen v0.4.4 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/sys v0.18.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index d0e8c2c..6cb11f2 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,34 @@ +github.com/blinklabs-io/gouroboros v0.78.0 h1:m/qI5WhN4ZpF96p+dQteiqWP0w+jn1vpL2BG/9JCjgo= +github.com/blinklabs-io/gouroboros v0.78.0/go.mod h1:xlSqLRuMknQWY73AJCx2HRWo49BfyGnreYT1t3/riVo= +github.com/blinklabs-io/ouroboros-mock v0.3.0 h1:6VRWyhAv0k7nQEgzFpuqhS/n8OM+OAaLN/sCT5K2Hbc= +github.com/blinklabs-io/ouroboros-mock v0.3.0/go.mod h1:0dzTNEk/Kvqa7qYHDy7/Nn3OTt+EOosMknB37FRzI1k= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= +github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/utxorpc/go-codegen v0.4.4 h1:NnRFkqKJ9ZkZ3pffpD3bb6K0IU6IaDXbkVD/DzDmuKs= +github.com/utxorpc/go-codegen v0.4.4/go.mod h1:NWAVa5/vHWgSrp/wJzXnMpbaBeST0PmWCU5vX63hZdw= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/node/node.go b/internal/node/node.go index 7f3ad9e..efa3b50 100644 --- a/internal/node/node.go +++ b/internal/node/node.go @@ -16,14 +16,31 @@ package node import ( "log/slog" + "net" "github.com/blinklabs-io/node" ) -func Run() error { - // TODO - slog.Info("running node") - n, err := node.New() +func Run(logger *slog.Logger) error { + logger.Info("running node") + // TODO: make this configurable + l, err := net.Listen("tcp", ":3000") + if err != nil { + return err + } + logger.Info("listening for connections on :3000") + n, err := node.New( + node.NewConfig( + node.WithLogger(logger), + // TODO: make this configurable + node.WithNetwork("preview"), + node.WithListeners( + node.ListenerConfig{ + Listener: l, + }, + ), + ), + ) if err != nil { return err } diff --git a/listener.go b/listener.go new file mode 100644 index 0000000..4b9d8a8 --- /dev/null +++ b/listener.go @@ -0,0 +1,60 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +import ( + "fmt" + "net" + + ouroboros "github.com/blinklabs-io/gouroboros" +) + +type ListenerConfig struct { + UseNtC bool + Listener net.Listener + // TODO +} + +func (n *Node) startListener(l ListenerConfig) error { + defaultConnOpts := []ouroboros.ConnectionOptionFunc{ + ouroboros.WithNetworkMagic(n.config.networkMagic), + ouroboros.WithNodeToNode(!l.UseNtC), + ouroboros.WithServer(true), + ouroboros.WithPeerSharing(n.config.peerSharing), + // TODO: add protocol configs to configure callback functions + } + for { + // Accept connection + conn, err := l.Listener.Accept() + if err != nil { + n.config.logger.Error(fmt.Sprintf("accept failed: %s", err)) + continue + } + n.config.logger.Info(fmt.Sprintf("accepted connection from %s", conn.RemoteAddr())) + // Setup Ouroboros connection + connOpts := append( + defaultConnOpts, + ouroboros.WithConnection(conn), + ) + oConn, err := ouroboros.NewConnection(connOpts...) + if err != nil { + n.config.logger.Error(fmt.Sprintf("failed to setup connection: %s", err)) + continue + } + // Add to connection manager + // TODO: add tags for connection for later tracking + n.connManager.AddConnection(oConn) + } +} diff --git a/node.go b/node.go index 4df8330..51bdc95 100644 --- a/node.go +++ b/node.go @@ -14,16 +14,60 @@ package node +import ( + "fmt" + + ouroboros "github.com/blinklabs-io/gouroboros" +) + type Node struct { + config Config + connManager *ouroboros.ConnectionManager // TODO } -func New() (*Node, error) { - // TODO - return &Node{}, nil +func New(cfg Config) (*Node, error) { + n := &Node{ + config: cfg, + } + if err := n.configPopulateNetworkMagic(); err != nil { + return nil, fmt.Errorf("invalid configuration: %s", err) + } + if err := n.configValidate(); err != nil { + return nil, fmt.Errorf("invalid configuration: %s", err) + } + return n, nil } func (n *Node) Run() error { + // Configure connection manager + n.connManager = ouroboros.NewConnectionManager( + ouroboros.ConnectionManagerConfig{ + ConnClosedFunc: n.connectionManagerConnClosed, + }, + ) + // Start listeners + for _, l := range n.config.listeners { + go n.startListener(l) + } // TODO + + // Wait forever + select {} return nil } + +func (n *Node) connectionManagerConnClosed(connId ouroboros.ConnectionId, err error) { + if err != nil { + n.config.logger.Error(fmt.Sprintf("unexpected connection failure: %s: %s", connId.String(), err)) + } else { + n.config.logger.Info(fmt.Sprintf("connection closed: %s", connId.String())) + } + conn := n.connManager.GetConnectionById(connId) + if conn == nil { + return + } + // Remove connection + n.connManager.RemoveConnection(connId) + // TODO: additional cleanup +}