From b8a00277d3b9c71d5a2b377141622b3979255ccf Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 19 Oct 2021 14:03:46 +0800 Subject: [PATCH 1/6] Add diagnostic for ApplicationEngine --- src/neo/IO/Caching/Tree.cs | 35 +++++++++++++++ src/neo/IO/Caching/TreeNode.cs | 43 +++++++++++++++++++ src/neo/Plugins/IApplicationEngineProvider.cs | 3 +- src/neo/SmartContract/ApplicationEngine.cs | 25 ++++++++--- src/neo/SmartContract/Diagnostic.cs | 19 ++++++++ .../UT_ApplicationEngineProvider.cs | 8 ++-- 6 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 src/neo/IO/Caching/Tree.cs create mode 100644 src/neo/IO/Caching/TreeNode.cs create mode 100644 src/neo/SmartContract/Diagnostic.cs diff --git a/src/neo/IO/Caching/Tree.cs b/src/neo/IO/Caching/Tree.cs new file mode 100644 index 0000000000..256e4535d5 --- /dev/null +++ b/src/neo/IO/Caching/Tree.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2021 The Neo Project. +// +// The neo is free software distributed under the MIT software license, +// see the accompanying file LICENSE in the main directory of the +// project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; + +namespace Neo.IO.Caching +{ + public class Tree + { + public TreeNode Root { get; private set; } + + public TreeNode AddRoot(T item) + { + if (Root is not null) + throw new InvalidOperationException(); + Root = new TreeNode(item, null); + return Root; + } + + public IEnumerable GetItems() + { + if (Root is null) yield break; + foreach (T item in Root.GetItems()) + yield return item; + } + } +} diff --git a/src/neo/IO/Caching/TreeNode.cs b/src/neo/IO/Caching/TreeNode.cs new file mode 100644 index 0000000000..923b75e927 --- /dev/null +++ b/src/neo/IO/Caching/TreeNode.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2015-2021 The Neo Project. +// +// The neo is free software distributed under the MIT software license, +// see the accompanying file LICENSE in the main directory of the +// project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; + +namespace Neo.IO.Caching +{ + public class TreeNode + { + public T Item { get; } + public TreeNode Parent { get; } + + private readonly List> children = new(); + + internal TreeNode(T item, TreeNode parent) + { + Item = item; + Parent = parent; + } + + public TreeNode AddChild(T item) + { + TreeNode child = new(item, this); + children.Add(child); + return child; + } + + internal IEnumerable GetItems() + { + yield return Item; + foreach (var child in children) + foreach (T item in child.GetItems()) + yield return item; + } + } +} diff --git a/src/neo/Plugins/IApplicationEngineProvider.cs b/src/neo/Plugins/IApplicationEngineProvider.cs index e03c30868d..2e15d87a10 100644 --- a/src/neo/Plugins/IApplicationEngineProvider.cs +++ b/src/neo/Plugins/IApplicationEngineProvider.cs @@ -28,7 +28,8 @@ public interface IApplicationEngineProvider /// The block being persisted. It should be if the is . /// The used by the engine. /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + /// The diagnostic to be used by the . /// The engine instance created. - ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas); + ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, Diagnostic diagnostic); } } diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 12f6f4753d..674dfdec00 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -9,6 +9,7 @@ // modifications are permitted. using Neo.IO; +using Neo.IO.Caching; using Neo.IO.Json; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -50,6 +51,8 @@ public partial class ApplicationEngine : ExecutionEngine private static IApplicationEngineProvider applicationEngineProvider; private static Dictionary services; + private readonly Diagnostic diagnostic; + private TreeNode currentNodeOfInvocationTree = null; private readonly long gas_amount; private List notifications; private List disposables; @@ -135,7 +138,8 @@ public partial class ApplicationEngine : ExecutionEngine /// The block being persisted. It should be if the is . /// The used by the engine. /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. - protected unsafe ApplicationEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas) + /// The diagnostic to be used by the . + protected unsafe ApplicationEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, Diagnostic diagnostic) { this.Trigger = trigger; this.ScriptContainer = container; @@ -143,6 +147,7 @@ protected unsafe ApplicationEngine(TriggerType trigger, IVerifiable container, D this.PersistingBlock = persistingBlock; this.ProtocolSettings = settings; this.gas_amount = gas; + this.diagnostic = diagnostic; this.exec_fee_factor = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultExecFeeFactor : NativeContract.Policy.GetExecFeeFactor(Snapshot); this.StoragePrice = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultStoragePrice : NativeContract.Policy.GetStoragePrice(Snapshot); this.nonceData = container is Transaction tx ? tx.Hash.ToArray()[..16] : new byte[16]; @@ -250,6 +255,8 @@ internal ContractTask CallFromNativeContract(UInt160 callingScriptHash, UI protected override void ContextUnloaded(ExecutionContext context) { base.ContextUnloaded(context); + if (diagnostic is not null) + currentNodeOfInvocationTree = currentNodeOfInvocationTree.Parent; if (!contractTasks.Remove(context, out var awaiter)) return; if (UncaughtException is not null) throw new VMUnhandledException(UncaughtException); @@ -265,21 +272,29 @@ protected override void ContextUnloaded(ExecutionContext context) /// The block being persisted. It should be if the is . /// The used by the engine. /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + /// The diagnostic to be used by the . /// The engine instance created. - public static ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock = null, ProtocolSettings settings = null, long gas = TestModeGas) + public static ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock = null, ProtocolSettings settings = null, long gas = TestModeGas, Diagnostic diagnostic = null) { - return applicationEngineProvider?.Create(trigger, container, snapshot, persistingBlock, settings, gas) - ?? new ApplicationEngine(trigger, container, snapshot, persistingBlock, settings, gas); + return applicationEngineProvider?.Create(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic) + ?? new ApplicationEngine(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic); } protected override void LoadContext(ExecutionContext context) { // Set default execution context state - var state = context.GetState(); state.ScriptHash ??= ((byte[])context.Script).ToScriptHash(); invocationCounter.TryAdd(state.ScriptHash, 1); + if (diagnostic is not null) + { + if (currentNodeOfInvocationTree is null) + currentNodeOfInvocationTree = diagnostic.InvocationTree.AddRoot(state.ScriptHash); + else + currentNodeOfInvocationTree = currentNodeOfInvocationTree.AddChild(state.ScriptHash); + } + base.LoadContext(context); } diff --git a/src/neo/SmartContract/Diagnostic.cs b/src/neo/SmartContract/Diagnostic.cs new file mode 100644 index 0000000000..d3de477429 --- /dev/null +++ b/src/neo/SmartContract/Diagnostic.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2015-2021 The Neo Project. +// +// The neo is free software distributed under the MIT software license, +// see the accompanying file LICENSE in the main directory of the +// project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO.Caching; + +namespace Neo.SmartContract +{ + public class Diagnostic + { + public Tree InvocationTree { get; } + } +} diff --git a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs index fb1b8f6e4f..aed76fc89e 100644 --- a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs +++ b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs @@ -63,16 +63,16 @@ public void TestCanResetAppEngineProviderTwice() class TestProvider : IApplicationEngineProvider { - public ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas) + public ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, Diagnostic diagnostic) { - return new TestEngine(trigger, container, snapshot, persistingBlock, settings, gas); + return new TestEngine(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic); } } class TestEngine : ApplicationEngine { - public TestEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas) - : base(trigger, container, snapshot, persistingBlock, settings, gas) + public TestEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, Diagnostic diagnostic) + : base(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic) { } } From 058c1df18c7af10faced53dd6822f18ad6b0b9a7 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 19 Oct 2021 16:35:15 +0800 Subject: [PATCH 2/6] Add the new parameter to ApplicationEngine.Run --- src/neo/SmartContract/ApplicationEngine.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 674dfdec00..55a8baef81 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -546,11 +546,12 @@ internal static void ResetApplicationEngineProvider() /// The used by the engine. /// The initial position of the instruction pointer. /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + /// The diagnostic to be used by the . /// The engine instance created. - public static ApplicationEngine Run(byte[] script, DataCache snapshot, IVerifiable container = null, Block persistingBlock = null, ProtocolSettings settings = null, int offset = 0, long gas = TestModeGas) + public static ApplicationEngine Run(byte[] script, DataCache snapshot, IVerifiable container = null, Block persistingBlock = null, ProtocolSettings settings = null, int offset = 0, long gas = TestModeGas, Diagnostic diagnostic = null) { persistingBlock ??= CreateDummyBlock(snapshot, settings ?? ProtocolSettings.Default); - ApplicationEngine engine = Create(TriggerType.Application, container, snapshot, persistingBlock, settings, gas); + ApplicationEngine engine = Create(TriggerType.Application, container, snapshot, persistingBlock, settings, gas, diagnostic); engine.LoadScript(script, initialPosition: offset); engine.Execute(); return engine; From 3b52670bb9b934618f853bb3244ca07a02f54bee Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Tue, 19 Oct 2021 16:39:27 +0800 Subject: [PATCH 3/6] public Diagnostic --- src/neo/SmartContract/ApplicationEngine.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 55a8baef81..2a08cf95d8 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -51,7 +51,6 @@ public partial class ApplicationEngine : ExecutionEngine private static IApplicationEngineProvider applicationEngineProvider; private static Dictionary services; - private readonly Diagnostic diagnostic; private TreeNode currentNodeOfInvocationTree = null; private readonly long gas_amount; private List notifications; @@ -67,6 +66,11 @@ public partial class ApplicationEngine : ExecutionEngine /// public static IReadOnlyDictionary Services => services; + /// + /// The diagnostic used by the engine. This property can be . + /// + public Diagnostic Diagnostic { get; } + private List Disposables => disposables ??= new List(); /// @@ -147,7 +151,7 @@ protected unsafe ApplicationEngine(TriggerType trigger, IVerifiable container, D this.PersistingBlock = persistingBlock; this.ProtocolSettings = settings; this.gas_amount = gas; - this.diagnostic = diagnostic; + this.Diagnostic = diagnostic; this.exec_fee_factor = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultExecFeeFactor : NativeContract.Policy.GetExecFeeFactor(Snapshot); this.StoragePrice = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultStoragePrice : NativeContract.Policy.GetStoragePrice(Snapshot); this.nonceData = container is Transaction tx ? tx.Hash.ToArray()[..16] : new byte[16]; @@ -255,7 +259,7 @@ internal ContractTask CallFromNativeContract(UInt160 callingScriptHash, UI protected override void ContextUnloaded(ExecutionContext context) { base.ContextUnloaded(context); - if (diagnostic is not null) + if (Diagnostic is not null) currentNodeOfInvocationTree = currentNodeOfInvocationTree.Parent; if (!contractTasks.Remove(context, out var awaiter)) return; if (UncaughtException is not null) @@ -287,10 +291,10 @@ protected override void LoadContext(ExecutionContext context) state.ScriptHash ??= ((byte[])context.Script).ToScriptHash(); invocationCounter.TryAdd(state.ScriptHash, 1); - if (diagnostic is not null) + if (Diagnostic is not null) { if (currentNodeOfInvocationTree is null) - currentNodeOfInvocationTree = diagnostic.InvocationTree.AddRoot(state.ScriptHash); + currentNodeOfInvocationTree = Diagnostic.InvocationTree.AddRoot(state.ScriptHash); else currentNodeOfInvocationTree = currentNodeOfInvocationTree.AddChild(state.ScriptHash); } From cdf6186055701a660fb7d28c85d162407b9cbe44 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Wed, 20 Oct 2021 13:29:05 +0800 Subject: [PATCH 4/6] Fix --- src/neo/SmartContract/Diagnostic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/SmartContract/Diagnostic.cs b/src/neo/SmartContract/Diagnostic.cs index d3de477429..f2f4231fff 100644 --- a/src/neo/SmartContract/Diagnostic.cs +++ b/src/neo/SmartContract/Diagnostic.cs @@ -14,6 +14,6 @@ namespace Neo.SmartContract { public class Diagnostic { - public Tree InvocationTree { get; } + public Tree InvocationTree { get; } = new(); } } From f56725c8221287c19c0d9aa015515e61128f1314 Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 20 Oct 2021 09:58:02 +0200 Subject: [PATCH 5/6] Add UT --- tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs index 66f4f87e64..34e144efba 100644 --- a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -50,10 +50,11 @@ public void TestCreateDummyBlock() { var snapshot = TestBlockchain.GetTestSnapshot(); byte[] SyscallSystemRuntimeCheckWitnessHash = new byte[] { 0x68, 0xf8, 0x27, 0xec, 0x8c }; - ApplicationEngine engine = ApplicationEngine.Run(SyscallSystemRuntimeCheckWitnessHash, snapshot); + ApplicationEngine engine = ApplicationEngine.Run(SyscallSystemRuntimeCheckWitnessHash, snapshot,diagnostic: new Diagnostic()); engine.PersistingBlock.Version.Should().Be(0); engine.PersistingBlock.PrevHash.Should().Be(TestBlockchain.TheNeoSystem.GenesisBlock.Hash); engine.PersistingBlock.MerkleRoot.Should().Be(new UInt256()); + string.Join(",",engine.Diagnostic.InvocationTree.GetItems()).Should().Be("0x67bd6dffcf36468c9ffbe6cc4cd0cb7108304d24"); } } } From ce6c023f5f7573fd2b8b2064a6d2e14253775024 Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 20 Oct 2021 09:58:44 +0200 Subject: [PATCH 6/6] format UT --- tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs index 34e144efba..c0c1199627 100644 --- a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -50,11 +50,11 @@ public void TestCreateDummyBlock() { var snapshot = TestBlockchain.GetTestSnapshot(); byte[] SyscallSystemRuntimeCheckWitnessHash = new byte[] { 0x68, 0xf8, 0x27, 0xec, 0x8c }; - ApplicationEngine engine = ApplicationEngine.Run(SyscallSystemRuntimeCheckWitnessHash, snapshot,diagnostic: new Diagnostic()); + ApplicationEngine engine = ApplicationEngine.Run(SyscallSystemRuntimeCheckWitnessHash, snapshot, diagnostic: new Diagnostic()); engine.PersistingBlock.Version.Should().Be(0); engine.PersistingBlock.PrevHash.Should().Be(TestBlockchain.TheNeoSystem.GenesisBlock.Hash); engine.PersistingBlock.MerkleRoot.Should().Be(new UInt256()); - string.Join(",",engine.Diagnostic.InvocationTree.GetItems()).Should().Be("0x67bd6dffcf36468c9ffbe6cc4cd0cb7108304d24"); + string.Join(",", engine.Diagnostic.InvocationTree.GetItems()).Should().Be("0x67bd6dffcf36468c9ffbe6cc4cd0cb7108304d24"); } } }