Merge pull request #531 from nspcc-dev/invoke-tx

Proper blockchained invocation TX, like this:

$ ./bin/neo-go contract invokefunction -e http://localhost:20331 -w KxDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr -g 0.00001 50befd26fdf6e4d957c11e078b24ebce6291456f
This commit is contained in:
Roman Khimov 2019-11-29 19:32:24 +03:00 committed by GitHub
commit 7ea318e1ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 132 additions and 57 deletions

View file

@ -31,9 +31,22 @@ var (
errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument") errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument")
errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag") errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag")
errFileExist = errors.New("A file with given smart-contract name already exists") errFileExist = errors.New("A file with given smart-contract name already exists")
endpointFlag = cli.StringFlag{
Name: "endpoint, e",
Usage: "trusted RPC endpoint address (like 'http://localhost:20331')",
}
wifFlag = cli.StringFlag{
Name: "wif, w",
Usage: "key to sign deployed transaction (in wif format)",
}
gasFlag = cli.Float64Flag{
Name: "gas, g",
Usage: "gas to pay for transaction",
}
) )
var ( const (
// smartContractTmpl is written to a file when used with `init` command. // smartContractTmpl is written to a file when used with `init` command.
// %s is parsed to be the smartContractName // %s is parsed to be the smartContractName
smartContractTmpl = `package %s smartContractTmpl = `package %s
@ -83,18 +96,41 @@ func NewCommands() []cli.Command {
Name: "config, c", Name: "config, c",
Usage: "configuration input file (*.yml)", Usage: "configuration input file (*.yml)",
}, },
cli.StringFlag{ endpointFlag,
Name: "endpoint, e", wifFlag,
Usage: "RPC endpoint address (like 'http://seed4.ngd.network:20332')", gasFlag,
}, },
cli.StringFlag{ },
Name: "wif, w", {
Usage: "key to sign deployed transaction (in wif format)", Name: "invoke",
}, Usage: "invoke deployed contract on the blockchain",
cli.IntFlag{ UsageText: "neo-go contract invoke -e endpoint -w wif [-g gas] scripthash [arguments...]",
Name: "gas, g", Description: `Executes given (as a script hash) deployed script with the given arguments.
Usage: "gas to pay for contract deployment", See testinvoke documentation for the details about parameters. It differs
}, from testinvoke in that this command sends an invocation transaction to
the network.
`,
Action: invoke,
Flags: []cli.Flag{
endpointFlag,
wifFlag,
gasFlag,
},
},
{
Name: "invokefunction",
Usage: "invoke deployed contract on the blockchain",
UsageText: "neo-go contract invokefunction -e endpoint -w wif [-g gas] scripthash [method] [arguments...]",
Description: `Executes given (as a script hash) deployed script with the given method and
and arguments. See testinvokefunction documentation for the details about
parameters. It differs from testinvokefunction in that this command sends an
invocation transaction to the network.
`,
Action: invokeFunction,
Flags: []cli.Flag{
endpointFlag,
wifFlag,
gasFlag,
}, },
}, },
{ {
@ -114,10 +150,7 @@ func NewCommands() []cli.Command {
`, `,
Action: testInvoke, Action: testInvoke,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ endpointFlag,
Name: "endpoint, e",
Usage: "RPC endpoint address (like 'http://seed4.ngd.network:20332')",
},
}, },
}, },
{ {
@ -188,10 +221,7 @@ func NewCommands() []cli.Command {
`, `,
Action: testInvokeFunction, Action: testInvokeFunction,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ endpointFlag,
Name: "endpoint, e",
Usage: "RPC endpoint address (like 'http://seed4.ngd.network:20332')",
},
}, },
}, },
{ {
@ -199,10 +229,7 @@ func NewCommands() []cli.Command {
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)", Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
Action: testInvokeScript, Action: testInvokeScript,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ endpointFlag,
Name: "endpoint, e",
Usage: "RPC endpoint address (like 'http://seed4.ngd.network:20332')",
},
cli.StringFlag{ cli.StringFlag{
Name: "in, i", Name: "in, i",
Usage: "Input location of the avm file that needs to be invoked", Usage: "Input location of the avm file that needs to be invoked",
@ -311,18 +338,31 @@ func contractCompile(ctx *cli.Context) error {
} }
func testInvoke(ctx *cli.Context) error { func testInvoke(ctx *cli.Context) error {
return testInvokeInternal(ctx, false) return invokeInternal(ctx, false, false)
} }
func testInvokeFunction(ctx *cli.Context) error { func testInvokeFunction(ctx *cli.Context) error {
return testInvokeInternal(ctx, true) return invokeInternal(ctx, true, false)
} }
func testInvokeInternal(ctx *cli.Context, withMethod bool) error { func invoke(ctx *cli.Context) error {
var resp *rpc.InvokeScriptResponse return invokeInternal(ctx, false, true)
var operation string }
var paramsStart = 1
var params = make([]smartcontract.Parameter, 0) func invokeFunction(ctx *cli.Context) error {
return invokeInternal(ctx, true, true)
}
func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
var (
err error
gas util.Fixed8
operation string
params = make([]smartcontract.Parameter, 0)
paramsStart = 1
resp *rpc.InvokeScriptResponse
wif *keys.WIF
)
endpoint := ctx.String("endpoint") endpoint := ctx.String("endpoint")
if len(endpoint) == 0 { if len(endpoint) == 0 {
@ -348,6 +388,14 @@ func testInvokeInternal(ctx *cli.Context, withMethod bool) error {
} }
} }
if signAndPush {
gas = util.Fixed8FromFloat(ctx.Float64("gas"))
wif, err = getWifFromContext(ctx)
if err != nil {
return err
}
}
client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{})
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
@ -361,14 +409,28 @@ func testInvokeInternal(ctx *cli.Context, withMethod bool) error {
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
if signAndPush {
if len(resp.Result.Script) == 0 {
return cli.NewExitError(errors.New("no script returned from the RPC node"), 1)
}
script, err := hex.DecodeString(resp.Result.Script)
if err != nil {
return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %v", err), 1)
}
txHash, err := client.SignAndPushInvocationTx(script, wif, gas)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1)
}
fmt.Printf("Sent invocation transaction %s\n", txHash.ReverseString())
} else {
b, err := json.MarshalIndent(resp.Result, "", " ")
if err != nil {
return cli.NewExitError(err, 1)
}
b, err := json.MarshalIndent(resp.Result, "", " ") fmt.Println(string(b))
if err != nil {
return cli.NewExitError(err, 1)
} }
fmt.Println(string(b))
return nil return nil
} }
@ -459,6 +521,19 @@ func inspect(ctx *cli.Context) error {
return nil return nil
} }
func getWifFromContext(ctx *cli.Context) (*keys.WIF, error) {
wifStr := ctx.String("wif")
if len(wifStr) == 0 {
return nil, cli.NewExitError(errNoWIF, 1)
}
wif, err := keys.WIFDecode(wifStr, 0)
if err != nil {
return nil, cli.NewExitError(fmt.Errorf("bad wif: %v", err), 1)
}
return wif, nil
}
// contractDeploy deploys contract. // contractDeploy deploys contract.
func contractDeploy(ctx *cli.Context) error { func contractDeploy(ctx *cli.Context) error {
in := ctx.String("in") in := ctx.String("in")
@ -473,15 +548,11 @@ func contractDeploy(ctx *cli.Context) error {
if len(endpoint) == 0 { if len(endpoint) == 0 {
return cli.NewExitError(errNoEndpoint, 1) return cli.NewExitError(errNoEndpoint, 1)
} }
wifStr := ctx.String("wif") gas := util.Fixed8FromFloat(ctx.Float64("gas"))
if len(wifStr) == 0 {
return cli.NewExitError(errNoWIF, 1)
}
gas := util.Fixed8FromInt64(int64(ctx.Int("gas")))
wif, err := keys.WIFDecode(wifStr, 0) wif, err := getWifFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("bad wif: %v", err), 1) return err
} }
avm, err := ioutil.ReadFile(in) avm, err := ioutil.ReadFile(in)
if err != nil { if err != nil {
@ -503,9 +574,14 @@ func contractDeploy(ctx *cli.Context) error {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
txHash, err := client.DeployContract(avm, &conf.Contract, wif, gas) txScript, err := rpc.CreateDeploymentScript(avm, &conf.Contract)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to deploy: %v", err), 1) return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1)
}
txHash, err := client.SignAndPushInvocationTx(txScript, wif, gas)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1)
} }
fmt.Printf("Sent deployment transaction %s for contract %s\n", txHash.ReverseString(), hash.Hash160(avm).ReverseString()) fmt.Printf("Sent deployment transaction %s for contract %s\n", txHash.ReverseString(), hash.Hash160(avm).ReverseString())
return nil return nil

View file

@ -153,25 +153,24 @@ func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.F
return response, nil return response, nil
} }
// DeployContract deploys given contract to the blockchain using given wif to // SignAndPushInvocationTx signs and pushes given script as an invocation
// sign the transaction and spending the amount of gas specified. It returns // transaction using given wif to sign it and spending the amount of gas
// a hash of the deployment transaction and an error. // specified. It returns a hash of the invocation transaction and an error.
func (c *Client) DeployContract(avm []byte, contract *ContractDetails, wif *keys.WIF, gas util.Fixed8) (util.Uint256, error) { func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util.Fixed8) (util.Uint256, error) {
var txHash util.Uint256 var txHash util.Uint256
var err error
gasIDB, _ := hex.DecodeString("602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7") gasIDB, _ := hex.DecodeString("602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7")
gasID, _ := util.Uint256DecodeReverseBytes(gasIDB) gasID, _ := util.Uint256DecodeReverseBytes(gasIDB)
txScript, err := CreateDeploymentScript(avm, contract) tx := transaction.NewInvocationTX(script, gas)
if err != nil {
return txHash, errors.Wrap(err, "failed creating deployment script")
}
tx := transaction.NewInvocationTX(txScript, gas)
fromAddress := wif.PrivateKey.Address() fromAddress := wif.PrivateKey.Address()
if err = AddInputsAndUnspentsToTx(tx, fromAddress, gasID, gas, c); err != nil { if gas > 0 {
return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction") if err = AddInputsAndUnspentsToTx(tx, fromAddress, gasID, gas, c); err != nil {
return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction")
}
} }
if err = SignTx(tx, wif); err != nil { if err = SignTx(tx, wif); err != nil {