diff --git a/.gitignore b/.gitignore index ed265446..0c9f4977 100644 --- a/.gitignore +++ b/.gitignore @@ -13,12 +13,19 @@ # Ignore vendor folder: every environment needs to build their own based on package management. vendor/ +pocket # Log files should be ignored *.log logs/Logs *.idea/ +main + + +app/cmd/rpc/data/pocket_evidence.db +app/cmd/rpc/data/session.db + tests/unit/*.log tests/unit/*.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06d25f96..4a0da7ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ This Contribution Guide aims to guide contributors through the process of propos #### Communicating proposals -The first step towards contribution is to effectively propose your change by opening up a new issue using the **Contribution Proposal** issue template. Communicating your proposal effectively will be of the upmost importance throughout the lifecycle of your contribution, so make sure your description is clear and concise. +The first step towards contribution is to effectively propose your change by opening up a new issue using the **Contribution Proposal** issue template. Communicating your proposal effectively will be of the upmost importance throughout the lifecycle of your contribution, so make sure your description is clear and concise. Feel free to also use the `#core-research` channel in our [Official Discord server](https://bit.ly/POKTARCADEdscrd) to ask any questions regarding the proposal you want to make to the repository. #### Consensus-breaking changes @@ -65,7 +65,6 @@ And you will build a `pocket` binary in the root folder which can now be run. - Code must adhere to the official Go formatting guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt)). - Code must be documented adhering to the official Go commentary guidelines. -- Pull requests need to be based on and opened against the `staging` branch. ### Quality Assurance @@ -81,13 +80,31 @@ The Regression Testing suite of Pocket Core is a series of scenarios that need t ### The Pull Request Process -#### Submitting a PR +#### Submitting a PR to a Release Window -Open a PR against the `staging` branch in the official repository: `https://github.com/pokt-network/pocket-core`. To learn how to open a PR in Github please follow this [guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). +Every PR will target an upcoming Release Window for the Pocket Core repository, these windows will be listed as Github Projects in the repository and will be listed in the repository [Projects Page](https://github.com/pokt-network/pocket-core/projects?type=classic). Every project in this page will represent a Release Window which contains the following: + +- A PR Cut-out date by which PR's must be approved to be included in the release. +- An integration branch to submit PR's to for this release. + +Open a PR against the **integration branch** in the official repository: `https://github.com/pokt-network/pocket-core`. To learn how to open a PR in Github please follow this [guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). #### The PR review process -Every Pull Request will require at least 1 reviewer from the Core team. Every proposal is different in scope and complexity, so the following points will increase the likelihood of you submitting a successful Pull Request: +Every Pull Request will require at least 2 reviewers from the Core team. The reviews will be done in a 2 phase approach: + +1. A functional PR review where the code will be extensively reviewed by a Core Team member and feedback will be provided until the code quality meets the functional requirements of the intended Proposal. +2. An Integration approval, every friday the Core Team will be evaluating PR's ready to be included, and selecting those into QA builds that will be tested as part of the Regression testing performed before each released is launched. + +#### The PR Success Criteria + +The Core Team will be requiring the following before merging a Pull Request: + +- It has a linked Contribution Proposal issue specifying the functionality implemented in the PR. +- There is an associated pull request to the [Pocket Core Functional Tests repository](https://github.com/pokt-network/pocket-core-func-tests) with the appropiate QA scenarios associated with the functionality or fix contained in the PR. +- Enough evidence (either automated or manual) of testing of these new scenarios included in the PR. This evidence must be presented in the format template contained [here](https://github.com/pokt-network/pocket-core/tree/staging/doc/qa/regression). + +Every proposal is different in scope and complexity, so the following points will increase the likelihood of you submitting a successful Pull Request: - Your proposal issue is clear, concise and informative. - Your PR is within the scope of the proposal. diff --git a/app/cmd/cli/accounts.go b/app/cmd/cli/accounts.go index f077ecd2..f96cace6 100644 --- a/app/cmd/cli/accounts.go +++ b/app/cmd/cli/accounts.go @@ -40,6 +40,8 @@ func init() { accountsCmd.AddCommand(signNexMS) accountsCmd.AddCommand(buildMultisig) accountsCmd.AddCommand(unsafeDeleteCmd) + accountsCmd.AddCommand(getNodesLean) + accountsCmd.AddCommand(setValidatorsLean) } // accountsCmd represents the accounts namespace command @@ -104,6 +106,29 @@ Will prompt the user for a passphrase to encrypt the generated keypair.`, }, } +var getNodesLean = &cobra.Command{ + Use: "get-validators", + Short: "Retrieves all nodes set by set-validators", + Long: `Retrieves all nodes set by set-validators`, + Run: func(cmd *cobra.Command, args []string) { + app.InitConfig(datadir, tmNode, persistentPeers, seeds, remoteCLIURL) + config := app.GlobalConfig + if !config.PocketConfig.LeanPocket { + fmt.Println("Lean pocket is not enabled") + return + } + + keys, err := app.LoadFilePVKeysFromFileLean(config.PocketConfig.DataDir + app.FS + config.TendermintConfig.PrivValidatorKey) + if err != nil { + fmt.Println("Failed to read set validators") + return + } + for _, value := range keys { + fmt.Printf("Node Address: %s\n", strings.ToLower(value.Address.String())) + } + }, +} + var getValidator = &cobra.Command{ Use: "get-validator", Short: "Retrieves the main validator from the priv_val file", @@ -120,6 +145,33 @@ var getValidator = &cobra.Command{ }, } +var setValidatorsLean = &cobra.Command{ + Use: `set-validators `, + Short: "Sets the main validator accounts for tendermint; NOTE: keyfile should be a json string array of private keys", + Long: `Sets the main validator accounts that will be used across all Tendermint functions; NOTE: keyfile should be a json string array of private keys`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + app.InitConfig(datadir, tmNode, persistentPeers, seeds, remoteCLIURL) + + if !app.GlobalConfig.PocketConfig.LeanPocket { + fmt.Println("Lean pocket is not enabled") + return + } + + keys, err := app.ReadValidatorPrivateKeyFileLean(args[0]) + if err != nil { + fmt.Println("Failed to read validators json file ", err) + os.Exit(1) + } + err = app.SetValidatorsFilesLean(keys) + if err != nil { + fmt.Println("Failed to set validators", err) + os.Exit(1) + return + } + }, +} + var setValidator = &cobra.Command{ Use: "set-validator
", Short: "Sets the main validator account for tendermint", diff --git a/app/cmd/cli/queryUtil.go b/app/cmd/cli/queryUtil.go index 883c2fb0..a08ef6f2 100644 --- a/app/cmd/cli/queryUtil.go +++ b/app/cmd/cli/queryUtil.go @@ -42,7 +42,8 @@ var ( GetAllParamsPath, GetParamPath, GetStopPath, - GetQueryChains string + GetQueryChains, + QueryAccounts string ) func init() { @@ -63,6 +64,8 @@ func init() { GetHeightPath = route.Path case "QueryAccount": GetAccountPath = route.Path + case "QueryAccounts": + GetAccountPath = route.Path case "QueryApp": GetAppPath = route.Path case "QueryTX": diff --git a/app/cmd/cli/root.go b/app/cmd/cli/root.go index f988988e..28d4b5c5 100644 --- a/app/cmd/cli/root.go +++ b/app/cmd/cli/root.go @@ -14,18 +14,20 @@ import ( ) var ( - datadir string - tmNode string - remoteCLIURL string - persistentPeers string - seeds string - simulateRelay bool - keybase bool - mainnet bool - allBlockTxs bool - testnet bool - profileApp bool - useCache bool + datadir string + tmNode string + remoteCLIURL string + persistentPeers string + seeds string + simulateRelay bool + keybase bool + mainnet bool + allBlockTxs bool + testnet bool + profileApp bool + useCache bool + forceSetValidatorsLean bool + useLean bool ) var CLIVersion = app.AppVersion @@ -58,6 +60,7 @@ func init() { startCmd.Flags().BoolVar(&testnet, "testnet", false, "run with testnet genesis") startCmd.Flags().BoolVar(&profileApp, "profileApp", false, "expose cpu & memory profiling") startCmd.Flags().BoolVar(&useCache, "useCache", false, "use cache") + startCmd.Flags().BoolVar(&forceSetValidatorsLean, "forceSetValidators", false, "reads your lean_pocket_user_key_file (lean_nodes_keys.json) and updates your last signed state/validator files before starting your node") rootCmd.AddCommand(startCmd) rootCmd.AddCommand(resetCmd) rootCmd.AddCommand(version) @@ -92,7 +95,7 @@ func start(cmd *cobra.Command, args []string) { if testnet { genesisType = app.TestnetGenesisType } - tmNode := app.InitApp(datadir, tmNode, persistentPeers, seeds, remoteCLIURL, keybase, genesisType, useCache) + tmNode := app.InitApp(datadir, tmNode, persistentPeers, seeds, remoteCLIURL, keybase, genesisType, useCache, forceSetValidatorsLean) go rpc.StartRPC(app.GlobalConfig.PocketConfig.RPCPort, app.GlobalConfig.PocketConfig.RPCTimeout, simulateRelay, profileApp, allBlockTxs, app.GlobalConfig.PocketConfig.ChainsHotReload) // trap kill signals (2,3,15,9) signalChannel := make(chan os.Signal, 1) diff --git a/app/cmd/rpc/common_test.go b/app/cmd/rpc/common_test.go index 75c66e50..e4cf2fd9 100644 --- a/app/cmd/rpc/common_test.go +++ b/app/cmd/rpc/common_test.go @@ -78,8 +78,7 @@ func NewInMemoryTendermintNode(t *testing.T, genesisState []byte) (tendermintNod if err != nil { panic(err) } - pocketTypes.ClearEvidence() - pocketTypes.ClearSessionCache() + pocketTypes.CleanPocketNodes() inMemKB = nil //err = os.RemoveAll(tendermintNode.Config().DBPath) if err != nil { @@ -166,11 +165,12 @@ func inMemTendermintNode(genesisState []byte) (*node.Node, keys.Keybase) { } db := dbm.NewMemDB() nodeKey := p2p.NodeKey{PrivKey: pk} - privVal := privval.GenFilePV(c.TmConfig.PrivValidatorKey, c.TmConfig.PrivValidatorState) - privVal.Key.PrivKey = pk - privVal.Key.PubKey = pk.PubKey() - privVal.Key.Address = pk.PubKey().Address() - pocketTypes.InitPVKeyFile(privVal.Key) + privVal := privval.GenFilePVLean(c.TmConfig.PrivValidatorKey, c.TmConfig.PrivValidatorState) + privVal.Keys[0].PrivKey = pk + privVal.Keys[0].PubKey = pk.PubKey() + privVal.Keys[0].Address = pk.PubKey().Address() + pocketTypes.CleanPocketNodes() + pocketTypes.AddPocketNodeByFilePVKey(privVal.Keys[0], c.Logger) creator := func(logger log.Logger, db dbm.DB, _ io.Writer) *app.PocketCoreApp { m := map[string]pocketTypes.HostedBlockchain{sdk.PlaceholderHash: { diff --git a/app/cmd/rpc/query.go b/app/cmd/rpc/query.go index c3656b47..565f0bc4 100644 --- a/app/cmd/rpc/query.go +++ b/app/cmd/rpc/query.go @@ -4,8 +4,10 @@ import ( "encoding/hex" "encoding/json" "fmt" + types4 "github.com/pokt-network/pocket-core/app/cmd/rpc/types" sdk "github.com/pokt-network/pocket-core/types" types2 "github.com/pokt-network/pocket-core/x/auth/types" + types3 "github.com/pokt-network/pocket-core/x/pocketcore/types" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/types" @@ -24,6 +26,31 @@ func Version(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { WriteResponse(w, APIVersion, r.URL.Path, r.Host) } +func LocalNodes(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + value := r.URL.Query().Get("authtoken") + if value != app.AuthToken.Value { + WriteErrorResponse(w, 401, "wrong authtoken "+value) + return + } + var localNodes []types4.PublicPocketNode + for _, node := range types3.GlobalPocketNodes { + if node == nil { + continue + } + localNodes = append(localNodes, types4.PublicPocketNode{Address: node.GetAddress().String()}) + } + j, err := json.Marshal(localNodes) + if err != nil { + WriteErrorResponse(w, 400, err.Error()) + return + } + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + _, err = w.Write(j) + if err != nil { + WriteErrorResponse(w, 400, err.Error()) + } +} + type HeightParams struct { Height int64 `json:"height"` } @@ -364,6 +391,28 @@ func Account(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { WriteJSONResponse(w, string(s), r.URL.Path, r.Host) } +func Accounts(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + var params = PaginatedHeightParams{Height: 0} + if err := PopModel(w, r, ps, ¶ms); err != nil { + WriteErrorResponse(w, 400, err.Error()) + return + } + if params.Height == 0 { + params.Height = app.PCA.BaseApp.LastBlockHeight() + } + res, err := app.PCA.QueryAccounts(params.Height, params.Page, params.PerPage) + if err != nil { + WriteErrorResponse(w, 400, err.Error()) + return + } + s, err := json.Marshal(res) + if err != nil { + WriteErrorResponse(w, 400, err.Error()) + return + } + WriteJSONResponse(w, string(s), r.URL.Path, r.Host) +} + func Nodes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { var params = HeightAndValidatorOptsParams{} if err := PopModel(w, r, ps, ¶ms); err != nil { diff --git a/app/cmd/rpc/rpc_test.go b/app/cmd/rpc/rpc_test.go index e8d855e1..c88923c9 100644 --- a/app/cmd/rpc/rpc_test.go +++ b/app/cmd/rpc/rpc_test.go @@ -311,6 +311,28 @@ func TestRPC_QueryAccount(t *testing.T) { stopCli() } +func TestRPC_QueryAccounts(t *testing.T) { + codec.UpgradeHeight = 7000 + _, _, cleanup := NewInMemoryTendermintNode(t, oneValTwoNodeGenesisState()) + _, stopCli, evtChan := subscribeTo(t, tmTypes.EventNewBlock) + <-evtChan + kb := getInMemoryKeybase() + cb, err := kb.GetCoinbase() + assert.Nil(t, err) + var params = PaginatedHeightParams{ + Height: 0, + } + address := cb.GetAddress().String() + q := newQueryRequest("accounts", newBody(params)) + rec := httptest.NewRecorder() + Accounts(rec, q, httprouter.Params{}) + body := rec.Body.String() + assert.True(t, strings.Contains(body, address)) + + cleanup() + stopCli() +} + func TestRPC_QueryNodes(t *testing.T) { codec.UpgradeHeight = 7000 _, _, cleanup := NewInMemoryTendermintNode(t, oneValTwoNodeGenesisState()) diff --git a/app/cmd/rpc/server.go b/app/cmd/rpc/server.go index fdd139d5..19b8dc22 100644 --- a/app/cmd/rpc/server.go +++ b/app/cmd/rpc/server.go @@ -96,6 +96,7 @@ func GetRoutes() Routes { Route{Name: "Stop", Method: "POST", Path: "/v1/private/stop", HandlerFunc: Stop}, Route{Name: "ServiceCORS", Method: "OPTIONS", Path: "/v1/client/relay", HandlerFunc: Relay}, Route{Name: "QueryAccount", Method: "POST", Path: "/v1/query/account", HandlerFunc: Account}, + Route{Name: "QueryAccounts", Method: "POST", Path: "/v1/query/accounts", HandlerFunc: Accounts}, Route{Name: "QueryAccountTxs", Method: "POST", Path: "/v1/query/accounttxs", HandlerFunc: AccountTxs}, Route{Name: "QueryACL", Method: "POST", Path: "/v1/query/acl", HandlerFunc: ACL}, Route{Name: "QueryAllParams", Method: "POST", Path: "/v1/query/allparams", HandlerFunc: AllParams}, @@ -120,6 +121,7 @@ func GetRoutes() Routes { Route{Name: "QueryTX", Method: "POST", Path: "/v1/query/tx", HandlerFunc: Tx}, Route{Name: "QueryUpgrade", Method: "POST", Path: "/v1/query/upgrade", HandlerFunc: Upgrade}, Route{Name: "QuerySigningInfo", Method: "POST", Path: "/v1/query/signinginfo", HandlerFunc: SigningInfo}, + Route{Name: "LocalNodes", Method: "POST", Path: "/v1/private/nodes", HandlerFunc: LocalNodes}, Route{Name: "QueryChains", Method: "POST", Path: "/v1/private/chains", HandlerFunc: Chains}, } return routes diff --git a/app/cmd/rpc/types/public_pocket_node.go b/app/cmd/rpc/types/public_pocket_node.go new file mode 100644 index 00000000..07f7ffc0 --- /dev/null +++ b/app/cmd/rpc/types/public_pocket_node.go @@ -0,0 +1,5 @@ +package types + +type PublicPocketNode struct { + Address string `json:"address"` +} diff --git a/app/common_test.go b/app/common_test.go index 16513ae2..74adfa4e 100644 --- a/app/common_test.go +++ b/app/common_test.go @@ -60,9 +60,16 @@ type codecUpgrade struct { //after8 bool } +// NewInMemoryTendermintNodeAmino will create a TM node with only one validator. LeanPocket is disabled. func NewInMemoryTendermintNodeAmino(t *testing.T, genesisState []byte) (tendermintNode *node.Node, keybase keys.Keybase, cleanup func()) { + return NewInMemoryTendermintNodeAminoWithValidators(t, genesisState, nil) +} + +// NewInMemoryTendermintNodeAminoWithValidators will create a TM node with 'n' "validators". +// If "validators" is nil, LeanPOKT is disabled +func NewInMemoryTendermintNodeAminoWithValidators(t *testing.T, genesisState []byte, validators []crypto.PrivateKey) (tendermintNode *node.Node, keybase keys.Keybase, cleanup func()) { // create the in memory tendermint node and keybase - tendermintNode, keybase = inMemTendermintNode(genesisState) + tendermintNode, keybase = inMemTendermintNodeWithValidators(genesisState, validators) // test assertions if tendermintNode == nil { panic("tendermintNode should not be nil") @@ -72,10 +79,16 @@ func NewInMemoryTendermintNodeAmino(t *testing.T, genesisState []byte) (tendermi } assert.NotNil(t, tendermintNode) assert.NotNil(t, keybase) + // init cache in memory + defaultConfig := sdk.DefaultTestingPocketConfig() + if validators != nil { + defaultConfig.PocketConfig.LeanPocket = true + } + pocketTypes.InitConfig(&pocketTypes.HostedBlockchains{ M: make(map[string]pocketTypes.HostedBlockchain), - }, tendermintNode.Logger, sdk.DefaultTestingPocketConfig()) + }, tendermintNode.Logger, defaultConfig) // start the in memory node err := tendermintNode.Start() if err != nil { @@ -89,8 +102,7 @@ func NewInMemoryTendermintNodeAmino(t *testing.T, genesisState []byte) (tendermi if err != nil { panic(err) } - pocketTypes.ClearEvidence() - pocketTypes.ClearSessionCache() + pocketTypes.CleanPocketNodes() PCA = nil inMemKB = nil err := inMemDB.Close() @@ -110,9 +122,17 @@ func NewInMemoryTendermintNodeAmino(t *testing.T, genesisState []byte) (tendermi } return } + +// NewInMemoryTendermintNodeProto will create a TM node with only one validator. LeanPocket is disabled. func NewInMemoryTendermintNodeProto(t *testing.T, genesisState []byte) (tendermintNode *node.Node, keybase keys.Keybase, cleanup func()) { + return NewInMemoryTendermintNodeProtoWithValidators(t, genesisState, nil) +} + +// NewInMemoryTendermintNodeWithValidators will create a TM node with 'n' "validators". +// If "validators" is nil, this creates a pre-leanpokt TM node, else it will enable lean pocket +func NewInMemoryTendermintNodeProtoWithValidators(t *testing.T, genesisState []byte, validators []crypto.PrivateKey) (tendermintNode *node.Node, keybase keys.Keybase, cleanup func()) { // create the in memory tendermint node and keybase - tendermintNode, keybase = inMemTendermintNode(genesisState) + tendermintNode, keybase = inMemTendermintNodeWithValidators(genesisState, validators) // test assertions if tendermintNode == nil { panic("tendermintNode should not be nil") @@ -122,10 +142,15 @@ func NewInMemoryTendermintNodeProto(t *testing.T, genesisState []byte) (tendermi } assert.NotNil(t, tendermintNode) assert.NotNil(t, keybase) + // init cache in memory + defaultConfig := sdk.DefaultTestingPocketConfig() + if validators != nil { + defaultConfig.PocketConfig.LeanPocket = true + } pocketTypes.InitConfig(&pocketTypes.HostedBlockchains{ M: make(map[string]pocketTypes.HostedBlockchain), - }, tendermintNode.Logger, sdk.DefaultTestingPocketConfig()) + }, tendermintNode.Logger, defaultConfig) // start the in memory node err := tendermintNode.Start() if err != nil { @@ -141,8 +166,8 @@ func NewInMemoryTendermintNodeProto(t *testing.T, genesisState []byte) (tendermi if err != nil { panic(err) } - pocketTypes.ClearEvidence() - pocketTypes.ClearSessionCache() + + pocketTypes.CleanPocketNodes() PCA = nil inMemKB = nil @@ -163,6 +188,18 @@ func NewInMemoryTendermintNodeProto(t *testing.T, genesisState []byte) (tendermi return } +func TestNewInMemoryAminoWithValidators(t *testing.T) { + gbz, validators, _, _ := generateGenesis(5, 5, 10) + _, _, cleanup := NewInMemoryTendermintNodeAminoWithValidators(t, gbz, validators) + defer cleanup() +} + +func TestNewInMemoryProtoWithValidators(t *testing.T) { + gbz, validators, _, _ := generateGenesis(5, 5, 10) + _, _, cleanup := NewInMemoryTendermintNodeProtoWithValidators(t, gbz, validators) + defer cleanup() +} + func TestNewInMemoryAmino(t *testing.T) { _, _, cleanup := NewInMemoryTendermintNodeAmino(t, oneAppTwoNodeGenesis()) defer cleanup() @@ -201,7 +238,9 @@ func getInMemoryDB() dbm.DB { return inMemDB } -func inMemTendermintNode(genesisState []byte) (*node.Node, keys.Keybase) { +// inMemTendermintNodeWithValidators will create a TM node with 'n' "validators". +// If "validators" is nil, LeanPokt is disabled and uses in memory CB as the sole validator for consensus +func inMemTendermintNodeWithValidators(genesisState []byte, validatorsPk []crypto.PrivateKey) (*node.Node, keys.Keybase) { kb := getInMemoryKeybase() cb, err := kb.GetCoinbase() if err != nil { @@ -244,11 +283,28 @@ func inMemTendermintNode(genesisState []byte) (*node.Node, keys.Keybase) { panic(err) } nodeKey := p2p.NodeKey{PrivKey: pk} - privVal := GenFilePV(c.TmConfig.PrivValidatorKey, c.TmConfig.PrivValidatorState) - privVal.Key.PrivKey = pk - privVal.Key.PubKey = pk.PubKey() - privVal.Key.Address = pk.PubKey().Address() - pocketTypes.InitPVKeyFile(privVal.Key) + var privVal *privval.FilePVLean + if validatorsPk == nil { + // only set cb as validator + privVal = privval.GenFilePVLean(c.TmConfig.PrivValidatorKey, c.TmConfig.PrivValidatorState) + privVal.Keys[0].PrivKey = pk + privVal.Keys[0].PubKey = pk.PubKey() + privVal.Keys[0].Address = pk.PubKey().Address() + pocketTypes.CleanPocketNodes() + pocketTypes.AddPocketNodeByFilePVKey(privVal.Keys[0], c.Logger) + } else { + // (LeanPOKT) Set multiple nodes as validators + pocketTypes.CleanPocketNodes() + // generating a stub of n validators + privVal = privval.GenFilePVsLean(c.TmConfig.PrivValidatorKey, c.TmConfig.PrivValidatorState, uint(len(validatorsPk))) + // replace the stub validators with the correct validators + for i, pk := range validatorsPk { + privVal.Keys[i].PrivKey = pk.PrivKey() + privVal.Keys[i].PubKey = pk.PubKey() + privVal.Keys[i].Address = pk.PubKey().Address() + pocketTypes.AddPocketNode(pk, c.Logger) + } + } dbProvider := func(*node.DBContext) (dbm.DB, error) { return db, nil @@ -279,19 +335,13 @@ func inMemTendermintNode(genesisState []byte) (*node.Node, keys.Keybase) { return tmNode, kb } -// GenFilePV generates a new validator with randomly generated private key -// and sets the filePaths, but does not call Save(). -func GenFilePV(keyFilePath, stateFilePath string) *privval.FilePV { - return privval.GenFilePV(keyFilePath, stateFilePath) -} - func GetApp(logger log.Logger, db dbm.DB, traceWriter io.Writer) *PocketCoreApp { creator := func(logger log.Logger, db dbm.DB, _ io.Writer) *PocketCoreApp { m := map[string]pocketTypes.HostedBlockchain{"0001": { ID: sdk.PlaceholderHash, URL: sdk.PlaceholderURL, }} - p := NewPocketCoreApp(GenState, getInMemoryKeybase(), getInMemoryTMClient(), &pocketTypes.HostedBlockchains{M: m, L: sync.Mutex{}}, logger, db, false, 5000000, bam.SetPruning(store.PruneNothing)) + p := NewPocketCoreApp(GenState, getInMemoryKeybase(), getInMemoryTMClient(), &pocketTypes.HostedBlockchains{M: m, L: sync.RWMutex{}}, logger, db, false, 5000000, bam.SetPruning(store.PruneNothing)) return p } return creator(logger, db, traceWriter) @@ -908,6 +958,157 @@ func fiveValidatorsOneAppGenesis() (genBz []byte, keys []crypto.PrivateKey, vali return j, kys, posGenesisState.Validators, appsGenesisState.Applications[0] } +// generateGenesis generates a genesis state of n validators, n servicers, and n apps +func generateGenesis(validators int, servicers int, appss int) ([]byte, []crypto.PrivateKey, []crypto.PrivateKey, []crypto.PrivateKey) { + + kb := getInMemoryKeybase() + kp1, err := kb.GetCoinbase() + + if err != nil { + panic(err) + } + + validatorPks := []crypto.PrivateKey{} + servicerPks := []crypto.PrivateKey{} + appPks := []crypto.PrivateKey{} + + encryptPassPhrase := "test" + for i := 0; i < validators; i++ { + keyPair, err := kb.Create(encryptPassPhrase) + if err != nil { + panic(err) + } + pk, err := kb.ExportPrivateKeyObject(keyPair.GetAddress(), encryptPassPhrase) + if err != nil { + panic(err) + } + validatorPks = append(validatorPks, pk) + } + + for i := 0; i < servicers; i++ { + keyPair, err := kb.Create(encryptPassPhrase) + if err != nil { + panic(err) + } + pk, err := kb.ExportPrivateKeyObject(keyPair.GetAddress(), encryptPassPhrase) + if err != nil { + panic(err) + } + servicerPks = append(servicerPks, pk) + } + + for i := 0; i < appss; i++ { + keyPair, err := kb.Create(encryptPassPhrase) + if err != nil { + panic(err) + } + pk, err := kb.ExportPrivateKeyObject(keyPair.GetAddress(), encryptPassPhrase) + if err != nil { + panic(err) + } + appPks = append(appPks, pk) + } + + defaultGenesis := module.NewBasicManager( + apps.AppModuleBasic{}, + auth.AppModuleBasic{}, + gov.AppModuleBasic{}, + nodes.AppModuleBasic{}, + pocket.AppModuleBasic{}, + ).DefaultGenesis() + + rawPOS := defaultGenesis[nodesTypes.ModuleName] + var posGenesisState nodesTypes.GenesisState + memCodec().MustUnmarshalJSON(rawPOS, &posGenesisState) + + rawAccounts := defaultGenesis[auth.ModuleName] + var authGenState auth.GenesisState + memCodec().MustUnmarshalJSON(rawAccounts, &authGenState) + + MinStake := int64(10000000000) + ValidatorStake := MinStake + 1000000 + + posGenesisState.Params.StakeMinimum = MinStake + posGenesisState.Params.MaxValidators = int64(validators) + // validators kp + for _, v := range validatorPks { + posGenesisState.Validators = append(posGenesisState.Validators, + nodesTypes.Validator{Address: sdk.Address(v.PublicKey().Address()), + PublicKey: v.PublicKey(), + Status: sdk.Staked, + Chains: []string{dummyChainsHash}, + ServiceURL: sdk.PlaceholderServiceURL, + StakedTokens: sdk.NewInt(ValidatorStake)}) + + authGenState.Accounts = append(authGenState.Accounts, &auth.BaseAccount{ + Address: sdk.Address(v.PublicKey().Address()), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultStakeDenom, sdk.NewInt(ValidatorStake))), + PubKey: v.PublicKey(), + }) + } + + for _, v := range servicerPks { + posGenesisState.Validators = append(posGenesisState.Validators, + nodesTypes.Validator{Address: sdk.Address(v.PublicKey().Address()), + PublicKey: v.PublicKey(), + Status: sdk.Staked, + Chains: []string{dummyChainsHash}, + ServiceURL: sdk.PlaceholderServiceURL, + StakedTokens: sdk.NewInt(MinStake)}) + } + + // validators+servicers + res := memCodec().MustMarshalJSON(posGenesisState) + defaultGenesis[nodesTypes.ModuleName] = res + + // pokt holders + res3 := memCodec().MustMarshalJSON(authGenState) + defaultGenesis[auth.ModuleName] = res3 + + // setup application + rawApps := defaultGenesis[appsTypes.ModuleName] + var appsGenesisState appsTypes.GenesisState + memCodec().MustUnmarshalJSON(rawApps, &appsGenesisState) + for _, pk := range appPks { + appsGenesisState.Applications = append(appsGenesisState.Applications, appsTypes.Application{ + Address: sdk.GetAddress(pk.PublicKey()), + PublicKey: pk.PublicKey(), + Jailed: false, + Status: sdk.Staked, + Chains: []string{dummyChainsHash}, + StakedTokens: sdk.NewInt(10000000), + MaxRelays: sdk.NewInt(100000), + UnstakingCompletionTime: time.Time{}, + }) + } + res2 := memCodec().MustMarshalJSON(appsGenesisState) + defaultGenesis[appsTypes.ModuleName] = res2 + + // set default chain for module + rawPocket := defaultGenesis[pocketTypes.ModuleName] + var pocketGenesisState pocketTypes.GenesisState + memCodec().MustUnmarshalJSON(rawPocket, &pocketGenesisState) + pocketGenesisState.Params.SessionNodeCount = int64(validators + servicers) + pocketGenesisState.Params.SupportedBlockchains = []string{"0001"} + res4 := memCodec().MustMarshalJSON(pocketGenesisState) + defaultGenesis[pocketTypes.ModuleName] = res4 + // set default governance in genesis + var govGenesisState govTypes.GenesisState + rawGov := defaultGenesis[govTypes.ModuleName] + memCodec().MustUnmarshalJSON(rawGov, &govGenesisState) + nMACL := createTestACL(kp1) + govGenesisState.Params.Upgrade = govTypes.NewUpgrade(10000, "2.0.0") + govGenesisState.Params.ACL = nMACL + govGenesisState.Params.DAOOwner = kp1.GetAddress() + govGenesisState.DAOTokens = sdk.NewInt(1000) + res5 := memCodec().MustMarshalJSON(govGenesisState) + defaultGenesis[govTypes.ModuleName] = res5 + // end genesis setup + GenState = defaultGenesis + j, _ := memCodec().MarshalJSONIndent(defaultGenesis, "", " ") + return j, validatorPks, servicerPks, appPks +} + // //func TestGatewayChecker(t *testing.T) { // startheight := 14681 diff --git a/app/config.go b/app/config.go index 5af01204..b7c8bc8f 100644 --- a/app/config.go +++ b/app/config.go @@ -2,12 +2,15 @@ package app import ( "bufio" + "encoding/hex" "encoding/json" + "errors" "fmt" "io" "io/ioutil" log2 "log" "os" + "path" fp "path/filepath" "strings" "sync" @@ -72,14 +75,12 @@ const ( DefaultGenesisType ) -func InitApp(datadir, tmNode, persistentPeers, seeds, remoteCLIURL string, keybase bool, genesisType GenesisType, useCache bool) *node.Node { +func InitApp(datadir, tmNode, persistentPeers, seeds, remoteCLIURL string, keybase bool, genesisType GenesisType, useCache bool, forceSetValidatorsLean bool) *node.Node { // init config InitConfig(datadir, tmNode, persistentPeers, seeds, remoteCLIURL) GlobalConfig.PocketConfig.Cache = useCache // init AuthToken - InitAuthToken() - // init the keyfiles - InitKeyfiles() + InitAuthToken(GlobalConfig.PocketConfig.GenerateTokenOnStart) // get hosted blockchains chains := NewHostedChains(false) if GlobalConfig.PocketConfig.ChainsHotReload { @@ -88,7 +89,31 @@ func InitApp(datadir, tmNode, persistentPeers, seeds, remoteCLIURL string, keyba } // create logger logger := InitLogger() - // init cache + + // prestart hook, so users don't have to create their own set-validator prestart script + if GlobalConfig.PocketConfig.LeanPocket { + userProvidedKeyPath := GlobalConfig.PocketConfig.GetLeanPocketUserKeyFilePath() + pvkName := path.Join(GlobalConfig.PocketConfig.DataDir, GlobalConfig.TendermintConfig.PrivValidatorKey) + if _, err := os.Stat(pvkName); err != nil && os.IsNotExist(err) || forceSetValidatorsLean { // user has not ran set-validators + // read the user provided lean nodes + keys, err := ReadValidatorPrivateKeyFileLean(userProvidedKeyPath) + if err != nil { + logger.Error("Can't read user provided validator keys, did you create keys in", userProvidedKeyPath, err) + os.Exit(1) + } + // set them + err = SetValidatorsFilesLean(keys) + if err != nil { + logger.Error("Failed to set validators for user provided file, try pocket accounts set-validators", userProvidedKeyPath, err) + os.Exit(1) + } + } + } + + // init key files + InitKeyfiles(logger) + + // init configs & evidence/session caches InitPocketCoreConfig(chains, logger) // init genesis InitGenesis(genesisType, logger) @@ -181,6 +206,11 @@ func InitConfig(datadir, tmNode, persistentPeers, seeds, remoteCLIURL string) { c.TendermintConfig.P2P.AllowDuplicateIP = true GlobalConfig = c + if GlobalConfig.PocketConfig.LeanPocket { + GlobalConfig.TendermintConfig.PrivValidatorState = sdk.DefaultPVSNameLean + GlobalConfig.TendermintConfig.PrivValidatorKey = sdk.DefaultPVKNameLean + GlobalConfig.TendermintConfig.NodeKey = sdk.DefaultNKNameLean + } } func UpdateConfig(datadir string) { @@ -346,7 +376,17 @@ func InitTendermint(keybase bool, chains *types.HostedBlockchains, logger log.Lo return tmNode } -func InitKeyfiles() { +func InitKeyfiles(logger log.Logger) { + + if GlobalConfig.PocketConfig.LeanPocket { + err := InitNodesLean(logger) + if err != nil { + logger.Error("Failed to init lean nodes", err) + os.Exit(1) + } + return + } + datadir := GlobalConfig.PocketConfig.DataDir // Check if privvalkey file exist if _, err := os.Stat(datadir + FS + GlobalConfig.TendermintConfig.PrivValidatorKey); err != nil { @@ -354,9 +394,10 @@ func InitKeyfiles() { if os.IsNotExist(err) { // generate random key for easy orchestration randomKey := crypto.GenerateEd25519PrivKey() - privValKey(randomKey) + privValKey := privValKey(randomKey) privValState() nodeKey(randomKey) + types.AddPocketNodeByFilePVKey(privValKey, logger) log2.Printf("No Validator Set! Creating Random Key: %s", randomKey.PublicKey().RawString()) return } else { @@ -366,8 +407,35 @@ func InitKeyfiles() { } else { // file exist so we can load pk from file. file, _ := loadPKFromFile(datadir + FS + GlobalConfig.TendermintConfig.PrivValidatorKey) - types.InitPVKeyFile(file) + types.AddPocketNodeByFilePVKey(file, logger) + } +} + +func InitNodesLean(logger log.Logger) error { + pvkName := path.Join(GlobalConfig.PocketConfig.DataDir, GlobalConfig.TendermintConfig.PrivValidatorKey) + + if _, err := os.Stat(pvkName); err != nil { + if os.IsNotExist(err) { + return errors.New("pocket accounts set-validators must be ran first") + } + return errors.New("Failed to retrieve information on " + pvkName) + } + + leanNodesTm, err := LoadFilePVKeysFromFileLean(pvkName) + + if err != nil { + return err + } + + if len(leanNodesTm) == 0 { + return errors.New("failed to load lean validators, length of zero") + } + + for _, node := range leanNodesTm { + types.AddPocketNodeByFilePVKey(node, logger) } + + return nil } func InitLogger() (logger log.Logger) { @@ -446,7 +514,45 @@ func loadPKFromFile(path string) (privval.FilePVKey, string) { return pvKey, path } -func privValKey(res crypto.PrivateKey) { +func LoadFilePVKeysFromFileLean(path string) ([]privval.FilePVKey, error) { + keyJSONBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + var pvKey []privval.FilePVKey + err = cdc.UnmarshalJSON(keyJSONBytes, &pvKey) + if err != nil { + return nil, err + } + + return pvKey, nil +} + +func privValKeysLean(res []crypto.PrivateKey) error { + var pvKL []privval.FilePVKey + for _, pk := range res { + pvKL = append(pvKL, privval.FilePVKey{ + Address: pk.PubKey().Address(), + PubKey: pk.PubKey(), + PrivKey: pk.PrivKey(), + }) + } + pvkBz, err := cdc.MarshalJSONIndent(pvKL, "", " ") + if err != nil { + return err + } + pvFile, err := os.OpenFile(GlobalConfig.PocketConfig.DataDir+FS+GlobalConfig.TendermintConfig.PrivValidatorKey, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return err + } + _, err = pvFile.Write(pvkBz) + if err != nil { + return err + } + return nil +} + +func privValKey(res crypto.PrivateKey) privval.FilePVKey { privValKey := privval.FilePVKey{ Address: res.PubKey().Address(), PubKey: res.PubKey(), @@ -464,7 +570,7 @@ func privValKey(res crypto.PrivateKey) { if err != nil { log2.Fatal(err) } - types.InitPVKeyFile(privValKey) + return privValKey } func nodeKey(res crypto.PrivateKey) { @@ -485,6 +591,41 @@ func nodeKey(res crypto.PrivateKey) { } } +func nodeKeyLean(res crypto.PrivateKey) error { + nodeKey := p2p.NodeKey{ + PrivKey: res.PrivKey(), + } + pvkBz, err := cdc.MarshalJSONIndent(nodeKey, "", " ") + if err != nil { + return err + } + pvFile, err := os.OpenFile(GlobalConfig.PocketConfig.DataDir+FS+GlobalConfig.TendermintConfig.NodeKey, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return err + } + _, err = pvFile.Write(pvkBz) + if err != nil { + return err + } + return nil +} + +func privValStateLean(size int) error { + pvkBz, err := cdc.MarshalJSONIndent(make([]privval.FilePVLastSignState, size), "", " ") + if err != nil { + return err + } + pvFile, err := os.OpenFile(GlobalConfig.PocketConfig.DataDir+FS+GlobalConfig.TendermintConfig.PrivValidatorState, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return err + } + _, err = pvFile.Write(pvkBz) + if err != nil { + return err + } + return nil +} + func privValState() { pvkBz, err := cdc.MarshalJSONIndent(privval.FilePVLastSignState{}, "", " ") if err != nil { @@ -604,7 +745,7 @@ func NewHostedChains(generate bool) *types.HostedBlockchains { // return the map return &types.HostedBlockchains{ M: m, - L: sync.Mutex{}, + L: sync.RWMutex{}, } } @@ -640,7 +781,7 @@ func generateChainsJson(chainsPath string) *types.HostedBlockchains { m[chain.ID] = chain } // return the map - return &types.HostedBlockchains{M: m, L: sync.Mutex{}} + return &types.HostedBlockchains{M: m, L: sync.RWMutex{}} } const ( @@ -771,7 +912,62 @@ func Confirmation(pwd string) bool { } } } +} + +func ReadValidatorPrivateKeyFileLean(filePath string) ([]crypto.PrivateKey, error) { + var arr []privval.PrivateKeyFile + data, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("an error occurred attempting to read the key file: %s", err.Error()) + } + if err := json.Unmarshal(data, &arr); err != nil { + return nil, fmt.Errorf("an error occurred unmarshalling the addresses into json format. Please make sure the input for this is a proper json array with priv_key as key value") + } + + pkFileDeduped := map[privval.PrivateKeyFile]struct{}{} + for _, pk := range arr { + pkFileDeduped[pk] = struct{}{} + } + + var pks []crypto.PrivateKey + for pk, _ := range pkFileDeduped { + bz, err := hex.DecodeString(pk.PrivateKey) + if err != nil { + return nil, fmt.Errorf("an error occurred hex decoding this private key: %s, %s", pk, err.Error()) + } + a, err := crypto.NewPrivateKeyBz(bz) + if err != nil { + return nil, fmt.Errorf("an error occurred creating a private key from hex input: %s, %s", pk, err) + } + pks = append(pks, a) + } + return pks, nil +} +func SetValidatorsFilesLean(keys []crypto.PrivateKey) error { + return SetValidatorsFilesWithPeerLean(keys, keys[0].PublicKey().Address().String()) +} + +func SetValidatorsFilesWithPeerLean(keys []crypto.PrivateKey, address string) error { + resetFilePVLean(GlobalConfig.PocketConfig.DataDir+FS+GlobalConfig.TendermintConfig.PrivValidatorKey, GlobalConfig.PocketConfig.DataDir+FS+GlobalConfig.TendermintConfig.PrivValidatorState, log.NewNopLogger()) + + err := privValKeysLean(keys) + if err != nil { + return err + } + + err = privValStateLean(len(keys)) + if err != nil { + return err + } + for _, k := range keys { + if strings.EqualFold(k.PublicKey().Address().String(), address) { + err := nodeKeyLean(k) + return err + } + } + log2.Println("Could not find " + address + " setting default peering to address: " + keys[0].PublicKey().Address().String()) + return nodeKeyLean(keys[0]) } func SetValidator(address sdk.Address, passphrase string) { @@ -841,6 +1037,17 @@ func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger) { "stateFile", privValStateFile) } +func resetFilePVLean(privValKeyFile, privValStateFile string, logger log.Logger) { + _, err := os.Stat(privValKeyFile) + if err == nil { + _ = os.Remove(privValKeyFile) + _ = os.Remove(privValStateFile) + _ = os.Remove(GlobalConfig.PocketConfig.DataDir + FS + GlobalConfig.TendermintConfig.NodeKey) + } + logger.Info("Reset private validator file", "keyFile", privValKeyFile, + "stateFile", privValStateFile) +} + func removeAddrBook(addrBookFile string, logger log.Logger) { if err := os.Remove(addrBookFile); err == nil { logger.Info("Removed existing address book", "file", addrBookFile) @@ -868,7 +1075,25 @@ func GetDefaultConfig(datadir string) string { return string(jsonbytes) } -func InitAuthToken() { +func InitAuthToken(generateToken bool) { + //Example auth.json located in the config folder + //{ + // "Value": "S6fvg51BOeUO89HafOhF6jPuT", + // "Issued": "2022-06-20T16:06:47.419153-04:00" + //} + + if generateToken { + //default behaviour: generate a new token on each start. + GenerateToken() + } else { + //new: if config is set to false use existing auth.json and do not generate + //User should make sure file exist, else execution will end with error ("cannot open/create auth token json file:"...) + t := GetAuthTokenFromFile() + AuthToken = t + } +} + +func GenerateToken() { var t = sdk.AuthToken{ Value: rand.Str(25), Issued: time.Now(), @@ -881,7 +1106,7 @@ func InitAuthToken() { jsonFile, err := os.OpenFile(configFilepath, os.O_RDWR|os.O_CREATE, os.ModePerm) if err != nil { - log2.Fatalf("canot open/create auth token json file: " + err.Error()) + log2.Fatalf("cannot open/create auth token json file: " + err.Error()) } err = jsonFile.Truncate(0) @@ -907,7 +1132,7 @@ func GetAuthTokenFromFile() sdk.AuthToken { defer jsonFile.Close() if _, err := os.Stat(configFilepath); err == nil { - jsonFile, err = os.OpenFile(configFilepath, os.O_RDWR, os.ModePerm) + jsonFile, err = os.OpenFile(configFilepath, os.O_RDONLY, os.ModePerm) if err != nil { log2.Fatalf("cannot open config json file: " + err.Error()) } diff --git a/app/query.go b/app/query.go index 880ff5db..deb684c6 100644 --- a/app/query.go +++ b/app/query.go @@ -158,6 +158,16 @@ func (app PocketCoreApp) QueryAccount(addr string, height int64) (res *exported. return &acc, nil } +func (app PocketCoreApp) QueryAccounts(height int64, page, perPage int) (res Page, err error) { + ctx, err := app.NewContext(height) + if err != nil { + return + } + page, perPage = checkPagination(page, perPage) + accs := app.accountKeeper.GetAllAccountsExport(ctx) + return paginate(page, perPage, accs, 10000) +} + func (app PocketCoreApp) QueryNodes(height int64, opts nodesTypes.QueryValidatorsParams) (res Page, err error) { ctx, err := app.NewContext(height) if err != nil { @@ -512,17 +522,20 @@ func (app PocketCoreApp) HandleDispatch(header pocketTypes.SessionHeader) (res * func (app PocketCoreApp) HandleRelay(r pocketTypes.Relay) (res *pocketTypes.RelayResponse, dispatch *pocketTypes.DispatchResponse, err error) { ctx, err := app.NewContext(app.LastBlockHeight()) + if err != nil { return nil, nil, err } - status, err := app.pocketKeeper.TmNode.Status() - if err != nil { - return nil, nil, fmt.Errorf("pocket node is unable to retrieve status from tendermint node, cannot service in this state") + status, sErr := app.pocketKeeper.TmNode.ConsensusReactorStatus() + if sErr != nil { + return nil, nil, fmt.Errorf("pocket node is unable to retrieve synced status from tendermint node, cannot service in this state") } - if status.SyncInfo.CatchingUp { + + if status.IsCatchingUp { return nil, nil, fmt.Errorf("pocket node is currently syncing to the blockchain, cannot service in this state") } + res, err = app.pocketKeeper.HandleRelay(ctx, r) var err1 error if err != nil && pocketTypes.ErrorWarrantsDispatch(err) { diff --git a/app/query_test.go b/app/query_test.go index 8877087f..dd95cd8f 100644 --- a/app/query_test.go +++ b/app/query_test.go @@ -795,7 +795,7 @@ func TestQueryRelay(t *testing.T) { ApplicationPubKey: aat.ApplicationPublicKey, Chain: relay.Proof.Blockchain, SessionBlockHeight: relay.Proof.SessionBlockHeight, - }, types.RelayEvidence, sdk.NewInt(10000)) + }, types.RelayEvidence, sdk.NewInt(10000), types.GlobalEvidenceCache) assert.Nil(t, err) assert.NotNil(t, inv) assert.Equal(t, inv.NumOfProofs, int64(1)) @@ -808,6 +808,106 @@ func TestQueryRelay(t *testing.T) { } } +func TestQueryRelayMultipleNodes(t *testing.T) { + const headerKey = "foo" + const headerVal = "bar" + + expectedRequest := `"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64` + expectedResponse := "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad" + tt := []struct { + name string + memoryNodeFn func(t *testing.T, genesisState []byte, validators []crypto.PrivateKey) (tendermint *node.Node, keybase keys.Keybase, cleanup func()) + *upgrades + }{ + {name: "query relay multiple validators amino", memoryNodeFn: NewInMemoryTendermintNodeAminoWithValidators, upgrades: &upgrades{codecUpgrade: codecUpgrade{false, 7000}}}, + {name: "query relay multiple validators proto", memoryNodeFn: NewInMemoryTendermintNodeProtoWithValidators, upgrades: &upgrades{codecUpgrade: codecUpgrade{true, 2}}}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + sdk.VbCCache = sdk.NewCache(1) + if tc.upgrades != nil { // NOTE: Use to perform neccesary upgrades for test + codec.UpgradeHeight = tc.codecUpgrade.height + _ = memCodecMod(tc.codecUpgrade.upgradeMod) + } + time.Sleep(time.Second * 2) + genBz, validators, servicers, app := generateGenesis(5, 5, 1) + validators = append(validators, servicers...) + // setup relay endpoint + gock.New(sdk.PlaceholderURL). + Post(""). + BodyString(expectedRequest). + MatchHeader(headerKey, headerVal). + Reply(200). + BodyString(expectedResponse) + _, _, cleanup := tc.memoryNodeFn(t, genBz, validators) + // setup AAT + aat := types.AAT{ + Version: "0.0.1", + ApplicationPublicKey: app[0].PublicKey().RawString(), + ClientPublicKey: app[0].PublicKey().RawString(), + ApplicationSignature: "", + } + sig, err := app[0].Sign(aat.Hash()) + if err != nil { + panic(err) + } + aat.ApplicationSignature = hex.EncodeToString(sig) + payload := types.Payload{ + Data: expectedRequest, + Headers: map[string]string{headerKey: headerVal}, + } + _, stopCli, evtChan := subscribeTo(t, tmTypes.EventNewBlock) + <-evtChan // Wait for block + // setup relay + for _, v := range validators { + relay := types.Relay{ + Payload: payload, + Meta: types.RelayMeta{BlockHeight: 5}, // todo race condition here + Proof: types.RelayProof{ + Entropy: 32598345349034509, + SessionBlockHeight: 1, + ServicerPubKey: v.PublicKey().RawString(), + Blockchain: sdk.PlaceholderHash, + Token: aat, + Signature: "", + }, + } + relay.Proof.RequestHash = relay.RequestHashString() + sig, err = app[0].Sign(relay.Proof.Hash()) + if err != nil { + panic(err) + } + relay.Proof.Signature = hex.EncodeToString(sig) + res, _, err := PCA.HandleRelay(relay) + assert.Nil(t, err, err) + assert.Equal(t, expectedResponse, res.Response) + gock.New(sdk.PlaceholderURL). + Post(""). + BodyString(expectedRequest). + Reply(200). + BodyString(expectedResponse) + + validatorAddress := sdk.GetAddress(v.PublicKey()) + node, nodeErr := types.GetPocketNodeByAddress(&validatorAddress) + + assert.Nil(t, nodeErr) + inv, err := types.GetEvidence(types.SessionHeader{ + ApplicationPubKey: aat.ApplicationPublicKey, + Chain: relay.Proof.Blockchain, + SessionBlockHeight: relay.Proof.SessionBlockHeight, + }, types.RelayEvidence, sdk.NewInt(10000), node.EvidenceStore) + assert.Nil(t, err) + assert.NotNil(t, inv) + assert.Equal(t, inv.NumOfProofs, int64(1)) + } + cleanup() + stopCli() + gock.Off() + return + }) + } +} + func TestQueryDispatch(t *testing.T) { tt := []struct { name string @@ -954,3 +1054,29 @@ func TestQueryNonExistingAccountBalance(t *testing.T) { }) } } + +func TestQueryAccounts(t *testing.T) { + tt := []struct { + name string + memoryNodeFn func(t *testing.T, genesisState []byte) (tendermint *node.Node, keybase keys.Keybase, cleanup func()) + *upgrades + }{ + {name: "query accounts amino", memoryNodeFn: NewInMemoryTendermintNodeAmino, upgrades: &upgrades{codecUpgrade: codecUpgrade{false, 7000}}}, + {name: "query accounts proto", memoryNodeFn: NewInMemoryTendermintNodeProto, upgrades: &upgrades{codecUpgrade: codecUpgrade{true, 2}}}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + if tc.upgrades != nil { // NOTE: Use to perform neccesary upgrades for test + codec.UpgradeHeight = tc.upgrades.codecUpgrade.height + _ = memCodecMod(tc.upgrades.codecUpgrade.upgradeMod) + } + _, _, cleanup := tc.memoryNodeFn(t, oneAppTwoNodeGenesis()) + got, err := PCA.QueryAccounts(PCA.LastBlockHeight(), 1, 1) + assert.Nil(t, err) + assert.NotNil(t, got) + assert.NotEqual(t, 1, got.Total) + + cleanup() + }) + } +} diff --git a/app/tendermint.go b/app/tendermint.go index 5a26679a..5bcc67c4 100644 --- a/app/tendermint.go +++ b/app/tendermint.go @@ -1,11 +1,8 @@ package app import ( + "errors" "github.com/pokt-network/pocket-core/codec" - "io" - "os" - "path/filepath" - sdk "github.com/pokt-network/pocket-core/types" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/libs/log" @@ -14,10 +11,86 @@ import ( pvm "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" dbm "github.com/tendermint/tm-db" + "io" + "os" + "path/filepath" + "time" ) type AppCreator func(log.Logger, dbm.DB, io.Writer) *PocketCoreApp +// loadFilePVWithConfig returns an array of pvkeys & last sign state for leanpokt or constructs an array of pv keys & lastsignstate if using pre leanpokt to maintain backwards compability +func loadFilePVWithConfig(c config) *pvm.FilePVLean { + privValPath := c.TmConfig.PrivValidatorKeyFile() + privStatePath := c.TmConfig.PrivValidatorStateFile() + if GlobalConfig.PocketConfig.LeanPocket { + return pvm.LoadOrGenFilePVLean(privValPath, privStatePath) + } + globalFilePV := pvm.LoadOrGenFilePV(privValPath, privStatePath) + return &pvm.FilePVLean{ + Keys: []pvm.FilePVKey{globalFilePV.Key}, + LastSignStates: []pvm.FilePVLastSignState{globalFilePV.LastSignState}, + KeyFilepath: privValPath, + StateFilepath: privStatePath, + } +} + +func ReloadValidatorKeys(c config, tmNode *node.Node) error { + + keys, err := ReadValidatorPrivateKeyFileLean(GlobalConfig.PocketConfig.GetLeanPocketUserKeyFilePath()) + if err != nil { + return err + } + + if len(keys) == 0 { + return errors.New("user key file contained zero validator keys") + } + + err = SetValidatorsFilesLean(keys) + if err != nil { + return err + } + + validators := loadFilePVWithConfig(c) + tmNode.ConsensusState().SetPrivValidators(validators) // set new lean nodes + + err = InitNodesLean(c.Logger) // initialize lean nodes + if err != nil { + return err + } + + return nil +} + +// hotReloadValidatorsLean - spins off a goroutine that reads from validator files +// TODO: Flesh out hot reloading (removing/adding) lean nodes +func hotReloadValidatorsLean(c config, tmNode *node.Node) { + userKeysPath := GlobalConfig.PocketConfig.GetLeanPocketUserKeyFilePath() + stat, err := os.Stat(userKeysPath) + if err != nil { + c.Logger.Error("Cannot find user provided key file to hot reload") + return + } + for { + time.Sleep(time.Second * 5) + c.Logger.Info("Checking for hot reload") + newStat, err := os.Stat(userKeysPath) + if err != nil { + continue + } + if newStat.Size() != stat.Size() || stat.ModTime() != newStat.ModTime() { + c.Logger.Info("Detected change in files, hot reloading validators") + err := ReloadValidatorKeys(c, tmNode) + if err != nil { + c.Logger.Error("Failed to hot reload validators") + continue + } + c.Logger.Info("Successfully hot reloaded validators") + stat = newStat + } + } +} + func NewClient(c config, creator AppCreator) (*node.Node, *PocketCoreApp, error) { // setup the database appDB, err := OpenApplicationDB(GlobalConfig) @@ -35,19 +108,21 @@ func NewClient(c config, creator AppCreator) (*node.Node, *PocketCoreApp, error) if err != nil { return nil, nil, err } - // load the node key + nodeKey, err := p2p.LoadOrGenNodeKey(c.TmConfig.NodeKeyFile()) if err != nil { return nil, nil, err } + // upgrade the privVal file + app := creator(c.Logger, appDB, traceWriter) PCA = app // create & start tendermint node tmNode, err := node.NewNode(app, c.TmConfig, codec.GetCodecUpgradeHeight(), - pvm.LoadOrGenFilePV(c.TmConfig.PrivValidatorKeyFile(), c.TmConfig.PrivValidatorStateFile()), + loadFilePVWithConfig(c), nodeKey, proxy.NewLocalClientCreator(app), transactionIndexer, @@ -56,10 +131,16 @@ func NewClient(c config, creator AppCreator) (*node.Node, *PocketCoreApp, error) node.DefaultMetricsProvider(c.TmConfig.Instrumentation), c.Logger.With("module", "node"), ) + if err != nil { return nil, nil, err } + // TODO: Flesh out hotreloading(removing/adding) lean nodes + //if GlobalConfig.PocketConfig.LeanPocket { + // go hotReloadValidatorsLean(c, tmNode) + //} + return tmNode, app, nil } @@ -101,14 +182,14 @@ type config struct { TraceWriter string } -func modifyPrivValidatorsFile(config *cfg.Config, rollbackHeight int64) error { - var sig []byte - filePv := pvm.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) - filePv.LastSignState.Height = rollbackHeight - filePv.LastSignState.Round = 0 - filePv.LastSignState.Step = 0 - filePv.LastSignState.Signature = sig - filePv.LastSignState.SignBytes = nil - filePv.Save() - return nil -} +//func modifyPrivValidatorsFile(config *cfg.Config, rollbackHeight int64) error { +// var sig []byte +// filePv := pvm.LoadOrGenFilePVLean(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) +// filePv.LastSignState.Height = rollbackHeight +// filePv.LastSignState.Round = 0 +// filePv.LastSignState.Step = 0 +// filePv.LastSignState.Signature = sig +// filePv.LastSignState.SignBytes = nil +// filePv.Save() +// return nil +//} diff --git a/app/tx_test.go b/app/tx_test.go index 262f6694..1a8bb5c5 100644 --- a/app/tx_test.go +++ b/app/tx_test.go @@ -26,8 +26,7 @@ import ( ) func TestMain(m *testing.M) { - pocketTypes.ClearSessionCache() - pocketTypes.ClearEvidence() + pocketTypes.CleanPocketNodes() sdk.InitCtxCache(1) m.Run() } @@ -915,6 +914,99 @@ func TestChangeParamsSimpleTx(t *testing.T) { } } +func TestChangeParamsMaxBlocksizeBeforeActivationHeight(t *testing.T) { + + tt := []struct { + name string + memoryNodeFn func(t *testing.T, genesisState []byte) (tendermint *node.Node, keybase keys.Keybase, cleanup func()) + *upgrades + }{ + {name: "change MaxBlocksize parameter before activation height", memoryNodeFn: NewInMemoryTendermintNodeProto, upgrades: &upgrades{codecUpgrade: codecUpgrade{true, 2}}}, // TODO: FULL PROTO SCENARIO + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + codec.TestMode = -2 + if tc.upgrades != nil { // NOTE: Use to perform neccesary upgrades for test + codec.UpgradeHeight = tc.upgrades.codecUpgrade.height + _ = memCodecMod(tc.upgrades.codecUpgrade.upgradeMod) + } + resetTestACL() + _, kb, cleanup := tc.memoryNodeFn(t, oneAppTwoNodeGenesis()) + time.Sleep(1 * time.Second) + cb, err := kb.GetCoinbase() + assert.Nil(t, err) + _, err = kb.List() + assert.Nil(t, err) + _, _, evtChan := subscribeTo(t, tmTypes.EventNewBlock) + <-evtChan // Wait for block + //Before Activation of the parameter ACL do not exist and the value and parameter should be 0 or nil + firstquery, _ := PCA.QueryParam(PCA.LastBlockHeight(), "pocketcore/BlockByteSize") + assert.Equal(t, "0", firstquery.Value) + memCli, stopCli, evtChan := subscribeTo(t, tmTypes.EventTx) + //Tx wont modify anything as ACL is not configured (Txresult should be gov code 5) + tx, err := gov.ChangeParamsTx(memCodec(), memCli, kb, cb.GetAddress(), "pocketcore/BlockByteSize", 9000000, "test", 10000, false) + assert.Nil(t, err) + assert.NotNil(t, tx) + select { + case _ = <-evtChan: + //fmt.Println(res) + assert.Nil(t, err) + o, _ := PCA.QueryParam(PCA.LastBlockHeight(), "pocketcore/BlockByteSize") + //value should be equal to the first query of the param + assert.Equal(t, firstquery.Value, o.Value) + cleanup() + stopCli() + } + }) + } +} + +func TestChangeParamsMaxBlocksizeAfterActivationHeight(t *testing.T) { + + tt := []struct { + name string + memoryNodeFn func(t *testing.T, genesisState []byte) (tendermint *node.Node, keybase keys.Keybase, cleanup func()) + *upgrades + }{ + {name: "change MaxBlocksize parameter past activation height", memoryNodeFn: NewInMemoryTendermintNodeProto, upgrades: &upgrades{codecUpgrade: codecUpgrade{true, 2}}}, // TODO: FULL PROTO SCENARIO + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + codec.TestMode = -2 + codec.UpgradeFeatureMap[codec.BlockSizeModifyKey] = tc.upgrades.codecUpgrade.height + if tc.upgrades != nil { // NOTE: Use to perform neccesary upgrades for test + codec.UpgradeHeight = tc.upgrades.codecUpgrade.height + _ = memCodecMod(tc.upgrades.codecUpgrade.upgradeMod) + } + resetTestACL() + _, kb, cleanup := tc.memoryNodeFn(t, oneAppTwoNodeGenesis()) + time.Sleep(1 * time.Second) + cb, err := kb.GetCoinbase() + assert.Nil(t, err) + _, err = kb.List() + assert.Nil(t, err) + _, _, evtChan := subscribeTo(t, tmTypes.EventNewBlock) + <-evtChan // Wait for block + //After Activation of the parameter ACL should be created(allowing modifying the value) and parameter should have default value of 4000000 + o, _ := PCA.QueryParam(PCA.LastBlockHeight(), "pocketcore/BlockByteSize") + assert.Equal(t, "4000000", o.Value) + memCli, stopCli, evtChan := subscribeTo(t, tmTypes.EventTx) + tx, err := gov.ChangeParamsTx(memCodec(), memCli, kb, cb.GetAddress(), "pocketcore/BlockByteSize", 9000000, "test", 10000, false) + assert.Nil(t, err) + assert.NotNil(t, tx) + select { + case _ = <-evtChan: + //fmt.Println(res) + assert.Nil(t, err) + o, _ := PCA.QueryParam(PCA.LastBlockHeight(), "pocketcore/BlockByteSize") + assert.Equal(t, "9000000", o.Value) + cleanup() + stopCli() + } + }) + } +} + func TestUpgrade(t *testing.T) { tt := []struct { name string @@ -1050,12 +1142,11 @@ func TestClaimAminoTx(t *testing.T) { ApplicationPubKey: appPrivateKey.PublicKey().RawString(), Chain: sdk.PlaceholderHash, SessionBlockHeight: 1, - }, pocketTypes.RelayEvidence, proof, sdk.NewInt(1000000)) + }, pocketTypes.RelayEvidence, proof, sdk.NewInt(1000000), pocketTypes.GlobalEvidenceCache) assert.Nil(t, err) } _, _, evtChan := subscribeTo(t, tmTypes.EventTx) res := <-evtChan - fmt.Println(res) if res.Events["message.action"][0] != pocketTypes.EventTypeClaim { t.Fatal("claim message was not received first") } @@ -1125,7 +1216,7 @@ func TestClaimProtoTx(t *testing.T) { ApplicationPubKey: appPrivateKey.PublicKey().RawString(), Chain: sdk.PlaceholderHash, SessionBlockHeight: 1, - }, pocketTypes.RelayEvidence, proof, sdk.NewInt(1000000)) + }, pocketTypes.RelayEvidence, proof, sdk.NewInt(1000000), pocketTypes.GlobalEvidenceCache) assert.Nil(t, err) } _, _, evtChan := subscribeTo(t, tmTypes.EventTx) @@ -1163,7 +1254,7 @@ func TestAminoClaimTxChallenge(t *testing.T) { challenges := NewValidChallengeProof(t, keys, 5) _, _, cleanup := tc.memoryNodeFn(t, genBz) for _, c := range challenges { - c.Store(sdk.NewInt(1000000)) + c.Store(sdk.NewInt(1000000), pocketTypes.GlobalEvidenceCache) } _, _, evtChan := subscribeTo(t, tmTypes.EventTx) res := <-evtChan // Wait for tx @@ -1200,7 +1291,7 @@ func TestProtoClaimTxChallenge(t *testing.T) { challenges := NewValidChallengeProof(t, keys, 5) _, _, cleanup := tc.memoryNodeFn(t, genBz) for _, c := range challenges { - c.Store(sdk.NewInt(1000000)) + c.Store(sdk.NewInt(1000000), pocketTypes.GlobalEvidenceCache) } _, _, evtChan := subscribeTo(t, tmTypes.EventTx) res := <-evtChan // Wait for tx diff --git a/codec/codec.go b/codec/codec.go index 965e0c36..7b085112 100644 --- a/codec/codec.go +++ b/codec/codec.go @@ -43,6 +43,9 @@ const ( NonCustodialUpdateKey = "NCUST" TxCacheEnhancementKey = "REDUP" ReplayBurnKey = "REPBR" + BlockSizeModifyKey = "BLOCK" + RSCALKey = "RSCAL" + VEDITKey = "VEDIT" ) func GetCodecUpgradeHeight() int64 { diff --git a/crypto/keys.go b/crypto/keys.go index 404419e5..81ac82d4 100644 --- a/crypto/keys.go +++ b/crypto/keys.go @@ -69,6 +69,14 @@ type MultiSig interface { GetSignatureByIndex(i int) (sig []byte, found bool) } +func NewPrivateKey(hexString string) (PrivateKey, error) { + b, err := hex.DecodeString(hexString) + if err != nil { + return nil, err + } + return NewPrivateKeyBz(b) +} + func NewPublicKey(hexString string) (PublicKey, error) { b, err := hex.DecodeString(hexString) if err != nil { diff --git a/doc/releases/changelog.md b/doc/releases/changelog.md index 52d74901..299372b5 100644 --- a/doc/releases/changelog.md +++ b/doc/releases/changelog.md @@ -1,5 +1,13 @@ # Changelog ======= +## Unreleased +- Mempool Refinements : + - Tx max life in mempool is 2 blocks, after that + - add validation for edge case on cache equals to 0. +- backport tendermint #6068 (terminate broadcastEvidenceRoutine when peer is stopped) +- Add new rpc endpoint /query/accounts +- New config.json option to enable/disable authToken generation on start (auth.json) + ## RC-0.8.3 - Add config.json check for Indexer and Mempool. diff --git a/doc/specs/rpc-spec.yaml b/doc/specs/rpc-spec.yaml index 4a1132f5..c716e36b 100644 --- a/doc/specs/rpc-spec.yaml +++ b/doc/specs/rpc-spec.yaml @@ -318,6 +318,30 @@ paths: $ref: '#/components/schemas/Account' '400': description: Failed to retrieve the account + /query/accounts: + post: + tags: + - query + requestBody: + description: 'Request accounts at the specified height, height = 0 is used as latest' + content: + application/json: + schema: + $ref: '#/components/schemas/QueryPaginatedHeightParams' + example: + height: 100 + page: 1 + per_page: 30 + required: true + responses: + '200': + description: Returns accounts at the specified height + content: + application/json: + schema: + $ref: '#/components/schemas/QueryAccountsResponse' + '400': + description: Failed to retrieve the account /query/accounttxs: post: tags: @@ -1356,8 +1380,45 @@ paths: message: type: string description: The error msg. + /private/nodes: + post: + tags: + - private + parameters: + - in: query + name: authtoken + schema: + type: string + description: Current Authorization Token from pocket core. + responses: + '200': + description: Return the json array of pocket core client's current set validators' addresses + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LocalNode' + '401': + description: Wrong Authtoken + content: + application/json: + schema: + type: object + properties: + code: + type: integer + description: The error code. + message: + type: string + description: The error msg. components: schemas: + LocalNode: + type: object + properties: + address: + type: string Chain: type: object properties: @@ -2140,6 +2201,21 @@ components: type: integer format: int64 description: maximum amount of pages + QueryAccountsResponse: + type: object + properties: + result: + type: array + items: + $ref: '#/components/schemas/Account' + page: + type: integer + format: int64 + description: current page + total_pages: + type: integer + format: int64 + description: maximum amount of pages QueryRawTXRequest: type: object properties: @@ -2334,6 +2410,18 @@ components: properties: transaction: $ref: '#/components/schemas/Transaction' + QueryPaginatedHeightParams: + type: object + properties: + height: + type: integer + format: int64 + page: + type: integer + format: int64 + per_page: + type: integer + format: int64 QueryPaginatedHeightAndAddrParams: type: object properties: diff --git a/go.mod b/go.mod index 2550e2e9..01745b13 100644 --- a/go.mod +++ b/go.mod @@ -74,6 +74,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/tendermint/tendermint => github.com/pokt-network/tendermint v0.32.11-0.20220420160934-de1729fc7dba +//replace github.com/tendermint/tendermint => github.com/pokt-network/tendermint v0.32.11-0.20220420160934-de1729fc7dba +replace github.com/tendermint/tendermint => ../tendermint replace github.com/tendermint/tm-db => github.com/pokt-network/tm-db v0.5.2-0.20220118210553-9b2300f289ba diff --git a/go.sum b/go.sum index f53c3c9b..b8e2854e 100644 --- a/go.sum +++ b/go.sum @@ -482,8 +482,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pokt-network/tendermint v0.32.11-0.20220420160934-de1729fc7dba h1:Fc7cRJrE7LhMf9dOFnI+EystuncZRpxpUKaxg/K8kd0= -github.com/pokt-network/tendermint v0.32.11-0.20220420160934-de1729fc7dba/go.mod h1:jpUvUU2sHmJ6LT0Dz6DYwq2yceCD566zjspJxAtydDg= github.com/pokt-network/tm-db v0.5.2-0.20220118210553-9b2300f289ba h1:u2yqCF5l3Kaz8fra6A023nx9Bsfj+KCm2t5xEO4weOo= github.com/pokt-network/tm-db v0.5.2-0.20220118210553-9b2300f289ba/go.mod h1:7TGWH92hEbEFVzNJxGi8Ka0O+OohcLLjZCxco/xMrWY= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -1026,7 +1024,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/types/config.go b/types/config.go index cc9969f3..2cac94f5 100644 --- a/types/config.go +++ b/types/config.go @@ -3,6 +3,8 @@ package types import ( "github.com/tendermint/tendermint/config" db "github.com/tendermint/tm-db" + "path" + "sync" "time" ) @@ -44,6 +46,13 @@ type PocketConfig struct { Cache bool `json:"-"` IavlCacheSize int64 `json:"iavl_cache_size"` ChainsHotReload bool `json:"chains_hot_reload"` + GenerateTokenOnStart bool `json:"generate_token_on_start"` + LeanPocket bool `json:"lean_pocket"` + LeanPocketUserKeyFileName string `json:"lean_pocket_user_key_file"` +} + +func (c PocketConfig) GetLeanPocketUserKeyFilePath() string { + return path.Join(c.DataDir, c.LeanPocketUserKeyFileName) } type Config struct { @@ -61,6 +70,9 @@ const ( DefaultKeybaseName = "pocket-keybase" DefaultPVKName = "priv_val_key.json" DefaultPVSName = "priv_val_state.json" + DefaultPVKNameLean = "priv_val_key_lean.json" + DefaultPVSNameLean = "priv_val_state_lean.json" + DefaultNKNameLean = "node_key_lean.json" DefaultNKName = "node_key.json" DefaultChainsName = "chains.json" DefaultGenesisName = "genesis.json" @@ -97,6 +109,9 @@ const ( AuthFileName = "auth.json" DefaultIavlCacheSize = 5000000 DefaultChainHotReload = false + DefaultGenerateTokenOnStart = true + DefaultLeanPocket = false + DefaultLeanPocketUserKeyFileName = "lean_nodes_keys.json" ) func DefaultConfig(dataDir string) Config { @@ -129,6 +144,9 @@ func DefaultConfig(dataDir string) Config { DisableTxEvents: DefaultRPCDisableTransactionEvents, IavlCacheSize: DefaultIavlCacheSize, ChainsHotReload: DefaultChainHotReload, + GenerateTokenOnStart: DefaultGenerateTokenOnStart, + LeanPocket: DefaultLeanPocket, + LeanPocketUserKeyFileName: DefaultLeanPocketUserKeyFileName, }, } c.TendermintConfig.LevelDBOptions = config.DefaultLevelDBOpts() diff --git a/types/decimal.go b/types/decimal.go index aa6053a2..ea503064 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -424,6 +424,32 @@ func (d BigDec) Power(power uint64) BigDec { return d.Mul(tmp) } +// returns the power for a Decimal power with a known denominator +func (d BigDec) FracPow(power BigDec, denominator int64) BigDec { + //A ^ (B/C) is the same as CthRoot(A ^ B). + // d^0 = 1 + + if power.IsZero() { + return OneDec() + } + + C := denominator + + //Multiply out B/C to isolate B and truncate B to denominator + B := NewInt(power.Mul(NewDec(denominator)).RoundInt64()).ToDec() + + // take CthRoot of A + CthA, err := d.ApproxRoot(uint64(C)) + + if err != nil { + return OneDec() + } + + // take power to B of CthRootA + output := CthA.Power(uint64(B.RoundInt64())) + return output +} + // ApproxSqrt is a wrapper around ApproxRoot for the common special case // of finding the square root of a number. It returns -(sqrt(abs(d)) if input is negative. func (d BigDec) ApproxSqrt() (BigDec, error) { diff --git a/types/int.go b/types/int.go index c84f4f4c..b5d8f80e 100644 --- a/types/int.go +++ b/types/int.go @@ -36,6 +36,8 @@ func mod(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Mod(i, i2) } func neg(i *big.Int) *big.Int { return new(big.Int).Neg(i) } +func exp(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Exp(i, i2, nil) } + func min(i *big.Int, i2 *big.Int) *big.Int { if i.Cmp(i2) == 1 { return new(big.Int).Set(i2) @@ -304,6 +306,10 @@ func MaxInt(i, i2 BigInt) BigInt { return BigInt{max(i.BigInt(), i2.BigInt())} } +func (i BigInt) Pow(i2 BigInt) (res BigInt) { + return BigInt{exp(i.BigInt(), i2.BigInt())} +} + // Human readable string func (i BigInt) String() string { return i.i.String() diff --git a/types/module/module.go b/types/module/module.go index 05e1528b..dadd6009 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -108,6 +108,7 @@ type AppModule interface { BeginBlock(sdk.Ctx, abci.RequestBeginBlock) EndBlock(sdk.Ctx, abci.RequestEndBlock) []abci.ValidatorUpdate UpgradeCodec(sdk.Ctx) + ConsensusParamsUpdate(ctx sdk.Ctx) *abci.ConsensusParams } //___________________________ @@ -116,6 +117,10 @@ type GenesisOnlyAppModule struct { AppModuleGenesis } +func (gam GenesisOnlyAppModule) ConsensusParamsUpdate(ctx sdk.Ctx) *abci.ConsensusParams { + return &abci.ConsensusParams{} +} + func (gam GenesisOnlyAppModule) UpgradeCodec(sdk.Ctx) {} // NewGenesisOnlyAppModule creates a new GenesisOnlyAppModule object @@ -294,6 +299,7 @@ func (m *Manager) EndBlock(ctx sdk.Ctx, req abci.RequestEndBlock) abci.ResponseE defer sdk.TimeTrack(time.Now()) ctx = ctx.WithEventManager(sdk.NewEventManager()) validatorUpdates := []abci.ValidatorUpdate{} + UpdateToApply := &abci.ConsensusParams{} for _, moduleName := range m.OrderEndBlockers { moduleValUpdates := m.Modules[moduleName].EndBlock(ctx, req) @@ -307,10 +313,25 @@ func (m *Manager) EndBlock(ctx sdk.Ctx, req abci.RequestEndBlock) abci.ResponseE validatorUpdates = moduleValUpdates } + //Currently its only a non empty struct on pocketcore module + consensusParamUpdate := m.Modules[moduleName].ConsensusParamsUpdate(ctx) + if !consensusParamUpdate.Equal(&abci.ConsensusParams{}) { + UpdateToApply = consensusParamUpdate + } } + + //not adding empty struct saves us from updating consensus params every block if there are no updates. + if UpdateToApply.Equal(&abci.ConsensusParams{}) { + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + Events: ctx.EventManager().ABCIEvents(), + } + } + return abci.ResponseEndBlock{ - ValidatorUpdates: validatorUpdates, - Events: ctx.EventManager().ABCIEvents(), + ValidatorUpdates: validatorUpdates, + Events: ctx.EventManager().ABCIEvents(), + ConsensusParamUpdates: UpdateToApply, } } diff --git a/types/param.go b/types/param.go index 68646f7f..23813229 100644 --- a/types/param.go +++ b/types/param.go @@ -100,6 +100,11 @@ func (s Subspace) Get(ctx Ctx, key []byte, ptr interface{}) { ctx.Logger().Error("error getting a value from a key in the subspace, could be an empty subspace:", err.Error()) return } + if len(bz) == 0 { + //if bytes are 0 we return + return + } + err = s.cdc.UnmarshalJSON(bz, ptr) if err != nil { ctx.Logger().Error("error unmarshalling from the subspace, could be an empty subspace", err.Error()) diff --git a/x/apps/module.go b/x/apps/module.go index d072b7f1..77a9ca46 100644 --- a/x/apps/module.go +++ b/x/apps/module.go @@ -52,6 +52,10 @@ type AppModule struct { keeper keeper.Keeper } +func (am AppModule) ConsensusParamsUpdate(ctx sdk.Ctx) *abci.ConsensusParams { + return &abci.ConsensusParams{} +} + // NewAppModule creates a new AppModule object func NewAppModule(keeper keeper.Keeper) AppModule { return AppModule{ diff --git a/x/auth/module.go b/x/auth/module.go index 06ad32b9..402ec8e0 100644 --- a/x/auth/module.go +++ b/x/auth/module.go @@ -51,6 +51,10 @@ type AppModule struct { accountKeeper keeper.Keeper } +func (am AppModule) ConsensusParamsUpdate(ctx sdk.Ctx) *abci.ConsensusParams { + return &abci.ConsensusParams{} +} + // NewAppModule creates a new AppModule object func NewAppModule(accountKeeper keeper.Keeper) AppModule { return AppModule{ diff --git a/x/gov/keeper/params.go b/x/gov/keeper/params.go index f42c5071..385d4826 100644 --- a/x/gov/keeper/params.go +++ b/x/gov/keeper/params.go @@ -1,6 +1,7 @@ package keeper import ( + "github.com/pokt-network/pocket-core/codec" sdk "github.com/pokt-network/pocket-core/types" "github.com/pokt-network/pocket-core/x/gov/types" ) @@ -39,3 +40,7 @@ func (k Keeper) GetUpgrade(ctx sdk.Ctx) (res types.Upgrade) { k.paramstore.Get(ctx, types.UpgradeKey, &res) return } + +func (k Keeper) GetCodec() *codec.Codec { + return k.cdc +} diff --git a/x/gov/module.go b/x/gov/module.go index e2c02669..1bcb4997 100644 --- a/x/gov/module.go +++ b/x/gov/module.go @@ -14,6 +14,7 @@ import ( ) var ( + _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} ) @@ -46,6 +47,10 @@ type AppModule struct { keeper keeper.Keeper } +func (am AppModule) ConsensusParamsUpdate(ctx sdk.Ctx) *abci.ConsensusParams { + return &abci.ConsensusParams{} +} + // NewAppModule creates a new AppModule object func NewAppModule(keeper keeper.Keeper) AppModule { return AppModule{ @@ -112,6 +117,26 @@ func (am AppModule) ExportGenesis(ctx sdk.Ctx) json.RawMessage { // module begin-block func (am AppModule) BeginBlock(ctx sdk.Ctx, req abci.RequestBeginBlock) { + + if am.keeper.GetCodec().IsOnNamedFeatureActivationHeight(ctx.BlockHeight(), codec.BlockSizeModifyKey) { + gParams := am.keeper.GetParams(ctx) + //on the height we get the ACL and insert the key + gParams.ACL.SetOwner("pocketcore/BlockByteSize", am.keeper.GetDAOOwner(ctx)) + //update params + am.keeper.SetParams(ctx, gParams) + } + + if am.keeper.GetCodec().IsOnNamedFeatureActivationHeight(ctx.BlockHeight(), codec.RSCALKey) { + params := am.keeper.GetParams(ctx) + //on the height we get the ACL and insert the key + params.ACL.SetOwner("pos/ServicerStakeFloorMultiplier", am.keeper.GetDAOOwner(ctx)) + params.ACL.SetOwner("pos/ServicerStakeWeightMultiplier", am.keeper.GetDAOOwner(ctx)) + params.ACL.SetOwner("pos/ServicerStakeWeightCeiling", am.keeper.GetDAOOwner(ctx)) + params.ACL.SetOwner("pos/ServicerStakeFloorMultiplierExponent", am.keeper.GetDAOOwner(ctx)) + //update params + am.keeper.SetParams(ctx, params) + } + u := am.keeper.GetUpgrade(ctx) if ctx.AppVersion() < u.Version && ctx.BlockHeight() >= u.UpgradeHeight() && ctx.BlockHeight() != 0 { ctx.Logger().Error("MUST UPGRADE TO NEXT VERSION: ", u.Version) diff --git a/x/gov/types/acl.go b/x/gov/types/acl.go index 130c02e1..f1adbe1b 100644 --- a/x/gov/types/acl.go +++ b/x/gov/types/acl.go @@ -36,15 +36,7 @@ func (a ACL) Validate(adjacencyMap map[string]bool) error { return ErrInvalidACL(ModuleName, fmt.Errorf("the address provided for: %s is nil", key)) } } - var unOwnedParams []string - for key, val := range adjacencyMap { - if !val { - unOwnedParams = append(unOwnedParams, key) - } - } - if len(unOwnedParams) != 0 { - return ErrInvalidACL(ModuleName, fmt.Errorf("the following params have no owner: %v aclPair", unOwnedParams)) - } + //removing unowned check as we need to be able to add new acl/params that won't be owned until active return nil } diff --git a/x/nodes/keeper/params.go b/x/nodes/keeper/params.go index 53d4183c..97267eed 100644 --- a/x/nodes/keeper/params.go +++ b/x/nodes/keeper/params.go @@ -10,6 +10,8 @@ import ( // Default parameter namespace const ( DefaultParamspace = types.ModuleName + // This is used as an input to the decimal power function used for calculating the exponent in PIP22. This avoids any overflows when taking the CthRoot of A by ensuring that the exponient is always devisable by 100 giving the effective range of ServicerStakeFloorMultiplierExponent 0-1 in steps of 0.01. + PIP_22_EXPONENT_DENOMINATOR = 100 ) // ParamKeyTable for staking module @@ -95,6 +97,32 @@ func (k Keeper) RelaysToTokensMultiplier(ctx sdk.Ctx) sdk.BigInt { return sdk.NewInt(multiplier) } +// ServicerStakeFloorMultiplier - Retrieve ServicerStakeFloorMultiplier +func (k Keeper) ServicerStakeFloorMultiplier(ctx sdk.Ctx) sdk.BigInt { + var multiplier int64 + k.Paramstore.Get(ctx, types.KeyServicerStakeFloorMultiplier, &multiplier) + return sdk.NewInt(multiplier) +} + +// ServicerStakeWeightMultiplier - Retrieve ServicerStakeWeightMultiplier +func (k Keeper) ServicerStakeWeightMultiplier(ctx sdk.Ctx) (res sdk.BigDec) { + k.Paramstore.Get(ctx, types.KeyServicerStakeWeightMultiplier, &res) + return +} + +// ServicerStakeWeightCeiling - Retrieve ServicerStakeWeightCeiling +func (k Keeper) ServicerStakeWeightCeiling(ctx sdk.Ctx) sdk.BigInt { + var multiplier int64 + k.Paramstore.Get(ctx, types.KeyServicerStakeWeightCeiling, &multiplier) + return sdk.NewInt(multiplier) +} + +// ServicerStakeFloorMultiplierExponent - Retrieve ServicerStakeFloorMultiplierExponent +func (k Keeper) ServicerStakeFloorMultiplierExponent(ctx sdk.Ctx) (res sdk.BigDec) { + k.Paramstore.Get(ctx, types.KeyServicerStakeFloorMultiplierExponent, &res) + return +} + func (k Keeper) NodeReward(ctx sdk.Ctx, reward sdk.BigInt) (nodeReward sdk.BigInt, feesCollected sdk.BigInt) { // convert reward to dec r := reward.ToDec() @@ -134,22 +162,26 @@ func (k Keeper) MaxJailedBlocks(ctx sdk.Ctx) (res int64) { // GetParams - Retrieve all parameters as types.Params func (k Keeper) GetParams(ctx sdk.Ctx) types.Params { return types.Params{ - RelaysToTokensMultiplier: k.RelaysToTokensMultiplier(ctx).Int64(), - UnstakingTime: k.UnStakingTime(ctx), - MaxValidators: k.MaxValidators(ctx), - StakeDenom: k.StakeDenom(ctx), - StakeMinimum: k.MinimumStake(ctx), - SessionBlockFrequency: k.BlocksPerSession(ctx), - DAOAllocation: k.DAOAllocation(ctx), - ProposerAllocation: k.ProposerAllocation(ctx), - MaximumChains: k.MaxChains(ctx), - MaxJailedBlocks: k.MaxJailedBlocks(ctx), - MaxEvidenceAge: k.MaxEvidenceAge(ctx), - SignedBlocksWindow: k.SignedBlocksWindow(ctx), - MinSignedPerWindow: sdk.NewDec(k.MinSignedPerWindow(ctx)), - DowntimeJailDuration: k.DowntimeJailDuration(ctx), - SlashFractionDoubleSign: k.SlashFractionDoubleSign(ctx), - SlashFractionDowntime: k.SlashFractionDowntime(ctx), + RelaysToTokensMultiplier: k.RelaysToTokensMultiplier(ctx).Int64(), + UnstakingTime: k.UnStakingTime(ctx), + MaxValidators: k.MaxValidators(ctx), + StakeDenom: k.StakeDenom(ctx), + StakeMinimum: k.MinimumStake(ctx), + SessionBlockFrequency: k.BlocksPerSession(ctx), + DAOAllocation: k.DAOAllocation(ctx), + ProposerAllocation: k.ProposerAllocation(ctx), + MaximumChains: k.MaxChains(ctx), + MaxJailedBlocks: k.MaxJailedBlocks(ctx), + MaxEvidenceAge: k.MaxEvidenceAge(ctx), + SignedBlocksWindow: k.SignedBlocksWindow(ctx), + MinSignedPerWindow: sdk.NewDec(k.MinSignedPerWindow(ctx)), + DowntimeJailDuration: k.DowntimeJailDuration(ctx), + SlashFractionDoubleSign: k.SlashFractionDoubleSign(ctx), + SlashFractionDowntime: k.SlashFractionDowntime(ctx), + ServicerStakeFloorMultiplier: k.ServicerStakeFloorMultiplier(ctx).Int64(), + ServicerStakeWeightMultiplier: k.ServicerStakeWeightMultiplier(ctx), + ServicerStakeWeightCeiling: k.ServicerStakeWeightCeiling(ctx).Int64(), + ServicerStakeFloorMultiplierExponent: k.ServicerStakeFloorMultiplierExponent(ctx), } } diff --git a/x/nodes/keeper/reward.go b/x/nodes/keeper/reward.go index 7cce1acb..11011c7c 100644 --- a/x/nodes/keeper/reward.go +++ b/x/nodes/keeper/reward.go @@ -2,6 +2,7 @@ package keeper import ( "fmt" + "github.com/pokt-network/pocket-core/codec" sdk "github.com/pokt-network/pocket-core/types" govTypes "github.com/pokt-network/pocket-core/x/gov/types" "github.com/pokt-network/pocket-core/x/nodes/types" @@ -17,7 +18,32 @@ func (k Keeper) RewardForRelays(ctx sdk.Ctx, relays sdk.BigInt, address sdk.Addr return sdk.ZeroInt() } } - coins := k.RelaysToTokensMultiplier(ctx).Mul(relays) + + var coins sdk.BigInt + + //check if PIP22 is enabled, if so scale the rewards + if k.Cdc.IsAfterNamedFeatureActivationHeight(ctx.BlockHeight(), codec.RSCALKey) { + //grab stake + validator, found := k.GetValidator(ctx, address) + if !found { + ctx.Logger().Error(fmt.Errorf("no validator found for address %s; at height %d\n", address.String(), ctx.BlockHeight()).Error()) + return sdk.ZeroInt() + } + + stake := validator.GetTokens() + //floorstake to the lowest bin multiple or take ceiling, whicherver is smaller + flooredStake := sdk.MinInt(stake.Sub(stake.Mod(k.ServicerStakeFloorMultiplier(ctx))), (k.ServicerStakeWeightCeiling(ctx).Sub(k.ServicerStakeWeightCeiling(ctx).Mod(k.ServicerStakeFloorMultiplier(ctx))))) + //Convert from tokens to a BIN number + bin := flooredStake.Quo(k.ServicerStakeFloorMultiplier(ctx)) + //calculate the weight value, weight will be a floatng point number so cast to DEC here and then truncate back to big int + weight := bin.ToDec().FracPow(k.ServicerStakeFloorMultiplierExponent(ctx), PIP_22_EXPONENT_DENOMINATOR).Quo(k.ServicerStakeWeightMultiplier(ctx)) + coinsDecimal := k.RelaysToTokensMultiplier(ctx).ToDec().Mul(relays.ToDec()).Mul(weight) + //truncate back to int + coins = coinsDecimal.TruncateInt() + } else { + coins = k.RelaysToTokensMultiplier(ctx).Mul(relays) + } + toNode, toFeeCollector := k.NodeReward(ctx, coins) if toNode.IsPositive() { k.mint(ctx, toNode, address) diff --git a/x/nodes/keeper/reward_test.go b/x/nodes/keeper/reward_test.go index 30fa2172..8d34d293 100644 --- a/x/nodes/keeper/reward_test.go +++ b/x/nodes/keeper/reward_test.go @@ -5,6 +5,7 @@ import ( "testing" sdk "github.com/pokt-network/pocket-core/types" + "github.com/pokt-network/pocket-core/x/nodes/types" "github.com/stretchr/testify/assert" ) @@ -99,6 +100,7 @@ func TestKeeper_rewardFromFees(t *testing.T) { } stakedValidator := getStakedValidator() stakedValidator.OutputAddress = getRandomValidatorAddress() + codec.UpgradeFeatureMap[codec.RSCALKey] = 0 codec.TestMode = -3 amount := sdk.NewInt(10000) fees := sdk.NewCoins(sdk.NewCoin("upokt", amount)) @@ -150,6 +152,7 @@ func TestKeeper_rewardFromRelays(t *testing.T) { stakedValidatorNoOutput.OutputAddress = nil stakedValidator.OutputAddress = getRandomValidatorAddress() codec.TestMode = -3 + codec.UpgradeFeatureMap[codec.RSCALKey] = 0 context, _, keeper := createTestInput(t, true) keeper.SetValidator(context, stakedValidator) keeper.SetValidator(context, stakedValidatorNoOutput) @@ -187,3 +190,216 @@ func TestKeeper_rewardFromRelays(t *testing.T) { }) } } + +func TestKeeper_rewardFromRelaysPIP22NoEXP(t *testing.T) { + type fields struct { + keeper Keeper + } + type args struct { + ctx sdk.Context + baseReward sdk.BigInt + relays int64 + validator1 types.Validator + validator2 types.Validator + validator3 types.Validator + validator4 types.Validator + } + + codec.UpgradeFeatureMap[codec.RSCALKey] = -1 + context, _, keeper := createTestInput(t, true) + p := keeper.GetParams(context) + p.ServicerStakeFloorMultiplier = types.DefaultServicerStakeFloorMultiplier + p.ServicerStakeWeightMultiplier = types.DefaultServicerStakeWeightMultiplier + p.ServicerStakeFloorMultiplierExponent = sdk.NewDec(1) + p.ServicerStakeWeightCeiling = 60000000000 + keeper.SetParams(context, p) + + stakedValidatorBin1 := getStakedValidator() + stakedValidatorBin1.StakedTokens = keeper.ServicerStakeFloorMultiplier(context) + stakedValidatorBin2 := getStakedValidator() + stakedValidatorBin2.StakedTokens = keeper.ServicerStakeFloorMultiplier(context).Mul(sdk.NewInt(2)) + stakedValidatorBin3 := getStakedValidator() + stakedValidatorBin3.StakedTokens = keeper.ServicerStakeFloorMultiplier(context).Mul(sdk.NewInt(3)) + stakedValidatorBin4 := getStakedValidator() + stakedValidatorBin4.StakedTokens = keeper.ServicerStakeFloorMultiplier(context).Mul(sdk.NewInt(4)) + + numRelays := int64(10000) + base := sdk.NewDec(1).Quo(keeper.ServicerStakeWeightMultiplier(context)).Mul(sdk.NewDec(numRelays)).Mul(sdk.NewDecWithPrec(89, 2)).TruncateInt().Mul(keeper.RelaysToTokensMultiplier(context)) + + keeper.SetValidator(context, stakedValidatorBin1) + keeper.SetValidator(context, stakedValidatorBin2) + keeper.SetValidator(context, stakedValidatorBin3) + keeper.SetValidator(context, stakedValidatorBin4) + tests := []struct { + name string + fields fields + args args + }{ + {"Test RelayReward", fields{keeper: keeper}, + args{ + ctx: context, + baseReward: base, + relays: numRelays, + validator1: stakedValidatorBin1, + validator2: stakedValidatorBin2, + validator3: stakedValidatorBin3, + validator4: stakedValidatorBin4, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := tt.fields.keeper + ctx := tt.args.ctx + k.RewardForRelays(tt.args.ctx, sdk.NewInt(tt.args.relays), tt.args.validator1.GetAddress()) + acc := k.GetAccount(ctx, tt.args.validator1.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", tt.args.baseReward)))) + k.RewardForRelays(tt.args.ctx, sdk.NewInt(tt.args.relays), tt.args.validator2.GetAddress()) + acc = k.GetAccount(ctx, tt.args.validator2.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", tt.args.baseReward.Mul(sdk.NewInt(2)))))) + k.RewardForRelays(tt.args.ctx, sdk.NewInt(tt.args.relays), tt.args.validator3.GetAddress()) + acc = k.GetAccount(ctx, tt.args.validator3.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", tt.args.baseReward.Mul(sdk.NewInt(3)))))) + k.RewardForRelays(tt.args.ctx, sdk.NewInt(tt.args.relays), tt.args.validator4.GetAddress()) + acc = k.GetAccount(ctx, tt.args.validator4.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", tt.args.baseReward.Mul(sdk.NewInt(4)))))) + }) + } +} + +func TestKeeper_checkPIP22CheckCeiling(t *testing.T) { + type fields struct { + keeper Keeper + } + type args struct { + ctx sdk.Context + baseReward sdk.BigInt + relays int64 + validator1 types.Validator + validator2 types.Validator + } + + codec.UpgradeFeatureMap[codec.RSCALKey] = -1 + context, _, keeper := createTestInput(t, true) + p := keeper.GetParams(context) + p.ServicerStakeFloorMultiplier = types.DefaultServicerStakeFloorMultiplier + p.ServicerStakeWeightMultiplier = types.DefaultServicerStakeWeightMultiplier + p.ServicerStakeFloorMultiplierExponent = sdk.NewDec(1) + p.ServicerStakeWeightCeiling = 15000000000 + keeper.SetParams(context, p) + + stakedValidatorBin1 := getStakedValidator() + stakedValidatorBin1.StakedTokens = keeper.ServicerStakeFloorMultiplier(context) + stakedValidatorBin2 := getStakedValidator() + stakedValidatorBin2.StakedTokens = keeper.ServicerStakeFloorMultiplier(context).Mul(sdk.NewInt(2)) + + numRelays := int64(10000) + base := sdk.NewDec(1).Quo(keeper.ServicerStakeWeightMultiplier(context)).Mul(sdk.NewDec(numRelays)).Mul(sdk.NewDecWithPrec(89, 2)).TruncateInt().Mul(keeper.RelaysToTokensMultiplier(context)) + + keeper.SetValidator(context, stakedValidatorBin1) + keeper.SetValidator(context, stakedValidatorBin2) + tests := []struct { + name string + fields fields + args args + }{ + {"Test RelayReward", fields{keeper: keeper}, + args{ + ctx: context, + baseReward: base, + relays: numRelays, + validator1: stakedValidatorBin1, + validator2: stakedValidatorBin2, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := tt.fields.keeper + ctx := tt.args.ctx + k.RewardForRelays(tt.args.ctx, sdk.NewInt(tt.args.relays), tt.args.validator1.GetAddress()) + acc := k.GetAccount(ctx, tt.args.validator1.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", tt.args.baseReward)))) + k.RewardForRelays(tt.args.ctx, sdk.NewInt(tt.args.relays), tt.args.validator2.GetAddress()) + acc = k.GetAccount(ctx, tt.args.validator2.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", tt.args.baseReward)))) + }) + } +} + +func TestKeeper_rewardFromRelaysPIP22EXP(t *testing.T) { + type fields struct { + keeper Keeper + } + type args struct { + ctx sdk.Context + validator1 types.Validator + validator2 types.Validator + validator3 types.Validator + validator4 types.Validator + } + + codec.UpgradeFeatureMap[codec.RSCALKey] = -1 + context, _, keeper := createTestInput(t, true) + p := keeper.GetParams(context) + p.ServicerStakeFloorMultiplier = types.DefaultServicerStakeFloorMultiplier + p.ServicerStakeWeightMultiplier = types.DefaultServicerStakeWeightMultiplier + p.ServicerStakeFloorMultiplierExponent = sdk.NewDecWithPrec(50, 2) + p.ServicerStakeWeightMultiplier = sdk.NewDec(1) + p.ServicerStakeWeightCeiling = 60000000000 + keeper.SetParams(context, p) + + stakedValidatorBin1 := getStakedValidator() + stakedValidatorBin1.StakedTokens = keeper.ServicerStakeFloorMultiplier(context) + stakedValidatorBin2 := getStakedValidator() + stakedValidatorBin2.StakedTokens = keeper.ServicerStakeFloorMultiplier(context).Mul(sdk.NewInt(2)) + stakedValidatorBin3 := getStakedValidator() + stakedValidatorBin3.StakedTokens = keeper.ServicerStakeFloorMultiplier(context).Mul(sdk.NewInt(3)) + stakedValidatorBin4 := getStakedValidator() + stakedValidatorBin4.StakedTokens = keeper.ServicerStakeFloorMultiplier(context).Mul(sdk.NewInt(4)) + + keeper.SetValidator(context, stakedValidatorBin1) + keeper.SetValidator(context, stakedValidatorBin2) + keeper.SetValidator(context, stakedValidatorBin3) + keeper.SetValidator(context, stakedValidatorBin4) + tests := []struct { + name string + fields fields + args args + }{ + {"Test RelayReward", fields{keeper: keeper}, + args{ + ctx: context, + validator1: stakedValidatorBin1, + validator2: stakedValidatorBin2, + validator3: stakedValidatorBin3, + validator4: stakedValidatorBin4, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := tt.fields.keeper + ctx := tt.args.ctx + k.RewardForRelays(tt.args.ctx, sdk.NewInt(1000), tt.args.validator1.GetAddress()) + acc := k.GetAccount(ctx, tt.args.validator1.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", sdk.NewInt(890000))))) + k.RewardForRelays(tt.args.ctx, sdk.NewInt(1000), tt.args.validator2.GetAddress()) + acc = k.GetAccount(ctx, tt.args.validator2.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", sdk.NewInt(1258650))))) + k.RewardForRelays(tt.args.ctx, sdk.NewInt(1000), tt.args.validator3.GetAddress()) + acc = k.GetAccount(ctx, tt.args.validator3.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", sdk.NewInt(1541525))))) + k.RewardForRelays(tt.args.ctx, sdk.NewInt(1000), tt.args.validator4.GetAddress()) + acc = k.GetAccount(ctx, tt.args.validator4.GetAddress()) + assert.False(t, acc.Coins.IsZero()) + assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", sdk.NewInt(1780000))))) + }) + } +} diff --git a/x/nodes/keeper/slash.go b/x/nodes/keeper/slash.go index bd78d964..5a1e5fd6 100644 --- a/x/nodes/keeper/slash.go +++ b/x/nodes/keeper/slash.go @@ -8,12 +8,34 @@ import ( "github.com/pokt-network/pocket-core/x/nodes/types" "github.com/tendermint/tendermint/crypto" + "github.com/pokt-network/pocket-core/codec" sdk "github.com/pokt-network/pocket-core/types" ) // BurnForChallenge - Tries to remove coins from account & supply for a challenged validator func (k Keeper) BurnForChallenge(ctx sdk.Ctx, challenges sdk.BigInt, address sdk.Address) { - coins := k.RelaysToTokensMultiplier(ctx).Mul(challenges) + var coins sdk.BigInt + //check if PIP22 is enabled, if so scale the rewards + if k.Cdc.IsAfterNamedFeatureActivationHeight(ctx.BlockHeight(), codec.RSCALKey) { + //grab stake + validator, found := k.GetValidator(ctx, address) + if !found { + ctx.Logger().Error(fmt.Errorf("no validator found for address %s; at height %d\n", address.String(), ctx.BlockHeight()).Error()) + return + } + + stake := validator.GetTokens() + //floorstake to the lowest bin multiple or take ceiling, whicherver is smaller + flooredStake := sdk.MinInt(stake.Sub(stake.Mod(k.ServicerStakeFloorMultiplier(ctx))), (k.ServicerStakeWeightCeiling(ctx).Sub(stake.Mod(k.ServicerStakeFloorMultiplier(ctx))))) + //Convert from tokens to a BIN number + bin := flooredStake.Quo(k.ServicerStakeFloorMultiplier(ctx)) + //calculate the weight value + weight := bin.ToDec().FracPow(k.ServicerStakeFloorMultiplierExponent(ctx), PIP_22_EXPONENT_DENOMINATOR).Quo(k.ServicerStakeWeightMultiplier(ctx)) + coinsDecimal := k.RelaysToTokensMultiplier(ctx).ToDec().Mul(challenges.ToDec()).Mul(weight) + coins = coinsDecimal.TruncateInt() + } else { + coins = k.RelaysToTokensMultiplier(ctx).Mul(challenges) + } k.simpleSlash(ctx, address, coins) } diff --git a/x/nodes/keeper/valStateChanges.go b/x/nodes/keeper/valStateChanges.go index 3e1b196a..b57dc501 100644 --- a/x/nodes/keeper/valStateChanges.go +++ b/x/nodes/keeper/valStateChanges.go @@ -10,6 +10,7 @@ import ( "github.com/pokt-network/pocket-core/crypto" abci "github.com/tendermint/tendermint/abci/types" + "github.com/pokt-network/pocket-core/codec" sdk "github.com/pokt-network/pocket-core/types" "github.com/pokt-network/pocket-core/x/nodes/types" ) @@ -200,6 +201,18 @@ func (k Keeper) ValidateEditStake(ctx sdk.Ctx, currentValidator, newValidtor typ if diff.IsNegative() { return types.ErrMinimumEditStake(k.codespace) } + + if k.Cdc.IsAfterNamedFeatureActivationHeight(ctx.BlockHeight(), codec.RSCALKey) && k.Cdc.IsAfterNamedFeatureActivationHeight(ctx.BlockHeight(), codec.VEDITKey) { + //check that we are bumping up into a new bin or are above the cieling, if not return an error + if amount.LT(k.ServicerStakeWeightCeiling(ctx)) { + //grab the bin and check if we are in a new bin + flooredNewStake := amount.Sub(amount.Mod(k.ServicerStakeFloorMultiplier(ctx))) + if flooredNewStake.LTE(currentValidator.StakedTokens) { + return types.ErrSameBinEditStake(k.codespace) + } + } + } + // if stake bump if !diff.IsZero() { // ensure account has enough coins for bump diff --git a/x/nodes/keeper/valStateChanges_test.go b/x/nodes/keeper/valStateChanges_test.go index 9552002a..9904f454 100644 --- a/x/nodes/keeper/valStateChanges_test.go +++ b/x/nodes/keeper/valStateChanges_test.go @@ -77,6 +77,16 @@ func TestValidatorStateChange_EditAndValidateStakeValidator(t *testing.T) { //same app no change no fail updateNothingval := val updateNothingval.StakedTokens = stakeAmount + //new staked amount doesn't push into the next bin + failPip22 := val + failPip22.StakedTokens = sdk.NewInt(29999000000) + //New staked amount does push into the next bin + passPip22NextBin := val + passPip22NextBin.StakedTokens = sdk.NewInt(30001000000) + //All updates should pass above the ceiling + passPip22AboveCeil := val + passPip22AboveCeil.StakedTokens = sdk.NewInt(60000000000).Add(sdk.OneInt()) + tests := []struct { name string accountAmount sdk.BigInt @@ -84,6 +94,7 @@ func TestValidatorStateChange_EditAndValidateStakeValidator(t *testing.T) { amount sdk.BigInt want types.Validator err sdk.Error + PIP22Edit bool }{ { name: "edit stake amount of existing validator", @@ -91,6 +102,7 @@ func TestValidatorStateChange_EditAndValidateStakeValidator(t *testing.T) { origApp: val, amount: stakeAmount, want: updateStakeAmountApp, + PIP22Edit: true, }, { name: "FAIL edit stake amount of existing validator", @@ -99,6 +111,7 @@ func TestValidatorStateChange_EditAndValidateStakeValidator(t *testing.T) { amount: stakeAmount, want: updateStakeAmountAppFail, err: types.ErrMinimumEditStake("pos"), + PIP22Edit: false, }, { name: "edit stake the chains of the validator", @@ -106,6 +119,7 @@ func TestValidatorStateChange_EditAndValidateStakeValidator(t *testing.T) { origApp: val, amount: stakeAmount, want: updateChainsVal, + PIP22Edit: false, }, { name: "edit stake the serviceurl of the validator", @@ -113,6 +127,7 @@ func TestValidatorStateChange_EditAndValidateStakeValidator(t *testing.T) { origApp: val, amount: stakeAmount, want: updateChainsVal, + PIP22Edit: false, }, { name: "FAIL not enough coins to bump stake amount of existing validator", @@ -121,6 +136,7 @@ func TestValidatorStateChange_EditAndValidateStakeValidator(t *testing.T) { amount: stakeAmount, want: updateStakeAmountApp, err: types.ErrNotEnoughCoins("pos"), + PIP22Edit: false, }, { name: "update nothing for the validator", @@ -128,12 +144,45 @@ func TestValidatorStateChange_EditAndValidateStakeValidator(t *testing.T) { origApp: val, amount: stakeAmount, want: updateNothingval, + PIP22Edit: false, + }, + { + name: "PIP22 not enough to bump bin", + accountAmount: accountAmount, + origApp: val, + amount: sdk.NewInt(15001000000), + want: failPip22, + err: types.ErrSameBinEditStake("pos"), + PIP22Edit: true, + }, + { + name: "PIP22 update to next bin", + accountAmount: accountAmount, + origApp: val, + amount: sdk.NewInt(15001000000), + want: passPip22NextBin, + PIP22Edit: true, + }, + { + name: "PIP22 above ceil", + accountAmount: accountAmount, + origApp: val, + amount: sdk.NewInt(60000000000), + want: passPip22AboveCeil, + PIP22Edit: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // test setup codec.UpgradeHeight = -1 + if tt.PIP22Edit { + codec.UpgradeFeatureMap[codec.RSCALKey] = -1 + codec.UpgradeFeatureMap[codec.VEDITKey] = -1 + } else { + codec.UpgradeFeatureMap[codec.RSCALKey] = 0 + codec.UpgradeFeatureMap[codec.VEDITKey] = 0 + } context, _, keeper := createTestInput(t, true) coins := sdk.NewCoins(sdk.NewCoin(keeper.StakeDenom(context), tt.accountAmount)) err := keeper.AccountKeeper.MintCoins(context, types.StakedPoolName, coins) diff --git a/x/nodes/module.go b/x/nodes/module.go index bded0877..303d5b06 100644 --- a/x/nodes/module.go +++ b/x/nodes/module.go @@ -56,6 +56,10 @@ type AppModule struct { keeper keeper.Keeper } +func (am AppModule) ConsensusParamsUpdate(ctx sdk.Ctx) *abci.ConsensusParams { + return &abci.ConsensusParams{} +} + // NewAppModule creates a new AppModule object func NewAppModule(keeper keeper.Keeper) AppModule { return AppModule{ @@ -114,6 +118,16 @@ func (am AppModule) ExportGenesis(ctx sdk.Ctx) json.RawMessage { // module begin-block func (am AppModule) BeginBlock(ctx sdk.Ctx, req abci.RequestBeginBlock) { + if am.keeper.Cdc.IsOnNamedFeatureActivationHeight(ctx.BlockHeight(), codec.RSCALKey) { + //on the height we set the default value + params := am.keeper.GetParams(ctx) + params.ServicerStakeFloorMultiplier = types.DefaultServicerStakeFloorMultiplier + params.ServicerStakeWeightMultiplier = types.DefaultServicerStakeWeightMultiplier + params.ServicerStakeWeightCeiling = types.DefaultServicerStakeWeightCeiling + params.ServicerStakeFloorMultiplierExponent = types.DefaultServicerStakeFloorMultiplierExponent + am.keeper.SetParams(ctx, params) + } + keeper.BeginBlocker(ctx, req, am.keeper) } diff --git a/x/nodes/types/errors.go b/x/nodes/types/errors.go index 589cd4dc..8bcace81 100644 --- a/x/nodes/types/errors.go +++ b/x/nodes/types/errors.go @@ -95,6 +95,10 @@ func ErrMinimumEditStake(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeMinimumEditStake, "validator must edit stake with a stake greater than or equal to current stake") } +func ErrSameBinEditStake(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeMinimumEditStake, "validator must edit stake to a greater stake Bin than the current staked bin") +} + func ErrValidatorPubKeyExists(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "validator already exist for this pubkey, must use new validator pubkey") } diff --git a/x/nodes/types/params.go b/x/nodes/types/params.go index 3ccde4f4..e7a08204 100644 --- a/x/nodes/types/params.go +++ b/x/nodes/types/params.go @@ -11,65 +11,77 @@ import ( // POS params default values const ( // DefaultParamspace for params keeper - DefaultRelaysToTokensMultiplier int64 = 1000 - DefaultParamspace = ModuleName - DefaultUnstakingTime = time.Hour * 24 * 7 * 3 - DefaultMaxValidators int64 = 5000 - DefaultMinStake int64 = 1000000 - DefaultMaxEvidenceAge = 60 * 2 * time.Second - DefaultSignedBlocksWindow = int64(100) - DefaultDowntimeJailDuration = 60 * 10 * time.Second - DefaultSessionBlocktime = 25 - DefaultProposerAllocation = 1 - DefaultDAOAllocation = 10 - DefaultMaxChains = 15 - DefaultMaxJailedBlocks = 1000 + DefaultRelaysToTokensMultiplier int64 = 1000 + DefaultParamspace = ModuleName + DefaultUnstakingTime = time.Hour * 24 * 7 * 3 + DefaultMaxValidators int64 = 5000 + DefaultMinStake int64 = 1000000 + DefaultMaxEvidenceAge = 60 * 2 * time.Second + DefaultSignedBlocksWindow = int64(100) + DefaultDowntimeJailDuration = 60 * 10 * time.Second + DefaultSessionBlocktime = 25 + DefaultProposerAllocation = 1 + DefaultDAOAllocation = 10 + DefaultMaxChains = 15 + DefaultMaxJailedBlocks = 1000 + DefaultServicerStakeFloorMultiplier int64 = 15000000000 + DefaultServicerStakeWeightCeiling int64 = 15000000000 ) // - Keys for parameter access var ( - KeyUnstakingTime = []byte("UnstakingTime") - KeyMaxValidators = []byte("MaxValidators") - KeyStakeDenom = []byte("StakeDenom") - KeyStakeMinimum = []byte("StakeMinimum") - KeyMaxEvidenceAge = []byte("MaxEvidenceAge") - KeySignedBlocksWindow = []byte("SignedBlocksWindow") - KeyMinSignedPerWindow = []byte("MinSignedPerWindow") - KeyDowntimeJailDuration = []byte("DowntimeJailDuration") - KeySlashFractionDoubleSign = []byte("SlashFractionDoubleSign") - KeySlashFractionDowntime = []byte("SlashFractionDowntime") - KeyRelaysToTokensMultiplier = []byte("RelaysToTokensMultiplier") - KeySessionBlock = []byte("BlocksPerSession") - KeyDAOAllocation = []byte("DAOAllocation") - KeyProposerAllocation = []byte("ProposerPercentage") - KeyMaxChains = []byte("MaximumChains") - KeyMaxJailedBlocks = []byte("MaxJailedBlocks") - DoubleSignJailEndTime = time.Unix(253402300799, 0) // forever - DefaultMinSignedPerWindow = sdk.NewDecWithPrec(5, 1) - DefaultSlashFractionDoubleSign = sdk.NewDec(1).Quo(sdk.NewDec(20)) - DefaultSlashFractionDowntime = sdk.NewDec(1).Quo(sdk.NewDec(100)) + KeyUnstakingTime = []byte("UnstakingTime") + KeyMaxValidators = []byte("MaxValidators") + KeyStakeDenom = []byte("StakeDenom") + KeyStakeMinimum = []byte("StakeMinimum") + KeyMaxEvidenceAge = []byte("MaxEvidenceAge") + KeySignedBlocksWindow = []byte("SignedBlocksWindow") + KeyMinSignedPerWindow = []byte("MinSignedPerWindow") + KeyDowntimeJailDuration = []byte("DowntimeJailDuration") + KeySlashFractionDoubleSign = []byte("SlashFractionDoubleSign") + KeySlashFractionDowntime = []byte("SlashFractionDowntime") + KeyRelaysToTokensMultiplier = []byte("RelaysToTokensMultiplier") + KeySessionBlock = []byte("BlocksPerSession") + KeyDAOAllocation = []byte("DAOAllocation") + KeyProposerAllocation = []byte("ProposerPercentage") + KeyMaxChains = []byte("MaximumChains") + KeyMaxJailedBlocks = []byte("MaxJailedBlocks") + KeyServicerStakeFloorMultiplier = []byte("ServicerStakeFloorMultiplier") + KeyServicerStakeWeightMultiplier = []byte("ServicerStakeWeightMultiplier") + KeyServicerStakeWeightCeiling = []byte("ServicerStakeWeightCeiling") + KeyServicerStakeFloorMultiplierExponent = []byte("ServicerStakeFloorMultiplierExponent") + DefaultServicerStakeWeightMultiplier = sdk.NewDec(1) + DefaultServicerStakeFloorMultiplierExponent = sdk.NewDec(1) + DoubleSignJailEndTime = time.Unix(253402300799, 0) // forever + DefaultMinSignedPerWindow = sdk.NewDecWithPrec(5, 1) + DefaultSlashFractionDoubleSign = sdk.NewDec(1).Quo(sdk.NewDec(20)) + DefaultSlashFractionDowntime = sdk.NewDec(1).Quo(sdk.NewDec(100)) ) var _ sdk.ParamSet = (*Params)(nil) // Params defines the high level settings for pos module type Params struct { - RelaysToTokensMultiplier int64 `json:"relays_to_tokens_multiplier" yaml:"relays_to_tokens_multiplier"` - UnstakingTime time.Duration `json:"unstaking_time" yaml:"unstaking_time"` // how much time must pass between the begin_unstaking_tx and the node going to -> unstaked status - MaxValidators int64 `json:"max_validators" yaml:"max_validators"` // maximum number of validators in the network at any given block - StakeDenom string `json:"stake_denom" yaml:"stake_denom"` // the monetary denomination of the coins in the network `uPOKT` or `uAtom` or `Wei` - StakeMinimum int64 `json:"stake_minimum" yaml:"stake_minimum"` // minimum amount of `uPOKT` needed to stake in the network as a node - SessionBlockFrequency int64 `json:"session_block_frequency" yaml:"session_block_frequency"` // how many blocks are in a session (pocket network unit) - DAOAllocation int64 `json:"dao_allocation" yaml:"dao_allocation"` - ProposerAllocation int64 `json:"proposer_allocation" yaml:"proposer_allocation"` - MaximumChains int64 `json:"maximum_chains" yaml:"maximum_chains"` - MaxJailedBlocks int64 `json:"max_jailed_blocks" yaml:"max_jailed_blocks"` - MaxEvidenceAge time.Duration `json:"max_evidence_age" yaml:"max_evidence_age"` // maximum age of tendermint evidence that is still valid (currently not implemented in Cosmos or Pocket-Core) - SignedBlocksWindow int64 `json:"signed_blocks_window" yaml:"signed_blocks_window"` // window of time in blocks (unit) used for signature verification -> specifically in not signing (missing) blocks - MinSignedPerWindow sdk.BigDec `json:"min_signed_per_window" yaml:"min_signed_per_window"` // minimum number of blocks the node must sign per window - DowntimeJailDuration time.Duration `json:"downtime_jail_duration" yaml:"downtime_jail_duration"` // minimum amount of time node must spend in jail after missing blocks - SlashFractionDoubleSign sdk.BigDec `json:"slash_fraction_double_sign" yaml:"slash_fraction_double_sign"` // the factor of which a node is slashed for a double sign - SlashFractionDowntime sdk.BigDec `json:"slash_fraction_downtime" yaml:"slash_fraction_downtime"` // the factor of which a node is slashed for missing blocks + RelaysToTokensMultiplier int64 `json:"relays_to_tokens_multiplier" yaml:"relays_to_tokens_multiplier"` + UnstakingTime time.Duration `json:"unstaking_time" yaml:"unstaking_time"` // how much time must pass between the begin_unstaking_tx and the node going to -> unstaked status + MaxValidators int64 `json:"max_validators" yaml:"max_validators"` // maximum number of validators in the network at any given block + StakeDenom string `json:"stake_denom" yaml:"stake_denom"` // the monetary denomination of the coins in the network `uPOKT` or `uAtom` or `Wei` + StakeMinimum int64 `json:"stake_minimum" yaml:"stake_minimum"` // minimum amount of `uPOKT` needed to stake in the network as a node + SessionBlockFrequency int64 `json:"session_block_frequency" yaml:"session_block_frequency"` // how many blocks are in a session (pocket network unit) + DAOAllocation int64 `json:"dao_allocation" yaml:"dao_allocation"` + ProposerAllocation int64 `json:"proposer_allocation" yaml:"proposer_allocation"` + MaximumChains int64 `json:"maximum_chains" yaml:"maximum_chains"` + MaxJailedBlocks int64 `json:"max_jailed_blocks" yaml:"max_jailed_blocks"` + MaxEvidenceAge time.Duration `json:"max_evidence_age" yaml:"max_evidence_age"` // maximum age of tendermint evidence that is still valid (currently not implemented in Cosmos or Pocket-Core) + SignedBlocksWindow int64 `json:"signed_blocks_window" yaml:"signed_blocks_window"` // window of time in blocks (unit) used for signature verification -> specifically in not signing (missing) blocks + MinSignedPerWindow sdk.BigDec `json:"min_signed_per_window" yaml:"min_signed_per_window"` // minimum number of blocks the node must sign per window + DowntimeJailDuration time.Duration `json:"downtime_jail_duration" yaml:"downtime_jail_duration"` // minimum amount of time node must spend in jail after missing blocks + SlashFractionDoubleSign sdk.BigDec `json:"slash_fraction_double_sign" yaml:"slash_fraction_double_sign"` // the factor of which a node is slashed for a double sign + SlashFractionDowntime sdk.BigDec `json:"slash_fraction_downtime" yaml:"slash_fraction_downtime"` // the factor of which a node is slashed for missing blocks + ServicerStakeFloorMultiplier int64 `json:"servicer_stake_floor_multipler" yaml:"servicer_stake_floor_multipler"` + ServicerStakeWeightMultiplier sdk.BigDec `json:"servicer_stake_weight_multipler" yaml:"servicer_stake_weight_multipler"` + ServicerStakeWeightCeiling int64 `json:"servicer_stake_weight_ceiling" yaml:"servicer_stake_weight_cieling"` + ServicerStakeFloorMultiplierExponent sdk.BigDec `json:"servicer_stake_floor_multiplier_exponent" yaml:"servicer_stake_floor_multiplier_exponent"` } // Implements sdk.ParamSet @@ -91,6 +103,10 @@ func (p *Params) ParamSetPairs() sdk.ParamSetPairs { {Key: KeyRelaysToTokensMultiplier, Value: &p.RelaysToTokensMultiplier}, {Key: KeyMaxChains, Value: &p.MaximumChains}, {Key: KeyMaxJailedBlocks, Value: &p.MaxJailedBlocks}, + {Key: KeyServicerStakeFloorMultiplier, Value: &p.ServicerStakeFloorMultiplier}, + {Key: KeyServicerStakeWeightMultiplier, Value: &p.ServicerStakeWeightMultiplier}, + {Key: KeyServicerStakeWeightCeiling, Value: &p.ServicerStakeWeightCeiling}, + {Key: KeyServicerStakeFloorMultiplierExponent, Value: &p.ServicerStakeFloorMultiplierExponent}, } } diff --git a/x/pocketcore/handler.go b/x/pocketcore/handler.go index 87121a41..5e7f8a21 100644 --- a/x/pocketcore/handler.go +++ b/x/pocketcore/handler.go @@ -65,12 +65,12 @@ func handleProofMsg(ctx sdk.Ctx, k keeper.Keeper, proof types.MsgProof) sdk.Resu if err != nil { if err.Code() == types.CodeInvalidMerkleVerifyError && !claim.IsEmpty() { // delete local evidence - processSelf(ctx, k, proof.GetSigners()[0], claim.SessionHeader, claim.EvidenceType, sdk.ZeroInt()) + processSelf(ctx, proof.GetSigners()[0], claim.SessionHeader, claim.EvidenceType, sdk.ZeroInt()) return err.Result() } if err.Code() == types.CodeReplayAttackError && !claim.IsEmpty() { // delete local evidence - processSelf(ctx, k, proof.GetSigners()[0], claim.SessionHeader, claim.EvidenceType, sdk.ZeroInt()) + processSelf(ctx, proof.GetSigners()[0], claim.SessionHeader, claim.EvidenceType, sdk.ZeroInt()) // if is a replay attack, handle accordingly k.HandleReplayAttack(ctx, addr, sdk.NewInt(claim.TotalProofs)) err := k.DeleteClaim(ctx, addr, claim.SessionHeader, claim.EvidenceType) @@ -86,7 +86,7 @@ func handleProofMsg(ctx sdk.Ctx, k keeper.Keeper, proof types.MsgProof) sdk.Resu return err.Result() } // delete local evidence - processSelf(ctx, k, proof.GetSigners()[0], claim.SessionHeader, claim.EvidenceType, tokens) + processSelf(ctx, proof.GetSigners()[0], claim.SessionHeader, claim.EvidenceType, tokens) // create the event ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( @@ -97,15 +97,21 @@ func handleProofMsg(ctx sdk.Ctx, k keeper.Keeper, proof types.MsgProof) sdk.Resu return sdk.Result{Events: ctx.EventManager().Events()} } -func processSelf(ctx sdk.Ctx, k keeper.Keeper, signer sdk.Address, header types.SessionHeader, evidenceType types.EvidenceType, tokens sdk.BigInt) { - // delete local evidence - if signer.Equals(k.GetSelfAddress(ctx)) { - err := types.DeleteEvidence(header, evidenceType) - if err != nil { - ctx.Logger().Error("Unable to delete evidence: " + err.Error()) - } - if !tokens.IsZero() { - types.GlobalServiceMetric().AddUPOKTEarnedFor(header.Chain, float64(tokens.Int64())) +func processSelf(ctx sdk.Ctx, signer sdk.Address, header types.SessionHeader, evidenceType types.EvidenceType, tokens sdk.BigInt) { + node, ok := types.GlobalPocketNodes[signer.String()] + if !ok { + return + } + evidenceStore := node.EvidenceStore + err := types.DeleteEvidence(header, evidenceType, evidenceStore) + if err != nil { + ctx.Logger().Error("Unable to delete evidence: " + err.Error()) + } + if !tokens.IsZero() { + if types.GlobalPocketConfig.LeanPocket { + go types.GlobalServiceMetric().AddUPOKTEarnedFor(header.Chain, float64(tokens.Int64()), &signer) + } else { + types.GlobalServiceMetric().AddUPOKTEarnedFor(header.Chain, float64(tokens.Int64()), &signer) } } } diff --git a/x/pocketcore/keeper/chain_test.go b/x/pocketcore/keeper/chain_test.go index d55a745c..25aeb8b9 100644 --- a/x/pocketcore/keeper/chain_test.go +++ b/x/pocketcore/keeper/chain_test.go @@ -2,10 +2,9 @@ package keeper import ( "encoding/hex" - "testing" - "github.com/pokt-network/pocket-core/x/pocketcore/types" "github.com/stretchr/testify/assert" + "testing" ) func TestKeeper_GetHostedBlockchains(t *testing.T) { diff --git a/x/pocketcore/keeper/claim.go b/x/pocketcore/keeper/claim.go index 5403f0f7..8c8a2e73 100644 --- a/x/pocketcore/keeper/claim.go +++ b/x/pocketcore/keeper/claim.go @@ -3,6 +3,7 @@ package keeper import ( "encoding/hex" "fmt" + "time" "github.com/pokt-network/pocket-core/crypto" sdk "github.com/pokt-network/pocket-core/types" @@ -13,18 +14,15 @@ import ( ) // "SendClaimTx" - Automatically sends a claim of work/challenge based on relays or challenges stored. -func (k Keeper) SendClaimTx(ctx sdk.Ctx, keeper Keeper, n client.Client, claimTx func(pk crypto.PrivateKey, cliCtx util.CLIContext, txBuilder auth.TxBuilder, header pc.SessionHeader, totalProofs int64, root pc.HashRange, evidenceType pc.EvidenceType) (*sdk.TxResponse, error)) { +func (k Keeper) SendClaimTx(ctx sdk.Ctx, keeper Keeper, n client.Client, node *pc.PocketNode, claimTx func(pk crypto.PrivateKey, cliCtx util.CLIContext, txBuilder auth.TxBuilder, header pc.SessionHeader, totalProofs int64, root pc.HashRange, evidenceType pc.EvidenceType) (*sdk.TxResponse, error)) { // get the private val key (main) account from the keybase - kp, err := k.GetPKFromFile(ctx) - if err != nil { - ctx.Logger().Error(fmt.Sprintf("an error occured retrieving the private key from file for the claim transaction:\n%s", err.Error())) - return - } + address := node.GetAddress() // retrieve the iterator to go through each piece of evidence in storage - iter := pc.EvidenceIterator() + iter := pc.EvidenceIterator(node.EvidenceStore) defer iter.Close() // loop through each evidence for ; iter.Valid(); iter.Next() { + now := time.Now() evidence := iter.Value() // if the number of proofs in the evidence object is zero if evidence.NumOfProofs == 0 { @@ -41,7 +39,7 @@ func (k Keeper) SendClaimTx(ctx sdk.Ctx, keeper Keeper, n client.Client, claimTx } // if the evidence length is less than minimum, it would not satisfy our merkle tree needs if evidence.NumOfProofs < keeper.MinimumNumberOfProofs(sessionCtx) { - if err := pc.DeleteEvidence(evidence.SessionHeader, evidenceType); err != nil { + if err := pc.DeleteEvidence(evidence.SessionHeader, evidenceType, node.EvidenceStore); err != nil { ctx.Logger().Debug(err.Error()) } continue @@ -53,32 +51,36 @@ func (k Keeper) SendClaimTx(ctx sdk.Ctx, keeper Keeper, n client.Client, claimTx // if the blockchain in the evidence is not supported then delete it because nodes don't get paid/challenged for unsupported blockchains if !k.IsPocketSupportedBlockchain(sessionCtx.WithBlockHeight(evidence.SessionHeader.SessionBlockHeight), evidence.SessionHeader.Chain) { ctx.Logger().Info(fmt.Sprintf("claim for %s blockchain isn't pocket supported, so will not send. Deleting evidence\n", evidence.SessionHeader.Chain)) - if err := pc.DeleteEvidence(evidence.SessionHeader, evidenceType); err != nil { + if err := pc.DeleteEvidence(evidence.SessionHeader, evidenceType, node.EvidenceStore); err != nil { ctx.Logger().Debug(err.Error()) } continue } // check the current state to see if the unverified evidence has already been sent and processed (if so, then skip this evidence) - if _, found := k.GetClaim(ctx, sdk.Address(kp.PublicKey().Address()), evidence.SessionHeader, evidenceType); found { + if _, found := k.GetClaim(ctx, address, evidence.SessionHeader, evidenceType); found { continue } // if the claim is mature, delete it because we cannot submit a mature claim if k.ClaimIsMature(ctx, evidence.SessionBlockHeight) { - if err := pc.DeleteEvidence(evidence.SessionHeader, evidenceType); err != nil { + if err := pc.DeleteEvidence(evidence.SessionHeader, evidenceType, node.EvidenceStore); err != nil { ctx.Logger().Debug(err.Error()) } continue } // generate the merkle root for this evidence - root := evidence.GenerateMerkleRoot(evidence.SessionHeader.SessionBlockHeight) + root := evidence.GenerateMerkleRoot(evidence.SessionHeader.SessionBlockHeight, node.EvidenceStore) + claimTxTotalTime := float64(time.Since(now).Milliseconds()) + go func() { + pc.GlobalServiceMetric().AddClaimTiming(evidence.SessionHeader.Chain, claimTxTotalTime, &address) + }() // generate the auto txbuilder and clictx - txBuilder, cliCtx, err := newTxBuilderAndCliCtx(ctx, &pc.MsgClaim{}, n, kp, k) + txBuilder, cliCtx, err := newTxBuilderAndCliCtx(ctx, &pc.MsgClaim{}, n, node.PrivateKey, k) if err != nil { ctx.Logger().Error(fmt.Sprintf("an error occured creating the tx builder for the claim tx:\n%s", err.Error())) return } // send in the evidence header, the total relays completed, and the merkle root (ensures data integrity) - if _, err := claimTx(kp, cliCtx, txBuilder, evidence.SessionHeader, evidence.NumOfProofs, root, evidenceType); err != nil { + if _, err := claimTx(node.PrivateKey, cliCtx, txBuilder, evidence.SessionHeader, evidence.NumOfProofs, root, evidenceType); err != nil { ctx.Logger().Error(fmt.Sprintf("an error occured executing the claim transaciton: \n%s", err.Error())) } } @@ -122,7 +124,7 @@ func (k Keeper) ValidateClaim(ctx sdk.Ctx, claim pc.MsgClaim) (err sdk.Error) { // get the session node count for the time of the session sessionNodeCount := int(k.SessionNodeCount(sessionContext)) // check cache - session, found := pc.GetSession(claim.SessionHeader) + session, found := pc.GetSession(claim.SessionHeader, pc.GlobalSessionCache) if !found { // use the session end context to ensure that people who were jailed mid session do not get to submit claims sessionEndCtx, er := ctx.PrevCtx(sessionEndHeight) diff --git a/x/pocketcore/keeper/claim_test.go b/x/pocketcore/keeper/claim_test.go index e9a48034..735a7bb1 100644 --- a/x/pocketcore/keeper/claim_test.go +++ b/x/pocketcore/keeper/claim_test.go @@ -12,11 +12,11 @@ import ( func TestKeeper_GetSetClaim(t *testing.T) { ctx, _, _, _, keeper, _, _ := createTestInput(t, false) npk, header, _ := simulateRelays(t, keeper, &ctx, 5) - evidence, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(100000)) + evidence, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(100000), types.GlobalEvidenceCache) assert.Nil(t, err) claim := types.MsgClaim{ SessionHeader: header, - MerkleRoot: evidence.GenerateMerkleRoot(0), + MerkleRoot: evidence.GenerateMerkleRoot(0, types.GlobalEvidenceCache), TotalProofs: 9, FromAddress: sdk.Address(npk.Address()), EvidenceType: types.RelayEvidence, @@ -40,11 +40,11 @@ func TestKeeper_GetSetDeleteClaims(t *testing.T) { for i := 0; i < 2; i++ { npk, header, _ := simulateRelays(t, keeper, &ctx, 5) - evidence, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(1000)) + evidence, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(1000), types.GlobalEvidenceCache) assert.Nil(t, err) claim := types.MsgClaim{ SessionHeader: header, - MerkleRoot: evidence.GenerateMerkleRoot(0), + MerkleRoot: evidence.GenerateMerkleRoot(0, types.GlobalEvidenceCache), TotalProofs: 9, FromAddress: sdk.Address(sdk.Address(npk.Address())), EvidenceType: types.RelayEvidence, @@ -70,21 +70,21 @@ func TestKeeper_GetMatureClaims(t *testing.T) { npk, header, _ := simulateRelays(t, keeper, &ctx, 5) npk2, header2, _ := simulateRelays(t, keeper, &ctx, 20) - i, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(1000)) + i, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(1000), types.GlobalEvidenceCache) assert.Nil(t, err) - i2, err := types.GetEvidence(header2, types.RelayEvidence, sdk.NewInt(1000)) + i2, err := types.GetEvidence(header2, types.RelayEvidence, sdk.NewInt(1000), types.GlobalEvidenceCache) assert.Nil(t, err) matureClaim := types.MsgClaim{ SessionHeader: header, - MerkleRoot: i.GenerateMerkleRoot(0), + MerkleRoot: i.GenerateMerkleRoot(0, types.GlobalEvidenceCache), TotalProofs: 9, FromAddress: sdk.Address(npk.Address()), EvidenceType: types.RelayEvidence, } immatureClaim := types.MsgClaim{ SessionHeader: header2, - MerkleRoot: i2.GenerateMerkleRoot(0), + MerkleRoot: i2.GenerateMerkleRoot(0, types.GlobalEvidenceCache), TotalProofs: 9, FromAddress: sdk.Address(npk2.Address()), EvidenceType: types.RelayEvidence, @@ -117,13 +117,13 @@ func TestKeeper_DeleteExpiredClaims(t *testing.T) { npk, header, _ := simulateRelays(t, keeper, &ctx, 5) npk2, header2, _ := simulateRelays(t, keeper, &ctx, 20) - i, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(1000)) + i, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(1000), types.GlobalEvidenceCache) assert.Nil(t, err) - i2, err := types.GetEvidence(header2, types.RelayEvidence, sdk.NewInt(1000)) + i2, err := types.GetEvidence(header2, types.RelayEvidence, sdk.NewInt(1000), types.GlobalEvidenceCache) assert.Nil(t, err) expiredClaim := types.MsgClaim{ SessionHeader: header, - MerkleRoot: i.GenerateMerkleRoot(0), + MerkleRoot: i.GenerateMerkleRoot(0, types.GlobalEvidenceCache), TotalProofs: 9, FromAddress: sdk.Address(npk.Address()), EvidenceType: types.RelayEvidence, @@ -131,7 +131,7 @@ func TestKeeper_DeleteExpiredClaims(t *testing.T) { header2.SessionBlockHeight = int64(20) // NOTE start a later block than 1 notExpired := types.MsgClaim{ SessionHeader: header2, - MerkleRoot: i2.GenerateMerkleRoot(0), + MerkleRoot: i2.GenerateMerkleRoot(0, types.GlobalEvidenceCache), TotalProofs: 9, FromAddress: sdk.Address(npk2.Address()), EvidenceType: types.RelayEvidence, diff --git a/x/pocketcore/keeper/common_test.go b/x/pocketcore/keeper/common_test.go index 05a41ee3..693fab3a 100644 --- a/x/pocketcore/keeper/common_test.go +++ b/x/pocketcore/keeper/common_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" types2 "github.com/pokt-network/pocket-core/codec/types" + "github.com/tendermint/tendermint/privval" "math" "math/big" "os" @@ -35,7 +36,6 @@ import ( abci "github.com/tendermint/tendermint/abci/types" abcitypes "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/privval" tmStore "github.com/tendermint/tendermint/store" tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" @@ -84,16 +84,7 @@ func createTestInput(t *testing.T, isCheckTx bool) (sdk.Ctx, []nodesTypes.Valida kb := NewTestKeybase() _, err := kb.Create("test") assert.Nil(t, err) - cb, err := kb.GetCoinbase() - assert.Nil(t, err) - addr := tmtypes.Address(cb.GetAddress()) - pk, err := kb.ExportPrivateKeyObject(cb.GetAddress(), "test") - assert.Nil(t, err) - types.InitPVKeyFile(privval.FilePVKey{ - Address: addr, - PubKey: cb.PublicKey, - PrivKey: pk, - }) + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) keyParams := sdk.ParamsKey tkeyParams := sdk.ParamsTKey @@ -155,7 +146,20 @@ func createTestInput(t *testing.T, isCheckTx bool) (sdk.Ctx, []nodesTypes.Valida URL: "https://www.google.com:443", }}, } + + cb, err := kb.GetCoinbase() + assert.Nil(t, err) + addr := tmtypes.Address(cb.GetAddress()) + pk, err := kb.ExportPrivateKeyObject(cb.GetAddress(), "test") + assert.Nil(t, err) + types.CleanPocketNodes() + types.AddPocketNodeByFilePVKey(privval.FilePVKey{ + Address: addr, + PubKey: cb.PublicKey, + PrivKey: pk, + }, ctx.Logger()) types.InitConfig(&hb, log.NewTMLogger(os.Stdout), sdk.DefaultTestingPocketConfig()) + authSubspace := sdk.NewSubspace(auth.DefaultParamspace) nodesSubspace := sdk.NewSubspace(nodesTypes.DefaultParamspace) appSubspace := sdk.NewSubspace(appsTypes.DefaultParamspace) @@ -186,6 +190,11 @@ func createTestInput(t *testing.T, isCheckTx bool) (sdk.Ctx, []nodesTypes.Valida return ctx, vals, ap, accs, keeper, keys, kb } +func createTestInputWithLean(t *testing.T, isCheckTx bool) (sdk.Ctx, []nodesTypes.Validator, []appsTypes.Application, []auth.BaseAccount, Keeper, map[string]*sdk.KVStoreKey, keys.Keybase) { + ctx, vals, ap, accs, keeper, keys, kb := createTestInput(t, isCheckTx) + return ctx, vals, ap, accs, keeper, keys, kb +} + var ( testApp appsTypes.Application testAppPrivateKey crypto.PrivateKey @@ -376,7 +385,7 @@ func simulateRelays(t *testing.T, k Keeper, ctx *sdk.Ctx, maxRelays int) (npk cr // NOTE Add a minimum of 5 proofs to memInvoice to be able to create a merkle tree for j := 0; j < maxRelays; j++ { proof := createProof(getTestApplicationPrivateKey(), clientKey, npk, ethereum, j) - types.SetProof(validHeader, types.RelayEvidence, proof, sdk.NewInt(100000)) + types.SetProof(validHeader, types.RelayEvidence, proof, sdk.NewInt(100000), types.GlobalEvidenceCache) } mockCtx := new(Ctx) mockCtx.On("KVStore", k.storeKey).Return((*ctx).KVStore(k.storeKey)) @@ -385,6 +394,7 @@ func simulateRelays(t *testing.T, k Keeper, ctx *sdk.Ctx, maxRelays int) (npk cr keys = simulateRelayKeys{getTestApplicationPrivateKey(), clientKey} return } + func createProof(private, client crypto.PrivateKey, npk crypto.PublicKey, chain string, entropy int) types.Proof { aat := types.AAT{ Version: "0.0.1", diff --git a/x/pocketcore/keeper/keeper.go b/x/pocketcore/keeper/keeper.go index 0e13646e..24aca562 100644 --- a/x/pocketcore/keeper/keeper.go +++ b/x/pocketcore/keeper/keeper.go @@ -4,6 +4,7 @@ import ( "github.com/pokt-network/pocket-core/codec" sdk "github.com/pokt-network/pocket-core/types" "github.com/pokt-network/pocket-core/x/pocketcore/types" + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/rpc/client" coretypes "github.com/tendermint/tendermint/rpc/core/types" ) @@ -58,3 +59,26 @@ func (k Keeper) ConvertState(ctx sdk.Ctx) { k.SetClaims(ctx, claims) k.Cdc.DisableUpgradeOverride() } + +func (k Keeper) ConsensusParamUpdate(ctx sdk.Ctx) *abci.ConsensusParams { + currentHeightBlockSize := k.BlockByteSize(ctx) + //If not 0 and different update + if currentHeightBlockSize > 0 { + previousBlockCtx, _ := ctx.PrevCtx(ctx.BlockHeight() - 1) + lastBlockSize := k.BlockByteSize(previousBlockCtx) + if lastBlockSize != currentHeightBlockSize { + //not go under default value + if currentHeightBlockSize < types.DefaultBlockByteSize { + return &abci.ConsensusParams{} + } + return &abci.ConsensusParams{ + Block: &abci.BlockParams{ + MaxBytes: currentHeightBlockSize, + MaxGas: -1, + }, + } + } + } + + return &abci.ConsensusParams{} +} diff --git a/x/pocketcore/keeper/nodes.go b/x/pocketcore/keeper/nodes.go index 785bfeb2..ecd861f2 100644 --- a/x/pocketcore/keeper/nodes.go +++ b/x/pocketcore/keeper/nodes.go @@ -1,10 +1,8 @@ package keeper import ( - "github.com/pokt-network/pocket-core/crypto" sdk "github.com/pokt-network/pocket-core/types" "github.com/pokt-network/pocket-core/x/nodes/exported" - pc "github.com/pokt-network/pocket-core/x/pocketcore/types" ) // "GetNode" - Gets a node from the state storage @@ -16,35 +14,6 @@ func (k Keeper) GetNode(ctx sdk.Ctx, address sdk.Address) (n exported.ValidatorI return n, true } -func (k Keeper) GetSelfAddress(ctx sdk.Ctx) sdk.Address { - kp, err := k.GetPKFromFile(ctx) - if err != nil { - ctx.Logger().Error("Unable to retrieve selfAddress: " + err.Error()) - return nil - } - return sdk.Address(kp.PublicKey().Address()) -} - -func (k Keeper) GetSelfPrivKey(ctx sdk.Ctx) (crypto.PrivateKey, sdk.Error) { - // get the private key from the private validator file - pk, er := k.GetPKFromFile(ctx) - if er != nil { - return nil, pc.NewKeybaseError(pc.ModuleName, er) - } - return pk, nil -} - -// "GetSelfNode" - Gets self node (private val key) from the world state -func (k Keeper) GetSelfNode(ctx sdk.Ctx) (node exported.ValidatorI, er sdk.Error) { - // get the node from the world state - self, found := k.GetNode(ctx, k.GetSelfAddress(ctx)) - if !found { - er = pc.NewSelfNotFoundError(pc.ModuleName) - return nil, er - } - return self, nil -} - // "AwardCoinsForRelays" - Award coins to nodes for relays completed using the nodes keeper func (k Keeper) AwardCoinsForRelays(ctx sdk.Ctx, relays int64, toAddr sdk.Address) sdk.BigInt { return k.posKeeper.RewardForRelays(ctx, sdk.NewInt(relays), toAddr) diff --git a/x/pocketcore/keeper/params.go b/x/pocketcore/keeper/params.go index 87de6a4f..780e89b8 100644 --- a/x/pocketcore/keeper/params.go +++ b/x/pocketcore/keeper/params.go @@ -52,13 +52,18 @@ func (k Keeper) SupportedBlockchains(ctx sdk.Ctx) (res []string) { return } -// "SupportedBlockchains" - Returns a supported blockchain parameter from the paramstore +// "MinimumNumberOfProofs" - Returns a minimun number of proofs parameter from the paramstore // What blockchains are supported in pocket network (list of network identifier hashes) func (k Keeper) MinimumNumberOfProofs(ctx sdk.Ctx) (res int64) { k.Paramstore.Get(ctx, types.KeyMinimumNumberOfProofs, &res) return } +func (k Keeper) BlockByteSize(ctx sdk.Ctx) (res int64) { + k.Paramstore.Get(ctx, types.KeyBlockByteSize, &res) + return +} + // "GetParams" - Returns all module parameters in a `Params` struct func (k Keeper) GetParams(ctx sdk.Ctx) types.Params { return types.Params{ @@ -68,6 +73,7 @@ func (k Keeper) GetParams(ctx sdk.Ctx) types.Params { ClaimExpiration: k.ClaimExpiration(ctx), ReplayAttackBurnMultiplier: k.ReplayAttackBurnMultiplier(ctx), MinimumNumberOfProofs: k.MinimumNumberOfProofs(ctx), + BlockByteSize: k.BlockByteSize(ctx), } } diff --git a/x/pocketcore/keeper/params_test.go b/x/pocketcore/keeper/params_test.go index e15418bc..f9d6385b 100644 --- a/x/pocketcore/keeper/params_test.go +++ b/x/pocketcore/keeper/params_test.go @@ -89,3 +89,12 @@ func TestKeeper_SetParams(t *testing.T) { paramz := k.GetParams(ctx) assert.Equal(t, paramz, p) } + +func TestKeeper_BlockByteSize(t *testing.T) { + ctx, _, _, _, keeper, _, _ := createTestInput(t, false) + blocksize := keeper.BlockByteSize(ctx) + assert.NotNil(t, blocksize) + assert.Empty(t, blocksize) + //expected 0 as we are not using a default value for compatibility + assert.Equal(t, int64(0), blocksize) +} diff --git a/x/pocketcore/keeper/proof.go b/x/pocketcore/keeper/proof.go index b4e0653f..fc6e5f39 100644 --- a/x/pocketcore/keeper/proof.go +++ b/x/pocketcore/keeper/proof.go @@ -13,48 +13,45 @@ import ( "github.com/tendermint/tendermint/rpc/client" "math" "reflect" + "time" ) // auto sends a proof transaction for the claim -func (k Keeper) SendProofTx(ctx sdk.Ctx, n client.Client, proofTx func(cliCtx util.CLIContext, txBuilder auth.TxBuilder, merkleProof pc.MerkleProof, leafNode pc.Proof, evidenceType pc.EvidenceType) (*sdk.TxResponse, error)) { - kp, err := k.GetPKFromFile(ctx) - if err != nil { - ctx.Logger().Error(fmt.Sprintf("an error occured retrieving the pk from the file for the Proof Transaction:\n%v", err)) - return - } - // get the self address - addr := sdk.Address(kp.PublicKey().Address()) +func (k Keeper) SendProofTx(ctx sdk.Ctx, n client.Client, node *pc.PocketNode, proofTx func(cliCtx util.CLIContext, txBuilder auth.TxBuilder, merkleProof pc.MerkleProof, leafNode pc.Proof, evidenceType pc.EvidenceType) (*sdk.TxResponse, error)) { + addr := node.GetAddress() // get all mature (waiting period has passed) claims for your address claims, err := k.GetMatureClaims(ctx, addr) if err != nil { ctx.Logger().Error(fmt.Sprintf("an error occured getting the mature claims in the Proof Transaction:\n%v", err)) return } + // for every claim of the mature set for _, claim := range claims { + now := time.Now() // check to see if evidence is stored in cache - evidence, err := pc.GetEvidence(claim.SessionHeader, claim.EvidenceType, sdk.ZeroInt()) + evidence, err := pc.GetEvidence(claim.SessionHeader, claim.EvidenceType, sdk.ZeroInt(), node.EvidenceStore) if err != nil || evidence.Proofs == nil || len(evidence.Proofs) == 0 { ctx.Logger().Info(fmt.Sprintf("the evidence object for evidence is not found, ignoring pending claim for app: %s, at sessionHeight: %d", claim.SessionHeader.ApplicationPubKey, claim.SessionHeader.SessionBlockHeight)) continue } if ctx.BlockHeight()-claim.SessionHeader.SessionBlockHeight > int64(pc.GlobalPocketConfig.MaxClaimAgeForProofRetry) { - err := pc.DeleteEvidence(claim.SessionHeader, claim.EvidenceType) + err := pc.DeleteEvidence(claim.SessionHeader, claim.EvidenceType, node.EvidenceStore) ctx.Logger().Error(fmt.Sprintf("deleting evidence older than MaxClaimAgeForProofRetry")) if err != nil { ctx.Logger().Error(fmt.Sprintf("unable to delete evidence that is older than 32 blocks: %s", err.Error())) } continue } - if !evidence.IsSealed() { - err := pc.DeleteEvidence(claim.SessionHeader, claim.EvidenceType) + if !node.EvidenceStore.IsSealed(evidence) { + err := pc.DeleteEvidence(claim.SessionHeader, claim.EvidenceType, node.EvidenceStore) ctx.Logger().Error(fmt.Sprintf("evidence is not sealed, could cause a relay leak:")) if err != nil { ctx.Logger().Error(fmt.Sprintf("could not delete evidence is not sealed, could cause a relay leak: %s", err.Error())) } } if evidence.NumOfProofs != claim.TotalProofs { - err := pc.DeleteEvidence(claim.SessionHeader, claim.EvidenceType) + err := pc.DeleteEvidence(claim.SessionHeader, claim.EvidenceType, node.EvidenceStore) ctx.Logger().Error(fmt.Sprintf("evidence num of proofs does not equal claim total proofs... possible relay leak")) if err != nil { ctx.Logger().Error(fmt.Sprintf("evidence num of proofs does not equal claim total proofs... possible relay leak: %s", err.Error())) @@ -87,8 +84,12 @@ func (k Keeper) SendProofTx(ctx sdk.Ctx, n client.Client, proofTx func(cliCtx ut continue } } + proofTxTotalTime := float64(time.Since(now)) + go func() { + pc.GlobalServiceMetric().AddProofTiming(evidence.SessionHeader.Chain, proofTxTotalTime, &addr) + }() // generate the auto txbuilder and clictx - txBuilder, cliCtx, err := newTxBuilderAndCliCtx(ctx, &pc.MsgProof{}, n, kp, k) + txBuilder, cliCtx, err := newTxBuilderAndCliCtx(ctx, &pc.MsgProof{}, n, node.PrivateKey, k) if err != nil { ctx.Logger().Error(fmt.Sprintf("an error occured in the transaction process of the Proof Transaction:\n%v", err)) return @@ -233,11 +234,8 @@ func newTxBuilderAndCliCtx(ctx sdk.Ctx, msg sdk.ProtoMsg, n client.Client, key c fromAddr := sdk.Address(key.PublicKey().Address()) // create a client context for sending cliCtx = util.NewCLIContext(n, fromAddr, "").WithCodec(k.Cdc).WithHeight(ctx.BlockHeight()) - pk, err := k.GetPKFromFile(ctx) - if err != nil { - return txBuilder, cliCtx, err - } - cliCtx.PrivateKey = pk + + cliCtx.PrivateKey = key // broadcast synchronously cliCtx.BroadcastMode = util.BroadcastSync // get the account to ensure balance diff --git a/x/pocketcore/keeper/proof_test.go b/x/pocketcore/keeper/proof_test.go index d962d415..237b9457 100644 --- a/x/pocketcore/keeper/proof_test.go +++ b/x/pocketcore/keeper/proof_test.go @@ -15,14 +15,14 @@ import ( func TestKeeper_ValidateProof(t *testing.T) { // happy path only todo ctx, _, _, _, keeper, keys, _ := createTestInput(t, false) - types.ClearEvidence() + types.ClearEvidence(types.GlobalEvidenceCache) npk, header, _ := simulateRelays(t, keeper, &ctx, 5) - evidence, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(1000)) + evidence, err := types.GetEvidence(header, types.RelayEvidence, sdk.NewInt(1000), types.GlobalEvidenceCache) if err != nil { t.Fatalf("Set evidence not found") } - root := evidence.GenerateMerkleRoot(0) - _, totalRelays := types.GetTotalProofs(header, types.RelayEvidence, sdk.NewInt(1000)) + root := evidence.GenerateMerkleRoot(0, types.GlobalEvidenceCache) + _, totalRelays := types.GetTotalProofs(header, types.RelayEvidence, sdk.NewInt(1000), types.GlobalEvidenceCache) assert.Equal(t, totalRelays, int64(5)) // generate a claim message claimMsg := types.MsgClaim{ @@ -47,7 +47,7 @@ func TestKeeper_ValidateProof(t *testing.T) { // happy path only todo assert.Nil(t, er) merkleProofs, _ := evidence.GenerateMerkleProof(0, int(neededLeafIndex)) // get leaf and cousin node - leafNode := types.GetProof(header, types.RelayEvidence, neededLeafIndex) + leafNode := types.GetProof(header, types.RelayEvidence, neededLeafIndex, types.GlobalEvidenceCache) // create proof message proofMsg := types.MsgProof{ MerkleProof: merkleProofs, diff --git a/x/pocketcore/keeper/service.go b/x/pocketcore/keeper/service.go index 204977f0..f2d437df 100644 --- a/x/pocketcore/keeper/service.go +++ b/x/pocketcore/keeper/service.go @@ -3,27 +3,42 @@ package keeper import ( "encoding/hex" "fmt" - "time" - + "github.com/pokt-network/pocket-core/crypto" sdk "github.com/pokt-network/pocket-core/types" pc "github.com/pokt-network/pocket-core/x/pocketcore/types" + "time" ) -// "HandleRelay" - Handles an api (read/write) request to a non-native (external) blockchain +// HandleRelay handles an api (read/write) request to a non-native (external) blockchain func (k Keeper) HandleRelay(ctx sdk.Ctx, relay pc.Relay) (*pc.RelayResponse, sdk.Error) { relayTimeStart := time.Now() // get the latest session block height because this relay will correspond with the latest session sessionBlockHeight := k.GetLatestSessionBlockHeight(ctx) - // get self node (your validator) from the current state - pk, err := k.GetSelfPrivKey(ctx) - if err != nil { - return nil, err + var node *pc.PocketNode + // There is reference to node address so that way we don't have to recreate address twice for pre-leanpokt + var nodeAddress sdk.Address + + if pc.GlobalPocketConfig.LeanPocket { + // if lean pocket enabled, grab the targeted servicer through the relay proof + servicerRelayPublicKey, err := crypto.NewPublicKey(relay.Proof.ServicerPubKey) + if err != nil { + return nil, sdk.ErrInternal("Could not convert servicer hex to public key") + } + nodeAddress = sdk.GetAddress(servicerRelayPublicKey) + node, err = pc.GetPocketNodeByAddress(&nodeAddress) + if err != nil { + return nil, sdk.ErrInternal("Failed to find correct servicer PK") + } + } else { + // get self node (your validator) from the current state + node = pc.GetPocketNode() + nodeAddress = node.GetAddress() } - selfAddr := sdk.Address(pk.PublicKey().Address()) + // retrieve the nonNative blockchains your node is hosting hostedBlockchains := k.GetHostedBlockchains() // ensure the validity of the relay - maxPossibleRelays, err := relay.Validate(ctx, k.posKeeper, k.appKeeper, k, selfAddr, hostedBlockchains, sessionBlockHeight) + maxPossibleRelays, err := relay.Validate(ctx, k.posKeeper, k.appKeeper, k, hostedBlockchains, sessionBlockHeight, node) if err != nil { if pc.GlobalPocketConfig.RelayErrors { ctx.Logger().Error( @@ -38,7 +53,7 @@ func (k Keeper) HandleRelay(ctx sdk.Ctx, relay pc.Relay) (*pc.RelayResponse, sdk "could not validate relay for app: %s, for chainID %v on node %s, at session height: %v, with error: %s", relay.Proof.ServicerPubKey, relay.Proof.Blockchain, - selfAddr.String(), + nodeAddress.String(), sessionBlockHeight, err.Error(), ), @@ -47,9 +62,9 @@ func (k Keeper) HandleRelay(ctx sdk.Ctx, relay pc.Relay) (*pc.RelayResponse, sdk return nil, err } // store the proof before execution, because the proof corresponds to the previous relay - relay.Proof.Store(maxPossibleRelays) + relay.Proof.Store(maxPossibleRelays, node.EvidenceStore) // attempt to execute - respPayload, err := relay.Execute(hostedBlockchains) + respPayload, err := relay.Execute(hostedBlockchains, &nodeAddress) if err != nil { ctx.Logger().Error(fmt.Sprintf("could not send relay with error: %s", err.Error())) return nil, err @@ -60,11 +75,11 @@ func (k Keeper) HandleRelay(ctx sdk.Ctx, relay pc.Relay) (*pc.RelayResponse, sdk Proof: relay.Proof, } // sign the response - sig, er := pk.Sign(resp.Hash()) + sig, er := node.PrivateKey.Sign(resp.Hash()) if er != nil { ctx.Logger().Error( fmt.Sprintf("could not sign response for address: %s with hash: %v, with error: %s", - selfAddr.String(), resp.HashString(), er.Error()), + nodeAddress.String(), resp.HashString(), er.Error()), ) return nil, pc.NewKeybaseError(pc.ModuleName, er) } @@ -73,15 +88,49 @@ func (k Keeper) HandleRelay(ctx sdk.Ctx, relay pc.Relay) (*pc.RelayResponse, sdk // track the relay time relayTime := time.Since(relayTimeStart) // add to metrics - pc.GlobalServiceMetric().AddRelayTimingFor(relay.Proof.Blockchain, float64(relayTime.Milliseconds())) - pc.GlobalServiceMetric().AddRelayFor(relay.Proof.Blockchain) + addRelayMetricsFunc := func() { + pc.GlobalServiceMetric().AddRelayTimingFor(relay.Proof.Blockchain, float64(relayTime.Milliseconds()), &nodeAddress) + pc.GlobalServiceMetric().AddRelayFor(relay.Proof.Blockchain, &nodeAddress) + } + if pc.GlobalPocketConfig.LeanPocket { + go addRelayMetricsFunc() + } else { + addRelayMetricsFunc() + } return resp, nil } // "HandleChallenge" - Handles a client relay response challenge request func (k Keeper) HandleChallenge(ctx sdk.Ctx, challenge pc.ChallengeProofInvalidData) (*pc.ChallengeResponse, sdk.Error) { - // get self node (your validator) from the current state - selfNode := k.GetSelfAddress(ctx) + + var node *pc.PocketNode + // There is reference to self address so that way we don't have to recreate address twice for pre-leanpokt + var nodeAddress sdk.Address + + if pc.GlobalPocketConfig.LeanPocket { + // try to retrieve a PocketNode that was part of session + for _, r := range challenge.MajorityResponses { + servicerRelayPublicKey, err := crypto.NewPublicKey(r.Proof.ServicerPubKey) + if err != nil { + continue + } + potentialNodeAddress := sdk.GetAddress(servicerRelayPublicKey) + potentialNode, err := pc.GetPocketNodeByAddress(&nodeAddress) + if err != nil || potentialNode == nil { + continue + } + node = potentialNode + nodeAddress = potentialNodeAddress + break + } + if node == nil { + return nil, pc.NewNodeNotInSessionError(pc.ModuleName) + } + } else { + node = pc.GetPocketNode() + nodeAddress = node.GetAddress() + } + sessionBlkHeight := k.GetLatestSessionBlockHeight(ctx) // get the session context sessionCtx, er := ctx.PrevCtx(sessionBlkHeight) @@ -100,7 +149,7 @@ func (k Keeper) HandleChallenge(ctx sdk.Ctx, challenge pc.ChallengeProofInvalidD SessionBlockHeight: sessionCtx.BlockHeight(), } // check cache - session, found := pc.GetSession(header) + session, found := pc.GetSession(header, node.SessionStore) // if not found generate the session if !found { var err sdk.Error @@ -113,16 +162,22 @@ func (k Keeper) HandleChallenge(ctx sdk.Ctx, challenge pc.ChallengeProofInvalidD return nil, err } // add to cache - pc.SetSession(session) + pc.SetSession(session, node.SessionStore) } // validate the challenge - err := challenge.ValidateLocal(header, app.GetMaxRelays(), app.GetChains(), int(k.SessionNodeCount(sessionCtx)), session.SessionNodes, selfNode) + err := challenge.ValidateLocal(header, app.GetMaxRelays(), app.GetChains(), int(k.SessionNodeCount(sessionCtx)), session.SessionNodes, nodeAddress, node.EvidenceStore) if err != nil { return nil, err } // store the challenge in memory - challenge.Store(app.GetMaxRelays()) + challenge.Store(app.GetMaxRelays(), node.EvidenceStore) // update metric - pc.GlobalServiceMetric().AddChallengeFor(header.Chain) + + if pc.GlobalPocketConfig.LeanPocket { + go pc.GlobalServiceMetric().AddChallengeFor(header.Chain, &nodeAddress) + } else { + pc.GlobalServiceMetric().AddChallengeFor(header.Chain, &nodeAddress) + } + return &pc.ChallengeResponse{Response: fmt.Sprintf("successfully stored challenge proof for %s", challenge.MinorityResponse.Proof.ServicerPubKey)}, nil } diff --git a/x/pocketcore/keeper/session.go b/x/pocketcore/keeper/session.go index 43981dfe..6466bbc3 100644 --- a/x/pocketcore/keeper/session.go +++ b/x/pocketcore/keeper/session.go @@ -24,7 +24,7 @@ func (k Keeper) HandleDispatch(ctx sdk.Ctx, header types.SessionHeader) (*types. return nil, sdk.ErrInternal(er.Error()) } // check cache - session, found := types.GetSession(header) + session, found := types.GetSession(header, types.GlobalSessionCache) // if not found generate the session if !found { var err sdk.Error @@ -37,7 +37,7 @@ func (k Keeper) HandleDispatch(ctx sdk.Ctx, header types.SessionHeader) (*types. return nil, err } // add to cache - types.SetSession(session) + types.SetSession(session, types.GlobalSessionCache) } actualNodes := make([]exported.ValidatorI, len(session.SessionNodes)) for i, addr := range session.SessionNodes { @@ -85,5 +85,5 @@ func (k Keeper) IsPocketSupportedBlockchain(ctx sdk.Ctx, chain string) bool { } func (Keeper) ClearSessionCache() { - types.ClearSessionCache() + types.ClearSessionCache(types.GlobalSessionCache) } diff --git a/x/pocketcore/keeper/util.go b/x/pocketcore/keeper/util.go index eb67601e..b55569d4 100644 --- a/x/pocketcore/keeper/util.go +++ b/x/pocketcore/keeper/util.go @@ -1,22 +1 @@ package keeper - -import ( - "github.com/pokt-network/pocket-core/crypto" - sdk "github.com/pokt-network/pocket-core/types" - "github.com/pokt-network/pocket-core/x/pocketcore/types" -) - -// "GetPKFromFile" - Returns the private key object from a file -func (k Keeper) GetPKFromFile(ctx sdk.Ctx) (crypto.PrivateKey, error) { - // get the Private validator key from the file - pvKey, err := types.GetPVKeyFile() - if err != nil { - return nil, err - } - // convert the privKey to a private key object (compatible interface) - pk, er := crypto.PrivKeyToPrivateKey(pvKey.PrivKey) - if er != nil { - return nil, er - } - return pk, nil -} diff --git a/x/pocketcore/module.go b/x/pocketcore/module.go index e7c74821..6e722c46 100644 --- a/x/pocketcore/module.go +++ b/x/pocketcore/module.go @@ -55,6 +55,11 @@ type AppModule struct { keeper keeper.Keeper // responsible for store operations } +func (am AppModule) ConsensusParamsUpdate(ctx sdk.Ctx) *abci.ConsensusParams { + + return am.keeper.ConsensusParamUpdate(ctx) +} + func (am AppModule) UpgradeCodec(ctx sdk.Ctx) { am.keeper.UpgradeCodec(ctx) } @@ -92,6 +97,12 @@ func (am AppModule) NewQuerierHandler() sdk.Querier { // "BeginBlock" - Functionality that is called at the beginning of (every) block func (am AppModule) BeginBlock(ctx sdk.Ctx, req abci.RequestBeginBlock) { + if am.keeper.Cdc.IsOnNamedFeatureActivationHeight(ctx.BlockHeight(), codec.BlockSizeModifyKey) { + //on the height we set the default value + params := am.keeper.GetParams(ctx) + params.BlockByteSize = types.DefaultBlockByteSize + am.keeper.SetParams(ctx, params) + } // delete the expired claims am.keeper.DeleteExpiredClaims(ctx) } @@ -100,33 +111,38 @@ func (am AppModule) BeginBlock(ctx sdk.Ctx, req abci.RequestBeginBlock) { func (am AppModule) EndBlock(ctx sdk.Ctx, _ abci.RequestEndBlock) []abci.ValidatorUpdate { // get blocks per session blocksPerSession := am.keeper.BlocksPerSession(ctx) - // get self address - addr := am.keeper.GetSelfAddress(ctx) - if addr != nil { - // use the offset as a trigger to see if it's time to attempt to submit proofs - if (ctx.BlockHeight()+int64(addr[0]))%blocksPerSession == 1 && ctx.BlockHeight() != 1 { - // run go routine because cannot access TmNode during end-block period - go func() { - // use this sleep timer to bypass the beginBlock lock over transactions - time.Sleep(time.Duration(rand.Intn(5000)) * time.Millisecond) - s, err := am.keeper.TmNode.Status() - if err != nil { - ctx.Logger().Error(fmt.Sprintf("could not get status for tendermint node (cannot submit claims/proofs in this state): %s", err.Error())) - } else { - if !s.SyncInfo.CatchingUp { - // auto send the proofs - am.keeper.SendClaimTx(ctx, am.keeper, am.keeper.TmNode, ClaimTx) - // auto claim the proofs - am.keeper.SendProofTx(ctx, am.keeper.TmNode, ProofTx) - // clear session cache and db - types.ClearSessionCache() - } - } - }() + + // run go routine because cannot access TmNode during end-block period + go func() { + // use this sleep timer to bypass the beginBlock lock over transactions + minSleep := 2000 + maxSleep := 5000 + time.Sleep(time.Duration(rand.Intn(maxSleep-minSleep)+minSleep) * time.Millisecond) + + // check the consensus reactor sync status + status, err := am.keeper.TmNode.ConsensusReactorStatus() + if err != nil { + ctx.Logger().Error(fmt.Sprintf("could not get status for tendermint node (cannot submit claims/proofs in this state): %s", err.Error())) + return } - } else { - ctx.Logger().Error("could not get self address in end block") - } + + if status.IsCatchingUp { + ctx.Logger().Error("tendermint is currently syncing still (cannot submit claims/proofs in this state)") + return + } + + for _, node := range types.GlobalPocketNodes { + address := node.GetAddress() + if (ctx.BlockHeight()+int64(address[0]))%blocksPerSession == 1 && ctx.BlockHeight() != 1 { + // auto send the proofs + am.keeper.SendClaimTx(ctx, am.keeper, am.keeper.TmNode, node, ClaimTx) + // auto claim the proofs + am.keeper.SendProofTx(ctx, am.keeper.TmNode, node, ProofTx) + // clear session cache and db + types.ClearSessionCache(node.SessionStore) + } + } + }() return []abci.ValidatorUpdate{} } diff --git a/x/pocketcore/types/cache.go b/x/pocketcore/types/cache.go index 22f29e1c..ba66c505 100644 --- a/x/pocketcore/types/cache.go +++ b/x/pocketcore/types/cache.go @@ -16,29 +16,24 @@ import ( ) var ( - // cache for session objects - globalSessionCache *CacheStorage - // cache for GOBEvidence objects - globalEvidenceCache *CacheStorage // sync.once to perform initialization - cacheOnce sync.Once - - globalEvidenceSealedMap sync.Map + ConfigOnce sync.Once ) // "CacheStorage" - Contains an LRU cache and a database instance w/ mutex type CacheStorage struct { - Cache *sdk.Cache // lru cache - DB db.DB // persisted - l sync.Mutex // lock + Cache *sdk.Cache // lru cache + DB db.DB // persisted + l sync.Mutex // lock + SealMap *sync.Map } type CacheObject interface { MarshalObject() ([]byte, error) UnmarshalObject(b []byte) (CacheObject, error) Key() ([]byte, error) - Seal() CacheObject - IsSealed() bool + IsSealable() bool + HashString() string } // "Init" - Initializes a cache storage object @@ -59,6 +54,7 @@ func (cs *CacheStorage) Init(dir, name string, options config.LevelDBOptions, ma } panic(err) } + cs.SealMap = &sync.Map{} } // "Get" - Returns the value from a key @@ -88,11 +84,41 @@ func (cs *CacheStorage) GetWithoutLock(key []byte, object CacheObject) (interfac return res, true } +// "DeleteEvidence" - Remove the GOBEvidence from the store +func DeleteEvidence(header SessionHeader, evidenceType EvidenceType, evidenceStore *CacheStorage) error { + // generate key for GOBEvidence + key, err := KeyForEvidence(header, evidenceType) + if err != nil { + return err + } + // delete from cache + evidenceStore.Delete(key) + evidenceStore.SealMap.Delete(header.HashString()) + return nil +} + +func (cs *CacheStorage) IsSealedWithoutLock(object CacheObject) bool { + _, ok := cs.SealMap.Load(object.HashString()) + return ok +} + +func (cs *CacheStorage) IsSealed(object CacheObject) bool { + cs.l.Lock() + defer cs.l.Unlock() + return cs.IsSealedWithoutLock(object) +} + // "Seal" - Seals the cache object so it is no longer writable in the cache store func (cs *CacheStorage) Seal(object CacheObject) (cacheObject CacheObject, isOK bool) { - if object.IsSealed() { + + if !object.IsSealable() { + return object, false + } + + if cs.IsSealed(object) { return object, true } + cs.l.Lock() defer cs.l.Unlock() // get the key from the object @@ -101,10 +127,10 @@ func (cs *CacheStorage) Seal(object CacheObject) (cacheObject CacheObject, isOK return object, false } // make READONLY - sealed := object.Seal() + cs.SealMap.Store(object.HashString(), struct{}{}) // set in db and cache - cs.SetWithoutLockAndSealCheck(hex.EncodeToString(k), sealed) - return sealed, true + cs.SetWithoutLockAndSealCheck(hex.EncodeToString(k), object) + return object, true } // "Set" - Sets the KV pair in cache and db @@ -121,8 +147,8 @@ func (cs *CacheStorage) Set(key []byte, val CacheObject) { return } // if evidence, check sealed map - if ev, ok := co.(Evidence); ok { - if _, ok := globalEvidenceSealedMap.Load(ev.HashString()); ok { + if co.IsSealable() { + if ok := cs.IsSealedWithoutLock(co); ok { return } } @@ -208,15 +234,16 @@ func (cs *CacheStorage) Iterator() (db.Iterator, error) { if err != nil { fmt.Printf("unable to flush to db before iterator created in cacheStorage Iterator(): %s", err.Error()) } + return cs.DB.Iterator(nil, nil) } // "GetSession" - Returns a session (value) from the stores using a header (key) -func GetSession(header SessionHeader) (session Session, found bool) { +func GetSession(header SessionHeader, sessionStore *CacheStorage) (session Session, found bool) { // generate the key from the header key := header.Hash() // check stores - val, found := globalSessionCache.Get(key, session) + val, found := sessionStore.Get(key, session) if !found { return Session{}, found } @@ -228,22 +255,22 @@ func GetSession(header SessionHeader) (session Session, found bool) { } // "SetSession" - Sets a session (value) in the stores using the header (key) -func SetSession(session Session) { +func SetSession(session Session, sessionStore *CacheStorage) { // get the key for the session key := session.SessionHeader.Hash() - globalSessionCache.Set(key, session) + sessionStore.Set(key, session) } // "DeleteSession" - Deletes a session (value) from the stores -func DeleteSession(header SessionHeader) { +func DeleteSession(header SessionHeader, sessionStore *CacheStorage) { // delete from stores using header.ID as key - globalSessionCache.Delete(header.Hash()) + sessionStore.Delete(header.Hash()) } // "ClearSessionCache" - Clears all items from the session cache db -func ClearSessionCache() { - if globalSessionCache != nil { - globalSessionCache.Clear() +func ClearSessionCache(sessionStore *CacheStorage) { + if sessionStore != nil { + sessionStore.Clear() } } @@ -265,30 +292,37 @@ func (si *SessionIt) Value() (session Session) { return } -// "SessionIterator" - Returns an instance iterator of the globalSessionCache -func SessionIterator() SessionIt { - it, _ := globalSessionCache.Iterator() +// "SessionIterator" - Returns an instance iterator of the GlobalSessionCache +func SessionIterator(sessionStore *CacheStorage) SessionIt { + it, _ := sessionStore.Iterator() return SessionIt{ Iterator: it, } } // "GetEvidence" - Retrieves the GOBEvidence object from the storage -func GetEvidence(header SessionHeader, evidenceType EvidenceType, max sdk.BigInt) (evidence Evidence, err error) { +func GetEvidence(header SessionHeader, evidenceType EvidenceType, max sdk.BigInt, storage *CacheStorage) (evidence Evidence, err error) { // generate the key for the GOBEvidence key, err := KeyForEvidence(header, evidenceType) if err != nil { return } // get the bytes from the storage - val, found := globalEvidenceCache.Get(key, evidence) + val, found := storage.Get(key, evidence) if !found && max.Equal(sdk.ZeroInt()) { return Evidence{}, fmt.Errorf("GOBEvidence not found") } if !found { bloomFilter := bloom.NewWithEstimates(uint(sdk.NewUintFromBigInt(max.BigInt()).Uint64()), .01) // add to metric - GlobalServiceMetric().AddSessionFor(header.Chain) + addSessionMetricFunc := func() { + GlobalServiceMetric().AddSessionFor(header.Chain, nil) + } + if GlobalPocketConfig.LeanPocket { + go addSessionMetricFunc() + } else { + addSessionMetricFunc() + } return Evidence{ Bloom: *bloomFilter, SessionHeader: header, @@ -302,12 +336,12 @@ func GetEvidence(header SessionHeader, evidenceType EvidenceType, max sdk.BigInt err = fmt.Errorf("could not unmarshal into evidence from cache with header %v", header) return } - if evidence.IsSealed() { + if storage.IsSealed(evidence) { return evidence, nil } // if hit relay limit... Seal the evidence if found && !max.Equal(sdk.ZeroInt()) && evidence.NumOfProofs >= max.Int64() { - evidence, ok = SealEvidence(evidence) + evidence, ok = SealEvidence(evidence, storage) if !ok { err = fmt.Errorf("max relays is hit and could not seal evidence! GetEvidence() with header %v", header) return @@ -317,32 +351,18 @@ func GetEvidence(header SessionHeader, evidenceType EvidenceType, max sdk.BigInt } // "SetEvidence" - Sets an GOBEvidence object in the storage -func SetEvidence(evidence Evidence) { +func SetEvidence(evidence Evidence, evidenceStore *CacheStorage) { // generate the key for the evidence key, err := evidence.Key() if err != nil { return } - globalEvidenceCache.Set(key, evidence) -} - -// "DeleteEvidence" - Remove the GOBEvidence from the stores -func DeleteEvidence(header SessionHeader, evidenceType EvidenceType) error { - // generate key for GOBEvidence - key, err := KeyForEvidence(header, evidenceType) - if err != nil { - return err - } - // delete from cache - globalEvidenceCache.Delete(key) - globalEvidenceSealedMap.Delete(header.HashString()) - return nil + evidenceStore.Set(key, evidence) } // "SealEvidence" - Locks/sets the evidence from the stores -func SealEvidence(evidence Evidence) (Evidence, bool) { - // delete from cache - co, ok := globalEvidenceCache.Seal(evidence) +func SealEvidence(evidence Evidence, storage *CacheStorage) (Evidence, bool) { + co, ok := storage.Seal(evidence) if !ok { return Evidence{}, ok } @@ -351,14 +371,14 @@ func SealEvidence(evidence Evidence) (Evidence, bool) { } // "ClearEvidence" - Clear stores of all evidence -func ClearEvidence() { - if globalEvidenceCache != nil { - globalEvidenceCache.Clear() - globalEvidenceSealedMap = sync.Map{} +func ClearEvidence(evidenceStore *CacheStorage) { + if evidenceStore != nil { + evidenceStore.Clear() + evidenceStore.SealMap = &sync.Map{} } } -// "EvidenceIt" - An GOBEvidence iterator instance of the globalEvidenceCache +// "EvidenceIt" - An GOBEvidence iterator instance of the GlobalEvidenceCache type EvidenceIt struct { db.Iterator } @@ -377,19 +397,18 @@ func (ei *EvidenceIt) Value() (evidence Evidence) { return } -// "EvidenceIterator" - Returns a globalEvidenceCache iterator instance -func EvidenceIterator() EvidenceIt { - it, _ := globalEvidenceCache.Iterator() - +// "EvidenceIterator" - Returns a GlobalEvidenceCache iterator instance +func EvidenceIterator(evidenceStore *CacheStorage) EvidenceIt { + it, _ := evidenceStore.Iterator() return EvidenceIt{ Iterator: it, } } // "GetProof" - Returns the Proof object from a specific piece of GOBEvidence at a certain index -func GetProof(header SessionHeader, evidenceType EvidenceType, index int64) Proof { +func GetProof(header SessionHeader, evidenceType EvidenceType, index int64, evidenceStore *CacheStorage) Proof { // retrieve the GOBEvidence - evidence, err := GetEvidence(header, evidenceType, sdk.ZeroInt()) + evidence, err := GetEvidence(header, evidenceType, sdk.ZeroInt(), evidenceStore) if err != nil { return nil } @@ -402,9 +421,9 @@ func GetProof(header SessionHeader, evidenceType EvidenceType, index int64) Proo } // "SetProof" - Sets a proof object in the GOBEvidence, using the header and GOBEvidence type -func SetProof(header SessionHeader, evidenceType EvidenceType, p Proof, max sdk.BigInt) { +func SetProof(header SessionHeader, evidenceType EvidenceType, p Proof, max sdk.BigInt, evidenceStore *CacheStorage) { // retireve the GOBEvidence - evidence, err := GetEvidence(header, evidenceType, max) + evidence, err := GetEvidence(header, evidenceType, max, evidenceStore) // if not found generate the GOBEvidence object if err != nil { log.Fatalf("could not set proof object: %s", err.Error()) @@ -412,7 +431,7 @@ func SetProof(header SessionHeader, evidenceType EvidenceType, p Proof, max sdk. // add proof evidence.AddProof(p) // set GOBEvidence back - SetEvidence(evidence) + SetEvidence(evidence, evidenceStore) } func IsUniqueProof(p Proof, evidence Evidence) bool { @@ -420,9 +439,9 @@ func IsUniqueProof(p Proof, evidence Evidence) bool { } // "GetTotalProofs" - Returns the total number of proofs for a piece of GOBEvidence -func GetTotalProofs(h SessionHeader, et EvidenceType, maxPossibleRelays sdk.BigInt) (Evidence, int64) { +func GetTotalProofs(h SessionHeader, et EvidenceType, maxPossibleRelays sdk.BigInt, evidenceStore *CacheStorage) (Evidence, int64) { // retrieve the GOBEvidence - evidence, err := GetEvidence(h, et, maxPossibleRelays) + evidence, err := GetEvidence(h, et, maxPossibleRelays, evidenceStore) if err != nil { log.Fatalf("could not get total proofs for GOBEvidence: %s", err.Error()) } diff --git a/x/pocketcore/types/cache_test.go b/x/pocketcore/types/cache_test.go index 8b8384b4..b0542118 100644 --- a/x/pocketcore/types/cache_test.go +++ b/x/pocketcore/types/cache_test.go @@ -2,21 +2,22 @@ package types import ( "encoding/hex" + sdk "github.com/pokt-network/pocket-core/types" + "github.com/stretchr/testify/assert" "github.com/tendermint/tendermint/libs/log" "os" "reflect" "testing" - - sdk "github.com/pokt-network/pocket-core/types" - "github.com/stretchr/testify/assert" ) func InitCacheTest() { logger := log.NewNopLogger() - // init cache in memory + testingConfig := sdk.DefaultTestingPocketConfig() + CleanPocketNodes() + AddPocketNode(GetRandomPrivateKey(), log.NewNopLogger()) InitConfig(&HostedBlockchains{ M: make(map[string]HostedBlockchain), - }, logger, sdk.DefaultTestingPocketConfig()) + }, logger, testingConfig) } func TestMain(m *testing.M) { @@ -35,7 +36,7 @@ func TestIsUniqueProof(t *testing.T) { Chain: "0001", SessionBlockHeight: 0, } - e, _ := GetEvidence(h, RelayEvidence, sdk.NewInt(100000)) + e, _ := GetEvidence(h, RelayEvidence, sdk.NewInt(100000), GlobalEvidenceCache) p := RelayProof{ Entropy: 1, } @@ -44,12 +45,11 @@ func TestIsUniqueProof(t *testing.T) { } assert.True(t, IsUniqueProof(p, e), "p is unique") e.AddProof(p) - SetEvidence(e) - e, err := GetEvidence(h, RelayEvidence, sdk.ZeroInt()) + SetEvidence(e, GlobalEvidenceCache) + e, err := GetEvidence(h, RelayEvidence, sdk.ZeroInt(), GlobalEvidenceCache) assert.Nil(t, err) assert.False(t, IsUniqueProof(p, e), "p is no longer unique") assert.True(t, IsUniqueProof(p1, e), "p is unique") - } func TestAllEvidence_AddGetEvidence(t *testing.T) { @@ -76,8 +76,43 @@ func TestAllEvidence_AddGetEvidence(t *testing.T) { }, Signature: "", } - SetProof(header, RelayEvidence, proof, sdk.NewInt(100000)) - assert.True(t, reflect.DeepEqual(GetProof(header, RelayEvidence, 0), proof)) + SetProof(header, RelayEvidence, proof, sdk.NewInt(100000), GlobalEvidenceCache) + assert.True(t, reflect.DeepEqual(GetProof(header, RelayEvidence, 0, GlobalEvidenceCache), proof)) +} + +func TestAllEvidence_Iterator(t *testing.T) { + ClearEvidence(GlobalEvidenceCache) + appPubKey := getRandomPubKey().RawString() + servicerPubKey := getRandomPubKey().RawString() + clientPubKey := getRandomPubKey().RawString() + ethereum := hex.EncodeToString([]byte{0001}) + header := SessionHeader{ + ApplicationPubKey: appPubKey, + Chain: ethereum, + SessionBlockHeight: 1, + } + proof := RelayProof{ + Entropy: 0, + RequestHash: header.HashString(), // fake + SessionBlockHeight: 1, + ServicerPubKey: servicerPubKey, + Blockchain: ethereum, + Token: AAT{ + Version: "0.0.1", + ApplicationPublicKey: appPubKey, + ClientPublicKey: clientPubKey, + ApplicationSignature: "", + }, + Signature: "", + } + SetProof(header, RelayEvidence, proof, sdk.NewInt(100000), GlobalEvidenceCache) + iter := EvidenceIterator(GlobalEvidenceCache) + var count = 0 + defer iter.Close() + for ; iter.Valid(); iter.Next() { + count++ + } + assert.Equal(t, 1, int(count)) } func TestAllEvidence_DeleteEvidence(t *testing.T) { @@ -104,11 +139,11 @@ func TestAllEvidence_DeleteEvidence(t *testing.T) { }, Signature: "", } - SetProof(header, RelayEvidence, proof, sdk.NewInt(100000)) - assert.True(t, reflect.DeepEqual(GetProof(header, RelayEvidence, 0), proof)) - GetProof(header, RelayEvidence, 0) - _ = DeleteEvidence(header, RelayEvidence) - assert.Empty(t, GetProof(header, RelayEvidence, 0)) + SetProof(header, RelayEvidence, proof, sdk.NewInt(100000), GlobalEvidenceCache) + assert.True(t, reflect.DeepEqual(GetProof(header, RelayEvidence, 0, GlobalEvidenceCache), proof)) + GetProof(header, RelayEvidence, 0, GlobalEvidenceCache) + _ = DeleteEvidence(header, RelayEvidence, GlobalEvidenceCache) + assert.Empty(t, GetProof(header, RelayEvidence, 0, GlobalEvidenceCache)) } func TestAllEvidence_GetTotalProofs(t *testing.T) { @@ -154,41 +189,41 @@ func TestAllEvidence_GetTotalProofs(t *testing.T) { }, Signature: "", } - SetProof(header, RelayEvidence, proof, sdk.NewInt(100000)) - SetProof(header, RelayEvidence, proof2, sdk.NewInt(100000)) - SetProof(header2, RelayEvidence, proof2, sdk.NewInt(100000)) // different header so shouldn't be counted - _, totalRelays := GetTotalProofs(header, RelayEvidence, sdk.NewInt(100000)) + SetProof(header, RelayEvidence, proof, sdk.NewInt(100000), GlobalEvidenceCache) + SetProof(header, RelayEvidence, proof2, sdk.NewInt(100000), GlobalEvidenceCache) + SetProof(header2, RelayEvidence, proof2, sdk.NewInt(100000), GlobalEvidenceCache) // different header so shouldn't be counted + _, totalRelays := GetTotalProofs(header, RelayEvidence, sdk.NewInt(100000), GlobalEvidenceCache) assert.Equal(t, totalRelays, int64(2)) } func TestSetGetSession(t *testing.T) { session := NewTestSession(t, hex.EncodeToString(Hash([]byte("foo")))) session2 := NewTestSession(t, hex.EncodeToString(Hash([]byte("bar")))) - SetSession(session) - s, found := GetSession(session.SessionHeader) + SetSession(session, GlobalSessionCache) + s, found := GetSession(session.SessionHeader, GlobalSessionCache) assert.True(t, found) assert.Equal(t, s, session) - _, found = GetSession(session2.SessionHeader) + _, found = GetSession(session2.SessionHeader, GlobalSessionCache) assert.False(t, found) - SetSession(session2) - s, found = GetSession(session2.SessionHeader) + SetSession(session2, GlobalSessionCache) + s, found = GetSession(session2.SessionHeader, GlobalSessionCache) assert.True(t, found) assert.Equal(t, s, session2) } func TestDeleteSession(t *testing.T) { session := NewTestSession(t, hex.EncodeToString(Hash([]byte("foo")))) - SetSession(session) - DeleteSession(session.SessionHeader) - _, found := GetSession(session.SessionHeader) + SetSession(session, GlobalSessionCache) + DeleteSession(session.SessionHeader, GlobalSessionCache) + _, found := GetSession(session.SessionHeader, GlobalSessionCache) assert.False(t, found) } func TestClearCache(t *testing.T) { session := NewTestSession(t, hex.EncodeToString(Hash([]byte("foo")))) - SetSession(session) - ClearSessionCache() - iter := SessionIterator() + SetSession(session, GlobalSessionCache) + ClearSessionCache(GlobalSessionCache) + iter := SessionIterator(GlobalSessionCache) var count = 0 defer iter.Close() for ; iter.Valid(); iter.Next() { diff --git a/x/pocketcore/types/config.go b/x/pocketcore/types/config.go index bef61c64..782400a4 100644 --- a/x/pocketcore/types/config.go +++ b/x/pocketcore/types/config.go @@ -3,11 +3,11 @@ package types import ( "encoding/hex" "fmt" - "sync" - "time" - + "github.com/pokt-network/pocket-core/crypto" "github.com/pokt-network/pocket-core/types" + "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/libs/log" + "time" ) const ( @@ -17,27 +17,33 @@ const ( ) var ( - globalRPCTimeout time.Duration - GlobalPocketConfig types.PocketConfig + globalRPCTimeout time.Duration + GlobalPocketConfig types.PocketConfig + GlobalTenderMintConfig config.Config ) -// "InitConfig" - Initializes the cache for sessions and evidence func InitConfig(chains *HostedBlockchains, logger log.Logger, c types.Config) { - cacheOnce.Do(func() { - globalEvidenceCache = new(CacheStorage) - globalSessionCache = new(CacheStorage) - globalEvidenceSealedMap = sync.Map{} - globalEvidenceCache.Init(c.PocketConfig.DataDir, c.PocketConfig.EvidenceDBName, c.TendermintConfig.LevelDBOptions, c.PocketConfig.MaxEvidenceCacheEntires, false) - globalSessionCache.Init(c.PocketConfig.DataDir, "", c.TendermintConfig.LevelDBOptions, c.PocketConfig.MaxSessionCacheEntries, true) + ConfigOnce.Do(func() { InitGlobalServiceMetric(chains, logger, c.PocketConfig.PrometheusAddr, c.PocketConfig.PrometheusMaxOpenfiles) }) + InitPocketNodeCaches(c, logger) GlobalPocketConfig = c.PocketConfig + GlobalTenderMintConfig = c.TendermintConfig + if GlobalPocketConfig.LeanPocket { + GlobalTenderMintConfig.PrivValidatorState = types.DefaultPVSNameLean + GlobalTenderMintConfig.PrivValidatorKey = types.DefaultPVKNameLean + GlobalTenderMintConfig.NodeKey = types.DefaultPVSNameLean + } SetRPCTimeout(c.PocketConfig.RPCTimeout) } func ConvertEvidenceToProto(config types.Config) error { + // we have to add a random pocket node so that way lean pokt can still support getting the global evidence cache + node := AddPocketNode(crypto.GenerateEd25519PrivKey().GenPrivateKey(), log.NewNopLogger()) + InitConfig(nil, log.NewNopLogger(), config) - gec := globalEvidenceCache + + gec := node.EvidenceStore it, err := gec.Iterator() if err != nil { return fmt.Errorf("error creating evidence iterator: %s", err.Error()) @@ -61,15 +67,20 @@ func ConvertEvidenceToProto(config types.Config) error { return nil } -// NOTE: evidence cache is flushed every time db iterator is created (every claim/proof submission) func FlushSessionCache() { - err := globalSessionCache.FlushToDB() - if err != nil { - fmt.Printf("unable to flush sessions to the database before shutdown!! %s\n", err.Error()) - } - err = globalEvidenceCache.FlushToDB() - if err != nil { - fmt.Printf("unable to flush GOBEvidence to the database before shutdown!! %s\n", err.Error()) + for _, k := range GlobalPocketNodes { + if k.SessionStore != nil { + err := k.SessionStore.FlushToDB() + if err != nil { + fmt.Printf("unable to flush sessions to the database before shutdown!! %s\n", err.Error()) + } + } + if k.EvidenceStore != nil { + err := k.EvidenceStore.FlushToDB() + if err != nil { + fmt.Printf("unable to flush GOBEvidence to the database before shutdown!! %s\n", err.Error()) + } + } } } diff --git a/x/pocketcore/types/crypto.go b/x/pocketcore/types/crypto.go index 16403921..ea0f8456 100644 --- a/x/pocketcore/types/crypto.go +++ b/x/pocketcore/types/crypto.go @@ -66,20 +66,6 @@ func SignatureVerification(publicKey, msgHex, sigHex string) sdk.Error { return nil } -// "InitPVKeyFile" - Initializes the global private validator key variable -func InitPVKeyFile(filePVKey privval.FilePVKey) { - globalPVKeyFile = filePVKey -} - -// "GetPVKeyFile" - Returns the globalPVKeyFile instance -func GetPVKeyFile() (privval.FilePVKey, sdk.Error) { - if globalPVKeyFile.PrivKey == nil { - return globalPVKeyFile, NewInvalidPKError(ModuleName) - } else { - return globalPVKeyFile, nil - } -} - // "PubKeyVerification" - Verifies the public key format (hex string) func PubKeyVerification(pk string) sdk.Error { // decode the bz diff --git a/x/pocketcore/types/evidence.go b/x/pocketcore/types/evidence.go index 432d2d75..bb79a532 100644 --- a/x/pocketcore/types/evidence.go +++ b/x/pocketcore/types/evidence.go @@ -2,11 +2,10 @@ package types import ( "fmt" - "strings" - "github.com/pokt-network/pocket-core/codec" "github.com/pokt-network/pocket-core/types" "github.com/willf/bloom" + "strings" ) // "Evidence" - A proof of work/burn for nodes. @@ -18,22 +17,14 @@ type Evidence struct { EvidenceType EvidenceType `json:"evidence_type"` } -func (e Evidence) IsSealed() bool { - globalEvidenceCache.l.Lock() - defer globalEvidenceCache.l.Unlock() - _, ok := globalEvidenceSealedMap.Load(e.HashString()) - return ok -} - -func (e Evidence) Seal() CacheObject { - globalEvidenceSealedMap.Store(e.HashString(), struct{}{}) - return e +func (e Evidence) IsSealable() bool { + return true } // "GenerateMerkleRoot" - Generates the merkle root for an GOBEvidence object -func (e *Evidence) GenerateMerkleRoot(height int64) (root HashRange) { +func (e *Evidence) GenerateMerkleRoot(height int64, storage *CacheStorage) (root HashRange) { // seal the evidence in cache/db - ev, ok := SealEvidence(*e) + ev, ok := SealEvidence(*e, storage) if !ok { return HashRange{} } diff --git a/x/pocketcore/types/hostedBlockchain.go b/x/pocketcore/types/hostedBlockchain.go index 2a9250dd..fc339a14 100644 --- a/x/pocketcore/types/hostedBlockchain.go +++ b/x/pocketcore/types/hostedBlockchain.go @@ -20,13 +20,13 @@ type BasicAuth struct { // HostedBlockchains" - An object that represents the local hosted non-native blockchains type HostedBlockchains struct { M map[string]HostedBlockchain // M[addr] -> addr, url - L sync.Mutex + L sync.RWMutex } // "Contains" - Checks to see if the hosted chain is within the HostedBlockchains object func (c *HostedBlockchains) Contains(id string) bool { - c.L.Lock() - defer c.L.Unlock() + c.L.RLock() + defer c.L.RUnlock() // quick map check _, found := c.M[id] return found @@ -34,8 +34,8 @@ func (c *HostedBlockchains) Contains(id string) bool { // "GetChainURL" - Returns the url or error of the hosted blockchain using the hex network identifier func (c *HostedBlockchains) GetChain(id string) (chain HostedBlockchain, err sdk.Error) { - c.L.Lock() - defer c.L.Unlock() + c.L.RLock() + defer c.L.RUnlock() // map check res, found := c.M[id] if !found { @@ -55,8 +55,8 @@ func (c *HostedBlockchains) GetChainURL(id string) (url string, err sdk.Error) { // "Validate" - Validates the hosted blockchain object func (c *HostedBlockchains) Validate() error { - c.L.Lock() - defer c.L.Unlock() + c.L.RLock() + defer c.L.RUnlock() // loop through all of the chains for _, chain := range c.M { // validate not empty diff --git a/x/pocketcore/types/hostedBlockchain_test.go b/x/pocketcore/types/hostedBlockchain_test.go index 8d18bf60..842bbdc4 100644 --- a/x/pocketcore/types/hostedBlockchain_test.go +++ b/x/pocketcore/types/hostedBlockchain_test.go @@ -17,7 +17,7 @@ func TestHostedBlockchains_GetChainURL(t *testing.T) { } hb := HostedBlockchains{ M: map[string]HostedBlockchain{testHostedBlockchain.ID: testHostedBlockchain}, - L: sync.Mutex{}, + L: sync.RWMutex{}, } u, err := hb.GetChainURL(ethereum) assert.Nil(t, err) @@ -34,7 +34,7 @@ func TestHostedBlockchains_ContainsFromString(t *testing.T) { } hb := HostedBlockchains{ M: map[string]HostedBlockchain{testHostedBlockchain.ID: testHostedBlockchain}, - L: sync.Mutex{}, + L: sync.RWMutex{}, } assert.True(t, hb.Contains(ethereum)) assert.False(t, hb.Contains(bitcoin)) @@ -66,22 +66,22 @@ func TestHostedBlockchains_Validate(t *testing.T) { }{ { name: "Invalid HostedBlockchain, no URL", - hc: &HostedBlockchains{M: map[string]HostedBlockchain{HCNoURL.URL: HCNoURL}, L: sync.Mutex{}}, + hc: &HostedBlockchains{M: map[string]HostedBlockchain{HCNoURL.URL: HCNoURL}, L: sync.RWMutex{}}, hasError: true, }, { name: "Invalid HostedBlockchain, no URL", - hc: &HostedBlockchains{M: map[string]HostedBlockchain{HCNoHash.URL: HCNoHash}, L: sync.Mutex{}}, + hc: &HostedBlockchains{M: map[string]HostedBlockchain{HCNoHash.URL: HCNoHash}, L: sync.RWMutex{}}, hasError: true, }, { name: "Invalid HostedBlockchain, invalid ID", - hc: &HostedBlockchains{M: map[string]HostedBlockchain{HCInvalidHash.URL: HCInvalidHash}, L: sync.Mutex{}}, + hc: &HostedBlockchains{M: map[string]HostedBlockchain{HCInvalidHash.URL: HCInvalidHash}, L: sync.RWMutex{}}, hasError: true, }, { name: "Valid HostedBlockchain", - hc: &HostedBlockchains{M: map[string]HostedBlockchain{testHostedBlockchain.ID: testHostedBlockchain}, L: sync.Mutex{}}, + hc: &HostedBlockchains{M: map[string]HostedBlockchain{testHostedBlockchain.ID: testHostedBlockchain}, L: sync.RWMutex{}}, hasError: false, }, } diff --git a/x/pocketcore/types/merkle_test.go b/x/pocketcore/types/merkle_test.go index 19fd27df..fd63a78c 100644 --- a/x/pocketcore/types/merkle_test.go +++ b/x/pocketcore/types/merkle_test.go @@ -40,7 +40,7 @@ func TestMultiAppend(t *testing.T) { } func TestEvidence_GenerateMerkleRoot(t *testing.T) { - ClearEvidence() + ClearEvidence(GlobalEvidenceCache) appPrivateKey := GetRandomPrivateKey() appPubKey := appPrivateKey.PublicKey().RawString() clientPrivateKey := GetRandomPrivateKey() @@ -115,7 +115,7 @@ func TestEvidence_GenerateMerkleRoot(t *testing.T) { }, }, } - root := i.GenerateMerkleRoot(0) + root := i.GenerateMerkleRoot(0, GlobalEvidenceCache) assert.NotNil(t, root.Hash) assert.NotEmpty(t, root.Hash) assert.Nil(t, HashVerification(hex.EncodeToString(root.Hash))) @@ -123,13 +123,13 @@ func TestEvidence_GenerateMerkleRoot(t *testing.T) { assert.Zero(t, root.Range.Lower) assert.NotZero(t, root.Range.Upper) - iter := EvidenceIterator() + iter := EvidenceIterator(GlobalEvidenceCache) // Make sure its stored in order! defer iter.Close() for ; iter.Valid(); iter.Next() { e := iter.Value() assert.Equal(t, i, e) - newRoot := e.GenerateMerkleRoot(0) + newRoot := e.GenerateMerkleRoot(0, GlobalEvidenceCache) assert.Equal(t, root, newRoot) } } @@ -384,13 +384,13 @@ func TestEvidence_VerifyMerkleProof(t *testing.T) { }, } index := 4 - root := i.GenerateMerkleRoot(0) + root := i.GenerateMerkleRoot(0, GlobalEvidenceCache) proofs, leaf := i.GenerateMerkleProof(0, index) // validate level count on claim by total relays res, _ := proofs.Validate(0, root, leaf, len(proofs.HashRanges)) assert.True(t, res) index2 := 0 - root2 := i2.GenerateMerkleRoot(0) + root2 := i2.GenerateMerkleRoot(0, GlobalEvidenceCache) proofs2, leaf2 := i2.GenerateMerkleProof(0, index2) res, _ = proofs2.Validate(0, root2, leaf2, len(proofs2.HashRanges)) assert.True(t, res) diff --git a/x/pocketcore/types/metrics.go b/x/pocketcore/types/metrics.go index dbc5b6e4..6ea53091 100644 --- a/x/pocketcore/types/metrics.go +++ b/x/pocketcore/types/metrics.go @@ -4,6 +4,7 @@ import ( "context" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/prometheus" + sdk "github.com/pokt-network/pocket-core/types" stdPrometheus "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/tendermint/tendermint/libs/log" @@ -30,6 +31,10 @@ const ( SessionsCountHelp = "the number of unique sessions generated for: " UPOKTCountName = "tokens_earned_for_" UPOKTCountHelp = "the number of tokens earned in uPOKT for : " + AvgClaimTimeName = "avg_claim_time_for_" + AvgClaimTimeHelp = "the average time in ms to generate the work needed for claim tx:" + AvgProofTimeName = "avg_proof_time_for_" + AvgProofTimeHelp = "the average time in ms to generate the work needed for claim tx:" ) type ServiceMetrics struct { @@ -64,6 +69,10 @@ func StopServiceMetrics() { } } +func (sm *ServiceMetrics) getValidatorLabel(nodeAddress *sdk.Address) []string { + return []string{"validator_address", nodeAddress.String()} +} + // startPrometheusServer starts a Prometheus HTTP server, listening for metrics // collectors on addr. func (sm *ServiceMetrics) StartPrometheusServer(addr string, maxOpenConn int) *http.Server { @@ -85,7 +94,7 @@ func (sm *ServiceMetrics) StartPrometheusServer(addr string, maxOpenConn int) *h return srv } -func (sm *ServiceMetrics) AddRelayFor(networkID string) { +func (sm *ServiceMetrics) AddRelayFor(networkID string, nodeAddress *sdk.Address) { sm.l.Lock() defer sm.l.Unlock() // attempt to locate nn chain @@ -96,14 +105,15 @@ func (sm *ServiceMetrics) AddRelayFor(networkID string) { return } // add relay to accumulated count - sm.RelayCount.Add(1) + labels := sm.getValidatorLabel(nodeAddress) + sm.RelayCount.With(labels...).Add(1) // add to individual relay count - nnc.RelayCount.Add(1) + nnc.RelayCount.With(labels...).Add(1) // update nnc sm.NonNativeChains[networkID] = nnc } -func (sm *ServiceMetrics) AddChallengeFor(networkID string) { +func (sm *ServiceMetrics) AddChallengeFor(networkID string, nodeAddress *sdk.Address) { sm.l.Lock() defer sm.l.Unlock() // attempt to locate nn chain @@ -113,15 +123,16 @@ func (sm *ServiceMetrics) AddChallengeFor(networkID string) { sm.NonNativeChains[networkID] = NewServiceMetricsFor(networkID) return } + labels := sm.getValidatorLabel(nodeAddress) // add to accumulated count - sm.ChallengeCount.Add(1) + sm.ChallengeCount.With(labels...).Add(1) // add to individual count - nnc.ChallengeCount.Add(1) + nnc.ChallengeCount.With(labels...).Add(1) // update nnc sm.NonNativeChains[networkID] = nnc } -func (sm *ServiceMetrics) AddErrorFor(networkID string) { +func (sm *ServiceMetrics) AddErrorFor(networkID string, nodeAddress *sdk.Address) { sm.l.Lock() defer sm.l.Unlock() // attempt to locate nn chain @@ -131,15 +142,15 @@ func (sm *ServiceMetrics) AddErrorFor(networkID string) { sm.NonNativeChains[networkID] = NewServiceMetricsFor(networkID) return } - // add to accumulated count - sm.ErrCount.Add(1) + labels := sm.getValidatorLabel(nodeAddress) + sm.ErrCount.With(labels...).Add(1) // add to individual count - nnc.ErrCount.Add(1) + nnc.ErrCount.With(labels...).Add(1) // update nnc sm.NonNativeChains[networkID] = nnc } -func (sm *ServiceMetrics) AddRelayTimingFor(networkID string, relayTime float64) { +func (sm *ServiceMetrics) AddRelayTimingFor(networkID string, relayTime float64, nodeAddress *sdk.Address) { sm.l.Lock() defer sm.l.Unlock() // attempt to locate nn chain @@ -150,14 +161,16 @@ func (sm *ServiceMetrics) AddRelayTimingFor(networkID string, relayTime float64) return } // add to accumulated hist - sm.AverageRelayTime.Observe(relayTime) + labels := sm.getValidatorLabel(nodeAddress) + sm.AverageRelayTime.With(labels...).Observe(relayTime) // add to individual hist - nnc.AverageRelayTime.Observe(relayTime) + nnc.AverageRelayTime.With(labels...).Observe(relayTime) // update nnc sm.NonNativeChains[networkID] = nnc } -func (sm *ServiceMetrics) AddSessionFor(networkID string) { +func (sm *ServiceMetrics) AddClaimTiming(networkID string, time float64, nodeAddress *sdk.Address) { + sm.l.Lock() defer sm.l.Unlock() // attempt to locate nn chain @@ -165,18 +178,72 @@ func (sm *ServiceMetrics) AddSessionFor(networkID string) { if !ok { sm.tmLogger.Error("unable to find corresponding networkID in service metrics: ", networkID) sm.NonNativeChains[networkID] = NewServiceMetricsFor(networkID) + return + } + labels := sm.getValidatorLabel(nodeAddress) + // add to accumulated hist + sm.AverageClaimTime.With(labels...).Observe(time) + // add to individual hist + nnc.AverageClaimTime.With(labels...).Observe(time) + // update nnc + sm.NonNativeChains[networkID] = nnc +} + +func (sm *ServiceMetrics) AddProofTiming(networkID string, time float64, nodeAddress *sdk.Address) { + sm.l.Lock() + defer sm.l.Unlock() + // attempt to locate nn chain + nnc, ok := sm.NonNativeChains[networkID] + if !ok { + sm.tmLogger.Error("unable to find corresponding networkID in service metrics: ", networkID) + sm.NonNativeChains[networkID] = NewServiceMetricsFor(networkID) return } + labels := sm.getValidatorLabel(nodeAddress) + + // add to accumulated hist + sm.AverageProofTime.With(labels...).Observe(time) + // add to individual hist + nnc.AverageProofTime.With(labels...).Observe(time) + // update nnc + sm.NonNativeChains[networkID] = nnc +} + +func (sm *ServiceMetrics) AddSessionFor(networkID string, nodeAddress *sdk.Address) { + sm.l.Lock() + defer sm.l.Unlock() + // attempt to locate nn chain + nnc, ok := sm.NonNativeChains[networkID] + if !ok { + sm.tmLogger.Error("unable to find corresponding networkID in service metrics: ", networkID) + sm.NonNativeChains[networkID] = NewServiceMetricsFor(networkID) + + return + } + + if nodeAddress == nil { + // this implies that user is not running in lean pocket + node := GetPocketNode() + if node == nil { + sm.tmLogger.Error("unable to load privateKey", networkID) + return + } + addr := sdk.GetAddress(node.PrivateKey.PublicKey()) + nodeAddress = &addr + } + labels := sm.getValidatorLabel(nodeAddress) + // add to accumulated count - sm.TotalSessions.Add(1) + sm.TotalSessions.With(labels...).Add(1) // add to individual count - nnc.TotalSessions.Add(1) + nnc.TotalSessions.With(labels...).Add(1) + // update nnc sm.NonNativeChains[networkID] = nnc } -func (sm *ServiceMetrics) AddUPOKTEarnedFor(networkID string, upoktEarned float64) { +func (sm *ServiceMetrics) AddUPOKTEarnedFor(networkID string, upoktEarned float64, nodeAddress *sdk.Address) { sm.l.Lock() defer sm.l.Unlock() // attempt to locate nn chain @@ -186,10 +253,11 @@ func (sm *ServiceMetrics) AddUPOKTEarnedFor(networkID string, upoktEarned float6 sm.NonNativeChains[networkID] = NewServiceMetricsFor(networkID) return } + labels := sm.getValidatorLabel(nodeAddress) // add to accumulated count - sm.UPOKTEarned.Add(1) + sm.UPOKTEarned.With(labels...).Add(upoktEarned) // add to individual count - nnc.UPOKTEarned.Add(1) + nnc.UPOKTEarned.With(labels...).Add(upoktEarned) // update nnc sm.NonNativeChains[networkID] = nnc } @@ -219,6 +287,8 @@ type ServiceMetric struct { ChallengeCount metrics.Counter `json:"challenge_count"` ErrCount metrics.Counter `json:"err_count"` AverageRelayTime metrics.Histogram `json:"avg_relay_time"` + AverageClaimTime metrics.Histogram `json:"avg_claim_time"` + AverageProofTime metrics.Histogram `json:"avg_proof_time"` TotalSessions metrics.Counter `json:"total_sessions"` UPOKTEarned metrics.Counter `json:"upokt_earned"` } @@ -226,26 +296,27 @@ type ServiceMetric struct { func NewServiceMetricsFor(networkID string) ServiceMetric { //labels := make([]string, 1) // relay counter metric + labels := []string{} relayCounter := prometheus.NewCounterFrom(stdPrometheus.CounterOpts{ Namespace: ModuleName, Subsystem: ServiceMetricsNamespace, Name: RelayCountName + networkID, Help: RelayCountHelp + networkID, - }, nil) + }, append(labels, "validator_address")) // challenge counter metric challengeCounter := prometheus.NewCounterFrom(stdPrometheus.CounterOpts{ Namespace: ModuleName, Subsystem: ServiceMetricsNamespace, Name: ChallengeCountName + networkID, Help: ChallengeCountHelp + networkID, - }, nil) + }, append(labels, "validator_address")) // err counter metric errCounter := prometheus.NewCounterFrom(stdPrometheus.CounterOpts{ Namespace: ModuleName, Subsystem: ServiceMetricsNamespace, Name: ErrCountName + networkID, Help: ErrCountHelp + networkID, - }, nil) + }, append(labels, "validator_address")) // Avg relay time histogram metric avgRelayTime := prometheus.NewHistogramFrom(stdPrometheus.HistogramOpts{ Namespace: ModuleName, @@ -254,21 +325,37 @@ func NewServiceMetricsFor(networkID string) ServiceMetric { Help: AvgrelayHistHelp + networkID, ConstLabels: nil, Buckets: stdPrometheus.LinearBuckets(1, 20, 20), - }, nil) + }, append(labels, "validator_address")) // session counter metric totalSessions := prometheus.NewCounterFrom(stdPrometheus.CounterOpts{ Namespace: ModuleName, Subsystem: ServiceMetricsNamespace, Name: SessionsCountName + networkID, Help: SessionsCountHelp + networkID, - }, nil) + }, append(labels, "validator_address")) // tokens earned metric uPOKTEarned := prometheus.NewCounterFrom(stdPrometheus.CounterOpts{ Namespace: ModuleName, Subsystem: ServiceMetricsNamespace, Name: UPOKTCountName + networkID, Help: UPOKTCountHelp + networkID, - }, nil) + }, append(labels, "validator_address")) + avgClaimTime := prometheus.NewHistogramFrom(stdPrometheus.HistogramOpts{ + Namespace: ModuleName, + Subsystem: ServiceMetricsNamespace, + Name: AvgClaimTimeName + networkID, + Help: AvgClaimTimeHelp + networkID, + ConstLabels: nil, + Buckets: stdPrometheus.LinearBuckets(1, 20, 20), + }, append(labels, "validator_address")) + avgProofTime := prometheus.NewHistogramFrom(stdPrometheus.HistogramOpts{ + Namespace: ModuleName, + Subsystem: ServiceMetricsNamespace, + Name: AvgProofTimeName + networkID, + Help: AvgProofTimeHelp + networkID, + ConstLabels: nil, + Buckets: stdPrometheus.LinearBuckets(1, 20, 20), + }, append(labels, "validator_address")) return ServiceMetric{ RelayCount: relayCounter, ChallengeCount: challengeCounter, @@ -276,5 +363,7 @@ func NewServiceMetricsFor(networkID string) ServiceMetric { AverageRelayTime: avgRelayTime, TotalSessions: totalSessions, UPOKTEarned: uPOKTEarned, + AverageClaimTime: avgClaimTime, + AverageProofTime: avgProofTime, } } diff --git a/x/pocketcore/types/params.go b/x/pocketcore/types/params.go index 63ae9939..a6b286f2 100644 --- a/x/pocketcore/types/params.go +++ b/x/pocketcore/types/params.go @@ -12,11 +12,12 @@ import ( const ( // DefaultParamspace for params keeper DefaultParamspace = ModuleName - DefaultSessionNodeCount = int64(5) // default number of nodes in a session - DefaultClaimSubmissionWindow = int64(3) // default sessions to submit a claim - DefaultClaimExpiration = int64(100) // default sessions to exprie claims - DefaultReplayAttackBurnMultiplier = int64(3) // default replay attack burn multiplier - DefaultMinimumNumberOfProofs = int64(5) // default minimum number of proofs + DefaultSessionNodeCount = int64(5) // default number of nodes in a session + DefaultClaimSubmissionWindow = int64(3) // default sessions to submit a claim + DefaultClaimExpiration = int64(100) // default sessions to exprie claims + DefaultReplayAttackBurnMultiplier = int64(3) // default replay attack burn multiplier + DefaultMinimumNumberOfProofs = int64(5) // default minimum number of proofs + DefaultBlockByteSize = int64(4000000) // default block size in bytes ) @@ -28,6 +29,7 @@ var ( KeyClaimExpiration = []byte("ClaimExpiration") KeyReplayAttackBurnMultiplier = []byte("ReplayAttackBurnMultiplier") KeyMinimumNumberOfProofs = []byte("MinimumNumberOfProofs") + KeyBlockByteSize = []byte("BlockByteSize") ) var _ types.ParamSet = (*Params)(nil) @@ -40,6 +42,7 @@ type Params struct { ClaimExpiration int64 `json:"claim_expiration"` // per session ReplayAttackBurnMultiplier int64 `json:"replay_attack_burn_multiplier"` MinimumNumberOfProofs int64 `json:"minimum_number_of_proofs"` + BlockByteSize int64 `json:"block_byte_size,omitempty"` } // "ParamSetPairs" - returns an kv params object @@ -52,6 +55,7 @@ func (p *Params) ParamSetPairs() types.ParamSetPairs { {Key: KeyClaimExpiration, Value: &p.ClaimExpiration}, {Key: KeyReplayAttackBurnMultiplier, Value: p.ReplayAttackBurnMultiplier}, {Key: KeyMinimumNumberOfProofs, Value: p.MinimumNumberOfProofs}, + {Key: KeyBlockByteSize, Value: p.BlockByteSize}, } } @@ -110,10 +114,12 @@ func (p Params) String() string { Supported Blockchains %v ClaimExpiration %d ReplayAttackBurnMultiplier %d + BlockByteSize %d `, p.SessionNodeCount, p.ClaimSubmissionWindow, p.SupportedBlockchains, p.ClaimExpiration, - p.ReplayAttackBurnMultiplier) + p.ReplayAttackBurnMultiplier, + p.BlockByteSize) } diff --git a/x/pocketcore/types/pocketNode.go b/x/pocketcore/types/pocketNode.go new file mode 100644 index 00000000..e6d82371 --- /dev/null +++ b/x/pocketcore/types/pocketNode.go @@ -0,0 +1,123 @@ +package types + +import ( + "fmt" + "github.com/pokt-network/pocket-core/crypto" + "github.com/pokt-network/pocket-core/types" + sdk "github.com/pokt-network/pocket-core/types" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/privval" + "sync" +) + +// GlobalEvidenceCache & GlobalSessionCache is used for the first pocket node and acts as backwards-compatibility for pre-lean pocket +var GlobalEvidenceCache *CacheStorage +var GlobalSessionCache *CacheStorage + +var GlobalPocketNodes = map[string]*PocketNode{} + +// PocketNode represents an entity in the network that is able to handle dispatches, servicing, challenges, and submit proofs/claims. +type PocketNode struct { + PrivateKey crypto.PrivateKey + EvidenceStore *CacheStorage + SessionStore *CacheStorage + DoCacheInitOnce sync.Once +} + +func (n PocketNode) GetAddress() sdk.Address { + return sdk.GetAddress(n.PrivateKey.PublicKey()) +} + +func AddPocketNode(pk crypto.PrivateKey, logger log.Logger) *PocketNode { + key := sdk.GetAddress(pk.PublicKey()).String() + logger.Info("Adding " + key + " to list of pocket nodes") + node, exists := GlobalPocketNodes[key] + if exists { + return node + } + node = &PocketNode{ + PrivateKey: pk, + } + GlobalPocketNodes[key] = node + return node +} + +func AddPocketNodeByFilePVKey(fpvKey privval.FilePVKey, logger log.Logger) { + key, err := crypto.PrivKeyToPrivateKey(fpvKey.PrivKey) + if err != nil { + return + } + AddPocketNode(key, logger) +} + +// InitPocketNodeCache adds a PocketNode with its SessionStore and EvidenceStore initialized +func InitPocketNodeCache(node *PocketNode, c types.Config, logger log.Logger) { + node.DoCacheInitOnce.Do(func() { + evidenceDbName := c.PocketConfig.EvidenceDBName + address := node.GetAddress().String() + // In LeanPocket, we create a evidence store on disk with suffix of the node's address + if c.PocketConfig.LeanPocket { + evidenceDbName = evidenceDbName + "_" + address + } + logger.Info("Initializing " + address + " session and evidence cache") + node.EvidenceStore = &CacheStorage{} + node.SessionStore = &CacheStorage{} + node.EvidenceStore.Init(c.PocketConfig.DataDir, evidenceDbName, c.TendermintConfig.LevelDBOptions, c.PocketConfig.MaxEvidenceCacheEntires, false) + node.SessionStore.Init(c.PocketConfig.DataDir, "", c.TendermintConfig.LevelDBOptions, c.PocketConfig.MaxSessionCacheEntries, true) + + // Set the GOBSession and GOBEvidence Global for backwards compatibility for pre-LeanPocket + if GlobalSessionCache == nil { + GlobalSessionCache = node.SessionStore + GlobalEvidenceCache = node.EvidenceStore + } + }) +} + +func InitPocketNodeCaches(c types.Config, logger log.Logger) { + for _, node := range GlobalPocketNodes { + InitPocketNodeCache(node, c, logger) + } +} + +// GetPocketNodeByAddress returns a PocketNode from global map GlobalPocketNodes +func GetPocketNodeByAddress(address *sdk.Address) (*PocketNode, error) { + node, ok := GlobalPocketNodes[address.String()] + if !ok { + return nil, fmt.Errorf("failed to find private key for %s", address.String()) + } + return node, nil +} + +// CleanPocketNodes sets the global pocket nodes and its caches back to original state as if the node is starting up again. +// Cleaning up pocket nodes is used for unit and integration tests where the cache is initialized in various scenarios (relays, tx, etc). +func CleanPocketNodes() { + for _, n := range GlobalPocketNodes { + if n == nil { + continue + } + cacheToClean := []*CacheStorage{n.EvidenceStore, n.SessionStore} + for _, r := range cacheToClean { + if r == nil { + continue + } + r.Clear() + if r.DB == nil { + continue + } + r.DB.Close() + } + GlobalEvidenceCache = nil + GlobalSessionCache = nil + GlobalPocketNodes = map[string]*PocketNode{} + } +} + +// GetPocketNode returns a PocketNode from global map GlobalPocketNodes, it does not guarantee order +func GetPocketNode() *PocketNode { + for _, r := range GlobalPocketNodes { + if r != nil { + return r + } + } + return nil +} diff --git a/x/pocketcore/types/pocketNode_test.go b/x/pocketcore/types/pocketNode_test.go new file mode 100644 index 00000000..5ee6fa40 --- /dev/null +++ b/x/pocketcore/types/pocketNode_test.go @@ -0,0 +1,79 @@ +package types + +import ( + sdk "github.com/pokt-network/pocket-core/types" + "github.com/stretchr/testify/assert" + "github.com/tendermint/tendermint/libs/log" + "testing" +) + +func TestPocketNodeAdd(t *testing.T) { + key := GetRandomPrivateKey() + address := sdk.GetAddress(key.PublicKey()) + AddPocketNode(key, log.NewNopLogger()) + _, ok := GlobalPocketNodes[address.String()] + assert.True(t, ok) +} + +func TestPocketNodeGetByAddress(t *testing.T) { + key := GetRandomPrivateKey() + address := sdk.GetAddress(key.PublicKey()) + AddPocketNode(key, log.NewNopLogger()) + node, err := GetPocketNodeByAddress(&address) + assert.Nil(t, err) + assert.NotNil(t, node) +} + +func TestPocketNodeGet(t *testing.T) { + key := GetRandomPrivateKey() + AddPocketNode(key, log.NewNopLogger()) + node := GetPocketNode() + assert.NotNil(t, node) +} + +func TestPocketNodeCleanCache(t *testing.T) { + key := GetRandomPrivateKey() + AddPocketNode(key, log.NewNopLogger()) + CleanPocketNodes() + assert.Nil(t, GlobalSessionCache) + assert.Nil(t, GlobalEvidenceCache) + assert.EqualValues(t, 0, len(GlobalPocketNodes)) +} + +func TestPocketNodeInitCache(t *testing.T) { + CleanPocketNodes() + key := GetRandomPrivateKey() + testingConfig := sdk.DefaultTestingPocketConfig() + AddPocketNode(key, log.NewNopLogger()) + InitPocketNodeCaches(testingConfig, log.NewNopLogger()) + address := sdk.GetAddress(key.PublicKey()) + node, err := GetPocketNodeByAddress(&address) + assert.NotNil(t, GlobalSessionCache) + assert.NotNil(t, GlobalEvidenceCache) + assert.EqualValues(t, 1, len(GlobalPocketNodes)) + assert.Nil(t, err) + assert.NotNil(t, node.EvidenceStore) + assert.NotNil(t, node.SessionStore) +} + +func TestPocketNodeInitCaches(t *testing.T) { + CleanPocketNodes() + key := GetRandomPrivateKey() + key2 := GetRandomPrivateKey() + logger := log.NewNopLogger() + testingConfig := sdk.DefaultTestingPocketConfig() + testingConfig.PocketConfig.LeanPocket = true + AddPocketNode(key, logger) + AddPocketNode(key2, logger) + InitPocketNodeCaches(testingConfig, logger) + assert.NotNil(t, GlobalSessionCache) + assert.NotNil(t, GlobalEvidenceCache) + assert.EqualValues(t, 2, len(GlobalPocketNodes)) + addresses := []sdk.Address{sdk.GetAddress(key.PublicKey()), sdk.GetAddress(key2.PublicKey())} + for _, address := range addresses { + node, err := GetPocketNodeByAddress(&address) + assert.Nil(t, err) + assert.NotNil(t, node.EvidenceStore) + assert.NotNil(t, node.SessionStore) + } +} diff --git a/x/pocketcore/types/proof.go b/x/pocketcore/types/proof.go index 75311f5d..b11bd5fa 100644 --- a/x/pocketcore/types/proof.go +++ b/x/pocketcore/types/proof.go @@ -4,10 +4,9 @@ import ( "encoding/hex" "encoding/json" "fmt" - "log" - "github.com/pokt-network/pocket-core/crypto" sdk "github.com/pokt-network/pocket-core/types" + "log" ) // "Proof" - An interface representation of an economic proof of work/burn (relay or challenge) @@ -19,7 +18,7 @@ type Proof interface { GetSigner() sdk.Address // returns the main signer(s) for the proof (used in messages) SessionHeader() SessionHeader // returns the session header Validate(appSupportedBlockchains []string, sessionNodeCount int, sessionBlockHeight int64) sdk.Error // validate the object - Store(max sdk.BigInt) // handle the proof after validation + Store(max sdk.BigInt, storage *CacheStorage) // handle the proof after validation ToProto() ProofI // convert to protobuf } @@ -211,9 +210,9 @@ func (rp RelayProof) HashStringWithSignature() string { } // "Store" - Handles the relay proof object by adding it to the cache -func (rp RelayProof) Store(maxRelays sdk.BigInt) { +func (rp RelayProof) Store(maxRelays sdk.BigInt, evidenceStore *CacheStorage) { // add the Proof to the global (in memory) collection of proofs - SetProof(rp.SessionHeader(), RelayEvidence, rp, maxRelays) + SetProof(rp.SessionHeader(), RelayEvidence, rp, maxRelays, evidenceStore) } func (rp RelayProof) GetSigner() sdk.Address { @@ -236,7 +235,7 @@ func (rp RelayProof) GetSigner() sdk.Address { var _ Proof = ChallengeProofInvalidData{} // compile time interface implementation // "ValidateLocal" - Validate local is used to validate a challenge request directly from a client -func (c ChallengeProofInvalidData) ValidateLocal(h SessionHeader, maxRelays sdk.BigInt, supportedBlockchains []string, sessionNodeCount int, sessionNodes SessionNodes, selfAddr sdk.Address) sdk.Error { +func (c ChallengeProofInvalidData) ValidateLocal(h SessionHeader, maxRelays sdk.BigInt, supportedBlockchains []string, sessionNodeCount int, sessionNodes SessionNodes, selfAddr sdk.Address, evidenceStore *CacheStorage) sdk.Error { // check if verifyPubKey in session (must be in session to do challenges) if !sessionNodes.Contains(selfAddr) { return NewNodeNotInSessionError(ModuleName) @@ -245,7 +244,7 @@ func (c ChallengeProofInvalidData) ValidateLocal(h SessionHeader, maxRelays sdk. // calculate the maximum possible challenges maxPossibleChallenges := maxRelays.ToDec().Quo(sdk.NewDec(int64(len(supportedBlockchains)))).Quo(sdk.NewDec(int64(sessionNodeCount))).RoundInt() // check for overflow on # of proofs - evidence, er := GetEvidence(h, ChallengeEvidence, maxPossibleChallenges) + evidence, er := GetEvidence(h, ChallengeEvidence, maxPossibleChallenges, evidenceStore) if er != nil { return sdk.ErrInternal(er.Error()) } @@ -448,9 +447,9 @@ func (c ChallengeProofInvalidData) GetSigner() sdk.Address { } // "Store" - Stores the challenge proof (stores in cache) -func (c ChallengeProofInvalidData) Store(maxChallenges sdk.BigInt) { +func (c ChallengeProofInvalidData) Store(maxChallenges sdk.BigInt, evidenceStore *CacheStorage) { // add the Proof to the global (in memory) collection of proofs - SetProof(c.SessionHeader(), ChallengeEvidence, c, maxChallenges) + SetProof(c.SessionHeader(), ChallengeEvidence, c, maxChallenges, evidenceStore) } func (c ChallengeProofInvalidData) ToProto() ProofI { diff --git a/x/pocketcore/types/proot_test.go b/x/pocketcore/types/proot_test.go index 9a64dd0a..26109249 100644 --- a/x/pocketcore/types/proot_test.go +++ b/x/pocketcore/types/proot_test.go @@ -516,7 +516,7 @@ func TestChallengeProofInvalidData_ValidateLocal(t *testing.T) { Chain: tt.proof.MinorityResponse.Proof.Blockchain, SessionBlockHeight: tt.proof.MinorityResponse.Proof.SessionBlockHeight, } - if err := tt.proof.ValidateLocal(h, tt.maxRelays, tt.supportedBlockchains, 5, tt.sessionNodes, tt.reporterAddress); (err != nil) != tt.hasError { + if err := tt.proof.ValidateLocal(h, tt.maxRelays, tt.supportedBlockchains, 5, tt.sessionNodes, tt.reporterAddress, GlobalEvidenceCache); (err != nil) != tt.hasError { fmt.Println(tt.name) fmt.Println(err) t.Fatalf(err.Error()) diff --git a/x/pocketcore/types/service.go b/x/pocketcore/types/service.go index 3322c5d5..c37b6232 100644 --- a/x/pocketcore/types/service.go +++ b/x/pocketcore/types/service.go @@ -25,7 +25,7 @@ type Relay struct { } // "Validate" - Checks the validity of a relay request using store data -func (r *Relay) Validate(ctx sdk.Ctx, posKeeper PosKeeper, appsKeeper AppsKeeper, pocketKeeper PocketKeeper, node sdk.Address, hb *HostedBlockchains, sessionBlockHeight int64) (maxPossibleRelays sdk.BigInt, err sdk.Error) { +func (r *Relay) Validate(ctx sdk.Ctx, posKeeper PosKeeper, appsKeeper AppsKeeper, pocketKeeper PocketKeeper, hb *HostedBlockchains, sessionBlockHeight int64, node *PocketNode) (maxPossibleRelays sdk.BigInt, err sdk.Error) { // validate payload if err := r.Payload.Validate(); err != nil { return sdk.ZeroInt(), NewEmptyPayloadDataError(ModuleName) @@ -67,8 +67,8 @@ func (r *Relay) Validate(ctx sdk.Ctx, posKeeper PosKeeper, appsKeeper AppsKeeper SessionBlockHeight: r.Proof.SessionBlockHeight, } // validate unique relay - evidence, totalRelays := GetTotalProofs(header, RelayEvidence, maxPossibleRelays) - if evidence.IsSealed() { + evidence, totalRelays := GetTotalProofs(header, RelayEvidence, maxPossibleRelays, node.EvidenceStore) + if node.EvidenceStore.IsSealed(evidence) { return sdk.ZeroInt(), NewSealedEvidenceError(ModuleName) } // get evidence key by proof @@ -80,11 +80,12 @@ func (r *Relay) Validate(ctx sdk.Ctx, posKeeper PosKeeper, appsKeeper AppsKeeper return sdk.ZeroInt(), NewOverServiceError(ModuleName) } // validate the Proof - if err := r.Proof.ValidateLocal(app.GetChains(), int(sessionNodeCount), sessionBlockHeight, node); err != nil { + nodeAddr := node.GetAddress() + if err := r.Proof.ValidateLocal(app.GetChains(), int(sessionNodeCount), sessionBlockHeight, nodeAddr); err != nil { return sdk.ZeroInt(), err } // check cache - session, found := GetSession(header) + session, found := GetSession(header, node.SessionStore) // if not found generate the session if !found { bh, err := sessionCtx.BlockHash(pocketKeeper.Codec(), sessionCtx.BlockHeight()) @@ -97,10 +98,10 @@ func (r *Relay) Validate(ctx sdk.Ctx, posKeeper PosKeeper, appsKeeper AppsKeeper return sdk.ZeroInt(), er } // add to cache - SetSession(session) + SetSession(session, node.SessionStore) } // validate the session - err = session.Validate(node, app, int(sessionNodeCount)) + err = session.Validate(nodeAddr, app, int(sessionNodeCount)) if err != nil { return sdk.ZeroInt(), err } @@ -111,13 +112,21 @@ func (r *Relay) Validate(ctx sdk.Ctx, posKeeper PosKeeper, appsKeeper AppsKeeper return maxPossibleRelays, nil } +func addServiceMetricErrorFor(blockchain string, address *sdk.Address) { + if GlobalPocketConfig.LeanPocket { + go GlobalServiceMetric().AddErrorFor(blockchain, address) + } else { + GlobalServiceMetric().AddErrorFor(blockchain, address) + } +} + // "Execute" - Attempts to do a request on the non-native blockchain specified -func (r Relay) Execute(hostedBlockchains *HostedBlockchains) (string, sdk.Error) { +func (r Relay) Execute(hostedBlockchains *HostedBlockchains, address *sdk.Address) (string, sdk.Error) { // retrieve the hosted blockchain url requested chain, err := hostedBlockchains.GetChain(r.Proof.Blockchain) if err != nil { // metric track - GlobalServiceMetric().AddErrorFor(r.Proof.Blockchain) + addServiceMetricErrorFor(r.Proof.Blockchain, address) return "", err } url := strings.Trim(chain.URL, `/`) @@ -128,7 +137,7 @@ func (r Relay) Execute(hostedBlockchains *HostedBlockchains) (string, sdk.Error) res, er := executeHTTPRequest(r.Payload.Data, url, GlobalPocketConfig.UserAgent, chain.BasicAuth, r.Payload.Method, r.Payload.Headers) if er != nil { // metric track - GlobalServiceMetric().AddErrorFor(r.Proof.Blockchain) + addServiceMetricErrorFor(r.Proof.Blockchain, address) return res, NewHTTPExecutionError(ModuleName, er) } return res, nil diff --git a/x/pocketcore/types/service_test.go b/x/pocketcore/types/service_test.go index 8044fb88..8d1f0814 100644 --- a/x/pocketcore/types/service_test.go +++ b/x/pocketcore/types/service_test.go @@ -129,14 +129,14 @@ func TestRelay_Validate(t *testing.T) { // TODO add overservice, and not unique } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - k := MockPosKeeper{Validators: tt.allNodes} k2 := MockAppsKeeper{Applications: []exported2.ApplicationI{tt.app}} k3 := MockPocketKeeper{} - _, err := tt.relay.Validate(newContext(t, false).WithAppVersion("0.0.0"), k, k2, k3, tt.node.Address, tt.hb, 1) + pocketNode := GetPocketNode() + _, err := tt.relay.Validate(newContext(t, false).WithAppVersion("0.0.0"), k, k2, k3, tt.hb, 1, pocketNode) assert.Equal(t, err != nil, tt.hasError) }) - ClearSessionCache() + ClearSessionCache(GlobalSessionCache) } } @@ -146,6 +146,7 @@ func TestRelay_Execute(t *testing.T) { appPrivateKey := GetRandomPrivateKey() appPubKey := appPrivateKey.PublicKey().RawString() npk := getRandomPubKey() + nodeAddr := sdk.Address(npk.Address()) nodePubKey := npk.RawString() ethereum := hex.EncodeToString([]byte{01}) p := Payload{ @@ -184,7 +185,7 @@ func TestRelay_Execute(t *testing.T) { URL: "https://server.com/relay/", }}, } - response, err := validRelay.Execute(&hb) + response, err := validRelay.Execute(&hb, &nodeAddr) assert.True(t, err == nil) assert.Equal(t, response, "bar") } @@ -220,12 +221,12 @@ func TestRelay_HandleProof(t *testing.T) { }, } validRelay.Proof.RequestHash = validRelay.RequestHashString() - validRelay.Proof.Store(sdk.NewInt(100000)) + validRelay.Proof.Store(sdk.NewInt(100000), GlobalEvidenceCache) res := GetProof(SessionHeader{ ApplicationPubKey: appPubKey, Chain: ethereum, SessionBlockHeight: 1, - }, RelayEvidence, 0) + }, RelayEvidence, 0, GlobalEvidenceCache) assert.True(t, reflect.DeepEqual(validRelay.Proof, res)) } diff --git a/x/pocketcore/types/session.go b/x/pocketcore/types/session.go index 0ffa7cb1..78888552 100644 --- a/x/pocketcore/types/session.go +++ b/x/pocketcore/types/session.go @@ -12,12 +12,12 @@ import ( // "Session" - The relationship between an application and the pocket network -func (s Session) IsSealed() bool { +func (s Session) IsSealable() bool { return false } -func (s Session) Seal() CacheObject { - return s +func (s Session) HashString() string { + return s.HashString() } // "NewSession" - create a new session from seed data