mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 13:41:37 +00:00
interop/contract: fix state rollbacks for nested contexts
Our wrapping optimization relied on the caller context having a TRY block, but each context (including internal calls!) has an exception handling stack of its own, which means that for an invocation stack of entry A.someMethodFromEntry() # this one has a TRY A.internalMethodViaCALL() # this one doesn't B.someMethod() we get `HasTryBlock() == false` for `A.internalMethodViaCALL()` context, which leads to missing wrapper and missing rollbacks if B is to THROW. What this patch does instead is it checks for any context within contract boundaries. Fixes #3045. Signed-off-by: Roman Khimov <roman@nspcc.ru>
This commit is contained in:
parent
9185820289
commit
20b19d4599
4 changed files with 26 additions and 11 deletions
|
@ -117,7 +117,7 @@ func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contra
|
||||||
ic.Invocations[cs.Hash]++
|
ic.Invocations[cs.Hash]++
|
||||||
f = ic.VM.Context().GetCallFlags() & f
|
f = ic.VM.Context().GetCallFlags() & f
|
||||||
|
|
||||||
wrapped := ic.VM.Context().HasTryBlock() && // If the method is not wrapped into try-catch block, then changes should be discarded anyway if exception occurs.
|
wrapped := ic.VM.ContractHasTryBlock() && // If the method is not wrapped into try-catch block, then changes should be discarded anyway if exception occurs.
|
||||||
f&(callflag.All^callflag.ReadOnly) != 0 // If the method is safe, then it's read-only and doesn't perform storage changes or emit notifications.
|
f&(callflag.All^callflag.ReadOnly) != 0 // If the method is safe, then it's read-only and doesn't perform storage changes or emit notifications.
|
||||||
baseNtfCount := len(ic.Notifications)
|
baseNtfCount := len(ic.Notifications)
|
||||||
baseDAO := ic.DAO
|
baseDAO := ic.DAO
|
||||||
|
|
|
@ -304,6 +304,9 @@ func TestSnapshotIsolation_Exceptions(t *testing.T) {
|
||||||
for i := 0; i < nNtfB1; i++ {
|
for i := 0; i < nNtfB1; i++ {
|
||||||
runtime.Notify("NotificationFromB before panic", i)
|
runtime.Notify("NotificationFromB before panic", i)
|
||||||
}
|
}
|
||||||
|
internalCaller(keyA, valueA, nNtfA)
|
||||||
|
}
|
||||||
|
func internalCaller(keyA, valueA []byte, nNtfA int) {
|
||||||
contract.Call(interop.Hash160{` + hashAStr + `}, "doAndPanic", contract.All, keyA, valueA, nNtfA)
|
contract.Call(interop.Hash160{` + hashAStr + `}, "doAndPanic", contract.All, keyA, valueA, nNtfA)
|
||||||
}
|
}
|
||||||
func CheckStorageChanges() bool {
|
func CheckStorageChanges() bool {
|
||||||
|
|
|
@ -315,16 +315,6 @@ func (v *VM) PushContextScriptHash(n int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) HasTryBlock() bool {
|
|
||||||
for i := 0; i < c.tryStack.Len(); i++ {
|
|
||||||
eCtx := c.tryStack.Peek(i).Value().(*exceptionHandlingContext)
|
|
||||||
if eCtx.State == eTry {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements the JSON marshalling interface.
|
// MarshalJSON implements the JSON marshalling interface.
|
||||||
func (c *Context) MarshalJSON() ([]byte, error) {
|
func (c *Context) MarshalJSON() ([]byte, error) {
|
||||||
var aux = contextAux{
|
var aux = contextAux{
|
||||||
|
|
22
pkg/vm/vm.go
22
pkg/vm/vm.go
|
@ -1804,6 +1804,28 @@ func throwUnhandledException(item stackitem.Item) {
|
||||||
panic(msg)
|
panic(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContractHasTryBlock checks if the currently executing contract has a TRY
|
||||||
|
// block in one of its contexts.
|
||||||
|
func (v *VM) ContractHasTryBlock() bool {
|
||||||
|
var topctx *Context // Currently executing context.
|
||||||
|
for i := 0; i < len(v.istack); i++ {
|
||||||
|
ictx := v.istack[len(v.istack)-1-i] // It's a stack, going backwards like handleException().
|
||||||
|
if topctx == nil {
|
||||||
|
topctx = ictx
|
||||||
|
}
|
||||||
|
if ictx.sc != topctx.sc {
|
||||||
|
return false // Different contract -> no one cares.
|
||||||
|
}
|
||||||
|
for j := 0; j < ictx.tryStack.Len(); j++ {
|
||||||
|
eCtx := ictx.tryStack.Peek(j).Value().(*exceptionHandlingContext)
|
||||||
|
if eCtx.State == eTry {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// CheckMultisigPar checks if the sigs contains sufficient valid signatures.
|
// CheckMultisigPar checks if the sigs contains sufficient valid signatures.
|
||||||
func CheckMultisigPar(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sigs [][]byte) bool {
|
func CheckMultisigPar(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sigs [][]byte) bool {
|
||||||
if len(sigs) == 1 {
|
if len(sigs) == 1 {
|
||||||
|
|
Loading…
Reference in a new issue