From 3b8c6c826039e1693a069af582a125d9d61c98f1 Mon Sep 17 00:00:00 2001 From: Adam Babik Date: Wed, 25 Jul 2018 16:48:02 +0200 Subject: [PATCH] Add interface to verify trusted MailServers (#1112) The goal of this PR is to add an interface to verify MailServers. In this PR, MailServers are hardcoded in status-go. The next iteration will use a smart contract. --- node/node.go | 8 ++++ node/node_test.go | 11 +++++ node/status_node.go | 1 + params/cluster.go | 21 +++++++++ params/config.go | 4 ++ peers/cotopicpool.go | 49 ++++++++++++++++---- peers/cotopicpool_test.go | 41 +++++++++++++++-- peers/peerpool.go | 5 +- peers/peerpool_test.go | 82 ++++++++++++++++++++++++++++++--- peers/topicpool_test.go | 2 +- peers/verifier/verifier.go | 30 ++++++++++++ peers/verifier/verifier_test.go | 17 +++++++ 12 files changed, 249 insertions(+), 22 deletions(-) create mode 100644 peers/verifier/verifier.go create mode 100644 peers/verifier/verifier_test.go diff --git a/node/node.go b/node/node.go index c6f0871fa..76062aa03 100644 --- a/node/node.go +++ b/node/node.go @@ -327,6 +327,14 @@ func parseNodesV5(enodes []string) []*discv5.Node { return nodes } +func parseNodesToNodeID(enodes []string) []discover.NodeID { + nodeIDs := make([]discover.NodeID, 0, len(enodes)) + for _, node := range parseNodes(enodes) { + nodeIDs = append(nodeIDs, node.ID) + } + return nodeIDs +} + // whisperTimeSource get timeSource to be used by whisper func whisperTimeSource(ctx *node.ServiceContext) (func() time.Time, error) { var timeSource *timesource.NTPTimeSource diff --git a/node/node_test.go b/node/node_test.go index 5f600e8a0..3cebb9713 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -1,8 +1,10 @@ package node import ( + "fmt" "testing" + "github.com/ethereum/go-ethereum/p2p/discover" . "github.com/status-im/status-go/t/utils" "github.com/stretchr/testify/require" "github.com/syndtr/goleveldb/leveldb" @@ -57,3 +59,12 @@ func TestMakeNodeMalformedBootnodes(t *testing.T) { _, err = MakeNode(config, db) require.NoError(t, err) } + +func TestParseNodesToNodeID(t *testing.T) { + nodeIDs := parseNodesToNodeID([]string{ + "enode://badkey@127.0.0.1:30303", + fmt.Sprintf("enode://%s@127.0.0.1:30303", discover.NodeID{1}), + }) + require.Len(t, nodeIDs, 1) + require.Equal(t, discover.NodeID{1}, nodeIDs[0]) +} diff --git a/node/status_node.go b/node/status_node.go index d64e13c7d..2d4ed8a87 100644 --- a/node/status_node.go +++ b/node/status_node.go @@ -223,6 +223,7 @@ func (n *StatusNode) startDiscovery() error { options := peers.NewDefaultOptions() // TODO(dshulyak) consider adding a flag to define this behaviour options.AllowStop = len(n.config.RegisterTopics) == 0 + options.TrustedMailServers = parseNodesToNodeID(n.config.ClusterConfig.TrustedMailServers) n.peerPool = peers.NewPeerPool( n.discovery, n.config.RequireTopics, diff --git a/params/cluster.go b/params/cluster.go index 32ad467ab..16b0270b1 100644 --- a/params/cluster.go +++ b/params/cluster.go @@ -12,6 +12,7 @@ type cluster struct { NetworkID int `json:"networkID"` StaticNodes []string `json:"staticnodes"` BootNodes []string `json:"bootnodes"` + MailServers []string `json:"mailservers"` // list of trusted mail servers } var ropstenCluster = cluster{ @@ -26,6 +27,14 @@ var ropstenCluster = cluster{ "enode://a6a2a9b3a7cbb0a15da74301537ebba549c990e3325ae78e1272a19a3ace150d03c184b8ac86cc33f1f2f63691e467d49308f02d613277754c4dccd6773b95e8@206.189.243.176:30304", // node-01.do-ams3.eth.beta "enode://207e53d9bf66be7441e3daba36f53bfbda0b6099dba9a865afc6260a2d253fb8a56a72a48598a4f7ba271792c2e4a8e1a43aaef7f34857f520c8c820f63b44c8@35.224.15.65:30304", // node-01.gc-us-central1-a.eth.beta }, + MailServers: []string{ + "enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.108.78:30504", // mail-01.do-ams3.eth.beta + "enode://7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8@206.189.108.63:30504", // mail-02.do-ams3.eth.beta + "enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@206.189.7.30:30504", // mail-03.do-ams3.eth.beta + "enode://7de99e4cb1b3523bd26ca212369540646607c721ad4f3e5c821ed9148150ce6ce2e72631723002210fac1fd52dfa8bbdf3555e05379af79515e1179da37cc3db@35.188.19.210:30504", // mail-01.gc-us-central1-a.eth.beta + "enode://015e22f6cd2b44c8a51bd7a23555e271e0759c7d7f52432719665a74966f2da456d28e154e836bee6092b4d686fe67e331655586c57b718be3997c1629d24167@35.226.21.19:30504", // mail-02.gc-us-central1-a.eth.beta + "enode://531e252ec966b7e83f5538c19bf1cde7381cc7949026a6e499b6e998e695751aadf26d4c98d5a4eabfb7cefd31c3c88d600a775f14ed5781520a88ecd25da3c6@35.225.227.79:30504", // mail-03.gc-us-central1-a.eth.beta + }, } var rinkebyCluster = cluster{ @@ -40,6 +49,10 @@ var rinkebyCluster = cluster{ "enode://ba41aa829287a0a9076d9bffed97c8ce2e491b99873288c9e886f16fd575306ac6c656db4fbf814f5a9021aec004ffa9c0ae8650f92fd10c12eeb7c364593eb3@51.15.69.147:30303", "enode://28ecf5272b560ca951f4cd7f1eb8bd62da5853b026b46db432c4b01797f5b0114819a090a72acd7f32685365ecd8e00450074fa0673039aefe10f3fb666e0f3f@51.15.76.249:30303", }, + MailServers: []string{ + "enode://43829580446ad138386dadb7fa50b6bd4d99f7c28659a0bc08115f8c0380005922a340962496f6af756a42b94a1522baa38a694fa27de59c3a73d4e08d5dbb31@206.189.6.48:30504", + "enode://70a2004e78399075f566033c42e9a0b1d43c683d4742755bb5457d03191be66a1b48c2b4fb259696839f28646a5828a1958b900860e27897f984ad0fc8482404@206.189.56.154:30504", + }, } var mainnetCluster = cluster{ @@ -54,6 +67,14 @@ var mainnetCluster = cluster{ "enode://a6a2a9b3a7cbb0a15da74301537ebba549c990e3325ae78e1272a19a3ace150d03c184b8ac86cc33f1f2f63691e467d49308f02d613277754c4dccd6773b95e8@206.189.243.176:30304", // node-01.do-ams3.eth.beta "enode://207e53d9bf66be7441e3daba36f53bfbda0b6099dba9a865afc6260a2d253fb8a56a72a48598a4f7ba271792c2e4a8e1a43aaef7f34857f520c8c820f63b44c8@35.224.15.65:30304", // node-01.gc-us-central1-a.eth.beta }, + MailServers: []string{ + "enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.108.78:30504", // mail-01.do-ams3.eth.beta + "enode://7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8@206.189.108.63:30504", // mail-02.do-ams3.eth.beta + "enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@206.189.7.30:30504", // mail-03.do-ams3.eth.beta + "enode://7de99e4cb1b3523bd26ca212369540646607c721ad4f3e5c821ed9148150ce6ce2e72631723002210fac1fd52dfa8bbdf3555e05379af79515e1179da37cc3db@35.188.19.210:30504", // mail-01.gc-us-central1-a.eth.beta + "enode://015e22f6cd2b44c8a51bd7a23555e271e0759c7d7f52432719665a74966f2da456d28e154e836bee6092b4d686fe67e331655586c57b718be3997c1629d24167@35.226.21.19:30504", // mail-02.gc-us-central1-a.eth.beta + "enode://531e252ec966b7e83f5538c19bf1cde7381cc7949026a6e499b6e998e695751aadf26d4c98d5a4eabfb7cefd31c3c88d600a775f14ed5781520a88ecd25da3c6@35.225.227.79:30504", // mail-03.gc-us-central1-a.eth.beta + }, } var betaCluster = []cluster{ropstenCluster, rinkebyCluster, mainnetCluster} diff --git a/params/config.go b/params/config.go index 64398e326..b38dedaa8 100644 --- a/params/config.go +++ b/params/config.go @@ -200,6 +200,9 @@ type ClusterConfig struct { // for a given mode (production vs development) BootNodes []string + // TrustedMailServers is a list of verified Mail Servers. + TrustedMailServers []string + // RendezvousNodes is a rendezvous discovery server. RendezvousNodes []string } @@ -644,6 +647,7 @@ func (c *NodeConfig) updateClusterConfig() error { if len(cluster.BootNodes) == 0 { c.NoDiscovery = true } + c.ClusterConfig.TrustedMailServers = cluster.MailServers break } } diff --git a/peers/cotopicpool.go b/peers/cotopicpool.go index b88e86b59..b227e5a7b 100644 --- a/peers/cotopicpool.go +++ b/peers/cotopicpool.go @@ -1,6 +1,8 @@ package peers import ( + "context" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discv5" @@ -8,22 +10,29 @@ import ( "github.com/status-im/status-go/signal" ) +// Verifier verifies if a give node is trusted. +type Verifier interface { + VerifyNode(context.Context, discover.NodeID) bool +} + // MailServerDiscoveryTopic topic name for mailserver discovery. const MailServerDiscoveryTopic = "whispermail" // MailServerDiscoveryLimits default mailserver discovery limits. var MailServerDiscoveryLimits = params.Limits{Min: 3, Max: 3} -// newCacheOnlyTopicPool returns instance of CacheOnlyTopicPool. -func newCacheOnlyTopicPool(t *TopicPool) *cacheOnlyTopicPool { - return &cacheOnlyTopicPool{ - TopicPool: t, - } -} - // cacheOnlyTopicPool handles a mail server topic pool. type cacheOnlyTopicPool struct { *TopicPool + verifier Verifier +} + +// newCacheOnlyTopicPool returns instance of CacheOnlyTopicPool. +func newCacheOnlyTopicPool(t *TopicPool, verifier Verifier) *cacheOnlyTopicPool { + return &cacheOnlyTopicPool{ + TopicPool: t, + verifier: verifier, + } } // MaxReached checks if the max allowed peers is reached or not. When true @@ -45,14 +54,34 @@ var sendEnodeDiscovered = signal.SendEnodeDiscovered // ConfirmAdded calls base TopicPool ConfirmAdded method and sends a signal // confirming the enode has been discovered. func (t *cacheOnlyTopicPool) ConfirmAdded(server *p2p.Server, nodeID discover.NodeID) { - t.TopicPool.ConfirmAdded(server, nodeID) - sendEnodeDiscovered(nodeID.String(), string(t.topic)) + trusted := t.verifier.VerifyNode(context.TODO(), nodeID) + if trusted { + // add to cache only if trusted + t.TopicPool.ConfirmAdded(server, nodeID) + sendEnodeDiscovered(nodeID.String(), string(t.topic)) + t.subtractToLimits() + } id := discv5.NodeID(nodeID) + + // If a peer was trusted, it was moved to connectedPeers, + // signal was sent and we can safely remove it. if peer, ok := t.connectedPeers[id]; ok { t.removeServerPeer(server, peer) + // Delete it from `connectedPeers` immediately to + // prevent removing it from the cache which logic is + // implemented in TopicPool. delete(t.connectedPeers, id) - t.subtractToLimits() + } + + // It a peer was not trusted, it is still in pendingPeers. + // We should remove it from the p2p.Server. + if peer, ok := t.pendingPeers[id]; ok { + t.removeServerPeer(server, peer.peerInfo) + // Delete it from `connectedPeers` immediately to + // prevent removing it from the cache which logic is + // implemented in TopicPool. + delete(t.pendingPeers, id) } } diff --git a/peers/cotopicpool_test.go b/peers/cotopicpool_test.go index a318f7df6..549553b0a 100644 --- a/peers/cotopicpool_test.go +++ b/peers/cotopicpool_test.go @@ -1,6 +1,7 @@ package peers import ( + "context" "testing" "time" @@ -42,7 +43,7 @@ func (s *CacheOnlyTopicPoolSuite) SetupTest() { cache, err := newInMemoryCache() s.Require().NoError(err) t := newTopicPool(nil, MailServerDiscoveryTopic, limits, 100*time.Millisecond, 200*time.Millisecond, cache) - s.topicPool = newCacheOnlyTopicPool(t) + s.topicPool = newCacheOnlyTopicPool(t, &testTrueVerifier{}) s.topicPool.running = 1 // This is a buffered channel to simplify testing. // If your test generates more than 10 mode changes, @@ -70,15 +71,15 @@ func (s *CacheOnlyTopicPoolSuite) TestReplacementPeerIsCounted() { // When we stop searching for peers (when Max limit is reached) s.topicPool.StopSearch(s.peer) s.True(s.topicPool.MaxReached()) - s.Equal(s.topicPool.limits.Max, 0) - s.Equal(s.topicPool.limits.Min, 0) + s.Equal(0, s.topicPool.limits.Max) + s.Equal(0, s.topicPool.limits.Min) // Then we should drop all connected peers s.Equal(len(s.topicPool.connectedPeers), 0) // And cached peers should remain cachedPeers := s.topicPool.cache.GetPeersRange(s.topicPool.topic, s.topicPool.maxCachedPeers) - s.Equal(len(cachedPeers), 1) + s.Equal(1, len(cachedPeers)) } func (s *CacheOnlyTopicPoolSuite) TestConfirmAddedSignals() { @@ -94,3 +95,35 @@ func (s *CacheOnlyTopicPoolSuite) TestConfirmAddedSignals() { s.Equal((discv5.NodeID{1}).String(), sentNodeID) s.Equal(MailServerDiscoveryTopic, sentTopic) } + +func (s *CacheOnlyTopicPoolSuite) TestNotTrustedPeer() { + var signalCalled bool + sendEnodeDiscovered = func(_, _ string) { signalCalled = true } + + s.topicPool.limits = params.NewLimits(1, 1) + s.topicPool.maxCachedPeers = 1 + s.topicPool.verifier = &testFalseVerifier{} + + foundPeer := discv5.NewNode(discv5.NodeID{1}, s.peer.Self().IP, 32311, 32311) + s.topicPool.processFoundNode(s.peer, foundPeer) + s.topicPool.ConfirmAdded(s.peer, discover.NodeID(foundPeer.ID)) + + s.False(signalCalled) + // limits should not change + s.Equal(1, s.topicPool.limits.Max) + s.Equal(1, s.topicPool.limits.Min) + // not verified peer shoud not be added to the cache + s.Equal(0, len(s.topicPool.cache.GetPeersRange(s.topicPool.topic, s.topicPool.limits.Max))) +} + +type testTrueVerifier struct{} + +func (v *testTrueVerifier) VerifyNode(context.Context, discover.NodeID) bool { + return true +} + +type testFalseVerifier struct{} + +func (v *testFalseVerifier) VerifyNode(context.Context, discover.NodeID) bool { + return false +} diff --git a/peers/peerpool.go b/peers/peerpool.go index a4406e93c..3eee91354 100644 --- a/peers/peerpool.go +++ b/peers/peerpool.go @@ -14,6 +14,7 @@ import ( "github.com/status-im/status-go/discovery" "github.com/status-im/status-go/params" + "github.com/status-im/status-go/peers/verifier" "github.com/status-im/status-go/signal" ) @@ -53,6 +54,8 @@ type Options struct { // TopicStopSearchDelay time stopSearch will be waiting for max cached peers to be // filled before really stopping the search. TopicStopSearchDelay time.Duration + // TrustedMailServers is a list of trusted nodes. + TrustedMailServers []discover.NodeID } // NewDefaultOptions returns a struct with default Options. @@ -140,7 +143,7 @@ func (p *PeerPool) Start(server *p2p.Server) error { var topicPool TopicPoolInterface t := newTopicPool(p.discovery, topic, limits, p.opts.SlowSync, p.opts.FastSync, p.cache) if topic == MailServerDiscoveryTopic { - topicPool = newCacheOnlyTopicPool(t) + topicPool = newCacheOnlyTopicPool(t, verifier.NewLocalVerifier(p.opts.TrustedMailServers)) } else { topicPool = t } diff --git a/peers/peerpool_test.go b/peers/peerpool_test.go index c2c538074..4275ba91b 100644 --- a/peers/peerpool_test.go +++ b/peers/peerpool_test.go @@ -162,7 +162,7 @@ func (s *PeerPoolSimulationSuite) TestPeerPoolCacheEthV5() { config := map[discv5.Topic]params.Limits{ topic: params.NewLimits(1, 1), } - peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond} + peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond, nil} cache, err := newInMemoryCache() s.Require().NoError(err) peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts) @@ -220,7 +220,7 @@ func (s *PeerPoolSimulationSuite) singleTopicDiscoveryWithFailover() { config := map[discv5.Topic]params.Limits{ topic: params.NewLimits(1, 1), // limits are chosen for simplicity of the simulation } - peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 0} + peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 0, nil} cache, err := newInMemoryCache() s.Require().NoError(err) peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts) @@ -302,7 +302,7 @@ func TestPeerPoolMaxPeersOverflow(t *testing.T) { defer func() { assert.NoError(t, discovery.Stop()) }() require.True(t, discovery.Running()) - poolOpts := &Options{DefaultFastSync, DefaultSlowSync, 0, true, 100 * time.Millisecond} + poolOpts := &Options{DefaultFastSync, DefaultSlowSync, 0, true, 100 * time.Millisecond, nil} pool := NewPeerPool(discovery, nil, nil, poolOpts) require.NoError(t, pool.Start(peer)) require.Equal(t, signal.EventDiscoveryStarted, <-signals) @@ -355,7 +355,7 @@ func TestPeerPoolDiscV5Timeout(t *testing.T) { require.True(t, discovery.Running()) // start PeerPool - poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, true, 100 * time.Millisecond} + poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, true, 100 * time.Millisecond, nil} pool := NewPeerPool(discovery, nil, nil, poolOpts) require.NoError(t, pool.Start(server)) require.Equal(t, signal.EventDiscoveryStarted, <-signals) @@ -402,7 +402,7 @@ func TestPeerPoolNotAllowedStopping(t *testing.T) { require.True(t, discovery.Running()) // start PeerPool - poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, false, 100 * time.Millisecond} + poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, false, 100 * time.Millisecond, nil} pool := NewPeerPool(discovery, nil, nil, poolOpts) require.NoError(t, pool.Start(server)) @@ -419,7 +419,7 @@ func (s *PeerPoolSimulationSuite) TestUpdateTopicLimits() { config := map[discv5.Topic]params.Limits{ topic: params.NewLimits(1, 1), } - peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond} + peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond, nil} cache, err := newInMemoryCache() s.Require().NoError(err) peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts) @@ -447,3 +447,73 @@ func (s *PeerPoolSimulationSuite) TestUpdateTopicLimits() { s.Equal(5, tp.limits.Min) } } + +func (s *PeerPoolSimulationSuite) TestMailServerPeersDiscovery() { + s.setupEthV5() + + // Buffered channels must be used because we expect the events + // to be in the same order. Use a buffer length greater than + // the expected number of events to avoid deadlock. + poolEvents := make(chan string, 10) + summaries := make(chan []*p2p.PeerInfo, 10) + signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) { + var envelope struct { + Type string + Event json.RawMessage + } + s.NoError(json.Unmarshal([]byte(jsonEvent), &envelope)) + + switch typ := envelope.Type; typ { + case signal.EventDiscoverySummary: + poolEvents <- envelope.Type + var summary []*p2p.PeerInfo + s.NoError(json.Unmarshal(envelope.Event, &summary)) + summaries <- summary + } + }) + defer signal.ResetDefaultNodeNotificationHandler() + + // subscribe for peer events before starting the peer pool + events := make(chan *p2p.PeerEvent, 20) + subscription := s.peers[1].SubscribeEvents(events) + defer subscription.Unsubscribe() + + // create and start topic registry + register := NewRegister(s.discovery[0], MailServerDiscoveryTopic) + s.Require().NoError(register.Start()) + + // create and start peer pool + config := map[discv5.Topic]params.Limits{ + MailServerDiscoveryTopic: params.NewLimits(1, 1), + } + cache, err := newInMemoryCache() + s.Require().NoError(err) + peerPoolOpts := &Options{ + 100 * time.Millisecond, + 100 * time.Millisecond, + 0, + true, + 100 * time.Millisecond, + []discover.NodeID{s.peers[0].Self().ID}, + } + peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts) + s.Require().NoError(peerPool.Start(s.peers[1])) + defer peerPool.Stop() + + // wait for and verify the mail server peer + connectedPeer := s.getPeerFromEvent(events, p2p.PeerEventTypeAdd) + s.Equal(s.peers[0].Self().ID, connectedPeer) + + // wait for a summary event to be sure that ConfirmAdded() was called + s.Equal(signal.EventDiscoverySummary, s.getPoolEvent(poolEvents)) + s.Equal(s.peers[0].Self().ID.String(), (<-summaries)[0].ID) + + // check cache + cachedPeers := peerPool.cache.GetPeersRange(MailServerDiscoveryTopic, 5) + s.Require().Len(cachedPeers, 1) + s.Equal(s.peers[0].Self().ID[:], cachedPeers[0].ID[:]) + + // wait for another summary event as the peer should be removed + s.Equal(signal.EventDiscoverySummary, s.getPoolEvent(poolEvents)) + s.Len(<-summaries, 0) +} diff --git a/peers/topicpool_test.go b/peers/topicpool_test.go index aaa56ea98..b043bc477 100644 --- a/peers/topicpool_test.go +++ b/peers/topicpool_test.go @@ -325,6 +325,6 @@ func (s *TopicPoolSuite) TestNewTopicPoolInterface() { s.IsType(&TopicPool{}, t) tp := newTopicPool(nil, MailServerDiscoveryTopic, limits, 100*time.Millisecond, 200*time.Millisecond, cache) - cacheTP := newCacheOnlyTopicPool(tp) + cacheTP := newCacheOnlyTopicPool(tp, &testTrueVerifier{}) s.IsType(&cacheOnlyTopicPool{}, cacheTP) } diff --git a/peers/verifier/verifier.go b/peers/verifier/verifier.go new file mode 100644 index 000000000..cfeb2fdb8 --- /dev/null +++ b/peers/verifier/verifier.go @@ -0,0 +1,30 @@ +package verifier + +import ( + "context" + + "github.com/ethereum/go-ethereum/p2p/discover" +) + +// LocalVerifier verifies nodes based on a provided local list. +type LocalVerifier struct { + KnownPeers map[discover.NodeID]struct{} +} + +// NewLocalVerifier returns a new LocalVerifier instance. +func NewLocalVerifier(peers []discover.NodeID) *LocalVerifier { + knownPeers := make(map[discover.NodeID]struct{}) + for _, peer := range peers { + knownPeers[peer] = struct{}{} + } + + return &LocalVerifier{KnownPeers: knownPeers} +} + +// VerifyNode checks if a given node is trusted using a local list. +func (v *LocalVerifier) VerifyNode(_ context.Context, nodeID discover.NodeID) bool { + if _, ok := v.KnownPeers[nodeID]; ok { + return true + } + return false +} diff --git a/peers/verifier/verifier_test.go b/peers/verifier/verifier_test.go new file mode 100644 index 000000000..00b7d19aa --- /dev/null +++ b/peers/verifier/verifier_test.go @@ -0,0 +1,17 @@ +package verifier + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/stretchr/testify/require" +) + +func TestLocalVerifierForNodeIDTypes(t *testing.T) { + nodeID := discover.NodeID{1} + + v := NewLocalVerifier([]discover.NodeID{{1}}) + require.True(t, v.VerifyNode(context.TODO(), nodeID)) + require.False(t, v.VerifyNode(context.TODO(), discover.NodeID{2})) +}