forked from TrueCloudLab/neoneo-go
rpcsrv: use stricter GAS limit for calculatenetworkfee
Valid transactions can't use more than MaxVerificationGAS for script execution and this applies to the whole set of signers, so use this value by default unless local instance configuration suggests something lower for generic invocations. Signed-off-by: Roman Khimov <roman@nspcc.ru>
This commit is contained in:
parent
fd2774ea91
commit
49a44b0b9d
3 changed files with 45 additions and 3 deletions
|
@ -233,7 +233,9 @@ where:
|
||||||
proxy can be used to have proper app-specific CORS settings), but it's an
|
proxy can be used to have proper app-specific CORS settings), but it's an
|
||||||
easy way to make RPC interface accessible from the browser.
|
easy way to make RPC interface accessible from the browser.
|
||||||
- `MaxGasInvoke` is the maximum GAS allowed to spend during `invokefunction` and
|
- `MaxGasInvoke` is the maximum GAS allowed to spend during `invokefunction` and
|
||||||
`invokescript` RPC-calls.
|
`invokescript` RPC-calls. `calculatenetworkfee` also can't exceed this GAS amount
|
||||||
|
(normally the limit for it is MaxVerificationGAS from Policy, but if MaxGasInvoke
|
||||||
|
is lower than that then this limit is respected).
|
||||||
- `MaxIteratorResultItems` - maximum number of elements extracted from iterator
|
- `MaxIteratorResultItems` - maximum number of elements extracted from iterator
|
||||||
returned by `invoke*` call. When the `MaxIteratorResultItems` value is set to
|
returned by `invoke*` call. When the `MaxIteratorResultItems` value is set to
|
||||||
`n`, only `n` iterations are returned and truncated is true, indicating that
|
`n`, only `n` iterations are returned and truncated is true, indicating that
|
||||||
|
|
|
@ -917,7 +917,15 @@ func (s *Server) calculateNetworkFee(reqParams params.Params) (any, *neorpc.Erro
|
||||||
return 0, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("failed to compute tx size: %s", err))
|
return 0, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("failed to compute tx size: %s", err))
|
||||||
}
|
}
|
||||||
size := len(hashablePart) + io.GetVarSize(len(tx.Signers))
|
size := len(hashablePart) + io.GetVarSize(len(tx.Signers))
|
||||||
var netFee int64
|
var (
|
||||||
|
netFee int64
|
||||||
|
// Verification GAS cost can't exceed this policy.
|
||||||
|
gasLimit = s.chain.GetMaxVerificationGAS()
|
||||||
|
)
|
||||||
|
if gasLimit > int64(s.config.MaxGasInvoke) {
|
||||||
|
// But we honor instance configuration as well.
|
||||||
|
gasLimit = int64(s.config.MaxGasInvoke)
|
||||||
|
}
|
||||||
for i, signer := range tx.Signers {
|
for i, signer := range tx.Signers {
|
||||||
w := tx.Scripts[i]
|
w := tx.Scripts[i]
|
||||||
if len(w.InvocationScript) == 0 { // No invocation provided, try to infer one.
|
if len(w.InvocationScript) == 0 { // No invocation provided, try to infer one.
|
||||||
|
@ -951,10 +959,11 @@ func (s *Server) calculateNetworkFee(reqParams params.Params) (any, *neorpc.Erro
|
||||||
}
|
}
|
||||||
w.InvocationScript = inv.Bytes()
|
w.InvocationScript = inv.Bytes()
|
||||||
}
|
}
|
||||||
gasConsumed, err := s.chain.VerifyWitness(signer.Account, tx, &w, int64(s.config.MaxGasInvoke))
|
gasConsumed, err := s.chain.VerifyWitness(signer.Account, tx, &w, gasLimit)
|
||||||
if err != nil && !errors.Is(err, core.ErrInvalidSignature) {
|
if err != nil && !errors.Is(err, core.ErrInvalidSignature) {
|
||||||
return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidSignature, err.Error())
|
return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidSignature, err.Error())
|
||||||
}
|
}
|
||||||
|
gasLimit -= gasConsumed
|
||||||
netFee += gasConsumed
|
netFee += gasConsumed
|
||||||
size += io.GetVarSize(w.VerificationScript) + io.GetVarSize(w.InvocationScript)
|
size += io.GetVarSize(w.VerificationScript) + io.GetVarSize(w.InvocationScript)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package rpcsrv
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
gio "io"
|
gio "io"
|
||||||
|
@ -26,6 +27,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
|
||||||
|
@ -3184,6 +3186,21 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
||||||
body := calcReq(t, tx)
|
body := calcReq(t, tx)
|
||||||
_ = checkErrGetResult(t, body, true, neorpc.ErrInvalidVerificationFunctionCode, "signer 0 has no verify method in deployed contract")
|
_ = checkErrGetResult(t, body, true, neorpc.ErrInvalidVerificationFunctionCode, "signer 0 has no verify method in deployed contract")
|
||||||
})
|
})
|
||||||
|
t.Run("execution limit, fail", func(t *testing.T) {
|
||||||
|
// 1_6000_0000 GAS with the default 1.5 allowed by Policy
|
||||||
|
verifScript := []byte{byte(opcode.PUSHINT32), 0x00, 0x58, 0x89, 0x09, byte(opcode.SYSCALL), 0, 0, 0, 0, byte(opcode.PUSHT)}
|
||||||
|
binary.LittleEndian.PutUint32(verifScript[6:], interopnames.ToID([]byte(interopnames.SystemRuntimeBurnGas)))
|
||||||
|
tx := &transaction.Transaction{
|
||||||
|
Script: []byte{byte(opcode.RET)},
|
||||||
|
Signers: []transaction.Signer{{Account: hash.Hash160(verifScript)}},
|
||||||
|
Scripts: []transaction.Witness{{
|
||||||
|
InvocationScript: []byte{byte(opcode.NOP)},
|
||||||
|
VerificationScript: verifScript,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
body := calcReq(t, tx)
|
||||||
|
_ = checkErrGetResult(t, body, true, neorpc.ErrInvalidSignatureCode, "GAS limit exceeded")
|
||||||
|
})
|
||||||
checkCalc := func(t *testing.T, tx *transaction.Transaction, fee int64) {
|
checkCalc := func(t *testing.T, tx *transaction.Transaction, fee int64) {
|
||||||
resp := checkErrGetResult(t, calcReq(t, tx), false, 0)
|
resp := checkErrGetResult(t, calcReq(t, tx), false, 0)
|
||||||
res := new(result.NetworkFee)
|
res := new(result.NetworkFee)
|
||||||
|
@ -3258,6 +3275,20 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
||||||
invocScript := invocWriter.Bytes()
|
invocScript := invocWriter.Bytes()
|
||||||
checkContract(t, verAcc, invocScript, 146960) // No C# match, but we believe it's OK and it has a specific invocation script overriding anything server-side.
|
checkContract(t, verAcc, invocScript, 146960) // No C# match, but we believe it's OK and it has a specific invocation script overriding anything server-side.
|
||||||
})
|
})
|
||||||
|
t.Run("execution limit, ok", func(t *testing.T) {
|
||||||
|
// 1_4000_0000 GAS with the default 1.5 allowed by Policy
|
||||||
|
verifScript := []byte{byte(opcode.PUSHINT32), 0x00, 0x3b, 0x58, 0x08, byte(opcode.SYSCALL), 0, 0, 0, 0, byte(opcode.PUSHT)}
|
||||||
|
binary.LittleEndian.PutUint32(verifScript[6:], interopnames.ToID([]byte(interopnames.SystemRuntimeBurnGas)))
|
||||||
|
tx := &transaction.Transaction{
|
||||||
|
Script: []byte{byte(opcode.RET)},
|
||||||
|
Signers: []transaction.Signer{{Account: hash.Hash160(verifScript)}},
|
||||||
|
Scripts: []transaction.Witness{{
|
||||||
|
InvocationScript: []byte{byte(opcode.NOP)},
|
||||||
|
VerificationScript: verifScript,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
checkCalc(t, tx, 140065570)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
t.Run("sendrawtransaction", func(t *testing.T) {
|
t.Run("sendrawtransaction", func(t *testing.T) {
|
||||||
rpc := `{"jsonrpc": "2.0", "id": 1, "method": "sendrawtransaction", "params": ["%s"]}`
|
rpc := `{"jsonrpc": "2.0", "id": 1, "method": "sendrawtransaction", "params": ["%s"]}`
|
||||||
|
|
Loading…
Reference in a new issue