Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: CoreContext Refactor #1741

Merged
merged 5 commits into from
Aug 6, 2018
Merged

R4R: CoreContext Refactor #1741

merged 5 commits into from
Aug 6, 2018

Conversation

alexanderbez
Copy link
Contributor

@alexanderbez alexanderbez commented Jul 18, 2018

ChangeLog

  • Separated logic/concerns from CoreContext. QueryContext now facilitates querying and sending txs and TxContext facilitates building and signing txs.
  • Cleaned up a bunch of the actual query context code
    • Separated into smaller composable methods with clear godocs
  • Implemented GetKeyInfo and GetPassphrase as a general helper as I saw the same logic being used a lot
  • Added enums for key.Info opposed to checking just strings

I've updated the democoin cool cmd to show how the new contexts would be used. One thing I came across and am not sure if it's the best approach is the utils.SendTx. That is what EnsureSignBuildBroadcast used to handle, but now that the logic and concerns have been split, not sure where to place that. Maybe utils is fine?


  • Updated all relevant documentation (docs/)
  • Updated all relevant code comments
  • Wrote tests
  • Updated CHANGELOG.md
  • Updated cmd/gaia and examples/

closes: #1551


For Admin Use:

  • Added appropriate labels to PR (ex. wip, ready-for-review, docs)
  • Reviewers Assigned
  • Squashed all commits, uses message "Merge pull request #XYZ: [title]" (coding standards)

// signed transaction to a node.
func SendTx(txCtx authctx.TxContext, queryCtx context.QueryContext, from []byte, name string, msgs []sdk.Msg) error {
if txCtx.AccountNumber == 0 {
accNum, err := queryCtx.GetAccountNumber(from)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When rebasing, can you be sure to add the "EnsureAccountExists" function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ValarDragon why is that? SendTx does what EnsureAccountExists virtually did. Do you want me to rip that out and call it EnsureAccountExists which SendTx would call?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally I'd like the more user-friendly error message which EnsureAccountExists provides before checking the account number / sequence. If the account doesn't exist, then we shouldn't be printing messages for updating account num / sequence, and we should never get to the point where it signs. (Then we're asking for password, and only get returned with an unfriendly ABCI error)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. Yeah I think I can leverage something similar 👍

Copy link
Contributor Author

@alexanderbez alexanderbez Jul 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a method to QueryContext#EnsureAccountExists. This will get called before the any signing even happens.

Edit: nvm, I already have GetAccount. I'll just use this.

type KeyType uint

// Info KeyTypes
const (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think key types is a great idea!

In the future, hopefully we can offload more code onto the key type, rather than have switch statements everyhere!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe KeyType should be a string?

@alexanderbez
Copy link
Contributor Author

alexanderbez commented Jul 19, 2018

@ValarDragon @cwgoes would love some initial/high-level feedback if this is a good way to approach things.

If so, then all that's left is updating all the internal CLI run functions.

@ValarDragon
Copy link
Contributor

Anything else you're looking for feedback on, other than what I commented on?

@alexanderbez
Copy link
Contributor Author

@ValarDragon, nope! I updated the cool example. It now checks that the account exists first.

@ValarDragon
Copy link
Contributor

Maybe i'm misunderstanding. I thought SendTx is intended to replace the EnsureSignBuildBroadcast method. If so, then we want this check to go inside of SendTx similar to whats done here: https://github.com/cosmos/cosmos-sdk/blob/develop/client/context/viper.go#L76. I think we want this in the send functionality, not inside of everything trying to call send. (Similar to how we get account number and sequence number in send)

@codecov
Copy link

codecov bot commented Jul 24, 2018

Codecov Report

Merging #1741 into develop will increase coverage by 1.13%.
The diff coverage is 60%.

@@             Coverage Diff             @@
##           develop    #1741      +/-   ##
===========================================
+ Coverage    63.55%   64.68%   +1.13%     
===========================================
  Files          119      114       -5     
  Lines         7062     6782     -280     
===========================================
- Hits          4488     4387     -101     
+ Misses        2314     2115     -199     
- Partials       260      280      +20

Copy link
Contributor

@cwgoes cwgoes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass (apologies for the delay in review) - definitely progress, this is still rife with conditionals but I guess that can't really be avoided.

Also I know @sunnya97 was working on integrating the keepers into the context somehow (so that gaiacli could just call keeper functions to query, which would be super convenient) - not sure exactly what the implementation plan is there but worth touching base.

This also needs a rebase/merge with develop.

rpcclient "github.com/tendermint/tendermint/rpc/client"
)

const ctxAccStoreName = "acc"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm should this be a constant? Why not a QueryContext field in case we want to change it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact we have QueryContext.AccountStore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed it is already a field in the QueryContext, however:

  1. There is no path (from what I can tell) where this is actually configurable or desirable (i.e. everywhere in the code uses acc)
  2. Since there is no CLI flag for it, a user can just do queryCtx.WithAccountStore(...).

I'm happy to revert it to being hard-coded.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks fine for now.

func (ctx QueryContext) WithUseLedger(useLedger bool) QueryContext {
ctx.UseLedger = useLedger
return ctx
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some fields in QueryContext without WithXYZ functions, although I guess if you didn't need to use them it doesn't matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll leave as is for now 👍

return res, err
}

if res.CheckTx.Code != uint32(0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reference some constant here - sdk.ABCICodeOK in types/ IIRC?

res.CheckTx.Log)
}

if res.DeliverTx.Code != uint32(0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto on using a constant.

}

resp := result.Response
if resp.Code != uint32(0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sdk.ABCICodeOK

return err
}

if txCtx.AccountNumber == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really isn't the right check - what if the account number was intentionally tried as 0? I think we want to check whether or not the user passed the --account-number flag at all.

Copy link
Contributor Author

@alexanderbez alexanderbez Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, it seems to be identical to the logic that currently exists (unless I'm missing something?):

https://github.com/cosmos/cosmos-sdk/blob/develop/client/context/viper.go#L105-L122

If the client does indeed intentionally pass 0, then it'll look it up automatically -- kind of quirky, I understand.

If, as you're suggesting, we want to check if the client passed 0 and actually go with that, then'll be need to introduce the dependency of viper here. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just change the default value to like -1 to mean automatic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but then the UX will be a little weird cause the CLI description will have the default in there of -1.

Let me think on this a bit more -- I can probably come up with a cleaner way outside the scope of utils.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! I'd actually recommend just putting a "TODO: " above the relevant lines / moving this to a new issue (since this isn't a performance regression). We can do these UI nice to haves until later! (I doubt practically anyone will be doing --sequence=0 for the next month)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, fair point! I'll do this :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, it seems to be identical to the logic that currently exists (unless I'm missing something?):

It is; the current logic is wrong. Unfortunately IIRC it's wrong due to a bug in Viper which we couldn't fix without forking the upstream.

I guess it's fine to leave as-is (with a comment) for now.

txCtx = txCtx.WithAccountNumber(accNum)
}

if txCtx.Sequence == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, with --sequence instead of --account-number.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.


// String implements the stringer interface for KeyType.
func (kt KeyType) String() string {
keyTypes := map[KeyType]string{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a constant? No need to recreate a map every time .String() is called.

}

ctx.Logger.Write(bz)
io.WriteString(ctx.Logger, "\n")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to use the logger here instead of just io.WriteString and fmt.Printf in various places? Although I guess we want machine-readable output and the logger adds extra info - maybe we could have a json logger and a human-readable logger though.

Copy link
Contributor Author

@alexanderbez alexanderbez Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm not sure I follow? The io.WriteString is just a convenience method for writing a newline. I can easily just append a newline byte to bz. The actual logger here is os.Stdout. I also don't see any fmt.Printfs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, you're right, I misread - never mind.

@ValarDragon
Copy link
Contributor

ValarDragon commented Aug 1, 2018

@alexanderbez while we're still reviewing, could you hold off on squashing? It makes it harder to to check if a fix was resolved as intended. I think we should actually update the PR template to not encourage squashing, and only have the merger squash when merging the PR. (Unless specific commit history is requested, or if review is complete)

@alexanderbez
Copy link
Contributor Author

@ValarDragon sorry, I squashed already (to resolve rebasing). I won't squash from here on out until approved 👍

@alexanderbez
Copy link
Contributor Author

@cwgoes, @ValarDragon I believe I addressed majority of your concerns and comments.

Regarding some aspects being rife with conditionals, if you could point them out, I'd be happy to improve! But generally I found there is little way around it just due to the nature of the way the context is used.

Regarding @sunnya97's proposed integration of keepers -- great idea, but let's address that in a separate PR that uses this as the basis 👍

// queryStore performs a query from a Tendermint node with the provided a store
// name and path.
func (ctx QueryContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([]byte, error) {
path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe "store" here should likewise be parameterized

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That prefix (/store for store queries) is hardcoded in baseapp


// we only need a passphrase for locally stored keys
if keyInfo.GetType() == keys.TypeLocal {
passphrase, err = ReadPassphraseFromStdin(name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Make Issue to address this before launch

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we shouldn't be passing around passphrases even in memory)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There already is an issue for this. #864

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we avoid storing the passphrase (transiently) in memory? We need to decrypt the encrypted keyfile.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cwgoes brings up a good point. Warrants further discussion. I'll add a reference to #864 in the code tho

@@ -13,7 +13,7 @@ type BroadcastTxBody struct {
}

// BroadcastTx REST Handler
func BroadcastTxRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
func BroadcastTxRequestHandlerFn(queryTx context.QueryContext) http.HandlerFunc {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queryTx?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errr typo. gracias.

@@ -53,19 +57,22 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
return cmd
}

func searchTxs(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]txInfo, error) {
func searchTxs(queryTx context.QueryContext, cdc *wire.Codec, tags []string) ([]txInfo, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queryTx?

)

// TxContext implements a transaction context created in SDK modules.
type TxContext struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TxBuilder?


// defaultChainID returns the chain ID from the genesis file if present. An
// error is returned if the file cannot be read or parsed.
func defaultChainID() (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems kinda weird to define this here... should it be defined in tendermint/commands?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if so maybe a NOTE is enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a note because I do not have enough context here. Honestly, I think we should remove it all together and require that the user always provide the correct chainID.

@@ -68,3 +69,14 @@ BUG FIXES
* \#1828 Force user to specify amount on create-validator command by removing default
* \#1839 Fixed bug where intra-tx counter wasn't set correctly for genesis validators
* [tests] \#1675 Fix non-deterministic `test_cover`
* [client] \#1551: Refactored `CoreContext`
* Renamed `CoreContext` to `QueryContext`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

propose: QueryContext -> CliContext. Since Query isn't the only thing we're doing there.

@jaekwon
Copy link
Contributor

jaekwon commented Aug 2, 2018

Overall great refactor for splitting the piece into two. Have a few items commented, like renaming QueryContext to CliContext.

Copy link
Contributor

@cwgoes cwgoes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK

Agree with Jae on the renaming but not strongly opinionated; my previous comments have all been addressed.

- Rename QueryContext
- Add a few TODOs
@alexanderbez
Copy link
Contributor Author

Updated to reflect the latest PR review(s):

  • Cleaned up faulty variable names
  • Add TODOs with issue references
  • Renamed QueryContext => CLIContext

/cc @jaekwon @cwgoes

@alexanderbez
Copy link
Contributor Author

@cwgoes @jaekwon can this be merged?

@cwgoes cwgoes merged commit 12c2c23 into develop Aug 6, 2018
@cwgoes cwgoes deleted the bez/1551-core-ctx-refactor branch August 6, 2018 18:11
@cwgoes
Copy link
Contributor

cwgoes commented Aug 6, 2018

@cwgoes @jaekwon can this be merged?

Verily!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Client: Refactor CoreContext
4 participants