diff --git a/consensus/e2e_tests/utils_test.go b/consensus/e2e_tests/utils_test.go index 41340abc6..2f533b21b 100644 --- a/consensus/e2e_tests/utils_test.go +++ b/consensus/e2e_tests/utils_test.go @@ -3,6 +3,9 @@ package e2e_tests import ( "context" "fmt" + "github.com/pokt-network/pocket/internal/testutil/p2p" + "github.com/pokt-network/pocket/internal/testutil/persistence" + telemetry_testutil "github.com/pokt-network/pocket/internal/testutil/telemetry" "os" "reflect" "sort" @@ -13,7 +16,6 @@ import ( "github.com/golang/mock/gomock" "github.com/pokt-network/pocket/consensus" typesCons "github.com/pokt-network/pocket/consensus/types" - persistenceMocks "github.com/pokt-network/pocket/persistence/types/mocks" "github.com/pokt-network/pocket/runtime" "github.com/pokt-network/pocket/runtime/configs" "github.com/pokt-network/pocket/runtime/defaults" @@ -101,7 +103,7 @@ func CreateTestConsensusPocketNode( bus modules.Bus, eventsChannel modules.EventsChannel, ) *shared.Node { - persistenceMock := basePersistenceMock(t, eventsChannel, bus) + persistenceMock := persistence_testutil.PersistenceMockWithBlockStore(t, eventsChannel, bus) bus.RegisterModule(persistenceMock) consensusMod, err := consensus.Create(bus) @@ -115,9 +117,9 @@ func CreateTestConsensusPocketNode( runtimeMgr := (bus).GetRuntimeMgr() // TODO(olshansky): At the moment we are using the same base mocks for all the tests, // but note that they will need to be customized on a per test basis. - p2pMock := baseP2PMock(t, eventsChannel) + p2pMock := p2p_testutil.BaseP2PMock(t, eventsChannel) utilityMock := baseUtilityMock(t, eventsChannel, runtimeMgr.GetGenesis(), consensusModule) - telemetryMock := baseTelemetryMock(t, eventsChannel) + telemetryMock := telemetry_testutil.MinimalTelemetryMock(t) loggerMock := baseLoggerMock(t, eventsChannel) rpcMock := baseRpcMock(t, eventsChannel) @@ -548,21 +550,6 @@ func baseReplicaUtilityUnitOfWorkMock(t *testing.T, genesisState *genesis.Genesi return utilityReplicaUnitOfWorkMock } -func baseTelemetryMock(t *testing.T, _ modules.EventsChannel) *mockModules.MockTelemetryModule { - ctrl := gomock.NewController(t) - telemetryMock := mockModules.NewMockTelemetryModule(ctrl) - timeSeriesAgentMock := baseTelemetryTimeSeriesAgentMock(t) - eventMetricsAgentMock := baseTelemetryEventMetricsAgentMock(t) - - telemetryMock.EXPECT().Start().Return(nil).AnyTimes() - telemetryMock.EXPECT().SetBus(gomock.Any()).Return().AnyTimes() - telemetryMock.EXPECT().GetTimeSeriesAgent().Return(timeSeriesAgentMock).AnyTimes() - telemetryMock.EXPECT().GetEventMetricsAgent().Return(eventMetricsAgentMock).AnyTimes() - telemetryMock.EXPECT().GetModuleName().Return(modules.TelemetryModuleName).AnyTimes() - - return telemetryMock -} - func baseRpcMock(t *testing.T, _ modules.EventsChannel) *mockModules.MockRPCModule { ctrl := gomock.NewController(t) rpcMock := mockModules.NewMockRPCModule(ctrl) diff --git a/internal/testutil/bus.go b/internal/testutil/bus.go new file mode 100644 index 000000000..1f83597a3 --- /dev/null +++ b/internal/testutil/bus.go @@ -0,0 +1,96 @@ +package testutil + +import ( + "github.com/golang/mock/gomock" + "github.com/pokt-network/pocket/p2p/providers/current_height_provider" + "github.com/pokt-network/pocket/p2p/providers/peerstore_provider" + "github.com/pokt-network/pocket/runtime" + "github.com/pokt-network/pocket/shared/messaging" + "github.com/regen-network/gocuke" + + "github.com/pokt-network/pocket/shared/modules" + "github.com/pokt-network/pocket/shared/modules/mocks" +) + +type BusEventHandler func(*messaging.PocketEnvelope) +type BusEventHandlerFactory func(t gocuke.TestingT, busMock *mock_modules.MockBus) BusEventHandler + +// MinimalBusMock returns a bus mock with a module registry and minimal +// expectations registered to maximize re-usability. +func MinimalBusMock( + t gocuke.TestingT, + runtimeMgr modules.RuntimeMgr, +) *mock_modules.MockBus { + t.Helper() + + ctrl := gomock.NewController(t) + busMock := mock_modules.NewMockBus(ctrl) + busMock.EXPECT().GetRuntimeMgr().Return(runtimeMgr).AnyTimes() + busMock.EXPECT().RegisterModule(gomock.Any()).DoAndReturn(func(m modules.Module) { + m.SetBus(busMock) + }).AnyTimes() + + mockModulesRegistry := mock_modules.NewMockModulesRegistry(ctrl) + + // TODO_THIS_COMMIT: refactor - this doesn't belong here + mockModulesRegistry.EXPECT().GetModule(peerstore_provider.ModuleName).Return(nil, runtime.ErrModuleNotRegistered(peerstore_provider.ModuleName)).AnyTimes() + mockModulesRegistry.EXPECT().GetModule(current_height_provider.ModuleName).Return(nil, runtime.ErrModuleNotRegistered(current_height_provider.ModuleName)).AnyTimes() + + busMock.EXPECT().GetModulesRegistry().Return(mockModulesRegistry).AnyTimes() + return busMock +} + +// BaseBusMock returns a base bus mock which will accept any event, +// passing it to the provided handler function, any number of times. +func BaseBusMock( + t gocuke.TestingT, + runtimeMgr modules.RuntimeMgr, +) *mock_modules.MockBus { + t.Helper() + + return WithoutBusEventHandler(t, MinimalBusMock(t, runtimeMgr)) +} + +// BusMockWithEventHandler returns a base bus mock which will accept any event, +// any number of times, calling the `handler` returned from `handlerFactory` +// with the event as an argument. +func BusMockWithEventHandler( + t gocuke.TestingT, + runtimeMgr modules.RuntimeMgr, + handlerFactory BusEventHandlerFactory, +) *mock_modules.MockBus { + t.Helper() + + busMock := MinimalBusMock(t, runtimeMgr) + return WithBusEventHandler(t, busMock, handlerFactory) +} + +// WithBusEventHandler adds an expectation to a bus mock such that it will accept +// any event, any number of times, calling the `handler` returned from `handlerFactory` +// with the event as an argument. +func WithBusEventHandler( + t gocuke.TestingT, + busMock *mock_modules.MockBus, + handlerFactory BusEventHandlerFactory, +) *mock_modules.MockBus { + t.Helper() + + if handlerFactory != nil { + handler := handlerFactory(t, busMock) + busMock.EXPECT().PublishEventToBus(gomock.Any()).Do(handler).AnyTimes() + } + + return busMock +} + +// WithoutBusEventHandler adds an expectation to a bus mock such that it will accept +// any event, any number of times. +func WithoutBusEventHandler( + t gocuke.TestingT, + busMock *mock_modules.MockBus, +) *mock_modules.MockBus { + t.Helper() + + busMock.EXPECT().PublishEventToBus(gomock.Any()).AnyTimes() + return busMock +} diff --git a/internal/testutil/bus/bus.go b/internal/testutil/bus/bus.go new file mode 100644 index 000000000..f83e27735 --- /dev/null +++ b/internal/testutil/bus/bus.go @@ -0,0 +1,30 @@ +package bus_testutil + +import ( + "github.com/regen-network/gocuke" + + "github.com/pokt-network/pocket/internal/testutil" + "github.com/pokt-network/pocket/internal/testutil/runtime" + "github.com/pokt-network/pocket/runtime/genesis" + "github.com/pokt-network/pocket/shared/crypto" + "github.com/pokt-network/pocket/shared/modules/mocks" +) + +func NewBus( + t gocuke.TestingT, + privKey crypto.PrivateKey, + serviceURL string, + genesisState *genesis.GenesisState, + busEventHandlerFactory testutil.BusEventHandlerFactory, +) *mock_modules.MockBus { + t.Helper() + + runtimeMgrMock := runtime_testutil.BaseRuntimeManagerMock( + t, privKey, + serviceURL, + genesisState, + ) + busMock := testutil.BusMockWithEventHandler(t, runtimeMgrMock, busEventHandlerFactory) + busMock.EXPECT().GetRuntimeMgr().Return(runtimeMgrMock).AnyTimes() + return busMock +} diff --git a/internal/testutil/composition.go b/internal/testutil/composition.go new file mode 100644 index 000000000..5e4d66b0f --- /dev/null +++ b/internal/testutil/composition.go @@ -0,0 +1,30 @@ +package testutil + +// PipeTwoToOne threads two values of any type (T and U) through a pipeline of +// functions and returns the result of any type (U). Each function in the pipeline +// takes two arguments of type T and U, and returns a value of type U. +// +// Applies each function in the pipeline to the current value of U and the +// constant value of T, effectively "threading" the initial U value through the +// pipeline of functions. +// +// Does *not* mutate the original U value. Instead, it operates on a reference +// to U, ensuring that value types (non-pointer types) are not mutated. +// +// Returns the final value of U after it has been threaded through all the functions in the pipeline. +// +// Usage: +// +// result := PipeTwo(initialT, initialU, func1, func2, func3) +// +// In this example, initialT and initialU are the initial values of T and U, and func1, func2, and func3 +// are functions that take two arguments of type T and U and return a value of type U. +func PipeTwoToOne[T, U any](t T, u U, pipeline ...func(T, U) U) U { + // NB: don't mutate potential value type `u` (i.e. non-pointer) + uRef := u + for _, fn := range pipeline { + uRef = fn(t, uRef) + } + + return u +} diff --git a/internal/testutil/consensus/mocks.go b/internal/testutil/consensus/mocks.go new file mode 100644 index 000000000..51bdc3338 --- /dev/null +++ b/internal/testutil/consensus/mocks.go @@ -0,0 +1,24 @@ +package consensus_testutil + +import ( + "github.com/golang/mock/gomock" + "github.com/regen-network/gocuke" + + "github.com/pokt-network/pocket/shared/modules" + "github.com/pokt-network/pocket/shared/modules/mocks" +) + +// Consensus mock - only needed for validatorMap access +func BaseConsensusMock(t gocuke.TestingT, busMock *mock_modules.MockBus) *mock_modules.MockConsensusModule { + ctrl := gomock.NewController(t) + consensusMock := mock_modules.NewMockConsensusModule(ctrl) + consensusMock.EXPECT().CurrentHeight().Return(uint64(1)).AnyTimes() + + consensusMock.EXPECT().GetBus().Return(busMock).AnyTimes() + consensusMock.EXPECT().SetBus(busMock).AnyTimes() + consensusMock.EXPECT().GetModuleName().Return(modules.ConsensusModuleName).AnyTimes() + busMock.EXPECT().GetConsensusModule().Return(consensusMock).AnyTimes() + //busMock.RegisterModule(consensusMock) + + return consensusMock +} diff --git a/internal/testutil/constructors/constructors.go b/internal/testutil/constructors/constructors.go new file mode 100644 index 000000000..2d6e7900f --- /dev/null +++ b/internal/testutil/constructors/constructors.go @@ -0,0 +1,156 @@ +package constructors + +import ( + "github.com/foxcpp/go-mockdns" + libp2pHost "github.com/libp2p/go-libp2p/core/host" + libp2pNetwork "github.com/libp2p/go-libp2p/core/network" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/pokt-network/pocket/internal/testutil/bus" + consensus_testutil "github.com/pokt-network/pocket/internal/testutil/consensus" + persistence_testutil "github.com/pokt-network/pocket/internal/testutil/persistence" + telemetry_testutil "github.com/pokt-network/pocket/internal/testutil/telemetry" + "github.com/regen-network/gocuke" + "github.com/stretchr/testify/require" + "net" + "strconv" + + "github.com/pokt-network/pocket/internal/testutil" + p2p_testutil "github.com/pokt-network/pocket/internal/testutil/p2p" + "github.com/pokt-network/pocket/p2p" + "github.com/pokt-network/pocket/runtime/genesis" + cryptoPocket "github.com/pokt-network/pocket/shared/crypto" + "github.com/pokt-network/pocket/shared/modules" + mock_modules "github.com/pokt-network/pocket/shared/modules/mocks" +) + +type serviceURLStr = string + +// NewP2PMocknetModules returns a map of peer IDs to P2PModules using libp2p mocknet hosts. +func NewBusesMocknetAndP2PModules( + t gocuke.TestingT, + count int, + dnsSrv *mockdns.Server, + genesisState *genesis.GenesisState, + busEventHandlerFactory testutil.BusEventHandlerFactory, + notifiee libp2pNetwork.Notifiee, +) ( + buses map[serviceURLStr]*mock_modules.MockBus, + libp2pNetworkMock mocknet.Mocknet, + p2pModules map[serviceURLStr]modules.P2PModule, +) { + libp2pNetworkMock = p2p_testutil.NewLibp2pNetworkMock(t) + serviceURLKeyMap := testutil.SequentialServiceURLPrivKeyMap(t, count) + + buses, p2pModules = NewBusesAndP2PModules( + t, busEventHandlerFactory, + dnsSrv, + genesisState, + libp2pNetworkMock, + serviceURLKeyMap, + notifiee, + ) + err := libp2pNetworkMock.LinkAll() + require.NoError(t, err) + + return buses, libp2pNetworkMock, p2pModules +} + +// TODO_THIS_COMMIT: rename / move, if possible +func NewP2PModule( + t gocuke.TestingT, + busMock *mock_modules.MockBus, + dnsSrv *mockdns.Server, + libp2pNetworkMock mocknet.Mocknet, + notifiee libp2pNetwork.Notifiee, + // TODO_THIS_COMMIT: consider *p2p.P2PModule instead +) modules.P2PModule { + genesisState := busMock.GetRuntimeMgr().GetGenesis() + + _ = consensus_testutil.BaseConsensusMock(t, busMock) + _ = persistence_testutil.BasePersistenceMock(t, busMock, genesisState) + + // -- option 1 + _ = telemetry_testutil.BaseTelemetryMock(t, busMock) + + // -- option 2 + //_ = telemetry_testutil.WithTimeSeriesAgent( + // t, telemetry_testutil.MinimalTelemetryMock(t, busMock), + //) + + p2pCfg := busMock.GetRuntimeMgr().GetConfig().P2P + serviceURL := net.JoinHostPort(p2pCfg.Hostname, strconv.Itoa(int(p2pCfg.Port))) + privKey, err := cryptoPocket.NewPrivateKey(p2pCfg.PrivateKey) + require.NoError(t, err) + + // MUST register DNS before instantiating P2PModule + testutil.AddServiceURLZone(t, dnsSrv, serviceURL) + + host := testutil.NewMocknetHost(t, libp2pNetworkMock, privKey, notifiee) + return NewP2PModuleWithHost(t, busMock, host) +} + +// TODO_THIS_TEST: need this? +func NewBusesAndP2PModules( + t gocuke.TestingT, + busEventHandlerFactory testutil.BusEventHandlerFactory, + dnsSrv *mockdns.Server, + genesisState *genesis.GenesisState, + libp2pNetworkMock mocknet.Mocknet, + serviceURLKeyMap map[serviceURLStr]cryptoPocket.PrivateKey, + notifiee libp2pNetwork.Notifiee, +) ( + busMocks map[serviceURLStr]*mock_modules.MockBus, + // TODO_THIS_COMMIT: consider *p2p.P2PModule instead + p2pModules map[serviceURLStr]modules.P2PModule, +) { + busMocks = make(map[serviceURLStr]*mock_modules.MockBus) + p2pModules = make(map[serviceURLStr]modules.P2PModule) + + for serviceURL, privKey := range serviceURLKeyMap { + busMock := bus_testutil.NewBus( + t, privKey, + serviceURL, + genesisState, + busEventHandlerFactory, + ) + busMocks[serviceURL] = busMock + + p2pModules[serviceURL] = NewP2PModule( + t, busMock, + dnsSrv, + libp2pNetworkMock, + notifiee, + ) + } + return busMocks, p2pModules +} + +// TODO_THIS_TEST: need this? +// TODO_THIS_COMMIT: consider following create factory convention (?) +func NewBusesAndP2PModuleWithHost( + t gocuke.TestingT, + privKey cryptoPocket.PrivateKey, + serviceURL string, + host libp2pHost.Host, + genesisState *genesis.GenesisState, + busEventHandlerFactory testutil.BusEventHandlerFactory, +) (*mock_modules.MockBus, modules.P2PModule) { + t.Helper() + + busMock := bus_testutil.NewBus(t, privKey, serviceURL, genesisState, busEventHandlerFactory) + return busMock, NewP2PModuleWithHost(t, busMock, host) +} + +// TODO_THIS_COMMIT: rename; consider returning *p2p.P2PModule instead +func NewP2PModuleWithHost( + t gocuke.TestingT, + busMock *mock_modules.MockBus, + host libp2pHost.Host, +) modules.P2PModule { + t.Helper() + + mod, err := p2p.Create(busMock, p2p.WithHostOption(host)) + require.NoError(t, err) + + return mod.(modules.P2PModule) +} diff --git a/internal/testutil/generics/composition.go b/internal/testutil/generics/composition.go new file mode 100644 index 000000000..5e4d66b0f --- /dev/null +++ b/internal/testutil/generics/composition.go @@ -0,0 +1,30 @@ +package testutil + +// PipeTwoToOne threads two values of any type (T and U) through a pipeline of +// functions and returns the result of any type (U). Each function in the pipeline +// takes two arguments of type T and U, and returns a value of type U. +// +// Applies each function in the pipeline to the current value of U and the +// constant value of T, effectively "threading" the initial U value through the +// pipeline of functions. +// +// Does *not* mutate the original U value. Instead, it operates on a reference +// to U, ensuring that value types (non-pointer types) are not mutated. +// +// Returns the final value of U after it has been threaded through all the functions in the pipeline. +// +// Usage: +// +// result := PipeTwo(initialT, initialU, func1, func2, func3) +// +// In this example, initialT and initialU are the initial values of T and U, and func1, func2, and func3 +// are functions that take two arguments of type T and U and return a value of type U. +func PipeTwoToOne[T, U any](t T, u U, pipeline ...func(T, U) U) U { + // NB: don't mutate potential value type `u` (i.e. non-pointer) + uRef := u + for _, fn := range pipeline { + uRef = fn(t, uRef) + } + + return u +} diff --git a/internal/testutil/map.go b/internal/testutil/generics/map.go similarity index 100% rename from internal/testutil/map.go rename to internal/testutil/generics/map.go diff --git a/internal/testutil/generics/proxy.go b/internal/testutil/generics/proxy.go new file mode 100644 index 000000000..8dd837735 --- /dev/null +++ b/internal/testutil/generics/proxy.go @@ -0,0 +1,3 @@ +package generics_testutil + +type ProxyFactory[T any] func(target T) (proxy T) diff --git a/internal/testutil/identity.go b/internal/testutil/identity.go new file mode 100644 index 000000000..110b2e6a7 --- /dev/null +++ b/internal/testutil/identity.go @@ -0,0 +1 @@ +package testutil diff --git a/internal/testutil/keys.go b/internal/testutil/keys.go new file mode 100644 index 000000000..5f8b6accc --- /dev/null +++ b/internal/testutil/keys.go @@ -0,0 +1,45 @@ +package testutil + +import ( + "bufio" + "os" + "path/filepath" + "regexp" + "runtime" + + cryptoPocket "github.com/pokt-network/pocket/shared/crypto" + "github.com/stretchr/testify/require" +) + +var ( + privKeyManifestKeyRegex = regexp.MustCompile(`\s+"\d+":\s+(\w+)\s+`) +) + +func LoadLocalnetPrivateKeys(t require.TestingT, keyCount int) (privKeys []cryptoPocket.PrivateKey) { + _, filename, _, _ := runtime.Caller(0) + pkgDir := filepath.Dir(filename) + relativePathToKeys := filepath.Join(pkgDir, "..", "..", "build", "localnet", "manifests", "private-keys.yaml") + + privKeyManifest, err := os.Open(relativePathToKeys) + require.NoError(t, err) + + privKeys = make([]cryptoPocket.PrivateKey, 0, keyCount) + + // scan through file & extract private keys + scanner := bufio.NewScanner(privKeyManifest) + scanner.Split(bufio.ScanLines) + + for i, done := 0, false; i < keyCount && !done; { + done = !scanner.Scan() + line := scanner.Text() + matches := privKeyManifestKeyRegex.FindStringSubmatch(line) + if len(matches) > 0 { + privKey, err := cryptoPocket.NewPrivateKey(matches[1]) + require.NoError(t, err) + + privKeys = append(privKeys, privKey) + i++ + } + } + return privKeys +} diff --git a/internal/testutil/keys_test.go b/internal/testutil/keys_test.go new file mode 100644 index 000000000..fc423fb5e --- /dev/null +++ b/internal/testutil/keys_test.go @@ -0,0 +1,23 @@ +package testutil_test + +import ( + "testing" + + "github.com/pokt-network/pocket/internal/testutil" + "github.com/stretchr/testify/require" +) + +func TestLoadLocalnetPrivateKeys(t *testing.T) { + keyCount := 1000 + privKeys := testutil.LoadLocalnetPrivateKeys(t, keyCount) + + require.Lenf(t, privKeys, keyCount, "expected %d private keys; got %d", keyCount, len(privKeys)) + + // ensure each key is unique + seen := make(map[string]struct{}) + for _, privKey := range privKeys { + seen[privKey.String()] = struct{}{} + } + + require.Lenf(t, seen, keyCount, "expected %d unique private keys; got %d", keyCount, len(seen)) +} diff --git a/internal/testutil/mockdns.go b/internal/testutil/mockdns.go index 009a0e0fa..2adc4ab74 100644 --- a/internal/testutil/mockdns.go +++ b/internal/testutil/mockdns.go @@ -3,43 +3,59 @@ package testutil import ( "fmt" "net" - "net/url" - "testing" "github.com/foxcpp/go-mockdns" + "github.com/regen-network/gocuke" "github.com/stretchr/testify/require" ) -func PrepareDNSMockFromServiceURLs(t *testing.T, serviceURLs []string) (done func()) { - zones := make(map[string]mockdns.Zone) - for i, u := range serviceURLs { - // Perpend `scheme://` as serviceURLs are currently scheme-less. - // Required for parsing to produce useful results. - // (see: https://pkg.go.dev/net/url@go1.20.2#URL) - serviceURL, err := url.Parse(fmt.Sprintf("scheme://%s", u)) - require.NoError(t, err) +func DNSMockFromServiceURLs(t gocuke.TestingT, serviceURLs []string) *mockdns.Server { + t.Helper() + + srv := MinimalDNSMock(t) + for _, serviceURL := range serviceURLs { + AddServiceURLZone(t, srv, serviceURL) + } + return srv +} - ipStr := fmt.Sprintf("10.0.0.%d", i+1) +func AddServiceURLZone(t gocuke.TestingT, srv *mockdns.Server, serviceURL string) { + t.Helper() - if i >= 254 { - panic(fmt.Sprintf("would generate invalid IPv4 address: %s", ipStr)) - } + // TODO_THIS_COMMIT: move & de-dup + hostname, _, err := net.SplitHostPort(serviceURL) + require.NoError(t, err) - zones[fmt.Sprintf("%s.", serviceURL.Hostname())] = mockdns.Zone{ - A: []string{ipStr}, - } + zone := mockdns.Zone{ + A: []string{"10.0.0.1"}, } - return PrepareDNSMock(zones) + err = srv.AddZone(fmt.Sprintf("%s.", hostname), zone) + require.NoError(t, err) +} + +func MinimalDNSMock(t gocuke.TestingT) *mockdns.Server { + t.Helper() + + return BaseDNSMock(t, nil) } -func PrepareDNSMock(zones map[string]mockdns.Zone) (done func()) { +func BaseDNSMock(t gocuke.TestingT, zones map[string]mockdns.Zone) *mockdns.Server { + t.Helper() + + if zones == nil { + zones = make(map[string]mockdns.Zone) + } + srv, _ := mockdns.NewServerWithLogger(zones, noopLogger{}, false) srv.PatchNet(net.DefaultResolver) - return func() { - _ = srv.Close() + t.Cleanup(func() { + err := srv.Close() + require.NoError(t, err) mockdns.UnpatchNet(net.DefaultResolver) - } + }) + + return srv } // NB: default logging behavior is too noisy. diff --git a/internal/testutil/module.go b/internal/testutil/module.go new file mode 100644 index 000000000..a59710b02 --- /dev/null +++ b/internal/testutil/module.go @@ -0,0 +1,41 @@ +package testutil + +import ( + "github.com/foxcpp/go-mockdns" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/pokt-network/pocket/runtime/genesis" + "github.com/pokt-network/pocket/shared/modules" + "github.com/pokt-network/pocket/shared/modules/base_modules" +) + +// TODO_THIS_COMMIT: is this helpful? +const TestModuleName = "testModule" + +var ( + _ modules.Module = &TestModule{} + _ modules.ModuleFactoryWithOptions = &TestModule{} +) + +type TestModule struct { + base_modules.IntegratableModule + base_modules.InterruptableModule + + DNS *mockdns.Server + Genesis *genesis.GenesisState + Libp2pNetworkMock mocknet.Mocknet +} + +func (m *TestModule) GetModuleName() string { + return TestModuleName +} + +func (m *TestModule) Create( + bus modules.Bus, + opts ...modules.ModuleOption, +) (modules.Module, error) { + panic("implement me") +} + +func (m *TestModule) GetDNS() *mockdns.Server { + return m.DNS +} diff --git a/internal/testutil/network.go b/internal/testutil/network.go new file mode 100644 index 000000000..a86d5a866 --- /dev/null +++ b/internal/testutil/network.go @@ -0,0 +1,105 @@ +package testutil + +import ( + "fmt" + + crypto2 "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/host" + libp2pNetwork "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/multiformats/go-multiaddr" + "github.com/regen-network/gocuke" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/pocket/runtime/defaults" + "github.com/pokt-network/pocket/shared/crypto" +) + +const ServiceURLFormat = "node%d.consensus:42069" + +func NewMocknetHost( + t gocuke.TestingT, + libp2pNetworkMock mocknet.Mocknet, + privKey crypto.PrivateKey, + notifiee libp2pNetwork.Notifiee, +) host.Host { + t.Helper() + + // TODO_THIS_COMMIT: move to const + addrMock, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/10.0.0.1/tcp/%d", defaults.DefaultP2PPort)) + require.NoError(t, err) + + libp2pPrivKey, err := crypto2.UnmarshalEd25519PrivateKey(privKey.Bytes()) + require.NoError(t, err) + + host, err := libp2pNetworkMock.AddPeer(libp2pPrivKey, addrMock) + require.NoError(t, err) + + if notifiee != nil { + host.Network().Notify(notifiee) + } + + return host +} + +func SequentialServiceURLPrivKeyMap(t gocuke.TestingT, count int) map[string]crypto.PrivateKey { + t.Helper() + + // CONSIDERATION: using an iterator/generator would prevent unintentional + // ID collisions + privKeys := LoadLocalnetPrivateKeys(t, count) + // CONSIDERATION: using an iterator/generator would prevent unintentional + // serviceURL collisions + serviceURLs := SequentialServiceURLs(t, count) + + require.GreaterOrEqualf(t, len(privKeys), len(serviceURLs), "not enough private keys for service URLs") + + serviceURLKeysMap := make(map[string]crypto.PrivateKey, len(serviceURLs)) + + for i, serviceURL := range serviceURLs { + serviceURLKeysMap[serviceURL] = privKeys[i] + } + return serviceURLKeysMap +} + +// CONSIDERATION: serviceURLs are only unique within their respective slice; +// consider building an iterator/generator instead. +func SequentialServiceURLs(t gocuke.TestingT, count int) (serviceURLs []string) { + t.Helper() + + for i := 0; i < count; i++ { + serviceURLs = append(serviceURLs, NewServiceURL(i+1)) + } + return serviceURLs +} + +// TECHDEBT: rename `validatorId()` to `serviceURL()` +func NewServiceURL(i int) string { + return fmt.Sprintf(ServiceURLFormat, i) +} + +// TODO_THIS_COMMIT: move +func NewDebugNotifee(t gocuke.TestingT) libp2pNetwork.Notifiee { + t.Helper() + + return &libp2pNetwork.NotifyBundle{ + ConnectedF: func(_ libp2pNetwork.Network, conn libp2pNetwork.Conn) { + t.Logf("connected: local: %s; remote: %s", + conn.LocalPeer().String(), + conn.RemotePeer().String(), + ) + }, + DisconnectedF: func(_ libp2pNetwork.Network, conn libp2pNetwork.Conn) { + t.Logf("disconnected: local: %s; remote: %s", + conn.LocalPeer().String(), + conn.RemotePeer().String(), + ) + }, + ListenF: func(_ libp2pNetwork.Network, addr multiaddr.Multiaddr) { + t.Logf("listening: %s", addr.String()) + }, + ListenCloseF: func(_ libp2pNetwork.Network, addr multiaddr.Multiaddr) { + t.Logf("closed: %s", addr.String()) + }, + } +} diff --git a/internal/testutil/p2p/constants.go b/internal/testutil/p2p/constants.go new file mode 100644 index 000000000..8cc1f0bc9 --- /dev/null +++ b/internal/testutil/p2p/constants.go @@ -0,0 +1,15 @@ +package p2p_testutil + +import ( + "fmt" + + "github.com/pokt-network/pocket/runtime/defaults" +) + +var ( + // IP4ServiceURL is a string representing a valid IPv4 based ServiceURL using the loopback interface. + IP4ServiceURL = fmt.Sprintf("127.0.0.1:%d", defaults.DefaultP2PPort) + // IP6ServiceURL is a string representing a valid IPv6 based ServiceURL. + // (see: https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2) + IP6ServiceURL = fmt.Sprintf("[2a00:1450:4005:802::2004]:%d", defaults.DefaultP2PPort) +) diff --git a/internal/testutil/p2p/mocknet.go b/internal/testutil/p2p/mocknet.go new file mode 100644 index 000000000..e97234510 --- /dev/null +++ b/internal/testutil/p2p/mocknet.go @@ -0,0 +1,45 @@ +package p2p_testutil + +import ( + "testing" + + libp2pCrypto "github.com/libp2p/go-libp2p/core/crypto" + libp2pHost "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/pocket/p2p/types" + "github.com/pokt-network/pocket/p2p/utils" + "github.com/pokt-network/pocket/shared/crypto" +) + +func NewTestPeer(t *testing.T) (*types.NetworkPeer, libp2pHost.Host) { + t.Helper() + + selfPrivKey, err := crypto.GeneratePrivateKey() + require.NoError(t, err) + + selfAddr := selfPrivKey.Address() + selfPeer := &types.NetworkPeer{ + PublicKey: selfPrivKey.PublicKey(), + Address: selfAddr, + ServiceURL: IP4ServiceURL, + } + return selfPeer, NewLibp2pMockNetHost(t, selfPrivKey, selfPeer) +} + +func NewLibp2pMockNetHost(t *testing.T, privKey crypto.PrivateKey, peer *types.NetworkPeer) libp2pHost.Host { + t.Helper() + + libp2pPrivKey, err := libp2pCrypto.UnmarshalEd25519PrivateKey(privKey.Bytes()) + require.NoError(t, err) + + libp2pMultiAddr, err := utils.Libp2pMultiaddrFromServiceURL(peer.ServiceURL) + require.NoError(t, err) + + libp2pMockNet := mocknet.New() + host, err := libp2pMockNet.AddPeer(libp2pPrivKey, libp2pMultiAddr) + require.NoError(t, err) + + return host +} diff --git a/internal/testutil/p2p/mocks.go b/internal/testutil/p2p/mocks.go new file mode 100644 index 000000000..61c437a34 --- /dev/null +++ b/internal/testutil/p2p/mocks.go @@ -0,0 +1,41 @@ +package p2p_testutil + +import ( + "testing" + + "github.com/golang/mock/gomock" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/pokt-network/pocket/shared/crypto" + "github.com/pokt-network/pocket/shared/messaging" + "github.com/pokt-network/pocket/shared/modules" + "github.com/pokt-network/pocket/shared/modules/mocks" +) + +// Creates a p2p module mock with mock implementations of some basic functionality +func BaseP2PMock(t *testing.T, eventsChannel modules.EventsChannel) *mock_modules.MockP2PModule { + ctrl := gomock.NewController(t) + p2pMock := mock_modules.NewMockP2PModule(ctrl) + + p2pMock.EXPECT().Start().Return(nil).AnyTimes() + p2pMock.EXPECT().SetBus(gomock.Any()).Return().AnyTimes() + p2pMock.EXPECT(). + Broadcast(gomock.Any()). + Do(func(msg *anypb.Any) { + e := &messaging.PocketEnvelope{Content: msg} + eventsChannel <- e + }). + AnyTimes() + // CONSIDERATION: Adding a check to not to send message to itself + p2pMock.EXPECT(). + Send(gomock.Any(), gomock.Any()). + Do(func(addr crypto.Address, msg *anypb.Any) { + e := &messaging.PocketEnvelope{Content: msg} + eventsChannel <- e + }). + AnyTimes() + p2pMock.EXPECT().GetModuleName().Return(modules.P2PModuleName).AnyTimes() + p2pMock.EXPECT().HandleEvent(gomock.Any()).Return(nil).AnyTimes() + + return p2pMock +} diff --git a/internal/testutil/p2p/network.go b/internal/testutil/p2p/network.go new file mode 100644 index 000000000..2fd47f150 --- /dev/null +++ b/internal/testutil/p2p/network.go @@ -0,0 +1,140 @@ +package p2p_testutil + +import ( + "testing" + + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + libp2pPeer "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/regen-network/gocuke" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/pokt-network/pocket/internal/testutil" + "github.com/pokt-network/pocket/p2p/types" + "github.com/pokt-network/pocket/p2p/utils" + cryptoPocket "github.com/pokt-network/pocket/shared/crypto" + "github.com/pokt-network/pocket/shared/messaging" +) + +// TODO: remove if not needed +func NewMocknetWithNPeers(t gocuke.TestingT, peerCount int) (mocknet.Mocknet, []string) { + t.Helper() + + // load pre-generated validator keypairs + libp2pNetworkMock := mocknet.New() + privKeys := testutil.LoadLocalnetPrivateKeys(t, peerCount) + serviceURLs := testutil.SequentialServiceURLs(t, peerCount) + _ = SetupMockNetPeers(t, libp2pNetworkMock, privKeys, serviceURLs) + + return libp2pNetworkMock, serviceURLs +} + +func NewLibp2pNetworkMock(t gocuke.TestingT) mocknet.Mocknet { + t.Helper() + + libp2pNetworkMock := mocknet.New() + // destroy mocknet on test cleanup + t.Cleanup(func() { + err := libp2pNetworkMock.Close() + require.NoError(t, err) + }) + + return libp2pNetworkMock +} + +func SetupMockNetPeers( + t gocuke.TestingT, + netMock mocknet.Mocknet, + privKeys []cryptoPocket.PrivateKey, + serviceURLs []string, +) (peerIDs []peer.ID) { + t.Helper() + + // TODO_THIS_COMMIT: return these + // + // MUST add mockdns before any libp2p host comes online. Otherwise, it will + // error while attempting to resolve its own hostname. + _ = testutil.DNSMockFromServiceURLs(t, serviceURLs) + + // Add a libp2p peers/hosts to the `MockNet` with the keypairs corresponding + // to the genesis validators' keypairs + for i, peerInfo := range PeersFromPrivKeysAndServiceURLs(t, privKeys, serviceURLs) { + libp2pPrivKey, err := crypto.UnmarshalEd25519PrivateKey(privKeys[i].Bytes()) + require.NoError(t, err) + + // TODO_THIS_COMMIT: add mock DNS zone per peer instead of all at once + _, err = netMock.AddPeer(libp2pPrivKey, peerInfo.Addrs[0]) + require.NoError(t, err) + + peerIDs = append(peerIDs, peerInfo.ID) + } + + // Link all peers such that any may dial/connect to any other. + err := netMock.LinkAll() + require.NoError(t, err) + + return peerIDs +} + +func PeersFromPrivKeysAndServiceURLs( + t gocuke.TestingT, + privKeys []cryptoPocket.PrivateKey, + serviceURLs []string, +) (peersInfo []libp2pPeer.AddrInfo) { + t.Helper() + + serviceURLCount, privKeyCount := len(serviceURLs), len(privKeys) + maxCount := serviceURLCount + if privKeyCount < serviceURLCount { + maxCount = privKeyCount + } + + for i, privKey := range privKeys[:maxCount] { + peerInfo := peerFromPrivKeyAndServiceURL(t, privKey, testutil.NewServiceURL(i+1)) + peersInfo = append(peersInfo, peerInfo) + } + return peersInfo +} + +func peerFromPrivKeyAndServiceURL( + t gocuke.TestingT, + privKey cryptoPocket.PrivateKey, + serviceURL string, +) libp2pPeer.AddrInfo { + t.Helper() + + peerInfo, err := utils.Libp2pAddrInfoFromPeer(&types.NetworkPeer{ + PublicKey: privKey.PublicKey(), + Address: privKey.Address(), + ServiceURL: serviceURL, + }) + require.NoError(t, err) + + return peerInfo +} + +func NewTestPoktEnvelopeBz(t *testing.T, msg string) []byte { + debugMsg := NewDebugStringMessage(t, msg) + + poktEnvelope, err := messaging.PackMessage(debugMsg) + require.NoError(t, err) + + poktEnvelopeBz, err := proto.Marshal(poktEnvelope) + require.NoError(t, err) + + return poktEnvelopeBz +} + +func NewDebugStringMessage(t gocuke.TestingT, msg string) *messaging.DebugMessage { + debugStringMsg, err := anypb.New(&messaging.DebugStringMessage{Value: msg}) + require.NoError(t, err) + + return &messaging.DebugMessage{ + Action: messaging.DebugMessageAction_DEBUG_ACTION_UNKNOWN, + Type: messaging.DebugMessageRoutingType_DEBUG_MESSAGE_TYPE_BROADCAST, + Message: debugStringMsg, + } +} diff --git a/internal/testutil/persistence/mocks.go b/internal/testutil/persistence/mocks.go new file mode 100644 index 000000000..2e18385b6 --- /dev/null +++ b/internal/testutil/persistence/mocks.go @@ -0,0 +1,85 @@ +package persistence_testutil + +import ( + "fmt" + "github.com/golang/mock/gomock" + "github.com/regen-network/gocuke" + + persistence_mocks "github.com/pokt-network/pocket/persistence/types/mocks" + "github.com/pokt-network/pocket/runtime/genesis" + "github.com/pokt-network/pocket/shared/codec" + "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/modules" + "github.com/pokt-network/pocket/shared/modules/mocks" + "github.com/pokt-network/pocket/shared/utils" +) + +// Persistence mock - only needed for validatorMap access +func BasePersistenceMock(t gocuke.TestingT, busMock *mock_modules.MockBus, genesisState *genesis.GenesisState) *mock_modules.MockPersistenceModule { + ctrl := gomock.NewController(t) + + persistenceModuleMock := mock_modules.NewMockPersistenceModule(ctrl) + readCtxMock := mock_modules.NewMockPersistenceReadContext(ctrl) + + readCtxMock.EXPECT().GetAllValidators(gomock.Any()).Return(genesisState.GetValidators(), nil).AnyTimes() + persistenceModuleMock.EXPECT().NewReadContext(gomock.Any()).Return(readCtxMock, nil).AnyTimes() + readCtxMock.EXPECT().Release().AnyTimes() + + persistenceModuleMock.EXPECT().GetBus().Return(busMock).AnyTimes() + persistenceModuleMock.EXPECT().SetBus(busMock).AnyTimes() + persistenceModuleMock.EXPECT().GetModuleName().Return(modules.PersistenceModuleName).AnyTimes() + busMock.EXPECT().GetPersistenceModule().Return(persistenceModuleMock).AnyTimes() + //busMock.RegisterModule(persistenceModuleMock) + + return persistenceModuleMock +} + +// Creates a persistence module mock with mock implementations of some basic functionality +func PersistenceMockWithBlockStore(t gocuke.TestingT, _ modules.EventsChannel, bus modules.Bus) *mock_modules.MockPersistenceModule { + ctrl := gomock.NewController(t) + persistenceMock := mock_modules.NewMockPersistenceModule(ctrl) + persistenceReadContextMock := mock_modules.NewMockPersistenceReadContext(ctrl) + + persistenceMock.EXPECT().GetModuleName().Return(modules.PersistenceModuleName).AnyTimes() + persistenceMock.EXPECT().Start().Return(nil).AnyTimes() + persistenceMock.EXPECT().SetBus(gomock.Any()).Return().AnyTimes() + persistenceMock.EXPECT().NewReadContext(gomock.Any()).Return(persistenceReadContextMock, nil).AnyTimes() + + persistenceMock.EXPECT().ReleaseWriteContext().Return(nil).AnyTimes() + + blockStoreMock := persistence_mocks.NewMockBlockStore(ctrl) + + blockStoreMock.EXPECT().Get(gomock.Any()).DoAndReturn(func(height []byte) ([]byte, error) { + heightInt := utils.HeightFromBytes(height) + if bus.GetConsensusModule().CurrentHeight() < heightInt { + return nil, fmt.Errorf("requested height is higher than current height of the node's consensus module") + } + blockWithHeight := &types.Block{ + BlockHeader: &types.BlockHeader{ + Height: utils.HeightFromBytes(height), + }, + } + return codec.GetCodec().Marshal(blockWithHeight) + }).AnyTimes() + + persistenceMock.EXPECT().GetBlockStore().Return(blockStoreMock).AnyTimes() + + persistenceReadContextMock.EXPECT().GetMaximumBlockHeight().DoAndReturn(func() (uint64, error) { + height := bus.GetConsensusModule().CurrentHeight() + return height, nil + }).AnyTimes() + + persistenceReadContextMock.EXPECT().GetMinimumBlockHeight().DoAndReturn(func() (uint64, error) { + // mock minimum block height in persistence module to 1 if current height is equal or more than 1, else return 0 as the minimum height + if bus.GetConsensusModule().CurrentHeight() >= 1 { + return 1, nil + } + return 0, nil + }).AnyTimes() + + persistenceReadContextMock.EXPECT().GetAllValidators(gomock.Any()).Return(bus.GetRuntimeMgr().GetGenesis().Validators, nil).AnyTimes() + persistenceReadContextMock.EXPECT().GetBlockHash(gomock.Any()).Return("", nil).AnyTimes() + persistenceReadContextMock.EXPECT().Release().AnyTimes() + + return persistenceMock +} diff --git a/internal/testutil/runtime/genesis.go b/internal/testutil/runtime/genesis.go new file mode 100644 index 000000000..47da6caed --- /dev/null +++ b/internal/testutil/runtime/genesis.go @@ -0,0 +1,68 @@ +package runtime_testutil + +import ( + "github.com/pokt-network/pocket/internal/testutil" + "github.com/pokt-network/pocket/runtime/genesis" + "github.com/pokt-network/pocket/runtime/test_artifacts" + "github.com/pokt-network/pocket/shared/core/types" + cryptoPocket "github.com/pokt-network/pocket/shared/crypto" + "github.com/regen-network/gocuke" +) + +func BaseGenesisStateMock(t gocuke.TestingT, valKeys []cryptoPocket.PublicKey, serviceURLs []string) *genesis.GenesisState { + t.Helper() + + genesisState := new(genesis.GenesisState) + validators := make([]*types.Actor, len(valKeys)) + for i, valKey := range valKeys { + addr := valKey.Address().String() + mockActor := &types.Actor{ + ActorType: types.ActorType_ACTOR_TYPE_VAL, + Address: addr, + PublicKey: valKey.String(), + ServiceUrl: serviceURLs[i], + StakedAmount: test_artifacts.DefaultStakeAmountString, + PausedHeight: int64(0), + UnstakingHeight: int64(0), + Output: addr, + } + validators[i] = mockActor + } + genesisState.Validators = validators + + return genesisState +} + +func BaseGenesisStateMockFromServiceURLKeyMap(t gocuke.TestingT, serviceURLKeyMap map[string]cryptoPocket.PrivateKey) *genesis.GenesisState { + t.Helper() + + var validators []*types.Actor + genesisState := new(genesis.GenesisState) + for serviceURL, privKey := range serviceURLKeyMap { + addr := privKey.Address().String() + mockValidator := &types.Actor{ + ActorType: types.ActorType_ACTOR_TYPE_VAL, + Address: addr, + PublicKey: privKey.PublicKey().String(), + ServiceUrl: serviceURL, + StakedAmount: test_artifacts.DefaultStakeAmountString, + PausedHeight: int64(0), + UnstakingHeight: int64(0), + Output: addr, + } + validators = append(validators, mockValidator) + } + genesisState.Validators = validators + + return genesisState +} + +func GenesisWithSequentialServiceURLs(t gocuke.TestingT, valKeys []cryptoPocket.PublicKey) *genesis.GenesisState { + t.Helper() + + serviceURLs := make([]string, len(valKeys)) + for i := range valKeys { + serviceURLs[i] = testutil.NewServiceURL(i + 1) + } + return BaseGenesisStateMock(t, valKeys, serviceURLs) +} diff --git a/internal/testutil/runtime/mocks.go b/internal/testutil/runtime/mocks.go new file mode 100644 index 000000000..cf6549a08 --- /dev/null +++ b/internal/testutil/runtime/mocks.go @@ -0,0 +1,51 @@ +package runtime_testutil + +import ( + "net" + "strconv" + + "github.com/golang/mock/gomock" + "github.com/regen-network/gocuke" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/pocket/runtime/configs" + "github.com/pokt-network/pocket/runtime/configs/types" + "github.com/pokt-network/pocket/runtime/defaults" + "github.com/pokt-network/pocket/runtime/genesis" + cryptoPocket "github.com/pokt-network/pocket/shared/crypto" + "github.com/pokt-network/pocket/shared/modules" + mock_modules "github.com/pokt-network/pocket/shared/modules/mocks" +) + +func BaseRuntimeManagerMock( + t gocuke.TestingT, + privKey cryptoPocket.PrivateKey, + serviceURL string, + genesisState *genesis.GenesisState, +) modules.RuntimeMgr { + ctrl := gomock.NewController(t) + runtimeMgrMock := mock_modules.NewMockRuntimeMgr(ctrl) + + hostname, portStr, err := net.SplitHostPort(serviceURL) + require.NoError(t, err) + + port, err := strconv.Atoi(portStr) + require.NoError(t, err) + + cfg := &configs.Config{ + RootDirectory: "", + // TODO: need this? + //PrivateKey: privKey.String(), + P2P: &configs.P2PConfig{ + Hostname: hostname, + PrivateKey: privKey.String(), + Port: uint32(port), + ConnectionType: types.ConnectionType_EmptyConnection, + MaxNonces: defaults.DefaultP2PMaxNonces, + }, + } + + runtimeMgrMock.EXPECT().GetConfig().Return(cfg).AnyTimes() + runtimeMgrMock.EXPECT().GetGenesis().Return(genesisState).AnyTimes() + return runtimeMgrMock +} diff --git a/internal/testutil/telemetry/event_metrics_agent.go b/internal/testutil/telemetry/event_metrics_agent.go new file mode 100644 index 000000000..5de2987c0 --- /dev/null +++ b/internal/testutil/telemetry/event_metrics_agent.go @@ -0,0 +1,110 @@ +package telemetry_testutil + +import ( + "github.com/golang/mock/gomock" + "github.com/regen-network/gocuke" + "sync" + + "github.com/pokt-network/pocket/shared/modules/mocks" + "github.com/pokt-network/pocket/telemetry" +) + +func WithP2PIntegrationEvents( + t gocuke.TestingT, + eventMetricsAgentMock *mock_modules.MockEventMetricsAgent, +) *mock_modules.MockEventMetricsAgent { + t.Helper() + + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + + // TODO_THIS_COMMIT: remove v -- may represent failure condition w/ reused nonces.. + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + // END TODO + + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + return eventMetricsAgentMock +} + +// TODO_THIS_COMMIT: refactor... +// Events metric mock - Needed to help with proper counts for number of expected network writes +func PrepareEventMetricsAgentMock(t gocuke.TestingT, valId string, wg *sync.WaitGroup, expectedNumNetworkWrites int) *mock_modules.MockEventMetricsAgent { + ctrl := gomock.NewController(t) + eventMetricsAgentMock := mock_modules.NewMockEventMetricsAgent(ctrl) + + // TODO_THIS_COMMIT: remove + logEvent := func(n, e string, l ...any) { + //t.Logf("n: %s, e: %s, l: %v\n", n, e, l) + } + + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do(logEvent).AnyTimes() + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Eq(telemetry.P2P_RAINTREE_MESSAGE_EVENT_METRIC_SEND_LABEL), gomock.Any()).Do(func(n, e any, l ...any) { + t.Logf("[valId: %s] Write", valId) + wg.Done() + }).Do(logEvent).Times(expectedNumNetworkWrites) + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Not(telemetry.P2P_RAINTREE_MESSAGE_EVENT_METRIC_SEND_LABEL), gomock.Any()).Do(logEvent).AnyTimes() + + return eventMetricsAgentMock +} + +func WhyEventMetricsAgentMock( + t gocuke.TestingT, + eventMetricsAgentMock *mock_modules.MockEventMetricsAgent, + valId string, + wg *sync.WaitGroup, + //handler func(namespace, eventName string, labels ...any), + expectedNumNetworkWrites int, +) *mock_modules.MockEventMetricsAgent { + // TODO_THIS_COMMIT: remove + logEvent := func(n, e string, l ...any) { + //t.Logf("n: %s, e: %s, l: %v\n", n, e, l) + } + + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do(logEvent).AnyTimes() + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Eq(telemetry.P2P_RAINTREE_MESSAGE_EVENT_METRIC_SEND_LABEL), gomock.Any()).Do(func(n, e any, l ...any) { + t.Logf("[valId: %s] Write", valId) + wg.Done() + }).Do(logEvent).Times(expectedNumNetworkWrites) + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Not(telemetry.P2P_RAINTREE_MESSAGE_EVENT_METRIC_SEND_LABEL), gomock.Any()).Do(logEvent).AnyTimes() + + return eventMetricsAgentMock +} + +func EventMetricsAgentMockWithHandler( + t gocuke.TestingT, + label string, + // TODO_THIS_COMMIT: consider refactoring as a type + handler func(namespace, eventName string, labels ...any), + times int, +) *mock_modules.MockEventMetricsAgent { + t.Helper() + + ctrl := gomock.NewController(t) + eventMetricsAgentMock := mock_modules.NewMockEventMetricsAgent(ctrl) + + return WithEventMetricsHandler(t, eventMetricsAgentMock, label, handler, times) +} + +func WithEventMetricsHandler( + t gocuke.TestingT, + eventMetricsAgentMock *mock_modules.MockEventMetricsAgent, + label string, + handler func(namespace, eventName string, labels ...any), + times int, +) *mock_modules.MockEventMetricsAgent { + t.Helper() + + //eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do(logEvent).AnyTimes() + //eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Eq(telemetry.P2P_RAINTREE_MESSAGE_EVENT_METRIC_SEND_LABEL), gomock.Any()).Do(func(n, e any, l ...any) { + // t.Logf("[valId: %s] Write", valId) + // wg.Done() + //}).Do(logEvent).Times(expectedNumNetworkWrites) + //eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Not(telemetry.P2P_RAINTREE_MESSAGE_EVENT_METRIC_SEND_LABEL), gomock.Any()).Do(logEvent).AnyTimes() + + // TODO_THIS_COMMIT: scrutinize these & their order + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Eq(label), gomock.Any()).Do(handler).Times(times) + // TODO_THIS_COMMIT: is this really needed? + eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Not(label), gomock.Any()).AnyTimes() + + return eventMetricsAgentMock +} diff --git a/internal/testutil/telemetry/mocks.go b/internal/testutil/telemetry/mocks.go new file mode 100644 index 000000000..633d03129 --- /dev/null +++ b/internal/testutil/telemetry/mocks.go @@ -0,0 +1,79 @@ +package telemetry_testutil + +import ( + "github.com/golang/mock/gomock" + "github.com/pokt-network/pocket/internal/testutil" + "github.com/regen-network/gocuke" + + "github.com/pokt-network/pocket/shared/modules" + "github.com/pokt-network/pocket/shared/modules/mocks" +) + +func MinimalTelemetryMock( + t gocuke.TestingT, + busMock *mock_modules.MockBus, +) *mock_modules.MockTelemetryModule { + t.Helper() + + ctrl := gomock.NewController(t) + telemetryMock := mock_modules.NewMockTelemetryModule(ctrl) + + busMock.EXPECT().GetTelemetryModule().Return(telemetryMock).AnyTimes() + + return telemetryMock +} + +func BehavesLikeBaseTelemetryMock( + t gocuke.TestingT, + telemetryMock *mock_modules.MockTelemetryModule, +) *mock_modules.MockTelemetryModule { + t.Helper() + + telemetryMock.EXPECT().Start().Return(nil).AnyTimes() + telemetryMock.EXPECT().SetBus(gomock.Any()).Return().AnyTimes() + telemetryMock.EXPECT().GetModuleName().Return(modules.TelemetryModuleName).AnyTimes() + + return telemetryMock +} + +func BaseTelemetryMock( + t gocuke.TestingT, + busMock *mock_modules.MockBus, +) *mock_modules.MockTelemetryModule { + t.Helper() + + return testutil.PipeTwoToOne[ + gocuke.TestingT, + *mock_modules.MockTelemetryModule + ]( + t, MinimalTelemetryMock(t, busMock), + BehavesLikeBaseTelemetryMock, + WithEventMetricsAgent, + WithTimeSeriesAgent, + ) +} + +func WithTimeSeriesAgent( + t gocuke.TestingT, + telemetryMock *mock_modules.MockTelemetryModule, +) *mock_modules.MockTelemetryModule { + t.Helper() + + timeSeriesAgentMock := BaseTimeSeriesAgentMock(t) + + telemetryMock.EXPECT().GetTimeSeriesAgent().Return(timeSeriesAgentMock).AnyTimes() + return telemetryMock +} + +func WithEventMetricsAgent( + t gocuke.TestingT, + telemetryMock *mock_modules.MockTelemetryModule, +) *mock_modules.MockTelemetryModule { + t.Helper() + + ctrl := gomock.NewController(t) + eventMetricsAgentMock := mock_modules.NewMockEventMetricsAgent(ctrl) + + telemetryMock.EXPECT().GetEventMetricsAgent().Return(eventMetricsAgentMock).AnyTimes() + return telemetryMock +} diff --git a/internal/testutil/telemetry/time_series_agent.go b/internal/testutil/telemetry/time_series_agent.go new file mode 100644 index 000000000..9d63dbd03 --- /dev/null +++ b/internal/testutil/telemetry/time_series_agent.go @@ -0,0 +1,31 @@ +package telemetry_testutil + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/regen-network/gocuke" + + "github.com/pokt-network/pocket/shared/modules/mocks" +) + +func BaseTimeSeriesAgentMock(t gocuke.TestingT) *mock_modules.MockTimeSeriesAgent { + t.Helper() + + ctrl := gomock.NewController(t) + timeSeriesAgentMock := mock_modules.NewMockTimeSeriesAgent(ctrl) + timeSeriesAgentMock.EXPECT().CounterRegister(gomock.Any(), gomock.Any()).AnyTimes() + timeSeriesAgentMock.EXPECT().CounterIncrement(gomock.Any()).AnyTimes() + return timeSeriesAgentMock +} + +// Noop mock - no specific business logic to tend to in the timeseries agent mock +func NoopTelemetryTimeSeriesAgentMock(t *testing.T) *mock_modules.MockTimeSeriesAgent { + ctrl := gomock.NewController(t) + timeseriesAgentMock := mock_modules.NewMockTimeSeriesAgent(ctrl) + + timeseriesAgentMock.EXPECT().CounterRegister(gomock.Any(), gomock.Any()).AnyTimes() + timeseriesAgentMock.EXPECT().CounterIncrement(gomock.Any()).AnyTimes() + + return timeseriesAgentMock +} diff --git a/p2p/background/router_test.go b/p2p/background/router_test.go index a1a0fe40b..6b3219d12 100644 --- a/p2p/background/router_test.go +++ b/p2p/background/router_test.go @@ -14,7 +14,11 @@ import ( libp2pPeer "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/multiformats/go-multiaddr" - "github.com/pokt-network/pocket/internal/testutil" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "github.com/pokt-network/pocket/internal/testutil/generics" + "github.com/pokt-network/pocket/internal/testutil/p2p" "github.com/pokt-network/pocket/p2p/config" typesP2P "github.com/pokt-network/pocket/p2p/types" mock_types "github.com/pokt-network/pocket/p2p/types/mocks" @@ -22,16 +26,28 @@ import ( "github.com/pokt-network/pocket/runtime/configs" "github.com/pokt-network/pocket/runtime/defaults" cryptoPocket "github.com/pokt-network/pocket/shared/crypto" + "github.com/pokt-network/pocket/shared/messaging" mockModules "github.com/pokt-network/pocket/shared/modules/mocks" - "github.com/stretchr/testify/require" ) // https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2 -const testIP6ServiceURL = "[2a00:1450:4005:802::2004]:8080" +const ( + testIP6ServiceURL = "[2a00:1450:4005:802::2004]:8080" + numPeers = 4 + testMsg = "test messsage" + testTimeoutDuration = time.Second * 2 +) // TECHDEBT(#609): move & de-dup. var testLocalServiceURL = fmt.Sprintf("127.0.0.1:%d", defaults.DefaultP2PPort) +func TestBackgroundRouter_InvalidConfig(t *testing.T) { + t.Skip("pending") + //busMock := bus_testutil.NewBus(t) + // + //router, err := NewBackgroundRouter() +} + func TestBackgroundRouter_AddPeer(t *testing.T) { testRouter := newTestRouter(t, nil) libp2pPStore := testRouter.host.Peerstore() @@ -115,19 +131,15 @@ func TestBackgroundRouter_RemovePeer(t *testing.T) { } func TestBackgroundRouter_Broadcast(t *testing.T) { - const ( - numPeers = 4 - testMsg = "test messsage" - testTimeoutDuration = time.Second * 5 - ) - var ( ctx = context.Background() // mutex preventing concurrent writes to `seenMessages` - seenMessagesMutext sync.Mutex + seenMessagesMutex sync.Mutex // map used as a set to collect IDs of peers which have received a message seenMessages = make(map[string]struct{}) bootstrapWaitgroup = sync.WaitGroup{} + bootstrapPeerIDCh = make(chan string) + bootstrapPeerIDs = make(map[string]struct{}) broadcastWaitgroup = sync.WaitGroup{} broadcastDone = make(chan struct{}, 1) testTimeout = time.After(testTimeoutDuration) @@ -148,9 +160,31 @@ func TestBackgroundRouter_Broadcast(t *testing.T) { testHosts = append(testHosts, host) expectedPeerIDs[i] = host.ID().String() rtr := newRouterWithSelfPeerAndHost(t, selfPeer, host) - go readSubscription(t, ctx, &broadcastWaitgroup, rtr, &seenMessagesMutext, seenMessages) + rtr.HandlerProxy(t, func(origHandler typesP2P.RouterHandler) typesP2P.RouterHandler { + return func(data []byte) error { + seenMessagesMutex.Lock() + broadcastWaitgroup.Done() + seenMessages[rtr.host.ID().String()] = struct{}{} + seenMessagesMutex.Unlock() + + return origHandler(data) + } + }) } + // concurrently update the set of bootstrapped peer IDs as they connect + go func() { + for { + peerIDStr := <-bootstrapPeerIDCh + if _, ok := bootstrapPeerIDs[peerIDStr]; ok { + // already connected to this peer during bootstrapping + continue + } + bootstrapPeerIDs[peerIDStr] = struct{}{} + bootstrapWaitgroup.Done() + } + }() + // bootstrap off of arbitrary testHost privKey, selfPeer := newTestPeer(t) @@ -166,9 +200,14 @@ func TestBackgroundRouter_Broadcast(t *testing.T) { // setup notifee/notify BEFORE bootstrapping notifee := &libp2pNetwork.NotifyBundle{ - ConnectedF: func(_ libp2pNetwork.Network, _ libp2pNetwork.Conn) { + ConnectedF: func(_ libp2pNetwork.Network, conn libp2pNetwork.Conn) { t.Logf("connected!") - bootstrapWaitgroup.Done() + t.Logf("local PeerID %s; remote PeerID: %s", + conn.LocalPeer().String(), + conn.RemotePeer().String(), + ) + bootstrapPeerIDCh <- conn.RemotePeer().String() + //bootstrapWaitgroup.Done() }, } testRouter.host.Network().Notify(notifee) @@ -189,7 +228,8 @@ func TestBackgroundRouter_Broadcast(t *testing.T) { // broadcast message t.Log("broadcasting...") - err := testRouter.Broadcast([]byte(testMsg)) + testPoktEnvelopeBz := p2p_testutil.NewTestPoktEnvelopeBz(t, testMsg) + err = testRouter.Broadcast(testPoktEnvelopeBz) require.NoError(t, err) // wait for broadcast to be received by all peers @@ -200,6 +240,7 @@ func TestBackgroundRouter_Broadcast(t *testing.T) { // waitgroup broadcastDone or timeout select { case <-testTimeout: + seenMessagesMutex.Lock() t.Fatalf( "timed out waiting for all expected messages: got %d; wanted %d", len(seenMessages), @@ -208,7 +249,10 @@ func TestBackgroundRouter_Broadcast(t *testing.T) { case <-broadcastDone: } - actualPeerIDs = testutil.GetKeys[string](seenMessages) + seenMessagesMutex.Lock() + defer seenMessagesMutex.Unlock() + + actualPeerIDs = generics_testutil.GetKeys[string](seenMessages) require.ElementsMatchf(t, expectedPeerIDs, actualPeerIDs, "peerIDs don't match") } @@ -284,11 +328,33 @@ func newRouterWithSelfPeerAndHost(t *testing.T, selfPeer typesP2P.Peer, host lib err := pstore.AddPeer(selfPeer) require.NoError(t, err) + handler := func(poktEnvelopeBz []byte) error { + poktEnvelope := &messaging.PocketEnvelope{} + err := proto.Unmarshal(poktEnvelopeBz, poktEnvelope) + require.NoError(t, err) + + require.NotEmpty(t, poktEnvelope.Nonce) + require.NotEmpty(t, poktEnvelope.Content) + + debugMsg := &messaging.DebugMessage{} + err = poktEnvelope.Content.UnmarshalTo(debugMsg) + require.NoError(t, err) + + debugStringMsg := &messaging.DebugStringMessage{} + err = debugMsg.Message.UnmarshalTo(debugStringMsg) + require.NoError(t, err) + + require.Equal(t, testMsg, debugStringMsg.Value, "debug string messages don't match") + + return nil + } + router, err := NewBackgroundRouter(busMock, &config.BackgroundConfig{ Addr: selfPeer.GetAddress(), PeerstoreProvider: pstoreProviderMock, CurrentHeightProvider: consensusMock, Host: host, + Handler: handler, }) require.NoError(t, err) @@ -345,31 +411,3 @@ func newTestHost(t *testing.T, mockNet mocknet.Mocknet, privKey cryptoPocket.Pri // construct mock host return newMockNetHostFromPeer(t, mockNet, privKey, peer) } - -func readSubscription( - t *testing.T, - ctx context.Context, - broadcastWaitGroup *sync.WaitGroup, - rtr *backgroundRouter, - mu *sync.Mutex, - seenMsgs map[string]struct{}, -) { - t.Helper() - - for { - if err := ctx.Err(); err != nil { - if err != context.Canceled || err != context.DeadlineExceeded { - require.NoError(t, err) - } - return - } - - _, err := rtr.subscription.Next(ctx) - require.NoError(t, err) - - mu.Lock() - broadcastWaitGroup.Done() - seenMsgs[rtr.host.ID().String()] = struct{}{} - mu.Unlock() - } -} diff --git a/p2p/module_raintree_test.go b/p2p/module_raintree_test.go index 9bd873913..c67c6fb34 100644 --- a/p2p/module_raintree_test.go +++ b/p2p/module_raintree_test.go @@ -1,26 +1,29 @@ //go:build test -package p2p +package p2p_test import ( + runtime_testutil "github.com/pokt-network/pocket/internal/testutil/runtime" + telemetry_testutil "github.com/pokt-network/pocket/internal/testutil/telemetry" + cryptoPocket "github.com/pokt-network/pocket/shared/crypto" + "github.com/pokt-network/pocket/shared/modules" + mock_modules "github.com/pokt-network/pocket/shared/modules/mocks" "log" "os" "path/filepath" "regexp" - "sort" "strconv" - "strings" "sync" "testing" - libp2pNetwork "github.com/libp2p/go-libp2p/core/network" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/regen-network/gocuke" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/anypb" "github.com/pokt-network/pocket/internal/testutil" - "github.com/pokt-network/pocket/p2p/protocol" - "github.com/pokt-network/pocket/p2p/raintree" + "github.com/pokt-network/pocket/internal/testutil/constructors" + persistence_testutil "github.com/pokt-network/pocket/internal/testutil/persistence" + "github.com/pokt-network/pocket/shared/messaging" ) // TODO(#314): Add the tooling and instructions on how to generate unit tests in this file. @@ -40,7 +43,7 @@ func TestMain(m *testing.M) { // ### RainTree Unit Tests ### func TestRainTreeNetworkCompleteOneNodes(t *testing.T) { // val_1 - originatorNode := validatorId(1) + originatorNode := testutil.NewServiceURL(1) expectedCalls := TestNetworkSimulationConfig{ originatorNode: {0, 0}, // val_1, the originator, does 0 network reads or writes } @@ -51,14 +54,13 @@ func TestRainTreeNetworkCompleteTwoNodes(t *testing.T) { // val_1 // └───────┐ // val_2 - originatorNode := validatorId(1) + originatorNode := testutil.NewServiceURL(1) // Per the diagram above, in the case of a 2 node network, the originator node (val_1) does a // single write to another node (val_2), also the // originator node and never performs any reads or writes during a RainTree broadcast. expectedCalls := TestNetworkSimulationConfig{ - // Attempt: I think Validator 1 is sending a message in a 2 (including self) node network - originatorNode: {0, 1}, // val_1 does a single network write (to val_2) - validatorId(2): {1, 0}, // val_2 does a single network read (from val_1) + // Attempt: I think Validator 1 is sending a message in a 2 (including self) node network originatorNode: {0, 1}, // val_1 does a single network write (to val_2) + testutil.NewServiceURL(2): {1, 0}, // val_2 does a single network read (from val_1) } testRainTreeCalls(t, originatorNode, expectedCalls) } @@ -67,11 +69,11 @@ func TestRainTreeNetworkCompleteThreeNodes(t *testing.T) { // val_1 // ┌───────┴────┬─────────┐ // val_2 val_1 val_3 - originatorNode := validatorId(1) + originatorNode := testutil.NewServiceURL(1) expectedCalls := TestNetworkSimulationConfig{ - originatorNode: {0, 2}, // val_1 does two network writes (to val_2 and val_3) - validatorId(2): {1, 0}, // val_2 does a single network read (from val_1) - validatorId(3): {1, 0}, // val_2 does a single network read (from val_3) + originatorNode: {0, 2}, // val_1 does two network writes (to val_2 and val_3) + testutil.NewServiceURL(2): {1, 0}, // val_2 does a single network read (from val_1) + testutil.NewServiceURL(3): {1, 0}, // val_2 does a single network read (from val_3) } testRainTreeCalls(t, originatorNode, expectedCalls) } @@ -83,12 +85,12 @@ func TestRainTreeNetworkCompleteFourNodes(t *testing.T) { // val_2 val_1 val_3 // └───────┐ └───────┐ └───────┐ // val_3 val_2 val_4 - originatorNode := validatorId(1) + originatorNode := testutil.NewServiceURL(1) expectedCalls := TestNetworkSimulationConfig{ - originatorNode: {0, 3}, // val_1 does 3 network writes (two to val_2 and 1 to val_3) - validatorId(2): {2, 1}, // val_2 does 2 network reads (both from val_1) and 1 network write (to val_3) - validatorId(3): {2, 1}, // val_2 does 2 network reads (from val_1 and val_2) and 1 network write (to val_4) - validatorId(4): {1, 0}, // val_2 does 1 network read (from val_3) + originatorNode: {0, 3}, // val_1 does 3 network writes (two to val_2 and 1 to val_3) + testutil.NewServiceURL(2): {2, 1}, // val_2 does 2 network reads (both from val_1) and 1 network write (to val_3) + testutil.NewServiceURL(3): {2, 1}, // val_2 does 2 network reads (from val_1 and val_2) and 1 network write (to val_4) + testutil.NewServiceURL(4): {1, 0}, // val_2 does 1 network read (from val_3) } testRainTreeCalls(t, originatorNode, expectedCalls) } @@ -99,17 +101,17 @@ func TestRainTreeNetworkCompleteNineNodes(t *testing.T) { // val_4 val_1 val_7 // ┌───────┴────┬─────────┐ ┌───────┴────┬─────────┐ ┌───────┴────┬─────────┐ // val_6 val_4 val_8 val_3 val_1 val_5 val_9 val_7 val_2 - originatorNode := validatorId(1) + originatorNode := testutil.NewServiceURL(1) expectedCalls := TestNetworkSimulationConfig{ - originatorNode: {0, 4}, - validatorId(2): {1, 0}, - validatorId(3): {1, 0}, - validatorId(4): {1, 2}, - validatorId(5): {1, 0}, - validatorId(6): {1, 0}, - validatorId(7): {1, 2}, - validatorId(8): {1, 0}, - validatorId(9): {1, 0}, + originatorNode: {0, 4}, + testutil.NewServiceURL(2): {1, 0}, + testutil.NewServiceURL(3): {1, 0}, + testutil.NewServiceURL(4): {1, 2}, + testutil.NewServiceURL(5): {1, 0}, + testutil.NewServiceURL(6): {1, 0}, + testutil.NewServiceURL(7): {1, 2}, + testutil.NewServiceURL(8): {1, 0}, + testutil.NewServiceURL(9): {1, 0}, } testRainTreeCalls(t, originatorNode, expectedCalls) } @@ -123,20 +125,20 @@ func TestRainTreeNetworkCompleteNineNodes(t *testing.T) { // val_8 val_7 val_10 val_6 val_5 val_8 val_11 val_10 val_5 val_4 val_3 val_6 val_2 val_1 val_4 val_7 val_6 val_1 val_12 val_11 val_2 val_10 val_9 val_12 val_3 val_2 val_9 func TestRainTreeCompleteTwelveNodes(t *testing.T) { - originatorNode := validatorId(1) + originatorNode := testutil.NewServiceURL(1) expectedCalls := TestNetworkSimulationConfig{ - originatorNode: {1, 6}, - validatorId(2): {3, 2}, - validatorId(3): {2, 2}, - validatorId(4): {2, 0}, - validatorId(5): {2, 4}, - validatorId(6): {3, 2}, - validatorId(7): {2, 2}, - validatorId(8): {2, 0}, - validatorId(9): {2, 4}, - validatorId(10): {3, 2}, - validatorId(11): {2, 2}, - validatorId(12): {2, 0}, + originatorNode: {1, 6}, + testutil.NewServiceURL(2): {3, 2}, + testutil.NewServiceURL(3): {2, 2}, + testutil.NewServiceURL(4): {2, 0}, + testutil.NewServiceURL(5): {2, 4}, + testutil.NewServiceURL(6): {3, 2}, + testutil.NewServiceURL(7): {2, 2}, + testutil.NewServiceURL(8): {2, 0}, + testutil.NewServiceURL(9): {2, 4}, + testutil.NewServiceURL(10): {3, 2}, + testutil.NewServiceURL(11): {2, 2}, + testutil.NewServiceURL(12): {2, 0}, } testRainTreeCalls(t, originatorNode, expectedCalls) } @@ -149,26 +151,26 @@ func TestRainTreeNetworkCompleteEighteenNodes(t *testing.T) { // val_11 val_7 val_15 val_5 val_1 val_9 val_17 val_13 val_3 // ┌────────┴─────┬───────────┐ ┌───────┴────┬──────────┐ ┌────────┴─────┬──────────┐ ┌───────┴────┬──────────┐ ┌───────┴────┬─────────┐ ┌────────┴────┬─────────┐ ┌───────┴─────┬──────────┐ ┌────────┴─────┬───────────┐ ┌───────┴────┬──────────┐ // val_13 val_11 val_16 val_9 val_7 val_12 val_17 val_15 val_8 val_7 val_5 val_10 val_3 val_1 val_6 val_11 val_9 val_2 val_1 val_17 val_4 val_15 val_13 val_18 val_5 val_3 val_14 - originatorNode := validatorId(1) + originatorNode := testutil.NewServiceURL(1) expectedCalls := TestNetworkSimulationConfig{ - originatorNode: {1, 6}, - validatorId(2): {1, 0}, - validatorId(3): {2, 2}, - validatorId(4): {1, 0}, - validatorId(5): {2, 2}, - validatorId(6): {1, 0}, - validatorId(7): {2, 4}, - validatorId(8): {1, 0}, - validatorId(9): {2, 2}, - validatorId(10): {1, 0}, - validatorId(11): {2, 2}, - validatorId(12): {1, 0}, - validatorId(13): {2, 4}, - validatorId(14): {1, 0}, - validatorId(15): {2, 2}, - validatorId(16): {1, 0}, - validatorId(17): {2, 2}, - validatorId(18): {1, 0}, + originatorNode: {1, 6}, + testutil.NewServiceURL(2): {1, 0}, + testutil.NewServiceURL(3): {2, 2}, + testutil.NewServiceURL(4): {1, 0}, + testutil.NewServiceURL(5): {2, 2}, + testutil.NewServiceURL(6): {1, 0}, + testutil.NewServiceURL(7): {2, 4}, + testutil.NewServiceURL(8): {1, 0}, + testutil.NewServiceURL(9): {2, 2}, + testutil.NewServiceURL(10): {1, 0}, + testutil.NewServiceURL(11): {2, 2}, + testutil.NewServiceURL(12): {1, 0}, + testutil.NewServiceURL(13): {2, 4}, + testutil.NewServiceURL(14): {1, 0}, + testutil.NewServiceURL(15): {2, 2}, + testutil.NewServiceURL(16): {1, 0}, + testutil.NewServiceURL(17): {2, 2}, + testutil.NewServiceURL(18): {1, 0}, } testRainTreeCalls(t, originatorNode, expectedCalls) } @@ -181,35 +183,35 @@ func TestRainTreeNetworkCompleteTwentySevenNodes(t *testing.T) { // val_16 val_10 val_22 val_7 val_1 val_13 val_25 val_19 val_4 // ┌────────┴─────┬───────────┐ ┌────────┴─────┬───────────┐ ┌────────┴─────┬───────────┐ ┌────────┴────┬──────────┐ ┌───────┴────┬─────────┐ ┌────────┴─────┬──────────┐ ┌───────┴─────┬──────────┐ ┌────────┴─────┬───────────┐ ┌───────┴────┬──────────┐ // val_20 val_16 val_24 val_14 val_10 val_18 val_26 val_22 val_12 val_11 val_7 val_15 val_5 val_1 val_9 val_17 val_13 val_3 val_2 val_25 val_6 val_23 val_19 val_27 val_8 val_4 val_21 - originatorNode := validatorId(1) + originatorNode := testutil.NewServiceURL(1) expectedCalls := TestNetworkSimulationConfig{ - originatorNode: {0, 6}, - validatorId(2): {1, 0}, - validatorId(3): {1, 0}, - validatorId(4): {1, 2}, - validatorId(5): {1, 0}, - validatorId(6): {1, 0}, - validatorId(7): {1, 2}, - validatorId(8): {1, 0}, - validatorId(9): {1, 0}, - validatorId(10): {1, 4}, - validatorId(11): {1, 0}, - validatorId(12): {1, 0}, - validatorId(13): {1, 2}, - validatorId(14): {1, 0}, - validatorId(15): {1, 0}, - validatorId(16): {1, 2}, - validatorId(17): {1, 0}, - validatorId(18): {1, 0}, - validatorId(19): {1, 4}, - validatorId(20): {1, 0}, - validatorId(21): {1, 0}, - validatorId(22): {1, 2}, - validatorId(23): {1, 0}, - validatorId(24): {1, 0}, - validatorId(25): {1, 2}, - validatorId(26): {1, 0}, - validatorId(27): {1, 0}, + originatorNode: {0, 6}, + testutil.NewServiceURL(2): {1, 0}, + testutil.NewServiceURL(3): {1, 0}, + testutil.NewServiceURL(4): {1, 2}, + testutil.NewServiceURL(5): {1, 0}, + testutil.NewServiceURL(6): {1, 0}, + testutil.NewServiceURL(7): {1, 2}, + testutil.NewServiceURL(8): {1, 0}, + testutil.NewServiceURL(9): {1, 0}, + testutil.NewServiceURL(10): {1, 4}, + testutil.NewServiceURL(11): {1, 0}, + testutil.NewServiceURL(12): {1, 0}, + testutil.NewServiceURL(13): {1, 2}, + testutil.NewServiceURL(14): {1, 0}, + testutil.NewServiceURL(15): {1, 0}, + testutil.NewServiceURL(16): {1, 2}, + testutil.NewServiceURL(17): {1, 0}, + testutil.NewServiceURL(18): {1, 0}, + testutil.NewServiceURL(19): {1, 4}, + testutil.NewServiceURL(20): {1, 0}, + testutil.NewServiceURL(21): {1, 0}, + testutil.NewServiceURL(22): {1, 2}, + testutil.NewServiceURL(23): {1, 0}, + testutil.NewServiceURL(24): {1, 0}, + testutil.NewServiceURL(25): {1, 2}, + testutil.NewServiceURL(26): {1, 0}, + testutil.NewServiceURL(27): {1, 0}, } testRainTreeCalls(t, originatorNode, expectedCalls) } @@ -220,65 +222,122 @@ func TestRainTreeNetworkCompleteTwentySevenNodes(t *testing.T) { // 1. It creates and configures a "real" P2P module where all the other components of the node are mocked. // 2. It then triggers a single message and waits for all of the expected messages transmission to complete before announcing failure. func testRainTreeCalls(t *testing.T, origNode string, networkSimulationConfig TestNetworkSimulationConfig) { + dnsSrv := testutil.MinimalDNSMock(t) + // Configure & prepare test module numValidators := len(networkSimulationConfig) - runtimeConfigs := createMockRuntimeMgrs(t, numValidators) - genesisMock := runtimeConfigs[0].GetGenesis() - busMocks := createMockBuses(t, runtimeConfigs) + //runtimeConfigs := createMockRuntimeMgrs(t, numValidators) + //genesisState := runtimeConfigs[0].GetGenesis() - valIds := make([]string, 0, numValidators) - for valId := range networkSimulationConfig { - valIds = append(valIds, valId) + privKeys := testutil.LoadLocalnetPrivateKeys(t, numValidators) + pubKeys := make([]cryptoPocket.PublicKey, len(privKeys)) + for i, privKey := range privKeys { + pubKeys[i] = privKey.PublicKey() } - sort.Slice(valIds, func(i, j int) bool { - iId := extractNumericId(valIds[i]) - jId := extractNumericId(valIds[j]) - return iId < jId - }) + genesisState := runtime_testutil.GenesisWithSequentialServiceURLs(t, pubKeys) + + var ( + wg sync.WaitGroup + busMocks map[string]*mock_modules.MockBus + p2pModules map[string]modules.P2PModule + ) + //busMocks := createMockBuses(t, runtimeConfigs, &wg) + busEventHandlerFactory := func(t gocuke.TestingT, busMock *mock_modules.MockBus) testutil.BusEventHandler { + return func(data *messaging.PocketEnvelope) { + p2pCfg := busMock.GetRuntimeMgr().GetConfig().P2P + + // `p2pModule#handleNetworkData()` calls `modules.Bus#PublishEventToBus()` + // assumes that P2P module is the only bus event producer running during + // the test. + t.Logf("[valId: %s:%d] Read", p2pCfg.Hostname, p2pCfg.Port) + wg.Done() + } + } - testutil.PrepareDNSMockFromServiceURLs(t, valIds) + busMocks, _, p2pModules = constructors.NewBusesMocknetAndP2PModules( + t, numValidators, + dnsSrv, + genesisState, + busEventHandlerFactory, + nil, + ) + + //for _, busMock := range busMocks { + // telemetryMock := busMock.GetTelemetryModule().(*mock_modules.MockTelemetryModule) + // telemetryMock.GetEventMetricsAgent().(*mock_modules.MockEventMetricsAgent).EXPECT().EmitEvent( + // gomock.Any(), + // gomock.Any(), + // gomock.Any(), + // gomock.Any(), + // ).AnyTimes() + //} + + //serviceURLs := make([]string, 0, numValidators) + //for valId := range networkSimulationConfig { + // serviceURLs = append(serviceURLs, valId) + //} + // + //// TODO_THIS_COMMIT: need this? + //// sort `serviceURLs` in ascending order + //sort.Slice(serviceURLs, func(i, j int) bool { + // iId := extractNumericId(serviceURLs[i]) + // jId := extractNumericId(serviceURLs[j]) + // return iId < jId + //}) // Create connection and bus mocks along with a shared WaitGroup to track the number of expected // reads and writes throughout the mocked local network - var wg sync.WaitGroup - for i, valId := range valIds { - expectedCall := networkSimulationConfig[valId] + for serviceURL, busMock := range busMocks { + expectedCall := networkSimulationConfig[serviceURL] expectedReads := expectedCall.numNetworkReads expectedWrites := expectedCall.numNetworkWrites - log.Printf("[valId: %s] expected reads: %d\n", valId, expectedReads) - log.Printf("[valId: %s] expected writes: %d\n", valId, expectedWrites) + log.Printf("[serviceURL: %s] expected reads: %d\n", serviceURL, expectedReads) + log.Printf("[serviceURL: %s] expected writes: %d\n", serviceURL, expectedWrites) wg.Add(expectedReads) wg.Add(expectedWrites) - persistenceMock := preparePersistenceMock(t, busMocks[i], genesisMock) - consensusMock := prepareConsensusMock(t, busMocks[i]) - telemetryMock := prepareTelemetryMock(t, busMocks[i], valId, &wg, expectedWrites) - - prepareBusMock(busMocks[i], persistenceMock, consensusMock, telemetryMock) + // TODO_THIS_COMMIT: + //if serviceURL == origNode { + // ... + //} + + // TODO_THIS_COMMIT: MOVE + //telemetryEventHandler := func(namespace, eventName string, labels ...any) { + // t.Log("telemetry event received") + // wg.Done() + //} + + eventMetricsAgentMock := telemetry_testutil.PrepareEventMetricsAgentMock(t, serviceURL, &wg, expectedWrites) + busMock.GetTelemetryModule().(*mock_modules.MockTelemetryModule).EXPECT(). + GetEventMetricsAgent().Return(eventMetricsAgentMock).AnyTimes() + + _ = persistence_testutil.BasePersistenceMock(t, busMock, genesisState) + //telemetryMock := prepareTelemetryMock(t, busMock, serviceURL, &wg, expectedWrites) + //_ = telemetry_testutil.BaseTelemetryMock(t, busMock) + //telemetryMock.GetEventMetricsAgent().(*mock_modules.MockEventMetricsAgent).EXPECT().EmitEvent( + // gomock.Any(), + // gomock.Any(), + // gomock.Eq(telemetry.P2P_RAINTREE_MESSAGE_EVENT_METRIC_SEND_LABEL), + // gomock.Any(), + // //).Do(telemetryEventHandler).Times(expectedWrites) + //).Do(telemetryEventHandler).AnyTimes() } - libp2pMockNet := mocknet.New() - defer func() { - err := libp2pMockNet.Close() - require.NoError(t, err) - }() - - // Inject the connection and bus mocks into the P2P modules - p2pModules := createP2PModules(t, busMocks, libp2pMockNet) - - for serviceURL, p2pMod := range p2pModules { + // Start all p2p modules + for _, p2pMod := range p2pModules { err := p2pMod.Start() require.NoError(t, err) - sURL := strings.Clone(serviceURL) - mod := *p2pMod - p2pMod.host.SetStreamHandler(protocol.PoktProtocolID, func(stream libp2pNetwork.Stream) { - log.Printf("[valID: %s] Read\n", sURL) - (&mod).router.(*raintree.RainTreeRouter).HandleStream(stream) - wg.Done() - }) + // TODO_THIS_COMMIT: decide where to `wg.Done()` + //sURL := strings.Clone(serviceURL) + //mod := *p2pMod + //p2pMod.host.SetStreamHandler(protocol.PoktProtocolID, func(stream libp2pNetwork.Stream) { + // log.Printf("[valID: %s] Read\n", sURL) + // (&mod).router.(*raintree.RainTreeRouter).HandleStream(stream) + // wg.Done() + //}) } // Wait for completion diff --git a/p2p/module_test.go b/p2p/module_test.go index 79cd17066..d39c9f097 100644 --- a/p2p/module_test.go +++ b/p2p/module_test.go @@ -9,6 +9,11 @@ import ( libp2pCrypto "github.com/libp2p/go-libp2p/core/crypto" libp2pHost "github.com/libp2p/go-libp2p/core/host" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/pocket/internal/testutil" + "github.com/pokt-network/pocket/internal/testutil/persistence" + "github.com/pokt-network/pocket/internal/testutil/runtime" typesP2P "github.com/pokt-network/pocket/p2p/types" "github.com/pokt-network/pocket/p2p/utils" "github.com/pokt-network/pocket/runtime/configs" @@ -16,7 +21,6 @@ import ( cryptoPocket "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/modules" mockModules "github.com/pokt-network/pocket/shared/modules/mocks" - "github.com/stretchr/testify/require" ) // TECHDEBT(#609): move & de-dup. @@ -107,14 +111,23 @@ func Test_Create_configureBootstrapNodes(t *testing.T) { }, } + keys := testutil.LoadLocalnetPrivateKeys(t, 10) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) mockRuntimeMgr := mockModules.NewMockRuntimeMgr(ctrl) - mockBus := createMockBus(t, mockRuntimeMgr) + mockBus := testutil.BaseBusMock(t, mockRuntimeMgr) + + // TODO_THIS_COMMIT: refactor + pubKeys := make([]cryptoPocket.PublicKey, len(keys)) + for i, privKey := range keys { + pubKeys[i] = privKey.PublicKey() + } - genesisStateMock := createMockGenesisState(keys) - persistenceMock := preparePersistenceMock(t, mockBus, genesisStateMock) + serviceURLs := testutil.SequentialServiceURLs(t, len(pubKeys)) + genesisStateMock := runtime_testutil.BaseGenesisStateMock(t, pubKeys, serviceURLs) + persistenceMock := persistence_testutil.BasePersistenceMock(t, mockBus, genesisStateMock) mockBus.EXPECT().GetPersistenceModule().Return(persistenceMock).AnyTimes() mockConsensusModule := mockModules.NewMockConsensusModule(ctrl) @@ -155,10 +168,10 @@ func TestP2pModule_WithHostOption_Restart(t *testing.T) { privKey := cryptoPocket.GetPrivKeySeed(1) mockRuntimeMgr := mockModules.NewMockRuntimeMgr(ctrl) - mockBus := createMockBus(t, mockRuntimeMgr) + mockBus := testutil.BaseBusMock(t, mockRuntimeMgr) - genesisStateMock := createMockGenesisState(nil) - persistenceMock := preparePersistenceMock(t, mockBus, genesisStateMock) + genesisStateMock := runtime_testutil.BaseGenesisStateMock(t, nil, nil) + persistenceMock := persistence_testutil.BasePersistenceMock(t, mockBus, genesisStateMock) mockBus.EXPECT().GetPersistenceModule().Return(persistenceMock).AnyTimes() consensusModuleMock := mockModules.NewMockConsensusModule(ctrl) diff --git a/p2p/raintree/peers_manager_test.go b/p2p/raintree/peers_manager_test.go index 7fa01a3d3..caca32f5b 100644 --- a/p2p/raintree/peers_manager_test.go +++ b/p2p/raintree/peers_manager_test.go @@ -356,5 +356,6 @@ func mockAlphabetValidatorServiceURLsDNS(t *testing.T) (done func()) { } } - return testutil.PrepareDNSMock(zones) + _ = testutil.BaseDNSMock(t, zones) + return done } diff --git a/p2p/testutil.go b/p2p/testutil.go new file mode 100644 index 000000000..a030d8682 --- /dev/null +++ b/p2p/testutil.go @@ -0,0 +1,6 @@ +//go:build test + +package p2p + +// P2PModule exports the `p2pModule` type for use in tests +type P2PModule = p2pModule diff --git a/p2p/transport_encryption_test.go b/p2p/transport_encryption_test.go index d95cb6496..86e3c0f1d 100644 --- a/p2p/transport_encryption_test.go +++ b/p2p/transport_encryption_test.go @@ -1,8 +1,13 @@ -package p2p +package p2p_test import ( "context" "fmt" + "github.com/pokt-network/pocket/internal/testutil" + "github.com/pokt-network/pocket/internal/testutil/persistence" + "github.com/pokt-network/pocket/internal/testutil/runtime" + "github.com/pokt-network/pocket/internal/testutil/telemetry" + "github.com/pokt-network/pocket/p2p" "testing" "time" @@ -11,7 +16,6 @@ import ( "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" - "github.com/pokt-network/pocket/internal/testutil" "github.com/pokt-network/pocket/p2p/protocol" typesP2P "github.com/pokt-network/pocket/p2p/types" "github.com/pokt-network/pocket/p2p/utils" @@ -19,7 +23,6 @@ import ( "github.com/pokt-network/pocket/runtime/configs/types" "github.com/pokt-network/pocket/runtime/defaults" cryptoPocket "github.com/pokt-network/pocket/shared/crypto" - "github.com/pokt-network/pocket/shared/modules" mockModules "github.com/pokt-network/pocket/shared/modules/mocks" ) @@ -45,35 +48,28 @@ func TestP2pModule_Insecure_Error(t *testing.T) { }, }).AnyTimes() - timeSeriesAgentMock := prepareNoopTimeSeriesAgentMock(t) - eventMetricsAgentMock := mockModules.NewMockEventMetricsAgent(ctrl) - eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() - - telemetryMock := mockModules.NewMockTelemetryModule(ctrl) - telemetryMock.EXPECT().GetTimeSeriesAgent().Return(timeSeriesAgentMock).AnyTimes() - telemetryMock.EXPECT().GetEventMetricsAgent().Return(eventMetricsAgentMock).AnyTimes() - telemetryMock.EXPECT().GetModuleName().Return(modules.TelemetryModuleName).AnyTimes() - - busMock := createMockBus(t, runtimeMgrMock) + busMock := testutil.BaseBusMock(t, runtimeMgrMock) busMock.EXPECT().GetConsensusModule().Return(mockConsensusModule).AnyTimes() - busMock.EXPECT().GetRuntimeMgr().Return(runtimeMgrMock).AnyTimes() - busMock.EXPECT().GetTelemetryModule().Return(telemetryMock).AnyTimes() - genesisStateMock := createMockGenesisState(keys[:1]) - persistenceMock := preparePersistenceMock(t, busMock, genesisStateMock) - busMock.EXPECT().GetPersistenceModule().Return(persistenceMock).AnyTimes() + telemetryMock := telemetry_testutil.BaseTelemetryMock(t, busMock) + busMock.EXPECT().GetTelemetryModule().Return(telemetryMock).AnyTimes() - telemetryMock.EXPECT().GetBus().Return(busMock).AnyTimes() - telemetryMock.EXPECT().SetBus(busMock).AnyTimes() + keys := testutil.LoadLocalnetPrivateKeys(t, 1) - serviceURLs := make([]string, len(genesisStateMock.Validators)) - for i, actor := range genesisStateMock.Validators { - serviceURLs[i] = actor.ServiceUrl + // TODO_THIS_COMMIT: refactor + pubKeys := make([]cryptoPocket.PublicKey, len(keys)) + for i, privKey := range keys { + pubKeys[i] = privKey.PublicKey() } - dnsDone := testutil.PrepareDNSMockFromServiceURLs(t, serviceURLs) - t.Cleanup(dnsDone) + serviceURLs := testutil.SequentialServiceURLs(t, len(pubKeys)) + genesisStateMock := runtime_testutil.BaseGenesisStateMock(t, pubKeys, serviceURLs) + persistenceMock := persistence_testutil.BasePersistenceMock(t, busMock, genesisStateMock) + busMock.EXPECT().GetPersistenceModule().Return(persistenceMock).AnyTimes() + + // mock DNS for service URL hostnames + _ = testutil.DNSMockFromServiceURLs(t, serviceURLs) - p2pMod, err := Create(busMock) + p2pMod, err := p2p.Create(busMock) require.NoError(t, err) err = p2pMod.Start() diff --git a/p2p/utils/host.go b/p2p/utils/host.go index e9c6e130b..b016bcc47 100644 --- a/p2p/utils/host.go +++ b/p2p/utils/host.go @@ -94,6 +94,17 @@ func Libp2pSendToPeer(host libp2pHost.Host, data []byte, peer typesP2P.Peer) err logger.Global.Debug().Err(err).Msg("logging resource scope stats") } + // TODO: remove me! + //pstore := host.Peerstore() + //for _, peerID := range pstore.Peers() { + // addr := pstore.Addrs(peerID)[0] + // logger.Global.Debug(). + // Str("peerID", peerID.String()). + // Str("addr", addr.String()). + // Msg("peerstore") + //} + // -- end TODO + stream, err := host.NewStream(ctx, peerInfo.ID, protocol.PoktProtocolID) if err != nil { return fmt.Errorf("opening stream: %w", err) diff --git a/p2p/utils_test.go b/p2p/utils_test.go index 66466acb0..57f5729d2 100644 --- a/p2p/utils_test.go +++ b/p2p/utils_test.go @@ -1,42 +1,19 @@ -package p2p +//go:build test + +package p2p_test import ( - "fmt" - "log" - "net" "sort" - "strconv" "sync" "testing" "time" - "github.com/golang/mock/gomock" - libp2pCrypto "github.com/libp2p/go-libp2p/core/crypto" - libp2pPeer "github.com/libp2p/go-libp2p/core/peer" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/pokt-network/pocket/p2p/providers/current_height_provider" - "github.com/pokt-network/pocket/p2p/providers/peerstore_provider" - typesP2P "github.com/pokt-network/pocket/p2p/types" - "github.com/pokt-network/pocket/p2p/utils" - "github.com/pokt-network/pocket/runtime" - "github.com/pokt-network/pocket/runtime/configs" - "github.com/pokt-network/pocket/runtime/configs/types" - "github.com/pokt-network/pocket/runtime/defaults" - "github.com/pokt-network/pocket/runtime/genesis" - "github.com/pokt-network/pocket/runtime/test_artifacts" - coreTypes "github.com/pokt-network/pocket/shared/core/types" cryptoPocket "github.com/pokt-network/pocket/shared/crypto" - "github.com/pokt-network/pocket/shared/modules" - mockModules "github.com/pokt-network/pocket/shared/modules/mocks" - "github.com/pokt-network/pocket/telemetry" - "github.com/stretchr/testify/require" ) // ~~~~~~ RainTree Unit Test Configurations ~~~~~~ const ( - serviceURLFormat = "node%d.consensus:42069" - eventsChannelSize = 10000 // Since we simulate up to a 27 node network, we will pre-generate a n >= 27 number of keys to avoid generation // every time. The genesis config seed start is set for deterministic key generation and 42 was chosen arbitrarily. genesisConfigSeedStart = 42 @@ -75,12 +52,6 @@ type TestNetworkSimulationConfig map[string]struct { // node IDs the specific read or write is coming from or going to. } -// CLEANUP: This could (should?) be a codebase-wide shared test helper -// TECHDEBT: rename `validatorId()` to `serviceURL()` -func validatorId(i int) string { - return fmt.Sprintf(serviceURLFormat, i) -} - func waitForNetworkSimulationCompletion(t *testing.T, wg *sync.WaitGroup) { // Wait for all messages to be transmitted done := make(chan struct{}) @@ -98,217 +69,3 @@ func waitForNetworkSimulationCompletion(t *testing.T, wg *sync.WaitGroup) { t.Fatal("Timeout waiting for message to be handled") } } - -// ~~~~~~ RainTree Unit Test Mocks ~~~~~~ - -// createP2PModules returns a map of configured p2pModules keyed by an incremental naming convention (eg: `val_1`, `val_2`, etc.) -func createP2PModules(t *testing.T, busMocks []*mockModules.MockBus, netMock mocknet.Mocknet) (p2pModules map[string]*p2pModule) { - peerIDs := setupMockNetPeers(t, netMock, len(busMocks)) - - p2pModules = make(map[string]*p2pModule, len(busMocks)) - for i := range busMocks { - host := netMock.Host(peerIDs[i]) - p2pMod, err := Create(busMocks[i], WithHostOption(host)) - require.NoError(t, err) - p2pModules[validatorId(i+1)] = p2pMod.(*p2pModule) - } - return -} - -func setupMockNetPeers(t *testing.T, netMock mocknet.Mocknet, numPeers int) (peerIDs []libp2pPeer.ID) { - // Add a libp2p peers/hosts to the `MockNet` with the keypairs corresponding - // to the genesis validators' keypairs - for i, privKey := range keys[:numPeers] { - peerInfo, err := utils.Libp2pAddrInfoFromPeer(&typesP2P.NetworkPeer{ - PublicKey: privKey.PublicKey(), - Address: privKey.Address(), - ServiceURL: validatorId(i + 1), - }) - require.NoError(t, err) - - libp2pPrivKey, err := libp2pCrypto.UnmarshalEd25519PrivateKey(privKey.Bytes()) - require.NoError(t, err) - - _, err = netMock.AddPeer(libp2pPrivKey, peerInfo.Addrs[0]) - require.NoError(t, err) - - peerIDs = append(peerIDs, peerInfo.ID) - } - - // Link all peers such that any may dial/connect to any other. - err := netMock.LinkAll() - require.NoError(t, err) - - return peerIDs -} - -// createMockRuntimeMgrs creates `numValidators` instances of mocked `RuntimeMgr` that are essentially -// representing the runtime environments of the validators that we will use in our tests -func createMockRuntimeMgrs(t *testing.T, numValidators int) []modules.RuntimeMgr { - ctrl := gomock.NewController(t) - mockRuntimeMgrs := make([]modules.RuntimeMgr, numValidators) - valKeys := make([]cryptoPocket.PrivateKey, numValidators) - copy(valKeys, keys[:numValidators]) - mockGenesisState := createMockGenesisState(valKeys) - for i := range mockRuntimeMgrs { - serviceURL := validatorId(i + 1) - hostname, portStr, err := net.SplitHostPort(serviceURL) - require.NoError(t, err) - - port, err := strconv.Atoi(portStr) - require.NoError(t, err) - - cfg := &configs.Config{ - RootDirectory: "", - PrivateKey: valKeys[i].String(), - P2P: &configs.P2PConfig{ - Hostname: hostname, - PrivateKey: valKeys[i].String(), - Port: uint32(port), - ConnectionType: types.ConnectionType_EmptyConnection, - MaxNonces: defaults.DefaultP2PMaxNonces, - }, - } - - mockRuntimeMgr := mockModules.NewMockRuntimeMgr(ctrl) - mockRuntimeMgr.EXPECT().GetConfig().Return(cfg).AnyTimes() - mockRuntimeMgr.EXPECT().GetGenesis().Return(mockGenesisState).AnyTimes() - mockRuntimeMgrs[i] = mockRuntimeMgr - } - return mockRuntimeMgrs -} - -func createMockBuses(t *testing.T, runtimeMgrs []modules.RuntimeMgr) []*mockModules.MockBus { - mockBuses := make([]*mockModules.MockBus, len(runtimeMgrs)) - for i := range mockBuses { - mockBuses[i] = createMockBus(t, runtimeMgrs[i]) - } - return mockBuses -} - -func createMockBus(t *testing.T, runtimeMgr modules.RuntimeMgr) *mockModules.MockBus { - ctrl := gomock.NewController(t) - mockBus := mockModules.NewMockBus(ctrl) - mockBus.EXPECT().GetRuntimeMgr().Return(runtimeMgr).AnyTimes() - mockBus.EXPECT().RegisterModule(gomock.Any()).DoAndReturn(func(m modules.Module) { - m.SetBus(mockBus) - }).AnyTimes() - mockModulesRegistry := mockModules.NewMockModulesRegistry(ctrl) - mockModulesRegistry.EXPECT().GetModule(peerstore_provider.ModuleName).Return(nil, runtime.ErrModuleNotRegistered(peerstore_provider.ModuleName)).AnyTimes() - mockModulesRegistry.EXPECT().GetModule(current_height_provider.ModuleName).Return(nil, runtime.ErrModuleNotRegistered(current_height_provider.ModuleName)).AnyTimes() - mockBus.EXPECT().GetModulesRegistry().Return(mockModulesRegistry).AnyTimes() - mockBus.EXPECT().PublishEventToBus(gomock.Any()).AnyTimes() - return mockBus -} - -// createMockGenesisState configures and returns a mocked GenesisState -func createMockGenesisState(valKeys []cryptoPocket.PrivateKey) *genesis.GenesisState { - genesisState := new(genesis.GenesisState) - validators := make([]*coreTypes.Actor, len(valKeys)) - for i, valKey := range valKeys { - addr := valKey.Address().String() - mockActor := &coreTypes.Actor{ - ActorType: coreTypes.ActorType_ACTOR_TYPE_VAL, - Address: addr, - PublicKey: valKey.PublicKey().String(), - ServiceUrl: validatorId(i + 1), - StakedAmount: test_artifacts.DefaultStakeAmountString, - PausedHeight: int64(0), - UnstakingHeight: int64(0), - Output: addr, - } - validators[i] = mockActor - } - genesisState.Validators = validators - - return genesisState -} - -// Bus Mock - needed to return the appropriate modules when accessed -func prepareBusMock(busMock *mockModules.MockBus, - persistenceMock *mockModules.MockPersistenceModule, - consensusMock *mockModules.MockConsensusModule, - telemetryMock *mockModules.MockTelemetryModule, -) { - busMock.EXPECT().GetPersistenceModule().Return(persistenceMock).AnyTimes() - busMock.EXPECT().GetConsensusModule().Return(consensusMock).AnyTimes() - busMock.EXPECT().GetTelemetryModule().Return(telemetryMock).AnyTimes() -} - -// Consensus mock - only needed for validatorMap access -func prepareConsensusMock(t *testing.T, busMock *mockModules.MockBus) *mockModules.MockConsensusModule { - ctrl := gomock.NewController(t) - consensusMock := mockModules.NewMockConsensusModule(ctrl) - consensusMock.EXPECT().CurrentHeight().Return(uint64(1)).AnyTimes() - - consensusMock.EXPECT().GetBus().Return(busMock).AnyTimes() - consensusMock.EXPECT().SetBus(busMock).AnyTimes() - consensusMock.EXPECT().GetModuleName().Return(modules.ConsensusModuleName).AnyTimes() - busMock.RegisterModule(consensusMock) - - return consensusMock -} - -// Persistence mock - only needed for validatorMap access -func preparePersistenceMock(t *testing.T, busMock *mockModules.MockBus, genesisState *genesis.GenesisState) *mockModules.MockPersistenceModule { - ctrl := gomock.NewController(t) - - persistenceModuleMock := mockModules.NewMockPersistenceModule(ctrl) - readCtxMock := mockModules.NewMockPersistenceReadContext(ctrl) - - readCtxMock.EXPECT().GetAllValidators(gomock.Any()).Return(genesisState.GetValidators(), nil).AnyTimes() - persistenceModuleMock.EXPECT().NewReadContext(gomock.Any()).Return(readCtxMock, nil).AnyTimes() - readCtxMock.EXPECT().Release().AnyTimes() - - persistenceModuleMock.EXPECT().GetBus().Return(busMock).AnyTimes() - persistenceModuleMock.EXPECT().SetBus(busMock).AnyTimes() - persistenceModuleMock.EXPECT().GetModuleName().Return(modules.PersistenceModuleName).AnyTimes() - busMock.RegisterModule(persistenceModuleMock) - - return persistenceModuleMock -} - -// Telemetry mock - Needed to help with proper counts for number of expected network writes -func prepareTelemetryMock(t *testing.T, busMock *mockModules.MockBus, valId string, wg *sync.WaitGroup, expectedNumNetworkWrites int) *mockModules.MockTelemetryModule { - ctrl := gomock.NewController(t) - telemetryMock := mockModules.NewMockTelemetryModule(ctrl) - - timeSeriesAgentMock := prepareNoopTimeSeriesAgentMock(t) - eventMetricsAgentMock := prepareEventMetricsAgentMock(t, valId, wg, expectedNumNetworkWrites) - - telemetryMock.EXPECT().GetTimeSeriesAgent().Return(timeSeriesAgentMock).AnyTimes() - telemetryMock.EXPECT().GetEventMetricsAgent().Return(eventMetricsAgentMock).AnyTimes() - - telemetryMock.EXPECT().GetModuleName().Return(modules.TelemetryModuleName).AnyTimes() - telemetryMock.EXPECT().GetBus().Return(busMock).AnyTimes() - telemetryMock.EXPECT().SetBus(busMock).AnyTimes() - busMock.RegisterModule(telemetryMock) - - return telemetryMock -} - -// Noop mock - no specific business logic to tend to in the timeseries agent mock -func prepareNoopTimeSeriesAgentMock(t *testing.T) *mockModules.MockTimeSeriesAgent { - ctrl := gomock.NewController(t) - timeseriesAgentMock := mockModules.NewMockTimeSeriesAgent(ctrl) - - timeseriesAgentMock.EXPECT().CounterRegister(gomock.Any(), gomock.Any()).AnyTimes() - timeseriesAgentMock.EXPECT().CounterIncrement(gomock.Any()).AnyTimes() - - return timeseriesAgentMock -} - -// Events metric mock - Needed to help with proper counts for number of expected network writes -func prepareEventMetricsAgentMock(t *testing.T, valId string, wg *sync.WaitGroup, expectedNumNetworkWrites int) *mockModules.MockEventMetricsAgent { - ctrl := gomock.NewController(t) - eventMetricsAgentMock := mockModules.NewMockEventMetricsAgent(ctrl) - - eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() - eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Eq(telemetry.P2P_RAINTREE_MESSAGE_EVENT_METRIC_SEND_LABEL), gomock.Any()).Do(func(n, e any, l ...any) { - log.Printf("[valId: %s] Write\n", valId) - wg.Done() - }).Times(expectedNumNetworkWrites) - eventMetricsAgentMock.EXPECT().EmitEvent(gomock.Any(), gomock.Any(), gomock.Not(telemetry.P2P_RAINTREE_MESSAGE_EVENT_METRIC_SEND_LABEL), gomock.Any()).AnyTimes() - - return eventMetricsAgentMock -} diff --git a/shared/messaging/proto/debug_message.proto b/shared/messaging/proto/debug_message.proto index 7ce079afa..7e42f7ddd 100644 --- a/shared/messaging/proto/debug_message.proto +++ b/shared/messaging/proto/debug_message.proto @@ -30,6 +30,10 @@ message DebugMessage { google.protobuf.Any message = 3; } +message DebugStringMessage { + string value = 1; +} + // NB: See https://en.wikipedia.org/wiki/Routing for more info on routing and delivery schemes. enum DebugMessageRoutingType { DEBUG_MESSAGE_TYPE_UNKNOWN = 0;