mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 03:41:34 +00:00
rpc: refactor SignAndPushInvocationTx
We have a set of accounts provided via `cosigners` argument, so we should fill all transaction witnesses in (not only sender's witness). If we can't properly construct witnesses for all of the signers then an error should be returned.
This commit is contained in:
parent
eaf45d243b
commit
48ae1cc486
3 changed files with 197 additions and 39 deletions
|
@ -314,6 +314,25 @@ func TestComlileAndInvokeFunction(t *testing.T) {
|
|||
require.Len(t, res.Stack, 1)
|
||||
require.Equal(t, []byte("on create|sub create"), res.Stack[0].Value())
|
||||
|
||||
// deploy verification contract
|
||||
nefName = path.Join(tmpDir, "verify.nef")
|
||||
manifestName = path.Join(tmpDir, "verify.manifest.json")
|
||||
e.Run(t, "neo-go", "contract", "compile",
|
||||
"--in", "testdata/verify.go",
|
||||
"--config", "testdata/verify.yml",
|
||||
"--out", nefName, "--manifest", manifestName)
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "contract", "deploy",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet, "--address", validatorAddr,
|
||||
"--in", nefName, "--manifest", manifestName)
|
||||
line, err = e.Out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: "))
|
||||
hVerify, err := util.Uint160DecodeStringLE(line)
|
||||
require.NoError(t, err)
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
t.Run("real invoke", func(t *testing.T) {
|
||||
cmd := []string{"neo-go", "contract", "invokefunction",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addr}
|
||||
|
@ -338,31 +357,17 @@ func TestComlileAndInvokeFunction(t *testing.T) {
|
|||
e.In.WriteString("one\r")
|
||||
e.Run(t, append(cmd, "--force", h.StringLE(), "fail")...)
|
||||
})
|
||||
|
||||
t.Run("cosigner is deployed contract", func(t *testing.T) {
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, append(cmd, h.StringLE(), "getValue",
|
||||
"--", validatorAddr, hVerify.StringLE())...)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("real invoke and save tx", func(t *testing.T) {
|
||||
txout := path.Join(tmpDir, "test_contract_tx.json")
|
||||
|
||||
nefName = path.Join(tmpDir, "verify.nef")
|
||||
manifestName = path.Join(tmpDir, "verify.manifest.json")
|
||||
e.Run(t, "neo-go", "contract", "compile",
|
||||
"--in", "testdata/verify.go",
|
||||
"--config", "testdata/verify.yml",
|
||||
"--out", nefName, "--manifest", manifestName)
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "contract", "deploy",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||
"--wallet", validatorWallet, "--address", validatorAddr,
|
||||
"--in", nefName, "--manifest", manifestName)
|
||||
|
||||
line, err := e.Out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
line = strings.TrimSpace(strings.TrimPrefix(line, "Contract: "))
|
||||
hVerify, err := util.Uint160DecodeStringLE(line)
|
||||
require.NoError(t, err)
|
||||
e.checkTxPersisted(t)
|
||||
|
||||
cmd = []string{"neo-go", "contract", "invokefunction",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||
"--out", txout,
|
||||
|
|
|
@ -530,8 +530,10 @@ func (c *Client) SubmitRawOracleResponse(ps request.RawParams) error {
|
|||
}
|
||||
|
||||
// SignAndPushInvocationTx signs and pushes given script as an invocation
|
||||
// transaction using given wif to sign it and spending the amount of gas
|
||||
// specified. It returns a hash of the invocation transaction and an error.
|
||||
// transaction using given wif to sign it and given cosigners to cosign it if
|
||||
// possible. It spends the amount of gas specified. It returns a hash of the
|
||||
// invocation transaction and an error. If one of the cosigners accounts is
|
||||
// neither contract-based nor unlocked an error is returned.
|
||||
func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sysfee int64, netfee fixedn.Fixed8, cosigners []SignerAccount) (util.Uint256, error) {
|
||||
var txHash util.Uint256
|
||||
var err error
|
||||
|
@ -543,6 +545,28 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
|
|||
if err = acc.SignTx(c.GetNetwork(), tx); err != nil {
|
||||
return txHash, fmt.Errorf("failed to sign tx: %w", err)
|
||||
}
|
||||
// try to add witnesses for the rest of the signers
|
||||
for i, signer := range tx.Signers[1:] {
|
||||
var isOk bool
|
||||
for _, cosigner := range cosigners {
|
||||
if signer.Account == cosigner.Signer.Account {
|
||||
err = cosigner.Account.SignTx(c.GetNetwork(), tx)
|
||||
if err != nil { // then account is non-contract-based and locked, but let's provide more detailed error
|
||||
if paramNum := len(cosigner.Account.Contract.Parameters); paramNum != 0 && cosigner.Account.Contract.Deployed {
|
||||
return txHash, fmt.Errorf("failed to add contract-based witness for signer #%d (%s): "+
|
||||
"%d parameters must be provided to construct invocation script", i, address.Uint160ToString(signer.Account), paramNum)
|
||||
}
|
||||
return txHash, fmt.Errorf("failed to add witness for signer #%d (%s): account should be unlocked to add the signature. "+
|
||||
"Store partially-signed transaction and then use 'wallet sign' command to cosign it", i, address.Uint160ToString(signer.Account))
|
||||
}
|
||||
isOk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isOk {
|
||||
return txHash, fmt.Errorf("failed to add witness for signer #%d (%s): account wasn't provided", i, address.Uint160ToString(signer.Account))
|
||||
}
|
||||
}
|
||||
txHash = tx.Hash()
|
||||
actualHash, err := c.SendRawTransaction(tx)
|
||||
if err != nil {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
|
@ -396,24 +397,152 @@ func TestSignAndPushInvocationTx(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.NoError(t, c.Init())
|
||||
|
||||
priv := testchain.PrivateKey(0)
|
||||
acc := wallet.NewAccountFromPrivateKey(priv)
|
||||
h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc, 30, 0, []client.SignerAccount{
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: priv.GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
priv0 := testchain.PrivateKeyByID(0)
|
||||
acc0 := wallet.NewAccountFromPrivateKey(priv0)
|
||||
|
||||
mp := chain.GetMemPool()
|
||||
tx, ok := mp.TryGetValue(h)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, h, tx.Hash())
|
||||
require.EqualValues(t, 30, tx.SystemFee)
|
||||
verifyWithoutParamsCtr, err := util.Uint160DecodeStringLE(verifyContractHash)
|
||||
require.NoError(t, err)
|
||||
acc1 := &wallet.Account{
|
||||
Address: address.Uint160ToString(verifyWithoutParamsCtr),
|
||||
Contract: &wallet.Contract{
|
||||
Parameters: []wallet.ContractParam{},
|
||||
Deployed: true,
|
||||
},
|
||||
Locked: true,
|
||||
Default: false,
|
||||
}
|
||||
|
||||
verifyWithParamsCtr, err := util.Uint160DecodeStringLE(verifyWithArgsContractHash)
|
||||
require.NoError(t, err)
|
||||
acc2 := &wallet.Account{
|
||||
Address: address.Uint160ToString(verifyWithParamsCtr),
|
||||
Contract: &wallet.Contract{
|
||||
Parameters: []wallet.ContractParam{
|
||||
{Name: "argString", Type: smartcontract.StringType},
|
||||
{Name: "argInt", Type: smartcontract.IntegerType},
|
||||
{Name: "argBool", Type: smartcontract.BoolType},
|
||||
},
|
||||
Deployed: true,
|
||||
},
|
||||
Locked: true,
|
||||
Default: false,
|
||||
}
|
||||
|
||||
priv3 := testchain.PrivateKeyByID(3)
|
||||
acc3 := wallet.NewAccountFromPrivateKey(priv3)
|
||||
|
||||
check := func(t *testing.T, h util.Uint256) {
|
||||
mp := chain.GetMemPool()
|
||||
tx, ok := mp.TryGetValue(h)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, h, tx.Hash())
|
||||
require.EqualValues(t, 30, tx.SystemFee)
|
||||
}
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
t.Run("signer0: sig", func(t *testing.T) {
|
||||
h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []client.SignerAccount{
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: priv0.GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc0,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
check(t, h)
|
||||
})
|
||||
t.Run("signer0: sig; signer1: sig", func(t *testing.T) {
|
||||
h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []client.SignerAccount{
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: priv0.GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc0,
|
||||
},
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: priv3.GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc3,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
check(t, h)
|
||||
})
|
||||
t.Run("signer0: sig; signer1: contract-based paramless", func(t *testing.T) {
|
||||
h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []client.SignerAccount{
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: priv0.GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc0,
|
||||
},
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: verifyWithoutParamsCtr,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc1,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
check(t, h)
|
||||
})
|
||||
})
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Run("signer0: sig; signer1: contract-based with params", func(t *testing.T) {
|
||||
_, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []client.SignerAccount{
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: priv0.GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc0,
|
||||
},
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: verifyWithParamsCtr,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc2,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("signer0: sig; signer1: locked sig", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
acc4 := &wallet.Account{
|
||||
Address: address.Uint160ToString(pk.GetScriptHash()),
|
||||
Contract: &wallet.Contract{
|
||||
Script: pk.PublicKey().GetVerificationScript(),
|
||||
Parameters: []wallet.ContractParam{{Name: "parameter0", Type: smartcontract.SignatureType}},
|
||||
},
|
||||
}
|
||||
_, err = c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []client.SignerAccount{
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: priv0.GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc0,
|
||||
},
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: util.Uint160{1, 2, 3},
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc4,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSignAndPushP2PNotaryRequest(t *testing.T) {
|
||||
|
|
Loading…
Reference in a new issue