diff --git a/Makefile b/Makefile index df2e50780..7e751bc5b 100644 --- a/Makefile +++ b/Makefile @@ -436,8 +436,10 @@ 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. +# 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 # 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. @@ -453,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 "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. : ; @@ -470,6 +472,13 @@ TODO_KEYWORDS = -e "TODO" -e "TECHDEBT" -e "IMPROVE" -e "DISCUSS" -e "INCOMPLETE 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/build/config/genesis.json b/build/config/genesis.json index c979bb34f..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, @@ -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/build/docs/CHANGELOG.md b/build/docs/CHANGELOG.md index 5e4ddf428..b83f4bc10 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.39] - 2023-04-28 + +- Added a `fisherman_per_session` governance parameter +- Updated the default `blocks_per_session` from `4` to `1` + ## [0.0.0.38] - 2023-04-28 - Removed unused `tmpDir` in `debug_keybase` package diff --git a/build/localnet/manifests/configs.yaml b/build/localnet/manifests/configs.yaml index 0b781ea88..4c50ecf25 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, @@ -4142,6 +4142,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, @@ -4196,6 +4197,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/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/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. 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/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 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/persistence/actor.go b/persistence/actor.go index 092314797..ed9079b1f 100644 --- a/persistence/actor.go +++ b/persistence/actor.go @@ -1,10 +1,29 @@ package persistence import ( + "fmt" + "github.com/pokt-network/pocket/persistence/types" coreTypes "github.com/pokt-network/pocket/shared/core/types" ) +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. @@ -113,7 +132,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: 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..2c9ec570a 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.50] - 2023-04-28 + +- 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/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..ddc2e82b1 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"} @@ -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 @@ -360,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/persistence/test/state_test.go b/persistence/test/state_test.go index 41247f2af..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{ - "3a9a33c4d6b106f656c859296cd5ac16b608980d1d921f6de77051a707f48cb5", - "ec3d62106f1ca61dfe9c1d80a16c4ee3923550db5175389777bd1ecd9d50136e", - "e440914fba03bbb5ff4fbb73f760e4e75c3635263bbf7c15ca649870ee865222", + "4b5da068a4792c30a73c36d00f01af12262e9d753992f2d56d4ca64f3f2ac894", + "0565768b758981f511a71131a63710b9e43fa51095de658a1454f3b8b7895a15", + "f0796a7072c402e22abd0d2ef5c5a199aa5b6cb208824af24b58c4c777fc3284", } stakeAmount := initialStakeAmount diff --git a/persistence/types/gov_test.go b/persistence/types/gov_test.go index c8d8f4919..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)," + @@ -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/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/docs/CHANGELOG.md b/runtime/docs/CHANGELOG.md index 073f6a66b..a8395fd75 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.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 diff --git a/runtime/genesis/proto/genesis.proto b/runtime/genesis/proto/genesis.proto index 8c9e32a53..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 { @@ -61,186 +63,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 = 56; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string blocks_per_session_owner = 55; + string app_minimum_stake_owner = 57; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_minimum_stake_owner = 56; + string app_max_chains_owner = 58; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_max_chains_owner = 57; + string app_session_tokens_multiplier_owner = 59; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_session_tokens_multiplier_owner = 58; + string app_unstaking_blocks_owner = 60; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_unstaking_blocks_owner = 59; + string app_minimum_pause_blocks_owner = 61; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_minimum_pause_blocks_owner = 60; + string app_max_paused_blocks_owner = 62; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string app_max_paused_blocks_owner = 61; + string servicer_minimum_stake_owner = 63; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_minimum_stake_owner = 62; + string servicer_max_chains_owner = 64; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_max_chains_owner = 63; + string servicer_unstaking_blocks_owner = 65; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_unstaking_blocks_owner = 64; + string servicer_minimum_pause_blocks_owner = 66; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_minimum_pause_blocks_owner = 65; + string servicer_max_paused_blocks_owner = 67; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicer_max_paused_blocks_owner = 66; + string servicers_per_session_owner = 68; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string servicers_per_session_owner = 67; + string fisherman_minimum_stake_owner = 69; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_minimum_stake_owner = 68; + string fisherman_max_chains_owner = 70; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_max_chains_owner = 69; + string fisherman_unstaking_blocks_owner = 71; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_unstaking_blocks_owner = 70; + string fisherman_minimum_pause_blocks_owner = 72; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_minimum_pause_blocks_owner = 71; + string fisherman_max_paused_blocks_owner = 73; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string fisherman_max_paused_blocks_owner = 72; + string fisherman_per_session_owner = 74; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_minimum_stake_owner = 73; + string validator_minimum_stake_owner = 75; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_unstaking_blocks_owner = 74; + string validator_unstaking_blocks_owner = 76; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_minimum_pause_blocks_owner = 75; + string validator_minimum_pause_blocks_owner = 77; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_max_paused_blocks_owner = 76; + string validator_max_paused_blocks_owner = 78; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_maximum_missed_blocks_owner = 77; + string validator_maximum_missed_blocks_owner = 79; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string validator_max_evidence_age_in_blocks_owner = 78; + string validator_max_evidence_age_in_blocks_owner = 80; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string proposer_percentage_of_fees_owner = 79; + string proposer_percentage_of_fees_owner = 81; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string missed_blocks_burn_percentage_owner = 80; + string missed_blocks_burn_percentage_owner = 82; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string double_sign_burn_percentage_owner = 81; + string double_sign_burn_percentage_owner = 83; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_double_sign_fee_owner = 82; + string message_double_sign_fee_owner = 84; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_send_fee_owner = 83; + string message_send_fee_owner = 85; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_stake_fisherman_fee_owner = 84; + string message_stake_fisherman_fee_owner = 86; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_edit_stake_fisherman_fee_owner = 85; + string message_edit_stake_fisherman_fee_owner = 87; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unstake_fisherman_fee_owner = 86; + string message_unstake_fisherman_fee_owner = 88; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_pause_fisherman_fee_owner = 87; + string message_pause_fisherman_fee_owner = 89; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unpause_fisherman_fee_owner = 88; + string message_unpause_fisherman_fee_owner = 90; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_fisherman_pause_servicer_fee_owner = 89; + string message_fisherman_pause_servicer_fee_owner = 91; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_test_score_fee_owner = 90; + string message_test_score_fee_owner = 92; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_prove_test_score_fee_owner = 91; + string message_prove_test_score_fee_owner = 93; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_stake_app_fee_owner = 92; + string message_stake_app_fee_owner = 94; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_edit_stake_app_fee_owner = 93; + string message_edit_stake_app_fee_owner = 95; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unstake_app_fee_owner = 94; + string message_unstake_app_fee_owner = 96; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_pause_app_fee_owner = 95; + string message_pause_app_fee_owner = 97; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unpause_app_fee_owner = 96; + string message_unpause_app_fee_owner = 98; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_stake_validator_fee_owner = 97; + string message_stake_validator_fee_owner = 99; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_edit_stake_validator_fee_owner = 98; + string message_edit_stake_validator_fee_owner = 100; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unstake_validator_fee_owner = 99; + string message_unstake_validator_fee_owner = 101; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_pause_validator_fee_owner = 100; + string message_pause_validator_fee_owner = 102; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unpause_validator_fee_owner = 101; + string message_unpause_validator_fee_owner = 103; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_stake_servicer_fee_owner = 102; + string message_stake_servicer_fee_owner = 104; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_edit_stake_servicer_fee_owner = 103; + string message_edit_stake_servicer_fee_owner = 105; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unstake_servicer_fee_owner = 104; + string message_unstake_servicer_fee_owner = 106; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_pause_servicer_fee_owner = 105; + string message_pause_servicer_fee_owner = 107; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_unpause_servicer_fee_owner = 106; + string message_unpause_servicer_fee_owner = 108; //@gotags: pokt:"val_type=STRING,owner=acl_owner" - string message_change_parameter_fee_owner = 107; + string message_change_parameter_fee_owner = 109; } diff --git a/runtime/test_artifacts/defaults.go b/runtime/test_artifacts/defaults.go index 64258dd04..c9c8f3074 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 = "" @@ -13,9 +18,124 @@ 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) + DefaultParamsOwner, _ = crypto.NewPrivateKey("ff538589deb7f28bbce1ba68b37d2efc0eaa03204b36513cf88422a875559e38d6cbe0430ddd85a5e48e0c99ef3dea47bf0d1a83c6e6ad1640f72201dc8a0120") ) + +func DefaultParams() *genesis.Params { + return &genesis.Params{ + BlocksPerSession: 1, + 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 f396e5db9..e83809255 100644 --- a/runtime/test_artifacts/generator.go +++ b/runtime/test_artifacts/generator.go @@ -6,108 +6,175 @@ 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" "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" ) -// 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) - vals, validatorPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_VAL, numValidators) - servicers, snPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_SERVICER, numServicers) - fish, fishPrivateKeys := NewActors(coreTypes.ActorType_ACTOR_TYPE_FISH, numFisherman) +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, +) { + 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) 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 - Applications: apps, - Validators: vals, + Accounts: allActorAccounts, + Applications: applications, + Validators: validators, Servicers: servicers, - Fishermen: fish, + Fishermen: fishermen, Params: DefaultParams(), } - // TODO: Generalize this to all actors and not just validators + 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 { cfgs = append(cfgs, configs.NewDefaultConfig( configs.WithPK(pk), - configs.WithNodeSchema("node"+strconv.Itoa(i+1)), + configs.WithNodeSchema(getPostgresSchema(i+1)), )) } - return + 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) } -// 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 +} + +//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() - 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, 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(). + 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++ { 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 + return actors, privateKeys } -func getServiceURL(n int) string { - return fmt.Sprintf(ServiceURLFormat, n) -} - -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, @@ -116,6 +183,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 1f836bbbb..000000000 --- a/runtime/test_artifacts/gov.go +++ /dev/null @@ -1,126 +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, - 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(), - 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/CHANGELOG.md b/shared/CHANGELOG.md index a59b8cda9..c5f7ce1d1 100644 --- a/shared/CHANGELOG.md +++ b/shared/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.0.55] - 2023-04-28 + +- 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.54] - 2023-04-28 - Made `cluster-manager` aware of kubernetes namespace in which it is running @@ -17,7 +23,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 @@ -27,9 +33,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/shared/core/types/proto/actor.proto b/shared/core/types/proto/actor.proto index e6d0bbdee..28378eb45 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 outputs for business purposes) } diff --git a/shared/core/types/proto/session.proto b/shared/core/types/proto/session.proto new file mode 100644 index 000000000..31d0515a6 --- /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"; + +// 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; // 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/persistence_module.go b/shared/modules/persistence_module.go index a64fdddf5..a05b6c872 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(actorType coreTypes.ActorType, 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 c5c0b5d26..03d82ded5 100644 --- a/shared/modules/utility_module.go +++ b/shared/modules/utility_module.go @@ -3,6 +3,7 @@ package modules //go:generate mockgen -destination=./mocks/utility_module_mock.go github.com/pokt-network/pocket/shared/modules UtilityModule,UnstakingActor,UtilityUnitOfWork,LeaderUtilityUnitOfWork,ReplicaUtilityUnitOfWork import ( + coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/mempool" "google.golang.org/protobuf/types/known/anypb" ) @@ -12,7 +13,6 @@ const ( ) // TECHDEBT: Replace []byte with semantic type (addresses, transactions, etc...) - type UtilityModule interface { Module @@ -29,6 +29,11 @@ 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 + + // 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) } // TECHDEBT: Remove this interface from `shared/modules` and use the `Actor` protobuf type instead diff --git a/utility/doc/CHANGELOG.md b/utility/doc/CHANGELOG.md index 4db81a6ff..5b94f6ecf 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-28 + +- 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/doc/PROTOCOL_SESSION.md b/utility/doc/PROTOCOL_SESSION.md index cca6cd349..b9d2205e3 100644 --- a/utility/doc/PROTOCOL_SESSION.md +++ b/utility/doc/PROTOCOL_SESSION.md @@ -1,81 +1,52 @@ -## 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 simplification 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 -``` + %% The `Querier` is anyone (app or not) that asks to retrieve session metadata + actor Q AS Querier -### Pseudorandom Selection + participant S AS Session Hydrator + participant WS AS WorldState -```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] + 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/main_test.go b/utility/main_test.go new file mode 100644 index 000000000..bc2c4f389 --- /dev/null +++ b/utility/main_test.go @@ -0,0 +1,125 @@ +package utility + +import ( + "log" + "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" + "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" +) + +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 + + exitCode := m.Run() + test_artifacts.CleanupPostgresDocker(m, pool, resource) + os.Exit(exitCode) +} + +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) +} + +// Prepares a runtime environment for testing along with a genesis state, a persistence module and a utility module +func prepareEnvironment( + t *testing.T, + numValidators, // nolint:unparam // we are not currently modifying parameter but want to keep it modifiable in the future + 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, genesisOpts...) + bus, err := runtime.CreateBus(runtimeCfg) + require.NoError(t, err) + + testPersistenceMod := newTestPersistenceModule(bus) + err = testPersistenceMod.Start() + require.NoError(t, err) + + testUtilityMod := newTestUtilityModule(bus) + err = testUtilityMod.Start() + require.NoError(t, err) + + // Reset database to genesis before every test + err = testPersistenceMod.HandleDebugMessage(&messaging.DebugMessage{ + Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, + Message: nil, + }) + require.NoError(t, err) + + t.Cleanup(func() { + teardownDeterministicKeygen() + err := testPersistenceMod.Stop() + require.NoError(t, err) + err = testUtilityMod.Stop() + require.NoError(t, err) + }) + + return runtimeCfg, testUtilityMod, testPersistenceMod +} + +// REFACTOR: This should be in a shared testing package +func newTestRuntimeConfig( + numValidators, + numServicers, + numApplications, + numFisherman int, + genesisOpts ...test_artifacts.GenesisOption, +) *runtime.Manager { + cfg := &configs.Config{ + Utility: &configs.UtilityConfig{ + MaxMempoolTransactionBytes: 1000000, + MaxMempoolTransactions: 1000, + }, + Persistence: &configs.PersistenceConfig{ + PostgresUrl: dbURL, + NodeSchema: "test_schema", + BlockStorePath: "", // in memory + TxIndexerPath: "", // in memory + TreesStoreDir: "", // in memory + MaxConnsCount: 50, + MinConnsCount: 1, + MaxConnLifetime: "5m", + MaxConnIdleTime: "1m", + HealthCheckPeriod: "30s", + }, + } + genesisState, _ := test_artifacts.NewGenesisState( + numValidators, + numServicers, + numApplications, + numFisherman, + genesisOpts..., + ) + runtimeCfg := runtime.NewManager(cfg, genesisState) + return runtimeCfg +} diff --git a/utility/session.go b/utility/session.go index 1260248d1..ae96fba7b 100644 --- a/utility/session.go +++ b/utility/session.go @@ -1,157 +1,273 @@ 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" + "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" ) -// 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 -} +// 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) + if err != nil { + return nil, err + } + defer readCtx.Release() -var _ Session = &session{} + session := &coreTypes.Session{ + RelayChain: relayChain, + GeoZone: geoZone, + } -type session struct { - sessionId []byte - height int64 - relayChain RelayChain - geoZone GeoZone - application *coreTypes.Actor - servicers []*coreTypes.Actor - fishermen []*coreTypes.Actor -} + sessionHydrator := &sessionHydrator{ + logger: m.logger.With().Str("source", "sessionHydrator").Logger(), + session: session, + blockHeight: height, + readCtx: readCtx, + } -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, - } - - // TODO: make these configurable or based on governance params - numServicers := 1 - numFisherman := 1 - - var err types.Error - if s.servicers, err = s.selectSessionServicers(numServicers); err != nil { - return nil, err + if err := sessionHydrator.hydrateSessionMetadata(); err != nil { + return nil, fmt.Errorf("failed to hydrate session metadata: %w", err) } - if s.fishermen, err = s.selectSessionFishermen(numFisherman); err != nil { - return nil, err + + if err := sessionHydrator.hydrateSessionApplication(appAddr); err != nil { + return nil, fmt.Errorf("failed to hydrate session application: %w", err) } - if s.sessionId, err = s.getSessionId(); err != nil { - return nil, err + + if err := sessionHydrator.validateApplicationSession(); err != nil { + return nil, fmt.Errorf("failed to validate application session: %w", err) } - return s, nil -} -func (s *session) GetSessionID() []byte { - return s.sessionId -} + if err := sessionHydrator.hydrateSessionID(); err != nil { + return nil, fmt.Errorf("failed to hydrate session ID: %w", err) + } -func (s *session) GetSessionHeight() int64 { - return s.height -} + if err := sessionHydrator.hydrateSessionServicers(); err != nil { + return nil, fmt.Errorf("failed to hydrate session servicers: %w", err) + } + + if err := sessionHydrator.hydrateSessionFishermen(); err != nil { + return nil, fmt.Errorf("failed to hydrate session fishermen: %w", err) + } -func (s *session) GetRelayChain() RelayChain { - return s.relayChain + return sessionHydrator.session, nil } -func (s *session) GetGeoZone() GeoZone { - return s.geoZone +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 } -func (s *session) GetApplication() *coreTypes.Actor { - return s.application +// 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 := s.blockHeight % int64(numBlocksPerSession) + + s.session.NumSessionBlocks = int64(numBlocksPerSession) + s.session.SessionNumber = int64(s.blockHeight / int64(numBlocksPerSession)) + s.session.SessionHeight = s.blockHeight - numBlocksAheadOfSession + return nil } -func (s *session) GetFishermen() []*coreTypes.Actor { - return s.fishermen +// hydrateSessionApplication hydrates the full Application actor based on the address provided +func (s *sessionHydrator) hydrateSessionApplication(appAddr string) error { + // 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 + } + s.session.Application, err = s.readCtx.GetActor(coreTypes.ActorType_ACTOR_TYPE_APP, addr, s.session.SessionHeight) + return err } -func (s *session) GetServicers() []*coreTypes.Actor { - return s.servicers +// 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 + + 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(#697): Filter by geo-zone + + // INVESTIGATE: Consider what else we should validate for here (e.g. Application stake amount, etc.) + + 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 *session) getSessionId() ([]byte, types.Error) { +// 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.height)) + 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) + + if err != nil { + return err + } + appPubKeyBz := []byte(s.session.Application.PublicKey) + relayChainBz := []byte(string(s.session.RelayChain)) + geoZoneBz := []byte(s.session.GeoZone) - blockHashBz := []byte("get block hash bytes at s.sessionHeight from persistence module") + s.sessionIdBz = concat(sessionHeightBz, prevHashBz, geoZoneBz, relayChainBz, appPubKeyBz) + s.session.Id = crypto.GetHashStringFromBytes(s.sessionIdBz) + + return nil +} + +// 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) + if err != nil { + return err + } - appPubKey, err := crypto.NewPublicKey(s.application.GetPublicKey()) + // returns all the staked servicers at this session height + servicers, err := s.readCtx.GetAllServicers(s.session.SessionHeight) if err != nil { - return nil, types.ErrNewPublicKeyFromBytes(err) + return err + } + + // 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, 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) + } + + // TECHDEBT(#697): Filter by geo-zone + + // 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) + } } - return concat(sessionHeightBz, blockHashBz, []byte(s.geoZone), []byte(s.relayChain), appPubKey.Bytes()), nil + s.session.Servicers = pseudoRandomSelection(candidateServicers, numServicers, s.sessionIdBz) + 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)` -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 +// hydrateSessionFishermen finds the fishermen that are staked at the session height and populates the session with them +func (s *sessionHydrator) hydrateSessionFishermen() error { + // 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 fishermen at this session height + fishermen, err := s.readCtx.GetAllFishermen(s.session.SessionHeight) + if err != nil { + return err + } + + // 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 _, 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(#697): Filter by geo-zone + + // OPTIMIZE: If this was a map[string]struct{}, we could have avoided the loop + if slices.Contains(fisher.Chains, s.session.RelayChain) { + candidateFishermen = append(candidateFishermen, fisher) + } + } + + s.session.Fishermen = pseudoRandomSelection(candidateFishermen, numFishermen, s.sessionIdBz) + 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)` -func (s *session) selectSessionFishermen(numFishermen int) ([]*coreTypes.Actor, types.Error) { - // IMPORTANT: This function is for behaviour illustrative purposes only and implementation may differ. - return nil, nil +// 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 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) { + logger.Global.Warn().Msgf("pseudoRandomSelection: numTarget (%d) is greater than the number of candidates (%d)", numTarget, len(candidates)) + return candidates + } + + // 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 + actors := make([]*coreTypes.Actor, 0) + uniqueIndices := uniqueRandomIndices(seed, int64(len(candidates)), int64(numTarget)) + for idx := range uniqueIndices { + actors = append(actors, candidates[idx]) + } + + return actors } -// 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 -// -//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 +// 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`. +// 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 + 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 new file mode 100644 index 000000000..16ba35877 --- /dev/null +++ b/utility/session_test.go @@ -0,0 +1,539 @@ +package utility + +import ( + "fmt" + "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" +) + +// TECHDEBT(#697): Geozones are not current implemented, used or tested + +func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) { + // Test parameters + height := int64(1) + relayChain := test_artifacts.DefaultChains[0] + geoZone := "unused_geo" + numFishermen := 1 + numServicers := 1 + // needs to be manually updated if business logic changes + expectedSessionId := "5acf559f1a3faf3bea7eb692fe51bc1e2e5fb687ede0a6daa7d42399da4aa82b" + + runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, numServicers, 1, numFishermen) + + // Sanity check genesis + require.Len(t, runtimeCfg.GetGenesis().Applications, 1) + app := runtimeCfg.GetGenesis().Applications[0] + require.Len(t, runtimeCfg.GetGenesis().Fishermen, 1) + fisher := runtimeCfg.GetGenesis().Fishermen[0] + 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) + require.Len(t, session.Servicers, numServicers) + require.Equal(t, servicer.Address, session.Servicers[0].Address) + require.Len(t, session.Fishermen, numFishermen) + require.Equal(t, fisher.Address, session.Fishermen[0].Address) +} + +func TestSession_GetSession_ApplicationInvalid(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, 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 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) +} + +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] + + // Local variable to keep track of the height we're getting a session for + currentHeight := int64(0) + + // 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, currentHeight+1, session.SessionHeight) + + // Expect an error for a few heights into the future + 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 ; currentHeight < 10; currentHeight++ { + writeCtx, err := persistenceMod.NewRWContext(currentHeight + 1) + require.NoError(t, err) + 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 <= currentHeight+1; height++ { + _, err := utilityMod.GetSession(app.Address, height, relayChain, geoZone) + require.NoError(t, err) + } + + // Verify that currentHeight + 2 fails + _, err = utilityMod.GetSession(app.Address, currentHeight+2, relayChain, geoZone) + require.Error(t, err) +} + +func TestSession_GetSession_ServicersAndFishermenCounts_TotalAvailability(t *testing.T) { + // Prepare an environment with a lot of servicers and fishermen + 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 { + name string + numServicersPerSession int64 + numFishermanPerSession int64 + wantServicerCount int + wantFishermanCount int + }{ + { + name: "more actors per session than available in network", + numServicersPerSession: int64(numStakedServicers) * 10, + numFishermanPerSession: int64(numStakedFishermen) * 10, + wantServicerCount: numStakedServicers, + wantFishermanCount: numStakedFishermen, + }, + { + name: "less actors per session than available in network", + 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(numStakedServicers), + numFishermanPerSession: int64(numStakedFishermen), + wantServicerCount: numStakedServicers, + wantFishermanCount: numStakedFishermen, + }, + { + name: "more than enough servicers but not enough fishermen", + numServicersPerSession: int64(numStakedServicers) / 2, + numFishermanPerSession: int64(numStakedFishermen) * 10, + wantServicerCount: numStakedServicers / 2, + wantFishermanCount: numStakedFishermen, + }, + { + name: "more than enough fishermen but not enough servicers", + numServicersPerSession: int64(numStakedServicers) * 10, + numFishermanPerSession: int64(numStakedFishermen) / 2, + wantServicerCount: numStakedServicers, + wantFishermanCount: numStakedFishermen / 2, + }, + } + + // Constant parameters for testing + 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) { + // 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() + + 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) + + // 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)) + require.Equal(t, tt.wantFishermanCount, len(session.Fishermen)) + }) + } +} + +func TestSession_GetSession_ServicersAndFishermenCounts_ChainAvailability(t *testing.T) { + // 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"}) + 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"}) + 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"}) + + //nolint:gocritic // intentionally not appending result to a new slice + actors := append(application, append(servicersChain1, append(servicersChain2, append(fishermenChain1, fishermenChain2...)...)...)...) + //nolint:gocritic // intentionally not appending result to a new slice + keys := append(applicationKey, append(servicerKeysChain1, append(servicerKeysChain2, append(fishermenKeysChain1, fishermenKeysChain2...)...)...)...) + + // Prepare the environment + 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 { + 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: "chn3 has no servicers and fishermen", + chain: "chn3", + wantServicerCount: 0, + wantFishermanCount: 0, + }, + } + + // 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) + 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() + + // Test parameters + app := runtimeCfg.GetGenesis().Applications[0] + geoZone := "unused_geo" + + 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.Len(t, session.Servicers, tt.wantServicerCount) + require.Len(t, session.Fishermen, tt.wantFishermanCount) + }) + } +} + +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). + // 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 + setNumBlocksPerSession int64 + provideBlockHeight int64 + wantSessionHeight int64 + wantSessionNumber int64 + }{ + { + 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", + setNumBlocksPerSession: 5, + provideBlockHeight: 4, + wantSessionHeight: 0, + wantSessionNumber: 0, + }, + { + 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", + 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.setNumBlocksPerSession) + require.NoError(t, err) + + 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) + require.Equal(t, tt.wantSessionNumber, s.session.SessionNumber) + }) + } +} + +func TestSession_GetSession_ServicersAndFishermanEntropy(t *testing.T) { + // Prepare an environment with a lot of servicers and fishermen + numServicers := 1000 + 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 = (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 + runtimeCfg, utilityMod, persistenceMod := prepareEnvironment(t, 5, numServicers, numApplications, numFishermen) + + // 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() + + // 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" + + // 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] + + // 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) + 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.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.Len(t, 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) + + 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) + 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() + } +} + +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) { + // TECHDEBT(#697): 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) { + t.Helper() + + slice1 := actorsToAddrs(t, actors1) + slice2 := actorsToAddrs(t, actors2) + var commonCount float64 + for _, s1 := range slice1 { + for _, s2 := range slice2 { + if s1 == s2 { + commonCount++ + break + } + } + } + maxCommonCount := math.Round(maxSimilarityThreshold * float64(len(slice1))) + assert.LessOrEqual(t, commonCount, maxCommonCount, "Slices have more similarity than expected: %v vs max %v", slice1, slice2) +} + +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 + } + return addresses +} 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" 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, 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