Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure System.Runtime.GetNotifications can't break MaxStackSize constraint #3485

Merged
merged 3 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions pkg/core/blockchain_neotest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2620,3 +2620,78 @@ func TestBlockchain_StoreAsTransaction_ExecutableConflict(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 2, len(aer))
}

// TestEngineLimits ensures that MaxStackSize limit is preserved during System.Runtime.GetNotifications
// syscall handling. This test is an adjusted port of https://github.com/lazynode/Tanya/pull/33 and makes
// sure that NeoGo node is not affected by https://github.com/neo-project/neo/issues/3300 and does not need
// the https://github.com/neo-project/neo/pull/3301.
func TestEngineLimits(t *testing.T) {
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)

src := `package test
import (
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
)
// args is an array of LargeEvent parameters containing 500 empty strings.
var args = []any{"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" };
func ProduceNumerousNotifications(count int) [][]any {
for i := 0; i < count; i++ {
runtime.Notify("LargeEvent", args...)
}
return runtime.GetNotifications(runtime.GetExecutingScriptHash())
}
func ProduceLargeObject(count int) int {
for i := 0; i < count; i++ {
runtime.Notify("LargeEvent", args...)
}
var (
smallObject = make([]int, 100)
res []int
)
for i := 0; i < count; i++ {
runtime.GetNotifications(runtime.GetExecutingScriptHash())
res = append(res, smallObject...)
}
return len(res)
}`
const eArgsCount = 500
eParams := make([]compiler.HybridParameter, eArgsCount)
for i := range eParams {
eParams[i].Name = fmt.Sprintf("str%d", i)
eParams[i].Type = smartcontract.ByteArrayType
}
c := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(src), &compiler.Options{
Name: "test_contract",
ContractEvents: []compiler.HybridEvent{
{
Name: "LargeEvent",
Parameters: eParams,
},
},
})
e.DeployContract(t, c, nil)

// ProduceNumerousNotifications: 1 iteration, no limits are hit.
var args = make([]stackitem.Item, eArgsCount)
for i := range args {
args[i] = stackitem.Make("")
}
cInv := e.NewInvoker(c.Hash, acc)
cInv.Invoke(t, stackitem.Make([]stackitem.Item{
stackitem.Make([]stackitem.Item{
stackitem.Make(c.Hash),
stackitem.Make("LargeEvent"),
stackitem.Make(args),
}),
}), "produceNumerousNotifications", 1)

// ProduceNumerousNotifications: hit the limit.
cInv.InvokeFail(t, "stack is too big", "produceNumerousNotifications", 500)

// ProduceLargeObject: 1 iteration, no limits are hit.
cInv.Invoke(t, 100*1, "produceLargeObject", 1)

// ProduceLargeObject: hit the limit.
cInv.InvokeFail(t, "stack is too big", "produceLargeObject", 500)
}
6 changes: 6 additions & 0 deletions pkg/vm/stackitem/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ func Make(v any) Item {
a = append(a, Make(i))
}
return Make(a)
case []string:
var a []Item
for _, i := range val {
a = append(a, Make(i))
}
return Make(a)
case []any:
res := make([]Item, len(val))
for i := range val {
Expand Down
4 changes: 2 additions & 2 deletions pkg/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
err = newError(ctx.ip, op, errRecover)
} else if v.refs > MaxStackSize {
v.state = vmstate.Fault
err = newError(ctx.ip, op, "stack is too big")
err = newError(ctx.ip, op, fmt.Sprintf("stack is too big: %d vs %d", int(v.refs), MaxStackSize))
}
}()

Expand Down Expand Up @@ -1995,7 +1995,7 @@ func validateMapKey(key Element) {

func (v *VM) checkInvocationStackSize() {
if len(v.istack) >= MaxInvocationStackSize {
panic("invocation stack is too big")
panic(fmt.Sprintf("invocation stack is too big: %d", len(v.istack)))
}
}

Expand Down
Loading