From c473aea52543ba6992bda2f908f5052c92678cbe Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 10 Apr 2023 21:13:01 -0700 Subject: [PATCH 01/46] session.proto added - good starting point --- Makefile | 3 +- persistence/actor.go | 2 +- persistence/test/module_test.go | 19 +- persistence/test/setup_test.go | 4 +- shared/core/types/proto/session.proto | 25 +++ shared/modules/persistence_module.go | 5 + shared/modules/utility_module.go | 5 + utility/module_test.go | 133 ++++++++++++ utility/session.go | 198 +++++++++--------- utility/session_test.go | 47 +++++ .../{module_test.go => unit_of_work_test.go} | 0 11 files changed, 333 insertions(+), 108 deletions(-) create mode 100644 shared/core/types/proto/session.proto create mode 100644 utility/module_test.go create mode 100644 utility/session_test.go rename utility/unit_of_work/{module_test.go => unit_of_work_test.go} (100%) diff --git a/Makefile b/Makefile index 1d635320e..d7c069e4a 100644 --- a/Makefile +++ b/Makefile @@ -441,6 +441,7 @@ benchmark_p2p_addrbook: ## Benchmark all P2P addr book related tests # TODO - General Purpose catch-all. # TECHDEBT - Not a great implementation, but we need to fix it later. # IMPROVE - A nice to have, but not a priority. It's okay if we never get to this. +# OPTIMIZE - An opportunity for performance improvement if/when it's necessary # DISCUSS - Probably requires a lengthy offline discussion to understand next steps. # INCOMPLETE - A change which was out of scope of a specific PR but needed to be documented. # INVESTIGATE - TBD what was going on, but needed to continue moving and not get distracted. @@ -456,7 +457,7 @@ benchmark_p2p_addrbook: ## Benchmark all P2P addr book related tests # BUG - There is a known existing bug in this code # DISCUSS_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way for the reviewer of a PR to start / reply to a discussion. # TODO_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way to start the review process while non-critical changes are still in progress -TODO_KEYWORDS = -e "TODO" -e "TECHDEBT" -e "IMPROVE" -e "DISCUSS" -e "INCOMPLETE" -e "INVESTIGATE" -e "CLEANUP" -e "HACK" -e "REFACTOR" -e "CONSIDERATION" -e "TODO_IN_THIS_COMMIT" -e "DISCUSS_IN_THIS_COMMIT" -e "CONSOLIDATE" -e "DEPRECATE" -e "ADDTEST" -e "RESEARCH" -e "BUG" +TODO_KEYWORDS = -e "TODO" -e "TECHDEBT" -e "IMPROVE" -e "OPTIMIZE" -e "DISCUSS" -e "INCOMPLETE" -e "INVESTIGATE" -e "CLEANUP" -e "HACK" -e "REFACTOR" -e "CONSIDERATION" -e "TODO_IN_THIS_COMMIT" -e "DISCUSS_IN_THIS_COMMIT" -e "CONSOLIDATE" -e "DEPRECATE" -e "ADDTEST" -e "RESEARCH" -e "BUG" # How do I use TODOs? # 1. : ; diff --git a/persistence/actor.go b/persistence/actor.go index 092314797..39cb55621 100644 --- a/persistence/actor.go +++ b/persistence/actor.go @@ -113,7 +113,7 @@ func (p *PostgresContext) GetAllFishermen(height int64) (f []*coreTypes.Actor, e return } -// IMPROVE: This is a proof of concept. Ideally we should have a single query that returns all actors. +// OPTIMIZE: Ideally we should have a single query that returns all actors. func (p *PostgresContext) GetAllStakedActors(height int64) (allActors []*coreTypes.Actor, err error) { type actorGetter func(height int64) ([]*coreTypes.Actor, error) actorGetters := []actorGetter{p.GetAllValidators, p.GetAllServicers, p.GetAllFishermen, p.GetAllApps} diff --git a/persistence/test/module_test.go b/persistence/test/module_test.go index ac69d9437..500222225 100644 --- a/persistence/test/module_test.go +++ b/persistence/test/module_test.go @@ -9,7 +9,8 @@ import ( ) func TestPersistenceContextParallelReadWrite(t *testing.T) { - prepareAndCleanContext(t) + clearAllState() + t.Cleanup(clearAllState) // variables for testing _, _, poolAddr := keygen.GetInstance().Next() @@ -57,7 +58,8 @@ func TestPersistenceContextParallelReadWrite(t *testing.T) { } func TestPersistenceContextTwoWritesErrors(t *testing.T) { - prepareAndCleanContext(t) + clearAllState() + t.Cleanup(clearAllState) // Opening up first write context succeeds rwCtx1, err := testPersistenceMod.NewRWContext(0) @@ -74,7 +76,8 @@ func TestPersistenceContextTwoWritesErrors(t *testing.T) { } func TestPersistenceContextSequentialWrites(t *testing.T) { - prepareAndCleanContext(t) + clearAllState() + t.Cleanup(clearAllState) // Opening up first write context succeeds writeContext1, err := testPersistenceMod.NewRWContext(0) @@ -93,7 +96,8 @@ func TestPersistenceContextSequentialWrites(t *testing.T) { } func TestPersistenceContextMultipleParallelReads(t *testing.T) { - prepareAndCleanContext(t) + clearAllState() + t.Cleanup(clearAllState) // Opening up first read context succeeds readContext1, err := testPersistenceMod.NewReadContext(0) @@ -111,10 +115,3 @@ func TestPersistenceContextMultipleParallelReads(t *testing.T) { readContext2.Release() readContext3.Release() } - -func prepareAndCleanContext(t *testing.T) { - // Cleanup context after the test - t.Cleanup(clearAllState) - - clearAllState() -} diff --git a/persistence/test/setup_test.go b/persistence/test/setup_test.go index c84a970ff..f9e4f5f19 100644 --- a/persistence/test/setup_test.go +++ b/persistence/test/setup_test.go @@ -53,8 +53,10 @@ var ( genesisStateNumServicers = 1 genesisStateNumApplications = 1 genesisStateNumFishermen = 1 + + // Initialized in TestMain + testPersistenceMod modules.PersistenceModule ) -var testPersistenceMod modules.PersistenceModule // initialized in TestMain // See https://github.com/ory/dockertest as reference for the template of this code // Postgres example can be found here: https://github.com/ory/dockertest/blob/v3/examples/PostgreSQL.md diff --git a/shared/core/types/proto/session.proto b/shared/core/types/proto/session.proto new file mode 100644 index 000000000..dfae27a52 --- /dev/null +++ b/shared/core/types/proto/session.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package core; + +option go_package = "github.com/pokt-network/pocket/shared/core/types"; + +import "actor.proto"; + +// TECHDEBT: Do we need backwards with v0? https://docs.pokt.network/supported-blockchains/ +enum RelayChain { + UNSPECIFIED_RELAY_CHAIN = 0; + ETHEREUM = 1; + POLYGON = 2; + // TODO: Add all the other chains we need +} + +message Session { + string id = 1; + int64 height = 2; + RelayChain relay_chain = 3; // CONSIDERATION: Will we ever want to support more than one relay chain? + string geo_zone = 4; + core.Actor application = 5; + repeated core.Actor servicers = 6; + repeated core.Actor fishermen = 7; +} \ No newline at end of file diff --git a/shared/modules/persistence_module.go b/shared/modules/persistence_module.go index 895180fe9..91d47aff2 100644 --- a/shared/modules/persistence_module.go +++ b/shared/modules/persistence_module.go @@ -54,6 +54,7 @@ type PersistenceRWContext interface { // - Decouple functions that can be split into two or more independent behaviours (e.g. `SetAppStatusAndUnstakingHeightIfPausedBefore`) // - Rename `Unstaking` to `Unbonding` where appropriate // - convert address and public key to string from bytes +// - Remove `height` from all write context functions because it should only write at the height it was initiated in // PersistenceWriteContext has no use-case independent of `PersistenceRWContext`, but is a useful abstraction type PersistenceWriteContext interface { @@ -130,6 +131,7 @@ type PersistenceWriteContext interface { type PersistenceReadContext interface { // Context Operations + // TECHDEBT: Remove this function since read contexts are height agnostic - it's an accessor to the state of the blockchain at any height. GetHeight() (int64, error) // Returns the height of the context Release() // Releases the read context @@ -151,6 +153,9 @@ type PersistenceReadContext interface { GetAccountAmount(address []byte, height int64) (string, error) GetAllAccounts(height int64) ([]*coreTypes.Account, error) + // Actor Queries + // GetActor( address []byte, height int64) (*coreTypes.Actor, error) + // App Queries GetAllApps(height int64) ([]*coreTypes.Actor, error) GetAppExists(address []byte, height int64) (exists bool, err error) diff --git a/shared/modules/utility_module.go b/shared/modules/utility_module.go index 068b81cc8..bd926ad1e 100644 --- a/shared/modules/utility_module.go +++ b/shared/modules/utility_module.go @@ -3,6 +3,7 @@ package modules //go:generate mockgen -source=$GOFILE -destination=./mocks/utility_module_mock.go -aux_files=github.com/pokt-network/pocket/shared/modules=module.go import ( + coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/mempool" "google.golang.org/protobuf/types/known/anypb" ) @@ -29,6 +30,10 @@ type UtilityModule interface { // It is useful for handling messages from the utility module's of other nodes that do not directly affect the state. // IMPROVE: Find opportunities to break this apart as the module matures. HandleUtilityMessage(*anypb.Any) error + + // Return a pseudo-random session object for the given application address, session height, relay chain and geo zones + // using on-chain data as he entropy source. + GetSession(appAddr string, sessionHeight int64, relayChain coreTypes.RelayChain, geoZone string) (*coreTypes.Session, error) } // TECHDEBT: Remove this interface from `shared/modules` and use the `Actor` protobuf type instead diff --git a/utility/module_test.go b/utility/module_test.go new file mode 100644 index 000000000..58c25512f --- /dev/null +++ b/utility/module_test.go @@ -0,0 +1,133 @@ +package utility + +import ( + "log" + "os" + "testing" + + "github.com/pokt-network/pocket/persistence" + "github.com/pokt-network/pocket/runtime" + "github.com/pokt-network/pocket/runtime/configs" + "github.com/pokt-network/pocket/runtime/test_artifacts" + "github.com/pokt-network/pocket/shared/modules" +) + +const ( + testingValidatorCount = 5 + testingServicerCount = 1 + testingApplicationCount = 1 + testingFishermenCount = 1 + + testNonce = "defaultNonceString" + testSchema = "test_schema" +) + +var ( + testUtilityMod modules.UtilityModule + + // DISCUSS_IN_THIS_COMMIT(#261): We used to think that utility shouldn't have any dependencies on persistence, but + // mocking everything in `persistence_module` seems complex when all we want is just a configuration/state using a certain genesis + // config or runtime environment. + testPersistenceMod modules.PersistenceModule +) + +// func NewTestingMempool(_ *testing.T) mempool.TXMempool { +// return utilTypes.NewTxFIFOMempool(1000000, 1000) +// } + +func TestMain(m *testing.M) { + pool, resource, dbURL := test_artifacts.SetupPostgresDocker() + + runtimeCfg := newTestRuntimeConfig(dbURL) + bus, err := runtime.CreateBus(runtimeCfg) + if err != nil { + log.Fatalf("Error creating bus: %s", err) + } + + testPersistenceMod = newTestPersistenceModule(bus) + testUtilityMod = newTestUtilityModule(bus) + + exitCode := m.Run() + test_artifacts.CleanupPostgresDocker(m, pool, resource) + os.Exit(exitCode) +} + +// func newTestingUtilityUnitOfWork(t *testing.T, height int64, options ...func(*baseUtilityUnitOfWork)) *baseUtilityUnitOfWork { +// rwCtx, err := testPersistenceMod.NewRWContext(height) +// require.NoError(t, err) + +// // TECHDEBT: Move the internal of cleanup into a separate function and call this in the +// // beginning of every test. This (the current implementation) is an issue because if we call +// // `NewTestingUtilityContext` more than once in a single test, we create unnecessary calls to clean. +// t.Cleanup(func() { +// err := testPersistenceMod.ReleaseWriteContext() +// require.NoError(t, err) +// err = testPersistenceMod.HandleDebugMessage(&messaging.DebugMessage{ +// Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, +// Message: nil, +// }) +// require.NoError(t, err) +// // TODO: May need to run `bus.GetUtilityModule().GetMempool().Clear()` here +// }) + +// uow := &baseUtilityUnitOfWork{ +// logger: logger.Global.CreateLoggerForModule(modules.UtilityModuleName), +// height: height, +// // TODO(@deblasis): Refactor this +// persistenceRWContext: rwCtx, +// persistenceReadContext: rwCtx, +// } + +// uow.SetBus(testPersistenceMod.GetBus()) + +// for _, option := range options { +// option(uow) +// } + +// return uow +// } + +func newTestUtilityModule(bus modules.Bus) modules.UtilityModule { + utilityMod, err := Create(bus) + if err != nil { + log.Fatalf("Error creating utility module: %s", err) + } + return utilityMod.(modules.UtilityModule) +} + +func newTestPersistenceModule(bus modules.Bus) modules.PersistenceModule { + persistenceMod, err := persistence.Create(bus) + if err != nil { + log.Fatalf("Error creating persistence module: %s", err) + } + return persistenceMod.(modules.PersistenceModule) +} + +func newTestRuntimeConfig(databaseURL string) *runtime.Manager { + cfg := &configs.Config{ + Utility: &configs.UtilityConfig{ + MaxMempoolTransactionBytes: 1000000, + MaxMempoolTransactions: 1000, + }, + Persistence: &configs.PersistenceConfig{ + PostgresUrl: databaseURL, + NodeSchema: testSchema, + BlockStorePath: "", // in memory + TxIndexerPath: "", // in memory + TreesStoreDir: "", // in memory + MaxConnsCount: 50, + MinConnsCount: 1, + MaxConnLifetime: "5m", + MaxConnIdleTime: "1m", + HealthCheckPeriod: "30s", + }, + } + genesisState, _ := test_artifacts.NewGenesisState( + testingValidatorCount, + testingServicerCount, + testingApplicationCount, + testingFishermenCount, + ) + runtimeCfg := runtime.NewManager(cfg, genesisState) + return runtimeCfg +} diff --git a/utility/session.go b/utility/session.go index 1260248d1..d7876ae15 100644 --- a/utility/session.go +++ b/utility/session.go @@ -4,114 +4,85 @@ package utility // and need to be revisited before any implementation commences. import ( - "encoding/binary" + "fmt" coreTypes "github.com/pokt-network/pocket/shared/core/types" - "github.com/pokt-network/pocket/shared/crypto" + "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/utility/types" ) // TODO: When implementing please review if block height tolerance (+,-1) is included in the session protocol: pokt-network/pocket-core#1464 CC @Olshansk -// REFACTOR: Move these into `utility/types` and consider creating an enum -type RelayChain string -type GeoZone string - -type Session interface { - NewSession(sessionHeight int64, relayChain RelayChain, geoZone GeoZone, application *coreTypes.Actor) (Session, types.Error) - GetSessionID() []byte // the identifier of the dispatched session - GetSessionHeight() int64 // the block height when the session started - GetRelayChain() RelayChain // the web3 chain identifier - GetGeoZone() GeoZone // the geo-location zone where the application is intending to operate during the session - GetApplication() *coreTypes.Actor // the Application consuming the web3 access - GetServicers() []*coreTypes.Actor // the Servicers providing Web3 to the application - GetFishermen() []*coreTypes.Actor // the Fishermen monitoring the servicers +type sessionHydrator struct { + logger modules.Logger + session *coreTypes.Session + readCtx modules.PersistenceReadContext } -var _ Session = &session{} - -type session struct { - sessionId []byte - height int64 - relayChain RelayChain - geoZone GeoZone - application *coreTypes.Actor - servicers []*coreTypes.Actor - fishermen []*coreTypes.Actor -} +func (m *utilityModule) GetSession(appAddr string, height int64, relayChain coreTypes.RelayChain, geoZone string) (*coreTypes.Session, error) { + persistenceModule := m.GetBus().GetPersistenceModule() + readCtx, err := persistenceModule.NewReadContext(height) + if err != nil { + return nil, err + } + defer readCtx.Release() -func (*session) NewSession( - sessionHeight int64, - relayChain RelayChain, - geoZone GeoZone, - application *coreTypes.Actor, -) (Session, types.Error) { - s := &session{ - height: sessionHeight, - relayChain: relayChain, - geoZone: geoZone, - application: application, + session := &coreTypes.Session{ + Height: height, + RelayChain: relayChain, + GeoZone: geoZone, } - // TODO: make these configurable or based on governance params - numServicers := 1 - numFisherman := 1 + sessionHydrator := &sessionHydrator{ + logger: m.logger.With().Str("source", "sessionHydrator").Logger(), + session: session, + readCtx: readCtx, + } - var err types.Error - if s.servicers, err = s.selectSessionServicers(numServicers); err != nil { + if err := sessionHydrator.hydrateSessionId(); err != nil { return nil, err } - if s.fishermen, err = s.selectSessionFishermen(numFisherman); err != nil { + + if err := sessionHydrator.hydrateSessionApplication(); err != nil { return nil, err } - if s.sessionId, err = s.getSessionId(); err != nil { + + if err := sessionHydrator.hydrateSessionServicers(); err != nil { return nil, err } - return s, nil -} - -func (s *session) GetSessionID() []byte { - return s.sessionId -} -func (s *session) GetSessionHeight() int64 { - return s.height -} - -func (s *session) GetRelayChain() RelayChain { - return s.relayChain -} - -func (s *session) GetGeoZone() GeoZone { - return s.geoZone -} - -func (s *session) GetApplication() *coreTypes.Actor { - return s.application -} + if err := sessionHydrator.hydrateSessionFishermen(); err != nil { + return nil, err + } -func (s *session) GetFishermen() []*coreTypes.Actor { - return s.fishermen + return sessionHydrator.session, nil } -func (s *session) GetServicers() []*coreTypes.Actor { - return s.servicers +func getSessionHeight(readCtx modules.PersistenceReadContext, blockHeight int64) (int64, error) { + return blockHeight, nil } // use the seed information to determine a SHA3Hash that is used to find the closest N actors based // by comparing the sessionKey with the actors' public key -func (s *session) getSessionId() ([]byte, types.Error) { - sessionHeightBz := make([]byte, 8) - binary.LittleEndian.PutUint64(sessionHeightBz, uint64(s.height)) +func (s *sessionHydrator) hydrateSessionId() error { + s.readCtx.GetAppExists() + // sessionIdBz := make([]byte, 8) + // binary.LittleEndian.PutUint64(sessionHeightBz, uint64(s.height)) - blockHashBz := []byte("get block hash bytes at s.sessionHeight from persistence module") + // blockHashBz := []byte("get block hash bytes at s.sessionHeight from persistence module") - appPubKey, err := crypto.NewPublicKey(s.application.GetPublicKey()) - if err != nil { - return nil, types.ErrNewPublicKeyFromBytes(err) - } + // appPubKey, err := crypto.NewPublicKey(s.application.GetPublicKey()) + // if err != nil { + // return nil, types.ErrNewPublicKeyFromBytes(err) + // } - return concat(sessionHeightBz, blockHashBz, []byte(s.geoZone), []byte(s.relayChain), appPubKey.Bytes()), nil + // return concat(sessionHeightBz, blockHashBz, []byte(s.geoZone), []byte(s.relayChain), appPubKey.Bytes()), nil + return nil +} + +func (s *sessionHydrator) hydrateSessionApplication() error { + // IMPORTANT: This function is for behaviour illustrative purposes only and implementation may differ. + return nil } // uses the current 'world state' to determine the servicers in the session @@ -121,9 +92,47 @@ func (s *session) getSessionId() ([]byte, types.Error) { // - staked for relay-chain // // 2) calls `pseudoRandomSelection(servicers, numberOfNodesPerSession)` -func (s *session) selectSessionServicers(numServicers int) ([]*coreTypes.Actor, types.Error) { - // IMPORTANT: This function is for behaviour illustrative purposes only and implementation may differ. - return nil, nil +func (s *sessionHydrator) hydrateSessionServicers() error { + // number of servicers per session at this height + numServicers, err := s.readCtx.GetIntParam(types.ServicersPerSessionParamName, s.session.Height) + if err != nil { + return err + } + // s.session.Servicers = make([]*coreTypes.Actor, numServicers) + + // returns all the staked servicers at this session height + servicers, err := s.readCtx.GetAllServicers(s.session.Height) + if err != nil { + return err + } + + // OPTIMIZE: Update the persistence module to allow for querying for filtered servicers directly + // Determine the servicers for this session + candidateServicers := make([]*coreTypes.Actor, 0) + for _, servicer := range servicers { + // Sanity check the servicer is not paused or unstaking + if servicer.PausedHeight == -1 || servicer.UnstakingHeight == -1 { + return fmt.Errorf("selectSessionServicers should not have encountered a paused or unstaking servicer: %s", servicer.Address) + } + + // TODO_IN_THIS_COMMIT: if servicer.GeoZone includes session.GeoZone + + // OPTIMIZE: If this was a map, we could have avoided the loop over chains + var chain string + for _, chain = range servicer.Chains { + // TODO_IN_THIS_COMMIT: Change actor chains to use the enum + if chain != string(s.session.RelayChain) { + chain = "" + continue + } + } + if chain != "" { + candidateServicers = append(candidateServicers, servicer) + } + } + + s.session.Servicers = s.pseudoRandomSelection(candidateServicers, numServicers) + return nil } // uses the current 'world state' to determine the fishermen in the session @@ -133,9 +142,9 @@ func (s *session) selectSessionServicers(numServicers int) ([]*coreTypes.Actor, // - staked for relay-chain // // 2) calls `pseudoRandomSelection(fishermen, numberOfFishPerSession)` -func (s *session) selectSessionFishermen(numFishermen int) ([]*coreTypes.Actor, types.Error) { +func (s *sessionHydrator) hydrateSessionFishermen() error { // IMPORTANT: This function is for behaviour illustrative purposes only and implementation may differ. - return nil, nil + return nil } // 1) passed an ordered list of the public keys of actors and number of nodes @@ -147,16 +156,17 @@ func (s *session) selectSessionFishermen(numFishermen int) ([]*coreTypes.Actor, // A) pseudo-random selection only works if each iteration is re-randomized // // or it would be subject to lexicographical proximity bias attacks -// -//nolint:unused // This is a demonstratable function -func (s *session) pseudoRandomSelection(orderedListOfPublicKeys []string, numActorsToSelect int) []*coreTypes.Actor { - // IMPORTANT: This function is for behaviour illustrative purposes only and implementation may differ. - return nil -} - -func concat(b ...[]byte) (result []byte) { - for _, bz := range b { - result = append(result, bz...) +func (s *sessionHydrator) pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int) []*coreTypes.Actor { + if numTarget < len(candidates) { + s.logger.Warn().Msgf("pseudoRandomSelection: numTarget (%d) is less than the number of candidates (%d)", numTarget, len(candidates)) } - return result + // TODO_IN_THIS_COMMIT: Actually implement this + return candidates[:numTarget] } + +// func concat(b ...[]byte) (result []byte) { +// for _, bz := range b { +// result = append(result, bz...) +// } +// return result +// } diff --git a/utility/session_test.go b/utility/session_test.go new file mode 100644 index 000000000..a99a1ca69 --- /dev/null +++ b/utility/session_test.go @@ -0,0 +1,47 @@ +package utility + +import ( + "testing" + + coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/stretchr/testify/require" +) + +func TestSession_NewSession(t *testing.T) { + session, err := testUtilityMod.GetSession("app", 1, coreTypes.RelayChain_ETHEREUM, "geo") + require.NoError(t, err) + + require.Equal(t, session.Application.Address, "app") +} + +// Not enough servicers + +// What if someone paused mid session? + +// stake a new servicer -> do I get them? + +// Invalid application + +// Not enough servicers in region + +// No fisherman available + +// Check session block height + +// Configurable number of geo zones per session +// above max +// below max +// at max + +// invalid relay chain +// valid relay chain +// application is not staked for relay chain + +// dispatching session in the future +// dispatching session in the past + +// generate session id +// validate entropy and randomness + +func TestSession_MatchNewSession(t *testing.T) { +} diff --git a/utility/unit_of_work/module_test.go b/utility/unit_of_work/unit_of_work_test.go similarity index 100% rename from utility/unit_of_work/module_test.go rename to utility/unit_of_work/unit_of_work_test.go From c0f7269de92769a419c38333e148482f6c80f02c Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 10 Apr 2023 21:41:54 -0700 Subject: [PATCH 02/46] Got session to almost generate --- persistence/actor.go | 20 ++++++++ shared/modules/persistence_module.go | 2 +- utility/module_test.go | 5 ++ utility/session.go | 69 ++++++++++++++++++---------- utility/session_test.go | 16 ++++++- 5 files changed, 86 insertions(+), 26 deletions(-) diff --git a/persistence/actor.go b/persistence/actor.go index 39cb55621..43ad43141 100644 --- a/persistence/actor.go +++ b/persistence/actor.go @@ -1,10 +1,30 @@ package persistence import ( + "fmt" + "github.com/pokt-network/pocket/persistence/types" coreTypes "github.com/pokt-network/pocket/shared/core/types" ) +// TODO_IN_THIS_COMMIT: Add tests for this function +func (p *PostgresContext) GetActor(actorType coreTypes.ActorType, address []byte, height int64) (*coreTypes.Actor, error) { + var schema types.ProtocolActorSchema + switch actorType { + case types.ApplicationActor.GetActorType(): + schema = types.ApplicationActor + case types.ServicerActor.GetActorType(): + schema = types.ServicerActor + case types.FishermanActor.GetActorType(): + schema = types.FishermanActor + case types.ValidatorActor.GetActorType(): + schema = types.FishermanActor + default: + return nil, fmt.Errorf("invalid actor type: %s", actorType) + } + return p.getActor(schema, address, height) +} + // TODO (#399): All of the functions below following a structure similar to `GetAll` // can easily be refactored and condensed into a single function using a generic type or a common // interface. diff --git a/shared/modules/persistence_module.go b/shared/modules/persistence_module.go index 91d47aff2..9e1f77263 100644 --- a/shared/modules/persistence_module.go +++ b/shared/modules/persistence_module.go @@ -154,7 +154,7 @@ type PersistenceReadContext interface { GetAllAccounts(height int64) ([]*coreTypes.Account, error) // Actor Queries - // GetActor( address []byte, height int64) (*coreTypes.Actor, error) + GetActor(actorType coreTypes.ActorType, address []byte, height int64) (*coreTypes.Actor, error) // App Queries GetAllApps(height int64) ([]*coreTypes.Actor, error) diff --git a/utility/module_test.go b/utility/module_test.go index 58c25512f..1b2573bf9 100644 --- a/utility/module_test.go +++ b/utility/module_test.go @@ -45,7 +45,12 @@ func TestMain(m *testing.M) { } testPersistenceMod = newTestPersistenceModule(bus) + testPersistenceMod.Start() + defer testPersistenceMod.Stop() + testUtilityMod = newTestUtilityModule(bus) + testUtilityMod.Start() + defer testUtilityMod.Stop() exitCode := m.Run() test_artifacts.CleanupPostgresDocker(m, pool, resource) diff --git a/utility/session.go b/utility/session.go index d7876ae15..61a336937 100644 --- a/utility/session.go +++ b/utility/session.go @@ -4,9 +4,12 @@ package utility // and need to be revisited before any implementation commences. import ( + "encoding/binary" + "encoding/hex" "fmt" coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/utility/types" ) @@ -38,12 +41,15 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain core session: session, readCtx: readCtx, } + if err := sessionHydrator.hydrateSessionApplication(appAddr); err != nil { + return nil, err + } - if err := sessionHydrator.hydrateSessionId(); err != nil { + if err := sessionHydrator.validateApplicationDispatch(); err != nil { return nil, err } - if err := sessionHydrator.hydrateSessionApplication(); err != nil { + if err := sessionHydrator.hydrateSessionId(); err != nil { return nil, err } @@ -65,24 +71,41 @@ func getSessionHeight(readCtx modules.PersistenceReadContext, blockHeight int64) // use the seed information to determine a SHA3Hash that is used to find the closest N actors based // by comparing the sessionKey with the actors' public key func (s *sessionHydrator) hydrateSessionId() error { - s.readCtx.GetAppExists() - // sessionIdBz := make([]byte, 8) - // binary.LittleEndian.PutUint64(sessionHeightBz, uint64(s.height)) - - // blockHashBz := []byte("get block hash bytes at s.sessionHeight from persistence module") - - // appPubKey, err := crypto.NewPublicKey(s.application.GetPublicKey()) - // if err != nil { - // return nil, types.ErrNewPublicKeyFromBytes(err) - // } - - // return concat(sessionHeightBz, blockHashBz, []byte(s.geoZone), []byte(s.relayChain), appPubKey.Bytes()), nil + sessionHeightBz := make([]byte, 8) + binary.LittleEndian.PutUint64(sessionHeightBz, uint64(s.session.Height)) + prevHash, err := s.readCtx.GetBlockHash(s.session.Height - 1) + if err != nil { + return err + } + prevHashBz, err := hex.DecodeString(prevHash) + appPubKeyBz := []byte(s.session.Application.PublicKey) + relayChainBz := []byte(string(s.session.RelayChain)) + geoZoneBz := []byte(s.session.GeoZone) + idBz := concat(sessionHeightBz, prevHashBz, geoZoneBz, relayChainBz, appPubKeyBz) + s.session.Id = crypto.GetHashStringFromBytes(idBz) return nil } -func (s *sessionHydrator) hydrateSessionApplication() error { - // IMPORTANT: This function is for behaviour illustrative purposes only and implementation may differ. - return nil +// Uses the current 'world state' to determine the full application metadata based on its address at the current height +func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { + // TECHDEBT: We can remove this decoding process once we use `strings` instead of `[]byte` for addresses + addr, err := hex.DecodeString(appAddr) + if err != nil { + return err + } + s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.Height) + return err +} + +// Validate the the application can dispatch a session at the request geo-zone and for the request relay chain +func (s *sessionHydrator) validateApplicationDispatch() error { + // TECHDEBT: We can remove this decoding process once we use `strings` instead of `[]byte` for addresses + addr, err := hex.DecodeString(s.session.Application.Address) + if err != nil { + return err + } + s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.Height) + return err } // uses the current 'world state' to determine the servicers in the session @@ -164,9 +187,9 @@ func (s *sessionHydrator) pseudoRandomSelection(candidates []*coreTypes.Actor, n return candidates[:numTarget] } -// func concat(b ...[]byte) (result []byte) { -// for _, bz := range b { -// result = append(result, bz...) -// } -// return result -// } +func concat(b ...[]byte) (result []byte) { + for _, bz := range b { + result = append(result, bz...) + } + return result +} diff --git a/utility/session_test.go b/utility/session_test.go index a99a1ca69..ad7dbd538 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -1,19 +1,31 @@ package utility import ( + "encoding/hex" + "fmt" "testing" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/stretchr/testify/require" ) +// TECH_DEBT_IDENTIFIED_IN_THIS_COMMIT: +// 1. Replace []byte with string +// 2. Remove height from Write context in persistence +// 3. Need to add geozone to actors +// 4. Need to generalize persitence functions based on actor type +// 5. Need different protos for each actor + func TestSession_NewSession(t *testing.T) { - session, err := testUtilityMod.GetSession("app", 1, coreTypes.RelayChain_ETHEREUM, "geo") + session, err := testUtilityMod.GetSession(hex.EncodeToString([]byte("app")), 1, coreTypes.RelayChain_ETHEREUM, "geo") require.NoError(t, err) + fmt.Println(session) - require.Equal(t, session.Application.Address, "app") + // require.Equal(t, session.Application.Address, "app") } +// validate application dispatch + // Not enough servicers // What if someone paused mid session? From 363f4ac4388e67e401922006b5bc142cac7c303a Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 11 Apr 2023 21:19:12 -0700 Subject: [PATCH 03/46] Snapshot before changing how we manage chains --- runtime/test_artifacts/generator.go | 11 ++++-- utility/module_test.go | 49 ++++++++++-------------- utility/session.go | 7 ++-- utility/session_test.go | 59 +++++++++++++++++++++++++++-- 4 files changed, 86 insertions(+), 40 deletions(-) diff --git a/runtime/test_artifacts/generator.go b/runtime/test_artifacts/generator.go index f396e5db9..3d5572753 100644 --- a/runtime/test_artifacts/generator.go +++ b/runtime/test_artifacts/generator.go @@ -16,17 +16,21 @@ import ( // IMPROVE: Generate a proper genesis suite in the future. func NewGenesisState(numValidators, numServicers, numApplications, numFisherman int) (genesisState *genesis.GenesisState, validatorPrivateKeys []string) { - apps, appsPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_APP, numApplications) + apps, appPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_APP, numApplications) vals, validatorPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_VAL, numValidators) - servicers, snPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicers) + servicers, servicerPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicers) fish, fishPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFisherman) + numActors := numValidators + numServicers + numApplications + numFisherman + allActorsKeys := append(append(append(validatorPrivateKeys, servicerPrivateKeys...), fishPrivateKeys...), appPrivateKeys...) + allActorAccounts := NewAccounts(numActors, allActorsKeys...) + genesisState = &genesis.GenesisState{ GenesisTime: timestamppb.Now(), ChainId: DefaultChainID, MaxBlockBytes: DefaultMaxBlockBytes, Pools: NewPools(), - Accounts: NewAccounts(numValidators+numServicers+numApplications+numFisherman, append(append(append(validatorPrivateKeys, snPrivateKeys...), fishPrivateKeys...), appsPrivateKeys...)...), // TODO(olshansky): clean this up + Accounts: allActorAccounts, Applications: apps, Validators: vals, Servicers: servicers, @@ -35,6 +39,7 @@ func NewGenesisState(numValidators, numServicers, numApplications, numFisherman } // TODO: Generalize this to all actors and not just validators + // TECHDEBT: Not using the private keys of other actors return genesisState, validatorPrivateKeys } diff --git a/utility/module_test.go b/utility/module_test.go index 1b2573bf9..49f742815 100644 --- a/utility/module_test.go +++ b/utility/module_test.go @@ -23,34 +23,16 @@ const ( ) var ( - testUtilityMod modules.UtilityModule - - // DISCUSS_IN_THIS_COMMIT(#261): We used to think that utility shouldn't have any dependencies on persistence, but - // mocking everything in `persistence_module` seems complex when all we want is just a configuration/state using a certain genesis - // config or runtime environment. - testPersistenceMod modules.PersistenceModule + dbURL string + // testUtilityMod modules.UtilityModule + // NB: Note that the utility module has a direct dependence on the implementation of the persistence + // module in unit tests. This is not ideal but makes development much more efficient. + // testPersistenceMod modules.PersistenceModule ) -// func NewTestingMempool(_ *testing.T) mempool.TXMempool { -// return utilTypes.NewTxFIFOMempool(1000000, 1000) -// } - func TestMain(m *testing.M) { - pool, resource, dbURL := test_artifacts.SetupPostgresDocker() - - runtimeCfg := newTestRuntimeConfig(dbURL) - bus, err := runtime.CreateBus(runtimeCfg) - if err != nil { - log.Fatalf("Error creating bus: %s", err) - } - - testPersistenceMod = newTestPersistenceModule(bus) - testPersistenceMod.Start() - defer testPersistenceMod.Stop() - - testUtilityMod = newTestUtilityModule(bus) - testUtilityMod.Start() - defer testUtilityMod.Stop() + pool, resource, url := test_artifacts.SetupPostgresDocker() + dbURL = url exitCode := m.Run() test_artifacts.CleanupPostgresDocker(m, pool, resource) @@ -108,7 +90,14 @@ func newTestPersistenceModule(bus modules.Bus) modules.PersistenceModule { return persistenceMod.(modules.PersistenceModule) } -func newTestRuntimeConfig(databaseURL string) *runtime.Manager { +// REFACTOR: This should be in a shared testing package +func newTestRuntimeConfig( + databaseURL string, + numValidators int, + numServicers int, + numApplications int, + numFisherman int, +) *runtime.Manager { cfg := &configs.Config{ Utility: &configs.UtilityConfig{ MaxMempoolTransactionBytes: 1000000, @@ -128,10 +117,10 @@ func newTestRuntimeConfig(databaseURL string) *runtime.Manager { }, } genesisState, _ := test_artifacts.NewGenesisState( - testingValidatorCount, - testingServicerCount, - testingApplicationCount, - testingFishermenCount, + numValidators, + numServicers, + numApplications, + numFisherman, ) runtimeCfg := runtime.NewManager(cfg, genesisState) return runtimeCfg diff --git a/utility/session.go b/utility/session.go index 61a336937..53fe6e945 100644 --- a/utility/session.go +++ b/utility/session.go @@ -134,7 +134,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { candidateServicers := make([]*coreTypes.Actor, 0) for _, servicer := range servicers { // Sanity check the servicer is not paused or unstaking - if servicer.PausedHeight == -1 || servicer.UnstakingHeight == -1 { + if !(servicer.PausedHeight == -1 && servicer.UnstakingHeight == -1) { return fmt.Errorf("selectSessionServicers should not have encountered a paused or unstaking servicer: %s", servicer.Address) } @@ -180,8 +180,9 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { // // or it would be subject to lexicographical proximity bias attacks func (s *sessionHydrator) pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int) []*coreTypes.Actor { - if numTarget < len(candidates) { - s.logger.Warn().Msgf("pseudoRandomSelection: numTarget (%d) is less than the number of candidates (%d)", numTarget, len(candidates)) + if numTarget > len(candidates) { + s.logger.Warn().Msgf("pseudoRandomSelection: numTarget (%d) is greater than the number of candidates (%d)", numTarget, len(candidates)) + return candidates } // TODO_IN_THIS_COMMIT: Actually implement this return candidates[:numTarget] diff --git a/utility/session_test.go b/utility/session_test.go index ad7dbd538..8bad85a66 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -1,10 +1,10 @@ package utility import ( - "encoding/hex" - "fmt" "testing" + "github.com/pokt-network/pocket/runtime" + "github.com/pokt-network/pocket/runtime/test_artifacts/keygen" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/stretchr/testify/require" ) @@ -17,13 +17,64 @@ import ( // 5. Need different protos for each actor func TestSession_NewSession(t *testing.T) { - session, err := testUtilityMod.GetSession(hex.EncodeToString([]byte("app")), 1, coreTypes.RelayChain_ETHEREUM, "geo") + teardownDeterministicKeygen := keygen.GetInstance().SetSeed(42) + defer teardownDeterministicKeygen() + + runtimeCfg := newTestRuntimeConfig(dbURL, 5, 1, 1, 1) + bus, err := runtime.CreateBus(runtimeCfg) + require.NoError(t, err) + + testPersistenceMod := newTestPersistenceModule(bus) + testPersistenceMod.Start() + defer testPersistenceMod.Stop() + + testUtilityMod := newTestUtilityModule(bus) + testUtilityMod.Start() + defer testUtilityMod.Stop() + + /// The actual tests + + // Loop over these + app := runtimeCfg.GetGenesis().Applications[0] + height := int64(1) + relayChain := coreTypes.RelayChain_ETHEREUM + geoZone := "geo" + + session, err := testUtilityMod.GetSession(app.Address, height, relayChain, geoZone) require.NoError(t, err) - fmt.Println(session) + require.Equal(t, "61bf17f4c2b7b381095b1be393d58412e863f18497e8a4308bfbff356df25971", session.Id) + require.Equal(t, height, session.Height) + require.Equal(t, relayChain, session.RelayChain) + require.Equal(t, geoZone, session.GeoZone) + require.Equal(t, session.Application.Address, app.Address) + require.Equal(t, "servicer", session.Servicers[0].Address) + require.Equal(t, "fisherman", session.Fishermen[0].Address) // require.Equal(t, session.Application.Address, "app") } +func TestSession_SessionHeight(t *testing.T) { + + // BlocksPerSessionParamName = 4 + // blockHeigh = 4 + // % 4 = 0 + // % 4 = prevSessionHeight + // % 4 = nextSessionHeight + + // What if session height changes mid session + // + + // testUtilityMod := newTestUtilityModule(bus) + // testUtilityMod.Start() + // defer testUtilityMod.Stop() + + // BlocksPerSessionParamName +} + +// not enough servicers to choose from + +// no fisherman available + // validate application dispatch // Not enough servicers From fc0f1f59417d3746c6c044307d80e76387490676 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 12 Apr 2023 12:07:03 -0700 Subject: [PATCH 04/46] Added fisherman_per_session_owner --- build/localnet/manifests/configs.yaml | 2 + persistence/test/setup_test.go | 4 +- persistence/types/gov_test.go | 2 + runtime/genesis/proto/genesis.proto | 183 +++++++++++++------------- runtime/test_artifacts/defaults.go | 4 +- utility/session.go | 56 +++++++- utility/session_test.go | 2 +- utility/types/gov.go | 2 + 8 files changed, 158 insertions(+), 97 deletions(-) diff --git a/build/localnet/manifests/configs.yaml b/build/localnet/manifests/configs.yaml index 418e2399d..5ba5ba3ca 100644 --- a/build/localnet/manifests/configs.yaml +++ b/build/localnet/manifests/configs.yaml @@ -4201,6 +4201,7 @@ data: "fisherman_unstaking_blocks": 2016, "fisherman_minimum_pause_blocks": 4, "fisherman_max_pause_blocks": 672, + "fisherman_per_session": 1, "validator_minimum_stake": "15000000000", "validator_unstaking_blocks": 2016, "validator_minimum_pause_blocks": 4, @@ -4255,6 +4256,7 @@ data: "fisherman_unstaking_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "fisherman_minimum_pause_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "fisherman_max_paused_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", + "fisherman_per_session_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "validator_minimum_stake_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "validator_unstaking_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "validator_minimum_pause_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", diff --git a/persistence/test/setup_test.go b/persistence/test/setup_test.go index f9e4f5f19..79a01c3c6 100644 --- a/persistence/test/setup_test.go +++ b/persistence/test/setup_test.go @@ -41,8 +41,8 @@ var ( StakeToUpdate = utils.BigIntToString((&big.Int{}).Add(DefaultStakeBig, DefaultDeltaBig)) DefaultStakeStatus = int32(coreTypes.StakeStatus_Staked) - DefaultPauseHeight = int64(-1) // pauseHeight=-1 means not paused - DefaultUnstakingHeight = int64(-1) // pauseHeight=-1 means not unstaking + DefaultPauseHeight = int64(-1) // pauseHeight=-1 implies not paused + DefaultUnstakingHeight = int64(-1) // unstakingHeight=-1 implies not unstaking OlshanskyURL = "https://olshansky.info" OlshanskyChains = []string{"OLSH"} diff --git a/persistence/types/gov_test.go b/persistence/types/gov_test.go index c8d8f4919..d644a0ecc 100644 --- a/persistence/types/gov_test.go +++ b/persistence/types/gov_test.go @@ -41,6 +41,7 @@ func TestInsertParams(t *testing.T) { "('fisherman_unstaking_blocks', -1, 'BIGINT', 2016)," + "('fisherman_minimum_pause_blocks', -1, 'SMALLINT', 4)," + "('fisherman_max_pause_blocks', -1, 'SMALLINT', 672)," + + "('fisherman_per_session', -1, 'SMALLINT', 1)," + "('validator_minimum_stake', -1, 'STRING', '15000000000')," + "('validator_unstaking_blocks', -1, 'BIGINT', 2016)," + "('validator_minimum_pause_blocks', -1, 'SMALLINT', 4)," + @@ -95,6 +96,7 @@ func TestInsertParams(t *testing.T) { "('fisherman_unstaking_blocks_owner', -1, 'STRING', 'da034209758b78eaea06dd99c07909ab54c99b45')," + "('fisherman_minimum_pause_blocks_owner', -1, 'STRING', 'da034209758b78eaea06dd99c07909ab54c99b45')," + "('fisherman_max_paused_blocks_owner', -1, 'STRING', 'da034209758b78eaea06dd99c07909ab54c99b45')," + + "('fisherman_per_session_owner', -1, 'STRING', 'da034209758b78eaea06dd99c07909ab54c99b45')," + "('validator_minimum_stake_owner', -1, 'STRING', 'da034209758b78eaea06dd99c07909ab54c99b45')," + "('validator_unstaking_blocks_owner', -1, 'STRING', 'da034209758b78eaea06dd99c07909ab54c99b45')," + "('validator_minimum_pause_blocks_owner', -1, 'STRING', 'da034209758b78eaea06dd99c07909ab54c99b45')," + diff --git a/runtime/genesis/proto/genesis.proto b/runtime/genesis/proto/genesis.proto index 8c9e32a53..d20764c39 100644 --- a/runtime/genesis/proto/genesis.proto +++ b/runtime/genesis/proto/genesis.proto @@ -23,6 +23,7 @@ message GenesisState { // TECHDEBT: Explore a more general purpose "feature flag" approach that makes it easy to add/remove // parameters and add activation heights for them as well. +// n message Params { //@gotags: pokt:"val_type=BIGINT,owner=blocks_per_session_owner" int32 blocks_per_session = 1; @@ -61,186 +62,190 @@ message Params { int32 fisherman_minimum_pause_blocks = 17; //@gotags: pokt:"val_type=SMALLINT,owner=fisherman_max_paused_blocks_owner" int32 fisherman_max_pause_blocks = 18; + //@gotags: pokt:"val_type=SMALLINT,owner=fisherman_per_session_owner" + int32 fisherman_per_session = 19; //@gotags: pokt:"val_type=STRING,owner=validator_minimum_stake_owner" - string validator_minimum_stake = 19; + string validator_minimum_stake = 20; //@gotags: pokt:"val_type=BIGINT,owner=validator_unstaking_blocks_owner" - int32 validator_unstaking_blocks = 20; + int32 validator_unstaking_blocks = 21; //@gotags: pokt:"val_type=SMALLINT,owner=validator_minimum_pause_blocks_owner" - int32 validator_minimum_pause_blocks = 21; + int32 validator_minimum_pause_blocks = 22; //@gotags: pokt:"val_type=SMALLINT,owner=validator_max_paused_blocks_owner" - int32 validator_max_pause_blocks = 22; + int32 validator_max_pause_blocks = 23; //@gotags: pokt:"val_type=SMALLINT,owner=validator_maximum_missed_blocks_owner" - int32 validator_maximum_missed_blocks = 23; + int32 validator_maximum_missed_blocks = 24; //@gotags: pokt:"val_type=SMALLINT,owner=validator_max_evidence_age_in_blocks_owner" - int32 validator_max_evidence_age_in_blocks = 24; + int32 validator_max_evidence_age_in_blocks = 25; //@gotags: pokt:"val_type=SMALLINT,owner=proposer_percentage_of_fees_owner" - int32 proposer_percentage_of_fees = 25; + int32 proposer_percentage_of_fees = 26; //@gotags: pokt:"val_type=SMALLINT,owner=missed_blocks_burn_percentage_owner" - int32 missed_blocks_burn_percentage = 26; + int32 missed_blocks_burn_percentage = 27; //@gotags: pokt:"val_type=SMALLINT,owner=double_sign_burn_percentage_owner" - int32 double_sign_burn_percentage = 27; + int32 double_sign_burn_percentage = 28; //@gotags: pokt:"val_type=STRING,owner=message_double_sign_fee_owner" - string message_double_sign_fee = 28; + string message_double_sign_fee = 29; //@gotags: pokt:"val_type=STRING,owner=message_send_fee_owner" - string message_send_fee = 29; + string message_send_fee = 30; //@gotags: pokt:"val_type=STRING,owner=message_stake_fisherman_fee_owner" - string message_stake_fisherman_fee = 30; + string message_stake_fisherman_fee = 31; //@gotags: pokt:"val_type=STRING,owner=message_edit_stake_fisherman_fee_owner" - string message_edit_stake_fisherman_fee = 31; + string message_edit_stake_fisherman_fee = 32; //@gotags: pokt:"val_type=STRING,owner=message_unstake_fisherman_fee_owner" - string message_unstake_fisherman_fee = 32; + string message_unstake_fisherman_fee = 33; //@gotags: pokt:"val_type=STRING,owner=message_pause_fisherman_fee_owner" - string message_pause_fisherman_fee = 33; + string message_pause_fisherman_fee = 34; //@gotags: pokt:"val_type=STRING,owner=message_unpause_fisherman_fee_owner" - string message_unpause_fisherman_fee = 34; + string message_unpause_fisherman_fee = 35; //@gotags: pokt:"val_type=STRING,owner=message_fisherman_pause_servicer_fee_owner" - string message_fisherman_pause_servicer_fee = 35; + string message_fisherman_pause_servicer_fee = 36; //@gotags: pokt:"val_type=STRING,owner=message_test_score_fee_owner" - string message_test_score_fee = 36; + string message_test_score_fee = 37; //@gotags: pokt:"val_type=STRING,owner=message_prove_test_score_fee_owner" - string message_prove_test_score_fee = 37; + string message_prove_test_score_fee = 38; //@gotags: pokt:"val_type=STRING,owner=message_stake_app_fee_owner" - string message_stake_app_fee = 38; + string message_stake_app_fee = 39; //@gotags: pokt:"val_type=STRING,owner=message_edit_stake_app_fee_owner" - string message_edit_stake_app_fee = 39; + string message_edit_stake_app_fee = 40; //@gotags: pokt:"val_type=STRING,owner=message_unstake_app_fee_owner" - string message_unstake_app_fee = 40; + string message_unstake_app_fee = 41; //@gotags: pokt:"val_type=STRING,owner=message_pause_app_fee_owner" - string message_pause_app_fee = 41; + string message_pause_app_fee = 42; //@gotags: pokt:"val_type=STRING,owner=message_unpause_app_fee_owner" - string message_unpause_app_fee = 42; + string message_unpause_app_fee = 43; //@gotags: pokt:"val_type=STRING,owner=message_stake_validator_fee_owner" - string message_stake_validator_fee = 43; + string message_stake_validator_fee = 44; //@gotags: pokt:"val_type=STRING,owner=message_edit_stake_validator_fee_owner" - string message_edit_stake_validator_fee = 44; + string message_edit_stake_validator_fee = 45; //@gotags: pokt:"val_type=STRING,owner=message_unstake_validator_fee_owner" - string message_unstake_validator_fee = 45; + string message_unstake_validator_fee = 46; //@gotags: pokt:"val_type=STRING,owner=message_pause_validator_fee_owner" - string message_pause_validator_fee = 46; + string message_pause_validator_fee = 47; //@gotags: pokt:"val_type=STRING,owner=message_unpause_validator_fee_owner" - string message_unpause_validator_fee = 47; + string message_unpause_validator_fee = 48; //@gotags: pokt:"val_type=STRING,owner=message_stake_servicer_fee_owner" - string message_stake_servicer_fee = 48; + string message_stake_servicer_fee = 49; //@gotags: pokt:"val_type=STRING,owner=message_edit_stake_servicer_fee_owner" - string message_edit_stake_servicer_fee = 49; + string message_edit_stake_servicer_fee = 50; //@gotags: pokt:"val_type=STRING,owner=message_unstake_servicer_fee_owner" - string message_unstake_servicer_fee = 50; + string message_unstake_servicer_fee = 51; //@gotags: pokt:"val_type=STRING,owner=message_pause_servicer_fee_owner" - string message_pause_servicer_fee = 51; + string message_pause_servicer_fee = 52; //@gotags: pokt:"val_type=STRING,owner=message_unpause_servicer_fee_owner" - string message_unpause_servicer_fee = 52; + string message_unpause_servicer_fee = 53; //@gotags: pokt:"val_type=STRING,owner=message_change_parameter_fee_owner" - string message_change_parameter_fee = 53; + string message_change_parameter_fee = 54; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string acl_owner = 54; + string acl_owner = 55; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string blocks_per_session_owner = 55; + string blocks_per_session_owner = 56; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_minimum_stake_owner = 56; + string app_minimum_stake_owner = 57; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_max_chains_owner = 57; + string app_max_chains_owner = 58; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_session_tokens_multiplier_owner = 58; + string app_session_tokens_multiplier_owner = 59; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_unstaking_blocks_owner = 59; + string app_unstaking_blocks_owner = 60; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_minimum_pause_blocks_owner = 60; + string app_minimum_pause_blocks_owner = 61; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_max_paused_blocks_owner = 61; + string app_max_paused_blocks_owner = 62; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_minimum_stake_owner = 62; + string servicer_minimum_stake_owner = 63; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_max_chains_owner = 63; + string servicer_max_chains_owner = 64; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_unstaking_blocks_owner = 64; + string servicer_unstaking_blocks_owner = 65; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_minimum_pause_blocks_owner = 65; + string servicer_minimum_pause_blocks_owner = 66; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_max_paused_blocks_owner = 66; + string servicer_max_paused_blocks_owner = 67; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicers_per_session_owner = 67; + string servicers_per_session_owner = 68; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_minimum_stake_owner = 68; + string fisherman_minimum_stake_owner = 69; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_max_chains_owner = 69; + string fisherman_max_chains_owner = 70; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_unstaking_blocks_owner = 70; + string fisherman_unstaking_blocks_owner = 71; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_minimum_pause_blocks_owner = 71; + string fisherman_minimum_pause_blocks_owner = 72; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_max_paused_blocks_owner = 72; + string fisherman_max_paused_blocks_owner = 73; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_minimum_stake_owner = 73; + string fisherman_per_session_owner = 74; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_unstaking_blocks_owner = 74; + string validator_minimum_stake_owner = 75; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_minimum_pause_blocks_owner = 75; + string validator_unstaking_blocks_owner = 76; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_max_paused_blocks_owner = 76; + string validator_minimum_pause_blocks_owner = 77; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_maximum_missed_blocks_owner = 77; + string validator_max_paused_blocks_owner = 78; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_max_evidence_age_in_blocks_owner = 78; + string validator_maximum_missed_blocks_owner = 79; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string proposer_percentage_of_fees_owner = 79; + string validator_max_evidence_age_in_blocks_owner = 80; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string missed_blocks_burn_percentage_owner = 80; + string proposer_percentage_of_fees_owner = 81; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string double_sign_burn_percentage_owner = 81; + string missed_blocks_burn_percentage_owner = 82; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_double_sign_fee_owner = 82; + string double_sign_burn_percentage_owner = 83; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_send_fee_owner = 83; + string message_double_sign_fee_owner = 84; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_stake_fisherman_fee_owner = 84; + string message_send_fee_owner = 85; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_edit_stake_fisherman_fee_owner = 85; + string message_stake_fisherman_fee_owner = 86; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unstake_fisherman_fee_owner = 86; + string message_edit_stake_fisherman_fee_owner = 87; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_pause_fisherman_fee_owner = 87; + string message_unstake_fisherman_fee_owner = 88; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unpause_fisherman_fee_owner = 88; + string message_pause_fisherman_fee_owner = 89; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_fisherman_pause_servicer_fee_owner = 89; + string message_unpause_fisherman_fee_owner = 90; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_test_score_fee_owner = 90; + string message_fisherman_pause_servicer_fee_owner = 91; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_prove_test_score_fee_owner = 91; + string message_test_score_fee_owner = 92; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_stake_app_fee_owner = 92; + string message_prove_test_score_fee_owner = 93; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_edit_stake_app_fee_owner = 93; + string message_stake_app_fee_owner = 94; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unstake_app_fee_owner = 94; + string message_edit_stake_app_fee_owner = 95; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_pause_app_fee_owner = 95; + string message_unstake_app_fee_owner = 96; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unpause_app_fee_owner = 96; + string message_pause_app_fee_owner = 97; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_stake_validator_fee_owner = 97; + string message_unpause_app_fee_owner = 98; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_edit_stake_validator_fee_owner = 98; + string message_stake_validator_fee_owner = 99; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unstake_validator_fee_owner = 99; + string message_edit_stake_validator_fee_owner = 100; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_pause_validator_fee_owner = 100; + string message_unstake_validator_fee_owner = 101; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unpause_validator_fee_owner = 101; + string message_pause_validator_fee_owner = 102; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_stake_servicer_fee_owner = 102; + string message_unpause_validator_fee_owner = 103; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_edit_stake_servicer_fee_owner = 103; + string message_stake_servicer_fee_owner = 104; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unstake_servicer_fee_owner = 104; + string message_edit_stake_servicer_fee_owner = 105; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_pause_servicer_fee_owner = 105; + string message_unstake_servicer_fee_owner = 106; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unpause_servicer_fee_owner = 106; + string message_pause_servicer_fee_owner = 107; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_change_parameter_fee_owner = 107; + string message_unpause_servicer_fee_owner = 108; + //@gotags: pokt:"val_type=STRING,owner=acl_owner" + string message_change_parameter_fee_owner = 109; } diff --git a/runtime/test_artifacts/defaults.go b/runtime/test_artifacts/defaults.go index 64258dd04..9427f66e8 100644 --- a/runtime/test_artifacts/defaults.go +++ b/runtime/test_artifacts/defaults.go @@ -13,8 +13,8 @@ var ( DefaultStakeAmountString = utils.BigIntToString(DefaultStakeAmount) DefaultAccountAmount = big.NewInt(100000000000000) DefaultAccountAmountString = utils.BigIntToString(DefaultAccountAmount) - DefaultPauseHeight = int64(-1) - DefaultUnstakingHeight = int64(-1) + DefaultPauseHeight = int64(-1) // pauseHeight=-1 implies not paused + DefaultUnstakingHeight = int64(-1) // unstakingHeight=-1 implies not unstaking DefaultChainID = "testnet" ServiceURLFormat = "node%d.consensus:42069" DefaultMaxBlockBytes = uint64(4000000) diff --git a/utility/session.go b/utility/session.go index 53fe6e945..af2d6af85 100644 --- a/utility/session.go +++ b/utility/session.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "strconv" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" @@ -135,7 +136,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { for _, servicer := range servicers { // Sanity check the servicer is not paused or unstaking if !(servicer.PausedHeight == -1 && servicer.UnstakingHeight == -1) { - return fmt.Errorf("selectSessionServicers should not have encountered a paused or unstaking servicer: %s", servicer.Address) + return fmt.Errorf("hydrateSessionServicers should not have encountered a paused or unstaking servicer: %s", servicer.Address) } // TODO_IN_THIS_COMMIT: if servicer.GeoZone includes session.GeoZone @@ -144,7 +145,13 @@ func (s *sessionHydrator) hydrateSessionServicers() error { var chain string for _, chain = range servicer.Chains { // TODO_IN_THIS_COMMIT: Change actor chains to use the enum - if chain != string(s.session.RelayChain) { + + // quick hack + chainNum, _ := strconv.Atoi(chain) + enumNum := int(s.session.RelayChain) + // fmt.Println("OLSH", i) + + if chainNum != enumNum { chain = "" continue } @@ -166,7 +173,50 @@ func (s *sessionHydrator) hydrateSessionServicers() error { // // 2) calls `pseudoRandomSelection(fishermen, numberOfFishPerSession)` func (s *sessionHydrator) hydrateSessionFishermen() error { - // IMPORTANT: This function is for behaviour illustrative purposes only and implementation may differ. + // number of fisherman per session at this height + numFishermen, err := s.readCtx.GetIntParam(types.FishermanPerSessionParamName, s.session.Height) + if err != nil { + return err + } + + // returns all the staked fisherman at this session height + fishermen, err := s.readCtx.GetAllFishermen(s.session.Height) + if err != nil { + return err + } + + // OPTIMIZE: Update the persistence module to allow for querying for filtered fishermen directly + // Determine the fishermen for this session + candidateFishermen := make([]*coreTypes.Actor, 0) + for _, fisherman := range fishermen { + // Sanity check the fisherman is not paused or unstaking + if !(fisherman.PausedHeight == -1 && fisherman.UnstakingHeight == -1) { + return fmt.Errorf("hydrateSessionFishermen should not have encountered a paused or unstaking fisherman: %s", fisherman.Address) + } + + // TODO_IN_THIS_COMMIT: if sfisherman.GeoZone includes session.GeoZone + + // OPTIMIZE: If this was a map, we could have avoided the loop over chains + var chain string + for _, chain = range fisherman.Chains { + // TODO_IN_THIS_COMMIT: Change actor chains to use the enum + + // quick hack + chainNum, _ := strconv.Atoi(chain) + enumNum := int(s.session.RelayChain) + // fmt.Println("OLSH", i) + + if chainNum != enumNum { + chain = "" + continue + } + } + if chain != "" { + candidateFishermen = append(candidateFishermen, fisherman) + } + } + + s.session.Fishermen = s.pseudoRandomSelection(candidateFishermen, numFishermen) return nil } diff --git a/utility/session_test.go b/utility/session_test.go index 8bad85a66..2f014fb37 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -47,7 +47,7 @@ func TestSession_NewSession(t *testing.T) { require.Equal(t, relayChain, session.RelayChain) require.Equal(t, geoZone, session.GeoZone) require.Equal(t, session.Application.Address, app.Address) - require.Equal(t, "servicer", session.Servicers[0].Address) + require.Equal(t, "c7832263600476fd6ff4c5cb0a86080d0e5f48b2", session.Servicers[0].Address) require.Equal(t, "fisherman", session.Fishermen[0].Address) // require.Equal(t, session.Application.Address, "app") diff --git a/utility/types/gov.go b/utility/types/gov.go index 33cce6aff..52501f95f 100644 --- a/utility/types/gov.go +++ b/utility/types/gov.go @@ -33,6 +33,7 @@ const ( FishermanUnstakingBlocksParamName = "fisherman_unstaking_blocks" FishermanMinimumPauseBlocksParamName = "fisherman_minimum_pause_blocks" FishermanMaxPauseBlocksParamName = "fisherman_max_pause_blocks" + FishermanPerSessionParamName = "fisherman_per_session" // Validator actor gov params ValidatorMinimumStakeParamName = "validator_minimum_stake" @@ -113,6 +114,7 @@ const ( FishermanUnstakingBlocksOwner = "fisherman_unstaking_blocks_owner" FishermanMinimumPauseBlocksOwner = "fisherman_minimum_pause_blocks_owner" FishermanMaxPausedBlocksOwner = "fisherman_max_paused_blocks_owner" + FishermanPerSessionOwner = "fisherman_per_session_owner" ValidatorMinimumStakeOwner = "validator_minimum_stake_owner" ValidatorUnstakingBlocksOwner = "validator_unstaking_blocks_owner" From 74f11bea2e35459d51ba3b09a1a8f17f5e6141fd Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 12 Apr 2023 18:10:37 -0700 Subject: [PATCH 05/46] All unit tests pasing again --- build/config/genesis.json | 2 + build/config/genesis_localhost.json | 2 + persistence/test/state_test.go | 6 +- runtime/test_artifacts/gov.go | 2 + utility/module_test.go | 90 +++++++++++------------------ utility/session.go | 14 ++++- utility/session_test.go | 26 ++------- utility/unit_of_work/gov.go | 1 + 8 files changed, 61 insertions(+), 82 deletions(-) diff --git a/build/config/genesis.json b/build/config/genesis.json index c979bb34f..329723e42 100755 --- a/build/config/genesis.json +++ b/build/config/genesis.json @@ -4138,6 +4138,7 @@ "fisherman_unstaking_blocks": 2016, "fisherman_minimum_pause_blocks": 4, "fisherman_max_pause_blocks": 672, + "fisherman_per_session": 1, "validator_minimum_stake": "15000000000", "validator_unstaking_blocks": 2016, "validator_minimum_pause_blocks": 4, @@ -4192,6 +4193,7 @@ "fisherman_unstaking_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "fisherman_minimum_pause_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "fisherman_max_paused_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", + "fisherman_per_session_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "validator_minimum_stake_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "validator_unstaking_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "validator_minimum_pause_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", diff --git a/build/config/genesis_localhost.json b/build/config/genesis_localhost.json index a12ea7360..47d3fde0b 100755 --- a/build/config/genesis_localhost.json +++ b/build/config/genesis_localhost.json @@ -159,6 +159,7 @@ "fisherman_unstaking_blocks": 2016, "fisherman_minimum_pause_blocks": 4, "fisherman_max_pause_blocks": 672, + "fisherman_per_session": 1, "validator_minimum_stake": "15000000000", "validator_unstaking_blocks": 2016, "validator_minimum_pause_blocks": 4, @@ -214,6 +215,7 @@ "fisherman_unstaking_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "fisherman_minimum_pause_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "fisherman_max_paused_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", + "fisherman_per_session_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "validator_minimum_stake_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "validator_unstaking_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", "validator_minimum_pause_blocks_owner": "da034209758b78eaea06dd99c07909ab54c99b45", diff --git a/persistence/test/state_test.go b/persistence/test/state_test.go index e2170660f..850f6fa93 100644 --- a/persistence/test/state_test.go +++ b/persistence/test/state_test.go @@ -46,9 +46,9 @@ func TestStateHash_DeterministicStateWhenUpdatingAppStake(t *testing.T) { // logic changes, these hashes will need to be updated based on the test output. // TODO: Add an explicit updateSnapshots flag to the test to make this more clear. stateHashes := []string{ - "3a9a33c4d6b106f656c859296cd5ac16b608980d1d921f6de77051a707f48cb5", - "ec3d62106f1ca61dfe9c1d80a16c4ee3923550db5175389777bd1ecd9d50136e", - "e440914fba03bbb5ff4fbb73f760e4e75c3635263bbf7c15ca649870ee865222", + "93be62191cbc09fea4cf2046bea8e55ccf5e4c6fa7b483a55470d1bca85c3732", + "c2ed842d32b099de26dba1fa6d14f367504daf6b914076004aa3df5f7858e5be", + "a60fef8fe1b3c5b208041f43cb14641167eeb67faee730ae7d0689765bb9d487", } stakeAmount := initialStakeAmount diff --git a/runtime/test_artifacts/gov.go b/runtime/test_artifacts/gov.go index 1f836bbbb..5faa63f6e 100644 --- a/runtime/test_artifacts/gov.go +++ b/runtime/test_artifacts/gov.go @@ -33,6 +33,7 @@ func DefaultParams() *genesis.Params { FishermanUnstakingBlocks: 2016, FishermanMinimumPauseBlocks: 4, FishermanMaxPauseBlocks: 672, + FishermanPerSession: 1, ValidatorMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), ValidatorUnstakingBlocks: 2016, ValidatorMinimumPauseBlocks: 4, @@ -87,6 +88,7 @@ func DefaultParams() *genesis.Params { FishermanUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), FishermanMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), FishermanMaxPausedBlocksOwner: DefaultParamsOwner.Address().String(), + FishermanPerSessionOwner: DefaultParamsOwner.Address().String(), ValidatorMinimumStakeOwner: DefaultParamsOwner.Address().String(), ValidatorUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), ValidatorMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), diff --git a/utility/module_test.go b/utility/module_test.go index 49f742815..ee2d15ac6 100644 --- a/utility/module_test.go +++ b/utility/module_test.go @@ -9,25 +9,13 @@ import ( "github.com/pokt-network/pocket/runtime" "github.com/pokt-network/pocket/runtime/configs" "github.com/pokt-network/pocket/runtime/test_artifacts" + "github.com/pokt-network/pocket/runtime/test_artifacts/keygen" "github.com/pokt-network/pocket/shared/modules" -) - -const ( - testingValidatorCount = 5 - testingServicerCount = 1 - testingApplicationCount = 1 - testingFishermenCount = 1 - - testNonce = "defaultNonceString" - testSchema = "test_schema" + "github.com/stretchr/testify/require" ) var ( dbURL string - // testUtilityMod modules.UtilityModule - // NB: Note that the utility module has a direct dependence on the implementation of the persistence - // module in unit tests. This is not ideal but makes development much more efficient. - // testPersistenceMod modules.PersistenceModule ) func TestMain(m *testing.M) { @@ -39,41 +27,6 @@ func TestMain(m *testing.M) { os.Exit(exitCode) } -// func newTestingUtilityUnitOfWork(t *testing.T, height int64, options ...func(*baseUtilityUnitOfWork)) *baseUtilityUnitOfWork { -// rwCtx, err := testPersistenceMod.NewRWContext(height) -// require.NoError(t, err) - -// // TECHDEBT: Move the internal of cleanup into a separate function and call this in the -// // beginning of every test. This (the current implementation) is an issue because if we call -// // `NewTestingUtilityContext` more than once in a single test, we create unnecessary calls to clean. -// t.Cleanup(func() { -// err := testPersistenceMod.ReleaseWriteContext() -// require.NoError(t, err) -// err = testPersistenceMod.HandleDebugMessage(&messaging.DebugMessage{ -// Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, -// Message: nil, -// }) -// require.NoError(t, err) -// // TODO: May need to run `bus.GetUtilityModule().GetMempool().Clear()` here -// }) - -// uow := &baseUtilityUnitOfWork{ -// logger: logger.Global.CreateLoggerForModule(modules.UtilityModuleName), -// height: height, -// // TODO(@deblasis): Refactor this -// persistenceRWContext: rwCtx, -// persistenceReadContext: rwCtx, -// } - -// uow.SetBus(testPersistenceMod.GetBus()) - -// for _, option := range options { -// option(uow) -// } - -// return uow -// } - func newTestUtilityModule(bus modules.Bus) modules.UtilityModule { utilityMod, err := Create(bus) if err != nil { @@ -90,12 +43,39 @@ func newTestPersistenceModule(bus modules.Bus) modules.PersistenceModule { return persistenceMod.(modules.PersistenceModule) } +func prepareEnvironment( + t *testing.T, + numValidators, + numServicers, + numApplications, + numFisherman int, +) (*runtime.Manager, modules.UtilityModule, modules.PersistenceModule) { + teardownDeterministicKeygen := keygen.GetInstance().SetSeed(42) + + runtimeCfg := newTestRuntimeConfig(numValidators, numServicers, numApplications, numFisherman) + bus, err := runtime.CreateBus(runtimeCfg) + require.NoError(t, err) + + testPersistenceMod := newTestPersistenceModule(bus) + testPersistenceMod.Start() + + testUtilityMod := newTestUtilityModule(bus) + testUtilityMod.Start() + + t.Cleanup(func() { + teardownDeterministicKeygen() + testPersistenceMod.Stop() + testUtilityMod.Stop() + }) + + return runtimeCfg, testUtilityMod, testPersistenceMod +} + // REFACTOR: This should be in a shared testing package func newTestRuntimeConfig( - databaseURL string, - numValidators int, - numServicers int, - numApplications int, + numValidators, + numServicers, + numApplications, numFisherman int, ) *runtime.Manager { cfg := &configs.Config{ @@ -104,8 +84,8 @@ func newTestRuntimeConfig( MaxMempoolTransactions: 1000, }, Persistence: &configs.PersistenceConfig{ - PostgresUrl: databaseURL, - NodeSchema: testSchema, + PostgresUrl: dbURL, + NodeSchema: "test_schema", BlockStorePath: "", // in memory TxIndexerPath: "", // in memory TreesStoreDir: "", // in memory diff --git a/utility/session.go b/utility/session.go index af2d6af85..64e1583db 100644 --- a/utility/session.go +++ b/utility/session.go @@ -65,8 +65,18 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain core return sessionHydrator.session, nil } -func getSessionHeight(readCtx modules.PersistenceReadContext, blockHeight int64) (int64, error) { - return blockHeight, nil +// getSessionHeight returns the height at which the session started given the current block height +func (s *sessionHydrator) getSessionHeight(readCtx modules.PersistenceReadContext, blockHeight int64) (int64, error) { + numBlocksPerSession, err := readCtx.GetIntParam(types.BlocksPerSessionParamName, blockHeight) + if err != nil { + return 0, err + } + + numBlocksAheadOfSession := blockHeight % int64(numBlocksPerSession) + if numBlocksAheadOfSession == 0 { + return blockHeight, nil + } + return (blockHeight - numBlocksAheadOfSession), nil } // use the seed information to determine a SHA3Hash that is used to find the closest N actors based diff --git a/utility/session_test.go b/utility/session_test.go index 2f014fb37..61a6875bc 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -3,8 +3,6 @@ package utility import ( "testing" - "github.com/pokt-network/pocket/runtime" - "github.com/pokt-network/pocket/runtime/test_artifacts/keygen" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/stretchr/testify/require" ) @@ -17,22 +15,7 @@ import ( // 5. Need different protos for each actor func TestSession_NewSession(t *testing.T) { - teardownDeterministicKeygen := keygen.GetInstance().SetSeed(42) - defer teardownDeterministicKeygen() - - runtimeCfg := newTestRuntimeConfig(dbURL, 5, 1, 1, 1) - bus, err := runtime.CreateBus(runtimeCfg) - require.NoError(t, err) - - testPersistenceMod := newTestPersistenceModule(bus) - testPersistenceMod.Start() - defer testPersistenceMod.Stop() - - testUtilityMod := newTestUtilityModule(bus) - testUtilityMod.Start() - defer testUtilityMod.Stop() - - /// The actual tests + runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, 1, 1, 1) // Loop over these app := runtimeCfg.GetGenesis().Applications[0] @@ -40,20 +23,19 @@ func TestSession_NewSession(t *testing.T) { relayChain := coreTypes.RelayChain_ETHEREUM geoZone := "geo" - session, err := testUtilityMod.GetSession(app.Address, height, relayChain, geoZone) + session, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) require.NoError(t, err) - require.Equal(t, "61bf17f4c2b7b381095b1be393d58412e863f18497e8a4308bfbff356df25971", session.Id) + require.Equal(t, "8b50d1f751029a06d0b860e3b900163b3c6532fc48df2e11f848600019df5483", session.Id) require.Equal(t, height, session.Height) require.Equal(t, relayChain, session.RelayChain) require.Equal(t, geoZone, session.GeoZone) require.Equal(t, session.Application.Address, app.Address) require.Equal(t, "c7832263600476fd6ff4c5cb0a86080d0e5f48b2", session.Servicers[0].Address) require.Equal(t, "fisherman", session.Fishermen[0].Address) - - // require.Equal(t, session.Application.Address, "app") } func TestSession_SessionHeight(t *testing.T) { + // for i := 0; i < 100; i++ { // BlocksPerSessionParamName = 4 // blockHeigh = 4 diff --git a/utility/unit_of_work/gov.go b/utility/unit_of_work/gov.go index 4eabd14b1..36ce03b31 100644 --- a/utility/unit_of_work/gov.go +++ b/utility/unit_of_work/gov.go @@ -65,6 +65,7 @@ func prepareGovParamParamTypesMap() map[string]int { typesUtil.FishermanUnstakingBlocksParamName: INT64, typesUtil.FishermanMinimumPauseBlocksParamName: INT, typesUtil.FishermanMaxPauseBlocksParamName: INT, + typesUtil.FishermanPerSessionParamName: INT, typesUtil.MessageDoubleSignFee: BIGINT, typesUtil.MessageSendFee: BIGINT, typesUtil.MessageStakeFishermanFee: BIGINT, From 4f4cbbc6df3ac16e0d964bb2d5cb5ebe1af8e245 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 12 Apr 2023 18:43:36 -0700 Subject: [PATCH 06/46] Added TestSession_SessionHeight --- utility/session.go | 12 +++--- utility/session_test.go | 82 ++++++++++++++++++++++++++++++++--------- 2 files changed, 71 insertions(+), 23 deletions(-) diff --git a/utility/session.go b/utility/session.go index 64e1583db..7d6c83e13 100644 --- a/utility/session.go +++ b/utility/session.go @@ -42,6 +42,7 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain core session: session, readCtx: readCtx, } + if err := sessionHydrator.hydrateSessionApplication(appAddr); err != nil { return nil, err } @@ -66,17 +67,19 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain core } // getSessionHeight returns the height at which the session started given the current block height -func (s *sessionHydrator) getSessionHeight(readCtx modules.PersistenceReadContext, blockHeight int64) (int64, error) { +func getSessionHeight(readCtx modules.PersistenceReadContext, blockHeight int64) (int64, int64, error) { numBlocksPerSession, err := readCtx.GetIntParam(types.BlocksPerSessionParamName, blockHeight) if err != nil { - return 0, err + return 0, 0, err } numBlocksAheadOfSession := blockHeight % int64(numBlocksPerSession) + sessionNumber := int64(blockHeight / int64(numBlocksPerSession)) + fmt.Println("OLSH", blockHeight, int64(numBlocksPerSession), numBlocksAheadOfSession, 4%5) if numBlocksAheadOfSession == 0 { - return blockHeight, nil + return blockHeight, sessionNumber, nil } - return (blockHeight - numBlocksAheadOfSession), nil + return (blockHeight - numBlocksAheadOfSession), sessionNumber, nil } // use the seed information to determine a SHA3Hash that is used to find the closest N actors based @@ -132,7 +135,6 @@ func (s *sessionHydrator) hydrateSessionServicers() error { if err != nil { return err } - // s.session.Servicers = make([]*coreTypes.Actor, numServicers) // returns all the staked servicers at this session height servicers, err := s.readCtx.GetAllServicers(s.session.Height) diff --git a/utility/session_test.go b/utility/session_test.go index 61a6875bc..a8c11ded8 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -4,6 +4,7 @@ import ( "testing" coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/utility/types" "github.com/stretchr/testify/require" ) @@ -15,14 +16,14 @@ import ( // 5. Need different protos for each actor func TestSession_NewSession(t *testing.T) { - runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, 1, 1, 1) - - // Loop over these - app := runtimeCfg.GetGenesis().Applications[0] height := int64(1) relayChain := coreTypes.RelayChain_ETHEREUM geoZone := "geo" + runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, 1, 1, 1) + require.Len(t, runtimeCfg.GetGenesis().Applications, 1) + app := runtimeCfg.GetGenesis().Applications[0] + session, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) require.NoError(t, err) require.Equal(t, "8b50d1f751029a06d0b860e3b900163b3c6532fc48df2e11f848600019df5483", session.Id) @@ -31,26 +32,71 @@ func TestSession_NewSession(t *testing.T) { require.Equal(t, geoZone, session.GeoZone) require.Equal(t, session.Application.Address, app.Address) require.Equal(t, "c7832263600476fd6ff4c5cb0a86080d0e5f48b2", session.Servicers[0].Address) - require.Equal(t, "fisherman", session.Fishermen[0].Address) + require.Equal(t, "a6e7b6810df8120580f2a81710e228f454f99c97", session.Fishermen[0].Address) } func TestSession_SessionHeight(t *testing.T) { - // for i := 0; i < 100; i++ { - - // BlocksPerSessionParamName = 4 - // blockHeigh = 4 - // % 4 = 0 - // % 4 = prevSessionHeight - // % 4 = nextSessionHeight + _, _, persistenceMod := prepareEnvironment(t, 5, 1, 1, 1) - // What if session height changes mid session - // + readCtx, err := persistenceMod.NewReadContext(-1) + require.NoError(t, err) + defer readCtx.Release() - // testUtilityMod := newTestUtilityModule(bus) - // testUtilityMod.Start() - // defer testUtilityMod.Stop() + writeCtx, err := persistenceMod.NewRWContext(0) + require.NoError(t, err) + defer writeCtx.Release() + + tests := []struct { + name string + numBlocksPerSession int64 + haveBlockHeight int64 + wantSessionHeight int64 + wantSessionNumber int64 + }{ + { + name: "block is at start of first session", + numBlocksPerSession: 5, + haveBlockHeight: 5, + wantSessionHeight: 5, + wantSessionNumber: 1, + }, + { + name: "block is right before start of first session", + numBlocksPerSession: 5, + haveBlockHeight: 4, + wantSessionHeight: 0, + wantSessionNumber: 0, + }, + { + name: "block is right after start of first session", + numBlocksPerSession: 5, + haveBlockHeight: 6, + wantSessionHeight: 5, + wantSessionNumber: 1, + }, + { + name: "block is at start of second session", + numBlocksPerSession: 5, + haveBlockHeight: 10, + wantSessionHeight: 10, + wantSessionNumber: 2, + }, + // TODO: Different blocks per session + // What if we change the num blocks -> gets complex + // -> Need to enforce waiting until the end of the current sessions + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + writeCtx.SetParam(types.BlocksPerSessionParamName, tt.numBlocksPerSession) + // require.NoError(t, writeCtx.Commit([]byte(""), []byte(""))) + sessionHeight, sessionNumber, err := getSessionHeight(writeCtx, tt.haveBlockHeight) + require.NoError(t, err) + require.Equal(t, tt.wantSessionHeight, sessionHeight) + require.Equal(t, tt.wantSessionNumber, sessionNumber) + }) + } - // BlocksPerSessionParamName } // not enough servicers to choose from From 0bf2d75e06194a2bd499f05e205176d5c3c7d413 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Thu, 13 Apr 2023 16:57:38 -0700 Subject: [PATCH 07/46] Remove read ctx --- utility/session_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/utility/session_test.go b/utility/session_test.go index a8c11ded8..40b4e9ae8 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -38,10 +38,6 @@ func TestSession_NewSession(t *testing.T) { func TestSession_SessionHeight(t *testing.T) { _, _, persistenceMod := prepareEnvironment(t, 5, 1, 1, 1) - readCtx, err := persistenceMod.NewReadContext(-1) - require.NoError(t, err) - defer readCtx.Release() - writeCtx, err := persistenceMod.NewRWContext(0) require.NoError(t, err) defer writeCtx.Release() @@ -96,7 +92,6 @@ func TestSession_SessionHeight(t *testing.T) { require.Equal(t, tt.wantSessionNumber, sessionNumber) }) } - } // not enough servicers to choose from From 6c04122f2c94b63b0bb2f58ce92daabebb4d5185 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 14 Apr 2023 14:54:13 -0700 Subject: [PATCH 08/46] Refactor some tests --- shared/core/types/proto/session.proto | 9 +- utility/session.go | 63 +++++++----- utility/session_test.go | 135 +++++++++++++++++++------- 3 files changed, 141 insertions(+), 66 deletions(-) diff --git a/shared/core/types/proto/session.proto b/shared/core/types/proto/session.proto index dfae27a52..30593a578 100644 --- a/shared/core/types/proto/session.proto +++ b/shared/core/types/proto/session.proto @@ -6,18 +6,11 @@ option go_package = "github.com/pokt-network/pocket/shared/core/types"; import "actor.proto"; -// TECHDEBT: Do we need backwards with v0? https://docs.pokt.network/supported-blockchains/ -enum RelayChain { - UNSPECIFIED_RELAY_CHAIN = 0; - ETHEREUM = 1; - POLYGON = 2; - // TODO: Add all the other chains we need -} message Session { string id = 1; int64 height = 2; - RelayChain relay_chain = 3; // CONSIDERATION: Will we ever want to support more than one relay chain? + string relay_chain = 3; // CONSIDERATION: Should we add a `RelayChain` enum and use it across the board? // TECHDEBT: Do we need backwards with v0? https://docs.pokt.network/supported-blockchains/ string geo_zone = 4; core.Actor application = 5; repeated core.Actor servicers = 6; diff --git a/utility/session.go b/utility/session.go index 7d6c83e13..f08784e39 100644 --- a/utility/session.go +++ b/utility/session.go @@ -7,7 +7,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" - "strconv" + "math/rand" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" @@ -23,7 +23,7 @@ type sessionHydrator struct { readCtx modules.PersistenceReadContext } -func (m *utilityModule) GetSession(appAddr string, height int64, relayChain coreTypes.RelayChain, geoZone string) (*coreTypes.Session, error) { +func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geoZone string) (*coreTypes.Session, error) { persistenceModule := m.GetBus().GetPersistenceModule() readCtx, err := persistenceModule.NewReadContext(height) if err != nil { @@ -156,14 +156,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { // OPTIMIZE: If this was a map, we could have avoided the loop over chains var chain string for _, chain = range servicer.Chains { - // TODO_IN_THIS_COMMIT: Change actor chains to use the enum - - // quick hack - chainNum, _ := strconv.Atoi(chain) - enumNum := int(s.session.RelayChain) - // fmt.Println("OLSH", i) - - if chainNum != enumNum { + if chain != s.session.RelayChain { chain = "" continue } @@ -173,7 +166,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { } } - s.session.Servicers = s.pseudoRandomSelection(candidateServicers, numServicers) + s.session.Servicers = s.pseudoRandomSelection(candidateServicers, int64(numServicers)) return nil } @@ -211,14 +204,7 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { // OPTIMIZE: If this was a map, we could have avoided the loop over chains var chain string for _, chain = range fisherman.Chains { - // TODO_IN_THIS_COMMIT: Change actor chains to use the enum - - // quick hack - chainNum, _ := strconv.Atoi(chain) - enumNum := int(s.session.RelayChain) - // fmt.Println("OLSH", i) - - if chainNum != enumNum { + if chain != s.session.RelayChain { chain = "" continue } @@ -228,7 +214,7 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { } } - s.session.Fishermen = s.pseudoRandomSelection(candidateFishermen, numFishermen) + s.session.Fishermen = s.pseudoRandomSelection(candidateFishermen, int64(numFishermen)) return nil } @@ -241,13 +227,44 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { // A) pseudo-random selection only works if each iteration is re-randomized // // or it would be subject to lexicographical proximity bias attacks -func (s *sessionHydrator) pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int) []*coreTypes.Actor { +func (s *sessionHydrator) pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, sessionId []byte) []*coreTypes.Actor { + // If there aren't enough candidates, return all of them if numTarget > len(candidates) { s.logger.Warn().Msgf("pseudoRandomSelection: numTarget (%d) is greater than the number of candidates (%d)", numTarget, len(candidates)) return candidates } - // TODO_IN_THIS_COMMIT: Actually implement this - return candidates[:numTarget] + + // Take the first 8 bytes of sessionId to use as the seed + seed := int64(binary.BigEndian.Uint64(sessionId[:8])) + + // Retrieve the indices for the candidates + actors := make([]*coreTypes.Actor, 0) + uniqueIndices := uniqueRandomIndices(seed, int64(len(candidates)), int64(numTarget)) + for idx := range uniqueIndices { + actors = append(actors, candidates[idx]) + } + + return actors +} + +func uniqueRandomIndices(seed, maxIndex, numIndices int64) map[int64]struct{} { + // This should never happen + if numIndices > maxIndex { + panic(fmt.Sprintf("uniqueRandomIndices: numIndices (%d) is greater than maxIndex (%d)", numIndices, maxIndex)) + } + + // create a new random source with the seed + randSrc := rand.NewSource(seed) + + // initialize a map to capture the indicesMap we'll return + indicesMap := make(map[int64]struct{}, maxIndex) + + // The random source could potentially return duplicates, so while loop until we have enough unique indices + for int64(len(indicesMap)) < numIndices { + indicesMap[randSrc.Int63()%int64(maxIndex)] = struct{}{} + } + + return indicesMap } func concat(b ...[]byte) (result []byte) { diff --git a/utility/session_test.go b/utility/session_test.go index 40b4e9ae8..a5c7451a3 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -3,22 +3,14 @@ package utility import ( "testing" - coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/utility/types" "github.com/stretchr/testify/require" ) -// TECH_DEBT_IDENTIFIED_IN_THIS_COMMIT: -// 1. Replace []byte with string -// 2. Remove height from Write context in persistence -// 3. Need to add geozone to actors -// 4. Need to generalize persitence functions based on actor type -// 5. Need different protos for each actor - -func TestSession_NewSession(t *testing.T) { +func TestSession_NewSession_SimpleCase(t *testing.T) { height := int64(1) - relayChain := coreTypes.RelayChain_ETHEREUM - geoZone := "geo" + relayChain := "0001" + geoZone := "unused_geo" runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, 1, 1, 1) require.Len(t, runtimeCfg.GetGenesis().Applications, 1) @@ -35,7 +27,83 @@ func TestSession_NewSession(t *testing.T) { require.Equal(t, "a6e7b6810df8120580f2a81710e228f454f99c97", session.Fishermen[0].Address) } -func TestSession_SessionHeight(t *testing.T) { +// dispatching session in the future +// dispatching session in the past + +// not enough servicers to choose from +// no fisherman available +// validate application dispatch + +func TestSession_ServicersAndFishermanCount(t *testing.T) { + // Prepare an environment with lots of servicers and fisherman + _, _, persistenceMod := prepareEnvironment(t, 5, 100, 1, 100) + + writeCtx, err := persistenceMod.NewRWContext(0) + require.NoError(t, err) + defer writeCtx.Release() + + tests := []struct { + name string + numServicersPerSession int64 + numFishermanPerSession int64 + wantServicerCount int64 + wantFishermanCount int64 + }{ + { + name: "block is at start of first session", + numServicersPerSession: 5, + numFishermanPerSession: 5, + wantServicerCount: 5, + wantFishermanCount: 1, + }, + { + name: "block is right before start of first session", + numServicersPerSession: 5, + numFishermanPerSession: 4, + wantServicerCount: 0, + wantFishermanCount: 0, + }, + { + name: "block is right after start of first session", + numServicersPerSession: 5, + numFishermanPerSession: 6, + wantServicerCount: 5, + wantFishermanCount: 1, + }, + { + name: "block is at start of second session", + numServicersPerSession: 5, + numFishermanPerSession: 10, + wantServicerCount: 10, + wantFishermanCount: 2, + }, + // Not enough servicers in region + // Not enough fisherman in region + // Not enough servicers per chain + // Not enough fisherman per chain + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + writeCtx.SetParam(types.BlocksPerSessionParamName, tt.numBlocksPerSession) + // require.NoError(t, writeCtx.Commit([]byte(""), []byte(""))) + sessionHeight, sessionNumber, err := getSessionHeight(writeCtx, tt.haveBlockHeight) + require.NoError(t, err) + require.Equal(t, tt.wantSessionHeight, sessionHeight) + require.Equal(t, tt.wantSessionNumber, sessionNumber) + }) + } +} + +// generate session id + +func TestSession_ServicersAndFishermanRandomness(t *testing.T) { + // validate entropy and randomness + // different height + // different chain +} + +func TestSession_SessionHeightAndNumber_StaticBlocksPerSession(t *testing.T) { _, _, persistenceMod := prepareEnvironment(t, 5, 1, 1, 1) writeCtx, err := persistenceMod.NewRWContext(0) @@ -94,40 +162,37 @@ func TestSession_SessionHeight(t *testing.T) { } } -// not enough servicers to choose from +func TestSession_SessionHeightAndNumber_DynamicBlocksPerSession(t *testing.T) { -// no fisherman available - -// validate application dispatch +} -// Not enough servicers +func TestSession_MatchNewSession(t *testing.T) { +} -// What if someone paused mid session? +func TestSession_RelayChainVariability(t *testing.T) { + // invalid relay chain + // valid relay chain -// stake a new servicer -> do I get them? +} -// Invalid application +// Potential: Changing num blocks per session must wait until current session ends -> easily fixes things +// New servicers / fisherman -> need to wait until current session ends -> easily fixes things -// Not enough servicers in region +func TestSession_ActorReplacement(t *testing.T) { + // What if a servicers/fisherman paused mid session? + // -> Need to replace them -// No fisherman available + // What if a new servicers/fisherman staked mid session? + // -> They could potentially get selected +} -// Check session block height +func TestSession_InvalidApplication(t *testing.T) { + // TODO: What if the application pauses mid session? + // TODO: What if the application has no stake? +} // Configurable number of geo zones per session // above max // below max // at max - -// invalid relay chain -// valid relay chain // application is not staked for relay chain - -// dispatching session in the future -// dispatching session in the past - -// generate session id -// validate entropy and randomness - -func TestSession_MatchNewSession(t *testing.T) { -} From bfceded031ab42ef3e8f31d02ae3925a52f989fb Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 14 Apr 2023 20:14:06 -0700 Subject: [PATCH 09/46] Added a test validating num of servicers and fisherman is correct --- persistence/debug.go | 2 + shared/core/types/proto/session.proto | 14 +++- shared/modules/utility_module.go | 3 +- utility/session.go | 36 ++++----- utility/session_test.go | 105 +++++++++++++++++--------- 5 files changed, 99 insertions(+), 61 deletions(-) diff --git a/persistence/debug.go b/persistence/debug.go index 4c91439d9..a11594716 100644 --- a/persistence/debug.go +++ b/persistence/debug.go @@ -2,6 +2,7 @@ package persistence import ( "crypto/sha256" + "fmt" "runtime/debug" "github.com/pokt-network/pocket/persistence/types" @@ -22,6 +23,7 @@ var nonActorClearFunctions = []func() string{ } func (m *persistenceModule) HandleDebugMessage(debugMessage *messaging.DebugMessage) error { + fmt.Println("HERE") switch debugMessage.Action { case messaging.DebugMessageAction_DEBUG_SHOW_LATEST_BLOCK_IN_STORE: m.showLatestBlockInStore(debugMessage) diff --git a/shared/core/types/proto/session.proto b/shared/core/types/proto/session.proto index 30593a578..1f43af305 100644 --- a/shared/core/types/proto/session.proto +++ b/shared/core/types/proto/session.proto @@ -6,7 +6,6 @@ option go_package = "github.com/pokt-network/pocket/shared/core/types"; import "actor.proto"; - message Session { string id = 1; int64 height = 2; @@ -15,4 +14,15 @@ message Session { core.Actor application = 5; repeated core.Actor servicers = 6; repeated core.Actor fishermen = 7; -} \ No newline at end of file +} + +// TECH_DEBT_IDENTIFIED_IN_THIS_COMMIT: +// 1. Replace []byte with string +// 2. Remove height from Write context in persistence +// 3. Need to add geozone to actors +// 4. Need to generalize persitence functions based on actor type +// 5. Need different protos for each actor +// OPTIMIZE: Query the postgres DB directly to retrieve the hydrated actors +// - requires new endpoints and such +// 6. How to define geozone (uber h3, postgis, aws regions, etc...)? +// 7. How to define relay chains (enum, string, backwards compatability, etc...)? \ No newline at end of file diff --git a/shared/modules/utility_module.go b/shared/modules/utility_module.go index 47a4d234c..dc34252cb 100644 --- a/shared/modules/utility_module.go +++ b/shared/modules/utility_module.go @@ -13,7 +13,6 @@ const ( ) // TECHDEBT: Replace []byte with semantic type (addresses, transactions, etc...) - type UtilityModule interface { Module @@ -33,7 +32,7 @@ type UtilityModule interface { // Return a pseudo-random session object for the given application address, session height, relay chain and geo zones // using on-chain data as he entropy source. - GetSession(appAddr string, sessionHeight int64, relayChain coreTypes.RelayChain, geoZone string) (*coreTypes.Session, error) + GetSession(appAddr string, sessionHeight int64, relayChain string, geoZone string) (*coreTypes.Session, error) } // TECHDEBT: Remove this interface from `shared/modules` and use the `Actor` protobuf type instead diff --git a/utility/session.go b/utility/session.go index f08784e39..20431a1b3 100644 --- a/utility/session.go +++ b/utility/session.go @@ -1,14 +1,12 @@ package utility -// IMPORTANT: The interface and implementation defined in this file are for illustrative purposes only -// and need to be revisited before any implementation commences. - import ( "encoding/binary" "encoding/hex" "fmt" "math/rand" + "github.com/pokt-network/pocket/logger" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/modules" @@ -18,8 +16,15 @@ import ( // TODO: When implementing please review if block height tolerance (+,-1) is included in the session protocol: pokt-network/pocket-core#1464 CC @Olshansk type sessionHydrator struct { - logger modules.Logger + logger modules.Logger + + // the session being hydrated and returned session *coreTypes.Session + + // A helper that keeps a hex decoded copy of `session.Id` + sessionIdBz []byte + + // Caches the read context to avoid opening too many during session hydration readCtx modules.PersistenceReadContext } @@ -75,7 +80,6 @@ func getSessionHeight(readCtx modules.PersistenceReadContext, blockHeight int64) numBlocksAheadOfSession := blockHeight % int64(numBlocksPerSession) sessionNumber := int64(blockHeight / int64(numBlocksPerSession)) - fmt.Println("OLSH", blockHeight, int64(numBlocksPerSession), numBlocksAheadOfSession, 4%5) if numBlocksAheadOfSession == 0 { return blockHeight, sessionNumber, nil } @@ -95,8 +99,8 @@ func (s *sessionHydrator) hydrateSessionId() error { appPubKeyBz := []byte(s.session.Application.PublicKey) relayChainBz := []byte(string(s.session.RelayChain)) geoZoneBz := []byte(s.session.GeoZone) - idBz := concat(sessionHeightBz, prevHashBz, geoZoneBz, relayChainBz, appPubKeyBz) - s.session.Id = crypto.GetHashStringFromBytes(idBz) + s.sessionIdBz = concat(sessionHeightBz, prevHashBz, geoZoneBz, relayChainBz, appPubKeyBz) + s.session.Id = crypto.GetHashStringFromBytes(s.sessionIdBz) return nil } @@ -166,7 +170,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { } } - s.session.Servicers = s.pseudoRandomSelection(candidateServicers, int64(numServicers)) + s.session.Servicers = pseudoRandomSelection(candidateServicers, numServicers, s.sessionIdBz) return nil } @@ -214,23 +218,15 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { } } - s.session.Fishermen = s.pseudoRandomSelection(candidateFishermen, int64(numFishermen)) + s.session.Fishermen = pseudoRandomSelection(candidateFishermen, numFishermen, s.sessionIdBz) return nil } -// 1) passed an ordered list of the public keys of actors and number of nodes -// 2) pseudo-insert the session `key` string into the list and find the first actor directly below -// 3) newKey = Hash( key + actor1PublicKey ) -// 4) repeat steps 2 and 3 until all N actor are found -// FAQ: -// Q) why do we hash to find a newKey between every actor selection? -// A) pseudo-random selection only works if each iteration is re-randomized -// -// or it would be subject to lexicographical proximity bias attacks -func (s *sessionHydrator) pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, sessionId []byte) []*coreTypes.Actor { +// TODO_IN_THIS_COMMIT: Deterministic randomness algorithm +func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, sessionId []byte) []*coreTypes.Actor { // If there aren't enough candidates, return all of them if numTarget > len(candidates) { - s.logger.Warn().Msgf("pseudoRandomSelection: numTarget (%d) is greater than the number of candidates (%d)", numTarget, len(candidates)) + logger.Global.Warn().Msgf("pseudoRandomSelection: numTarget (%d) is greater than the number of candidates (%d)", numTarget, len(candidates)) return candidates } diff --git a/utility/session_test.go b/utility/session_test.go index a5c7451a3..bc8cde97f 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -3,6 +3,7 @@ package utility import ( "testing" + "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/utility/types" "github.com/stretchr/testify/require" ) @@ -18,7 +19,7 @@ func TestSession_NewSession_SimpleCase(t *testing.T) { session, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) require.NoError(t, err) - require.Equal(t, "8b50d1f751029a06d0b860e3b900163b3c6532fc48df2e11f848600019df5483", session.Id) + require.Equal(t, "3545185ff1519bf7706ec8f828d16525830d3c0dcc2425c40db597ee6b67b8bc", session.Id) require.Equal(t, height, session.Height) require.Equal(t, relayChain, session.RelayChain) require.Equal(t, geoZone, session.GeoZone) @@ -34,48 +35,59 @@ func TestSession_NewSession_SimpleCase(t *testing.T) { // no fisherman available // validate application dispatch +// invalid app +// unstaked app +// non-existent app + +// invalid chain +// unused chain +// non-existent chain + +// invalid geozone +// unused geozone +// non-existent geozone + func TestSession_ServicersAndFishermanCount(t *testing.T) { + numServicers := 100 + numFishermen := 100 // Prepare an environment with lots of servicers and fisherman - _, _, persistenceMod := prepareEnvironment(t, 5, 100, 1, 100) + runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, 1, numFishermen) - writeCtx, err := persistenceMod.NewRWContext(0) - require.NoError(t, err) - defer writeCtx.Release() + app := runtimeCfg.GetGenesis().Applications[0] + + // height := int64(1) + relayChain := "0001" + geoZone := "unused_geo" + + // defer writeCtx.Release() tests := []struct { name string numServicersPerSession int64 numFishermanPerSession int64 - wantServicerCount int64 - wantFishermanCount int64 + wantServicerCount int + wantFishermanCount int }{ { - name: "block is at start of first session", - numServicersPerSession: 5, - numFishermanPerSession: 5, - wantServicerCount: 5, - wantFishermanCount: 1, - }, - { - name: "block is right before start of first session", - numServicersPerSession: 5, - numFishermanPerSession: 4, - wantServicerCount: 0, - wantFishermanCount: 0, + name: "not enough actors to choose from (> 100)", + numServicersPerSession: int64(numServicers) * 10, + numFishermanPerSession: int64(numFishermen) * 10, + wantServicerCount: numServicers, + wantFishermanCount: numFishermen, }, { - name: "block is right after start of first session", - numServicersPerSession: 5, - numFishermanPerSession: 6, - wantServicerCount: 5, - wantFishermanCount: 1, + name: "too many actors to choose from (< 100)", + numServicersPerSession: int64(numServicers) / 2, + numFishermanPerSession: int64(numFishermen) / 2, + wantServicerCount: numServicers / 2, + wantFishermanCount: numFishermen / 2, }, { - name: "block is at start of second session", - numServicersPerSession: 5, - numFishermanPerSession: 10, - wantServicerCount: 10, - wantFishermanCount: 2, + name: "same number of servicers and fisherman", + numServicersPerSession: int64(numServicers), + numFishermanPerSession: int64(numFishermen), + wantServicerCount: numServicers, + wantFishermanCount: numFishermen, }, // Not enough servicers in region // Not enough fisherman in region @@ -85,12 +97,29 @@ func TestSession_ServicersAndFishermanCount(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - writeCtx.SetParam(types.BlocksPerSessionParamName, tt.numBlocksPerSession) + err := persistenceMod.HandleDebugMessage(&messaging.DebugMessage{ + Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, + Message: nil, + }) + require.NoError(t, err) + writeCtx, err := persistenceMod.NewRWContext(1) + require.NoError(t, err) + defer writeCtx.Release() + + err = writeCtx.SetParam(types.ServicersPerSessionParamName, tt.numServicersPerSession) + require.NoError(t, err) + writeCtx.SetParam(types.FishermanPerSessionParamName, tt.numFishermanPerSession) + require.NoError(t, err) + + err = writeCtx.Commit([]byte(""), []byte("")) + require.NoError(t, err) + + session, err := utilityMod.GetSession(app.Address, 2, relayChain, geoZone) + // require.NoError(t, writeCtx.Commit([]byte(""), []byte(""))) - sessionHeight, sessionNumber, err := getSessionHeight(writeCtx, tt.haveBlockHeight) require.NoError(t, err) - require.Equal(t, tt.wantSessionHeight, sessionHeight) - require.Equal(t, tt.wantSessionNumber, sessionNumber) + require.Equal(t, tt.wantServicerCount, len(session.Servicers)) + require.Equal(t, tt.wantFishermanCount, len(session.Fishermen)) }) } } @@ -152,8 +181,10 @@ func TestSession_SessionHeightAndNumber_StaticBlocksPerSession(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - writeCtx.SetParam(types.BlocksPerSessionParamName, tt.numBlocksPerSession) + err := writeCtx.SetParam(types.BlocksPerSessionParamName, tt.numBlocksPerSession) + require.NoError(t, err) // require.NoError(t, writeCtx.Commit([]byte(""), []byte(""))) + sessionHeight, sessionNumber, err := getSessionHeight(writeCtx, tt.haveBlockHeight) require.NoError(t, err) require.Equal(t, tt.wantSessionHeight, sessionHeight) @@ -175,9 +206,6 @@ func TestSession_RelayChainVariability(t *testing.T) { } -// Potential: Changing num blocks per session must wait until current session ends -> easily fixes things -// New servicers / fisherman -> need to wait until current session ends -> easily fixes things - func TestSession_ActorReplacement(t *testing.T) { // What if a servicers/fisherman paused mid session? // -> Need to replace them @@ -191,6 +219,9 @@ func TestSession_InvalidApplication(t *testing.T) { // TODO: What if the application has no stake? } +// Potential: Changing num blocks per session must wait until current session ends -> easily fixes things +// New servicers / fisherman -> need to wait until current session ends -> easily fixes things + // Configurable number of geo zones per session // above max // below max From 2eb3c6280f6de22961c23793284351a5488a7f93 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 17 Apr 2023 16:12:30 -0700 Subject: [PATCH 10/46] Unit tests passing before adding more complex ones --- runtime/test_artifacts/defaults.go | 120 +++++++++++++++++ runtime/test_artifacts/generator.go | 122 +++++++++++------- runtime/test_artifacts/genesis.go | 11 -- runtime/test_artifacts/gov.go | 128 ------------------- shared/core/types/proto/actor.proto | 9 +- utility/session_test.go | 192 +++++++++++++++++++++------- 6 files changed, 349 insertions(+), 233 deletions(-) delete mode 100644 runtime/test_artifacts/genesis.go delete mode 100644 runtime/test_artifacts/gov.go diff --git a/runtime/test_artifacts/defaults.go b/runtime/test_artifacts/defaults.go index 9427f66e8..0aebae684 100644 --- a/runtime/test_artifacts/defaults.go +++ b/runtime/test_artifacts/defaults.go @@ -3,9 +3,14 @@ package test_artifacts import ( "math/big" + "github.com/pokt-network/pocket/runtime/genesis" + "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/utils" ) +// TECHDEBT: This entire file should be re-scoped. +// The test suite should be customizable but the default params are a good starting point. + var ( DefaultChains = []string{"0001"} DefaultServiceURL = "" @@ -18,4 +23,119 @@ var ( DefaultChainID = "testnet" ServiceURLFormat = "node%d.consensus:42069" DefaultMaxBlockBytes = uint64(4000000) + DefaultParamsOwner, _ = crypto.NewPrivateKey("ff538589deb7f28bbce1ba68b37d2efc0eaa03204b36513cf88422a875559e38d6cbe0430ddd85a5e48e0c99ef3dea47bf0d1a83c6e6ad1640f72201dc8a0120") ) + +func DefaultParams() *genesis.Params { + return &genesis.Params{ + BlocksPerSession: 4, + AppMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), + AppMaxChains: 15, + AppSessionTokensMultiplier: 100, + AppUnstakingBlocks: 2016, + AppMinimumPauseBlocks: 4, + AppMaxPauseBlocks: 672, + ServicerMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), + ServicerMaxChains: 15, + ServicerUnstakingBlocks: 2016, + ServicerMinimumPauseBlocks: 4, + ServicerMaxPauseBlocks: 672, + ServicersPerSession: 24, + FishermanMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), + FishermanMaxChains: 15, + FishermanUnstakingBlocks: 2016, + FishermanMinimumPauseBlocks: 4, + FishermanMaxPauseBlocks: 672, + FishermanPerSession: 1, + ValidatorMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), + ValidatorUnstakingBlocks: 2016, + ValidatorMinimumPauseBlocks: 4, + ValidatorMaxPauseBlocks: 672, + ValidatorMaximumMissedBlocks: 5, + ValidatorMaxEvidenceAgeInBlocks: 8, + ProposerPercentageOfFees: 10, + MissedBlocksBurnPercentage: 1, + DoubleSignBurnPercentage: 5, + MessageDoubleSignFee: utils.BigIntToString(big.NewInt(10000)), + MessageSendFee: utils.BigIntToString(big.NewInt(10000)), + MessageStakeFishermanFee: utils.BigIntToString(big.NewInt(10000)), + MessageEditStakeFishermanFee: utils.BigIntToString(big.NewInt(10000)), + MessageUnstakeFishermanFee: utils.BigIntToString(big.NewInt(10000)), + MessagePauseFishermanFee: utils.BigIntToString(big.NewInt(10000)), + MessageUnpauseFishermanFee: utils.BigIntToString(big.NewInt(10000)), + MessageFishermanPauseServicerFee: utils.BigIntToString(big.NewInt(10000)), + MessageTestScoreFee: utils.BigIntToString(big.NewInt(10000)), + MessageProveTestScoreFee: utils.BigIntToString(big.NewInt(10000)), + MessageStakeAppFee: utils.BigIntToString(big.NewInt(10000)), + MessageEditStakeAppFee: utils.BigIntToString(big.NewInt(10000)), + MessageUnstakeAppFee: utils.BigIntToString(big.NewInt(10000)), + MessagePauseAppFee: utils.BigIntToString(big.NewInt(10000)), + MessageUnpauseAppFee: utils.BigIntToString(big.NewInt(10000)), + MessageStakeValidatorFee: utils.BigIntToString(big.NewInt(10000)), + MessageEditStakeValidatorFee: utils.BigIntToString(big.NewInt(10000)), + MessageUnstakeValidatorFee: utils.BigIntToString(big.NewInt(10000)), + MessagePauseValidatorFee: utils.BigIntToString(big.NewInt(10000)), + MessageUnpauseValidatorFee: utils.BigIntToString(big.NewInt(10000)), + MessageStakeServicerFee: utils.BigIntToString(big.NewInt(10000)), + MessageEditStakeServicerFee: utils.BigIntToString(big.NewInt(10000)), + MessageUnstakeServicerFee: utils.BigIntToString(big.NewInt(10000)), + MessagePauseServicerFee: utils.BigIntToString(big.NewInt(10000)), + MessageUnpauseServicerFee: utils.BigIntToString(big.NewInt(10000)), + MessageChangeParameterFee: utils.BigIntToString(big.NewInt(10000)), + AclOwner: DefaultParamsOwner.Address().String(), + BlocksPerSessionOwner: DefaultParamsOwner.Address().String(), + AppMinimumStakeOwner: DefaultParamsOwner.Address().String(), + AppMaxChainsOwner: DefaultParamsOwner.Address().String(), + AppSessionTokensMultiplierOwner: DefaultParamsOwner.Address().String(), + AppUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), + AppMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), + AppMaxPausedBlocksOwner: DefaultParamsOwner.Address().String(), + ServicerMinimumStakeOwner: DefaultParamsOwner.Address().String(), + ServicerMaxChainsOwner: DefaultParamsOwner.Address().String(), + ServicerUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), + ServicerMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), + ServicerMaxPausedBlocksOwner: DefaultParamsOwner.Address().String(), + ServicersPerSessionOwner: DefaultParamsOwner.Address().String(), + FishermanMinimumStakeOwner: DefaultParamsOwner.Address().String(), + FishermanMaxChainsOwner: DefaultParamsOwner.Address().String(), + FishermanUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), + FishermanMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), + FishermanMaxPausedBlocksOwner: DefaultParamsOwner.Address().String(), + FishermanPerSessionOwner: DefaultParamsOwner.Address().String(), + ValidatorMinimumStakeOwner: DefaultParamsOwner.Address().String(), + ValidatorUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), + ValidatorMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), + ValidatorMaxPausedBlocksOwner: DefaultParamsOwner.Address().String(), + ValidatorMaximumMissedBlocksOwner: DefaultParamsOwner.Address().String(), + ValidatorMaxEvidenceAgeInBlocksOwner: DefaultParamsOwner.Address().String(), + ProposerPercentageOfFeesOwner: DefaultParamsOwner.Address().String(), + MissedBlocksBurnPercentageOwner: DefaultParamsOwner.Address().String(), + DoubleSignBurnPercentageOwner: DefaultParamsOwner.Address().String(), + MessageDoubleSignFeeOwner: DefaultParamsOwner.Address().String(), + MessageSendFeeOwner: DefaultParamsOwner.Address().String(), + MessageStakeFishermanFeeOwner: DefaultParamsOwner.Address().String(), + MessageEditStakeFishermanFeeOwner: DefaultParamsOwner.Address().String(), + MessageUnstakeFishermanFeeOwner: DefaultParamsOwner.Address().String(), + MessagePauseFishermanFeeOwner: DefaultParamsOwner.Address().String(), + MessageUnpauseFishermanFeeOwner: DefaultParamsOwner.Address().String(), + MessageFishermanPauseServicerFeeOwner: DefaultParamsOwner.Address().String(), + MessageTestScoreFeeOwner: DefaultParamsOwner.Address().String(), + MessageProveTestScoreFeeOwner: DefaultParamsOwner.Address().String(), + MessageStakeAppFeeOwner: DefaultParamsOwner.Address().String(), + MessageEditStakeAppFeeOwner: DefaultParamsOwner.Address().String(), + MessageUnstakeAppFeeOwner: DefaultParamsOwner.Address().String(), + MessagePauseAppFeeOwner: DefaultParamsOwner.Address().String(), + MessageUnpauseAppFeeOwner: DefaultParamsOwner.Address().String(), + MessageStakeValidatorFeeOwner: DefaultParamsOwner.Address().String(), + MessageEditStakeValidatorFeeOwner: DefaultParamsOwner.Address().String(), + MessageUnstakeValidatorFeeOwner: DefaultParamsOwner.Address().String(), + MessagePauseValidatorFeeOwner: DefaultParamsOwner.Address().String(), + MessageUnpauseValidatorFeeOwner: DefaultParamsOwner.Address().String(), + MessageStakeServicerFeeOwner: DefaultParamsOwner.Address().String(), + MessageEditStakeServicerFeeOwner: DefaultParamsOwner.Address().String(), + MessageUnstakeServicerFeeOwner: DefaultParamsOwner.Address().String(), + MessagePauseServicerFeeOwner: DefaultParamsOwner.Address().String(), + MessageUnpauseServicerFeeOwner: DefaultParamsOwner.Address().String(), + MessageChangeParameterFeeOwner: DefaultParamsOwner.Address().String(), + } +} diff --git a/runtime/test_artifacts/generator.go b/runtime/test_artifacts/generator.go index 3d5572753..aa359223a 100644 --- a/runtime/test_artifacts/generator.go +++ b/runtime/test_artifacts/generator.go @@ -9,21 +9,23 @@ import ( "github.com/pokt-network/pocket/runtime/configs" "github.com/pokt-network/pocket/runtime/genesis" "github.com/pokt-network/pocket/runtime/test_artifacts/keygen" + "github.com/pokt-network/pocket/shared/core/types" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" "google.golang.org/protobuf/types/known/timestamppb" ) +type GenesisOption func(*genesis.GenesisState) + // IMPROVE: Generate a proper genesis suite in the future. -func NewGenesisState(numValidators, numServicers, numApplications, numFisherman int) (genesisState *genesis.GenesisState, validatorPrivateKeys []string) { - apps, appPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_APP, numApplications) - vals, validatorPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_VAL, numValidators) - servicers, servicerPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicers) - fish, fishPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFisherman) +func NewGenesisState(numValidators, numServicers, numApplications, numFisherman int, genesisOpts ...GenesisOption) (genesisState *genesis.GenesisState, validatorPrivateKeys []string) { + applications, appPrivateKeys := newActors(coreTypes.ActorType_ACTOR_TYPE_APP, numApplications) + validators, validatorPrivateKeys := newActors(coreTypes.ActorType_ACTOR_TYPE_VAL, numValidators) + servicers, servicerPrivateKeys := newActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicers) + fishermen, fishPrivateKeys := newActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFisherman) - numActors := numValidators + numServicers + numApplications + numFisherman allActorsKeys := append(append(append(validatorPrivateKeys, servicerPrivateKeys...), fishPrivateKeys...), appPrivateKeys...) - allActorAccounts := NewAccounts(numActors, allActorsKeys...) + allActorAccounts := newAccountsWithKeys(allActorsKeys) genesisState = &genesis.GenesisState{ GenesisTime: timestamppb.Now(), @@ -31,88 +33,119 @@ func NewGenesisState(numValidators, numServicers, numApplications, numFisherman MaxBlockBytes: DefaultMaxBlockBytes, Pools: NewPools(), Accounts: allActorAccounts, - Applications: apps, - Validators: vals, + Applications: applications, + Validators: validators, Servicers: servicers, - Fishermen: fish, + Fishermen: fishermen, Params: DefaultParams(), } - // TODO: Generalize this to all actors and not just validators - // TECHDEBT: Not using the private keys of other actors + for _, o := range genesisOpts { + o(genesisState) + } + + // TECHDEBT: Generalize this to all actors and not just validators return genesisState, validatorPrivateKeys } +func WithActors(actors []*coreTypes.Actor, actorKeys []string) func(*genesis.GenesisState) { + return func(genesis *genesis.GenesisState) { + newActorAccounts := newAccountsWithKeys(actorKeys) + genesis.Accounts = append(genesis.Accounts, newActorAccounts...) + for _, actor := range actors { + switch actor.ActorType { + case types.ActorType_ACTOR_TYPE_APP: + genesis.Applications = append(genesis.Applications, actor) + case coreTypes.ActorType_ACTOR_TYPE_VAL: + genesis.Validators = append(genesis.Validators, actor) + case coreTypes.ActorType_ACTOR_TYPE_SERVICER: + genesis.Servicers = append(genesis.Servicers, actor) + case coreTypes.ActorType_ACTOR_TYPE_FISH: + genesis.Fishermen = append(genesis.Fishermen, actor) + default: + panic(fmt.Sprintf("invalid actor type: %s", actor.ActorType)) + } + } + } +} + func NewDefaultConfigs(privateKeys []string) (cfgs []*configs.Config) { for i, pk := range privateKeys { + postgresSchema := "node" + strconv.Itoa(i+1) cfgs = append(cfgs, configs.NewDefaultConfig( configs.WithPK(pk), - configs.WithNodeSchema("node"+strconv.Itoa(i+1)), + configs.WithNodeSchema(postgresSchema), )) } - return + return cfgs } -// REFACTOR: Test artifact generator should reflect the sum of the initial account values to populate the initial pool values func NewPools() (pools []*coreTypes.Account) { for _, value := range coreTypes.Pools_value { if value == int32(coreTypes.Pools_POOLS_UNSPECIFIED) { continue } + // TECHDEBT: Test artifact should reflect the sum of the initial account values + // rather than be set to `DefaultAccountAmountString` amount := DefaultAccountAmountString if value == int32(coreTypes.Pools_POOLS_FEE_COLLECTOR) { - amount = "0" + amount = "0" // fees are empty at genesis } + poolAddr := hex.EncodeToString(coreTypes.Pools(value).Address()) + pools = append(pools, &coreTypes.Account{ - Address: hex.EncodeToString(coreTypes.Pools(value).Address()), + Address: poolAddr, Amount: amount, }) } - return + return pools } -func NewAccounts(n int, privateKeys ...string) (accounts []*coreTypes.Account) { - for i := 0; i < n; i++ { +func newAccountsWithKeys(privateKeys []string) (accounts []*coreTypes.Account) { + for _, pk := range privateKeys { + pk, _ := crypto.NewPrivateKey(pk) + addr := pk.Address().String() + accounts = append(accounts, &coreTypes.Account{ + Address: addr, + Amount: DefaultAccountAmountString, + }) + } + return accounts +} + +func newAccounts(numActors int) (accounts []*coreTypes.Account) { + for i := 0; i < numActors; i++ { _, _, addr := keygen.GetInstance().Next() - if privateKeys != nil { - pk, _ := crypto.NewPrivateKey(privateKeys[i]) - addr = pk.Address().String() - } accounts = append(accounts, &coreTypes.Account{ Address: addr, Amount: DefaultAccountAmountString, }) } - return + return accounts } -// TODO: The current implementation of NewActors will have overlapping `ServiceUrl` for different -// -// types of actors which needs to be fixed. -func NewActors(actorType coreTypes.ActorType, n int) (actors []*coreTypes.Actor, privateKeys []string) { - for i := 0; i < n; i++ { +// TECHDEBT: Current implementation of `newActors` will result in non-unique ServiceURLs if called +// more than once. +func newActors(actorType coreTypes.ActorType, numActors int) (actors []*coreTypes.Actor, privateKeys []string) { + chains := DefaultChains + if actorType == coreTypes.ActorType_ACTOR_TYPE_VAL { + chains = nil + } + for i := 0; i < numActors; i++ { serviceURL := getServiceURL(i + 1) - actor, pk := NewDefaultActor(int32(actorType), serviceURL) + actor, pk := NewDefaultActor(actorType, serviceURL, chains) actors = append(actors, actor) privateKeys = append(privateKeys, pk) } - - return -} - -func getServiceURL(n int) string { - return fmt.Sprintf(ServiceURLFormat, n) + return actors, privateKeys } -func NewDefaultActor(actorType int32, serviceURL string) (actor *coreTypes.Actor, privateKey string) { +func NewDefaultActor(actorType coreTypes.ActorType, serviceURL string, chains []string) (actor *coreTypes.Actor, privateKey string) { privKey, pubKey, addr := keygen.GetInstance().Next() - chains := DefaultChains - if actorType == int32(coreTypes.ActorType_ACTOR_TYPE_VAL) { - chains = nil - } return &coreTypes.Actor{ + ActorType: actorType, Address: addr, PublicKey: pubKey, Chains: chains, @@ -121,6 +154,9 @@ func NewDefaultActor(actorType int32, serviceURL string) (actor *coreTypes.Actor PausedHeight: DefaultPauseHeight, UnstakingHeight: DefaultUnstakingHeight, Output: addr, - ActorType: coreTypes.ActorType(actorType), }, privKey } + +func getServiceURL(n int) string { + return fmt.Sprintf(ServiceURLFormat, n) +} diff --git a/runtime/test_artifacts/genesis.go b/runtime/test_artifacts/genesis.go deleted file mode 100644 index 91594d001..000000000 --- a/runtime/test_artifacts/genesis.go +++ /dev/null @@ -1,11 +0,0 @@ -package test_artifacts - -import "fmt" - -const ( - genesisStatePostfix = "_genesis_state" -) - -func GetGenesisFileName(moduleName string) string { - return fmt.Sprintf("%s%s", moduleName, genesisStatePostfix) -} diff --git a/runtime/test_artifacts/gov.go b/runtime/test_artifacts/gov.go deleted file mode 100644 index 5faa63f6e..000000000 --- a/runtime/test_artifacts/gov.go +++ /dev/null @@ -1,128 +0,0 @@ -package test_artifacts - -import ( - "math/big" - - "github.com/pokt-network/pocket/runtime/genesis" - - "github.com/pokt-network/pocket/shared/crypto" - "github.com/pokt-network/pocket/shared/utils" -) - -// TODO (Team) this entire file should be re-scoped. DefaultParameters are only a testing thing because prod defers to the genesis file - -var DefaultParamsOwner, _ = crypto.NewPrivateKey("ff538589deb7f28bbce1ba68b37d2efc0eaa03204b36513cf88422a875559e38d6cbe0430ddd85a5e48e0c99ef3dea47bf0d1a83c6e6ad1640f72201dc8a0120") - -func DefaultParams() *genesis.Params { - return &genesis.Params{ - BlocksPerSession: 4, - AppMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), - AppMaxChains: 15, - AppSessionTokensMultiplier: 100, - AppUnstakingBlocks: 2016, - AppMinimumPauseBlocks: 4, - AppMaxPauseBlocks: 672, - ServicerMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), - ServicerMaxChains: 15, - ServicerUnstakingBlocks: 2016, - ServicerMinimumPauseBlocks: 4, - ServicerMaxPauseBlocks: 672, - ServicersPerSession: 24, - FishermanMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), - FishermanMaxChains: 15, - FishermanUnstakingBlocks: 2016, - FishermanMinimumPauseBlocks: 4, - FishermanMaxPauseBlocks: 672, - FishermanPerSession: 1, - ValidatorMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), - ValidatorUnstakingBlocks: 2016, - ValidatorMinimumPauseBlocks: 4, - ValidatorMaxPauseBlocks: 672, - ValidatorMaximumMissedBlocks: 5, - ValidatorMaxEvidenceAgeInBlocks: 8, - ProposerPercentageOfFees: 10, - MissedBlocksBurnPercentage: 1, - DoubleSignBurnPercentage: 5, - MessageDoubleSignFee: utils.BigIntToString(big.NewInt(10000)), - MessageSendFee: utils.BigIntToString(big.NewInt(10000)), - MessageStakeFishermanFee: utils.BigIntToString(big.NewInt(10000)), - MessageEditStakeFishermanFee: utils.BigIntToString(big.NewInt(10000)), - MessageUnstakeFishermanFee: utils.BigIntToString(big.NewInt(10000)), - MessagePauseFishermanFee: utils.BigIntToString(big.NewInt(10000)), - MessageUnpauseFishermanFee: utils.BigIntToString(big.NewInt(10000)), - MessageFishermanPauseServicerFee: utils.BigIntToString(big.NewInt(10000)), - MessageTestScoreFee: utils.BigIntToString(big.NewInt(10000)), - MessageProveTestScoreFee: utils.BigIntToString(big.NewInt(10000)), - MessageStakeAppFee: utils.BigIntToString(big.NewInt(10000)), - MessageEditStakeAppFee: utils.BigIntToString(big.NewInt(10000)), - MessageUnstakeAppFee: utils.BigIntToString(big.NewInt(10000)), - MessagePauseAppFee: utils.BigIntToString(big.NewInt(10000)), - MessageUnpauseAppFee: utils.BigIntToString(big.NewInt(10000)), - MessageStakeValidatorFee: utils.BigIntToString(big.NewInt(10000)), - MessageEditStakeValidatorFee: utils.BigIntToString(big.NewInt(10000)), - MessageUnstakeValidatorFee: utils.BigIntToString(big.NewInt(10000)), - MessagePauseValidatorFee: utils.BigIntToString(big.NewInt(10000)), - MessageUnpauseValidatorFee: utils.BigIntToString(big.NewInt(10000)), - MessageStakeServicerFee: utils.BigIntToString(big.NewInt(10000)), - MessageEditStakeServicerFee: utils.BigIntToString(big.NewInt(10000)), - MessageUnstakeServicerFee: utils.BigIntToString(big.NewInt(10000)), - MessagePauseServicerFee: utils.BigIntToString(big.NewInt(10000)), - MessageUnpauseServicerFee: utils.BigIntToString(big.NewInt(10000)), - MessageChangeParameterFee: utils.BigIntToString(big.NewInt(10000)), - AclOwner: DefaultParamsOwner.Address().String(), - BlocksPerSessionOwner: DefaultParamsOwner.Address().String(), - AppMinimumStakeOwner: DefaultParamsOwner.Address().String(), - AppMaxChainsOwner: DefaultParamsOwner.Address().String(), - AppSessionTokensMultiplierOwner: DefaultParamsOwner.Address().String(), - AppUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), - AppMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), - AppMaxPausedBlocksOwner: DefaultParamsOwner.Address().String(), - ServicerMinimumStakeOwner: DefaultParamsOwner.Address().String(), - ServicerMaxChainsOwner: DefaultParamsOwner.Address().String(), - ServicerUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), - ServicerMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), - ServicerMaxPausedBlocksOwner: DefaultParamsOwner.Address().String(), - ServicersPerSessionOwner: DefaultParamsOwner.Address().String(), - FishermanMinimumStakeOwner: DefaultParamsOwner.Address().String(), - FishermanMaxChainsOwner: DefaultParamsOwner.Address().String(), - FishermanUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), - FishermanMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), - FishermanMaxPausedBlocksOwner: DefaultParamsOwner.Address().String(), - FishermanPerSessionOwner: DefaultParamsOwner.Address().String(), - ValidatorMinimumStakeOwner: DefaultParamsOwner.Address().String(), - ValidatorUnstakingBlocksOwner: DefaultParamsOwner.Address().String(), - ValidatorMinimumPauseBlocksOwner: DefaultParamsOwner.Address().String(), - ValidatorMaxPausedBlocksOwner: DefaultParamsOwner.Address().String(), - ValidatorMaximumMissedBlocksOwner: DefaultParamsOwner.Address().String(), - ValidatorMaxEvidenceAgeInBlocksOwner: DefaultParamsOwner.Address().String(), - ProposerPercentageOfFeesOwner: DefaultParamsOwner.Address().String(), - MissedBlocksBurnPercentageOwner: DefaultParamsOwner.Address().String(), - DoubleSignBurnPercentageOwner: DefaultParamsOwner.Address().String(), - MessageDoubleSignFeeOwner: DefaultParamsOwner.Address().String(), - MessageSendFeeOwner: DefaultParamsOwner.Address().String(), - MessageStakeFishermanFeeOwner: DefaultParamsOwner.Address().String(), - MessageEditStakeFishermanFeeOwner: DefaultParamsOwner.Address().String(), - MessageUnstakeFishermanFeeOwner: DefaultParamsOwner.Address().String(), - MessagePauseFishermanFeeOwner: DefaultParamsOwner.Address().String(), - MessageUnpauseFishermanFeeOwner: DefaultParamsOwner.Address().String(), - MessageFishermanPauseServicerFeeOwner: DefaultParamsOwner.Address().String(), - MessageTestScoreFeeOwner: DefaultParamsOwner.Address().String(), - MessageProveTestScoreFeeOwner: DefaultParamsOwner.Address().String(), - MessageStakeAppFeeOwner: DefaultParamsOwner.Address().String(), - MessageEditStakeAppFeeOwner: DefaultParamsOwner.Address().String(), - MessageUnstakeAppFeeOwner: DefaultParamsOwner.Address().String(), - MessagePauseAppFeeOwner: DefaultParamsOwner.Address().String(), - MessageUnpauseAppFeeOwner: DefaultParamsOwner.Address().String(), - MessageStakeValidatorFeeOwner: DefaultParamsOwner.Address().String(), - MessageEditStakeValidatorFeeOwner: DefaultParamsOwner.Address().String(), - MessageUnstakeValidatorFeeOwner: DefaultParamsOwner.Address().String(), - MessagePauseValidatorFeeOwner: DefaultParamsOwner.Address().String(), - MessageUnpauseValidatorFeeOwner: DefaultParamsOwner.Address().String(), - MessageStakeServicerFeeOwner: DefaultParamsOwner.Address().String(), - MessageEditStakeServicerFeeOwner: DefaultParamsOwner.Address().String(), - MessageUnstakeServicerFeeOwner: DefaultParamsOwner.Address().String(), - MessagePauseServicerFeeOwner: DefaultParamsOwner.Address().String(), - MessageUnpauseServicerFeeOwner: DefaultParamsOwner.Address().String(), - MessageChangeParameterFeeOwner: DefaultParamsOwner.Address().String(), - } -} diff --git a/shared/core/types/proto/actor.proto b/shared/core/types/proto/actor.proto index e6d0bbdee..ef49dbf5c 100644 --- a/shared/core/types/proto/actor.proto +++ b/shared/core/types/proto/actor.proto @@ -20,16 +20,17 @@ enum StakeStatus { } // TODO(#555): Investigate ways of having actor specific params that are not part of the shared struct. +// Potentially having a separate struct for each actor type and a shared base. message Actor { ActorType actor_type = 1; string address = 2; string public_key = 3; - repeated string chains = 4; // Not applicable to `Validator` actors + repeated string chains = 4; // NB: Not applicable `Validator` actors // proto-gen-c does not support `go_name` at the time of writing resulting // in the output go field being snakeCase: ServiceUrl (https://github.com/golang/protobuf/issues/555) string service_url = 5; // Not applicable to `Application` actors string staked_amount = 6; - int64 paused_height = 7; - int64 unstaking_height = 8; - string output = 9; + int64 paused_height = 7; // TECHDEBT: Revisit this parameter and see if it can be removed for simplification purposes. + int64 unstaking_height = 8; // TECHDEBT: Revisit this parameter and see if it can be removed for simplification purposes. + string output = 9; // TECHDEBT: Revisit custodial / non-custodial flows (e.g. what if we want multiple outsputs for business purposes) } diff --git a/utility/session_test.go b/utility/session_test.go index bc8cde97f..3f7b514c2 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -3,64 +3,56 @@ package utility import ( "testing" + "github.com/pokt-network/pocket/runtime/test_artifacts" "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/utility/types" "github.com/stretchr/testify/require" ) -func TestSession_NewSession_SimpleCase(t *testing.T) { +// TECHDEBT: Geozones are not current implemented, used or tested + +// One fishermen and one servicer happy base case +func TestSession_NewSession_BaseCase(t *testing.T) { + // Test parameters height := int64(1) - relayChain := "0001" + relayChain := test_artifacts.DefaultChains[0] geoZone := "unused_geo" + numFishermen := 1 + numServicers := 1 + expectedSessionId := "3545185ff1519bf7706ec8f828d16525830d3c0dcc2425c40db597ee6b67b8bc" // needs to be manually updated if business logic changes + + runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, numServicers, 1, numFishermen) - runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, 1, 1, 1) + // Sanity check genesis require.Len(t, runtimeCfg.GetGenesis().Applications, 1) app := runtimeCfg.GetGenesis().Applications[0] + require.Len(t, runtimeCfg.GetGenesis().Fishermen, 1) + fish := runtimeCfg.GetGenesis().Fishermen[0] + require.Len(t, runtimeCfg.GetGenesis().Servicers, 1) + servicer := runtimeCfg.GetGenesis().Servicers[0] session, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) require.NoError(t, err) - require.Equal(t, "3545185ff1519bf7706ec8f828d16525830d3c0dcc2425c40db597ee6b67b8bc", session.Id) + require.Equal(t, expectedSessionId, session.Id) require.Equal(t, height, session.Height) require.Equal(t, relayChain, session.RelayChain) require.Equal(t, geoZone, session.GeoZone) - require.Equal(t, session.Application.Address, app.Address) - require.Equal(t, "c7832263600476fd6ff4c5cb0a86080d0e5f48b2", session.Servicers[0].Address) - require.Equal(t, "a6e7b6810df8120580f2a81710e228f454f99c97", session.Fishermen[0].Address) + require.Equal(t, app.Address, session.Application.Address) + require.Len(t, session.Servicers, numServicers) + require.Equal(t, servicer.Address, session.Servicers[0].Address) + require.Len(t, session.Fishermen, numFishermen) + require.Equal(t, fish.Address, session.Fishermen[0].Address) } -// dispatching session in the future -// dispatching session in the past - -// not enough servicers to choose from -// no fisherman available -// validate application dispatch - -// invalid app -// unstaked app -// non-existent app - -// invalid chain -// unused chain -// non-existent chain - -// invalid geozone -// unused geozone -// non-existent geozone - -func TestSession_ServicersAndFishermanCount(t *testing.T) { +func TestSession_ServicersAndFishermanCounts_TotalAvailability(t *testing.T) { numServicers := 100 numFishermen := 100 - // Prepare an environment with lots of servicers and fisherman runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, 1, numFishermen) app := runtimeCfg.GetGenesis().Applications[0] - - // height := int64(1) - relayChain := "0001" + relayChain := test_artifacts.DefaultChains[0] geoZone := "unused_geo" - // defer writeCtx.Release() - tests := []struct { name string numServicersPerSession int64 @@ -69,32 +61,45 @@ func TestSession_ServicersAndFishermanCount(t *testing.T) { wantFishermanCount int }{ { - name: "not enough actors to choose from (> 100)", + name: "more actors per session than available in network", numServicersPerSession: int64(numServicers) * 10, numFishermanPerSession: int64(numFishermen) * 10, wantServicerCount: numServicers, wantFishermanCount: numFishermen, }, { - name: "too many actors to choose from (< 100)", + name: "less actors per session than available in network", numServicersPerSession: int64(numServicers) / 2, numFishermanPerSession: int64(numFishermen) / 2, wantServicerCount: numServicers / 2, wantFishermanCount: numFishermen / 2, }, { - name: "same number of servicers and fisherman", + name: "same number of actors per session as available in network", numServicersPerSession: int64(numServicers), numFishermanPerSession: int64(numFishermen), wantServicerCount: numServicers, wantFishermanCount: numFishermen, }, - // Not enough servicers in region - // Not enough fisherman in region - // Not enough servicers per chain - // Not enough fisherman per chain + { + name: "more than enough servicers but not enough fishermen", + numServicersPerSession: int64(numServicers) / 2, + numFishermanPerSession: int64(numFishermen) * 10, + wantServicerCount: numServicers / 2, + wantFishermanCount: numFishermen, + }, + { + name: "more than enough fishermen but not enough servicers", + numServicersPerSession: int64(numServicers) * 10, + numFishermanPerSession: int64(numFishermen) / 2, + wantServicerCount: numServicers, + wantFishermanCount: numFishermen / 2, + }, } + updateParamsHeight := int64(1) + querySessionHeight := int64(2) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := persistenceMod.HandleDebugMessage(&messaging.DebugMessage{ @@ -102,21 +107,19 @@ func TestSession_ServicersAndFishermanCount(t *testing.T) { Message: nil, }) require.NoError(t, err) - writeCtx, err := persistenceMod.NewRWContext(1) + + writeCtx, err := persistenceMod.NewRWContext(updateParamsHeight) require.NoError(t, err) defer writeCtx.Release() err = writeCtx.SetParam(types.ServicersPerSessionParamName, tt.numServicersPerSession) require.NoError(t, err) - writeCtx.SetParam(types.FishermanPerSessionParamName, tt.numFishermanPerSession) + err = writeCtx.SetParam(types.FishermanPerSessionParamName, tt.numFishermanPerSession) require.NoError(t, err) - - err = writeCtx.Commit([]byte(""), []byte("")) + err = writeCtx.Commit([]byte("empty_proposed_addr"), []byte("empty_quorum_cert")) require.NoError(t, err) - session, err := utilityMod.GetSession(app.Address, 2, relayChain, geoZone) - - // require.NoError(t, writeCtx.Commit([]byte(""), []byte(""))) + session, err := utilityMod.GetSession(app.Address, querySessionHeight, relayChain, geoZone) require.NoError(t, err) require.Equal(t, tt.wantServicerCount, len(session.Servicers)) require.Equal(t, tt.wantFishermanCount, len(session.Fishermen)) @@ -124,6 +127,101 @@ func TestSession_ServicersAndFishermanCount(t *testing.T) { } } +func TestSession_ServicersAndFishermanCounts_ChainAvailability(t *testing.T) { + // numServicers := 100 + // numFishermen := 100 + // runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, 1, numFishermen) + + // app := runtimeCfg.GetGenesis().Applications[0] + // relayChain := test_artifacts.DefaultChains[0] + // geoZone := "unused_geo" + + // tests := []struct { + // name string + // numServicersPerSession int64 + // numFishermanPerSession int64 + // wantServicerCount int + // wantFishermanCount int + // }{ + // { + // name: "more actors per session than available in network", + // numServicersPerSession: int64(numServicers) * 10, + // numFishermanPerSession: int64(numFishermen) * 10, + // wantServicerCount: numServicers, + // wantFishermanCount: numFishermen, + // }, + // { + // name: "less actors per session than available in network", + // numServicersPerSession: int64(numServicers) / 2, + // numFishermanPerSession: int64(numFishermen) / 2, + // wantServicerCount: numServicers / 2, + // wantFishermanCount: numFishermen / 2, + // }, + // { + // name: "same number of actors per session as available in network", + // numServicersPerSession: int64(numServicers), + // numFishermanPerSession: int64(numFishermen), + // wantServicerCount: numServicers, + // wantFishermanCount: numFishermen, + // }, + // } + + // updateParamsHeight := int64(1) + // querySessionHeight := int64(2) + + // for _, tt := range tests { + // t.Run(tt.name, func(t *testing.T) { + // err := persistenceMod.HandleDebugMessage(&messaging.DebugMessage{ + // Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, + // Message: nil, + // }) + // require.NoError(t, err) + + // writeCtx, err := persistenceMod.NewRWContext(updateParamsHeight) + // require.NoError(t, err) + // defer writeCtx.Release() + + // err = writeCtx.SetParam(types.ServicersPerSessionParamName, tt.numServicersPerSession) + // require.NoError(t, err) + // err = writeCtx.SetParam(types.FishermanPerSessionParamName, tt.numFishermanPerSession) + // require.NoError(t, err) + // err = writeCtx.Commit([]byte("empty_proposed_addr"), []byte("empty_quorum_cert")) + // require.NoError(t, err) + + // session, err := utilityMod.GetSession(app.Address, querySessionHeight, relayChain, geoZone) + // require.NoError(t, err) + // require.Equal(t, tt.wantServicerCount, len(session.Servicers)) + // require.Equal(t, tt.wantFishermanCount, len(session.Fishermen)) + // }) + // } +} + +// Not enough servicers in region +// Not enough fisherman in region +// Not enough servicers per chain +// Not enough fisherman per chain + +// func TestSession_NewSession_BaseCase(t *testing.T) { + +// dispatching session in the future +// dispatching session in the past + +// not enough servicers to choose from +// no fisherman available +// validate application dispatch + +// invalid app +// unstaked app +// non-existent app + +// invalid chain +// unused chain +// non-existent chain + +// invalid geozone +// unused geozone +// non-existent geozone + // generate session id func TestSession_ServicersAndFishermanRandomness(t *testing.T) { From 42c59ec9ee9b67456948cddf9438d40eadc4ec31 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 17 Apr 2023 16:56:09 -0700 Subject: [PATCH 11/46] Finished TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability --- persistence/debug.go | 2 - runtime/test_artifacts/generator.go | 16 ++- utility/module_test.go | 5 +- utility/session_test.go | 210 ++++++++++++++-------------- 4 files changed, 119 insertions(+), 114 deletions(-) diff --git a/persistence/debug.go b/persistence/debug.go index a11594716..4c91439d9 100644 --- a/persistence/debug.go +++ b/persistence/debug.go @@ -2,7 +2,6 @@ package persistence import ( "crypto/sha256" - "fmt" "runtime/debug" "github.com/pokt-network/pocket/persistence/types" @@ -23,7 +22,6 @@ var nonActorClearFunctions = []func() string{ } func (m *persistenceModule) HandleDebugMessage(debugMessage *messaging.DebugMessage) error { - fmt.Println("HERE") switch debugMessage.Action { case messaging.DebugMessageAction_DEBUG_SHOW_LATEST_BLOCK_IN_STORE: m.showLatestBlockInStore(debugMessage) diff --git a/runtime/test_artifacts/generator.go b/runtime/test_artifacts/generator.go index aa359223a..537df9b73 100644 --- a/runtime/test_artifacts/generator.go +++ b/runtime/test_artifacts/generator.go @@ -6,6 +6,7 @@ import ( "fmt" "strconv" + "github.com/pokt-network/pocket/logger" "github.com/pokt-network/pocket/runtime/configs" "github.com/pokt-network/pocket/runtime/genesis" "github.com/pokt-network/pocket/runtime/test_artifacts/keygen" @@ -19,10 +20,10 @@ type GenesisOption func(*genesis.GenesisState) // IMPROVE: Generate a proper genesis suite in the future. func NewGenesisState(numValidators, numServicers, numApplications, numFisherman int, genesisOpts ...GenesisOption) (genesisState *genesis.GenesisState, validatorPrivateKeys []string) { - applications, appPrivateKeys := newActors(coreTypes.ActorType_ACTOR_TYPE_APP, numApplications) - validators, validatorPrivateKeys := newActors(coreTypes.ActorType_ACTOR_TYPE_VAL, numValidators) - servicers, servicerPrivateKeys := newActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicers) - fishermen, fishPrivateKeys := newActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFisherman) + applications, appPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_APP, numApplications, DefaultChains) + validators, validatorPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_VAL, numValidators, nil) + servicers, servicerPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicers, DefaultChains) + fishermen, fishPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFisherman, DefaultChains) allActorsKeys := append(append(append(validatorPrivateKeys, servicerPrivateKeys...), fishPrivateKeys...), appPrivateKeys...) allActorAccounts := newAccountsWithKeys(allActorsKeys) @@ -126,11 +127,12 @@ func newAccounts(numActors int) (accounts []*coreTypes.Account) { return accounts } -// TECHDEBT: Current implementation of `newActors` will result in non-unique ServiceURLs if called +// TECHDEBT: Current implementation of `NewActors` will result in non-unique ServiceURLs if called // more than once. -func newActors(actorType coreTypes.ActorType, numActors int) (actors []*coreTypes.Actor, privateKeys []string) { - chains := DefaultChains +func NewActors(actorType coreTypes.ActorType, numActors int, chains []string) (actors []*coreTypes.Actor, privateKeys []string) { + // If the actor type is a validator, the chains must be nil since they are chain agnostic if actorType == coreTypes.ActorType_ACTOR_TYPE_VAL { + logger.Global.Warn().Msgf("validator actors should not have chains but a list was provided: %v", chains) chains = nil } for i := 0; i < numActors; i++ { diff --git a/utility/module_test.go b/utility/module_test.go index ee2d15ac6..17c5bcdb9 100644 --- a/utility/module_test.go +++ b/utility/module_test.go @@ -49,10 +49,11 @@ func prepareEnvironment( numServicers, numApplications, numFisherman int, + genesisOpts ...test_artifacts.GenesisOption, ) (*runtime.Manager, modules.UtilityModule, modules.PersistenceModule) { teardownDeterministicKeygen := keygen.GetInstance().SetSeed(42) - runtimeCfg := newTestRuntimeConfig(numValidators, numServicers, numApplications, numFisherman) + runtimeCfg := newTestRuntimeConfig(numValidators, numServicers, numApplications, numFisherman, genesisOpts...) bus, err := runtime.CreateBus(runtimeCfg) require.NoError(t, err) @@ -77,6 +78,7 @@ func newTestRuntimeConfig( numServicers, numApplications, numFisherman int, + genesisOpts ...test_artifacts.GenesisOption, ) *runtime.Manager { cfg := &configs.Config{ Utility: &configs.UtilityConfig{ @@ -101,6 +103,7 @@ func newTestRuntimeConfig( numServicers, numApplications, numFisherman, + genesisOpts..., ) runtimeCfg := runtime.NewManager(cfg, genesisState) return runtimeCfg diff --git a/utility/session_test.go b/utility/session_test.go index 3f7b514c2..b3243e02d 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/pokt-network/pocket/runtime/test_artifacts" + coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/utility/types" "github.com/stretchr/testify/require" @@ -11,8 +12,7 @@ import ( // TECHDEBT: Geozones are not current implemented, used or tested -// One fishermen and one servicer happy base case -func TestSession_NewSession_BaseCase(t *testing.T) { +func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) { // Test parameters height := int64(1) relayChain := test_artifacts.DefaultChains[0] @@ -44,15 +44,13 @@ func TestSession_NewSession_BaseCase(t *testing.T) { require.Equal(t, fish.Address, session.Fishermen[0].Address) } -func TestSession_ServicersAndFishermanCounts_TotalAvailability(t *testing.T) { +func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *testing.T) { + // Prepare an environment with a lot of servicers and fishermen numServicers := 100 numFishermen := 100 runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, 1, numFishermen) - app := runtimeCfg.GetGenesis().Applications[0] - relayChain := test_artifacts.DefaultChains[0] - geoZone := "unused_geo" - + // Vary the number of actors per session using gov params and check that the session is populated with the correct number of actorss tests := []struct { name string numServicersPerSession int64 @@ -100,6 +98,10 @@ func TestSession_ServicersAndFishermanCounts_TotalAvailability(t *testing.T) { updateParamsHeight := int64(1) querySessionHeight := int64(2) + app := runtimeCfg.GetGenesis().Applications[0] + relayChain := test_artifacts.DefaultChains[0] + geoZone := "unused_geo" + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := persistenceMod.HandleDebugMessage(&messaging.DebugMessage{ @@ -127,110 +129,80 @@ func TestSession_ServicersAndFishermanCounts_TotalAvailability(t *testing.T) { } } -func TestSession_ServicersAndFishermanCounts_ChainAvailability(t *testing.T) { - // numServicers := 100 - // numFishermen := 100 - // runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, 1, numFishermen) - - // app := runtimeCfg.GetGenesis().Applications[0] - // relayChain := test_artifacts.DefaultChains[0] - // geoZone := "unused_geo" - - // tests := []struct { - // name string - // numServicersPerSession int64 - // numFishermanPerSession int64 - // wantServicerCount int - // wantFishermanCount int - // }{ - // { - // name: "more actors per session than available in network", - // numServicersPerSession: int64(numServicers) * 10, - // numFishermanPerSession: int64(numFishermen) * 10, - // wantServicerCount: numServicers, - // wantFishermanCount: numFishermen, - // }, - // { - // name: "less actors per session than available in network", - // numServicersPerSession: int64(numServicers) / 2, - // numFishermanPerSession: int64(numFishermen) / 2, - // wantServicerCount: numServicers / 2, - // wantFishermanCount: numFishermen / 2, - // }, - // { - // name: "same number of actors per session as available in network", - // numServicersPerSession: int64(numServicers), - // numFishermanPerSession: int64(numFishermen), - // wantServicerCount: numServicers, - // wantFishermanCount: numFishermen, - // }, - // } - - // updateParamsHeight := int64(1) - // querySessionHeight := int64(2) - - // for _, tt := range tests { - // t.Run(tt.name, func(t *testing.T) { - // err := persistenceMod.HandleDebugMessage(&messaging.DebugMessage{ - // Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, - // Message: nil, - // }) - // require.NoError(t, err) - - // writeCtx, err := persistenceMod.NewRWContext(updateParamsHeight) - // require.NoError(t, err) - // defer writeCtx.Release() - - // err = writeCtx.SetParam(types.ServicersPerSessionParamName, tt.numServicersPerSession) - // require.NoError(t, err) - // err = writeCtx.SetParam(types.FishermanPerSessionParamName, tt.numFishermanPerSession) - // require.NoError(t, err) - // err = writeCtx.Commit([]byte("empty_proposed_addr"), []byte("empty_quorum_cert")) - // require.NoError(t, err) - - // session, err := utilityMod.GetSession(app.Address, querySessionHeight, relayChain, geoZone) - // require.NoError(t, err) - // require.Equal(t, tt.wantServicerCount, len(session.Servicers)) - // require.Equal(t, tt.wantFishermanCount, len(session.Fishermen)) - // }) - // } -} +func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *testing.T) { + numServicersPerSession := 10 + numFishermenPerSession := 2 -// Not enough servicers in region -// Not enough fisherman in region -// Not enough servicers per chain -// Not enough fisherman per chain + // Make sure there are MORE THAN ENOUGH servicers and fishermen in the network for each session for chain 1 + servicersChain1, servicerKeysChain1 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicersPerSession*2, []string{"chn1"}) + fishermenChain2, fishermenKeysChain2 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFishermenPerSession*2, []string{"chn1"}) -// func TestSession_NewSession_BaseCase(t *testing.T) { + // Make sure there are NOT ENOUGH servicers and fishermen in the network for each session for chain 2 + servicersChain2, servicerKeysChain2 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicersPerSession/2, []string{"chn2"}) + fishermenChain1, fishermenKeysChain1 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFishermenPerSession/2, []string{"chn2"}) -// dispatching session in the future -// dispatching session in the past + actors := append(servicersChain1, append(servicersChain2, append(fishermenChain1, fishermenChain2...)...)...) + keys := append(servicerKeysChain1, append(servicerKeysChain2, append(fishermenKeysChain1, fishermenKeysChain2...)...)...) -// not enough servicers to choose from -// no fisherman available -// validate application dispatch + runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, 0, 1, 0, test_artifacts.WithActors(actors, keys)) -// invalid app -// unstaked app -// non-existent app + // Vary the chains and check the number of fishermen and servicers returned for each one + tests := []struct { + name string + chain string + wantServicerCount int + wantFishermanCount int + }{ + { + name: "chn1 has enough servicers and fishermen", + chain: "chn1", + wantServicerCount: numServicersPerSession, + wantFishermanCount: numFishermenPerSession, + }, + { + name: "chn2 does not have enough servicers and fishermen", + chain: "chn2", + wantServicerCount: numServicersPerSession / 2, + wantFishermanCount: numFishermenPerSession / 2, + }, + { + name: "chain3 has no servicers and fishermen", + chain: "chn3", + wantServicerCount: 0, + wantFishermanCount: 0, + }, + } -// invalid chain -// unused chain -// non-existent chain + err := persistenceMod.HandleDebugMessage(&messaging.DebugMessage{ + Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, + Message: nil, + }) + require.NoError(t, err) -// invalid geozone -// unused geozone -// non-existent geozone + writeCtx, err := persistenceMod.NewRWContext(1) + require.NoError(t, err) + err = writeCtx.SetParam(types.ServicersPerSessionParamName, numServicersPerSession) + require.NoError(t, err) + err = writeCtx.SetParam(types.FishermanPerSessionParamName, numFishermenPerSession) + require.NoError(t, err) + err = writeCtx.Commit([]byte("empty_proposed_addr"), []byte("empty_quorum_cert")) + require.NoError(t, err) + defer writeCtx.Release() -// generate session id + app := runtimeCfg.GetGenesis().Applications[0] + geoZone := "unused_geo" -func TestSession_ServicersAndFishermanRandomness(t *testing.T) { - // validate entropy and randomness - // different height - // different chain + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + session, err := utilityMod.GetSession(app.Address, 2, tt.chain, geoZone) + require.NoError(t, err) + require.Equal(t, tt.wantServicerCount, len(session.Servicers)) + require.Equal(t, tt.wantFishermanCount, len(session.Fishermen)) + }) + } } -func TestSession_SessionHeightAndNumber_StaticBlocksPerSession(t *testing.T) { +func TestSession_GetSession_SessionHeightAndNumber_StaticBlocksPerSession(t *testing.T) { _, _, persistenceMod := prepareEnvironment(t, 5, 1, 1, 1) writeCtx, err := persistenceMod.NewRWContext(0) @@ -272,16 +244,12 @@ func TestSession_SessionHeightAndNumber_StaticBlocksPerSession(t *testing.T) { wantSessionHeight: 10, wantSessionNumber: 2, }, - // TODO: Different blocks per session - // What if we change the num blocks -> gets complex - // -> Need to enforce waiting until the end of the current sessions } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := writeCtx.SetParam(types.BlocksPerSessionParamName, tt.numBlocksPerSession) require.NoError(t, err) - // require.NoError(t, writeCtx.Commit([]byte(""), []byte(""))) sessionHeight, sessionNumber, err := getSessionHeight(writeCtx, tt.haveBlockHeight) require.NoError(t, err) @@ -291,6 +259,40 @@ func TestSession_SessionHeightAndNumber_StaticBlocksPerSession(t *testing.T) { } } +// TODO: Different blocks per session +// What if we change the num blocks -> gets complex +// -> Need to enforce waiting until the end of the current sessions + +// Not enough servicers in region +// Not enough fisherman in region + +// func TestSession_NewSession_BaseCase(t *testing.T) { + +// dispatching session in the future +// dispatching session in the past + +// validate application dispatch + +// invalid app +// unstaked app +// non-existent app + +// invalid chain +// unused chain +// non-existent chain + +// invalid geozone +// unused geozone +// non-existent geozone + +// generate session id + +func TestSession_ServicersAndFishermanRandomness(t *testing.T) { + // validate entropy and randomness + // different height + // different chain +} + func TestSession_SessionHeightAndNumber_DynamicBlocksPerSession(t *testing.T) { } From ee5b3f499b7b3046800b13bf3df5cf6f0d5dad5b Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 17 Apr 2023 19:02:17 -0700 Subject: [PATCH 12/46] Finished TestSession_GetSession_InvalidFutureSession --- utility/session_test.go | 143 ++++++++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 55 deletions(-) diff --git a/utility/session_test.go b/utility/session_test.go index b3243e02d..71a1ea81a 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -1,10 +1,12 @@ package utility import ( + "fmt" "testing" "github.com/pokt-network/pocket/runtime/test_artifacts" coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/utility/types" "github.com/stretchr/testify/require" @@ -44,6 +46,70 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) require.Equal(t, fish.Address, session.Fishermen[0].Address) } +func TestSession_GetSession_InvalidApplication(t *testing.T) { + runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, 1, 1, 1) + + // Verify there's only 1 app + require.Len(t, runtimeCfg.GetGenesis().Applications, 1) + app := runtimeCfg.GetGenesis().Applications[0] + + // Create a new app address + pk, _ := crypto.GeneratePrivateKey() + addr := pk.Address().String() + + // Verify that the one app in the genesis is not the one we just generated + require.NotEqual(t, app.Address, addr) + + // Expect an error + _, err := utilityMod.GetSession(addr, 1, test_artifacts.DefaultChains[0], "unused_geo") + require.Error(t, err) +} + +func TestSession_GetSession_InvalidFutureSession(t *testing.T) { + runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, 1, 1, 1) + + relayChain := test_artifacts.DefaultChains[0] + geoZone := "unused_geo" + app := runtimeCfg.GetGenesis().Applications[0] + + latestCommitted := int64(0) + + // Successfully get a session at height=1 + session, err := utilityMod.GetSession(app.Address, latestCommitted+1, relayChain, geoZone) + require.NoError(t, err) + require.Equal(t, latestCommitted+1, session.Height) + + // Expect an error for a few heights into the future + for height := latestCommitted + 2; height < 10; height++ { + _, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) + require.Error(t, err) + } + + // Commit new blocks for all the heights that failed above + for ; latestCommitted < 10; latestCommitted++ { + writeCtx, err := persistenceMod.NewRWContext(latestCommitted + 1) + require.NoError(t, err) + err = writeCtx.Commit([]byte(fmt.Sprintf("proposer_height_%d", latestCommitted)), []byte(fmt.Sprintf("quorum_cert_height_%d", latestCommitted))) + require.NoError(t, err) + writeCtx.Release() + } + + // Expect no errors since those blocks exist now + // Note that we can get the session for latest_committed + 1 + for height := int64(1); height <= latestCommitted+1; height++ { + _, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) + require.NoError(t, err) + } + + // Verify that latestCommitted + 2 fails + _, err = utilityMod.GetSession(app.Address, latestCommitted+2, relayChain, geoZone) + require.Error(t, err) +} + +func TestSession_GetSession_ApplicationUnbonds(t *testing.T) { + // TODO: What if an Application unbonds (unstaking period elapses) mid session? +} + func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *testing.T) { // Prepare an environment with a lot of servicers and fishermen numServicers := 100 @@ -166,7 +232,7 @@ func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *tes wantFishermanCount: numFishermenPerSession / 2, }, { - name: "chain3 has no servicers and fishermen", + name: "chn3 has no servicers and fishermen", chain: "chn3", wantServicerCount: 0, wantFishermanCount: 0, @@ -259,71 +325,38 @@ func TestSession_GetSession_SessionHeightAndNumber_StaticBlocksPerSession(t *tes } } -// TODO: Different blocks per session -// What if we change the num blocks -> gets complex -// -> Need to enforce waiting until the end of the current sessions - -// Not enough servicers in region -// Not enough fisherman in region - -// func TestSession_NewSession_BaseCase(t *testing.T) { - -// dispatching session in the future -// dispatching session in the past - -// validate application dispatch - -// invalid app -// unstaked app -// non-existent app - -// invalid chain -// unused chain -// non-existent chain - -// invalid geozone -// unused geozone -// non-existent geozone - -// generate session id +func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { + // Prepare an environment with a lot of servicers and fishermen + // numServicers := 100 + // numFishermen := 100 + // runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, 1, numFishermen) -func TestSession_ServicersAndFishermanRandomness(t *testing.T) { // validate entropy and randomness // different height // different chain } -func TestSession_SessionHeightAndNumber_DynamicBlocksPerSession(t *testing.T) { - +func TestSession_GetSession_ServicersAndFishermenCounts_GeoZoneAvailability(t *testing.T) { + // TODO: Once GeoZones are implemented, the tests need to be added as well + // Cases: Invalid, unused, non-existent, empty, insufficiently complete, etc... } -func TestSession_MatchNewSession(t *testing.T) { +func TestSession_GetSession_ActorReplacement(t *testing.T) { + // TODO: Since sessions last multiple blocks, we need to design what happens when an actor is (un)jailed, (un)stakes, (un)bonds, (un)pauses + // mid session. There are open design questions that need to be made. } -func TestSession_RelayChainVariability(t *testing.T) { - // invalid relay chain - // valid relay chain - +func TestSession_GetSession_SessionHeightAndNumber_ModifiedBlocksPerSession(t *testing.T) { + // RESEARCH: Need to design what happens (actor replacement, session numbers, etc...) when the number + // of blocks per session changes mid session. For example, all existing sessions could go to completion + // until the new parameter takes effect. There are open design questions that need to be made. } -func TestSession_ActorReplacement(t *testing.T) { - // What if a servicers/fisherman paused mid session? - // -> Need to replace them - - // What if a new servicers/fisherman staked mid session? - // -> They could potentially get selected -} - -func TestSession_InvalidApplication(t *testing.T) { - // TODO: What if the application pauses mid session? - // TODO: What if the application has no stake? -} +// TODO: Different blocks per session +// What if we change the num blocks -> gets complex +// -> Need to enforce waiting until the end of the current sessions -// Potential: Changing num blocks per session must wait until current session ends -> easily fixes things -// New servicers / fisherman -> need to wait until current session ends -> easily fixes things +// func TestSession_NewSession_BaseCase(t *testing.T) { -// Configurable number of geo zones per session -// above max -// below max -// at max -// application is not staked for relay chain +// dispatching session in the future +// dispatching session in the past From 50ccff31c13c9b1410c9b2f817b19953069fb666 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 17 Apr 2023 22:28:29 -0700 Subject: [PATCH 13/46] Add tests for TestSession_GetSession_ServicersAndFishermanEntropy --- utility/session.go | 14 +++-- utility/session_test.go | 130 +++++++++++++++++++++++++++++++++++----- 2 files changed, 125 insertions(+), 19 deletions(-) diff --git a/utility/session.go b/utility/session.go index 20431a1b3..b50ef4e37 100644 --- a/utility/session.go +++ b/utility/session.go @@ -13,7 +13,8 @@ import ( "github.com/pokt-network/pocket/utility/types" ) -// TODO: When implementing please review if block height tolerance (+,-1) is included in the session protocol: pokt-network/pocket-core#1464 CC @Olshansk +// OPTIMIZE: Postgres uses `Twisted Mersenne Twister (TMT)` randomness algorithm. We could potentially look into changing everything a single +// SQL query but need to make sure that it can be implemented in a platform agnostic way. type sessionHydrator struct { logger modules.Logger @@ -155,7 +156,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { return fmt.Errorf("hydrateSessionServicers should not have encountered a paused or unstaking servicer: %s", servicer.Address) } - // TODO_IN_THIS_COMMIT: if servicer.GeoZone includes session.GeoZone + // TODO(#XXX): Add GeoZone filtering // OPTIMIZE: If this was a map, we could have avoided the loop over chains var chain string @@ -222,7 +223,9 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { return nil } -// TODO_IN_THIS_COMMIT: Deterministic randomness algorithm +// pseudoRandomSelection returns a random subset of the candidates. +// TECHDEBT: We are using a `Go` native implementation for a pseudo-random number generator. In order +// for it to be language agnostic, a general purpose algorithm needs ot be used. func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, sessionId []byte) []*coreTypes.Actor { // If there aren't enough candidates, return all of them if numTarget > len(candidates) { @@ -231,7 +234,7 @@ func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, session } // Take the first 8 bytes of sessionId to use as the seed - seed := int64(binary.BigEndian.Uint64(sessionId[:8])) + seed := int64(binary.BigEndian.Uint64(crypto.SHA3Hash(sessionId)[:8])) // Retrieve the indices for the candidates actors := make([]*coreTypes.Actor, 0) @@ -243,6 +246,9 @@ func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, session return actors } +// uniqueRandomIndices returns a map of `numIndices` unique random numbers less than `maxIndex` +// seeded by `seed`. +// NB: A map pointing to empty structs is used to simulate set behaviour. func uniqueRandomIndices(seed, maxIndex, numIndices int64) map[int64]struct{} { // This should never happen if numIndices > maxIndex { diff --git a/utility/session_test.go b/utility/session_test.go index 71a1ea81a..8c2e81c72 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -9,7 +9,9 @@ import ( "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/utility/types" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gonum.org/v1/gonum/stat/combin" ) // TECHDEBT: Geozones are not current implemented, used or tested @@ -327,13 +329,120 @@ func TestSession_GetSession_SessionHeightAndNumber_StaticBlocksPerSession(t *tes func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { // Prepare an environment with a lot of servicers and fishermen - // numServicers := 100 - // numFishermen := 100 - // runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, 1, numFishermen) + numServicers := 1000 + numFishermen := 1000 // make them equal for simplicity + numServicersPerSession := 10 + numFishermenPerSession := 10 // make them equal for simplicity + + // Determine probability of overlap using combinatorics + numChoices := combin.GeneralizedBinomial(float64(numServicers), float64(numServicersPerSession)) // numServicers C numServicersPerSession + numChoicesRemaining := combin.GeneralizedBinomial(float64(numServicers), float64(numServicersPerSession)) // (numServicers - numServicersPerSession) C numServicersPerSession + probabilityOfOverlap := (numChoices - numChoicesRemaining) / numChoices + + numApplications := 3 + runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, numApplications, numFishermen) + + // Set the number of servicers and fishermen per session + writeCtx, err := persistenceMod.NewRWContext(1) + require.NoError(t, err) + err = writeCtx.SetParam(types.ServicersPerSessionParamName, numServicersPerSession) + require.NoError(t, err) + err = writeCtx.SetParam(types.FishermanPerSessionParamName, numFishermenPerSession) + require.NoError(t, err) + err = writeCtx.Commit([]byte(fmt.Sprintf("proposer_height_%d", 1)), []byte(fmt.Sprintf("quorum_cert_height_%d", 1))) + require.NoError(t, err) + writeCtx.Release() + + // Keep the relay chain and geoZone static, but vary the app and height to verify that the servicers and fishermen vary + relayChain := test_artifacts.DefaultChains[0] + geoZone := "unused_geo" - // validate entropy and randomness - // different height - // different chain + // Sanity check we have 3 apps + require.Len(t, runtimeCfg.GetGenesis().Applications, numApplications) + app1 := runtimeCfg.GetGenesis().Applications[0] + app2 := runtimeCfg.GetGenesis().Applications[1] + app3 := runtimeCfg.GetGenesis().Applications[2] + + var app1PrevServicers, app2PrevServicers, app3PrevServicers []*coreTypes.Actor + var app1PrevFishermen, app2PrevFishermen, app3PrevFishermen []*coreTypes.Actor + + // Commit new blocks for all the heights that failed above + for height := int64(2); height < 10; height++ { + session1, err := utilityMod.GetSession(app1.Address, height, relayChain, geoZone) + require.NoError(t, err) + session2, err := utilityMod.GetSession(app2.Address, height, relayChain, geoZone) + require.NoError(t, err) + session3, err := utilityMod.GetSession(app3.Address, height, relayChain, geoZone) + require.NoError(t, err) + + // All the sessions have the same number of servicers + require.Equal(t, len(session1.Servicers), numServicersPerSession) + require.Equal(t, len(session1.Servicers), len(session2.Servicers)) + require.Equal(t, len(session1.Servicers), len(session3.Servicers)) + + // All the sessions have the same number of fishermen + require.Equal(t, len(session1.Fishermen), numFishermenPerSession) + require.Equal(t, len(session1.Fishermen), len(session2.Fishermen)) + require.Equal(t, len(session1.Fishermen), len(session3.Fishermen)) + + // Assert different services between apps + assertActorsDifference(t, session1.Servicers, session2.Servicers, probabilityOfOverlap) + assertActorsDifference(t, session1.Servicers, session3.Servicers, probabilityOfOverlap) + + // Assert different fishermen between apps + assertActorsDifference(t, session1.Fishermen, session2.Fishermen, probabilityOfOverlap) + assertActorsDifference(t, session1.Fishermen, session3.Fishermen, probabilityOfOverlap) + + // Assert different servicers between heights for the same app + assertActorsDifference(t, app1PrevServicers, session1.Servicers, probabilityOfOverlap) + assertActorsDifference(t, app2PrevServicers, session2.Servicers, probabilityOfOverlap) + assertActorsDifference(t, app3PrevServicers, session3.Servicers, probabilityOfOverlap) + + // Assert different fishermen between heights for the same app + assertActorsDifference(t, app1PrevFishermen, session1.Fishermen, probabilityOfOverlap) + assertActorsDifference(t, app2PrevFishermen, session2.Fishermen, probabilityOfOverlap) + assertActorsDifference(t, app3PrevFishermen, session3.Fishermen, probabilityOfOverlap) + + app1PrevServicers = session1.Servicers + app2PrevServicers = session2.Servicers + app3PrevServicers = session3.Servicers + app1PrevFishermen = session1.Fishermen + app2PrevFishermen = session2.Fishermen + app3PrevFishermen = session3.Fishermen + + // Advance block height + writeCtx, err := persistenceMod.NewRWContext(height) + require.NoError(t, err) + err = writeCtx.Commit([]byte(fmt.Sprintf("proposer_height_%d", height)), []byte(fmt.Sprintf("quorum_cert_height_%d", height))) + require.NoError(t, err) + writeCtx.Release() + + } + // different height -> different actors +} + +func assertActorsDifference(t *testing.T, actors1, actors2 []*coreTypes.Actor, maxSimilarityThreshold float64) { + slice1 := actorsToAdds(actors1) + slice2 := actorsToAdds(actors2) + var commonCount int + for _, s1 := range slice1 { + for _, s2 := range slice2 { + if s1 == s2 { + commonCount++ + break + } + } + } + maxCommonCount := int(maxSimilarityThreshold * float64(len(slice1))) + assert.LessOrEqual(t, commonCount, maxCommonCount, "Slices have more similarity than expected: %v vs max %v", slice1, slice2) +} + +func actorsToAdds(actors []*coreTypes.Actor) []string { + addresses := make([]string, len(actors)) + for i, actor := range actors { + addresses[i] = actor.Address + } + return addresses } func TestSession_GetSession_ServicersAndFishermenCounts_GeoZoneAvailability(t *testing.T) { @@ -351,12 +460,3 @@ func TestSession_GetSession_SessionHeightAndNumber_ModifiedBlocksPerSession(t *t // of blocks per session changes mid session. For example, all existing sessions could go to completion // until the new parameter takes effect. There are open design questions that need to be made. } - -// TODO: Different blocks per session -// What if we change the num blocks -> gets complex -// -> Need to enforce waiting until the end of the current sessions - -// func TestSession_NewSession_BaseCase(t *testing.T) { - -// dispatching session in the future -// dispatching session in the past From 88a36b777c624e15c82beb3cb8f0ae129dfb3a3e Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 18 Apr 2023 12:25:13 -0700 Subject: [PATCH 14/46] Changed getSessionHeight to hydrateSessionHeight --- shared/core/types/proto/session.proto | 35 ++++++++--------- shared/modules/utility_module.go | 5 ++- utility/session.go | 55 ++++++++++++++++----------- utility/session_test.go | 17 +++++---- 4 files changed, 61 insertions(+), 51 deletions(-) diff --git a/shared/core/types/proto/session.proto b/shared/core/types/proto/session.proto index 1f43af305..31d0515a6 100644 --- a/shared/core/types/proto/session.proto +++ b/shared/core/types/proto/session.proto @@ -6,23 +6,20 @@ option go_package = "github.com/pokt-network/pocket/shared/core/types"; import "actor.proto"; +// A deterministic pseudo-random structure that pairs applications to a set of servicers and fishermen +// using on-chain data as a source of entropy message Session { - string id = 1; - int64 height = 2; - string relay_chain = 3; // CONSIDERATION: Should we add a `RelayChain` enum and use it across the board? // TECHDEBT: Do we need backwards with v0? https://docs.pokt.network/supported-blockchains/ - string geo_zone = 4; - core.Actor application = 5; - repeated core.Actor servicers = 6; - repeated core.Actor fishermen = 7; -} - -// TECH_DEBT_IDENTIFIED_IN_THIS_COMMIT: -// 1. Replace []byte with string -// 2. Remove height from Write context in persistence -// 3. Need to add geozone to actors -// 4. Need to generalize persitence functions based on actor type -// 5. Need different protos for each actor -// OPTIMIZE: Query the postgres DB directly to retrieve the hydrated actors -// - requires new endpoints and such -// 6. How to define geozone (uber h3, postgis, aws regions, etc...)? -// 7. How to define relay chains (enum, string, backwards compatability, etc...)? \ No newline at end of file + string id = 1; // a universally unique ID for the session + int64 session_number = 2; // a monotonically increasing number representing the # on the chain + int64 session_height = 3; // the block height at which this session started + int64 num_session_blocks = 4; // the number of blocks the session is valid from + // CONSIDERATION: Should we add a `RelayChain` enum and use it across the board? + // CONSIDERATION: Should a single session support multiple relay chains? + // TECHDEBT: Do we need backwards with v0? https://docs.pokt.network/supported-blockchains/ + string relay_chain = 5; // the relay chain the session is valid for + // CONSIDERATION: Should a single session support multiple geo zones? + string geo_zone = 6; // the target geographic region where the actors are present + core.Actor application = 7; // the application that is being served + repeated core.Actor servicers = 8; // the set of servicers that are serving the application + repeated core.Actor fishermen = 9; // the set of fishermen that are fishing for servicers +} \ No newline at end of file diff --git a/shared/modules/utility_module.go b/shared/modules/utility_module.go index dc34252cb..03d82ded5 100644 --- a/shared/modules/utility_module.go +++ b/shared/modules/utility_module.go @@ -30,8 +30,9 @@ type UtilityModule interface { // IMPROVE: Find opportunities to break this apart as the module matures. HandleUtilityMessage(*anypb.Any) error - // Return a pseudo-random session object for the given application address, session height, relay chain and geo zones - // using on-chain data as he entropy source. + // GetSession returns a deterministic pseudo-random session object for the given application address, session height, + // relay chain and geo zones using on-chain data as the source of entropy. Sessions can be returned for + // any previous height or at most 1 block height into the future. GetSession(appAddr string, sessionHeight int64, relayChain string, geoZone string) (*coreTypes.Session, error) } diff --git a/utility/session.go b/utility/session.go index b50ef4e37..3fc11c95d 100644 --- a/utility/session.go +++ b/utility/session.go @@ -13,22 +13,25 @@ import ( "github.com/pokt-network/pocket/utility/types" ) -// OPTIMIZE: Postgres uses `Twisted Mersenne Twister (TMT)` randomness algorithm. We could potentially look into changing everything a single -// SQL query but need to make sure that it can be implemented in a platform agnostic way. +// OPTIMIZE: Postgres uses a `Twisted Mersenne Twister (TMT)` randomness algorithm. +// We could potentially look into changing everything a single SQL query but need to +// make sure that it can be implemented in a platform agnostic way. +// sessionHydrator is an internal structure used to prepare a Session returned by `GetSession` below type sessionHydrator struct { logger modules.Logger - // the session being hydrated and returned + // The session being hydrated and returned session *coreTypes.Session - // A helper that keeps a hex decoded copy of `session.Id` - sessionIdBz []byte - - // Caches the read context to avoid opening too many during session hydration + // Caches a readCtx to avoid draining to many connections to the database readCtx modules.PersistenceReadContext + + // A redundant helper that keeps a hex decoded copy of `session.Id` + sessionIdBz []byte } +// GetSession is an implementation of the exposed `UtilityModule.GetSession` function func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geoZone string) (*coreTypes.Session, error) { persistenceModule := m.GetBus().GetPersistenceModule() readCtx, err := persistenceModule.NewReadContext(height) @@ -38,7 +41,6 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geo defer readCtx.Release() session := &coreTypes.Session{ - Height: height, RelayChain: relayChain, GeoZone: geoZone, } @@ -49,6 +51,10 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geo readCtx: readCtx, } + if err := sessionHydrator.hydrateSessionHeight(height); err != nil { + return nil, err + } + if err := sessionHydrator.hydrateSessionApplication(appAddr); err != nil { return nil, err } @@ -72,27 +78,30 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geo return sessionHydrator.session, nil } -// getSessionHeight returns the height at which the session started given the current block height -func getSessionHeight(readCtx modules.PersistenceReadContext, blockHeight int64) (int64, int64, error) { - numBlocksPerSession, err := readCtx.GetIntParam(types.BlocksPerSessionParamName, blockHeight) +// hydrateSessionHeight returns the height at which the session started given the current block height +func (s *sessionHydrator) hydrateSessionHeight(blockHeight int64) error { + numBlocksPerSession, err := s.readCtx.GetIntParam(types.BlocksPerSessionParamName, blockHeight) if err != nil { - return 0, 0, err + return err } + s.session.NumSessionBlocks = int64(numBlocksPerSession) numBlocksAheadOfSession := blockHeight % int64(numBlocksPerSession) - sessionNumber := int64(blockHeight / int64(numBlocksPerSession)) + s.session.SessionNumber = int64(blockHeight / int64(numBlocksPerSession)) if numBlocksAheadOfSession == 0 { - return blockHeight, sessionNumber, nil + s.session.SessionHeight = blockHeight + } else { + s.session.SessionHeight = blockHeight - numBlocksAheadOfSession } - return (blockHeight - numBlocksAheadOfSession), sessionNumber, nil + return nil } // use the seed information to determine a SHA3Hash that is used to find the closest N actors based // by comparing the sessionKey with the actors' public key func (s *sessionHydrator) hydrateSessionId() error { sessionHeightBz := make([]byte, 8) - binary.LittleEndian.PutUint64(sessionHeightBz, uint64(s.session.Height)) - prevHash, err := s.readCtx.GetBlockHash(s.session.Height - 1) + binary.LittleEndian.PutUint64(sessionHeightBz, uint64(s.session.SessionHeight)) + prevHash, err := s.readCtx.GetBlockHash(s.session.SessionHeight - 1) if err != nil { return err } @@ -112,7 +121,7 @@ func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { if err != nil { return err } - s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.Height) + s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.SessionHeight) return err } @@ -123,7 +132,7 @@ func (s *sessionHydrator) validateApplicationDispatch() error { if err != nil { return err } - s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.Height) + s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.SessionHeight) return err } @@ -136,13 +145,13 @@ func (s *sessionHydrator) validateApplicationDispatch() error { // 2) calls `pseudoRandomSelection(servicers, numberOfNodesPerSession)` func (s *sessionHydrator) hydrateSessionServicers() error { // number of servicers per session at this height - numServicers, err := s.readCtx.GetIntParam(types.ServicersPerSessionParamName, s.session.Height) + numServicers, err := s.readCtx.GetIntParam(types.ServicersPerSessionParamName, s.session.SessionHeight) if err != nil { return err } // returns all the staked servicers at this session height - servicers, err := s.readCtx.GetAllServicers(s.session.Height) + servicers, err := s.readCtx.GetAllServicers(s.session.SessionHeight) if err != nil { return err } @@ -184,13 +193,13 @@ func (s *sessionHydrator) hydrateSessionServicers() error { // 2) calls `pseudoRandomSelection(fishermen, numberOfFishPerSession)` func (s *sessionHydrator) hydrateSessionFishermen() error { // number of fisherman per session at this height - numFishermen, err := s.readCtx.GetIntParam(types.FishermanPerSessionParamName, s.session.Height) + numFishermen, err := s.readCtx.GetIntParam(types.FishermanPerSessionParamName, s.session.SessionHeight) if err != nil { return err } // returns all the staked fisherman at this session height - fishermen, err := s.readCtx.GetAllFishermen(s.session.Height) + fishermen, err := s.readCtx.GetAllFishermen(s.session.SessionHeight) if err != nil { return err } diff --git a/utility/session_test.go b/utility/session_test.go index 8c2e81c72..b4cb51c25 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -38,7 +38,7 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) session, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) require.NoError(t, err) require.Equal(t, expectedSessionId, session.Id) - require.Equal(t, height, session.Height) + require.Equal(t, height, session.SessionHeight) require.Equal(t, relayChain, session.RelayChain) require.Equal(t, geoZone, session.GeoZone) require.Equal(t, app.Address, session.Application.Address) @@ -79,7 +79,7 @@ func TestSession_GetSession_InvalidFutureSession(t *testing.T) { // Successfully get a session at height=1 session, err := utilityMod.GetSession(app.Address, latestCommitted+1, relayChain, geoZone) require.NoError(t, err) - require.Equal(t, latestCommitted+1, session.Height) + require.Equal(t, latestCommitted+1, session.SessionHeight) // Expect an error for a few heights into the future for height := latestCommitted + 2; height < 10; height++ { @@ -277,6 +277,10 @@ func TestSession_GetSession_SessionHeightAndNumber_StaticBlocksPerSession(t *tes require.NoError(t, err) defer writeCtx.Release() + s := &sessionHydrator{ + session: &coreTypes.Session{}, + } + tests := []struct { name string numBlocksPerSession int64 @@ -319,10 +323,11 @@ func TestSession_GetSession_SessionHeightAndNumber_StaticBlocksPerSession(t *tes err := writeCtx.SetParam(types.BlocksPerSessionParamName, tt.numBlocksPerSession) require.NoError(t, err) - sessionHeight, sessionNumber, err := getSessionHeight(writeCtx, tt.haveBlockHeight) + err = s.hydrateSessionHeight(tt.haveBlockHeight) require.NoError(t, err) - require.Equal(t, tt.wantSessionHeight, sessionHeight) - require.Equal(t, tt.wantSessionNumber, sessionNumber) + require.Equal(t, tt.numBlocksPerSession, s.session.NumSessionBlocks) + require.Equal(t, tt.wantSessionHeight, s.session.SessionHeight) + require.Equal(t, tt.wantSessionNumber, s.session.SessionNumber) }) } } @@ -416,9 +421,7 @@ func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { err = writeCtx.Commit([]byte(fmt.Sprintf("proposer_height_%d", height)), []byte(fmt.Sprintf("quorum_cert_height_%d", height))) require.NoError(t, err) writeCtx.Release() - } - // different height -> different actors } func assertActorsDifference(t *testing.T, actors1, actors2 []*coreTypes.Actor, maxSimilarityThreshold float64) { From 99b25a3c935430fbc59bbac616ccce34fba4f778 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 18 Apr 2023 15:52:15 -0700 Subject: [PATCH 15/46] Updated DefaultNumBlockPerSession=1 and tests passing again --- build/localnet/manifests/configs.yaml | 2 +- persistence/test/state_test.go | 6 +- persistence/types/gov_test.go | 2 +- runtime/test_artifacts/defaults.go | 2 +- utility/module_test.go | 8 +++ utility/session.go | 30 ++++++---- utility/session_test.go | 85 +++++++++++++++------------ 7 files changed, 81 insertions(+), 54 deletions(-) diff --git a/build/localnet/manifests/configs.yaml b/build/localnet/manifests/configs.yaml index 510489dde..7610f596c 100644 --- a/build/localnet/manifests/configs.yaml +++ b/build/localnet/manifests/configs.yaml @@ -4124,7 +4124,7 @@ data: } ], "params": { - "blocks_per_session": 4, + "blocks_per_session": 1, "app_minimum_stake": "15000000000", "app_max_chains": 15, "app_session_tokens_multiplier": 100, diff --git a/persistence/test/state_test.go b/persistence/test/state_test.go index b43faf2f7..89ae893a7 100644 --- a/persistence/test/state_test.go +++ b/persistence/test/state_test.go @@ -44,9 +44,9 @@ func TestStateHash_DeterministicStateWhenUpdatingAppStake(t *testing.T) { // logic changes, these hashes will need to be updated based on the test output. // TODO: Add an explicit updateSnapshots flag to the test to make this more clear. stateHashes := []string{ - "93be62191cbc09fea4cf2046bea8e55ccf5e4c6fa7b483a55470d1bca85c3732", - "c2ed842d32b099de26dba1fa6d14f367504daf6b914076004aa3df5f7858e5be", - "a60fef8fe1b3c5b208041f43cb14641167eeb67faee730ae7d0689765bb9d487", + "4b5da068a4792c30a73c36d00f01af12262e9d753992f2d56d4ca64f3f2ac894", + "0565768b758981f511a71131a63710b9e43fa51095de658a1454f3b8b7895a15", + "f0796a7072c402e22abd0d2ef5c5a199aa5b6cb208824af24b58c4c777fc3284", } stakeAmount := initialStakeAmount diff --git a/persistence/types/gov_test.go b/persistence/types/gov_test.go index d644a0ecc..6a07f7ede 100644 --- a/persistence/types/gov_test.go +++ b/persistence/types/gov_test.go @@ -23,7 +23,7 @@ func TestInsertParams(t *testing.T) { params: test_artifacts.DefaultParams(), height: DefaultBigInt, }, - want: "INSERT INTO params VALUES ('blocks_per_session', -1, 'BIGINT', 4)," + + want: "INSERT INTO params VALUES ('blocks_per_session', -1, 'BIGINT', 1)," + "('app_minimum_stake', -1, 'STRING', '15000000000')," + "('app_max_chains', -1, 'SMALLINT', 15)," + "('app_session_tokens_multiplier', -1, 'BIGINT', 100)," + diff --git a/runtime/test_artifacts/defaults.go b/runtime/test_artifacts/defaults.go index 0aebae684..c9c8f3074 100644 --- a/runtime/test_artifacts/defaults.go +++ b/runtime/test_artifacts/defaults.go @@ -28,7 +28,7 @@ var ( func DefaultParams() *genesis.Params { return &genesis.Params{ - BlocksPerSession: 4, + BlocksPerSession: 1, AppMinimumStake: utils.BigIntToString(big.NewInt(15000000000)), AppMaxChains: 15, AppSessionTokensMultiplier: 100, diff --git a/utility/module_test.go b/utility/module_test.go index 17c5bcdb9..3c2d9b870 100644 --- a/utility/module_test.go +++ b/utility/module_test.go @@ -10,6 +10,7 @@ import ( "github.com/pokt-network/pocket/runtime/configs" "github.com/pokt-network/pocket/runtime/test_artifacts" "github.com/pokt-network/pocket/runtime/test_artifacts/keygen" + "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/shared/modules" "github.com/stretchr/testify/require" ) @@ -63,6 +64,13 @@ func prepareEnvironment( testUtilityMod := newTestUtilityModule(bus) testUtilityMod.Start() + // Reset to genesis + err = testPersistenceMod.HandleDebugMessage(&messaging.DebugMessage{ + Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, + Message: nil, + }) + require.NoError(t, err) + t.Cleanup(func() { teardownDeterministicKeygen() testPersistenceMod.Stop() diff --git a/utility/session.go b/utility/session.go index 3fc11c95d..fed880b89 100644 --- a/utility/session.go +++ b/utility/session.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "math" "math/rand" "github.com/pokt-network/pocket/logger" @@ -21,6 +22,9 @@ import ( type sessionHydrator struct { logger modules.Logger + // The height of the request for which the session is being hydrated + blockHeight int64 + // The session being hydrated and returned session *coreTypes.Session @@ -46,9 +50,10 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geo } sessionHydrator := &sessionHydrator{ - logger: m.logger.With().Str("source", "sessionHydrator").Logger(), - session: session, - readCtx: readCtx, + logger: m.logger.With().Str("source", "sessionHydrator").Logger(), + blockHeight: height, + session: session, + readCtx: readCtx, } if err := sessionHydrator.hydrateSessionHeight(height); err != nil { @@ -101,7 +106,8 @@ func (s *sessionHydrator) hydrateSessionHeight(blockHeight int64) error { func (s *sessionHydrator) hydrateSessionId() error { sessionHeightBz := make([]byte, 8) binary.LittleEndian.PutUint64(sessionHeightBz, uint64(s.session.SessionHeight)) - prevHash, err := s.readCtx.GetBlockHash(s.session.SessionHeight - 1) + prevHashHeight := int64(math.Max(float64(s.session.SessionHeight)-1, 0)) + prevHash, err := s.readCtx.GetBlockHash(prevHashHeight) if err != nil { return err } @@ -125,15 +131,17 @@ func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { return err } -// Validate the the application can dispatch a session at the request geo-zone and for the request relay chain +// Validate the the application can dispatch a session at the requested geo-zone and for the request relay chain func (s *sessionHydrator) validateApplicationDispatch() error { + // if s.session.Application.Chains { // TECHDEBT: We can remove this decoding process once we use `strings` instead of `[]byte` for addresses - addr, err := hex.DecodeString(s.session.Application.Address) - if err != nil { - return err - } - s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.SessionHeight) - return err + // addr, err := hex.DecodeString(s.session.Application.Address) + // if err != nil { + // return err + // } + // s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.SessionHeight) + // return err + return nil } // uses the current 'world state' to determine the servicers in the session diff --git a/utility/session_test.go b/utility/session_test.go index b4cb51c25..c2fdb805f 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -2,6 +2,7 @@ package utility import ( "fmt" + "math" "testing" "github.com/pokt-network/pocket/runtime/test_artifacts" @@ -23,7 +24,7 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) geoZone := "unused_geo" numFishermen := 1 numServicers := 1 - expectedSessionId := "3545185ff1519bf7706ec8f828d16525830d3c0dcc2425c40db597ee6b67b8bc" // needs to be manually updated if business logic changes + expectedSessionId := "5acf559f1a3faf3bea7eb692fe51bc1e2e5fb687ede0a6daa7d42399da4aa82b" // needs to be manually updated if business logic changes runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, numServicers, 1, numFishermen) @@ -264,8 +265,8 @@ func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *tes t.Run(tt.name, func(t *testing.T) { session, err := utilityMod.GetSession(app.Address, 2, tt.chain, geoZone) require.NoError(t, err) - require.Equal(t, tt.wantServicerCount, len(session.Servicers)) - require.Equal(t, tt.wantFishermanCount, len(session.Fishermen)) + require.Len(t, session.Servicers, tt.wantServicerCount) + require.Len(t, session.Fishermen, tt.wantFishermanCount) }) } } @@ -273,59 +274,69 @@ func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *tes func TestSession_GetSession_SessionHeightAndNumber_StaticBlocksPerSession(t *testing.T) { _, _, persistenceMod := prepareEnvironment(t, 5, 1, 1, 1) + // Note that we are using an ephemeral write context at the genesis block (height=0). + // This cannot be committed but useful for the test. writeCtx, err := persistenceMod.NewRWContext(0) require.NoError(t, err) defer writeCtx.Release() s := &sessionHydrator{ session: &coreTypes.Session{}, + readCtx: writeCtx, } tests := []struct { - name string - numBlocksPerSession int64 - haveBlockHeight int64 - wantSessionHeight int64 - wantSessionNumber int64 + name string + setNumBlocksPerSession int64 + provideBlockHeight int64 + wantSessionHeight int64 + wantSessionNumber int64 }{ { - name: "block is at start of first session", - numBlocksPerSession: 5, - haveBlockHeight: 5, - wantSessionHeight: 5, - wantSessionNumber: 1, + name: "genesis block", + setNumBlocksPerSession: 5, + provideBlockHeight: 0, + wantSessionHeight: 0, + wantSessionNumber: 0, + }, + { + name: "block is at start of first session", + setNumBlocksPerSession: 5, + provideBlockHeight: 5, + wantSessionHeight: 5, + wantSessionNumber: 1, }, { - name: "block is right before start of first session", - numBlocksPerSession: 5, - haveBlockHeight: 4, - wantSessionHeight: 0, - wantSessionNumber: 0, + name: "block is right before start of first session", + setNumBlocksPerSession: 5, + provideBlockHeight: 4, + wantSessionHeight: 0, + wantSessionNumber: 0, }, { - name: "block is right after start of first session", - numBlocksPerSession: 5, - haveBlockHeight: 6, - wantSessionHeight: 5, - wantSessionNumber: 1, + name: "block is right after start of first session", + setNumBlocksPerSession: 5, + provideBlockHeight: 6, + wantSessionHeight: 5, + wantSessionNumber: 1, }, { - name: "block is at start of second session", - numBlocksPerSession: 5, - haveBlockHeight: 10, - wantSessionHeight: 10, - wantSessionNumber: 2, + name: "block is at start of second session", + setNumBlocksPerSession: 5, + provideBlockHeight: 10, + wantSessionHeight: 10, + wantSessionNumber: 2, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := writeCtx.SetParam(types.BlocksPerSessionParamName, tt.numBlocksPerSession) + err := writeCtx.SetParam(types.BlocksPerSessionParamName, tt.setNumBlocksPerSession) require.NoError(t, err) - err = s.hydrateSessionHeight(tt.haveBlockHeight) + err = s.hydrateSessionHeight(tt.provideBlockHeight) require.NoError(t, err) - require.Equal(t, tt.numBlocksPerSession, s.session.NumSessionBlocks) + require.Equal(t, tt.setNumBlocksPerSession, s.session.NumSessionBlocks) require.Equal(t, tt.wantSessionHeight, s.session.SessionHeight) require.Equal(t, tt.wantSessionNumber, s.session.SessionNumber) }) @@ -340,8 +351,8 @@ func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { numFishermenPerSession := 10 // make them equal for simplicity // Determine probability of overlap using combinatorics - numChoices := combin.GeneralizedBinomial(float64(numServicers), float64(numServicersPerSession)) // numServicers C numServicersPerSession - numChoicesRemaining := combin.GeneralizedBinomial(float64(numServicers), float64(numServicersPerSession)) // (numServicers - numServicersPerSession) C numServicersPerSession + numChoices := combin.GeneralizedBinomial(float64(numServicers), float64(numServicersPerSession)) // numServicers C numServicersPerSession + numChoicesRemaining := combin.GeneralizedBinomial(float64(numServicers-numServicersPerSession), float64(numServicersPerSession)) // (numServicers - numServicersPerSession) C numServicersPerSession probabilityOfOverlap := (numChoices - numChoicesRemaining) / numChoices numApplications := 3 @@ -381,12 +392,12 @@ func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { require.NoError(t, err) // All the sessions have the same number of servicers - require.Equal(t, len(session1.Servicers), numServicersPerSession) + require.Len(t, session1.Servicers, numServicersPerSession) require.Equal(t, len(session1.Servicers), len(session2.Servicers)) require.Equal(t, len(session1.Servicers), len(session3.Servicers)) // All the sessions have the same number of fishermen - require.Equal(t, len(session1.Fishermen), numFishermenPerSession) + require.Len(t, session1.Fishermen, numFishermenPerSession) require.Equal(t, len(session1.Fishermen), len(session2.Fishermen)) require.Equal(t, len(session1.Fishermen), len(session3.Fishermen)) @@ -427,7 +438,7 @@ func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { func assertActorsDifference(t *testing.T, actors1, actors2 []*coreTypes.Actor, maxSimilarityThreshold float64) { slice1 := actorsToAdds(actors1) slice2 := actorsToAdds(actors2) - var commonCount int + var commonCount float64 for _, s1 := range slice1 { for _, s2 := range slice2 { if s1 == s2 { @@ -436,7 +447,7 @@ func assertActorsDifference(t *testing.T, actors1, actors2 []*coreTypes.Actor, m } } } - maxCommonCount := int(maxSimilarityThreshold * float64(len(slice1))) + maxCommonCount := math.Round(maxSimilarityThreshold * float64(len(slice1))) assert.LessOrEqual(t, commonCount, maxCommonCount, "Slices have more similarity than expected: %v vs max %v", slice1, slice2) } From 23f74dde166eb4ab6c715a6815485bec95768a11 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 18 Apr 2023 19:25:37 -0700 Subject: [PATCH 16/46] Self review --- build/docs/CHANGELOG.md | 5 + persistence/actor.go | 3 +- persistence/docs/CHANGELOG.md | 6 ++ runtime/docs/CHANGELOG.md | 6 ++ runtime/genesis/proto/genesis.proto | 1 - runtime/test_artifacts/generator.go | 3 +- shared/CHANGELOG.md | 14 ++- utility/doc/CHANGELOG.md | 8 +- utility/module_test.go | 17 +-- utility/session.go | 102 +++++++++--------- utility/session_test.go | 156 ++++++++++++++++++---------- 11 files changed, 195 insertions(+), 126 deletions(-) diff --git a/build/docs/CHANGELOG.md b/build/docs/CHANGELOG.md index c5c7cf393..7d7389833 100644 --- a/build/docs/CHANGELOG.md +++ b/build/docs/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.36] - 2023-04-18 + +- Added a `fisherman_per_session` governance parameter +- Updated the default `blocks_per_session` from `4` to `1` + ## [0.0.0.35] - 2023-04-17 - Removed runtime/configs.Config#UseLibp2p field diff --git a/persistence/actor.go b/persistence/actor.go index 43ad43141..ed9079b1f 100644 --- a/persistence/actor.go +++ b/persistence/actor.go @@ -7,7 +7,6 @@ import ( coreTypes "github.com/pokt-network/pocket/shared/core/types" ) -// TODO_IN_THIS_COMMIT: Add tests for this function func (p *PostgresContext) GetActor(actorType coreTypes.ActorType, address []byte, height int64) (*coreTypes.Actor, error) { var schema types.ProtocolActorSchema switch actorType { @@ -133,7 +132,7 @@ func (p *PostgresContext) GetAllFishermen(height int64) (f []*coreTypes.Actor, e return } -// OPTIMIZE: Ideally we should have a single query that returns all actors. +// OPTIMIZE: There is an opportunity to have one SQL query returning all the actorsp func (p *PostgresContext) GetAllStakedActors(height int64) (allActors []*coreTypes.Actor, err error) { type actorGetter func(height int64) ([]*coreTypes.Actor, error) actorGetters := []actorGetter{p.GetAllValidators, p.GetAllServicers, p.GetAllFishermen, p.GetAllApps} diff --git a/persistence/docs/CHANGELOG.md b/persistence/docs/CHANGELOG.md index 4d97e8aa0..18caffc8f 100644 --- a/persistence/docs/CHANGELOG.md +++ b/persistence/docs/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.49] - 2023-04-18 + +- Implemented a new `GetActor` persistence modular functoin +- Added `fisherman_per_session` parameter +- Minor code and comment cleanup + ## [0.0.0.49] - 2023-04-14 - Index transactions in the `TxIndexer` by sender and recipient using the height and block index of the transactions diff --git a/runtime/docs/CHANGELOG.md b/runtime/docs/CHANGELOG.md index 662cd6c21..2ceb55428 100644 --- a/runtime/docs/CHANGELOG.md +++ b/runtime/docs/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.34] - 2023-04-17 + +- Consolidated files for defaults together +- Updated `BlocksPerSession` default to 1 +- Added an ability to add options to the `NewGenesisState` helper for more thorough testing + ## [0.0.0.33] - 2023-04-17 - Removed `runtime/configs.Config#UseLibp2p` field diff --git a/runtime/genesis/proto/genesis.proto b/runtime/genesis/proto/genesis.proto index d20764c39..1379f4b51 100644 --- a/runtime/genesis/proto/genesis.proto +++ b/runtime/genesis/proto/genesis.proto @@ -23,7 +23,6 @@ message GenesisState { // TECHDEBT: Explore a more general purpose "feature flag" approach that makes it easy to add/remove // parameters and add activation heights for them as well. -// n message Params { //@gotags: pokt:"val_type=BIGINT,owner=blocks_per_session_owner" int32 blocks_per_session = 1; diff --git a/runtime/test_artifacts/generator.go b/runtime/test_artifacts/generator.go index 537df9b73..9d8887d23 100644 --- a/runtime/test_artifacts/generator.go +++ b/runtime/test_artifacts/generator.go @@ -18,7 +18,7 @@ import ( type GenesisOption func(*genesis.GenesisState) -// IMPROVE: Generate a proper genesis suite in the future. +// IMPROVE: Extend the utilities here into a proper genesis suite in the future. func NewGenesisState(numValidators, numServicers, numApplications, numFisherman int, genesisOpts ...GenesisOption) (genesisState *genesis.GenesisState, validatorPrivateKeys []string) { applications, appPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_APP, numApplications, DefaultChains) validators, validatorPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_VAL, numValidators, nil) @@ -116,6 +116,7 @@ func newAccountsWithKeys(privateKeys []string) (accounts []*coreTypes.Account) { return accounts } +//nolint:unused func newAccounts(numActors int) (accounts []*coreTypes.Account) { for i := 0; i < numActors; i++ { _, _, addr := keygen.GetInstance().Next() diff --git a/shared/CHANGELOG.md b/shared/CHANGELOG.md index 9be09b733..e3733605c 100644 --- a/shared/CHANGELOG.md +++ b/shared/CHANGELOG.md @@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.53] - 2023-04-18 + +- Added a new `Session` protobuf +- Added a `GetActor` function to the Persistence module interface +- Added a `GetSession` function to the Utility module interface + ## [0.0.0.52] - 2023-04-17 -- Removed *temporary* `shared/p2p` package; consolidated into `p2p` +- Removed _temporary_ `shared/p2p` package; consolidated into `p2p` ## [0.0.0.51] - 2023-04-13 @@ -19,9 +25,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `modules.ModuleFactoryWithOptions` interface - Added factory interfaces: - - `modules.FactoryWithRequired` - - `modules.FactoryWithOptions` - - `modules.FactoryWithRequiredAndOptions` + - `modules.FactoryWithRequired` + - `modules.FactoryWithOptions` + - `modules.FactoryWithRequiredAndOptions` - Embedded `ModuleFactoryWithOptions` in `Module` interface - Switched mock generation to use reflect mode for effected interfaces (embedders) diff --git a/utility/doc/CHANGELOG.md b/utility/doc/CHANGELOG.md index 4db81a6ff..329356ff3 100644 --- a/utility/doc/CHANGELOG.md +++ b/utility/doc/CHANGELOG.md @@ -7,10 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.38] - 2023-04-18 + +- Introduced testing helper to test the `UtilityModule` interface implementation +- Implemented the `GetSession` function from the Utility module interface +- Added a `sessionHydrator` structure used to populate a new session + ## [0.0.0.37] - 2023-04-13 - Remove utility specific `TxResult` protobuf and interface -- Utlise the `TxResult` protobuf in `shared/core/types` +- Utilize the `TxResult` protobuf in `shared/core/types` ## [0.0.0.36] - 2023-04-07 diff --git a/utility/module_test.go b/utility/module_test.go index 3c2d9b870..3e7b3c3f0 100644 --- a/utility/module_test.go +++ b/utility/module_test.go @@ -44,9 +44,10 @@ func newTestPersistenceModule(bus modules.Bus) modules.PersistenceModule { return persistenceMod.(modules.PersistenceModule) } +// Prepares a runtime environment for testing along with a genesis state, a persistence module and a utility module func prepareEnvironment( t *testing.T, - numValidators, + numValidators, // nolint:unparam // we are not currently modifying parameter but want to keep it modifiable in the future numServicers, numApplications, numFisherman int, @@ -59,12 +60,14 @@ func prepareEnvironment( require.NoError(t, err) testPersistenceMod := newTestPersistenceModule(bus) - testPersistenceMod.Start() + err = testPersistenceMod.Start() + require.NoError(t, err) testUtilityMod := newTestUtilityModule(bus) - testUtilityMod.Start() + err = testUtilityMod.Start() + require.NoError(t, err) - // Reset to genesis + // Reset database to genesis before every test err = testPersistenceMod.HandleDebugMessage(&messaging.DebugMessage{ Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, Message: nil, @@ -73,8 +76,10 @@ func prepareEnvironment( t.Cleanup(func() { teardownDeterministicKeygen() - testPersistenceMod.Stop() - testUtilityMod.Stop() + err := testPersistenceMod.Stop() + require.NoError(t, err) + err = testUtilityMod.Stop() + require.NoError(t, err) }) return runtimeCfg, testUtilityMod, testPersistenceMod diff --git a/utility/session.go b/utility/session.go index fed880b89..0d10e8bad 100644 --- a/utility/session.go +++ b/utility/session.go @@ -14,10 +14,6 @@ import ( "github.com/pokt-network/pocket/utility/types" ) -// OPTIMIZE: Postgres uses a `Twisted Mersenne Twister (TMT)` randomness algorithm. -// We could potentially look into changing everything a single SQL query but need to -// make sure that it can be implemented in a platform agnostic way. - // sessionHydrator is an internal structure used to prepare a Session returned by `GetSession` below type sessionHydrator struct { logger modules.Logger @@ -28,10 +24,10 @@ type sessionHydrator struct { // The session being hydrated and returned session *coreTypes.Session - // Caches a readCtx to avoid draining to many connections to the database + // Caches a readCtx to avoid draining too many connections to the database readCtx modules.PersistenceReadContext - // A redundant helper that keeps a hex decoded copy of `session.Id` + // A redundant helper that maintains a hex decoded copy of `session.Id` used for session hydration sessionIdBz []byte } @@ -64,11 +60,11 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geo return nil, err } - if err := sessionHydrator.validateApplicationDispatch(); err != nil { + if err := sessionHydrator.validateApplicationSession(); err != nil { return nil, err } - if err := sessionHydrator.hydrateSessionId(); err != nil { + if err := sessionHydrator.hydrateSessionID(); err != nil { return nil, err } @@ -83,44 +79,22 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geo return sessionHydrator.session, nil } -// hydrateSessionHeight returns the height at which the session started given the current block height +// hydrateSessionHeight hydrates the height at which the session started given the current block height func (s *sessionHydrator) hydrateSessionHeight(blockHeight int64) error { numBlocksPerSession, err := s.readCtx.GetIntParam(types.BlocksPerSessionParamName, blockHeight) if err != nil { return err } - s.session.NumSessionBlocks = int64(numBlocksPerSession) - numBlocksAheadOfSession := blockHeight % int64(numBlocksPerSession) - s.session.SessionNumber = int64(blockHeight / int64(numBlocksPerSession)) - if numBlocksAheadOfSession == 0 { - s.session.SessionHeight = blockHeight - } else { - s.session.SessionHeight = blockHeight - numBlocksAheadOfSession - } - return nil -} -// use the seed information to determine a SHA3Hash that is used to find the closest N actors based -// by comparing the sessionKey with the actors' public key -func (s *sessionHydrator) hydrateSessionId() error { - sessionHeightBz := make([]byte, 8) - binary.LittleEndian.PutUint64(sessionHeightBz, uint64(s.session.SessionHeight)) - prevHashHeight := int64(math.Max(float64(s.session.SessionHeight)-1, 0)) - prevHash, err := s.readCtx.GetBlockHash(prevHashHeight) - if err != nil { - return err - } - prevHashBz, err := hex.DecodeString(prevHash) - appPubKeyBz := []byte(s.session.Application.PublicKey) - relayChainBz := []byte(string(s.session.RelayChain)) - geoZoneBz := []byte(s.session.GeoZone) - s.sessionIdBz = concat(sessionHeightBz, prevHashBz, geoZoneBz, relayChainBz, appPubKeyBz) - s.session.Id = crypto.GetHashStringFromBytes(s.sessionIdBz) + s.session.NumSessionBlocks = int64(numBlocksPerSession) + s.session.SessionNumber = int64(blockHeight / int64(numBlocksPerSession)) + s.session.SessionHeight = blockHeight - numBlocksAheadOfSession return nil } -// Uses the current 'world state' to determine the full application metadata based on its address at the current height +// hydrateSessionApplication hydrates the full Application actor based on the address the session is being +// dispatched for. func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { // TECHDEBT: We can remove this decoding process once we use `strings` instead of `[]byte` for addresses addr, err := hex.DecodeString(appAddr) @@ -131,8 +105,8 @@ func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { return err } -// Validate the the application can dispatch a session at the requested geo-zone and for the request relay chain -func (s *sessionHydrator) validateApplicationDispatch() error { +// validateApplicationSession validates that the application can dispatch a session at the requested geo zone and for the request relay chain +func (s *sessionHydrator) validateApplicationSession() error { // if s.session.Application.Chains { // TECHDEBT: We can remove this decoding process once we use `strings` instead of `[]byte` for addresses // addr, err := hex.DecodeString(s.session.Application.Address) @@ -144,13 +118,29 @@ func (s *sessionHydrator) validateApplicationDispatch() error { return nil } -// uses the current 'world state' to determine the servicers in the session -// 1) get an ordered list of the public keys of servicers who are: -// - actively staked -// - staked within geo-zone (or closest geo-zones) -// - staked for relay-chain -// -// 2) calls `pseudoRandomSelection(servicers, numberOfNodesPerSession)` +// hydrateSessionID use both session and on-chain data to determine a unique session ID +func (s *sessionHydrator) hydrateSessionID() error { + sessionHeightBz := make([]byte, 8) + binary.LittleEndian.PutUint64(sessionHeightBz, uint64(s.session.SessionHeight)) + + prevHashHeight := int64(math.Max(float64(s.session.SessionHeight)-1, 0)) + prevHash, err := s.readCtx.GetBlockHash(prevHashHeight) + if err != nil { + return err + } + prevHashBz, err := hex.DecodeString(prevHash) + + appPubKeyBz := []byte(s.session.Application.PublicKey) + relayChainBz := []byte(string(s.session.RelayChain)) + geoZoneBz := []byte(s.session.GeoZone) + + s.sessionIdBz = concat(sessionHeightBz, prevHashBz, geoZoneBz, relayChainBz, appPubKeyBz) + s.session.Id = crypto.GetHashStringFromBytes(s.sessionIdBz) + + return nil +} + +// hydrateSessionServicers func (s *sessionHydrator) hydrateSessionServicers() error { // number of servicers per session at this height numServicers, err := s.readCtx.GetIntParam(types.ServicersPerSessionParamName, s.session.SessionHeight) @@ -164,18 +154,17 @@ func (s *sessionHydrator) hydrateSessionServicers() error { return err } - // OPTIMIZE: Update the persistence module to allow for querying for filtered servicers directly - // Determine the servicers for this session + // OPTIMIZE: Consider updating the persistence module so a single SQL query can retrieve all of the actors at once. candidateServicers := make([]*coreTypes.Actor, 0) for _, servicer := range servicers { - // Sanity check the servicer is not paused or unstaking + // Sanity check the servicer is not paused, jailed or unstaking if !(servicer.PausedHeight == -1 && servicer.UnstakingHeight == -1) { return fmt.Errorf("hydrateSessionServicers should not have encountered a paused or unstaking servicer: %s", servicer.Address) } - // TODO(#XXX): Add GeoZone filtering + // TODO(#XXX): Filter by geo-zone - // OPTIMIZE: If this was a map, we could have avoided the loop over chains + // OPTIMIZE: If this was a map[string]struct{}, we could have avoided the loop var chain string for _, chain = range servicer.Chains { if chain != s.session.RelayChain { @@ -212,18 +201,17 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { return err } - // OPTIMIZE: Update the persistence module to allow for querying for filtered fishermen directly - // Determine the fishermen for this session + // OPTIMIZE: Consider updating the persistence module so a single SQL query can retrieve all of the actors at once. candidateFishermen := make([]*coreTypes.Actor, 0) for _, fisherman := range fishermen { - // Sanity check the fisherman is not paused or unstaking + // Sanity check the fisherman is not paused, jailed or unstaking if !(fisherman.PausedHeight == -1 && fisherman.UnstakingHeight == -1) { return fmt.Errorf("hydrateSessionFishermen should not have encountered a paused or unstaking fisherman: %s", fisherman.Address) } - // TODO_IN_THIS_COMMIT: if sfisherman.GeoZone includes session.GeoZone + // TODO(#XXX): Filter by geo-zone - // OPTIMIZE: If this was a map, we could have avoided the loop over chains + // OPTIMIZE: If this was a map[string]struct{}, we could have avoided the loop var chain string for _, chain = range fisherman.Chains { if chain != s.session.RelayChain { @@ -263,6 +251,10 @@ func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, session return actors } +// OPTIMIZE: Postgres uses a `Twisted Mersenne Twister (TMT)` randomness algorithm. +// We could potentially look into changing everything into a single SQL query but +// would nee dto verify that it can be implemented in a platform agnostic way. + // uniqueRandomIndices returns a map of `numIndices` unique random numbers less than `maxIndex` // seeded by `seed`. // NB: A map pointing to empty structs is used to simulate set behaviour. diff --git a/utility/session_test.go b/utility/session_test.go index c2fdb805f..89059e7f6 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -36,10 +36,13 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) require.Len(t, runtimeCfg.GetGenesis().Servicers, 1) servicer := runtimeCfg.GetGenesis().Servicers[0] + // Verify some of the session defaults session, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) require.NoError(t, err) require.Equal(t, expectedSessionId, session.Id) require.Equal(t, height, session.SessionHeight) + require.Equal(t, int64(1), session.SessionNumber) + require.Equal(t, int64(1), session.NumSessionBlocks) require.Equal(t, relayChain, session.RelayChain) require.Equal(t, geoZone, session.GeoZone) require.Equal(t, app.Address, session.Application.Address) @@ -57,62 +60,61 @@ func TestSession_GetSession_InvalidApplication(t *testing.T) { app := runtimeCfg.GetGenesis().Applications[0] // Create a new app address - pk, _ := crypto.GeneratePrivateKey() - addr := pk.Address().String() + pk, err := crypto.GeneratePrivateKey() + require.NoError(t, err) // Verify that the one app in the genesis is not the one we just generated + addr := pk.Address().String() require.NotEqual(t, app.Address, addr) - // Expect an error - _, err := utilityMod.GetSession(addr, 1, test_artifacts.DefaultChains[0], "unused_geo") + // Expect an error trying to get a session for a non-existent application + _, err = utilityMod.GetSession(addr, 1, test_artifacts.DefaultChains[0], "unused_geo") require.Error(t, err) } func TestSession_GetSession_InvalidFutureSession(t *testing.T) { runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, 1, 1, 1) + // Test parameters relayChain := test_artifacts.DefaultChains[0] geoZone := "unused_geo" app := runtimeCfg.GetGenesis().Applications[0] - latestCommitted := int64(0) + // Local variable to keep track of the height we're getting a session for + currentHeight := int64(0) - // Successfully get a session at height=1 - session, err := utilityMod.GetSession(app.Address, latestCommitted+1, relayChain, geoZone) + // Successfully get a session for 1 block ahead of the latest committed height + session, err := utilityMod.GetSession(app.Address, currentHeight+1, relayChain, geoZone) require.NoError(t, err) - require.Equal(t, latestCommitted+1, session.SessionHeight) + require.Equal(t, currentHeight+1, session.SessionHeight) // Expect an error for a few heights into the future - for height := latestCommitted + 2; height < 10; height++ { + for height := currentHeight + 2; height < 10; height++ { _, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) require.Error(t, err) } // Commit new blocks for all the heights that failed above - for ; latestCommitted < 10; latestCommitted++ { - writeCtx, err := persistenceMod.NewRWContext(latestCommitted + 1) + for ; currentHeight < 10; currentHeight++ { + writeCtx, err := persistenceMod.NewRWContext(currentHeight + 1) require.NoError(t, err) - err = writeCtx.Commit([]byte(fmt.Sprintf("proposer_height_%d", latestCommitted)), []byte(fmt.Sprintf("quorum_cert_height_%d", latestCommitted))) + err = writeCtx.Commit([]byte(fmt.Sprintf("proposer_height_%d", currentHeight)), []byte(fmt.Sprintf("quorum_cert_height_%d", currentHeight))) require.NoError(t, err) writeCtx.Release() } // Expect no errors since those blocks exist now // Note that we can get the session for latest_committed + 1 - for height := int64(1); height <= latestCommitted+1; height++ { + for height := int64(1); height <= currentHeight+1; height++ { _, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) require.NoError(t, err) } - // Verify that latestCommitted + 2 fails - _, err = utilityMod.GetSession(app.Address, latestCommitted+2, relayChain, geoZone) + // Verify that currentHeight + 2 fails + _, err = utilityMod.GetSession(app.Address, currentHeight+2, relayChain, geoZone) require.Error(t, err) } -func TestSession_GetSession_ApplicationUnbonds(t *testing.T) { - // TODO: What if an Application unbonds (unstaking period elapses) mid session? -} - func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *testing.T) { // Prepare an environment with a lot of servicers and fishermen numServicers := 100 @@ -164,6 +166,7 @@ func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *tes }, } + // Test constant parameters updateParamsHeight := int64(1) querySessionHeight := int64(2) @@ -173,12 +176,14 @@ func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *tes for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Reset to genesis err := persistenceMod.HandleDebugMessage(&messaging.DebugMessage{ Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, Message: nil, }) require.NoError(t, err) + // Update the number of servicers and fishermen per session gov params writeCtx, err := persistenceMod.NewRWContext(updateParamsHeight) require.NoError(t, err) defer writeCtx.Release() @@ -190,6 +195,7 @@ func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *tes err = writeCtx.Commit([]byte("empty_proposed_addr"), []byte("empty_quorum_cert")) require.NoError(t, err) + // Verify that the session is populated with the correct number of actors session, err := utilityMod.GetSession(app.Address, querySessionHeight, relayChain, geoZone) require.NoError(t, err) require.Equal(t, tt.wantServicerCount, len(session.Servicers)) @@ -199,6 +205,7 @@ func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *tes } func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *testing.T) { + // Test constant parameters numServicersPerSession := 10 numFishermenPerSession := 2 @@ -210,12 +217,15 @@ func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *tes servicersChain2, servicerKeysChain2 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicersPerSession/2, []string{"chn2"}) fishermenChain1, fishermenKeysChain1 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFishermenPerSession/2, []string{"chn2"}) + //nolint:gocritic // intentionally not appending result to a new slice actors := append(servicersChain1, append(servicersChain2, append(fishermenChain1, fishermenChain2...)...)...) + //nolint:gocritic // intentionally not appending result to a new slice keys := append(servicerKeysChain1, append(servicerKeysChain2, append(fishermenKeysChain1, fishermenKeysChain2...)...)...) + // Prepare the environment runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, 0, 1, 0, test_artifacts.WithActors(actors, keys)) - // Vary the chains and check the number of fishermen and servicers returned for each one + // Vary the chain and check the number of fishermen and servicers returned for each one tests := []struct { name string chain string @@ -242,12 +252,14 @@ func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *tes }, } + // Reset to genesis err := persistenceMod.HandleDebugMessage(&messaging.DebugMessage{ Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, Message: nil, }) require.NoError(t, err) + // Update the number of servicers and fishermen per session gov params writeCtx, err := persistenceMod.NewRWContext(1) require.NoError(t, err) err = writeCtx.SetParam(types.ServicersPerSessionParamName, numServicersPerSession) @@ -258,6 +270,7 @@ func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *tes require.NoError(t, err) defer writeCtx.Release() + // Test parameters app := runtimeCfg.GetGenesis().Applications[0] geoZone := "unused_geo" @@ -272,6 +285,7 @@ func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *tes } func TestSession_GetSession_SessionHeightAndNumber_StaticBlocksPerSession(t *testing.T) { + // Prepare the environment _, _, persistenceMod := prepareEnvironment(t, 5, 1, 1, 1) // Note that we are using an ephemeral write context at the genesis block (height=0). @@ -349,22 +363,26 @@ func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { numFishermen := 1000 // make them equal for simplicity numServicersPerSession := 10 numFishermenPerSession := 10 // make them equal for simplicity + numApplications := 3 + numBlocksPerSession := 2 // expect a different every other height // Determine probability of overlap using combinatorics - numChoices := combin.GeneralizedBinomial(float64(numServicers), float64(numServicersPerSession)) // numServicers C numServicersPerSession - numChoicesRemaining := combin.GeneralizedBinomial(float64(numServicers-numServicersPerSession), float64(numServicersPerSession)) // (numServicers - numServicersPerSession) C numServicersPerSession + numChoices := combin.GeneralizedBinomial(float64(numServicers), float64(numServicersPerSession)) // (numServicers) C (numServicersPerSession) + numChoicesRemaining := combin.GeneralizedBinomial(float64(numServicers-numServicersPerSession), float64(numServicersPerSession)) // (numServicers - numServicersPerSession) C (numServicersPerSession) probabilityOfOverlap := (numChoices - numChoicesRemaining) / numChoices - numApplications := 3 + // Prepare the environment runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, numApplications, numFishermen) - // Set the number of servicers and fishermen per session + // Set the number of servicers and fishermen per session gov params writeCtx, err := persistenceMod.NewRWContext(1) require.NoError(t, err) err = writeCtx.SetParam(types.ServicersPerSessionParamName, numServicersPerSession) require.NoError(t, err) err = writeCtx.SetParam(types.FishermanPerSessionParamName, numFishermenPerSession) require.NoError(t, err) + err = writeCtx.SetParam(types.BlocksPerSessionParamName, numBlocksPerSession) + require.NoError(t, err) err = writeCtx.Commit([]byte(fmt.Sprintf("proposer_height_%d", 1)), []byte(fmt.Sprintf("quorum_cert_height_%d", 1))) require.NoError(t, err) writeCtx.Release() @@ -379,9 +397,13 @@ func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { app2 := runtimeCfg.GetGenesis().Applications[1] app3 := runtimeCfg.GetGenesis().Applications[2] + // Keep track of the actors from the session at the previous height to verify a delta var app1PrevServicers, app2PrevServicers, app3PrevServicers []*coreTypes.Actor var app1PrevFishermen, app2PrevFishermen, app3PrevFishermen []*coreTypes.Actor + // The number of blocks to increase until we expect a different set of servicers and fishermen; see numBlocksPerSession + numBlocksUntilChange := 0 + // Commit new blocks for all the heights that failed above for height := int64(2); height < 10; height++ { session1, err := utilityMod.GetSession(app1.Address, height, relayChain, geoZone) @@ -409,22 +431,40 @@ func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { assertActorsDifference(t, session1.Fishermen, session2.Fishermen, probabilityOfOverlap) assertActorsDifference(t, session1.Fishermen, session3.Fishermen, probabilityOfOverlap) - // Assert different servicers between heights for the same app - assertActorsDifference(t, app1PrevServicers, session1.Servicers, probabilityOfOverlap) - assertActorsDifference(t, app2PrevServicers, session2.Servicers, probabilityOfOverlap) - assertActorsDifference(t, app3PrevServicers, session3.Servicers, probabilityOfOverlap) - - // Assert different fishermen between heights for the same app - assertActorsDifference(t, app1PrevFishermen, session1.Fishermen, probabilityOfOverlap) - assertActorsDifference(t, app2PrevFishermen, session2.Fishermen, probabilityOfOverlap) - assertActorsDifference(t, app3PrevFishermen, session3.Fishermen, probabilityOfOverlap) - - app1PrevServicers = session1.Servicers - app2PrevServicers = session2.Servicers - app3PrevServicers = session3.Servicers - app1PrevFishermen = session1.Fishermen - app2PrevFishermen = session2.Fishermen - app3PrevFishermen = session3.Fishermen + if numBlocksUntilChange == 0 { + // Assert different servicers between heights for the same app + assertActorsDifference(t, app1PrevServicers, session1.Servicers, probabilityOfOverlap) + assertActorsDifference(t, app2PrevServicers, session2.Servicers, probabilityOfOverlap) + assertActorsDifference(t, app3PrevServicers, session3.Servicers, probabilityOfOverlap) + + // Assert different fishermen between heights for the same app + assertActorsDifference(t, app1PrevFishermen, session1.Fishermen, probabilityOfOverlap) + assertActorsDifference(t, app2PrevFishermen, session2.Fishermen, probabilityOfOverlap) + assertActorsDifference(t, app3PrevFishermen, session3.Fishermen, probabilityOfOverlap) + + // Store the new servicers and fishermen for the next height + app1PrevServicers = session1.Servicers + app2PrevServicers = session2.Servicers + app3PrevServicers = session3.Servicers + app1PrevFishermen = session1.Fishermen + app2PrevFishermen = session2.Fishermen + app3PrevFishermen = session3.Fishermen + + // Reset the number of blocks until we expect a different set of servicers and fishermen + numBlocksUntilChange = numBlocksPerSession - 1 + } else { + // Assert the same servicers between heights for the same app + require.ElementsMatch(t, app1PrevServicers, session1.Servicers) + require.ElementsMatch(t, app2PrevServicers, session2.Servicers) + require.ElementsMatch(t, app3PrevServicers, session3.Servicers) + + // Assert the same fishermen between heights for the same app + require.ElementsMatch(t, app1PrevFishermen, session1.Fishermen) + require.ElementsMatch(t, app2PrevFishermen, session2.Fishermen) + require.ElementsMatch(t, app3PrevFishermen, session3.Fishermen) + + numBlocksUntilChange-- + } // Advance block height writeCtx, err := persistenceMod.NewRWContext(height) @@ -435,6 +475,26 @@ func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { } } +func TestSession_GetSession_ApplicationUnbonds(t *testing.T) { + // TODO: What if an Application unbonds (unstaking period elapses) mid session? +} + +func TestSession_GetSession_ServicersAndFishermenCounts_GeoZoneAvailability(t *testing.T) { + // TODO: Once GeoZones are implemented, the tests need to be added as well + // Cases: Invalid, unused, non-existent, empty, insufficiently complete, etc... +} + +func TestSession_GetSession_ActorReplacement(t *testing.T) { + // TODO: Since sessions last multiple blocks, we need to design what happens when an actor is (un)jailed, (un)stakes, (un)bonds, (un)pauses + // mid session. There are open design questions that need to be made. +} + +func TestSession_GetSession_SessionHeightAndNumber_ModifiedBlocksPerSession(t *testing.T) { + // RESEARCH: Need to design what happens (actor replacement, session numbers, etc...) when the number + // of blocks per session changes mid session. For example, all existing sessions could go to completion + // until the new parameter takes effect. There are open design questions that need to be made. +} + func assertActorsDifference(t *testing.T, actors1, actors2 []*coreTypes.Actor, maxSimilarityThreshold float64) { slice1 := actorsToAdds(actors1) slice2 := actorsToAdds(actors2) @@ -458,19 +518,3 @@ func actorsToAdds(actors []*coreTypes.Actor) []string { } return addresses } - -func TestSession_GetSession_ServicersAndFishermenCounts_GeoZoneAvailability(t *testing.T) { - // TODO: Once GeoZones are implemented, the tests need to be added as well - // Cases: Invalid, unused, non-existent, empty, insufficiently complete, etc... -} - -func TestSession_GetSession_ActorReplacement(t *testing.T) { - // TODO: Since sessions last multiple blocks, we need to design what happens when an actor is (un)jailed, (un)stakes, (un)bonds, (un)pauses - // mid session. There are open design questions that need to be made. -} - -func TestSession_GetSession_SessionHeightAndNumber_ModifiedBlocksPerSession(t *testing.T) { - // RESEARCH: Need to design what happens (actor replacement, session numbers, etc...) when the number - // of blocks per session changes mid session. For example, all existing sessions could go to completion - // until the new parameter takes effect. There are open design questions that need to be made. -} From 3fa7ac3757a26171d4014b68227e81990922d5d7 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 18 Apr 2023 19:52:30 -0700 Subject: [PATCH 17/46] Added some session application validation checks --- utility/session.go | 31 +++++++++++++++---------------- utility/session_test.go | 10 +++++++++- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/utility/session.go b/utility/session.go index 0d10e8bad..16d97563a 100644 --- a/utility/session.go +++ b/utility/session.go @@ -12,6 +12,7 @@ import ( "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/utility/types" + "golang.org/x/exp/slices" ) // sessionHydrator is an internal structure used to prepare a Session returned by `GetSession` below @@ -107,14 +108,18 @@ func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { // validateApplicationSession validates that the application can dispatch a session at the requested geo zone and for the request relay chain func (s *sessionHydrator) validateApplicationSession() error { - // if s.session.Application.Chains { - // TECHDEBT: We can remove this decoding process once we use `strings` instead of `[]byte` for addresses - // addr, err := hex.DecodeString(s.session.Application.Address) - // if err != nil { - // return err - // } - // s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.SessionHeight) - // return err + // TODO(#XXX): Filter by geo-zone + app := s.session.Application + + if !slices.Contains(app.Chains, s.session.RelayChain) { + return fmt.Errorf("application %s does not stake for relay chain %s", app.Address, s.session.RelayChain) + } + + if !(app.PausedHeight == -1 && app.UnstakingHeight == -1) { + return fmt.Errorf("application %s is either unstaked or paused", app.Address) + } + + // TODO: Consider what else we should validate for here return nil } @@ -140,7 +145,7 @@ func (s *sessionHydrator) hydrateSessionID() error { return nil } -// hydrateSessionServicers +// hydrateSessionServicers finds the servicers that are staked at the session height and populates the session with them func (s *sessionHydrator) hydrateSessionServicers() error { // number of servicers per session at this height numServicers, err := s.readCtx.GetIntParam(types.ServicersPerSessionParamName, s.session.SessionHeight) @@ -181,13 +186,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { return nil } -// uses the current 'world state' to determine the fishermen in the session -// 1) get an ordered list of the public keys of fishermen who are: -// - actively staked -// - staked within geo-zone (or closest geo-zones) -// - staked for relay-chain -// -// 2) calls `pseudoRandomSelection(fishermen, numberOfFishPerSession)` +// hydrateSessionFishermen finds the fishermen that are staked at the session height and populates the session with them func (s *sessionHydrator) hydrateSessionFishermen() error { // number of fisherman per session at this height numFishermen, err := s.readCtx.GetIntParam(types.FishermanPerSessionParamName, s.session.SessionHeight) diff --git a/utility/session_test.go b/utility/session_test.go index 89059e7f6..94752dd88 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -52,7 +52,7 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) require.Equal(t, fish.Address, session.Fishermen[0].Address) } -func TestSession_GetSession_InvalidApplication(t *testing.T) { +func TestSession_GetSession_ApplicationInvalid(t *testing.T) { runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, 1, 1, 1) // Verify there's only 1 app @@ -67,6 +67,14 @@ func TestSession_GetSession_InvalidApplication(t *testing.T) { addr := pk.Address().String() require.NotEqual(t, app.Address, addr) + // Expect no error trying to get a session for the real application + _, err = utilityMod.GetSession(app.Address, 1, test_artifacts.DefaultChains[0], "unused_geo") + require.NoError(t, err) + + // Expect an error trying to get a session for an unstaked chain + _, err = utilityMod.GetSession(addr, 1, "chain", "unused_geo") + require.Error(t, err) + // Expect an error trying to get a session for a non-existent application _, err = utilityMod.GetSession(addr, 1, test_artifacts.DefaultChains[0], "unused_geo") require.Error(t, err) From cc15ba1ea64bab29113ec8247bc952b2cc657856 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 18 Apr 2023 20:00:01 -0700 Subject: [PATCH 18/46] Fix one of the broken tests --- utility/session_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/utility/session_test.go b/utility/session_test.go index 94752dd88..a840f5ec4 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -225,10 +225,12 @@ func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *tes servicersChain2, servicerKeysChain2 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicersPerSession/2, []string{"chn2"}) fishermenChain1, fishermenKeysChain1 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFishermenPerSession/2, []string{"chn2"}) + application, applicationKey := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_APP, 1, []string{"chn1", "chn2", "chn3"}) + //nolint:gocritic // intentionally not appending result to a new slice - actors := append(servicersChain1, append(servicersChain2, append(fishermenChain1, fishermenChain2...)...)...) + actors := append(application, append(servicersChain1, append(servicersChain2, append(fishermenChain1, fishermenChain2...)...)...)...) //nolint:gocritic // intentionally not appending result to a new slice - keys := append(servicerKeysChain1, append(servicerKeysChain2, append(fishermenKeysChain1, fishermenKeysChain2...)...)...) + keys := append(applicationKey, append(servicerKeysChain1, append(servicerKeysChain2, append(fishermenKeysChain1, fishermenKeysChain2...)...)...)...) // Prepare the environment runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, 0, 1, 0, test_artifacts.WithActors(actors, keys)) From ede48219b5c1b8350e7fae205ecaec009b3f205c Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Thu, 20 Apr 2023 16:43:47 -0700 Subject: [PATCH 19/46] Replied to all comments --- Makefile | 3 ++- persistence/test/setup_test.go | 1 + runtime/genesis/proto/genesis.proto | 2 ++ utility/session.go | 27 ++++++++++++++------------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index d1de7957d..1b50c6e79 100644 --- a/Makefile +++ b/Makefile @@ -436,6 +436,7 @@ benchmark_p2p_peerstore: ## Run P2P peerstore benchmarks ### Inspired by @goldinguy_ in this post: https://goldin.io/blog/stop-using-todo ### # TODO - General Purpose catch-all. +# ADR - A TODO that will require a full on ADR in the future # TECHDEBT - Not a great implementation, but we need to fix it later. # IMPROVE - A nice to have, but not a priority. It's okay if we never get to this. # OPTIMIZE - An opportunity for performance improvement if/when it's necessary @@ -454,7 +455,7 @@ benchmark_p2p_peerstore: ## Run P2P peerstore benchmarks # BUG - There is a known existing bug in this code # DISCUSS_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way for the reviewer of a PR to start / reply to a discussion. # TODO_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way to start the review process while non-critical changes are still in progress -TODO_KEYWORDS = -e "TODO" -e "TECHDEBT" -e "IMPROVE" -e "OPTIMIZE" -e "DISCUSS" -e "INCOMPLETE" -e "INVESTIGATE" -e "CLEANUP" -e "HACK" -e "REFACTOR" -e "CONSIDERATION" -e "TODO_IN_THIS_COMMIT" -e "DISCUSS_IN_THIS_COMMIT" -e "CONSOLIDATE" -e "DEPRECATE" -e "ADDTEST" -e "RESEARCH" -e "BUG" +TODO_KEYWORDS = -e "TODO" -e "ADR" -e "TECHDEBT" -e "IMPROVE" -e "OPTIMIZE" -e "DISCUSS" -e "INCOMPLETE" -e "INVESTIGATE" -e "CLEANUP" -e "HACK" -e "REFACTOR" -e "CONSIDERATION" -e "TODO_IN_THIS_COMMIT" -e "DISCUSS_IN_THIS_COMMIT" -e "CONSOLIDATE" -e "DEPRECATE" -e "ADDTEST" -e "RESEARCH" -e "BUG" # How do I use TODOs? # 1. : ; diff --git a/persistence/test/setup_test.go b/persistence/test/setup_test.go index 79a01c3c6..ddc2e82b1 100644 --- a/persistence/test/setup_test.go +++ b/persistence/test/setup_test.go @@ -362,6 +362,7 @@ func resetStateToGenesis() { } } +// TECHDEBT: Make sure all tests run `t.Cleanup(clearAllState)` // This is necessary for unit tests that are dependant on a completely clear state when starting func clearAllState() { if err := testPersistenceMod.ReleaseWriteContext(); err != nil { diff --git a/runtime/genesis/proto/genesis.proto b/runtime/genesis/proto/genesis.proto index 1379f4b51..5db5100ea 100644 --- a/runtime/genesis/proto/genesis.proto +++ b/runtime/genesis/proto/genesis.proto @@ -21,6 +21,8 @@ message GenesisState { Params params = 10; } +// TODO: Rename the appropriate fields from `fisherman_` to `fishermen_` or `fisherbeing_`, etc... + // TECHDEBT: Explore a more general purpose "feature flag" approach that makes it easy to add/remove // parameters and add activation heights for them as well. message Params { diff --git a/utility/session.go b/utility/session.go index 16d97563a..cc503e532 100644 --- a/utility/session.go +++ b/utility/session.go @@ -108,7 +108,6 @@ func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { // validateApplicationSession validates that the application can dispatch a session at the requested geo zone and for the request relay chain func (s *sessionHydrator) validateApplicationSession() error { - // TODO(#XXX): Filter by geo-zone app := s.session.Application if !slices.Contains(app.Chains, s.session.RelayChain) { @@ -119,7 +118,9 @@ func (s *sessionHydrator) validateApplicationSession() error { return fmt.Errorf("application %s is either unstaked or paused", app.Address) } - // TODO: Consider what else we should validate for here + // TODO(#697): Filter by geo-zone + + // INVESTIGATE: Consider what else we should validate for here return nil } @@ -167,7 +168,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { return fmt.Errorf("hydrateSessionServicers should not have encountered a paused or unstaking servicer: %s", servicer.Address) } - // TODO(#XXX): Filter by geo-zone + // TODO(#697): Filter by geo-zone // OPTIMIZE: If this was a map[string]struct{}, we could have avoided the loop var chain string @@ -188,13 +189,13 @@ func (s *sessionHydrator) hydrateSessionServicers() error { // hydrateSessionFishermen finds the fishermen that are staked at the session height and populates the session with them func (s *sessionHydrator) hydrateSessionFishermen() error { - // number of fisherman per session at this height + // number of fishermen per session at this height numFishermen, err := s.readCtx.GetIntParam(types.FishermanPerSessionParamName, s.session.SessionHeight) if err != nil { return err } - // returns all the staked fisherman at this session height + // returns all the staked fishermen at this session height fishermen, err := s.readCtx.GetAllFishermen(s.session.SessionHeight) if err != nil { return err @@ -202,24 +203,24 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { // OPTIMIZE: Consider updating the persistence module so a single SQL query can retrieve all of the actors at once. candidateFishermen := make([]*coreTypes.Actor, 0) - for _, fisherman := range fishermen { - // Sanity check the fisherman is not paused, jailed or unstaking - if !(fisherman.PausedHeight == -1 && fisherman.UnstakingHeight == -1) { - return fmt.Errorf("hydrateSessionFishermen should not have encountered a paused or unstaking fisherman: %s", fisherman.Address) + for _, fisher := range fishermen { + // Sanity check the fisher is not paused, jailed or unstaking + if !(fisher.PausedHeight == -1 && fisher.UnstakingHeight == -1) { + return fmt.Errorf("hydrateSessionFishermen should not have encountered a paused or unstaking fisherman: %s", fisher.Address) } - // TODO(#XXX): Filter by geo-zone + // TODO(#697): Filter by geo-zone // OPTIMIZE: If this was a map[string]struct{}, we could have avoided the loop var chain string - for _, chain = range fisherman.Chains { + for _, chain = range fisher.Chains { if chain != s.session.RelayChain { chain = "" continue } } if chain != "" { - candidateFishermen = append(candidateFishermen, fisherman) + candidateFishermen = append(candidateFishermen, fisher) } } @@ -228,7 +229,7 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { } // pseudoRandomSelection returns a random subset of the candidates. -// TECHDEBT: We are using a `Go` native implementation for a pseudo-random number generator. In order +// ADR/TECHDEBT: We are using a `Go` native implementation for a pseudo-random number generator. In order // for it to be language agnostic, a general purpose algorithm needs ot be used. func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, sessionId []byte) []*coreTypes.Actor { // If there aren't enough candidates, return all of them From 4caac93e07c5b4e4a4ee7864f019a044f4aa85ad Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Thu, 20 Apr 2023 19:35:48 -0700 Subject: [PATCH 20/46] Code and documentation cleanup --- Makefile | 7 +++ utility/doc/PROTOCOL_SESSION.md | 99 ++++++++++++--------------------- utility/session.go | 89 ++++++++++++----------------- utility/session_test.go | 5 +- 4 files changed, 83 insertions(+), 117 deletions(-) diff --git a/Makefile b/Makefile index 1b50c6e79..5b7db68ff 100644 --- a/Makefile +++ b/Makefile @@ -472,6 +472,13 @@ TODO_KEYWORDS = -e "TODO" -e "ADR" -e "TECHDEBT" -e "IMPROVE" -e "OPTIMIZE" -e " todo_list: ## List all the TODOs in the project (excludes vendor and prototype directories) grep --exclude-dir={.git,vendor,prototype} -r ${TODO_KEYWORDS} . + +TODO_SEARCH ?= $(shell pwd) + +.PHONY: todo_search +todo_search: ## List all the TODOs in a specific directory specific by `TODO_SEARCH` + grep --exclude-dir={.git,vendor,prototype} -r ${TODO_KEYWORDS} ${TODO_SEARCH} + .PHONY: todo_count todo_count: ## Print a count of all the TODOs in the project grep --exclude-dir={.git,vendor,prototype} -r ${TODO_KEYWORDS} . | wc -l diff --git a/utility/doc/PROTOCOL_SESSION.md b/utility/doc/PROTOCOL_SESSION.md index cca6cd349..6724ffd13 100644 --- a/utility/doc/PROTOCOL_SESSION.md +++ b/utility/doc/PROTOCOL_SESSION.md @@ -1,81 +1,54 @@ -## Protocols - -### Session Protocol - -`Pocket` implements the V1 Utility Specification's Session Protocol by satisfying the following interface: - -```golang -type Session interface { - NewSession(sessionHeight int64, blockHash string, geoZone GeoZone, relayChain RelayChain, application *coreTypes.Actor) (Session, types.Error) - GetServicers() []*coreTypes.Actor // the Servicers providing Web3 access to the Application - GetFishermen() []*coreTypes.Actor // the Fishermen monitoring the Servicers - GetApplication() *coreTypes.Actor // the Application consuming Web3 access - GetRelayChain() RelayChain // the identifier of the web3 Relay Chain - GetGeoZone() GeoZone // the geolocation zone where the Application is registered - GetSessionHeight() int64 // the block height when the Session started -} -``` +# Session Protocol -#### Session Creation Flow +- [Interface](#interface) +- [Session Creation Flow](#session-creation-flow) -1. Create a session object from the seed data (see #2) -2. Create a key concatenating and hashing the seed data - - `key = Hash(sessionHeight + blockHash + geoZone + relayChain + appPublicKey)` -3. Get an ordered list of the public keys of servicers who are: - - actively staked - - staked within geo-zone - - staked for relay-chain -4. Pseudo-insert the session `key` string into the list and find the first actor directly below on the list -5. Determine a new seedKey with the following formula: ` key = Hash( key + actor1PublicKey )` where `actor1PublicKey` is the key determined in step 4 -6. Repeat steps 4 and 5 until all N servicers are found -7. Do steps 3 - 6 for Fishermen as well +_WIP: Run `TODO_SEARCH=utility/session_ make todo*search` to identify all the WIP related to session generation.* -### FAQ +## Interface -- Q) why do we hash to find a newKey between every actor selection? -- A) pseudo-random selection only works if each iteration is re-randomized or it would be subject to lexicographical proximity bias attacks +`Pocket` satisfies the V1 Utility Specification's Session Protocol by implementing the following function from the [Utility Module Interface](../../shared/modules/utility_module.go) and returning a [Session Protobuf](../../shared/core/types/proto/session.proto): -- Q) Why do we not use Golang's `rand.Intn` with the key as a seed for random node selection? -- A) A proprietary randomization algorithm makes this approach language & library agnostic, so any client simply has to follow the specifications +```go +GetSession(appAddr string, sessionHeight int64, relayChain string, geoZone string) (*coreTypes.Session, error) +``` -- Q) what is `WorldState`? -- A) it represents a queryable view on the internal state of the network at a certain height. +## Session Creation Flow -- Q) Do Fishermen stake for a specific RelayChain? -- A) Fishermen are only going to be applicable to Pocket Supported Relay Chains (where the protocol pays out for the relay chain). It is unclear at this time what the limitations and scoping will be for Fishermen RelayChain support. +The following is a simplified flow of the session creation flow for illustrative purposes only. -- Q) What was the reasoning not to allow a list of geozones? -- A) Each session is mono-chain and mono-geo. This is fundamental as it would create even more possible combinations of sessions and increase computational complexity during block production and servicing +See [session.go](../session.go) and [session_test.go](../session_test.go) for the full implementation. -### Session Flow +1. Create a session object from the seed data +2. Create a key concatenating and hashing the seed data + - `sessionId = Hash(sessionHeight + blockHash + geoZone + relayChain + appPublicKey)` +3. Get an ordered list of the public keys of servicers and fishermen who are: + - actively staked + - staked within geo-zone + - staked for relay-chain +4. Use a pseudo-random selection algorithm to retrieve the fishermen and servicers for for the sessionId ```mermaid sequenceDiagram autonumber - participant WorldState - participant Session - %% The `Qurier` is anyone (app or not) that asks to retrieve session information - actor Querier - Querier->>WorldState: Who are my sessionNodes and sessionFish for [app], [relayChain], and [geoZone] - WorldState->>Session: seedData = height, blockHash, [geoZone], [relayChain], [app] - Session->>Session: sessionKey = hash(concat(seedData)) - WorldState->>Session: nodeList = Ordered list of public keys of applicable servicers - Session->>Session: sessionNodes = pseudorandomSelect(sessionKey, nodeList, max) - WorldState->>Session: fishList = Ordered list of public keys of applicable fishermen - Session->>Session: sessionFish = pseudorandomSelect(sessionKey, fishList, max) - Session->>Querier: SessionNodes, SessionFish -``` -### Pseudorandom Selection + %% The `Querier` is anyone (app or not) that asks to retrieve session metadata + actor Q AS Querier -```mermaid -graph TD - D[Pseudorandom Selection] -->|Ordered list of actors by pubKey|A - A[A1, A2, A3, A4] -->|Insert key in Ooder| B[A1, A2, A3, Key, A4] - B --> |A4 is selected due to order| C{A4} - C --> |Else| E["Key = Hash(A4) + Key "] - E --> A - C --> |IF selection is maxed| F[done] + participant S AS Session Hydrator + participant WS AS WorldState + + Q->>WS: Who are the servicers and fisherman ([app], [relayChain], [geoZone]) + WS->>S: seedData = (height, blockHash, [geoZone], [relayChain], [app]) + + S->>S: sessionId = hash(concat(seedData)) + WS->>S: servicerList = Ordered list of public keys of applicable servicers + + S->>S: sessionServicers = pseudorandomSelect(sessionKey, servicerList, max) + WS->>S: fishList = Ordered list of public keys of applicable fishermen + + S->>S: sessionFishermen = pseudorandomSelect(sessionKey, fishList, max) + S->>Q: SessionServicers, sessionFishermen ``` diff --git a/utility/session.go b/utility/session.go index cc503e532..8b4c528bd 100644 --- a/utility/session.go +++ b/utility/session.go @@ -15,24 +15,7 @@ import ( "golang.org/x/exp/slices" ) -// sessionHydrator is an internal structure used to prepare a Session returned by `GetSession` below -type sessionHydrator struct { - logger modules.Logger - - // The height of the request for which the session is being hydrated - blockHeight int64 - - // The session being hydrated and returned - session *coreTypes.Session - - // Caches a readCtx to avoid draining too many connections to the database - readCtx modules.PersistenceReadContext - - // A redundant helper that maintains a hex decoded copy of `session.Id` used for session hydration - sessionIdBz []byte -} - -// GetSession is an implementation of the exposed `UtilityModule.GetSession` function +// GetSession implements of the exposed `UtilityModule.GetSession` function func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geoZone string) (*coreTypes.Session, error) { persistenceModule := m.GetBus().GetPersistenceModule() readCtx, err := persistenceModule.NewReadContext(height) @@ -48,54 +31,69 @@ func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geo sessionHydrator := &sessionHydrator{ logger: m.logger.With().Str("source", "sessionHydrator").Logger(), - blockHeight: height, session: session, + blockHeight: height, readCtx: readCtx, } - if err := sessionHydrator.hydrateSessionHeight(height); err != nil { - return nil, err + if err := sessionHydrator.hydrateSessionMetadata(); err != nil { + return nil, fmt.Errorf("failed to hydrate session metadata: %w", err) } if err := sessionHydrator.hydrateSessionApplication(appAddr); err != nil { - return nil, err + return nil, fmt.Errorf("failed to hydrate session application: %w", err) } if err := sessionHydrator.validateApplicationSession(); err != nil { - return nil, err + return nil, fmt.Errorf("failed to validate application session: %w", err) } if err := sessionHydrator.hydrateSessionID(); err != nil { - return nil, err + return nil, fmt.Errorf("failed to hydrate session ID: %w", err) } if err := sessionHydrator.hydrateSessionServicers(); err != nil { - return nil, err + return nil, fmt.Errorf("failed to hydrate session servicers: %w", err) } if err := sessionHydrator.hydrateSessionFishermen(); err != nil { - return nil, err + return nil, fmt.Errorf("failed to hydrate session fishermen: %w", err) } return sessionHydrator.session, nil } -// hydrateSessionHeight hydrates the height at which the session started given the current block height -func (s *sessionHydrator) hydrateSessionHeight(blockHeight int64) error { - numBlocksPerSession, err := s.readCtx.GetIntParam(types.BlocksPerSessionParamName, blockHeight) +type sessionHydrator struct { + logger modules.Logger + + // The session being hydrated and returned + session *coreTypes.Session + + // The height at which the request is being made to get session information + blockHeight int64 + + // Caches a readCtx to avoid draining too many connections to the database + readCtx modules.PersistenceReadContext + + // A redundant helper that maintains a hex decoded copy of `session.Id` used for session hydration + sessionIdBz []byte +} + +// hydrateSessionMetadata hydrates the height at which the session started, its number, and the number of blocks per session +func (s *sessionHydrator) hydrateSessionMetadata() error { + numBlocksPerSession, err := s.readCtx.GetIntParam(types.BlocksPerSessionParamName, s.blockHeight) if err != nil { return err } - numBlocksAheadOfSession := blockHeight % int64(numBlocksPerSession) + numBlocksAheadOfSession := s.blockHeight % int64(numBlocksPerSession) s.session.NumSessionBlocks = int64(numBlocksPerSession) - s.session.SessionNumber = int64(blockHeight / int64(numBlocksPerSession)) - s.session.SessionHeight = blockHeight - numBlocksAheadOfSession + s.session.SessionNumber = int64(s.blockHeight / int64(numBlocksPerSession)) + s.session.SessionHeight = s.blockHeight - numBlocksAheadOfSession return nil } -// hydrateSessionApplication hydrates the full Application actor based on the address the session is being -// dispatched for. +// hydrateSessionApplication hydrates the full Application actor based on the address provided func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { // TECHDEBT: We can remove this decoding process once we use `strings` instead of `[]byte` for addresses addr, err := hex.DecodeString(appAddr) @@ -106,7 +104,7 @@ func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { return err } -// validateApplicationSession validates that the application can dispatch a session at the requested geo zone and for the request relay chain +// validateApplicationSession validates that the application can have a valid session for the provided relay chain and geo zone func (s *sessionHydrator) validateApplicationSession() error { app := s.session.Application @@ -120,7 +118,8 @@ func (s *sessionHydrator) validateApplicationSession() error { // TODO(#697): Filter by geo-zone - // INVESTIGATE: Consider what else we should validate for here + // INVESTIGATE: Consider what else we should validate for here (e.g. Application stake amount, etc.) + return nil } @@ -171,14 +170,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { // TODO(#697): Filter by geo-zone // OPTIMIZE: If this was a map[string]struct{}, we could have avoided the loop - var chain string - for _, chain = range servicer.Chains { - if chain != s.session.RelayChain { - chain = "" - continue - } - } - if chain != "" { + if slices.Contains(servicer.Chains, s.session.RelayChain) { candidateServicers = append(candidateServicers, servicer) } } @@ -212,14 +204,7 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { // TODO(#697): Filter by geo-zone // OPTIMIZE: If this was a map[string]struct{}, we could have avoided the loop - var chain string - for _, chain = range fisher.Chains { - if chain != s.session.RelayChain { - chain = "" - continue - } - } - if chain != "" { + if slices.Contains(fisher.Chains, s.session.RelayChain) { candidateFishermen = append(candidateFishermen, fisher) } } diff --git a/utility/session_test.go b/utility/session_test.go index a840f5ec4..b6e843430 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -233,7 +233,7 @@ func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *tes keys := append(applicationKey, append(servicerKeysChain1, append(servicerKeysChain2, append(fishermenKeysChain1, fishermenKeysChain2...)...)...)...) // Prepare the environment - runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, 0, 1, 0, test_artifacts.WithActors(actors, keys)) + runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, 0, 0, 0, test_artifacts.WithActors(actors, keys)) // Vary the chain and check the number of fishermen and servicers returned for each one tests := []struct { @@ -358,7 +358,8 @@ func TestSession_GetSession_SessionHeightAndNumber_StaticBlocksPerSession(t *tes err := writeCtx.SetParam(types.BlocksPerSessionParamName, tt.setNumBlocksPerSession) require.NoError(t, err) - err = s.hydrateSessionHeight(tt.provideBlockHeight) + s.blockHeight = tt.provideBlockHeight + err = s.hydrateSessionMetadata() require.NoError(t, err) require.Equal(t, tt.setNumBlocksPerSession, s.session.NumSessionBlocks) require.Equal(t, tt.wantSessionHeight, s.session.SessionHeight) From c723b33e617e0622e4431df83ec52a2c4ae4d958 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Thu, 20 Apr 2023 19:39:40 -0700 Subject: [PATCH 21/46] Fix broken test --- build/config/genesis.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config/genesis.json b/build/config/genesis.json index 329723e42..5ce30bd75 100755 --- a/build/config/genesis.json +++ b/build/config/genesis.json @@ -4120,7 +4120,7 @@ } ], "params": { - "blocks_per_session": 4, + "blocks_per_session": 1, "app_minimum_stake": "15000000000", "app_max_chains": 15, "app_session_tokens_multiplier": 100, From 32bf6804787263a7f3d42026e9fbdd09b996e5b9 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 21 Apr 2023 14:05:18 -0700 Subject: [PATCH 22/46] Checkpoint - replied to some of Bryan's comments --- Makefile | 2 +- .../configs/proto/persistence_config.proto | 3 ++- runtime/test_artifacts/generator.go | 24 +++++++++++++++---- utility/session.go | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 5b7db68ff..6e85af6b1 100644 --- a/Makefile +++ b/Makefile @@ -436,7 +436,7 @@ benchmark_p2p_peerstore: ## Run P2P peerstore benchmarks ### Inspired by @goldinguy_ in this post: https://goldin.io/blog/stop-using-todo ### # TODO - General Purpose catch-all. -# ADR - A TODO that will require a full on ADR in the future +# DECIDE - A TODO indicating we need to make a decision and document it using an ADR in the future; https://github.com/pokt-network/pocket-network-protocol/tree/main/ADRs # TECHDEBT - Not a great implementation, but we need to fix it later. # IMPROVE - A nice to have, but not a priority. It's okay if we never get to this. # OPTIMIZE - An opportunity for performance improvement if/when it's necessary diff --git a/runtime/configs/proto/persistence_config.proto b/runtime/configs/proto/persistence_config.proto index 5314be616..2b10daf74 100644 --- a/runtime/configs/proto/persistence_config.proto +++ b/runtime/configs/proto/persistence_config.proto @@ -4,9 +4,10 @@ package configs; option go_package = "github.com/pokt-network/pocket/runtime/configs"; +// CLEANUP: Need to make the configuration be "postgres agnostic" message PersistenceConfig { string postgres_url = 1; - string node_schema = 2; + string node_schema = 2; // the postgres schema used to store all the tables for a specific node; useful when multiple nodes share a single postgres instance string block_store_path = 3; string tx_indexer_path = 4; string trees_store_dir = 5; diff --git a/runtime/test_artifacts/generator.go b/runtime/test_artifacts/generator.go index 9d8887d23..6df8c2909 100644 --- a/runtime/test_artifacts/generator.go +++ b/runtime/test_artifacts/generator.go @@ -19,7 +19,16 @@ import ( type GenesisOption func(*genesis.GenesisState) // IMPROVE: Extend the utilities here into a proper genesis suite in the future. -func NewGenesisState(numValidators, numServicers, numApplications, numFisherman int, genesisOpts ...GenesisOption) (genesisState *genesis.GenesisState, validatorPrivateKeys []string) { +func NewGenesisState( + numValidators, + numServicers, + numApplications, + numFisherman int, + genesisOpts ...GenesisOption, +) ( + genesisState *genesis.GenesisState, + validatorPrivateKeys []string, +) { applications, appPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_APP, numApplications, DefaultChains) validators, validatorPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_VAL, numValidators, nil) servicers, servicerPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicers, DefaultChains) @@ -72,15 +81,22 @@ func WithActors(actors []*coreTypes.Actor, actorKeys []string) func(*genesis.Gen func NewDefaultConfigs(privateKeys []string) (cfgs []*configs.Config) { for i, pk := range privateKeys { - postgresSchema := "node" + strconv.Itoa(i+1) cfgs = append(cfgs, configs.NewDefaultConfig( configs.WithPK(pk), - configs.WithNodeSchema(postgresSchema), + configs.WithNodeSchema(getPostgresSchema(i+1)), )) } return cfgs } +// TECHDEBT: This is used for the `node_schema` field in `PersistenceConfig` and enables +// different nodes sharing the same database while being isolated from each other. +// The naming convention should be changed to be more reflective of the node (e.g. _
), +// which would require all related tooling and documentation to be updated as well. +func getPostgresSchema(i int) string { + return "node" + strconv.Itoa(i) +} + func NewPools() (pools []*coreTypes.Account) { for _, value := range coreTypes.Pools_value { if value == int32(coreTypes.Pools_POOLS_UNSPECIFIED) { @@ -116,7 +132,7 @@ func newAccountsWithKeys(privateKeys []string) (accounts []*coreTypes.Account) { return accounts } -//nolint:unused +//nolint:unused // useful if we want to generate accounts with random keys func newAccounts(numActors int) (accounts []*coreTypes.Account) { for i := 0; i < numActors; i++ { _, _, addr := keygen.GetInstance().Next() diff --git a/utility/session.go b/utility/session.go index 8b4c528bd..929804fee 100644 --- a/utility/session.go +++ b/utility/session.go @@ -214,7 +214,7 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { } // pseudoRandomSelection returns a random subset of the candidates. -// ADR/TECHDEBT: We are using a `Go` native implementation for a pseudo-random number generator. In order +// DECIDE: We are using a `Go` native implementation for a pseudo-random number generator. In order // for it to be language agnostic, a general purpose algorithm needs ot be used. func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, sessionId []byte) []*coreTypes.Actor { // If there aren't enough candidates, return all of them From 86886456f1dccaa6c8ea83c28f3f6cf70a34ea18 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 21 Apr 2023 14:11:32 -0700 Subject: [PATCH 23/46] Consolidated MarshalZerologArray() in logger/utils.go --- logger/utils.go | 17 +++++++++++++++++ p2p/utils/url_conversion.go | 18 +----------------- runtime/test_artifacts/generator.go | 4 +++- 3 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 logger/utils.go diff --git a/logger/utils.go b/logger/utils.go new file mode 100644 index 000000000..4dcac404e --- /dev/null +++ b/logger/utils.go @@ -0,0 +1,17 @@ +package logger + +import "github.com/rs/zerolog" + +// stringLogArrayMarshaler implements the `zerolog.LogArrayMarshaler` interface +// to marshal an array of strings for use with zerolog. +type StringLogArrayMarshaler struct { + Strings []string +} + +// MarshalZerologArray implements the respective `zerolog.LogArrayMarshaler` +// interface member. +func (marshaler StringLogArrayMarshaler) MarshalZerologArray(arr *zerolog.Array) { + for _, str := range marshaler.Strings { + arr.Str(str) + } +} diff --git a/p2p/utils/url_conversion.go b/p2p/utils/url_conversion.go index c2f0552bb..cf3910a20 100644 --- a/p2p/utils/url_conversion.go +++ b/p2p/utils/url_conversion.go @@ -12,7 +12,6 @@ import ( "github.com/multiformats/go-multiaddr" "github.com/pokt-network/pocket/logger" - "github.com/rs/zerolog" ) const ( @@ -183,23 +182,8 @@ func getPeerIP(hostname string) (net.IP, error) { logger.Global.Warn().Msg("resolved multiple addresses but only using one. See ticket #557 for more details") logger.Global.Warn(). Str("hostname", hostname). - Array("resolved", stringLogArrayMarshaler{strs: addrs}). + Array("resolved", logger.StringLogArrayMarshaler{Strings: addrs}). IPAddr("using", peerIP) return peerIP, nil } - -// stringLogArrayMarshaler implements the `zerolog.LogArrayMarshaler` interface -// to marshal an array of strings for use with zerolog. -// TECHDEBT(#609): move & de-duplicate -type stringLogArrayMarshaler struct { - strs []string -} - -// MarshalZerologArray implements the respective `zerolog.LogArrayMarshaler` -// interface member. -func (marshaler stringLogArrayMarshaler) MarshalZerologArray(arr *zerolog.Array) { - for _, str := range marshaler.strs { - arr.Str(str) - } -} diff --git a/runtime/test_artifacts/generator.go b/runtime/test_artifacts/generator.go index 6df8c2909..be2a82b26 100644 --- a/runtime/test_artifacts/generator.go +++ b/runtime/test_artifacts/generator.go @@ -149,7 +149,9 @@ func newAccounts(numActors int) (accounts []*coreTypes.Account) { func NewActors(actorType coreTypes.ActorType, numActors int, chains []string) (actors []*coreTypes.Actor, privateKeys []string) { // If the actor type is a validator, the chains must be nil since they are chain agnostic if actorType == coreTypes.ActorType_ACTOR_TYPE_VAL { - logger.Global.Warn().Msgf("validator actors should not have chains but a list was provided: %v", chains) + logger.Global.Warn(). + Array("chains", logger.StringLogArrayMarshaler{Strings: chains}). + Msg("validator actors should not have chains but a list was provided.") chains = nil } for i := 0; i < numActors; i++ { From 7df6c884d948d3e99d55b2304e2f6f98edf52932 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 21 Apr 2023 14:16:01 -0700 Subject: [PATCH 24/46] Replied to remaining review comments --- runtime/test_artifacts/generator.go | 9 ++++++++- shared/CHANGELOG.md | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/runtime/test_artifacts/generator.go b/runtime/test_artifacts/generator.go index be2a82b26..24b80a2df 100644 --- a/runtime/test_artifacts/generator.go +++ b/runtime/test_artifacts/generator.go @@ -163,7 +163,14 @@ func NewActors(actorType coreTypes.ActorType, numActors int, chains []string) (a return actors, privateKeys } -func NewDefaultActor(actorType coreTypes.ActorType, serviceURL string, chains []string) (actor *coreTypes.Actor, privateKey string) { +func NewDefaultActor( + actorType coreTypes.ActorType, + serviceURL string, + chains []string, +) ( + actor *coreTypes.Actor, + privateKey string, +) { privKey, pubKey, addr := keygen.GetInstance().Next() return &coreTypes.Actor{ ActorType: actorType, diff --git a/shared/CHANGELOG.md b/shared/CHANGELOG.md index e3733605c..f9ae0b2f2 100644 --- a/shared/CHANGELOG.md +++ b/shared/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.0.0.52] - 2023-04-17 -- Removed _temporary_ `shared/p2p` package; consolidated into `p2p` +- Removed *temporary* `shared/p2p` package; consolidated into `p2p` ## [0.0.0.51] - 2023-04-13 From 424cd54c81ce1c44a34a4b68621b79066468c70e Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 26 Apr 2023 15:39:06 -0700 Subject: [PATCH 25/46] Update utility/session.go Co-authored-by: Bryan White --- utility/session.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utility/session.go b/utility/session.go index 929804fee..77c7518e4 100644 --- a/utility/session.go +++ b/utility/session.go @@ -238,7 +238,9 @@ func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, session // OPTIMIZE: Postgres uses a `Twisted Mersenne Twister (TMT)` randomness algorithm. // We could potentially look into changing everything into a single SQL query but -// would nee dto verify that it can be implemented in a platform agnostic way. +// OPTIMIZE: Postgres uses a `Twisted Mersenne Twister (TMT)` randomness algorithm. +// We could potentially look into changing everything into a single SQL query but +// would need to verify that it can be implemented in a platform agnostic way. // uniqueRandomIndices returns a map of `numIndices` unique random numbers less than `maxIndex` // seeded by `seed`. From 6c4d3ce0c591e0994d00a55e9cf21fbdd50161f1 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 26 Apr 2023 15:45:05 -0700 Subject: [PATCH 26/46] Apply suggestions from code review Co-authored-by: Bryan White --- runtime/test_artifacts/generator.go | 3 ++- utility/doc/PROTOCOL_SESSION.md | 2 +- utility/module_test.go | 3 ++- utility/session.go | 11 ++++++----- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/runtime/test_artifacts/generator.go b/runtime/test_artifacts/generator.go index 24b80a2df..e83809255 100644 --- a/runtime/test_artifacts/generator.go +++ b/runtime/test_artifacts/generator.go @@ -6,6 +6,8 @@ import ( "fmt" "strconv" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/pokt-network/pocket/logger" "github.com/pokt-network/pocket/runtime/configs" "github.com/pokt-network/pocket/runtime/genesis" @@ -13,7 +15,6 @@ import ( "github.com/pokt-network/pocket/shared/core/types" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" - "google.golang.org/protobuf/types/known/timestamppb" ) type GenesisOption func(*genesis.GenesisState) diff --git a/utility/doc/PROTOCOL_SESSION.md b/utility/doc/PROTOCOL_SESSION.md index 6724ffd13..219204698 100644 --- a/utility/doc/PROTOCOL_SESSION.md +++ b/utility/doc/PROTOCOL_SESSION.md @@ -15,7 +15,7 @@ GetSession(appAddr string, sessionHeight int64, relayChain string, geoZone strin ## Session Creation Flow -The following is a simplified flow of the session creation flow for illustrative purposes only. +The following is a simplification of the session creation flow for illustrative purposes only. See [session.go](../session.go) and [session_test.go](../session_test.go) for the full implementation. diff --git a/utility/module_test.go b/utility/module_test.go index 3e7b3c3f0..946bea70a 100644 --- a/utility/module_test.go +++ b/utility/module_test.go @@ -5,6 +5,8 @@ import ( "os" "testing" + "github.com/stretchr/testify/require" + "github.com/pokt-network/pocket/persistence" "github.com/pokt-network/pocket/runtime" "github.com/pokt-network/pocket/runtime/configs" @@ -12,7 +14,6 @@ import ( "github.com/pokt-network/pocket/runtime/test_artifacts/keygen" "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/shared/modules" - "github.com/stretchr/testify/require" ) var ( diff --git a/utility/session.go b/utility/session.go index 77c7518e4..92e6c280c 100644 --- a/utility/session.go +++ b/utility/session.go @@ -7,12 +7,13 @@ import ( "math" "math/rand" + "golang.org/x/exp/slices" + "github.com/pokt-network/pocket/logger" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/utility/types" - "golang.org/x/exp/slices" ) // GetSession implements of the exposed `UtilityModule.GetSession` function @@ -112,7 +113,7 @@ func (s *sessionHydrator) validateApplicationSession() error { return fmt.Errorf("application %s does not stake for relay chain %s", app.Address, s.session.RelayChain) } - if !(app.PausedHeight == -1 && app.UnstakingHeight == -1) { + if app.PausedHeight == -1 || app.UnstakingHeight == -1 { return fmt.Errorf("application %s is either unstaked or paused", app.Address) } @@ -167,9 +168,9 @@ func (s *sessionHydrator) hydrateSessionServicers() error { return fmt.Errorf("hydrateSessionServicers should not have encountered a paused or unstaking servicer: %s", servicer.Address) } - // TODO(#697): Filter by geo-zone + // TECHDEBT(#697): Filter by geo-zone - // OPTIMIZE: If this was a map[string]struct{}, we could have avoided the loop + // OPTIMIZE: If `servicer.Chains` was a map[string]struct{}, we could eliminate `slices.Contains()`'s loop if slices.Contains(servicer.Chains, s.session.RelayChain) { candidateServicers = append(candidateServicers, servicer) } @@ -215,7 +216,7 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { // pseudoRandomSelection returns a random subset of the candidates. // DECIDE: We are using a `Go` native implementation for a pseudo-random number generator. In order -// for it to be language agnostic, a general purpose algorithm needs ot be used. +// for it to be language agnostic, a general purpose algorithm MUST be used. func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, sessionId []byte) []*coreTypes.Actor { // If there aren't enough candidates, return all of them if numTarget > len(candidates) { From 6f26138b92b1e9189b3087a2450a8ebc1456f1f2 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Thu, 27 Apr 2023 12:17:13 -0700 Subject: [PATCH 27/46] Update utility/session_test.go Co-authored-by: Bryan White --- utility/session_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utility/session_test.go b/utility/session_test.go index b6e843430..f953be253 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -491,7 +491,7 @@ func TestSession_GetSession_ApplicationUnbonds(t *testing.T) { } func TestSession_GetSession_ServicersAndFishermenCounts_GeoZoneAvailability(t *testing.T) { - // TODO: Once GeoZones are implemented, the tests need to be added as well + // TODO(#697): Once GeoZones are implemented, the tests need to be added as well // Cases: Invalid, unused, non-existent, empty, insufficiently complete, etc... } From 547d4b2e5f9f1b2deb53192e1ac2e30b7970f25a Mon Sep 17 00:00:00 2001 From: d7t Date: Mon, 24 Apr 2023 17:17:40 -0600 Subject: [PATCH 28/46] [Makefile] fixes the localnet_db_cli target (#700) ## Description #658 changed how Postgres instances are handled and assigned to Validators in the LocalNet, which broke the Make target specified for grabbing a shell to the LocalNet database. This PR fixes the command to target the Postgres instance for validator-001 by default, since validator-001 used for other tasks by default. ## Type of change Please mark the relevant option(s): - [ ] New feature, functionality or library - [x] Bug fix - [ ] Code health or cleanup - [ ] Major breaking change - [ ] Documentation - [ ] Other ## List of changes - Changes `make localnet_db_cli` to target the Postgres instance that `validator001` uses. ## Testing - [x] `make test_e2e` - [x] `make localnet_db_cli` produces a CLI shell to validator001's Postgres - [x] `make develop_test`; if any code changes were made - [ ] [Docker Compose LocalNet](https://github.com/pokt-network/pocket/blob/main/docs/development/README.md); if any major functionality was changed or introduced - [x] [k8s LocalNet](https://github.com/pokt-network/pocket/blob/main/build/localnet/README.md); if any infrastructure or configuration changes were made ## Required Checklist - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added, or updated, [`godoc` format comments](https://go.dev/blog/godoc) on touched members (see: [tip.golang.org/doc/comment](https://tip.golang.org/doc/comment)) - [x] I have tested my changes using the available tooling - [x] I have updated the corresponding CHANGELOG ### If Applicable Checklist - [ ] I have updated the corresponding README(s); local and/or global - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added, or updated, [mermaid.js](https://mermaid-js.github.io) diagrams in the corresponding README(s) - [ ] I have added, or updated, documentation and [mermaid.js](https://mermaid-js.github.io) diagrams in `shared/docs/*` if I updated `shared/*`README(s) --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 6e85af6b1..6dfd2d710 100644 --- a/Makefile +++ b/Makefile @@ -515,20 +515,20 @@ localnet_shell: ## Opens a shell in the pod that has the `client` cli available. .PHONY: localnet_logs_validators localnet_logs_validators: ## Outputs logs from all validators - kubectl logs -l v1-purpose=validator --all-containers=true --tail=-1 + kubectl logs -l "pokt.network/purpose=validator" --all-containers=true --tail=-1 .PHONY: localnet_logs_validators_follow localnet_logs_validators_follow: ## Outputs logs from all validators and follows them (i.e. tail) - kubectl logs -l v1-purpose=validator --all-containers=true --max-log-requests=1000 --tail=-1 -f + kubectl logs -l "pokt.network/purpose=validator" --all-containers=true --max-log-requests=1000 --tail=-1 -f .PHONY: localnet_down localnet_down: ## Stops LocalNet and cleans up dependencies (tl;dr `tilt down`) tilt down --file=build/localnet/Tiltfile .PHONY: localnet_db_cli -localnet_db_cli: ## Open a CLI to the local containerized postgres instancedb_cli: +localnet_db_cli: ## Open a CLI to the local containerized postgres instance of validator 001: echo "View schema by running 'SELECT schema_name FROM information_schema.schemata;'" - kubectl exec -it services/dependencies-postgresql -- bash -c "psql postgresql://postgres:LocalNetPassword@localhost" + kubectl exec -it validator-001-postgresql-0 -- bash -c "psql postgresql://postgres:LocalNetPassword@localhost" .PHONY: check_cross_module_imports check_cross_module_imports: ## Lists cross-module imports From f3a545ec58af6090b93b6d9bf546d4c63e758ef1 Mon Sep 17 00:00:00 2001 From: d7t Date: Mon, 24 Apr 2023 17:32:54 -0600 Subject: [PATCH 29/46] [E2E] adds in-cluster config for E2E tests (#689) Adds In-Cluster configuration support for the cluster-manager so that DevNet can run E2E tests. Related to #582 Please mark the relevant option(s): - [x] New feature, functionality or library - [ ] Bug fix - [ ] Code health or cleanup - [ ] Major breaking change - [x] Documentation - [ ] Other - Cluster-manager now attempts to use an InCluster config if it can't find one at the default `.kube` config location. - It uses this configuration to run the E2E tests in DevNet. - [x] `make develop_test`; if any code changes were made - [ ] [Docker Compose LocalNet](https://github.com/pokt-network/pocket/blob/main/docs/development/README.md); if any major functionality was changed or introduced - [x] [k8s LocalNet](https://github.com/pokt-network/pocket/blob/main/build/localnet/README.md); if any infrastructure or configuration changes were made - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added, or updated, [`godoc` format comments](https://go.dev/blog/godoc) on touched members (see: [tip.golang.org/doc/comment](https://tip.golang.org/doc/comment)) - [ ] I have tested my changes using the available tooling - [ ] I have updated the corresponding CHANGELOG - [ ] I have updated the corresponding README(s); local and/or global - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added, or updated, [mermaid.js](https://mermaid-js.github.io) diagrams in the corresponding README(s) - [ ] I have added, or updated, documentation and [mermaid.js](https://mermaid-js.github.io) diagrams in `shared/docs/*` if I updated `shared/*`README(s) --- build/Dockerfile.debian.dev | 2 +- build/docs/CHANGELOG.md | 6 +++++- e2e/docs/CHANGELOG.md | 4 ++++ e2e/tests/steps_init_test.go | 20 ++++++++++++++++++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/build/Dockerfile.debian.dev b/build/Dockerfile.debian.dev index 2ec71e003..be4e397db 100644 --- a/build/Dockerfile.debian.dev +++ b/build/Dockerfile.debian.dev @@ -13,7 +13,7 @@ ENV PATH $PATH:$HOME/.local/bin ### Install dependencies # Debian packages RUN apt-get update -qq && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential zip wget ca-certificates curl net-tools dnsutils && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential zip wget ca-certificates curl net-tools dnsutils kubernetes-client && \ rm -rf /var/lib/apt/lists/* /var/cache/apt # protoc diff --git a/build/docs/CHANGELOG.md b/build/docs/CHANGELOG.md index 217789d74..652f394e4 100644 --- a/build/docs/CHANGELOG.md +++ b/build/docs/CHANGELOG.md @@ -7,11 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.0.0.37] - 2023-04-21 +## [0.0.0.37] - 2023-04-27 - Added a `fisherman_per_session` governance parameter - Updated the default `blocks_per_session` from `4` to `1` +## [0.0.0.37] - 2023-04-24 + +- Adds kubectl to dev Dockerfile image + ## [0.0.0.36] - 2023-04-19 - Changed validator DNS names to match new naming convention (again, helm chart was renamed) diff --git a/e2e/docs/CHANGELOG.md b/e2e/docs/CHANGELOG.md index 841d94458..9c9d6d3b0 100644 --- a/e2e/docs/CHANGELOG.md +++ b/e2e/docs/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.5] - 2023-04-24 + +- Attempts to fetch an in-cluster kubeconfig for E2E tests if none is found in `$HOME/.kube` + ## [0.0.0.4] - 2023-04-19 - Changed validator DNS names to match new naming convention (again, helm chart was renamed) diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index 8badb06ae..38e593d27 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -13,6 +13,7 @@ import ( "github.com/pokt-network/pocket/runtime/defaults" cryptoPocket "github.com/pokt-network/pocket/shared/crypto" pocketk8s "github.com/pokt-network/pocket/shared/k8s" + "k8s.io/client-go/rest" "github.com/cucumber/godog" "k8s.io/client-go/kubernetes" @@ -207,11 +208,26 @@ func getClientset() (*kubernetes.Clientset, error) { kubeConfigPath := filepath.Join(userHomeDir, ".kube", "config") kubeConfig, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath) if err != nil { - return nil, fmt.Errorf("failed to build kubeconfig: %w", err) - } + logger.Info().Msgf("no default kubeconfig at %s; attempting to load InClusterConfig", kubeConfigPath) + config := inClusterConfig() + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("failed to get clientset from config: %w", err) + } + return clientset, nil + } + logger.Info().Msgf("e2e tests loaded default kubeconfig located at %s", kubeConfigPath) clientset, err := kubernetes.NewForConfig(kubeConfig) if err != nil { return nil, fmt.Errorf("failed to get clientset from config: %w", err) } return clientset, nil } + +func inClusterConfig() *rest.Config { + config, err := rest.InClusterConfig() + if err != nil { + logger.Fatal().AnErr("inClusterConfig", err) + } + return config +} From efba9a61a19e5792f8269700ec84fe9e59370208 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Tue, 25 Apr 2023 14:59:08 +0200 Subject: [PATCH 30/46] [Docs] Update shared module creation (#693) Incorporates #643 into the shared modules readme. Fixes #642 Please mark the relevant option(s): - [ ] New feature, functionality or library - [ ] Bug fix - [x] Code health or cleanup - [ ] Major breaking change - [x] Documentation - [ ] Other - Updates shared module readme - Adds `rainTeeFactory` type & compile-time enforcement - [ ] `make develop_test`; if any code changes were made - [ ] [Docker Compose LocalNet](https://github.com/pokt-network/pocket/blob/main/docs/development/README.md); if any major functionality was changed or introduced - [ ] [k8s LocalNet](https://github.com/pokt-network/pocket/blob/main/build/localnet/README.md); if any infrastructure or configuration changes were made - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added, or updated, [`godoc` format comments](https://go.dev/blog/godoc) on touched members (see: [tip.golang.org/doc/comment](https://tip.golang.org/doc/comment)) - [ ] I have tested my changes using the available tooling - [ ] I have updated the corresponding CHANGELOG - [ ] I have updated the corresponding README(s); local and/or global - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added, or updated, [mermaid.js](https://mermaid-js.github.io) diagrams in the corresponding README(s) - [ ] I have added, or updated, documentation and [mermaid.js](https://mermaid-js.github.io) diagrams in `shared/docs/*` if I updated `shared/*`README(s) --- p2p/CHANGELOG.md | 4 ++++ p2p/raintree/network.go | 3 +++ shared/CHANGELOG.md | 8 ++++++-- shared/modules/doc/README.md | 23 +++++++++++++---------- shared/modules/factory.go | 3 +++ 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/p2p/CHANGELOG.md b/p2p/CHANGELOG.md index 41993167e..e29c6dadc 100644 --- a/p2p/CHANGELOG.md +++ b/p2p/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.45] - 2023-04-25 + +- Added rainTeeFactory type & compile-time enforcement + ## [0.0.0.44] - 2023-04-20 - Refactor `mockdns` test helpers diff --git a/p2p/raintree/network.go b/p2p/raintree/network.go index a94e52928..a15662813 100644 --- a/p2p/raintree/network.go +++ b/p2p/raintree/network.go @@ -26,8 +26,11 @@ import ( var ( _ typesP2P.Network = &rainTreeNetwork{} _ modules.IntegratableModule = &rainTreeNetwork{} + _ rainTreeFactory = &rainTreeNetwork{} ) +type rainTreeFactory = modules.FactoryWithConfig[typesP2P.Network, RainTreeConfig] + type RainTreeConfig struct { Addr cryptoPocket.Address PeerstoreProvider providers.PeerstoreProvider diff --git a/shared/CHANGELOG.md b/shared/CHANGELOG.md index f9ae0b2f2..90de9fa20 100644 --- a/shared/CHANGELOG.md +++ b/shared/CHANGELOG.md @@ -7,15 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.0.0.53] - 2023-04-18 +## [0.0.0.53] - 2023-04-27 - Added a new `Session` protobuf - Added a `GetActor` function to the Persistence module interface - Added a `GetSession` function to the Utility module interface +## [0.0.0.53] - 2023-04-19 + +- Updated shared module README + ## [0.0.0.52] - 2023-04-17 -- Removed *temporary* `shared/p2p` package; consolidated into `p2p` +- Removed _temporary_ `shared/p2p` package; consolidated into `p2p` ## [0.0.0.51] - 2023-04-13 diff --git a/shared/modules/doc/README.md b/shared/modules/doc/README.md index f03ce4aa8..431e47a3b 100644 --- a/shared/modules/doc/README.md +++ b/shared/modules/doc/README.md @@ -50,6 +50,7 @@ A base module is a module that implements a common interface, exposing the most ```bash ├── base_modules # All the base modules are defined here ├── doc # Documentation for the modules +├── factory.go # Factory interfaces (generic & module specific) ├── mocks # Mocks of the modules will be generated in this folder ├── module.go # Common interfaces for the modules ├── [moduleName]_module.go # These files contain module interface definitions @@ -67,20 +68,22 @@ You might notice that these files include `go:generate` directives, these are us Module creation uses a typical constructor pattern signature `Create(bus modules.Bus, options ...modules.ModuleOption) (modules.Module, error)` where `options ...modules.ModuleOption` is an optional variadic argument that allows for the passing of options to the module. This is useful to configure the module at creation time and it's usually used during prototyping and in "sub-modules" that don't have a specific configuration file and where adding it would add unnecessary complexity and overhead. If a module has a lot of `ModuleOption`s, at that point a configuration file might be advisable. -Currently, module creation is not embedded or enforced in the interface to prevent the initializer from having to use -clunky creation syntax -> `modPackage.new(module).Create(bus modules.Bus)` rather `modPackage.Create(bus modules.Bus)` +To formalize the module creation convention, we've introduced a set of factory interfaces in [`shared/modules/factory.go`](https://github.com/pokt-network/pocket/tree/main/shared/modules/factory.go). +These interfaces allow for more flexible module (or "sub-module"; see: [`RainTreeFactory`](https://github.com/pokt-network/pocket/tree/main/p2p/raintree/network.go)) creation and configuration, while maintaining a clear and concise API. -This is done to optimize for code clarity rather than creation signature enforceability but **may change in the future**. +- `ModuleFactoryWithOptions`: Specialized factory interface for modules conforming to the "typical" signature above. Useful when creating modules that only require optional runtime configurations. +- `FactoryWithConfig`: A generic type, used to create a module with a specific configuration type. +- `FactoryWithOptions`: The generic form used to define `ModuleFactoryWithConfig`. +- `FactoryWithConfigAndOptions`: Another generic type which combines the capabilities of the previous two. Suitable for creating modules that require both specific configuration and optional runtime configurations. -```golang -newModule, err := newModule.Create(bus modules.Bus) +Module creation utilizes the factory interfaces defined above. +Depending on your module's requirements, you can choose the most suitable factory interface for your module (or "sub-module"). -if err != nil { - // handle error -} -``` +For examples of (sub-)modules that implement `ModuleFactoryWithConfig`, see: + +- [`p2pModule`](https://github.com/pokt-network/pocket/tree/main/p2p/module.go) - `WithHostOption` +- [`rpcCurrentHeightProvider`](https://github.com/pokt-network/pocket/tree/main/p2p/providers/current_height_provider/rpc/provider.go) - `WithCustomRPCURL` -For an example of a module that uses a `ModuleOption`, you can search for `WithCustomRPCURL` within the codebase. The code might have changed since this document was written so we are referring to the commit hash [19bf4d3f](https://github.com/pokt-network/pocket/tree/19bf4d3f6507f5d406d9fafdb69b81359bccf110). Essentially the `ModuleOption` sets a custom RPC URL for the module at runtime. diff --git a/shared/modules/factory.go b/shared/modules/factory.go index 95eea52f5..7027efbc4 100644 --- a/shared/modules/factory.go +++ b/shared/modules/factory.go @@ -6,6 +6,7 @@ type ModuleFactoryWithOptions FactoryWithOptions[Module, ModuleOption] // FactoryWithConfig implements a `#Create()` factory method which takes a // required "config" argument of type K and returns a value of type T and an error. +// TECHDEBT: apply enforcement across applicable "sub-modules" (see: `p2p/raintree/network.go`: `raintTreeFactory`) type FactoryWithConfig[T interface{}, K interface{}] interface { Create(bus Bus, cfg K) (T, error) } @@ -13,6 +14,7 @@ type FactoryWithConfig[T interface{}, K interface{}] interface { // FactoryWithOptions implements a `#Create()` factory method which takes a // variadic "optional" argument(s) of type O and returns a value of type T // and an error. +// TECHDEBT: apply enforcement across applicable "sub-modules" type FactoryWithOptions[T interface{}, O interface{}] interface { Create(bus Bus, opts ...O) (T, error) } @@ -20,6 +22,7 @@ type FactoryWithOptions[T interface{}, O interface{}] interface { // FactoryWithConfigAndOptions implements a `#Create()` factory method which // takes both a required "config" argument of type K and a variadic "optional" // argument(s) of type O and returns a value of type T and an error. +// TECHDEBT: apply enforcement across applicable "sub-modules" type FactoryWithConfigAndOptions[T interface{}, K interface{}, O interface{}] interface { Create(bus Bus, cfg K, opts ...O) (T, error) } From 673fccbda225d644b8277f9629765116fa7de659 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 26 Apr 2023 15:50:06 -0700 Subject: [PATCH 31/46] s/actorsToAdds/actorsToAddrs --- utility/session.go | 2 -- utility/session_test.go | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/utility/session.go b/utility/session.go index 92e6c280c..74c49147b 100644 --- a/utility/session.go +++ b/utility/session.go @@ -237,8 +237,6 @@ func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, session return actors } -// OPTIMIZE: Postgres uses a `Twisted Mersenne Twister (TMT)` randomness algorithm. -// We could potentially look into changing everything into a single SQL query but // OPTIMIZE: Postgres uses a `Twisted Mersenne Twister (TMT)` randomness algorithm. // We could potentially look into changing everything into a single SQL query but // would need to verify that it can be implemented in a platform agnostic way. diff --git a/utility/session_test.go b/utility/session_test.go index f953be253..2c4e99466 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -507,8 +507,8 @@ func TestSession_GetSession_SessionHeightAndNumber_ModifiedBlocksPerSession(t *t } func assertActorsDifference(t *testing.T, actors1, actors2 []*coreTypes.Actor, maxSimilarityThreshold float64) { - slice1 := actorsToAdds(actors1) - slice2 := actorsToAdds(actors2) + slice1 := actorsToAddrs(actors1) + slice2 := actorsToAddrs(actors2) var commonCount float64 for _, s1 := range slice1 { for _, s2 := range slice2 { @@ -522,7 +522,7 @@ func assertActorsDifference(t *testing.T, actors1, actors2 []*coreTypes.Actor, m assert.LessOrEqual(t, commonCount, maxCommonCount, "Slices have more similarity than expected: %v vs max %v", slice1, slice2) } -func actorsToAdds(actors []*coreTypes.Actor) []string { +func actorsToAddrs(actors []*coreTypes.Actor) []string { addresses := make([]string, len(actors)) for i, actor := range actors { addresses[i] = actor.Address From 565f0b7a4b6681b1f634629d7e45ed7c1f607a59 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 26 Apr 2023 15:54:57 -0700 Subject: [PATCH 32/46] Updated logger README --- logger/docs/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/logger/docs/README.md b/logger/docs/README.md index 7892e10fe..d6c5e2ad6 100644 --- a/logger/docs/README.md +++ b/logger/docs/README.md @@ -10,6 +10,7 @@ - [Global Logging](#global-logging) - [Module Logging](#module-logging) - [Logger Initialization](#logger-initialization) +- [Submodule / Subcontext Logging](#submodule--subcontext-logging) - [Accessing Logs](#accessing-logs) - [Grafana](#grafana) - [Example Queries](#example-queries) @@ -128,6 +129,24 @@ func (m *sweetModule) Start() error { } ``` +## Submodule / Subcontext Logging + +A common helpful practice is to create a logger that can be easily filtered for within a specific context, such as a specific submodule, a function or a code path. + +```golang +m.logger.With().Str("source", "contextName").Logger(), +``` + +For example: + +```golang + +func (m *Module) fooFunc() { + fooLogger := m.logger.With().Str("source", "fooFunc").Logger(), + // use fooLogger here +} +``` + ## Accessing Logs Logs are written to stdout. In LocalNet, Loki is used to capture log output. Logs can then be queried using [LogQL](https://grafana.com/docs/loki/latest/logql/) syntax. Grafana can be used to visualize the logs. From 5f072f20752198fec6098969d6a41e0b7b19dd45 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 26 Apr 2023 17:32:38 -0700 Subject: [PATCH 33/46] Add clarifying comments --- utility/session.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/utility/session.go b/utility/session.go index 74c49147b..a7a51f986 100644 --- a/utility/session.go +++ b/utility/session.go @@ -136,6 +136,9 @@ func (s *sessionHydrator) hydrateSessionID() error { } prevHashBz, err := hex.DecodeString(prevHash) + if err != nil { + return err + } appPubKeyBz := []byte(s.session.Application.PublicKey) relayChainBz := []byte(string(s.session.RelayChain)) geoZoneBz := []byte(s.session.GeoZone) @@ -225,6 +228,7 @@ func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, session } // Take the first 8 bytes of sessionId to use as the seed + // NB: There is specific reason why `BigEndian` was chosen over `LittleEndian` in this specific context. seed := int64(binary.BigEndian.Uint64(crypto.SHA3Hash(sessionId)[:8])) // Retrieve the indices for the candidates @@ -243,6 +247,7 @@ func pseudoRandomSelection(candidates []*coreTypes.Actor, numTarget int, session // uniqueRandomIndices returns a map of `numIndices` unique random numbers less than `maxIndex` // seeded by `seed`. +// panics if `numIndicies > maxIndex` since that code path SHOULD never be executed. // NB: A map pointing to empty structs is used to simulate set behaviour. func uniqueRandomIndices(seed, maxIndex, numIndices int64) map[int64]struct{} { // This should never happen From aebd770dfb08a8803d71b02a52727a49365cbcf6 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Thu, 27 Apr 2023 12:26:05 -0700 Subject: [PATCH 34/46] Reply to comments --- utility/session_test.go | 64 +++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/utility/session_test.go b/utility/session_test.go index 2c4e99466..88c40a06f 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -15,7 +15,7 @@ import ( "gonum.org/v1/gonum/stat/combin" ) -// TECHDEBT: Geozones are not current implemented, used or tested +// TECHDEBT(#697): Geozones are not current implemented, used or tested func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) { // Test parameters @@ -125,9 +125,9 @@ func TestSession_GetSession_InvalidFutureSession(t *testing.T) { func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *testing.T) { // Prepare an environment with a lot of servicers and fishermen - numServicers := 100 - numFishermen := 100 - runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, 1, numFishermen) + numStakedServicers := 100 + numStakedFishermen := 100 + runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numStakedServicers, 1, numStakedFishermen) // Vary the number of actors per session using gov params and check that the session is populated with the correct number of actorss tests := []struct { @@ -139,42 +139,42 @@ func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *tes }{ { name: "more actors per session than available in network", - numServicersPerSession: int64(numServicers) * 10, - numFishermanPerSession: int64(numFishermen) * 10, - wantServicerCount: numServicers, - wantFishermanCount: numFishermen, + numServicersPerSession: int64(numStakedServicers) * 10, + numFishermanPerSession: int64(numStakedFishermen) * 10, + wantServicerCount: numStakedServicers, + wantFishermanCount: numStakedFishermen, }, { name: "less actors per session than available in network", - numServicersPerSession: int64(numServicers) / 2, - numFishermanPerSession: int64(numFishermen) / 2, - wantServicerCount: numServicers / 2, - wantFishermanCount: numFishermen / 2, + numServicersPerSession: int64(numStakedServicers) / 2, + numFishermanPerSession: int64(numStakedFishermen) / 2, + wantServicerCount: numStakedServicers / 2, + wantFishermanCount: numStakedFishermen / 2, }, { name: "same number of actors per session as available in network", - numServicersPerSession: int64(numServicers), - numFishermanPerSession: int64(numFishermen), - wantServicerCount: numServicers, - wantFishermanCount: numFishermen, + numServicersPerSession: int64(numStakedServicers), + numFishermanPerSession: int64(numStakedFishermen), + wantServicerCount: numStakedServicers, + wantFishermanCount: numStakedFishermen, }, { name: "more than enough servicers but not enough fishermen", - numServicersPerSession: int64(numServicers) / 2, - numFishermanPerSession: int64(numFishermen) * 10, - wantServicerCount: numServicers / 2, - wantFishermanCount: numFishermen, + numServicersPerSession: int64(numStakedServicers) / 2, + numFishermanPerSession: int64(numStakedFishermen) * 10, + wantServicerCount: numStakedServicers / 2, + wantFishermanCount: numStakedFishermen, }, { name: "more than enough fishermen but not enough servicers", - numServicersPerSession: int64(numServicers) * 10, - numFishermanPerSession: int64(numFishermen) / 2, - wantServicerCount: numServicers, - wantFishermanCount: numFishermen / 2, + numServicersPerSession: int64(numStakedServicers) * 10, + numFishermanPerSession: int64(numStakedFishermen) / 2, + wantServicerCount: numStakedServicers, + wantFishermanCount: numStakedFishermen / 2, }, } - // Test constant parameters + // Constant parameters for testing updateParamsHeight := int64(1) querySessionHeight := int64(2) @@ -213,17 +213,17 @@ func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *tes } func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *testing.T) { - // Test constant parameters + // Constant parameters for testing numServicersPerSession := 10 numFishermenPerSession := 2 // Make sure there are MORE THAN ENOUGH servicers and fishermen in the network for each session for chain 1 servicersChain1, servicerKeysChain1 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicersPerSession*2, []string{"chn1"}) - fishermenChain2, fishermenKeysChain2 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFishermenPerSession*2, []string{"chn1"}) + fishermenChain1, fishermenKeysChain1 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFishermenPerSession*2, []string{"chn1"}) // Make sure there are NOT ENOUGH servicers and fishermen in the network for each session for chain 2 servicersChain2, servicerKeysChain2 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicersPerSession/2, []string{"chn2"}) - fishermenChain1, fishermenKeysChain1 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFishermenPerSession/2, []string{"chn2"}) + fishermenChain2, fishermenKeysChain2 := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFishermenPerSession/2, []string{"chn2"}) application, applicationKey := test_artifacts.NewActors(coreTypes.ActorType_ACTOR_TYPE_APP, 1, []string{"chn1", "chn2", "chn3"}) @@ -378,8 +378,10 @@ func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { numBlocksPerSession := 2 // expect a different every other height // Determine probability of overlap using combinatorics - numChoices := combin.GeneralizedBinomial(float64(numServicers), float64(numServicersPerSession)) // (numServicers) C (numServicersPerSession) - numChoicesRemaining := combin.GeneralizedBinomial(float64(numServicers-numServicersPerSession), float64(numServicersPerSession)) // (numServicers - numServicersPerSession) C (numServicersPerSession) + // numChoices = (numServicers) C (numServicersPerSession) + numChoices := combin.GeneralizedBinomial(float64(numServicers), float64(numServicersPerSession)) + // numChoicesRemaining = (numServicers - numServicersPerSession) C (numServicersPerSession) + numChoicesRemaining := combin.GeneralizedBinomial(float64(numServicers-numServicersPerSession), float64(numServicersPerSession)) probabilityOfOverlap := (numChoices - numChoicesRemaining) / numChoices // Prepare the environment @@ -491,7 +493,7 @@ func TestSession_GetSession_ApplicationUnbonds(t *testing.T) { } func TestSession_GetSession_ServicersAndFishermenCounts_GeoZoneAvailability(t *testing.T) { - // TODO(#697): Once GeoZones are implemented, the tests need to be added as well + // TECHDEBT(#697): Once GeoZones are implemented, the tests need to be added as well // Cases: Invalid, unused, non-existent, empty, insufficiently complete, etc... } From 664abf76d3567deb7301738b181b533a1d2aa3a9 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Thu, 27 Apr 2023 12:59:10 -0700 Subject: [PATCH 35/46] Update TECHDEBT ticket --- utility/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utility/session.go b/utility/session.go index a7a51f986..795e2f5ff 100644 --- a/utility/session.go +++ b/utility/session.go @@ -96,7 +96,7 @@ func (s *sessionHydrator) hydrateSessionMetadata() error { // hydrateSessionApplication hydrates the full Application actor based on the address provided func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { - // TECHDEBT: We can remove this decoding process once we use `strings` instead of `[]byte` for addresses + // TECHDEBT(#706): We can remove this decoding process once we use `strings` instead of `[]byte` for addresses addr, err := hex.DecodeString(appAddr) if err != nil { return err From c8249486343cfbf9a3a2ec27640ce16ae0a34b17 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Thu, 27 Apr 2023 13:23:06 -0700 Subject: [PATCH 36/46] Apply demorgan's law --- utility/session.go | 6 +++--- utility/session_test.go | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/utility/session.go b/utility/session.go index 795e2f5ff..b755484a9 100644 --- a/utility/session.go +++ b/utility/session.go @@ -113,7 +113,7 @@ func (s *sessionHydrator) validateApplicationSession() error { return fmt.Errorf("application %s does not stake for relay chain %s", app.Address, s.session.RelayChain) } - if app.PausedHeight == -1 || app.UnstakingHeight == -1 { + if app.PausedHeight != -1 || app.UnstakingHeight != -1 { return fmt.Errorf("application %s is either unstaked or paused", app.Address) } @@ -167,7 +167,7 @@ func (s *sessionHydrator) hydrateSessionServicers() error { candidateServicers := make([]*coreTypes.Actor, 0) for _, servicer := range servicers { // Sanity check the servicer is not paused, jailed or unstaking - if !(servicer.PausedHeight == -1 && servicer.UnstakingHeight == -1) { + if servicer.PausedHeight != -1 || servicer.UnstakingHeight != -1 { return fmt.Errorf("hydrateSessionServicers should not have encountered a paused or unstaking servicer: %s", servicer.Address) } @@ -201,7 +201,7 @@ func (s *sessionHydrator) hydrateSessionFishermen() error { candidateFishermen := make([]*coreTypes.Actor, 0) for _, fisher := range fishermen { // Sanity check the fisher is not paused, jailed or unstaking - if !(fisher.PausedHeight == -1 && fisher.UnstakingHeight == -1) { + if fisher.PausedHeight != -1 || fisher.UnstakingHeight != -1 { return fmt.Errorf("hydrateSessionFishermen should not have encountered a paused or unstaking fisherman: %s", fisher.Address) } diff --git a/utility/session_test.go b/utility/session_test.go index 88c40a06f..ce88154af 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -24,7 +24,8 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) geoZone := "unused_geo" numFishermen := 1 numServicers := 1 - expectedSessionId := "5acf559f1a3faf3bea7eb692fe51bc1e2e5fb687ede0a6daa7d42399da4aa82b" // needs to be manually updated if business logic changes + // needs to be manually updated if business logic changes + expectedSessionId := "5acf559f1a3faf3bea7eb692fe51bc1e2e5fb687ede0a6daa7d42399da4aa82b" runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, numServicers, 1, numFishermen) @@ -32,7 +33,7 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) require.Len(t, runtimeCfg.GetGenesis().Applications, 1) app := runtimeCfg.GetGenesis().Applications[0] require.Len(t, runtimeCfg.GetGenesis().Fishermen, 1) - fish := runtimeCfg.GetGenesis().Fishermen[0] + fisher := runtimeCfg.GetGenesis().Fishermen[0] require.Len(t, runtimeCfg.GetGenesis().Servicers, 1) servicer := runtimeCfg.GetGenesis().Servicers[0] @@ -49,7 +50,7 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) require.Len(t, session.Servicers, numServicers) require.Equal(t, servicer.Address, session.Servicers[0].Address) require.Len(t, session.Fishermen, numFishermen) - require.Equal(t, fish.Address, session.Fishermen[0].Address) + require.Equal(t, fisher.Address, session.Fishermen[0].Address) } func TestSession_GetSession_ApplicationInvalid(t *testing.T) { From cf2f6e070a07004e4e36ae84b414ecbd8161a0f9 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Thu, 27 Apr 2023 13:24:23 -0700 Subject: [PATCH 37/46] Markdown nit --- utility/doc/PROTOCOL_SESSION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utility/doc/PROTOCOL_SESSION.md b/utility/doc/PROTOCOL_SESSION.md index 219204698..92f3c28ee 100644 --- a/utility/doc/PROTOCOL_SESSION.md +++ b/utility/doc/PROTOCOL_SESSION.md @@ -3,7 +3,7 @@ - [Interface](#interface) - [Session Creation Flow](#session-creation-flow) -_WIP: Run `TODO_SEARCH=utility/session_ make todo*search` to identify all the WIP related to session generation.* +*WIP: Run `TODO*SEARCH=utility/session* make todo*search` to identify all the WIP related to session generation.\* ## Interface From d0d34634e4c798476653ffcc111ddb3b7e66e495 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 28 Apr 2023 13:09:34 -0700 Subject: [PATCH 38/46] Update Makefile Co-authored-by: Bryan White --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4b1621ac4..7e751bc5b 100644 --- a/Makefile +++ b/Makefile @@ -455,7 +455,7 @@ benchmark_p2p_peerstore: ## Run P2P peerstore benchmarks # BUG - There is a known existing bug in this code # DISCUSS_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way for the reviewer of a PR to start / reply to a discussion. # TODO_IN_THIS_COMMIT - SHOULD NEVER BE COMMITTED TO MASTER. It is a way to start the review process while non-critical changes are still in progress -TODO_KEYWORDS = -e "TODO" -e "ADR" -e "TECHDEBT" -e "IMPROVE" -e "OPTIMIZE" -e "DISCUSS" -e "INCOMPLETE" -e "INVESTIGATE" -e "CLEANUP" -e "HACK" -e "REFACTOR" -e "CONSIDERATION" -e "TODO_IN_THIS_COMMIT" -e "DISCUSS_IN_THIS_COMMIT" -e "CONSOLIDATE" -e "DEPRECATE" -e "ADDTEST" -e "RESEARCH" -e "BUG" +TODO_KEYWORDS = -e "TODO" -e "DECIDE" -e "TECHDEBT" -e "IMPROVE" -e "OPTIMIZE" -e "DISCUSS" -e "INCOMPLETE" -e "INVESTIGATE" -e "CLEANUP" -e "HACK" -e "REFACTOR" -e "CONSIDERATION" -e "TODO_IN_THIS_COMMIT" -e "DISCUSS_IN_THIS_COMMIT" -e "CONSOLIDATE" -e "DEPRECATE" -e "ADDTEST" -e "RESEARCH" -e "BUG" # How do I use TODOs? # 1. : ; From 2e92fd239a280ff3997dfbd2a438d7ce80f8946f Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 28 Apr 2023 13:25:05 -0700 Subject: [PATCH 39/46] Update utility/session_test.go Co-authored-by: Bryan White --- utility/session_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/utility/session_test.go b/utility/session_test.go index ce88154af..95d827b0e 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -5,14 +5,15 @@ import ( "math" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gonum.org/v1/gonum/stat/combin" + "github.com/pokt-network/pocket/runtime/test_artifacts" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/utility/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gonum.org/v1/gonum/stat/combin" ) // TECHDEBT(#697): Geozones are not current implemented, used or tested From b83efc7eea0b43369d7e1cd39e505aab601443be Mon Sep 17 00:00:00 2001 From: Bryan White Date: Fri, 28 Apr 2023 08:02:58 +0200 Subject: [PATCH 40/46] [P2P] chore: remove unnecessary `stdnetwork` package (#703) Removes the unneeded `stdnetwork` package. Covers removal goal of #266. The simplification goal is tracked by #553. TLDR; should close #266. Please mark the relevant option(s): - [ ] New feature, functionality or library - [ ] Bug fix - [x] Code health or cleanup - [ ] Major breaking change - [ ] Documentation - [ ] Other - Removed unneeded `stdnetwork` package - Removed unneeded `use_rain_tree` P2P config field - [ ] `make develop_test`; if any code changes were made - [x] [Docker Compose LocalNet](https://github.com/pokt-network/pocket/blob/main/docs/development/README.md); if any major functionality was changed or introduced - [x] [k8s LocalNet](https://github.com/pokt-network/pocket/blob/main/build/localnet/README.md); if any infrastructure or configuration changes were made - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added, or updated, [`godoc` format comments](https://go.dev/blog/godoc) on touched members (see: [tip.golang.org/doc/comment](https://tip.golang.org/doc/comment)) - [ ] I have tested my changes using the available tooling - [ ] I have updated the corresponding CHANGELOG - [ ] I have updated the corresponding README(s); local and/or global - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added, or updated, [mermaid.js](https://mermaid-js.github.io) diagrams in the corresponding README(s) - [ ] I have added, or updated, documentation and [mermaid.js](https://mermaid-js.github.io) diagrams in `shared/docs/*` if I updated `shared/*`README(s) --- p2p/CHANGELOG.md | 5 + p2p/module.go | 33 ++--- p2p/module_test.go | 2 +- p2p/stdnetwork/network.go | 103 ---------------- p2p/stdnetwork/network_test.go | 159 ------------------------- p2p/utils_test.go | 1 - runtime/configs/config.go | 1 - runtime/configs/proto/p2p_config.proto | 9 +- runtime/docs/CHANGELOG.md | 6 +- runtime/manager_test.go | 2 - 10 files changed, 27 insertions(+), 294 deletions(-) delete mode 100644 p2p/stdnetwork/network.go delete mode 100644 p2p/stdnetwork/network_test.go diff --git a/p2p/CHANGELOG.md b/p2p/CHANGELOG.md index e29c6dadc..db08f86ad 100644 --- a/p2p/CHANGELOG.md +++ b/p2p/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.46] - 2023-04-27 + +- Removed unneeded `stdnetwork` package +- Removed unneeded `use_rain_tree` P2P config field + ## [0.0.0.45] - 2023-04-25 - Added rainTeeFactory type & compile-time enforcement diff --git a/p2p/module.go b/p2p/module.go index 7f97a418e..e3bf1c9a8 100644 --- a/p2p/module.go +++ b/p2p/module.go @@ -20,7 +20,6 @@ import ( "github.com/pokt-network/pocket/p2p/providers/peerstore_provider" persABP "github.com/pokt-network/pocket/p2p/providers/peerstore_provider/persistence" "github.com/pokt-network/pocket/p2p/raintree" - "github.com/pokt-network/pocket/p2p/stdnetwork" typesP2P "github.com/pokt-network/pocket/p2p/types" "github.com/pokt-network/pocket/p2p/utils" "github.com/pokt-network/pocket/runtime/configs" @@ -156,10 +155,10 @@ func (m *p2pModule) Start() (err error) { // Return early if host has already been started (e.g. via `WithHostOption`) if m.host == nil { - // Libp2p host providea via `WithHost()` option are destroyed when + // Libp2p hosts provided via `WithHost()` option are destroyed when // `#Stop()`ing the module. Therefore, a new one must be created. - // The new host may be configured differently that which was provided - // originally in `WithHost()`. + // The new host may be configured differently than that which was + // provided originally in `WithHost()`. if len(m.options) != 0 { m.logger.Warn().Msg("creating new libp2p host") } @@ -285,23 +284,15 @@ func (m *p2pModule) setupCurrentHeightProvider() error { // setupNetwork instantiates the configured network implementation. func (m *p2pModule) setupNetwork() (err error) { - if m.cfg.UseRainTree { - m.network, err = raintree.NewRainTreeNetwork( - m.GetBus(), - raintree.RainTreeConfig{ - Host: m.host, - Addr: m.address, - PeerstoreProvider: m.pstoreProvider, - CurrentHeightProvider: m.currentHeightProvider, - }, - ) - } else { - m.network, err = stdnetwork.NewNetwork( - m.host, - m.pstoreProvider, - m.currentHeightProvider, - ) - } + m.network, err = raintree.NewRainTreeNetwork( + m.GetBus(), + raintree.RainTreeConfig{ + Host: m.host, + Addr: m.address, + PeerstoreProvider: m.pstoreProvider, + CurrentHeightProvider: m.currentHeightProvider, + }, + ) return err } diff --git a/p2p/module_test.go b/p2p/module_test.go index 9a8cc1e0c..111b4e4f7 100644 --- a/p2p/module_test.go +++ b/p2p/module_test.go @@ -157,7 +157,7 @@ func TestP2pModule_WithHostOption_Restart(t *testing.T) { mockRuntimeMgr := mockModules.NewMockRuntimeMgr(ctrl) mockBus := createMockBus(t, mockRuntimeMgr) - genesisStateMock := createMockGenesisState(keys) + genesisStateMock := createMockGenesisState(nil) persistenceMock := preparePersistenceMock(t, mockBus, genesisStateMock) mockBus.EXPECT().GetPersistenceModule().Return(persistenceMock).AnyTimes() diff --git a/p2p/stdnetwork/network.go b/p2p/stdnetwork/network.go deleted file mode 100644 index 5d4cd072a..000000000 --- a/p2p/stdnetwork/network.go +++ /dev/null @@ -1,103 +0,0 @@ -// TECHDEBT(olshansky): Delete this once we are fully comfortable with RainTree moving forward. - -package stdnetwork - -import ( - "fmt" - - libp2pHost "github.com/libp2p/go-libp2p/core/host" - - "github.com/pokt-network/pocket/logger" - "github.com/pokt-network/pocket/p2p/providers" - typesP2P "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/modules" -) - -var ( - _ typesP2P.Network = &network{} - _ modules.IntegratableModule = &network{} -) - -type network struct { - host libp2pHost.Host - pstore typesP2P.Peerstore - - logger *modules.Logger -} - -func NewNetwork(host libp2pHost.Host, pstoreProvider providers.PeerstoreProvider, currentHeightProvider providers.CurrentHeightProvider) (typesP2P.Network, error) { - networkLogger := logger.Global.CreateLoggerForModule("network") - networkLogger.Info().Msg("Initializing stdnetwork") - - pstore, err := pstoreProvider.GetStakedPeerstoreAtHeight(currentHeightProvider.CurrentHeight()) - if err != nil { - return nil, err - } - - return &network{ - host: host, - logger: networkLogger, - pstore: pstore, - }, nil -} - -func (n *network) NetworkBroadcast(data []byte) error { - for _, peer := range n.pstore.GetPeerList() { - if err := utils.Libp2pSendToPeer(n.host, data, peer); err != nil { - n.logger.Error(). - Err(err). - Bool("TODO", true). - Str("pokt address", peer.GetAddress().String()). - Msg("broadcasting to peer") - continue - } - } - return nil -} - -func (n *network) NetworkSend(data []byte, address cryptoPocket.Address) error { - peer := n.pstore.GetPeer(address) - if peer == nil { - return fmt.Errorf("peer with address %s not in peerstore", address) - } - - if err := utils.Libp2pSendToPeer(n.host, data, peer); err != nil { - return err - } - return nil -} - -func (n *network) HandleNetworkData(data []byte) ([]byte, error) { - return data, nil // intentional passthrough -} - -func (n *network) GetPeerstore() typesP2P.Peerstore { - return n.pstore -} - -func (n *network) AddPeer(peer typesP2P.Peer) error { - // Noop if peer with the pokt address already exists in the peerstore. - // TECHDEBT: add method(s) to update peers. - if p := n.pstore.GetPeer(peer.GetAddress()); p != nil { - return nil - } - - if err := utils.AddPeerToLibp2pHost(n.host, peer); err != nil { - return err - } - - return n.pstore.AddPeer(peer) -} - -func (n *network) RemovePeer(peer typesP2P.Peer) error { - if err := utils.RemovePeerFromLibp2pHost(n.host, peer); err != nil { - return err - } - - return n.pstore.RemovePeer(peer.GetAddress()) -} - -func (n *network) GetBus() modules.Bus { return nil } -func (n *network) SetBus(_ modules.Bus) {} diff --git a/p2p/stdnetwork/network_test.go b/p2p/stdnetwork/network_test.go deleted file mode 100644 index f19c7334d..000000000 --- a/p2p/stdnetwork/network_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package stdnetwork - -import ( - "fmt" - "github.com/pokt-network/pocket/runtime/defaults" - "testing" - - "github.com/golang/mock/gomock" - 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" - - typesP2P "github.com/pokt-network/pocket/p2p/types" - mock_types "github.com/pokt-network/pocket/p2p/types/mocks" - "github.com/pokt-network/pocket/p2p/utils" - cryptoPocket "github.com/pokt-network/pocket/shared/crypto" - mockModules "github.com/pokt-network/pocket/shared/modules/mocks" -) - -// https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2 -const testIP6ServiceURL = "[2a00:1450:4005:802::2004]:8080" - -// TECHDEBT(#609): move & de-dup. -var testLocalServiceURL = fmt.Sprintf("127.0.0.1:%d", defaults.DefaultP2PPort) - -func TestLibp2pNetwork_AddPeer(t *testing.T) { - p2pNet := newTestLibp2pNetwork(t) - libp2pPStore := p2pNet.host.Peerstore() - - // NB: assert initial state - require.Equal(t, 1, p2pNet.pstore.Size()) - - existingPeer := p2pNet.pstore.GetPeerList()[0] - require.NotNil(t, existingPeer) - - existingPeerInfo, err := utils.Libp2pAddrInfoFromPeer(existingPeer) - require.NoError(t, err) - - existingPeerstoreAddrs := libp2pPStore.Addrs(existingPeerInfo.ID) - require.Len(t, existingPeerstoreAddrs, 1) - - existingPeerMultiaddr, err := utils.Libp2pMultiaddrFromServiceURL(existingPeer.GetServiceURL()) - require.NoError(t, err) - require.Equal(t, existingPeerstoreAddrs[0].String(), existingPeerMultiaddr.String()) - - newPublicKey, err := cryptoPocket.GeneratePublicKey() - newPoktAddr := newPublicKey.Address() - require.NoError(t, err) - - newPeer := &typesP2P.NetworkPeer{ - PublicKey: newPublicKey, - Address: newPoktAddr, - ServiceURL: testIP6ServiceURL, - } - newPeerInfo, err := utils.Libp2pAddrInfoFromPeer(newPeer) - require.NoError(t, err) - newPeerMultiaddr := newPeerInfo.Addrs[0] - - // NB: add to address book - err = p2pNet.AddPeer(newPeer) - require.NoError(t, err) - - require.Len(t, p2pNet.pstore, 2) - require.Equal(t, p2pNet.pstore.GetPeer(existingPeer.GetAddress()), existingPeer) - require.Equal(t, p2pNet.pstore.GetPeer(newPeer.Address), newPeer) - - existingPeerstoreAddrs = libp2pPStore.Addrs(existingPeerInfo.ID) - newPeerstoreAddrs := libp2pPStore.Addrs(newPeerInfo.ID) - require.Len(t, existingPeerstoreAddrs, 1) - require.Len(t, newPeerstoreAddrs, 1) - require.Equal(t, newPeerstoreAddrs[0].String(), newPeerMultiaddr.String()) -} - -func TestLibp2pNetwork_RemovePeer(t *testing.T) { - p2pNet := newTestLibp2pNetwork(t) - peerstore := p2pNet.host.Peerstore() - - // NB: assert initial state - require.Len(t, p2pNet.pstore, 1) - - existingPeer := p2pNet.pstore.GetPeerList()[0] - require.NotNil(t, existingPeer) - - existingPeerInfo, err := utils.Libp2pAddrInfoFromPeer(existingPeer) - require.NoError(t, err) - - existingPeerstoreAddrs := peerstore.Addrs(existingPeerInfo.ID) - require.Len(t, existingPeerstoreAddrs, 1) - - existingPeerMultiaddr, err := utils.Libp2pMultiaddrFromServiceURL(existingPeer.GetServiceURL()) - require.NoError(t, err) - require.Equal(t, existingPeerstoreAddrs[0].String(), existingPeerMultiaddr.String()) - - err = p2pNet.RemovePeer(existingPeer) - require.NoError(t, err) - - require.Len(t, p2pNet.pstore, 0) - - // NB: libp2p peerstore implementations only remove peer keys and metadata - // but continue to resolve multiaddrs until their respective TTLs expire. - // (see: https://github.com/libp2p/go-libp2p/blob/v0.25.1/p2p/host/peerstore/pstoremem/peerstore.go#L108) - // (see: https://github.com/libp2p/go-libp2p/blob/v0.25.1/p2p/host/peerstore/pstoreds/peerstore.go#L187) - - existingPeerstoreAddrs = peerstore.Addrs(existingPeerInfo.ID) - require.Len(t, existingPeerstoreAddrs, 1) -} - -// TECHDEBT(#609): move & de-duplicate -func newTestLibp2pNetwork(t *testing.T) *network { - ctrl := gomock.NewController(t) - consensusMock := mockModules.NewMockConsensusModule(ctrl) - consensusMock.EXPECT().CurrentHeight().Return(uint64(1)).AnyTimes() - - pstore := make(typesP2P.PeerAddrMap) - pstoreProviderMock := mock_types.NewMockPeerstoreProvider(ctrl) - pstoreProviderMock.EXPECT().GetStakedPeerstoreAtHeight(gomock.Any()).Return(pstore, nil).AnyTimes() - - privKey, err := cryptoPocket.GeneratePrivateKey() - require.NoError(t, err) - - selfPeer := &typesP2P.NetworkPeer{ - PublicKey: privKey.PublicKey(), - Address: privKey.Address(), - ServiceURL: testLocalServiceURL, - } - err = pstore.AddPeer(selfPeer) - require.NoError(t, err) - - host := newLibp2pMockNetHost(t, privKey, selfPeer) - defer host.Close() - - p2pNetwork, err := NewNetwork( - host, - pstoreProviderMock, - consensusMock, - ) - require.NoError(t, err) - - libp2pNet, ok := p2pNetwork.(*network) - require.Truef(t, ok, "unexpected p2pNetwork type: %T", p2pNetwork) - - return libp2pNet -} - -// TECHDEBT(#609): move & de-duplicate -func newLibp2pMockNetHost(t *testing.T, privKey cryptoPocket.PrivateKey, peer *typesP2P.NetworkPeer) libp2pHost.Host { - 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/p2p/utils_test.go b/p2p/utils_test.go index ed79e8d07..c40b95210 100644 --- a/p2p/utils_test.go +++ b/p2p/utils_test.go @@ -165,7 +165,6 @@ func createMockRuntimeMgrs(t *testing.T, numValidators int) []modules.RuntimeMgr Hostname: hostname, PrivateKey: valKeys[i].String(), Port: uint32(port), - UseRainTree: true, ConnectionType: types.ConnectionType_EmptyConnection, }, } diff --git a/runtime/configs/config.go b/runtime/configs/config.go index a4f35ac4e..c3e3d4fe2 100644 --- a/runtime/configs/config.go +++ b/runtime/configs/config.go @@ -128,7 +128,6 @@ func NewDefaultConfig(options ...func(*Config)) *Config { }, P2P: &P2PConfig{ Port: defaults.DefaultP2PPort, - UseRainTree: defaults.DefaultP2PUseRainTree, ConnectionType: defaults.DefaultP2PConnectionType, MaxMempoolCount: defaults.DefaultP2PMaxMempoolCount, }, diff --git a/runtime/configs/proto/p2p_config.proto b/runtime/configs/proto/p2p_config.proto index d679e74b5..1ce193bcb 100644 --- a/runtime/configs/proto/p2p_config.proto +++ b/runtime/configs/proto/p2p_config.proto @@ -10,9 +10,8 @@ message P2PConfig { string private_key = 1; // hex encoded string hostname = 2; uint32 port = 3; - bool use_rain_tree = 4; - conn.ConnectionType connection_type = 5; - uint64 max_mempool_count = 6; // this is used to limit the number of nonces that can be stored in the mempool, after which a FIFO mechanism is used to remove the oldest nonces and make space for the new ones - bool is_client_only = 7; - string bootstrap_nodes_csv = 8; // string in the format "http://somenode:50832,http://someothernode:50832". Refer to `p2p/module_test.go` for additional details. + conn.ConnectionType connection_type = 4; + uint64 max_mempool_count = 5; // this is used to limit the number of nonces that can be stored in the mempool, after which a FIFO mechanism is used to remove the oldest nonces and make space for the new ones + bool is_client_only = 6; + string bootstrap_nodes_csv = 7; // string in the format "http://somenode:50832,http://someothernode:50832". Refer to `p2p/module_test.go` for additional details. } diff --git a/runtime/docs/CHANGELOG.md b/runtime/docs/CHANGELOG.md index da260b188..a8395fd75 100644 --- a/runtime/docs/CHANGELOG.md +++ b/runtime/docs/CHANGELOG.md @@ -7,12 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.0.0.35] - 2023-04-21 +## [0.0.0.36] - 2023-04-28 - Consolidated files for defaults together - Updated `BlocksPerSession` default to 1 - Added an ability to add options to the `NewGenesisState` helper for more thorough testing +## [0.0.0.35] - 2023-04-27 + +- Removed unneeded `use_rain_tree` P2P config field + ## [0.0.0.34] - 2023-04-19 - Changed `Validator1EndpointK8S` which now reflects the new value. diff --git a/runtime/manager_test.go b/runtime/manager_test.go index 0774fbd64..a120cad2d 100644 --- a/runtime/manager_test.go +++ b/runtime/manager_test.go @@ -4210,7 +4210,6 @@ func TestNewManagerFromReaders(t *testing.T) { PrivateKey: "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", Hostname: "node1.consensus", Port: defaults.DefaultP2PPort, - UseRainTree: true, ConnectionType: configTypes.ConnectionType_TCPConnection, MaxMempoolCount: 1e5, }, @@ -4259,7 +4258,6 @@ func TestNewManagerFromReaders(t *testing.T) { PrivateKey: "4ff3292ff14213149446f8208942b35439cb4b2c5e819f41fb612e880b5614bdd6cea8706f6ee6672c1e013e667ec8c46231e0e7abcf97ba35d89fceb8edae45", Hostname: "node1.consensus", Port: 42069, - UseRainTree: true, ConnectionType: configTypes.ConnectionType_TCPConnection, MaxMempoolCount: defaults.DefaultP2PMaxMempoolCount, }, From c2e2c9a1d16d0b914020ae9d7a451fbc1806be41 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Fri, 28 Apr 2023 08:37:10 +0200 Subject: [PATCH 41/46] [P2P] refactor: rename "network" to "router" (#704) ## Description Refactors everything in the P2P module which previously referred to a "network" to instead be thought of as a "router". This begins with the `typesP2P.Network` interface and the changes propagates down from there, through implementations and into variable names, tests, etc. ## Issue Fixes # ## Type of change Please mark the relevant option(s): - [ ] New feature, functionality or library - [ ] Bug fix - [x] Code health or cleanup - [ ] Major breaking change - [ ] Documentation - [ ] Other ## List of changes - Renamed `Network` interface to `Router` - Shortened `Router#NetworkBroadcast` to `#Broadcast` - Shortened `Router#NetworkSend` to `#Send` - Shortened `Router#networkSendInternal` to `#sendInternal` - Shortened `Router#networkBroadcastAtLevel` to `#broadcastAtLevel` - Renamed `rainTreeNetwork` to `rainTreeRouter` - Renamed `rainTreeNetwork` method receivers - Renamed `p2pModule#network` to `#router` - Renamed `p2pModule#setupNetwork()` to `#setupRouter()` - Renamed config var in `rainTreeRouter#Create` - Renamed router logger - Renamed `NewRainTreeNetwork()` to `NewRainTreeRouter()` - Refactored peers_manager_test.go - Refactored network_test.go - Simplified p2p module/router config handoff - Updated debug logging ## Testing - [ ] `make develop_test`; if any code changes were made - [ ] [Docker Compose LocalNet](https://github.com/pokt-network/pocket/blob/main/docs/development/README.md); if any major functionality was changed or introduced - [ ] [k8s LocalNet](https://github.com/pokt-network/pocket/blob/main/build/localnet/README.md); if any infrastructure or configuration changes were made ## Required Checklist - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added, or updated, [`godoc` format comments](https://go.dev/blog/godoc) on touched members (see: [tip.golang.org/doc/comment](https://tip.golang.org/doc/comment)) - [ ] I have tested my changes using the available tooling - [ ] I have updated the corresponding CHANGELOG ### If Applicable Checklist - [ ] I have updated the corresponding README(s); local and/or global - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added, or updated, [mermaid.js](https://mermaid-js.github.io) diagrams in the corresponding README(s) - [ ] I have added, or updated, documentation and [mermaid.js](https://mermaid-js.github.io) diagrams in `shared/docs/*` if I updated `shared/*`README(s) --- consensus/doc/CHANGELOG.md | 4 + consensus/state_sync/interfaces.go | 6 +- p2p/CHANGELOG.md | 24 +++ p2p/bootstrap.go | 6 +- p2p/event_handler.go | 8 +- p2p/module.go | 38 ++--- p2p/raintree/peers_manager_test.go | 32 ++-- p2p/raintree/peerstore_utils.go | 30 ++-- p2p/raintree/{network.go => router.go} | 152 +++++++++--------- .../{network_test.go => router_test.go} | 26 +-- p2p/raintree/target.go | 2 +- p2p/types/{network.go => router.go} | 6 +- shared/modules/factory.go | 2 +- 13 files changed, 183 insertions(+), 153 deletions(-) rename p2p/raintree/{network.go => router.go} (58%) rename p2p/raintree/{network_test.go => router_test.go} (88%) rename p2p/types/{network.go => router.go} (87%) diff --git a/consensus/doc/CHANGELOG.md b/consensus/doc/CHANGELOG.md index c94e9d933..5ee4809f7 100644 --- a/consensus/doc/CHANGELOG.md +++ b/consensus/doc/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.49] - 2023-04-27 + +- Updated comments following P2P refactor + ## [0.0.0.48] - 2023-04-17 - Debug logging improvements diff --git a/consensus/state_sync/interfaces.go b/consensus/state_sync/interfaces.go index c4aa53d3a..f6772ac45 100644 --- a/consensus/state_sync/interfaces.go +++ b/consensus/state_sync/interfaces.go @@ -58,7 +58,7 @@ type StateSyncModuleLEGACY interface { // - Retrieve a list of active peers with their metadata (identified and retrieved through P2P's `Churn Management`) GetPeerMetadata(GetPeerSyncMeta func() (peers []PeerSyncMeta, err error)) - // `NetworkSend` function contract: + // `typesP2P.Router#Send()` function contract: // - sends data to an address via P2P network NetworkSend(NetworkSend func(data []byte, address cryptoPocket.Address) error) @@ -91,10 +91,10 @@ type StateSyncModuleLEGACY interface { // An eligible peer is when `PeerMeta.MinHeight <= blockHeight <= PeerMeta.MaxHeight` GetRandomEligiblePeersForHeight(blockHeight int64) (eligiblePeer PeerSyncMeta, err error) - // Uses `NetworkSend` to send a `BlockRequestMessage` to a specific peer + // Uses `typesP2P.Router#Send()` to send a `BlockRequestMessage` to a specific peer SendBlockRequest(peerId string) error - // Uses 'NetworkSend' to send a `BlockResponseMessage` to a specific peer + // Uses 'typesP2P.Router#Send()' to send a `BlockResponseMessage` to a specific peer // This function is used in 'ServerMode()' HandleBlockRequest(message BlockRequestMessage) error diff --git a/p2p/CHANGELOG.md b/p2p/CHANGELOG.md index db08f86ad..e0da8a6cd 100644 --- a/p2p/CHANGELOG.md +++ b/p2p/CHANGELOG.md @@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.47] - 2023-04-27 + +- Renamed `Network` interface to `Router` +- Shortened `Router#NetworkBroadcast` to `#Broadcast` +- Shortened `Router#NetworkSend` to `#Send` +- Shortened `Router#networkSendInternal` to `#sendInternal` +- Shortened `Router#networkBroadcastAtLevel` to `#broadcastAtLevel` +- Renamed `rainTreeNetwork` to `rainTreeRouter` +- Renamed `rainTreeNetwork` method receivers +- Renamed `p2pModule#network` to `#router` +- Renamed `p2pModule#setupNetwork()` to `#setupRouter()` +- Renamed config var in `rainTreeRouter#Create` +- Renamed router logger +- Renamed `NewRainTreeNetwork()` to `NewRainTreeRouter()` +- Refactored peers_manager_test.go +- Refactored network_test.go +- Simplified p2p module/router config handoff +- Updated debug logging + +## [0.0.0.46] - 2023-04-27 + +- Removed unneeded `stdnetwork` package +- Removed unneeded `use_rain_tree` P2P config field + ## [0.0.0.46] - 2023-04-27 - Removed unneeded `stdnetwork` package diff --git a/p2p/bootstrap.go b/p2p/bootstrap.go index bd49ea2fd..f9f76a049 100644 --- a/p2p/bootstrap.go +++ b/p2p/bootstrap.go @@ -75,15 +75,15 @@ func (m *p2pModule) bootstrap() error { } for _, peer := range pstore.GetPeerList() { - m.logger.Debug().Str("address", peer.GetAddress().String()).Msg("Adding peer to network") - if err := m.network.AddPeer(peer); err != nil { + m.logger.Debug().Str("address", peer.GetAddress().String()).Msg("Adding peer to router") + if err := m.router.AddPeer(peer); err != nil { m.logger.Error().Err(err). Str("pokt_address", peer.GetAddress().String()). Msg("adding peer") } } - if m.network.GetPeerstore().Size() == 0 { + if m.router.GetPeerstore().Size() == 0 { return fmt.Errorf("bootstrap failed") } return nil diff --git a/p2p/event_handler.go b/p2p/event_handler.go index 7ea596eef..48e1a7d73 100644 --- a/p2p/event_handler.go +++ b/p2p/event_handler.go @@ -23,7 +23,7 @@ func (m *p2pModule) HandleEvent(event *anypb.Any) error { return fmt.Errorf("failed to cast event to ConsensusNewHeightEvent") } - oldPeerList := m.network.GetPeerstore().GetPeerList() + oldPeerList := m.router.GetPeerstore().GetPeerList() updatedPeerstore, err := m.pstoreProvider.GetStakedPeerstoreAtHeight(consensusNewHeightEvent.Height) if err != nil { return err @@ -31,12 +31,12 @@ func (m *p2pModule) HandleEvent(event *anypb.Any) error { added, removed := oldPeerList.Delta(updatedPeerstore.GetPeerList()) for _, add := range added { - if err := m.network.AddPeer(add); err != nil { + if err := m.router.AddPeer(add); err != nil { return err } } for _, rm := range removed { - if err := m.network.RemovePeer(rm); err != nil { + if err := m.router.RemovePeer(rm); err != nil { return err } } @@ -50,7 +50,7 @@ func (m *p2pModule) HandleEvent(event *anypb.Any) error { m.logger.Debug().Fields(messaging.TransitionEventToMap(stateMachineTransitionEvent)).Msg("Received state machine transition event") if stateMachineTransitionEvent.NewState == string(coreTypes.StateMachineState_P2P_Bootstrapping) { - if m.network.GetPeerstore().Size() == 0 { + if m.router.GetPeerstore().Size() == 0 { m.logger.Warn().Msg("No peers in addrbook, bootstrapping") if err := m.bootstrap(); err != nil { diff --git a/p2p/module.go b/p2p/module.go index e3bf1c9a8..093e12e1f 100644 --- a/p2p/module.go +++ b/p2p/module.go @@ -55,8 +55,8 @@ type p2pModule struct { pstoreProvider providers.PeerstoreProvider // Assigned during `#Start()`. TLDR; `host` listens on instantiation. - // and `network` depends on `host`. - network typesP2P.Network + // and `router` depends on `host`. + router typesP2P.Router // host represents a libp2p network node, it encapsulates a libp2p peerstore // & connection manager. `libp2p.New` configures and starts listening // according to options. Assigned via `#Start()` (starts on instantiation). @@ -82,7 +82,7 @@ func WithHostOption(host libp2pHost.Host) modules.ModuleOption { } func (m *p2pModule) Create(bus modules.Bus, options ...modules.ModuleOption) (modules.Module, error) { - logger.Global.Debug().Msg("Creating libp2p-backed network module") + logger.Global.Debug().Msg("Creating P2P module") *m = p2pModule{ cfg: bus.GetRuntimeMgr().GetConfig().P2P, logger: logger.Global.CreateLoggerForModule(modules.P2PModuleName), @@ -143,7 +143,7 @@ func (m *p2pModule) GetModuleName() string { } // Start instantiates and assigns `m.host`, unless one already exists, and -// `m.network` (which depends on `m.host` as a required config field). +// `m.router` (which depends on `m.host` as a required config field). func (m *p2pModule) Start() (err error) { m.GetBus(). GetTelemetryModule(). @@ -168,8 +168,8 @@ func (m *p2pModule) Start() (err error) { } } - if err := m.setupNetwork(); err != nil { - return fmt.Errorf("setting up network: %w", err) + if err := m.setupRouter(); err != nil { + return fmt.Errorf("setting up router: %w", err) } // Don't handle incoming streams in client debug mode. @@ -203,7 +203,7 @@ func (m *p2pModule) Broadcast(msg *anypb.Any) error { } m.logger.Info().Msg("broadcasting message to network") - return m.network.NetworkBroadcast(data) + return m.router.Broadcast(data) } func (m *p2pModule) Send(addr cryptoPocket.Address, msg *anypb.Any) error { @@ -216,7 +216,7 @@ func (m *p2pModule) Send(addr cryptoPocket.Address, msg *anypb.Any) error { return err } - return m.network.NetworkSend(data, addr) + return m.router.Send(data, addr) } // TECHDEBT(#348): Define what the node identity is throughout the codebase @@ -282,15 +282,17 @@ func (m *p2pModule) setupCurrentHeightProvider() error { return nil } -// setupNetwork instantiates the configured network implementation. -func (m *p2pModule) setupNetwork() (err error) { - m.network, err = raintree.NewRainTreeNetwork( +// setupRouter instantiates the configured router implementation. +func (m *p2pModule) setupRouter() (err error) { + m.router, err = raintree.NewRainTreeRouter( m.GetBus(), - raintree.RainTreeConfig{ - Host: m.host, + &raintree.RainTreeConfig{ Addr: m.address, - PeerstoreProvider: m.pstoreProvider, CurrentHeightProvider: m.currentHeightProvider, + Host: m.host, + Hostname: m.cfg.Hostname, + MaxMempoolCount: m.cfg.MaxMempoolCount, + PeerstoreProvider: m.pstoreProvider, }, ) return err @@ -356,10 +358,10 @@ func (m *p2pModule) handleStream(stream libp2pNetwork.Stream) { return } - if err := m.network.AddPeer(peer); err != nil { + if err := m.router.AddPeer(peer); err != nil { m.logger.Error().Err(err). Str("address", peer.GetAddress().String()). - Msg("adding remote peer to network") + Msg("adding remote peer to router") } go m.readStream(stream) @@ -414,9 +416,9 @@ func (m *p2pModule) readStream(stream libp2pNetwork.Stream) { } // handleNetworkData passes a network message to the configured -// `Network`implementation for routing. +// `Router`implementation for routing. func (m *p2pModule) handleNetworkData(data []byte) error { - appMsgData, err := m.network.HandleNetworkData(data) + appMsgData, err := m.router.HandleNetworkData(data) if err != nil { return err } diff --git a/p2p/raintree/peers_manager_test.go b/p2p/raintree/peers_manager_test.go index 931107a16..b3a3b716a 100644 --- a/p2p/raintree/peers_manager_test.go +++ b/p2p/raintree/peers_manager_test.go @@ -26,7 +26,7 @@ const ( addrAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ[" ) -type ExpectedRainTreeNetworkConfig struct { +type ExpectedRainTreeRouterConfig struct { numNodes int numExpectedLevels int } @@ -48,7 +48,7 @@ func TestRainTree_Peerstore_HandleUpdate(t *testing.T) { pubKey, err := cryptoPocket.GeneratePublicKey() require.NoError(t, err) - testCases := []ExpectedRainTreeNetworkConfig{ + testCases := []ExpectedRainTreeRouterConfig{ // 0 levels {1, 0}, // Just self // 1 level @@ -96,21 +96,21 @@ func TestRainTree_Peerstore_HandleUpdate(t *testing.T) { libp2pMockNet, err := mocknet.WithNPeers(1) require.NoError(t, err) - netCfg := RainTreeConfig{ + rtCfg := &RainTreeConfig{ Host: libp2pMockNet.Hosts()[0], Addr: pubKey.Address(), PeerstoreProvider: pstoreProviderMock, CurrentHeightProvider: currentHeightProviderMock, } - network, err := NewRainTreeNetwork(mockBus, netCfg) + router, err := NewRainTreeRouter(mockBus, rtCfg) require.NoError(t, err) - rainTree := network.(*rainTreeNetwork) + rainTree := router.(*rainTreeRouter) peersManagerStateView, actualMaxNumLevels := rainTree.peersManager.getPeersViewWithLevels() - require.Equal(t, n, network.GetPeerstore().Size()) + require.Equal(t, n, router.GetPeerstore().Size()) require.Len(t, peersManagerStateView.GetAddrs(), n) if n < 100 { // This is can be slow when `n` is very large. @@ -128,7 +128,7 @@ func BenchmarkPeerstoreUpdates(b *testing.B) { pubKey, err := cryptoPocket.GeneratePublicKey() require.NoError(b, err) - testCases := []ExpectedRainTreeNetworkConfig{ + testCases := []ExpectedRainTreeRouterConfig{ // Small {9, 2}, // Large @@ -137,7 +137,7 @@ func BenchmarkPeerstoreUpdates(b *testing.B) { // {1000000000, 19}, } - // the test will add this arbitrary number of addresses after the initial initialization (done via NewRainTreeNetwork) + // the test will add this arbitrary number of addresses after the initial initialization (done via NewRainTreeRouter) // this is to add extra subsequent work that -should- grow linearly and it's actually going to test AddressBook updates // not simply initializations. numAddressesToBeAdded := 1000 @@ -163,21 +163,21 @@ func BenchmarkPeerstoreUpdates(b *testing.B) { hostMock := mocksP2P.NewMockHost(ctrl) hostMock.EXPECT().Peerstore().Return(libp2pPStore).AnyTimes() - netCfg := RainTreeConfig{ + rtCfg := &RainTreeConfig{ Host: hostMock, Addr: pubKey.Address(), PeerstoreProvider: pstoreProviderMock, CurrentHeightProvider: currentHeightProviderMock, } - network, err := NewRainTreeNetwork(mockBus, netCfg) + router, err := NewRainTreeRouter(mockBus, rtCfg) require.NoError(b, err) - rainTree := network.(*rainTreeNetwork) + rainTree := router.(*rainTreeRouter) peersManagerStateView, actualMaxNumLevels := rainTree.peersManager.getPeersViewWithLevels() - require.Equal(b, n, network.GetPeerstore().Size()) + require.Equal(b, n, router.GetPeerstore().Size()) require.Equal(b, n, len(peersManagerStateView.GetAddrs())) require.ElementsMatchf(b, pstore.GetPeerList(), peersManagerStateView.GetPeers(), "peers don't match") require.Equal(b, testCase.numExpectedLevels, int(actualMaxNumLevels)) @@ -195,7 +195,7 @@ func BenchmarkPeerstoreUpdates(b *testing.B) { peersManagerStateView = rainTree.peersManager.GetPeersView() - require.Equal(b, n+numAddressesToBeAdded, network.GetPeerstore().Size()) + require.Equal(b, n+numAddressesToBeAdded, router.GetPeerstore().Size()) require.Equal(b, n+numAddressesToBeAdded, len(peersManagerStateView.GetAddrs())) }) } @@ -287,16 +287,16 @@ func testRainTreeMessageTargets(t *testing.T, expectedMsgProp *ExpectedRainTreeM hostMock := mocksP2P.NewMockHost(ctrl) hostMock.EXPECT().Peerstore().Return(libp2pPStore).AnyTimes() - netCfg := RainTreeConfig{ + rtCfg := &RainTreeConfig{ Host: hostMock, Addr: []byte{expectedMsgProp.orig}, PeerstoreProvider: pstoreProviderMock, CurrentHeightProvider: currentHeightProviderMock, } - network, err := NewRainTreeNetwork(busMock, netCfg) + router, err := NewRainTreeRouter(busMock, rtCfg) require.NoError(t, err) - rainTree := network.(*rainTreeNetwork) + rainTree := router.(*rainTreeRouter) rainTree.SetBus(busMock) diff --git a/p2p/raintree/peerstore_utils.go b/p2p/raintree/peerstore_utils.go index ac5df0699..e4fd52425 100644 --- a/p2p/raintree/peerstore_utils.go +++ b/p2p/raintree/peerstore_utils.go @@ -13,14 +13,14 @@ const ( floatPrecision = float64(0.0000001) ) -func (n *rainTreeNetwork) getPeerstoreSize(level uint32, height uint64) int { - peersView, maxNumLevels := n.peersManager.getPeersViewWithLevels() +func (rtr *rainTreeRouter) getPeerstoreSize(level uint32, height uint64) int { + peersView, maxNumLevels := rtr.peersManager.getPeersViewWithLevels() // if we are propagating a message from a previous height, we need to instantiate an ephemeral rainTreePeersManager (without add/remove) - if height < n.currentHeightProvider.CurrentHeight() { - peersMgr, err := newPeersManagerWithPeerstoreProvider(n.selfAddr, n.pstoreProvider, height) + if height < rtr.currentHeightProvider.CurrentHeight() { + peersMgr, err := newPeersManagerWithPeerstoreProvider(rtr.selfAddr, rtr.pstoreProvider, height) if err != nil { - n.logger.Fatal().Err(err).Msg("Error initializing rainTreeNetwork rainTreePeersManager") + rtr.logger.Fatal().Err(err).Msg("Error initializing rainTreeRouter rainTreePeersManager") } peersView, maxNumLevels = peersMgr.getPeersViewWithLevels() } @@ -30,13 +30,13 @@ func (n *rainTreeNetwork) getPeerstoreSize(level uint32, height uint64) int { } // getTargetsAtLevel returns the targets for a given level -func (n *rainTreeNetwork) getTargetsAtLevel(level uint32) []target { - height := n.currentHeightProvider.CurrentHeight() - pstoreSizeAtHeight := n.getPeerstoreSize(level, height) - firstTarget := n.getTarget(firstMsgTargetPercentage, pstoreSizeAtHeight, level) - secondTarget := n.getTarget(secondMsgTargetPercentage, pstoreSizeAtHeight, level) +func (rtr *rainTreeRouter) getTargetsAtLevel(level uint32) []target { + height := rtr.currentHeightProvider.CurrentHeight() + pstoreSizeAtHeight := rtr.getPeerstoreSize(level, height) + firstTarget := rtr.getTarget(firstMsgTargetPercentage, pstoreSizeAtHeight, level) + secondTarget := rtr.getTarget(secondMsgTargetPercentage, pstoreSizeAtHeight, level) - n.logger.Debug().Fields( + rtr.logger.Debug().Fields( map[string]any{ "firstTarget": firstTarget.serviceURL, "secondTarget": secondTarget.serviceURL, @@ -49,9 +49,9 @@ func (n *rainTreeNetwork) getTargetsAtLevel(level uint32) []target { return []target{firstTarget, secondTarget} } -func (n *rainTreeNetwork) getTarget(targetPercentage float64, pstoreSize int, level uint32) target { +func (rtr *rainTreeRouter) getTarget(targetPercentage float64, pstoreSize int, level uint32) target { i := int(targetPercentage * float64(pstoreSize)) - peersView := n.peersManager.GetPeersView() + peersView := rtr.peersManager.GetPeersView() serviceURL := peersView.GetPeers()[i].GetServiceURL() target := target{ @@ -70,12 +70,12 @@ func (n *rainTreeNetwork) getTarget(targetPercentage float64, pstoreSize int, le } addrStr := peersView.GetAddrs()[i] - if addr := n.GetPeerstore().GetPeerFromString(addrStr); addr != nil { + if addr := rtr.GetPeerstore().GetPeerFromString(addrStr); addr != nil { target.address = addr.GetAddress() return target } - n.logger.Debug().Str("address", addrStr).Msg("address not found in Peerstore") + rtr.logger.Debug().Str("address", addrStr).Msg("address not found in Peerstore") return target } diff --git a/p2p/raintree/network.go b/p2p/raintree/router.go similarity index 58% rename from p2p/raintree/network.go rename to p2p/raintree/router.go index a15662813..05a05c141 100644 --- a/p2p/raintree/network.go +++ b/p2p/raintree/router.go @@ -24,21 +24,23 @@ import ( ) var ( - _ typesP2P.Network = &rainTreeNetwork{} - _ modules.IntegratableModule = &rainTreeNetwork{} - _ rainTreeFactory = &rainTreeNetwork{} + _ typesP2P.Router = &rainTreeRouter{} + _ modules.IntegratableModule = &rainTreeRouter{} + _ rainTreeFactory = &rainTreeRouter{} ) -type rainTreeFactory = modules.FactoryWithConfig[typesP2P.Network, RainTreeConfig] +type rainTreeFactory = modules.FactoryWithConfig[typesP2P.Router, *RainTreeConfig] type RainTreeConfig struct { Addr cryptoPocket.Address - PeerstoreProvider providers.PeerstoreProvider CurrentHeightProvider providers.CurrentHeightProvider Host libp2pHost.Host + Hostname string + MaxMempoolCount uint64 + PeerstoreProvider providers.PeerstoreProvider } -type rainTreeNetwork struct { +type rainTreeRouter struct { base_modules.IntegratableModule logger *modules.Logger @@ -57,47 +59,45 @@ type rainTreeNetwork struct { nonceDeduper *mempool.GenericFIFOSet[uint64, uint64] } -func NewRainTreeNetwork(bus modules.Bus, cfg RainTreeConfig) (typesP2P.Network, error) { - return new(rainTreeNetwork).Create(bus, cfg) +func NewRainTreeRouter(bus modules.Bus, cfg *RainTreeConfig) (typesP2P.Router, error) { + return new(rainTreeRouter).Create(bus, cfg) } -func (*rainTreeNetwork) Create(bus modules.Bus, netCfg RainTreeConfig) (typesP2P.Network, error) { - networkLogger := logger.Global.CreateLoggerForModule("network") - networkLogger.Info().Msg("Initializing rainTreeNetwork") +func (*rainTreeRouter) Create(bus modules.Bus, cfg *RainTreeConfig) (typesP2P.Router, error) { + routerLogger := logger.Global.CreateLoggerForModule("router") + routerLogger.Info().Msg("Initializing rainTreeRouter") - if err := netCfg.isValid(); err != nil { + if err := cfg.isValid(); err != nil { return nil, err } - p2pCfg := bus.GetRuntimeMgr().GetConfig().P2P - - n := &rainTreeNetwork{ - host: netCfg.Host, - selfAddr: netCfg.Addr, - hostname: p2pCfg.Hostname, - nonceDeduper: mempool.NewGenericFIFOSet[uint64, uint64](int(p2pCfg.MaxMempoolCount)), - pstoreProvider: netCfg.PeerstoreProvider, - currentHeightProvider: netCfg.CurrentHeightProvider, - logger: networkLogger, + rtr := &rainTreeRouter{ + host: cfg.Host, + selfAddr: cfg.Addr, + hostname: cfg.Hostname, + nonceDeduper: mempool.NewGenericFIFOSet[uint64, uint64](int(cfg.MaxMempoolCount)), + pstoreProvider: cfg.PeerstoreProvider, + currentHeightProvider: cfg.CurrentHeightProvider, + logger: routerLogger, } - n.SetBus(bus) + rtr.SetBus(bus) - if err := n.setupDependencies(); err != nil { + if err := rtr.setupDependencies(); err != nil { return nil, err } - return typesP2P.Network(n), nil + return typesP2P.Router(rtr), nil } -// NetworkBroadcast implements the respective member of `typesP2P.Network`. -func (n *rainTreeNetwork) NetworkBroadcast(data []byte) error { - return n.networkBroadcastAtLevel(data, n.peersManager.GetMaxNumLevels(), crypto.GetNonce()) +// NetworkBroadcast implements the respective member of `typesP2P.Router`. +func (rtr *rainTreeRouter) Broadcast(data []byte) error { + return rtr.broadcastAtLevel(data, rtr.peersManager.GetMaxNumLevels(), crypto.GetNonce()) } -// networkBroadcastAtLevel recursively sends to both left and right target peers +// broadcastAtLevel recursively sends to both left and right target peers // from the starting level, demoting until level == 0. // (see: https://github.com/pokt-network/pocket-network-protocol/tree/main/p2p) -func (n *rainTreeNetwork) networkBroadcastAtLevel(data []byte, level uint32, nonce uint64) error { +func (rtr *rainTreeRouter) broadcastAtLevel(data []byte, level uint32, nonce uint64) error { // This is handled either by the cleanup layer or redundancy layer if level == 0 { return nil @@ -112,16 +112,16 @@ func (n *rainTreeNetwork) networkBroadcastAtLevel(data []byte, level uint32, non return err } - for _, target := range n.getTargetsAtLevel(level) { + for _, target := range rtr.getTargetsAtLevel(level) { if shouldSendToTarget(target) { - if err = n.networkSendInternal(msgBz, target.address); err != nil { - n.logger.Error().Err(err).Msg("sending to peer during broadcast") + if err = rtr.sendInternal(msgBz, target.address); err != nil { + rtr.logger.Error().Err(err).Msg("sending to peer during broadcast") } } } - if err = n.demote(msg); err != nil { - n.logger.Error().Err(err).Msg("demoting self during RainTree message propagation") + if err = rtr.demote(msg); err != nil { + rtr.logger.Error().Err(err).Msg("demoting self during RainTree message propagation") } return nil @@ -129,17 +129,17 @@ func (n *rainTreeNetwork) networkBroadcastAtLevel(data []byte, level uint32, non // demote broadcasts to the decremented level's targets. // (see: https://github.com/pokt-network/pocket-network-protocol/tree/main/p2p) -func (n *rainTreeNetwork) demote(rainTreeMsg *typesP2P.RainTreeMessage) error { +func (rtr *rainTreeRouter) demote(rainTreeMsg *typesP2P.RainTreeMessage) error { if rainTreeMsg.Level > 0 { - if err := n.networkBroadcastAtLevel(rainTreeMsg.Data, rainTreeMsg.Level-1, rainTreeMsg.Nonce); err != nil { + if err := rtr.broadcastAtLevel(rainTreeMsg.Data, rainTreeMsg.Level-1, rainTreeMsg.Nonce); err != nil { return err } } return nil } -// NetworkSend implements the respective member of `typesP2P.Network`. -func (n *rainTreeNetwork) NetworkSend(data []byte, address cryptoPocket.Address) error { +// NetworkSend implements the respective member of `typesP2P.Router`. +func (rtr *rainTreeRouter) Send(data []byte, address cryptoPocket.Address) error { msg := &typesP2P.RainTreeMessage{ Level: 0, // Direct send that does not need to be propagated Data: data, @@ -151,32 +151,32 @@ func (n *rainTreeNetwork) NetworkSend(data []byte, address cryptoPocket.Address) return err } - return n.networkSendInternal(bz, address) + return rtr.sendInternal(bz, address) } -// networkSendInternal sends `data` to the peer at pokt `address` if not self. -func (n *rainTreeNetwork) networkSendInternal(data []byte, address cryptoPocket.Address) error { +// sendInternal sends `data` to the peer at pokt `address` if not self. +func (rtr *rainTreeRouter) sendInternal(data []byte, address cryptoPocket.Address) error { // TODO: How should we handle this? - if n.selfAddr.Equals(address) { - n.logger.Debug().Str("pokt_addr", address.String()).Msg("attempted to send to self") + if rtr.selfAddr.Equals(address) { + rtr.logger.Debug().Str("pokt_addr", address.String()).Msg("attempted to send to self") return nil } - peer := n.peersManager.GetPeerstore().GetPeer(address) + peer := rtr.peersManager.GetPeerstore().GetPeer(address) if peer == nil { return fmt.Errorf("no known peer with pokt address %s", address) } // debug logging - utils.LogOutgoingMsg(n.logger, n.hostname, peer) + utils.LogOutgoingMsg(rtr.logger, rtr.hostname, peer) - if err := utils.Libp2pSendToPeer(n.host, data, peer); err != nil { - n.logger.Debug().Err(err).Msg("from libp2pSendInternal") + if err := utils.Libp2pSendToPeer(rtr.host, data, peer); err != nil { + rtr.logger.Debug().Err(err).Msg("from libp2pSendInternal") return err } // A bus is not available In client debug mode - bus := n.GetBus() + bus := rtr.GetBus() if bus == nil { return nil } @@ -193,12 +193,12 @@ func (n *rainTreeNetwork) networkSendInternal(data []byte, address cryptoPocket. return nil } -// HandleNetworkData implements the respective member of `typesP2P.Network`. -func (n *rainTreeNetwork) HandleNetworkData(data []byte) ([]byte, error) { - blockHeightInt := n.GetBus().GetConsensusModule().CurrentHeight() +// HandleNetworkData implements the respective member of `typesP2P.Router`. +func (rtr *rainTreeRouter) HandleNetworkData(data []byte) ([]byte, error) { + blockHeightInt := rtr.GetBus().GetConsensusModule().CurrentHeight() blockHeight := fmt.Sprintf("%d", blockHeightInt) - n.GetBus(). + rtr.GetBus(). GetTelemetryModule(). GetEventMetricsAgent(). EmitEvent( @@ -214,13 +214,13 @@ func (n *rainTreeNetwork) HandleNetworkData(data []byte) ([]byte, error) { networkMessage := messaging.PocketEnvelope{} if err := proto.Unmarshal(rainTreeMsg.Data, &networkMessage); err != nil { - n.logger.Error().Err(err).Msg("Error decoding network message") + rtr.logger.Error().Err(err).Msg("Error decoding network message") return nil, err } // Continue RainTree propagation if rainTreeMsg.Level > 0 { - if err := n.networkBroadcastAtLevel(rainTreeMsg.Data, rainTreeMsg.Level-1, rainTreeMsg.Nonce); err != nil { + if err := rtr.broadcastAtLevel(rainTreeMsg.Data, rainTreeMsg.Level-1, rainTreeMsg.Nonce); err != nil { return nil, err } } @@ -228,9 +228,9 @@ func (n *rainTreeNetwork) HandleNetworkData(data []byte) ([]byte, error) { // Avoids this node from processing a messages / transactions is has already processed at the // application layer. The logic above makes sure it is only propagated and returns. // DISCUSS(#278): Add more tests to verify this is sufficient for deduping purposes. - if contains := n.nonceDeduper.Contains(rainTreeMsg.Nonce); contains { + if contains := rtr.nonceDeduper.Contains(rainTreeMsg.Nonce); contains { log.Printf("RainTree message with nonce %d already processed, skipping\n", rainTreeMsg.Nonce) - n.GetBus(). + rtr.GetBus(). GetTelemetryModule(). GetEventMetricsAgent(). EmitEvent( @@ -244,7 +244,7 @@ func (n *rainTreeNetwork) HandleNetworkData(data []byte) ([]byte, error) { } // Add the nonce to the deduper - if err := n.nonceDeduper.Push(rainTreeMsg.Nonce); err != nil { + if err := rtr.nonceDeduper.Push(rainTreeMsg.Nonce); err != nil { return nil, err } @@ -252,24 +252,24 @@ func (n *rainTreeNetwork) HandleNetworkData(data []byte) ([]byte, error) { return rainTreeMsg.Data, nil } -// GetPeerstore implements the respective member of `typesP2P.Network`. -func (n *rainTreeNetwork) GetPeerstore() typesP2P.Peerstore { - return n.peersManager.GetPeerstore() +// GetPeerstore implements the respective member of `typesP2P.Router`. +func (rtr *rainTreeRouter) GetPeerstore() typesP2P.Peerstore { + return rtr.peersManager.GetPeerstore() } -// AddPeer implements the respective member of `typesP2P.Network`. -func (n *rainTreeNetwork) AddPeer(peer typesP2P.Peer) error { +// AddPeer implements the respective member of `typesP2P.Router`. +func (rtr *rainTreeRouter) AddPeer(peer typesP2P.Peer) error { // Noop if peer with the same pokt address exists in the peerstore. // TECHDEBT: add method(s) to update peers. - if p := n.peersManager.GetPeerstore().GetPeer(peer.GetAddress()); p != nil { + if p := rtr.peersManager.GetPeerstore().GetPeer(peer.GetAddress()); p != nil { return nil } - if err := utils.AddPeerToLibp2pHost(n.host, peer); err != nil { + if err := utils.AddPeerToLibp2pHost(rtr.host, peer); err != nil { return err } - n.peersManager.HandleEvent( + rtr.peersManager.HandleEvent( typesP2P.PeerManagerEvent{ EventType: typesP2P.AddPeerEventType, Peer: peer, @@ -278,8 +278,8 @@ func (n *rainTreeNetwork) AddPeer(peer typesP2P.Peer) error { return nil } -func (n *rainTreeNetwork) RemovePeer(peer typesP2P.Peer) error { - n.peersManager.HandleEvent( +func (rtr *rainTreeRouter) RemovePeer(peer typesP2P.Peer) error { + rtr.peersManager.HandleEvent( typesP2P.PeerManagerEvent{ EventType: typesP2P.RemovePeerEventType, Peer: peer, @@ -290,8 +290,8 @@ func (n *rainTreeNetwork) RemovePeer(peer typesP2P.Peer) error { // Size returns the number of peers the network is aware of and would attempt to // broadcast to. -func (n *rainTreeNetwork) Size() int { - return n.peersManager.GetPeerstore().Size() +func (rtr *rainTreeRouter) Size() int { + return rtr.peersManager.GetPeerstore().Size() } // shouldSendToTarget returns false if target is self. @@ -299,24 +299,24 @@ func shouldSendToTarget(target target) bool { return !target.isSelf } -func (n *rainTreeNetwork) setupDependencies() error { - pstore, err := n.pstoreProvider.GetStakedPeerstoreAtHeight(n.currentHeightProvider.CurrentHeight()) +func (rtr *rainTreeRouter) setupDependencies() error { + pstore, err := rtr.pstoreProvider.GetStakedPeerstoreAtHeight(rtr.currentHeightProvider.CurrentHeight()) if err != nil { return err } - if err := n.setupPeerManager(pstore); err != nil { + if err := rtr.setupPeerManager(pstore); err != nil { return err } - if err := utils.PopulateLibp2pHost(n.host, pstore); err != nil { + if err := utils.PopulateLibp2pHost(rtr.host, pstore); err != nil { return err } return nil } -func (n *rainTreeNetwork) setupPeerManager(pstore typesP2P.Peerstore) (err error) { - n.peersManager, err = newPeersManager(n.selfAddr, pstore, true) +func (rtr *rainTreeRouter) setupPeerManager(pstore typesP2P.Peerstore) (err error) { + rtr.peersManager, err = newPeersManager(rtr.selfAddr, pstore, true) return err } diff --git a/p2p/raintree/network_test.go b/p2p/raintree/router_test.go similarity index 88% rename from p2p/raintree/network_test.go rename to p2p/raintree/router_test.go index 5fe834cc3..9b8281d12 100644 --- a/p2p/raintree/network_test.go +++ b/p2p/raintree/router_test.go @@ -20,7 +20,7 @@ import ( // TECHDEBT(#609): move & de-dup. var testLocalServiceURL = fmt.Sprintf("127.0.0.1:%d", defaults.DefaultP2PPort) -func TestRainTreeNetwork_AddPeer(t *testing.T) { +func TestRainTreeRouter_AddPeer(t *testing.T) { ctrl := gomock.NewController(t) // Start with a peerstore containing self. @@ -52,17 +52,17 @@ func TestRainTreeNetwork_AddPeer(t *testing.T) { peerstoreProviderMock := mockPeerstoreProvider(ctrl, pstore) currentHeightProviderMock := mockCurrentHeightProvider(ctrl, 0) - netCfg := RainTreeConfig{ + rtCfg := &RainTreeConfig{ Host: host, Addr: selfAddr, PeerstoreProvider: peerstoreProviderMock, CurrentHeightProvider: currentHeightProviderMock, } - network, err := NewRainTreeNetwork(busMock, netCfg) + router, err := NewRainTreeRouter(busMock, rtCfg) require.NoError(t, err) - rainTreeNet := network.(*rainTreeNetwork) + rtRouter := router.(*rainTreeRouter) privKey, err := cryptoPocket.GeneratePrivateKey() require.NoError(t, err) @@ -73,14 +73,14 @@ func TestRainTreeNetwork_AddPeer(t *testing.T) { } // Add peerToAdd. - err = rainTreeNet.AddPeer(peerToAdd) + err = rtRouter.AddPeer(peerToAdd) require.NoError(t, err) expectedPStoreSize++ - peerAddrs, peers := getPeersViewParts(rainTreeNet.peersManager) + peerAddrs, peers := getPeersViewParts(rtRouter.peersManager) // Ensure size / lengths are consistent. - require.Equal(t, expectedPStoreSize, network.GetPeerstore().Size()) + require.Equal(t, expectedPStoreSize, router.GetPeerstore().Size()) require.Equal(t, expectedPStoreSize, len(peerAddrs)) require.Equal(t, expectedPStoreSize, len(peers)) @@ -90,11 +90,11 @@ func TestRainTreeNetwork_AddPeer(t *testing.T) { require.ElementsMatch(t, []string{selfAddr.String(), peerToAdd.GetAddress().String()}, peerAddrs, "addresses do not match") require.ElementsMatch(t, []*typesP2P.NetworkPeer{selfPeer, peerToAdd}, peers, "peers do not match") - require.Equal(t, selfPeer, network.GetPeerstore().GetPeer(selfAddr), "Peerstore does not contain self") - require.Equal(t, peerToAdd, network.GetPeerstore().GetPeer(peerToAdd.GetAddress()), "Peerstore does not contain added peer") + require.Equal(t, selfPeer, router.GetPeerstore().GetPeer(selfAddr), "Peerstore does not contain self") + require.Equal(t, peerToAdd, router.GetPeerstore().GetPeer(peerToAdd.GetAddress()), "Peerstore does not contain added peer") } -func TestRainTreeNetwork_RemovePeer(t *testing.T) { +func TestRainTreeRouter_RemovePeer(t *testing.T) { ctrl := gomock.NewController(t) // Start with a peerstore which contains self and some number of peers: the @@ -114,16 +114,16 @@ func TestRainTreeNetwork_RemovePeer(t *testing.T) { busMock := mockBus(ctrl) peerstoreProviderMock := mockPeerstoreProvider(ctrl, pstore) currentHeightProviderMock := mockCurrentHeightProvider(ctrl, 0) - netCfg := RainTreeConfig{ + rtCfg := &RainTreeConfig{ Host: host, Addr: selfAddr, PeerstoreProvider: peerstoreProviderMock, CurrentHeightProvider: currentHeightProviderMock, } - network, err := NewRainTreeNetwork(busMock, netCfg) + router, err := NewRainTreeRouter(busMock, rtCfg) require.NoError(t, err) - rainTree := network.(*rainTreeNetwork) + rainTree := router.(*rainTreeRouter) // Ensure expected starting size / lengths are consistent. peerAddrs, peers := getPeersViewParts(rainTree.peersManager) diff --git a/p2p/raintree/target.go b/p2p/raintree/target.go index f088b09d8..c3c3ba86a 100644 --- a/p2p/raintree/target.go +++ b/p2p/raintree/target.go @@ -18,7 +18,7 @@ type target struct { isSelf bool } -func (t target) DebugString(n *rainTreeNetwork) string { +func (t target) DebugString(n *rainTreeRouter) string { s := strings.Builder{} s.WriteString("[") peersManagerStateView := n.peersManager.GetPeersView() diff --git a/p2p/types/network.go b/p2p/types/router.go similarity index 87% rename from p2p/types/network.go rename to p2p/types/router.go index e3f94fc04..14c3fd2d8 100644 --- a/p2p/types/network.go +++ b/p2p/types/router.go @@ -9,11 +9,11 @@ import ( // TECHDEBT(olshansky): When we delete `stdnetwork` and only go with `raintree`, this interface // can be simplified greatly. -type Network interface { +type Router interface { modules.IntegratableModule - NetworkBroadcast(data []byte) error - NetworkSend(data []byte, address cryptoPocket.Address) error + Broadcast(data []byte) error + Send(data []byte, address cryptoPocket.Address) error // Address book helpers // TECHDEBT: simplify - remove `GetPeerstore` diff --git a/shared/modules/factory.go b/shared/modules/factory.go index 7027efbc4..c4c2a5f19 100644 --- a/shared/modules/factory.go +++ b/shared/modules/factory.go @@ -6,7 +6,7 @@ type ModuleFactoryWithOptions FactoryWithOptions[Module, ModuleOption] // FactoryWithConfig implements a `#Create()` factory method which takes a // required "config" argument of type K and returns a value of type T and an error. -// TECHDEBT: apply enforcement across applicable "sub-modules" (see: `p2p/raintree/network.go`: `raintTreeFactory`) +// TECHDEBT: apply enforcement across applicable "sub-modules" (see: `p2p/raintree/router.go`: `raintTreeFactory`) type FactoryWithConfig[T interface{}, K interface{}] interface { Create(bus Bus, cfg K) (T, error) } From 66a8940d00204475761d2bcaa7903218b7cb6b0f Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 28 Apr 2023 13:30:58 -0700 Subject: [PATCH 42/46] MInor NIT comments --- utility/doc/PROTOCOL_SESSION.md | 2 -- utility/session.go | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/utility/doc/PROTOCOL_SESSION.md b/utility/doc/PROTOCOL_SESSION.md index 92f3c28ee..b9d2205e3 100644 --- a/utility/doc/PROTOCOL_SESSION.md +++ b/utility/doc/PROTOCOL_SESSION.md @@ -30,8 +30,6 @@ See [session.go](../session.go) and [session_test.go](../session_test.go) for th ```mermaid sequenceDiagram - autonumber - %% The `Querier` is anyone (app or not) that asks to retrieve session metadata actor Q AS Querier diff --git a/utility/session.go b/utility/session.go index b755484a9..ae96fba7b 100644 --- a/utility/session.go +++ b/utility/session.go @@ -17,6 +17,7 @@ import ( ) // GetSession implements of the exposed `UtilityModule.GetSession` function +// TECHDEBT(#519): Add custom error types depending on the type of issue that occurred and assert on them in the unit tests. func (m *utilityModule) GetSession(appAddr string, height int64, relayChain, geoZone string) (*coreTypes.Session, error) { persistenceModule := m.GetBus().GetPersistenceModule() readCtx, err := persistenceModule.NewReadContext(height) From e26608aabfb21553939e3bf2ed57fc5023b52755 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 28 Apr 2023 13:37:47 -0700 Subject: [PATCH 43/46] utility/module_test.go -> utility/main_test.go --- utility/{module_test.go => main_test.go} | 1 + 1 file changed, 1 insertion(+) rename utility/{module_test.go => main_test.go} (97%) diff --git a/utility/module_test.go b/utility/main_test.go similarity index 97% rename from utility/module_test.go rename to utility/main_test.go index 946bea70a..bc2c4f389 100644 --- a/utility/module_test.go +++ b/utility/main_test.go @@ -20,6 +20,7 @@ var ( dbURL string ) +// NB: `TestMain` serves all tests in the immediate `utility` package and not its children func TestMain(m *testing.M) { pool, resource, url := test_artifacts.SetupPostgresDocker() dbURL = url From e4ac25e04f85295a0b909862218c989120cda1b3 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 28 Apr 2023 14:11:48 -0700 Subject: [PATCH 44/46] Typo --- shared/core/types/proto/actor.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/core/types/proto/actor.proto b/shared/core/types/proto/actor.proto index ef49dbf5c..28378eb45 100644 --- a/shared/core/types/proto/actor.proto +++ b/shared/core/types/proto/actor.proto @@ -32,5 +32,5 @@ message Actor { string staked_amount = 6; int64 paused_height = 7; // TECHDEBT: Revisit this parameter and see if it can be removed for simplification purposes. int64 unstaking_height = 8; // TECHDEBT: Revisit this parameter and see if it can be removed for simplification purposes. - string output = 9; // TECHDEBT: Revisit custodial / non-custodial flows (e.g. what if we want multiple outsputs for business purposes) + string output = 9; // TECHDEBT: Revisit custodial / non-custodial flows (e.g. what if we want multiple outputs for business purposes) } From d06787e78227b872e61af15b05f8414b4d4be31a Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 28 Apr 2023 14:22:57 -0700 Subject: [PATCH 45/46] Update all the changelogs --- logger/docs/CHANGELOG.md | 5 +++++ p2p/CHANGELOG.md | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/logger/docs/CHANGELOG.md b/logger/docs/CHANGELOG.md index 9e2fcfb53..b1cd46435 100644 --- a/logger/docs/CHANGELOG.md +++ b/logger/docs/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.10] - 2023-04-28 + +- Extracted a couple of shared helpers (e.g. `stringLogArrayMarshaler`, `MarshalZerologArray`) +- Updated documentation on how to build a context specific logger + ## [0.0.0.9] - 2023-02-28 - Removed the unused `bus` from the `logger` struct diff --git a/p2p/CHANGELOG.md b/p2p/CHANGELOG.md index e0da8a6cd..9f4a72ff1 100644 --- a/p2p/CHANGELOG.md +++ b/p2p/CHANGELOG.md @@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.0.0.47] - 2023-04-27 +## [0.0.0.49] - 2023-04-28 + +- Extracted a couple of shared helpers (e.g. `stringLogArrayMarshaler`, `MarshalZerologArray`) + +## [0.0.0.48] - 2023-04-27 - Renamed `Network` interface to `Router` - Shortened `Router#NetworkBroadcast` to `#Broadcast` @@ -26,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplified p2p module/router config handoff - Updated debug logging -## [0.0.0.46] - 2023-04-27 +## [0.0.0.47] - 2023-04-27 - Removed unneeded `stdnetwork` package - Removed unneeded `use_rain_tree` P2P config field From e2864b82e0b52b527cf11e0a05062f4779fef9fe Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 28 Apr 2023 15:46:54 -0700 Subject: [PATCH 46/46] Add t.Helper in session_test.go --- utility/session_test.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/utility/session_test.go b/utility/session_test.go index 95d827b0e..16ba35877 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -511,8 +511,10 @@ func TestSession_GetSession_SessionHeightAndNumber_ModifiedBlocksPerSession(t *t } func assertActorsDifference(t *testing.T, actors1, actors2 []*coreTypes.Actor, maxSimilarityThreshold float64) { - slice1 := actorsToAddrs(actors1) - slice2 := actorsToAddrs(actors2) + t.Helper() + + slice1 := actorsToAddrs(t, actors1) + slice2 := actorsToAddrs(t, actors2) var commonCount float64 for _, s1 := range slice1 { for _, s2 := range slice2 { @@ -526,7 +528,9 @@ func assertActorsDifference(t *testing.T, actors1, actors2 []*coreTypes.Actor, m assert.LessOrEqual(t, commonCount, maxCommonCount, "Slices have more similarity than expected: %v vs max %v", slice1, slice2) } -func actorsToAddrs(actors []*coreTypes.Actor) []string { +func actorsToAddrs(t *testing.T, actors []*coreTypes.Actor) []string { + t.Helper() + addresses := make([]string, len(actors)) for i, actor := range actors { addresses[i] = actor.Address