-
Notifications
You must be signed in to change notification settings - Fork 94
cli: db download-contract command #3960
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
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3960 +/- ##
==========================================
- Coverage 82.03% 81.75% -0.29%
==========================================
Files 348 348
Lines 49222 49419 +197
==========================================
+ Hits 40380 40402 +22
- Misses 7147 7329 +182
+ Partials 1695 1688 -7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
I overlooked downloading the contract storage 🙈 . Still open to input on the TODOs how to best expose the data. Consider this a draft (I don't have the option to change it to one) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good feature, but some critical decisions need to be polished.
&cli.UintFlag{ | ||
Name: "height", | ||
Usage: "Height at which to get the contract state for", | ||
Required: false, | ||
DefaultText: "latest", | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have options.Historic
designed specifically for such kind of things.
{ | ||
Name: "download-contract", | ||
Usage: "Download a contract including storage from a remote chain into the local database", | ||
UsageText: "neo-go db download-contract -c contract-hash -r endpoint [--height height] [--config-file] [--force]", | ||
Action: downloadContract, | ||
Flags: cfgDownloadFlags, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say that this command is closer to contract
subset of commands (e.g. ./neo-go contract download
). Also, it reminds me of ./neo-go wallet import-deployed
, so for the sake of CLI interface unification we can use it as ./neo-go contract import-deployed
. But it's a matter of discussion because, as @ixje said, this commend performs a direct DB modification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
performs a direct DB modification
Which makes it a db
command for me. But I'm thinking of separating download and import stages (consider contract state as well). In which case contract download
+ db import-contract
would be a nice pair. This separation can be useful for unit tests similar to ones we have in neofs-contract repository where some state is stored in the repository and imported as needed for tests.
This needs to be discussed, it is possible to unify these tools and we better do this now, before we have 25 variations on the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vote up for separation.
cli/server/server.go
Outdated
&cli.StringFlag{ | ||
Name: "contract-hash", | ||
Usage: "Script hash of the contract to download", | ||
Required: true, | ||
Aliases: []string{"c"}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be of flags.Address
type. We already have a designated flag for this, it can be moved to options
package and reused from both wallet
and contract
:
Lines 300 to 305 in 4340c76
&flags.AddressFlag{ | |
Name: "contract", | |
Aliases: []string{"c"}, | |
Required: true, | |
Usage: "Contract hash or address", | |
}, |
cli/server/server.go
Outdated
ch, err := util.Uint160DecodeStringLE(ctx.String("contract-hash")[2:]) | ||
if err != nil { | ||
return cli.Exit(fmt.Errorf("failed to decode contract hash: %v", err), 1) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code will be simplified after flag type replacement.
cli/server/server.go
Outdated
_, err := native.GetContractByID(d, contractState.ID) | ||
if !errors.Is(err, storage.ErrKeyNotFound) { | ||
// ID is in use, get a new one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to check two cases: if err == nil
means that ID is occupied, we can take the next free one. if err != nil && !errors.Is(err, storage.ErrKeyNotFound)
means that it's likely some internal DB error, we should exit in this case.
prefix := append([]byte{0x08}, hash.BytesBE()...) | ||
states, err := client.FindStates( | ||
stateResponse.Root, | ||
managementContract, | ||
prefix, | ||
nil, | ||
nil, | ||
) | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... we can get rid of this code. Just invoke native Management's getContract
via historic Invoker, that's it.
Using internal knowledge of native Management storage like []byte{0x08}
at upper-level modules is not the best idea, it should be avoided if we can. The drawback of the suggested approach is: it won't work with C# node, they don't support historic invocations. @ixje is it critical?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical no, but might be inconvenient as now we'll need to add some graceful handling if the RPC node doesn't support historic fetching.
How about adding a comment explaining the prefix
used while maintaining support for both types of nodes? It is not as if this is likely to ever change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let's stick to the implementation-agnostic solution.
How about adding a comment
Agree, it's needed.
Also, let's replace magic 0x08
byte with native.PrefixContract
constant, so that it will be more clear to other developers.
cli/server/server.go
Outdated
return nil, fmt.Errorf( | ||
"failed to fetch contract state for contract %s: %w", | ||
hash.StringLE(), | ||
err, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use single line.
cli/server/server.go
Outdated
func GetContractStateAtHeight( | ||
client *rpcclient.Client, | ||
height uint32, | ||
hash util.Uint160, | ||
) (*state.Contract, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use single line for this.
// TODO: check with nspcc if we can update PutContractState with an extra param | ||
// or else how to have the cache for ManagementContract initialized such that it doesn't panic | ||
// trying to run markUpdated() | ||
func PutContractStateNoCache(d *dao.Simple, cs *state.Contract) error { | ||
return putContractState(d, cs, false) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the comment above at downloadContract
.
|
||
// TODO: decide how to best expose. There are too many options that for the time I picked a duplicate | ||
// function to have something working and let nspcc decide their preferred exposing option | ||
func GetNextContractID(d *dao.Simple) (int32, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's firstly decide where to place DB modification code.
Just now seeing the feedback. Will update after lunch |
I'm going to refrain from further updates to the PR until you've internally decided what the next move is before I get to throw away significant parts. |
Discussed, let's restructure the code in the following way:
This representation differs from the one used by NeoFS contracts. @roman-khimov, ACK? @ixje, if there are some additional questions, we may setup a meeting call and discuss them in person. |
Seems to be easy to adopt. The only concern is contracts with huge state, CSV is easy to iterate over and process entry by entry, JSON is different. |
Agreed to use split format: JSON for contract states + CSV for storage (a raw set of key-value pairs without contract identifier). |
fyi; I'm about to go on a holiday, so won't be able to pick this up until late August. |
Problem
#2406
We (coz) have used the feature (in neo-express) for a couple of years now and have learned that
3.
of original issue)4.
of the original issue is theoretically a "problem" but in practice not yet encountered. So again can be ignored for the time being.Solution
The still relevant parts are addressed with this PR, being the basic functionality of fetching a contract state from a remote chain and storing it locally. From practice this was always done when the node was not running, so I implemented it as a subcommand of
neo-go db
.The most relevant discussion imo is how to handle the persisting to storage, specifically around contract IDs. In neo-express I implemented it as follows
--force
is specified (but keep the local contract ID)This PR is slightly different where for
1.
we do an additional check to see if the the remote contract ID is not already in use locally.open things to address
db download-contract
command, the first time the node is started the new contract cannot be found (via thegetcontractstate
RPC method). Only after a shutdown and starting again it can be found. It's unclear to me why