diff --git a/connection_manager.go b/connection_manager.go deleted file mode 100644 index b7951922..00000000 --- a/connection_manager.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2023 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 ouroboros - -import "sync" - -// ConnectionManagerConnClosedFunc is a function that takes a connection ID and an optional error -type ConnectionManagerConnClosedFunc func(ConnectionId, error) - -// ConnectionManagerTag represents the various tags that can be associated with a host or connection -type ConnectionManagerTag uint16 - -const ( - ConnectionManagerTagNone ConnectionManagerTag = iota - - ConnectionManagerTagHostProducer - ConnectionManagerTagHostLocalRoot - ConnectionManagerTagHostPublicRoot - ConnectionManagerTagHostP2PLedger - ConnectionManagerTagHostP2PGossip - - ConnectionManagerTagRoleInitiator - ConnectionManagerTagRoleResponder - // TODO: add more tags -) - -func (c ConnectionManagerTag) String() string { - tmp := map[ConnectionManagerTag]string{ - ConnectionManagerTagHostProducer: "HostProducer", - ConnectionManagerTagHostLocalRoot: "HostLocalRoot", - ConnectionManagerTagHostPublicRoot: "HostPublicRoot", - ConnectionManagerTagHostP2PLedger: "HostP2PLedger", - ConnectionManagerTagHostP2PGossip: "HostP2PGossip", - ConnectionManagerTagRoleInitiator: "RoleInitiator", - ConnectionManagerTagRoleResponder: "RoleResponder", - // TODO: add more tags to match those added above - } - ret, ok := tmp[c] - if !ok { - return "Unknown" - } - return ret -} - -type ConnectionManager struct { - config ConnectionManagerConfig - hosts []ConnectionManagerHost - connections map[ConnectionId]*ConnectionManagerConnection - connectionsMutex sync.Mutex -} - -type ConnectionManagerConfig struct { - ConnClosedFunc ConnectionManagerConnClosedFunc -} - -type ConnectionManagerHost struct { - Address string - Port uint - Tags map[ConnectionManagerTag]bool -} - -func NewConnectionManager(cfg ConnectionManagerConfig) *ConnectionManager { - return &ConnectionManager{ - config: cfg, - connections: make(map[ConnectionId]*ConnectionManagerConnection), - } -} - -func (c *ConnectionManager) AddHost( - address string, - port uint, - tags ...ConnectionManagerTag, -) { - tmpTags := map[ConnectionManagerTag]bool{} - for _, tag := range tags { - tmpTags[tag] = true - } - c.hosts = append( - c.hosts, - ConnectionManagerHost{ - Address: address, - Port: port, - Tags: tmpTags, - }, - ) -} - -func (c *ConnectionManager) AddHostsFromTopology(topology *TopologyConfig) { - for _, host := range topology.Producers { - c.AddHost(host.Address, host.Port, ConnectionManagerTagHostProducer) - } - for _, localRoot := range topology.LocalRoots { - for _, host := range localRoot.AccessPoints { - c.AddHost( - host.Address, - host.Port, - ConnectionManagerTagHostLocalRoot, - ) - } - } - for _, publicRoot := range topology.PublicRoots { - for _, host := range publicRoot.AccessPoints { - c.AddHost( - host.Address, - host.Port, - ConnectionManagerTagHostPublicRoot, - ) - } - } -} - -func (c *ConnectionManager) AddConnection(conn *Connection) { - connId := conn.Id() - c.connectionsMutex.Lock() - c.connections[connId] = &ConnectionManagerConnection{ - Conn: conn, - } - c.connectionsMutex.Unlock() - go func() { - err := <-conn.ErrorChan() - // Call configured connection closed callback func - c.config.ConnClosedFunc(connId, err) - }() -} - -func (c *ConnectionManager) RemoveConnection(connId ConnectionId) { - c.connectionsMutex.Lock() - delete(c.connections, connId) - c.connectionsMutex.Unlock() -} - -func (c *ConnectionManager) GetConnectionById( - connId ConnectionId, -) *ConnectionManagerConnection { - c.connectionsMutex.Lock() - defer c.connectionsMutex.Unlock() - return c.connections[connId] -} - -func (c *ConnectionManager) GetConnectionsByTags( - tags ...ConnectionManagerTag, -) []*ConnectionManagerConnection { - var ret []*ConnectionManagerConnection - c.connectionsMutex.Lock() - for _, conn := range c.connections { - skipConn := false - for _, tag := range tags { - if _, ok := conn.Tags[tag]; !ok { - skipConn = true - break - } - } - if !skipConn { - ret = append(ret, conn) - } - } - c.connectionsMutex.Unlock() - return ret -} - -type ConnectionManagerConnection struct { - Conn *Connection - Tags map[ConnectionManagerTag]bool -} - -func (c *ConnectionManagerConnection) AddTags(tags ...ConnectionManagerTag) { - for _, tag := range tags { - c.Tags[tag] = true - } -} - -func (c *ConnectionManagerConnection) RemoveTags(tags ...ConnectionManagerTag) { - for _, tag := range tags { - delete(c.Tags, tag) - } -} diff --git a/connection_manager_test.go b/connection_manager_test.go deleted file mode 100644 index 0b96b543..00000000 --- a/connection_manager_test.go +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2023 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 ouroboros_test - -import ( - "io" - "testing" - "time" - - ouroboros "github.com/blinklabs-io/gouroboros" - "github.com/blinklabs-io/gouroboros/protocol/keepalive" - ouroboros_mock "github.com/blinklabs-io/ouroboros-mock" - "go.uber.org/goleak" -) - -func TestConnectionManagerTagString(t *testing.T) { - testDefs := map[ouroboros.ConnectionManagerTag]string{ - ouroboros.ConnectionManagerTagHostP2PLedger: "HostP2PLedger", - ouroboros.ConnectionManagerTagHostP2PGossip: "HostP2PGossip", - ouroboros.ConnectionManagerTagRoleInitiator: "RoleInitiator", - ouroboros.ConnectionManagerTagRoleResponder: "RoleResponder", - ouroboros.ConnectionManagerTagNone: "Unknown", - ouroboros.ConnectionManagerTag(9999): "Unknown", - } - for k, v := range testDefs { - if k.String() != v { - t.Fatalf( - "did not get expected string for ID %d: got %s, expected %s", - k, - k.String(), - v, - ) - } - } -} - -func TestConnectionManagerConnError(t *testing.T) { - defer goleak.VerifyNone(t) - var expectedConnId ouroboros.ConnectionId - expectedErr := io.EOF - doneChan := make(chan any) - connManager := ouroboros.NewConnectionManager( - ouroboros.ConnectionManagerConfig{ - ConnClosedFunc: func(connId ouroboros.ConnectionId, err error) { - if err != nil { - if connId != expectedConnId { - t.Fatalf( - "did not receive error from expected connection: got %d, wanted %d", - connId, - expectedConnId, - ) - } - if err != expectedErr { - t.Fatalf( - "did not receive expected error: got: %s, expected: %s", - err, - expectedErr, - ) - } - close(doneChan) - } - }, - }, - ) - testIdx := 2 - for i := 0; i < 3; i++ { - mockConversation := ouroboros_mock.ConversationKeepAlive - if i == testIdx { - mockConversation = ouroboros_mock.ConversationKeepAliveClose - } - mockConn := ouroboros_mock.NewConnection( - ouroboros_mock.ProtocolRoleClient, - mockConversation, - ) - oConn, err := ouroboros.New( - ouroboros.WithConnection(mockConn), - ouroboros.WithNetworkMagic(ouroboros_mock.MockNetworkMagic), - ouroboros.WithNodeToNode(true), - ouroboros.WithKeepAlive(true), - ouroboros.WithKeepAliveConfig( - keepalive.NewConfig( - keepalive.WithCookie(ouroboros_mock.MockKeepAliveCookie), - keepalive.WithPeriod(2*time.Second), - keepalive.WithTimeout(1*time.Second), - ), - ), - ) - if err != nil { - t.Fatalf("unexpected error when creating Ouroboros object: %s", err) - } - if i == testIdx { - expectedConnId = oConn.Id() - } - connManager.AddConnection(oConn) - } - select { - case <-doneChan: - // Shutdown other connections - for _, tmpConn := range connManager.GetConnectionsByTags() { - if tmpConn.Conn.Id() != expectedConnId { - tmpConn.Conn.Close() - } - } - // TODO: actually wait for shutdown - time.Sleep(5 * time.Second) - return - case <-time.After(10 * time.Second): - t.Fatalf("did not receive error within timeout") - } -} - -func TestConnectionManagerConnClosed(t *testing.T) { - defer goleak.VerifyNone(t) - var expectedConnId ouroboros.ConnectionId - doneChan := make(chan any) - connManager := ouroboros.NewConnectionManager( - ouroboros.ConnectionManagerConfig{ - ConnClosedFunc: func(connId ouroboros.ConnectionId, err error) { - if connId != expectedConnId { - t.Fatalf( - "did not receive closed signal from expected connection: got %d, wanted %d", - connId, - expectedConnId, - ) - } - if err != nil { - t.Fatalf("received unexpected error: %s", err) - } - close(doneChan) - }, - }, - ) - mockConn := ouroboros_mock.NewConnection( - ouroboros_mock.ProtocolRoleClient, - []ouroboros_mock.ConversationEntry{ - ouroboros_mock.ConversationEntryHandshakeRequestGeneric, - ouroboros_mock.ConversationEntryHandshakeNtNResponse, - }, - ) - oConn, err := ouroboros.New( - ouroboros.WithConnection(mockConn), - ouroboros.WithNetworkMagic(ouroboros_mock.MockNetworkMagic), - ouroboros.WithNodeToNode(true), - ouroboros.WithKeepAlive(false), - ) - if err != nil { - t.Fatalf("unexpected error when creating Ouroboros object: %s", err) - } - expectedConnId = oConn.Id() - connManager.AddConnection(oConn) - time.AfterFunc( - 1*time.Second, - func() { - oConn.Close() - }, - ) - select { - case <-doneChan: - // TODO: actually wait for shutdown - time.Sleep(5 * time.Second) - return - case <-time.After(10 * time.Second): - t.Fatalf("did not receive error within timeout") - } -} diff --git a/topology.go b/topology.go deleted file mode 100644 index 13710be2..00000000 --- a/topology.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2023 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 ouroboros - -import ( - "encoding/json" - "io" - "os" -) - -// TopologyConfig represents a Cardano node topology config -type TopologyConfig struct { - Producers []TopologyConfigLegacyProducer `json:"Producers"` - LocalRoots []TopologyConfigP2PLocalRoot `json:"localRoots"` - PublicRoots []TopologyConfigP2PPublicRoot `json:"publicRoots"` - UseLedgerAfterSlot uint64 `json:"useLedgerAfterSlot"` -} - -type TopologyConfigLegacyProducer struct { - Address string `json:"addr"` - Port uint `json:"port"` - Valency uint `json:"valency"` - Continent string `json:"continent"` - State string `json:"state"` -} - -type TopologyConfigP2PAccessPoint struct { - Address string `json:"address"` - Port uint `json:"port"` -} - -type TopologyConfigP2PLocalRoot struct { - AccessPoints []TopologyConfigP2PAccessPoint `json:"accessPoints"` - Advertise bool `json:"advertise"` - Valency uint `json:"valency"` -} - -type TopologyConfigP2PPublicRoot struct { - AccessPoints []TopologyConfigP2PAccessPoint `json:"accessPoints"` - Advertise bool `json:"advertise"` - Valency uint `json:"valency"` -} - -func NewTopologyConfigFromFile(path string) (*TopologyConfig, error) { - dataFile, err := os.Open(path) - if err != nil { - return nil, err - } - return NewTopologyConfigFromReader(dataFile) -} - -func NewTopologyConfigFromReader(r io.Reader) (*TopologyConfig, error) { - t := &TopologyConfig{} - data, err := io.ReadAll(r) - if err != nil { - return nil, err - } - if err := json.Unmarshal(data, t); err != nil { - return nil, err - } - return t, nil -} diff --git a/topology_test.go b/topology_test.go deleted file mode 100644 index af2f3c2f..00000000 --- a/topology_test.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2023 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 ouroboros_test - -import ( - "reflect" - "strings" - "testing" - - ouroboros "github.com/blinklabs-io/gouroboros" -) - -type topologyTestDefinition struct { - jsonData string - expectedObject *ouroboros.TopologyConfig -} - -var topologyTests = []topologyTestDefinition{ - { - jsonData: ` -{ - "Producers": [ - { - "addr": "backbone.cardano.iog.io", - "port": 3001, - "valency": 2 - } - ] -} -`, - expectedObject: &ouroboros.TopologyConfig{ - Producers: []ouroboros.TopologyConfigLegacyProducer{ - { - Address: "backbone.cardano.iog.io", - Port: 3001, - Valency: 2, - }, - }, - }, - }, - { - jsonData: ` -{ - "localRoots": [ - { - "accessPoints": [], - "advertise": false, - "valency": 1 - } - ], - "publicRoots": [ - { - "accessPoints": [ - { - "address": "backbone.cardano.iog.io", - "port": 3001 - } - ], - "advertise": false - }, - { - "accessPoints": [ - { - "address": "backbone.mainnet.emurgornd.com", - "port": 3001 - } - ], - "advertise": false - } - ], - "useLedgerAfterSlot": 99532743 -} -`, - expectedObject: &ouroboros.TopologyConfig{ - LocalRoots: []ouroboros.TopologyConfigP2PLocalRoot{ - { - AccessPoints: []ouroboros.TopologyConfigP2PAccessPoint{}, - Advertise: false, - Valency: 1, - }, - }, - PublicRoots: []ouroboros.TopologyConfigP2PPublicRoot{ - { - AccessPoints: []ouroboros.TopologyConfigP2PAccessPoint{ - { - Address: "backbone.cardano.iog.io", - Port: 3001, - }, - }, - Advertise: false, - }, - { - AccessPoints: []ouroboros.TopologyConfigP2PAccessPoint{ - { - Address: "backbone.mainnet.emurgornd.com", - Port: 3001, - }, - }, - Advertise: false, - }, - }, - UseLedgerAfterSlot: 99532743, - }, - }, -} - -func TestParseTopologyConfig(t *testing.T) { - for _, test := range topologyTests { - topology, err := ouroboros.NewTopologyConfigFromReader( - strings.NewReader(test.jsonData), - ) - if err != nil { - t.Fatalf("failed to load TopologyConfig from JSON data: %s", err) - } - if !reflect.DeepEqual(topology, test.expectedObject) { - t.Fatalf( - "did not get expected object\n got:\n %#v\n wanted:\n %#v", - topology, - test.expectedObject, - ) - } - } -}