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:
commit
7ea318e1ef
2 changed files with 132 additions and 57 deletions
|
@ -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,19 +96,42 @@ 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)",
|
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
{
|
||||||
Name: "gas, g",
|
Name: "invoke",
|
||||||
Usage: "gas to pay for contract deployment",
|
Usage: "invoke deployed contract on the blockchain",
|
||||||
|
UsageText: "neo-go contract invoke -e endpoint -w wif [-g gas] scripthash [arguments...]",
|
||||||
|
Description: `Executes given (as a script hash) deployed script with the given arguments.
|
||||||
|
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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testinvoke",
|
Name: "testinvoke",
|
||||||
|
@ -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,13 +409,27 @@ 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, "", " ")
|
b, err := json.MarshalIndent(resp.Result, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(b))
|
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
|
||||||
|
|
|
@ -153,26 +153,25 @@ 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 gas > 0 {
|
||||||
if err = AddInputsAndUnspentsToTx(tx, fromAddress, gasID, gas, c); err != nil {
|
if err = AddInputsAndUnspentsToTx(tx, fromAddress, gasID, gas, c); err != nil {
|
||||||
return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction")
|
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 {
|
||||||
return txHash, errors.Wrap(err, "failed to sign tx")
|
return txHash, errors.Wrap(err, "failed to sign tx")
|
||||||
|
|
Loading…
Reference in a new issue