From 98aeefab17a5bd4c73209711e0b8bd5f4b619fa0 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 22 Apr 2025 13:32:33 +0300 Subject: [PATCH 1/2] Native: revert Update/Deploy callflag change for pre-Aspidochelone https://github.com/neo-project/neo/pull/2653 changed required callflags of native ContractManagement's Update and Deploy methods from States|AllowNotify to All. This change didn't affect N3 mainnet/T5 (ref. https://github.com/neo-project/neo/issues/2673), but the problem is that this change affected NeoFS mainnet network (see https://github.com/nspcc-dev/neo-go/pull/2848 and commits description). This commit fixes state difference between Go and C# nodes at height 451626 of NeoFS mainnet. Note that this commit does not affect existing N3 mainnet/testnet states, so no resynchronisation is required on update. The difference itself: ``` go run scripts/compare-dumps/compare-dumps.go ./godump-echidna-neofs-mainnet/ ../../neo-project/neo/neo-cli-notary-mainnet/Storage_0572dfa5/ Processing directory BlockStorage_0 Processing directory BlockStorage_100000 Processing directory BlockStorage_200000 Processing directory BlockStorage_300000 Processing directory BlockStorage_400000 Processing directory BlockStorage_500000 file BlockStorage_500000/dump-block-452000.json: block 451626, changes length mismatch: 25 vs 11 compare-dumps dumpDirA dumpDirB exit status 1 ``` Go node application log for the problem transaction: ``` anna@kiwi:~/Documents/GitProjects/nspcc-dev/neo-go$ curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["0x5028585a5c27b7f357771fa8b512c2d1b0ba40dcb3ea30e67d3db2d75d2da60d"] }' localhost:40332 | json_pp % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 592 100 450 100 142 461k 145k --:--:-- --:--:-- --:--:-- 578k { "id" : 1, "jsonrpc" : "2.0", "result" : { "executions" : [ { "exception" : null, "gasconsumed" : "900316660", "invocations" : null, "notifications" : [ { "contract" : "0xfffdc93764dbaddd97c48f252a53ea4643faa3fd", "eventname" : "Update", "state" : { "type" : "Array", "value" : [ { "type" : "ByteString", "value" : "r6jbcP2s5N7DIvRYS2ZiFdP7YXA=" } ] } } ], "stack" : [ { "type" : "Any" } ], "trigger" : "Application", "vmstate" : "HALT" } ], "txid" : "0x5028585a5c27b7f357771fa8b512c2d1b0ba40dcb3ea30e67d3db2d75d2da60d" } } ``` C# application log for the same transaction: ``` anna@kiwi:~/Documents/GitProjects/nspcc-dev/neo-go$ curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["0x5028585a5c27b7f357771fa8b512c2d1b0ba40dcb3ea30e67d3db2d75d2da60d"] }' localhost:50332 | json_pp % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 439 0 297 100 142 3502 1674 --:--:-- --:--:-- --:--:-- 5226 { "id" : 1, "jsonrpc" : "2.0", "result" : { "executions" : [ { "exception" : "Cannot call this method with the flag States, AllowNotify.", "gasconsumed" : "5684010", "notifications" : [], "stack" : [], "trigger" : "Application", "vmstate" : "FAULT" } ], "txid" : "0x5028585a5c27b7f357771fa8b512c2d1b0ba40dcb3ea30e67d3db2d75d2da60d" } } ``` Signed-off-by: Anna Shaleva --- src/Neo/SmartContract/Native/NativeContract.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Neo/SmartContract/Native/NativeContract.cs b/src/Neo/SmartContract/Native/NativeContract.cs index 3be9028185..48f215f644 100644 --- a/src/Neo/SmartContract/Native/NativeContract.cs +++ b/src/Neo/SmartContract/Native/NativeContract.cs @@ -420,7 +420,12 @@ internal async void Invoke(ApplicationEngine engine, byte version) if (method.DeprecatedIn is not null && engine.IsHardforkEnabled(method.DeprecatedIn.Value)) throw new InvalidOperationException($"Cannot call this method after hardfork {method.DeprecatedIn}."); var state = context.GetState(); - if (!state.CallFlags.HasFlag(method.RequiredCallFlags)) + var requiredFlags = method.RequiredCallFlags; + // A special case for `deploy` and `update` methods of native ContractManagement contract + // for pre-Aspidochelone blocks to avoid breaking existing chains, ref. #2653, #2673. + if (!engine.IsHardforkEnabled(Hardfork.HF_Aspidochelone) && Id == -1 && (method.Name == "deploy" || method.Name == "update")) + requiredFlags &= CallFlags.States | CallFlags.AllowNotify; + if (!state.CallFlags.HasFlag(requiredFlags)) throw new InvalidOperationException($"Cannot call this method with the flag {state.CallFlags}."); // In the unit of datoshi, 1 datoshi = 1e-8 GAS engine.AddFee(method.CpuFee * engine.ExecFeeFactor + method.StorageFee * engine.StoragePrice); From ac0d6d63acdce47cbef434ad74e2170afd1b6411 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 22 Apr 2025 19:55:08 +0300 Subject: [PATCH 2/2] Native: move Update/Deploy callflags check to Management implementation Make these checks implementation-specific in order not to affect the general native invocation code. Ref. https://github.com/neo-project/neo/pull/3909#pullrequestreview-2784113694. Signed-off-by: Anna Shaleva --- .../Native/ContractManagement.cs | 22 +++++++++++++++---- .../SmartContract/Native/NativeContract.cs | 7 +----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Neo/SmartContract/Native/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs index 31682ead63..c3afd94cbe 100644 --- a/src/Neo/SmartContract/Native/ContractManagement.cs +++ b/src/Neo/SmartContract/Native/ContractManagement.cs @@ -217,15 +217,22 @@ public IEnumerable ListContracts(IReadOnlyStore snapshot) return snapshot.Find(listContractsPrefix).Select(kvp => kvp.Value.GetInteroperableClone(false)); } - [ContractMethod(RequiredCallFlags = CallFlags.All)] + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] private ContractTask Deploy(ApplicationEngine engine, byte[] nefFile, byte[] manifest) { return Deploy(engine, nefFile, manifest, StackItem.Null); } - [ContractMethod(RequiredCallFlags = CallFlags.All)] + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] private async ContractTask Deploy(ApplicationEngine engine, byte[] nefFile, byte[] manifest, StackItem data) { + // Require CallFlags.All flag for post-Aspidochelone transactions, ref. #2653, #2673. + if (engine.IsHardforkEnabled(Hardfork.HF_Aspidochelone)) + { + var state = engine.CurrentContext.GetState(); + if (!state.CallFlags.HasFlag(CallFlags.All)) + throw new InvalidOperationException($"Cannot call Deploy with the flag {state.CallFlags}."); + } if (engine.ScriptContainer is not Transaction tx) throw new InvalidOperationException(); if (nefFile.Length == 0) @@ -268,15 +275,22 @@ private async ContractTask Deploy(ApplicationEngine engine, byte[ return contract; } - [ContractMethod(RequiredCallFlags = CallFlags.All)] + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest) { return Update(engine, nefFile, manifest, StackItem.Null); } - [ContractMethod(RequiredCallFlags = CallFlags.All)] + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest, StackItem data) { + // Require CallFlags.All flag for post-Aspidochelone transactions, ref. #2653, #2673. + if (engine.IsHardforkEnabled(Hardfork.HF_Aspidochelone)) + { + var state = engine.CurrentContext.GetState(); + if (!state.CallFlags.HasFlag(CallFlags.All)) + throw new InvalidOperationException($"Cannot call Update with the flag {state.CallFlags}."); + } if (nefFile is null && manifest is null) throw new ArgumentException("The nefFile and manifest cannot be null at the same time."); diff --git a/src/Neo/SmartContract/Native/NativeContract.cs b/src/Neo/SmartContract/Native/NativeContract.cs index 48f215f644..3be9028185 100644 --- a/src/Neo/SmartContract/Native/NativeContract.cs +++ b/src/Neo/SmartContract/Native/NativeContract.cs @@ -420,12 +420,7 @@ internal async void Invoke(ApplicationEngine engine, byte version) if (method.DeprecatedIn is not null && engine.IsHardforkEnabled(method.DeprecatedIn.Value)) throw new InvalidOperationException($"Cannot call this method after hardfork {method.DeprecatedIn}."); var state = context.GetState(); - var requiredFlags = method.RequiredCallFlags; - // A special case for `deploy` and `update` methods of native ContractManagement contract - // for pre-Aspidochelone blocks to avoid breaking existing chains, ref. #2653, #2673. - if (!engine.IsHardforkEnabled(Hardfork.HF_Aspidochelone) && Id == -1 && (method.Name == "deploy" || method.Name == "update")) - requiredFlags &= CallFlags.States | CallFlags.AllowNotify; - if (!state.CallFlags.HasFlag(requiredFlags)) + if (!state.CallFlags.HasFlag(method.RequiredCallFlags)) throw new InvalidOperationException($"Cannot call this method with the flag {state.CallFlags}."); // In the unit of datoshi, 1 datoshi = 1e-8 GAS engine.AddFee(method.CpuFee * engine.ExecFeeFactor + method.StorageFee * engine.StoragePrice);