Merge pull request #1362 from nspcc-dev/feature/verification

Allow to use account with contracts on client
This commit is contained in:
Roman Khimov 2020-08-27 17:30:29 +03:00 committed by GitHub
commit 9e0c13b69d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 203 additions and 35 deletions

View file

@ -13,6 +13,7 @@ import (
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -166,6 +167,20 @@ func NewCommands() []cli.Command {
}, },
}, },
}, },
{
Name: "import-deployed",
Usage: "import deployed contract",
UsageText: "import-multisig --wallet <path> --wif <wif> --contract <hash>",
Action: importDeployed,
Flags: append([]cli.Flag{
walletPathFlag,
wifFlag,
cli.StringFlag{
Name: "contract, c",
Usage: "Contract hash",
},
}, options.RPC...),
},
{ {
Name: "remove", Name: "remove",
Usage: "remove an account from the wallet", Usage: "remove an account from the wallet",
@ -390,7 +405,7 @@ func importMultisig(ctx *cli.Context) error {
return nil return nil
} }
func importWallet(ctx *cli.Context) error { func importDeployed(ctx *cli.Context) error {
wall, err := openWallet(ctx.String("wallet")) wall, err := openWallet(ctx.String("wallet"))
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
@ -398,6 +413,56 @@ func importWallet(ctx *cli.Context) error {
defer wall.Close() defer wall.Close()
rawHash := strings.TrimPrefix("0x", ctx.String("contract"))
h, err := util.Uint160DecodeStringLE(rawHash)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid contract hash: %w", err), 1)
}
acc, err := newAccountFromWIF(ctx.String("wif"))
if err != nil {
return cli.NewExitError(err, 1)
}
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return err
}
cs, err := c.GetContractState(h)
if err != nil {
return cli.NewExitError(fmt.Errorf("can't fetch contract info: %w", err), 1)
}
md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify)
if md == nil {
return cli.NewExitError("contract has no `verify` method", 1)
}
acc.Contract.Script = cs.Script
for _, p := range md.Parameters {
acc.Contract.Parameters = append(acc.Contract.Parameters, wallet.ContractParam{
Name: p.Name,
Type: p.Type,
})
}
acc.Contract.Deployed = true
if err := addAccountAndSave(wall, acc); err != nil {
return cli.NewExitError(err, 1)
}
return nil
}
func importWallet(ctx *cli.Context) error {
wall, err := openWallet(ctx.String("wallet"))
if err != nil {
return cli.NewExitError(err, 1)
}
defer wall.Close()
acc, err := newAccountFromWIF(ctx.String("wif")) acc, err := newAccountFromWIF(ctx.String("wif"))
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)

View file

@ -226,24 +226,7 @@ func TestCreateBasicChain(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Push some contract into the chain. // Push some contract into the chain.
name := prefix + "test_contract.go" txDeploy, avm := newDeployTx(t, prefix+"test_contract.go")
c, err := ioutil.ReadFile(name)
require.NoError(t, err)
avm, di, err := compiler.CompileWithDebugInfo(name, bytes.NewReader(c))
require.NoError(t, err)
t.Logf("contractHash: %s", hash.Hash160(avm).StringLE())
script := io.NewBufBinWriter()
m, err := di.ConvertToManifest(smartcontract.HasStorage, nil)
require.NoError(t, err)
bs, err := m.MarshalJSON()
require.NoError(t, err)
emit.Bytes(script.BinWriter, bs)
emit.Bytes(script.BinWriter, avm)
emit.Syscall(script.BinWriter, interopnames.SystemContractCreate)
txScript := script.Bytes()
txDeploy := transaction.New(testchain.Network(), txScript, 100*native.GASFactor)
txDeploy.Nonce = getNextNonce() txDeploy.Nonce = getNextNonce()
txDeploy.ValidUntilBlock = validUntilBlock txDeploy.ValidUntilBlock = validUntilBlock
txDeploy.Signers = []transaction.Signer{{Account: priv0ScriptHash}} txDeploy.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
@ -255,7 +238,7 @@ func TestCreateBasicChain(t *testing.T) {
t.Logf("Block2 hash: %s", b.Hash().StringLE()) t.Logf("Block2 hash: %s", b.Hash().StringLE())
// Now invoke this contract. // Now invoke this contract.
script = io.NewBufBinWriter() script := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "putValue", "testkey", "testvalue") emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "putValue", "testkey", "testvalue")
txInv := transaction.New(testchain.Network(), script.Bytes(), 1*native.GASFactor) txInv := transaction.New(testchain.Network(), script.Bytes(), 1*native.GASFactor)
@ -330,6 +313,16 @@ func TestCreateBasicChain(t *testing.T) {
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE()) t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE())
// Push verification contract into the chain.
txDeploy2, _ := newDeployTx(t, prefix+"verification_contract.go")
txDeploy2.Nonce = getNextNonce()
txDeploy2.ValidUntilBlock = validUntilBlock
txDeploy2.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, txDeploy2, acc0))
require.NoError(t, acc0.SignTx(txDeploy2))
b = bc.newBlock(txDeploy2)
require.NoError(t, bc.AddBlock(b))
if saveChain { if saveChain {
outStream, err := os.Create(prefix + "testblocks.acc") outStream, err := os.Create(prefix + "testblocks.acc")
require.NoError(t, err) require.NoError(t, err)
@ -378,6 +371,27 @@ func newNEP5Transfer(sc, from, to util.Uint160, amount int64) *transaction.Trans
return transaction.New(testchain.Network(), script, 10000000) return transaction.New(testchain.Network(), script, 10000000)
} }
func newDeployTx(t *testing.T, name string) (*transaction.Transaction, []byte) {
c, err := ioutil.ReadFile(name)
require.NoError(t, err)
avm, di, err := compiler.CompileWithDebugInfo(name, bytes.NewReader(c))
require.NoError(t, err)
t.Logf("contractHash (%s): %s", name, hash.Hash160(avm).StringLE())
t.Logf("contractScript: %x", avm)
script := io.NewBufBinWriter()
m, err := di.ConvertToManifest(smartcontract.HasStorage, nil)
require.NoError(t, err)
bs, err := m.MarshalJSON()
require.NoError(t, err)
emit.Bytes(script.BinWriter, bs)
emit.Bytes(script.BinWriter, avm)
emit.Syscall(script.BinWriter, interopnames.SystemContractCreate)
txScript := script.Bytes()
return transaction.New(testchain.Network(), txScript, 100*native.GASFactor), avm
}
func addSigners(txs ...*transaction.Transaction) { func addSigners(txs ...*transaction.Transaction) {
for _, tx := range txs { for _, tx := range txs {
tx.Signers = []transaction.Signer{{ tx.Signers = []transaction.Signer{{

View file

@ -9,6 +9,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
) )
@ -20,6 +21,9 @@ func CheckHashedWitness(ic *interop.Context, hash util.Uint160) (bool, error) {
return checkScope(ic.DAO, tx, ic.VM, hash) return checkScope(ic.DAO, tx, ic.VM, hash)
} }
if !ic.VM.Context().GetCallFlags().Has(smartcontract.AllowStates) {
return false, errors.New("missing AllowStates call flag")
}
return false, errors.New("script container is not a transaction") return false, errors.New("script container is not a transaction")
} }
@ -49,6 +53,9 @@ func checkScope(d dao.DAO, tx *transaction.Transaction, v *vm.VM, hash util.Uint
if callingScriptHash.Equals(util.Uint160{}) { if callingScriptHash.Equals(util.Uint160{}) {
return false, nil return false, nil
} }
if !v.Context().GetCallFlags().Has(smartcontract.AllowStates) {
return false, errors.New("missing AllowStates call flag")
}
cs, err := d.GetContractState(callingScriptHash) cs, err := d.GetContractState(callingScriptHash)
if err != nil { if err != nil {
return false, err return false, err

View file

@ -77,7 +77,7 @@ var systemInterops = []interop.Function{
{Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 500000, ParamCount: 1}, {Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 500000, ParamCount: 1},
{Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 100000, ParamCount: 1}, {Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 100000, ParamCount: 1},
{Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 30000, {Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 30000,
RequiredFlags: smartcontract.AllowStates, ParamCount: 1}, RequiredFlags: smartcontract.NoneFlag, ParamCount: 1},
{Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 400}, {Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 400},
{Name: interopnames.SystemRuntimeGetCallingScriptHash, Func: engineGetCallingScriptHash, Price: 400}, {Name: interopnames.SystemRuntimeGetCallingScriptHash, Func: engineGetCallingScriptHash, Price: 400},
{Name: interopnames.SystemRuntimeGetEntryScriptHash, Func: engineGetEntryScriptHash, Price: 400}, {Name: interopnames.SystemRuntimeGetEntryScriptHash, Func: engineGetEntryScriptHash, Price: 400},

View file

@ -13,6 +13,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
) )
@ -499,16 +500,19 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
} }
size := io.GetVarSize(tx) size := io.GetVarSize(tx)
for i, cosigner := range tx.Signers { for i, cosigner := range tx.Signers {
if accs[i].Contract.Script == nil { if accs[i].Contract.Deployed {
contract, err := c.GetContractState(cosigner.Account) res, err := c.InvokeFunction(cosigner.Account, manifest.MethodVerify, []smartcontract.Parameter{}, tx.Signers)
if err == nil { if err == nil && res.State == "HALT" && len(res.Stack) == 1 {
if contract == nil { r, err := topIntFromStack(res.Stack)
continue if err != nil || r == 0 {
return core.ErrVerificationFailed
} }
netFee, sizeDelta := core.CalculateNetworkFee(contract.Script) } else {
tx.NetworkFee += netFee return core.ErrVerificationFailed
size += sizeDelta
} }
tx.NetworkFee += res.GasConsumed
size += io.GetVarSize([]byte{}) * 2 // both scripts are empty
continue
} }
netFee, sizeDelta := core.CalculateNetworkFee(accs[i].Contract.Script) netFee, sizeDelta := core.CalculateNetworkFee(accs[i].Contract.Script)
tx.NetworkFee += netFee tx.NetworkFee += netFee

View file

@ -2,6 +2,7 @@ package server
import ( import (
"context" "context"
"encoding/hex"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
@ -132,6 +133,65 @@ func TestAddNetworkFee(t *testing.T) {
cFeeM, _ := core.CalculateNetworkFee(accs[1].Contract.Script) cFeeM, _ := core.CalculateNetworkFee(accs[1].Contract.Script)
require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+cFeeM+10, tx.NetworkFee) require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+cFeeM+10, tx.NetworkFee)
}) })
t.Run("Contract", func(t *testing.T) {
tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0)
priv := testchain.PrivateKeyByID(0)
acc1, err := wallet.NewAccountFromWIF(priv.WIF())
require.NoError(t, err)
acc1.Contract.Deployed = true
acc1.Contract.Script, _ = hex.DecodeString(verifyContractAVM)
h, _ := util.Uint160DecodeStringLE(verifyContractHash)
tx.ValidUntilBlock = chain.BlockHeight() + 10
t.Run("Valid", func(t *testing.T) {
acc0, err := wallet.NewAccountFromWIF(priv.WIF())
require.NoError(t, err)
tx.Signers = []transaction.Signer{
{
Account: acc0.PrivateKey().GetScriptHash(),
Scopes: transaction.CalledByEntry,
},
{
Account: h,
Scopes: transaction.Global,
},
}
require.NoError(t, c.AddNetworkFee(tx, 10, acc0, acc1))
require.NoError(t, acc0.SignTx(tx))
tx.Scripts = append(tx.Scripts, transaction.Witness{})
require.NoError(t, chain.VerifyTx(tx))
})
t.Run("Invalid", func(t *testing.T) {
acc0, err := wallet.NewAccount()
require.NoError(t, err)
tx.Signers = []transaction.Signer{
{
Account: acc0.PrivateKey().GetScriptHash(),
Scopes: transaction.CalledByEntry,
},
{
Account: h,
Scopes: transaction.Global,
},
}
require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1))
})
t.Run("InvalidContract", func(t *testing.T) {
acc0, err := wallet.NewAccountFromWIF(priv.WIF())
require.NoError(t, err)
tx.Signers = []transaction.Signer{
{
Account: acc0.PrivateKey().GetScriptHash(),
Scopes: transaction.CalledByEntry,
},
{
Account: util.Uint160{},
Scopes: transaction.Global,
},
}
require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1))
})
})
} }
func TestSignAndPushInvocationTx(t *testing.T) { func TestSignAndPushInvocationTx(t *testing.T) {

View file

@ -51,8 +51,11 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{}) check func(t *testing.T, e *executor, result interface{})
} }
const testContractHash = "93c4983afe01a75f74c1e56011bd630e9d8cc755" const testContractHash = "4546ec6fcdaa1c3ccdb048526b78624b457b60a4"
const deploymentTxHash = "583cf0e49d69d8854869efc3e97ad741061da478292a7280580789351a39a1ac" const deploymentTxHash = "17be1bbb0fdecae18cd4c6a2db19311f47bd540371e2ea479a46b349a66aa0b3"
const verifyContractHash = "47ef649f9a77cad161ddaa28b39c7e450e5429e7"
const verifyContractAVM = "560340570300412d510830db4121700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740"
var rpcTestCases = map[string][]rpcTestCase{ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": { "getapplicationlog": {
@ -438,7 +441,7 @@ var rpcTestCases = map[string][]rpcTestCase{
require.True(t, ok) require.True(t, ok)
expected := result.UnclaimedGas{ expected := result.UnclaimedGas{
Address: testchain.MultisigScriptHash(), Address: testchain.MultisigScriptHash(),
Unclaimed: *big.NewInt(36000), Unclaimed: *big.NewInt(42000),
} }
assert.Equal(t, expected, *actual) assert.Equal(t, expected, *actual)
}, },
@ -814,7 +817,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
require.NoErrorf(t, err, "could not parse response: %s", txOut) require.NoErrorf(t, err, "could not parse response: %s", txOut)
assert.Equal(t, *block.Transactions[0], actual.Transaction) assert.Equal(t, *block.Transactions[0], actual.Transaction)
assert.Equal(t, 8, actual.Confirmations) assert.Equal(t, 9, actual.Confirmations)
assert.Equal(t, TXHash, actual.Transaction.Hash()) assert.Equal(t, TXHash, actual.Transaction.Hash())
}) })
@ -999,8 +1002,8 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) {
}, },
{ {
Asset: e.chain.UtilityTokenHash(), Asset: e.chain.UtilityTokenHash(),
Amount: "915.61059740", Amount: "815.59478530",
LastUpdated: 6, LastUpdated: 7,
}}, }},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
} }

Binary file not shown.

View file

@ -0,0 +1,15 @@
package testdata
import (
"github.com/nspcc-dev/neo-go/pkg/interop/convert"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/util"
)
// Verify is a verification contract method.
// It returns true iff it is signed by NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc (id-0 private key from testchain).
func Verify() bool {
tx := runtime.GetScriptContainer()
addr := util.FromAddress("NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc")
return util.Equals(convert.ToByteArray(tx.Sender), convert.ToByteArray(addr))
}