Merge pull request #3046 from nspcc-dev/mainnet-state-fix

interop/contract: fix state rollbacks for nested contexts
This commit is contained in:
Roman Khimov 2023-06-29 12:24:23 +03:00 committed by GitHub
commit 91b57745ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 26 additions and 11 deletions

View file

@ -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

View file

@ -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 {

View file

@ -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{

View file

@ -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 {