From 1261dd53066d94384ce5f9a8d022fb6f7fdc7557 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 11 Mar 2021 20:15:23 +0300 Subject: [PATCH 1/2] core: refactor initVerificationVM It needs only VM and function to get contract state. Also exposed this method and extended Blockchainer. These changes are needed for the next commit. --- internal/fakechain/fakechain.go | 5 +++++ pkg/core/blockchain.go | 9 ++++----- pkg/core/blockchainer/blockchainer.go | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/internal/fakechain/fakechain.go b/internal/fakechain/fakechain.go index f90ce2bde..9fed4b11b 100644 --- a/internal/fakechain/fakechain.go +++ b/internal/fakechain/fakechain.go @@ -80,6 +80,11 @@ func (chain *FakeChain) IsTxStillRelevant(t *transaction.Transaction, txpool *me panic("TODO") } +// InitVerificationVM initializes VM for witness check. +func (chain *FakeChain) InitVerificationVM(v *vm.VM, getContract func(util.Uint160) (*state.Contract, error), hash util.Uint160, witness *transaction.Witness) error { + panic("TODO") +} + // IsExtensibleAllowed implements Blockchainer interface. func (*FakeChain) IsExtensibleAllowed(uint160 util.Uint160) bool { return true diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 5af463146..6849e799a 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1655,9 +1655,8 @@ var ( ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method") ) -// initVerificationVM initializes VM for witness check. -func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, witness *transaction.Witness) error { - v := ic.VM +// InitVerificationVM initializes VM for witness check. +func (bc *Blockchain) InitVerificationVM(v *vm.VM, getContract func(util.Uint160) (*state.Contract, error), hash util.Uint160, witness *transaction.Witness) error { if len(witness.VerificationScript) != 0 { if witness.ScriptHash() != hash { return ErrWitnessHashMismatch @@ -1671,7 +1670,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, } v.LoadScriptWithFlags(witness.VerificationScript, callflag.ReadOnly) } else { - cs, err := ic.GetContract(hash) + cs, err := getContract(hash) if err != nil { return ErrUnknownVerificationContract } @@ -1717,7 +1716,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa vm.SetPriceGetter(interopCtx.GetPrice) vm.LoadToken = contract.LoadToken(interopCtx) vm.GasLimit = gas - if err := bc.initVerificationVM(interopCtx, hash, witness); err != nil { + if err := bc.InitVerificationVM(vm, interopCtx.GetContract, hash, witness); err != nil { return 0, err } err := vm.Run() diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index bdfdc06d8..c9936e9d0 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -25,6 +25,7 @@ type Blockchainer interface { AddBlock(*block.Block) error CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error) Close() + InitVerificationVM(v *vm.VM, getContract func(util.Uint160) (*state.Contract, error), hash util.Uint160, witness *transaction.Witness) error IsTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool, isPartialTx bool) bool HeaderHeight() uint32 GetBlock(hash util.Uint256) (*block.Block, error) From edfca68a1747c644420b1df74daf0fe005cb0784 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 10 Mar 2021 17:43:52 +0300 Subject: [PATCH 2/2] rpc: refactor invokecontractverify In `(c *Client) AddNetworkFee` we define network fee for contract witness verification via `invokecontractverify` RPC call, and that's the initial purpose of this RPC method. But it was not implemented correctly. It used `System.Contract.Call` instead of beheiving like `initVerificationVM`. During real contract witness verification the whole contract's script is loaded into VM, and then we jump to the `verify` method. Thus, to define exact contract verification price, we should act like this (and not just perform `System.Contract.Call` of `verify` method). Tests are added. This bug is the reason of adding extra GAS (c.notary.extraVerifyFee) to pre-calculated value in https://github.com/nspcc-dev/neofs-node/pull/404/files#diff-639db437ca2578db46c9e8cbf18f9aa01f8ca5aee30e0fa7e70ba0354822d7b3R237 --- pkg/core/helper_test.go | 9 ++ pkg/rpc/request/txBuilder.go | 8 +- pkg/rpc/request/tx_builder_test.go | 4 +- pkg/rpc/server/client_test.go | 78 +++++++++----- pkg/rpc/server/server.go | 75 +++++++++----- pkg/rpc/server/server_test.go | 97 +++++++++++++++--- pkg/rpc/server/testdata/testblocks.acc | Bin 9788 -> 10913 bytes .../verification_with_args_contract.go | 7 ++ 8 files changed, 207 insertions(+), 71 deletions(-) create mode 100644 pkg/rpc/server/testdata/verification_with_args_contract.go diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 7ebfc6b88..661532dde 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -451,6 +451,15 @@ func initBasicChain(t *testing.T, bc *Blockchain) { bc.setNodesByRole(t, true, native.RoleP2PNotary, keys.PublicKeys{ntr.Accounts[0].PrivateKey().PublicKey()}) t.Logf("Designated Notary node: %s", hex.EncodeToString(ntr.Accounts[0].PrivateKey().PublicKey().Bytes())) + // Push verification contract with arguments into the chain. + txDeploy3, _ := newDeployTx(t, bc, priv0ScriptHash, prefix+"verification_with_args_contract.go", "VerifyWithArgs") + txDeploy3.Nonce = getNextNonce() + txDeploy3.ValidUntilBlock = validUntilBlock + require.NoError(t, addNetworkFee(bc, txDeploy3, acc0)) + require.NoError(t, acc0.SignTx(txDeploy3)) + b = bc.newBlock(txDeploy3) + require.NoError(t, bc.AddBlock(b)) + // Compile contract to test `invokescript` RPC call _, _ = newDeployTx(t, bc, priv0ScriptHash, prefix+"invokescript_contract.go", "ContractForInvokescriptTest") } diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index e43e8b1bd..2edb4ad1f 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -14,9 +14,9 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) -// expandArrayIntoScript pushes all FuncParam parameters from the given array +// ExpandArrayIntoScript pushes all FuncParam parameters from the given array // into the given buffer in reverse order. -func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { +func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error { for j := len(slice) - 1; j >= 0; j-- { fp, err := slice[j].GetFuncParam() if err != nil { @@ -87,7 +87,7 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { if err != nil { return err } - err = expandArrayIntoScript(script, val) + err = ExpandArrayIntoScript(script, val) if err != nil { return err } @@ -119,7 +119,7 @@ func CreateFunctionInvocationScript(contract util.Uint160, method string, params if err != nil { return nil, err } - err = expandArrayIntoScript(script.BinWriter, slice) + err = ExpandArrayIntoScript(script.BinWriter, slice) if err != nil { return nil, err } diff --git a/pkg/rpc/request/tx_builder_test.go b/pkg/rpc/request/tx_builder_test.go index af0fcbaa7..b79b9d62e 100644 --- a/pkg/rpc/request/tx_builder_test.go +++ b/pkg/rpc/request/tx_builder_test.go @@ -105,7 +105,7 @@ func TestExpandArrayIntoScript(t *testing.T) { } for _, c := range testCases { script := io.NewBufBinWriter() - err := expandArrayIntoScript(script.BinWriter, c.Input) + err := ExpandArrayIntoScript(script.BinWriter, c.Input) require.NoError(t, err) require.Equal(t, c.Expected, script.Bytes()) } @@ -119,7 +119,7 @@ func TestExpandArrayIntoScript(t *testing.T) { } for _, c := range errorCases { script := io.NewBufBinWriter() - err := expandArrayIntoScript(script.BinWriter, c) + err := ExpandArrayIntoScript(script.BinWriter, c) require.Error(t, err) } } diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index 87932f0f6..c7c459a9e 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -71,6 +71,7 @@ func TestAddNetworkFee(t *testing.T) { chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) defer chain.Close() defer rpcSrv.Shutdown() + const extraFee = 10 c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) require.NoError(t, err) @@ -95,7 +96,7 @@ func TestAddNetworkFee(t *testing.T) { Account: accs[0].PrivateKey().GetScriptHash(), Scopes: transaction.CalledByEntry, }} - require.Error(t, c.AddNetworkFee(tx, 10, accs[0], accs[1])) + require.Error(t, c.AddNetworkFee(tx, extraFee, accs[0], accs[1])) }) t.Run("Simple", func(t *testing.T) { tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0) @@ -107,7 +108,7 @@ func TestAddNetworkFee(t *testing.T) { require.NoError(t, c.AddNetworkFee(tx, 10, accs[0])) require.NoError(t, accs[0].SignTx(tx)) cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) - require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+10, tx.NetworkFee) + require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+extraFee, tx.NetworkFee) }) t.Run("Multi", func(t *testing.T) { @@ -126,43 +127,69 @@ func TestAddNetworkFee(t *testing.T) { Scopes: transaction.Global, }, } - require.NoError(t, c.AddNetworkFee(tx, 10, accs[0], accs[1])) + require.NoError(t, c.AddNetworkFee(tx, extraFee, accs[0], accs[1])) require.NoError(t, accs[0].SignTx(tx)) require.NoError(t, accs[1].SignTx(tx)) require.NoError(t, accs[2].SignTx(tx)) cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) cFeeM, _ := fee.Calculate(chain.GetBaseExecFee(), 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+extraFee, tx.NetworkFee) }) t.Run("Contract", func(t *testing.T) { - tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0) - priv := testchain.PrivateKeyByID(0) - acc1 := wallet.NewAccountFromPrivateKey(priv) - acc1.Contract.Deployed = true - acc1.Contract.Script, err = base64.StdEncoding.DecodeString(verifyContractAVM) - require.NoError(t, err) h, err := util.Uint160DecodeStringLE(verifyContractHash) require.NoError(t, err) - tx.ValidUntilBlock = chain.BlockHeight() + 10 + priv := testchain.PrivateKeyByID(0) + acc0 := wallet.NewAccountFromPrivateKey(priv) + acc1 := wallet.NewAccountFromPrivateKey(priv) // contract account + acc1.Contract.Deployed = true + acc1.Contract.Script, err = base64.StdEncoding.DecodeString(verifyContractAVM) + + newTx := func(t *testing.T) *transaction.Transaction { + tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0) + require.NoError(t, err) + tx.ValidUntilBlock = chain.BlockHeight() + 10 + return tx + } t.Run("Valid", func(t *testing.T) { - acc0 := wallet.NewAccountFromPrivateKey(priv) - tx.Signers = []transaction.Signer{ - { - Account: acc0.PrivateKey().GetScriptHash(), - Scopes: transaction.CalledByEntry, - }, - { - Account: h, - Scopes: transaction.Global, - }, + completeTx := func(t *testing.T) *transaction.Transaction { + tx := newTx(t) + tx.Signers = []transaction.Signer{ + { + Account: acc0.PrivateKey().GetScriptHash(), + Scopes: transaction.CalledByEntry, + }, + { + Account: h, + Scopes: transaction.Global, + }, + } + require.NoError(t, c.AddNetworkFee(tx, extraFee, acc0, acc1)) + return tx } - 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)) + + // check that network fee with extra value is enough + tx1 := completeTx(t) + require.NoError(t, acc0.SignTx(tx1)) + tx1.Scripts = append(tx1.Scripts, transaction.Witness{}) + require.NoError(t, chain.VerifyTx(tx1)) + + // check that network fee without extra value is enough + tx2 := completeTx(t) + tx2.NetworkFee -= extraFee + require.NoError(t, acc0.SignTx(tx2)) + tx2.Scripts = append(tx2.Scripts, transaction.Witness{}) + require.NoError(t, chain.VerifyTx(tx2)) + + // check that we don't add unexpected extra GAS + tx3 := completeTx(t) + tx3.NetworkFee -= extraFee + 1 + require.NoError(t, acc0.SignTx(tx3)) + tx3.Scripts = append(tx3.Scripts, transaction.Witness{}) + require.Error(t, chain.VerifyTx(tx3)) }) t.Run("Invalid", func(t *testing.T) { + tx := newTx(t) acc0, err := wallet.NewAccount() require.NoError(t, err) tx.Signers = []transaction.Signer{ @@ -178,6 +205,7 @@ func TestAddNetworkFee(t *testing.T) { require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1)) }) t.Run("InvalidContract", func(t *testing.T) { + tx := newTx(t) acc0 := wallet.NewAccountFromPrivateKey(priv) tx.Signers = []transaction.Signer{ { diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index b193746f4..fc299e9ef 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -36,9 +36,9 @@ import ( "github.com/nspcc-dev/neo-go/pkg/services/oracle" "github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "go.uber.org/zap" ) @@ -1118,7 +1118,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons return nil, response.NewInternalServerError("can't create invocation script", err) } tx.Script = script - return s.runScriptInVM(trigger.Application, script, tx) + return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx) } // invokescript implements the `invokescript` RPC call. @@ -1144,7 +1144,7 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response. tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}} } tx.Script = script - return s.runScriptInVM(trigger.Application, script, tx) + return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx) } // invokeContractVerify implements the `invokecontractverify` RPC call. @@ -1154,36 +1154,43 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r return nil, responseErr } - args := reqParams[1:2] - var tx *transaction.Transaction + bw := io.NewBufBinWriter() + if len(reqParams) > 1 { + args, err := reqParams[1].GetArray() // second `invokecontractverify` parameter is an array of arguments for `verify` method + if err != nil { + return nil, response.WrapErrorWithData(response.ErrInvalidParams, err) + } + if len(args) > 0 { + err := request.ExpandArrayIntoScript(bw.BinWriter, args) + if err != nil { + return nil, response.NewRPCError("can't create witness invocation script", err.Error(), err) + } + } + } + invocationScript := bw.Bytes() + + tx := &transaction.Transaction{Script: []byte{byte(opcode.RET)}} // need something in script if len(reqParams) > 2 { signers, witnesses, err := reqParams[2].GetSignersWithWitnesses() if err != nil { return nil, response.ErrInvalidParams } - tx = &transaction.Transaction{ - Signers: signers, - Scripts: witnesses, - } + tx.Signers = signers + tx.Scripts = witnesses + } else { // fill the only known signer - the contract with `verify` method + tx.Signers = []transaction.Signer{{Account: scriptHash}} + tx.Scripts = []transaction.Witness{{InvocationScript: invocationScript, VerificationScript: []byte{}}} } - cs := s.chain.GetContractState(scriptHash) - if cs == nil { - return nil, response.NewRPCError("unknown contract", scriptHash.StringBE(), nil) - } - script, err := request.CreateFunctionInvocationScript(cs.Hash, manifest.MethodVerify, args) - if err != nil { - return nil, response.NewInternalServerError("can't create invocation script", err) - } - if tx != nil { - tx.Script = script - } - return s.runScriptInVM(trigger.Verification, script, tx) + return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx) } // runScriptInVM runs given script in a new test VM and returns the invocation -// result. -func (s *Server) runScriptInVM(t trigger.Type, script []byte, tx *transaction.Transaction) (*result.Invoke, *response.Error) { +// result. The script is either a simple script in case of `application` trigger +// witness invocation script in case of `verification` trigger (it pushes `verify` +// arguments on stack before verification). In case of contract verification +// contractScriptHash should be specified. +func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction) (*result.Invoke, *response.Error) { // When transferring funds, script execution does no auto GAS claim, // because it depends on persisting tx height. // This is why we provide block here. @@ -1197,7 +1204,27 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, tx *transaction.Tr vm := s.chain.GetTestVM(t, tx, b) vm.GasLimit = int64(s.config.MaxGasInvoke) - vm.LoadScriptWithFlags(script, callflag.All) + if t == trigger.Verification { + // We need this special case because witnesses verification is not the simple System.Contract.Call, + // and we need to define exactly the amount of gas consumed for a contract witness verification. + gasPolicy := s.chain.GetPolicer().GetMaxVerificationGAS() + if vm.GasLimit > gasPolicy { + vm.GasLimit = gasPolicy + } + + err := s.chain.InitVerificationVM(vm, func(h util.Uint160) (*state.Contract, error) { + res := s.chain.GetContractState(h) + if res == nil { + return nil, fmt.Errorf("unknown contract: %s", h.StringBE()) + } + return res, nil + }, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}}) + if err != nil { + return nil, response.NewInternalServerError("can't prepare verification VM", err) + } + } else { + vm.LoadScriptWithFlags(script, callflag.All) + } err = vm.Run() var faultException string if err != nil { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index baebd390e..2ffea2f74 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -35,6 +35,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/assert" @@ -60,11 +61,13 @@ type rpcTestCase struct { } const testContractHash = "1e1c3024bd955ff3baf7cb92e3b7608c7bb3712b" -const deploymentTxHash = "b67eb38f1e6805d60b68cbec9cf8db7e4a71313b6f53ff8545c578b51ce874c5" +const deploymentTxHash = "298092d1619585b2fcd3045c8e5a749ddbe14a6fe41569a69b50f47b812112d9" const genesisBlockHash = "5b60644c6c6f58faca72c70689d7ed1f40c2e795772bd0de5a88e983ad55080c" const verifyContractHash = "5bb4bac40e961e334ba7bd36d2496010f67e246e" const verifyContractAVM = "VwMAQS1RCDAhcAwUVVQtU+0PVUb61E1umZEoZwIvzl7bMHFoE87bKGnbKJdA" +const verifyWithArgsContractHash = "59b08e81dcf94f6ddbef5c2d84a4c1a098b9a984" +const verifyWithArgsContractAVM = "VwIDeAwLZ29vZF9zdHJpbmeXJA15FSgJehHbIJciBRHbIHBoQA==" const invokescriptContractAVM = "VwcADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGhB+CfsjCGqJgQRQAwUDQ8DAgkAAgEDBwMEBQIBAA4GDAnbMHFpQfgn7IwhqiYEEkATQA==" var rpcTestCases = map[string][]rpcTestCase{ @@ -651,7 +654,7 @@ var rpcTestCases = map[string][]rpcTestCase{ require.True(t, ok) expected := result.UnclaimedGas{ Address: testchain.MultisigScriptHash(), - Unclaimed: *big.NewInt(4500), + Unclaimed: *big.NewInt(5000), } assert.Equal(t, expected, *actual) }, @@ -814,7 +817,7 @@ var rpcTestCases = map[string][]rpcTestCase{ check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) - assert.NotNil(t, res.Script) + assert.Nil(t, res.Script) // empty witness invocation script (pushes args of `verify` on stack, but this `verify` don't have args) assert.Equal(t, "HALT", res.State) assert.NotEqual(t, 0, res.GasConsumed) assert.Equal(t, true, res.Stack[0].Value().(bool), fmt.Sprintf("check address in verification_contract.go: expected %s", testchain.PrivateKeyByID(0).Address())) @@ -827,25 +830,74 @@ var rpcTestCases = map[string][]rpcTestCase{ check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) - assert.NotNil(t, res.Script) + assert.Nil(t, res.Script) assert.Equal(t, "HALT", res.State, res.FaultException) assert.NotEqual(t, 0, res.GasConsumed) assert.Equal(t, false, res.Stack[0].Value().(bool)) }, }, { - name: "positive, with scripts", + name: "positive, no arguments", + params: fmt.Sprintf(`["%s"]`, verifyContractHash), + result: func(e *executor) interface{} { return &result.Invoke{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*result.Invoke) + require.True(t, ok) + assert.Nil(t, res.Script) + assert.Equal(t, "HALT", res.State, res.FaultException) + assert.NotEqual(t, 0, res.GasConsumed) + assert.Equal(t, false, res.Stack[0].Value().(bool)) + }, + }, + { + name: "positive, with signers and scripts", params: fmt.Sprintf(`["%s", [], [{"account":"%s", "invocation":"MQo=", "verification": ""}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()), result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) - assert.NotNil(t, res.Script) + assert.Nil(t, res.Script) assert.Equal(t, "HALT", res.State) assert.NotEqual(t, 0, res.GasConsumed) assert.Equal(t, true, res.Stack[0].Value().(bool)) }, }, + { + name: "positive, with arguments, result=true", + params: fmt.Sprintf(`["%s", [{"type": "String", "value": "good_string"}, {"type": "Integer", "value": "4"}, {"type":"Boolean", "value": "false"}]]`, verifyWithArgsContractHash), + result: func(e *executor) interface{} { return &result.Invoke{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*result.Invoke) + require.True(t, ok) + expectedInvScript := io.NewBufBinWriter() + emit.Int(expectedInvScript.BinWriter, 0) + emit.Int(expectedInvScript.BinWriter, int64(4)) + emit.String(expectedInvScript.BinWriter, "good_string") + require.NoError(t, expectedInvScript.Err) + assert.Equal(t, expectedInvScript.Bytes(), res.Script) // witness invocation script (pushes args of `verify` on stack) + assert.Equal(t, "HALT", res.State, res.FaultException) + assert.NotEqual(t, 0, res.GasConsumed) + assert.Equal(t, true, res.Stack[0].Value().(bool)) + }, + }, + { + name: "positive, with arguments, result=false", + params: fmt.Sprintf(`["%s", [{"type": "String", "value": "invalid_string"}, {"type": "Integer", "value": "4"}, {"type":"Boolean", "value": "false"}]]`, verifyWithArgsContractHash), + result: func(e *executor) interface{} { return &result.Invoke{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*result.Invoke) + require.True(t, ok) + expectedInvScript := io.NewBufBinWriter() + emit.Int(expectedInvScript.BinWriter, 0) + emit.Int(expectedInvScript.BinWriter, int64(4)) + emit.String(expectedInvScript.BinWriter, "invalid_string") + require.NoError(t, expectedInvScript.Err) + assert.Equal(t, expectedInvScript.Bytes(), res.Script) + assert.Equal(t, "HALT", res.State, res.FaultException) + assert.NotEqual(t, 0, res.GasConsumed) + assert.Equal(t, false, res.Stack[0].Value().(bool)) + }, + }, { name: "unknown contract", params: fmt.Sprintf(`["%s", []]`, util.Uint160{}.String()), @@ -1369,7 +1421,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoErrorf(t, err, "could not parse response: %s", txOut) assert.Equal(t, *block.Transactions[0], actual.Transaction) - assert.Equal(t, 10, actual.Confirmations) + assert.Equal(t, 11, actual.Confirmations) assert.Equal(t, TXHash, actual.Transaction.Hash()) }) @@ -1487,12 +1539,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoError(t, json.Unmarshal(res, actual)) checkNep17TransfersAux(t, e, actual, sent, rcvd) } - t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{5, 6, 7, 8}, []int{1, 2}) }) + t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{6, 7, 8, 9}, []int{1, 2}) }) t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) }) - t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{2, 3}, []int{0}) }) - t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{5}, []int{1}) }) - t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{4, 5}, []int{1}) }) - t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{6, 7}, []int{2}) }) + t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{3, 4}, []int{0}) }) + t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{6}, []int{1}) }) + t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{5, 6}, []int{1}) }) + t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{7, 8}, []int{2}) }) }) } @@ -1597,8 +1649,8 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "78994302340", - LastUpdated: 8, + Amount: "68992647820", + LastUpdated: 10, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), } @@ -1607,7 +1659,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { } func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) { - checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []int{0, 1, 2, 3, 4, 5, 6}) + checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []int{0, 1, 2, 3, 4, 5, 6}) } func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) { @@ -1616,12 +1668,17 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc rublesHash, err := util.Uint160DecodeStringLE(testContractHash) require.NoError(t, err) + blockDeploy3, err := e.chain.GetBlock(e.chain.GetHeaderHash(10)) // deploy verification_with_args_contract.go + require.NoError(t, err) + require.Equal(t, 1, len(blockDeploy3.Transactions)) + txDeploy3 := blockDeploy3.Transactions[0] + blockDepositGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(8)) require.NoError(t, err) require.Equal(t, 1, len(blockDepositGAS.Transactions)) txDepositGAS := blockDepositGAS.Transactions[0] - blockDeploy2, err := e.chain.GetBlock(e.chain.GetHeaderHash(7)) + blockDeploy2, err := e.chain.GetBlock(e.chain.GetHeaderHash(7)) // deploy verification_contract.go require.NoError(t, err) require.Equal(t, 1, len(blockDeploy2.Transactions)) txDeploy2 := blockDeploy2.Transactions[0] @@ -1669,6 +1726,14 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc // duplicate the Server method. expected := result.NEP17Transfers{ Sent: []result.NEP17Transfer{ + { + Timestamp: blockDeploy3.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", // burn + Amount: big.NewInt(txDeploy3.SystemFee + txDeploy3.NetworkFee).String(), + Index: 10, + TxHash: blockDeploy3.Hash(), + }, { Timestamp: blockDepositGAS.Timestamp, Asset: e.chain.UtilityTokenHash(), diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 618d990545d777a0fdd45a95389736566ee6dd02..e787ddaee364c587316e02b86bb20f7687bf9893 100644 GIT binary patch delta 3530 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 delta 2983 zcmc)L2|JW+9|rIlvWyu!PqrDuo52*3tt`34WXdu~gpxgb$o6Bjzwju|!E#MJAng8@euXd8+FOwk|B?u22eF{Q?Q4B)53VNyjAB0v%8Dq9Odw~n(t($uK z>*da<+ShdP4D~H(8Mj|}T>c2fP2(UYnj>TdXAK)Eq5xFEJ;kqTkY2 zMx%=~6eRA8tu#&Xaf^mBk4J#Ab z)6(75FiI5j;jf=@^}_B>o$x5YLChklx<7O>lG*Pz*5%Alp~#$gvJawJbtal znVZMs$8Vf8(3PVCoaYP$Yhjnc0xnZ=4N7wc>E5S+la@#2>r$E`To$qsF>*vpUcnh6%W9o~%Dw;RhWD04u zRFxmdn-~v)4ubOL*({L5C!q#*o)qpY{JD&*pr?3eT|1dKmFOX-+)dSJiD{D@8A>=K z1WGrJLx@5W1@O6rRTUboYb(ihJc@P1xNC6S?oS)6hi$-}GQxCiZ78bvVh`M-COENI z_Kb_8(4K1iTF3e3w991?&8;FUlNi35&9Z7*%uIWOTZGH-JQ- z9;stZuoPdgwRK)o73ml-EI&rV^HAod3siD(P zcuL!E-y@-L=u7@NqY14650%hhl^_~+?GFIkAwc|e&~cq^dKA4*@ro$~nIt^B{?9XO zImVVB^I2b6qH5%#o|>f=wZ6SvMRvwBrd1}hv)`HGvuc{D#m1kYLPr2URo;zyFlQqz z+Z2eb(iOErTGBHdul;{YUvzJV6Lx&Qp1}5OQyqt(8 zLj2l3jaH!YQ8di{j{y6jKw|yPaCjy@r*HW#yjAHgK>!&8SwXeD!S1ZSUkBB)6=mG$u>uRo zdRDJdVzblM%$rBaoj(UIACH-w4;bAkzkQ&zeOi_8`7NGYiWidym)yaL0HkelPSBVT zPNIymKd%nw)w+Ko+VMt7N38mMB;Va}9t(g#UR5>@O_sSeO0tvSa%#`48oo7>CccZ7 z24ezQ6%HrRE3%LFL%Cenx>BFiNu7J^JhtYf$UHyR_?c)4WgR*3Z2 z9L%@?VUO^BsT8I%w0u zei1v<%BT9Vy`x~V=xy&BIz=c@S@D4WuvIQ*flm&cL86a1{1yP=C@GSQv={Pg0r5s! zdH;?xs=>#G`87Ve0*Wz8ED!g`xqfUQNm?;wcw?b)>sKjEER{dDPL<6yGh^70)v~pJJV6$| zKi8*cpCZduPD|v=E60&zln}syUH2MioC2}>Z)NOk^+#X5P~+S&ZULY00BwFf`5hJh z0ZtL@n|lh*A@W_$?&9IZJvG}M)D}DWn#8JYu#rm5GU^%h0_ij3<#ybYa~wS1aznl8 zx`v_WjU8H4gFXb`8Acnp$u(f&Jan<6jtzsPfhukXiSJ2mez}BJls@m$3_8tl#_`W8 zV27(s~y~BxK8DNaINg&AtGN`Yr1~Xdoga1UadQp zW3=RApRib~eY@t}vmkND8@y8#yQ}kRv={uiq2}L*M`XzJn|*tBQBBs8_h06_=9dYa zvM4EQ%=0o>Z^(|o=@&E9T;B)dsn&PIo7D5p5@{A}220;_3Tkyt$QGohkN#c7k-$iP z1JGGIn^dWeOz?0~a<>=>zK<{57*JIGWh-_NL?w2-StnN>yvL~bMZ$?tE!UOZ_;gM^ z))3Ns;as!E;zPO4yPMZ>k)yfi66}X2Kn>H&@graO7?^1n@L%-Rx*{QU#UO)2}II{6^mX z-G9Pm1~quA>_@fBM+4O;72cIQ_0G%G+@q%Yz$pT>+O}yHtyHTlr9HlLg&R*+X`}dFOFXfY|W~t%h odX4<~?@ASTT-9CCyamYTNC%!!0$c4|K-ecCu#epf{2=4=U!Dw)wEzGB diff --git a/pkg/rpc/server/testdata/verification_with_args_contract.go b/pkg/rpc/server/testdata/verification_with_args_contract.go new file mode 100644 index 000000000..bbd404166 --- /dev/null +++ b/pkg/rpc/server/testdata/verification_with_args_contract.go @@ -0,0 +1,7 @@ +package testdata + +// Verify is a verification contract method which takes several arguments. +func Verify(argString string, argInt int, argBool bool) bool { + isOK := argString == "good_string" || argInt == 5 || argBool == true + return isOK +}