From ff09a81d8bbff22fe968d6ad0ee5704e5220b632 Mon Sep 17 00:00:00 2001 From: doubiliu Date: Thu, 27 Jan 2022 17:05:07 +0800 Subject: [PATCH 01/13] init --- neo-modules.sln | 20 +- src/NotaryService/NotaryPlugin.cs | 105 +++++ src/NotaryService/NotaryService.cs | 407 ++++++++++++++++++++ src/NotaryService/NotaryService.csproj | 22 ++ src/NotaryService/NotaryService/config.json | 7 + src/NotaryService/Settings.cs | 25 ++ 6 files changed, 583 insertions(+), 3 deletions(-) create mode 100644 src/NotaryService/NotaryPlugin.cs create mode 100644 src/NotaryService/NotaryService.cs create mode 100644 src/NotaryService/NotaryService.csproj create mode 100644 src/NotaryService/NotaryService/config.json create mode 100644 src/NotaryService/Settings.cs diff --git a/neo-modules.sln b/neo-modules.sln index 5c381d3d2..d57b70653 100644 --- a/neo-modules.sln +++ b/neo-modules.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28729.10 MinimumVisualStudioVersion = 10.0.40219.1 @@ -34,9 +35,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.RpcServer.Tests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokensTracker", "src\TokensTracker\TokensTracker.csproj", "{48CB0583-26CE-48FC-9F53-F7DC6F1727D8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MPTTrie", "src\MPTTrie\MPTTrie.csproj", "{D167FA6B-D2A3-4D8A-A65D-686DD06650F6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPTTrie", "src\MPTTrie\MPTTrie.csproj", "{D167FA6B-D2A3-4D8A-A65D-686DD06650F6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Cryptography.MPTTrie.Tests", "tests\Neo.Cryptography.MPTTrie.Tests\Neo.Cryptography.MPTTrie.Tests.csproj", "{8D2EE375-2E2D-45FE-A4E9-0254D12C7554}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Cryptography.MPTTrie.Tests", "tests\Neo.Cryptography.MPTTrie.Tests\Neo.Cryptography.MPTTrie.Tests.csproj", "{8D2EE375-2E2D-45FE-A4E9-0254D12C7554}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotaryService", "src\NotaryService\NotaryService.csproj", "{DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "neo", "..\..\neo\src\neo\neo.csproj", "{A0E35436-F30E-4B36-B97D-D5C93790EBA7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -108,6 +113,14 @@ Global {8D2EE375-2E2D-45FE-A4E9-0254D12C7554}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D2EE375-2E2D-45FE-A4E9-0254D12C7554}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D2EE375-2E2D-45FE-A4E9-0254D12C7554}.Release|Any CPU.Build.0 = Release|Any CPU + {DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95}.Release|Any CPU.Build.0 = Release|Any CPU + {A0E35436-F30E-4B36-B97D-D5C93790EBA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0E35436-F30E-4B36-B97D-D5C93790EBA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0E35436-F30E-4B36-B97D-D5C93790EBA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0E35436-F30E-4B36-B97D-D5C93790EBA7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -129,6 +142,7 @@ Global {48CB0583-26CE-48FC-9F53-F7DC6F1727D8} = {97E81C78-1637-481F-9485-DA1225E94C23} {D167FA6B-D2A3-4D8A-A65D-686DD06650F6} = {97E81C78-1637-481F-9485-DA1225E94C23} {8D2EE375-2E2D-45FE-A4E9-0254D12C7554} = {59D802AB-C552-422A-B9C3-64D329FBCDCC} + {DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95} = {97E81C78-1637-481F-9485-DA1225E94C23} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {61D3ADE6-BBFC-402D-AB42-1C71C9F9EDE3} diff --git a/src/NotaryService/NotaryPlugin.cs b/src/NotaryService/NotaryPlugin.cs new file mode 100644 index 000000000..29e79890f --- /dev/null +++ b/src/NotaryService/NotaryPlugin.cs @@ -0,0 +1,105 @@ +using Akka.Actor; +using Neo.ConsoleService; +using Neo.Cryptography.ECC; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Persistence; +using Neo.Plugins; +using Neo.SmartContract.Native; +using Neo.Wallets; +using System; +using System.Linq; +using Settings = Neo.Plugins.Settings; + +namespace Neo.Consensus +{ + public class NotaryPlugin : Plugin + { + private NeoSystem System; + private Wallet wallet; + private IWalletProvider walletProvider; + private bool started = false; + private IActorRef notary; + + public override string Description => "Notary plugin"; + + protected override void Configure() + { + Settings.Load(GetConfiguration()); + } + + protected override void OnSystemLoaded(NeoSystem system) + { + if (system.Settings.Network != Settings.Default.Network) return; + System = system; + System.ServiceAdded += NeoSystem_ServiceAdded; + } + + private void NeoSystem_ServiceAdded(object sender, object service) + { + if (service is IWalletProvider) + { + walletProvider = service as IWalletProvider; + System.ServiceAdded -= NeoSystem_ServiceAdded; + if (Settings.Default.AutoStart) + { + walletProvider.WalletChanged += WalletProvider_WalletChanged; + } + } + } + + private void WalletProvider_WalletChanged(object sender, Wallet wallet) + { + walletProvider.WalletChanged -= WalletProvider_WalletChanged; + Start(wallet); + } + + [ConsoleCommand("notary start", Category = "Notary", Description = "Start notary service")] + private void OnStart() + { + Start(walletProvider?.GetWallet()); + } + + public void Start(Wallet wallet) + { + if (started) return; + if (wallet is null) + { + Console.WriteLine("Please open wallet first!"); + return; + } + if (!CheckNotaryAvaiblable(System.StoreView, out ECPoint[] notarynodes)) + { + Console.WriteLine("The notary service is unavailable"); + return; + } + Console.WriteLine("notarynodes:" + notarynodes[0].ToString()); + if (!CheckNotaryAccount(wallet, notarynodes)) + { + Console.WriteLine("There is no notary account in wallet"); + return; + } + this.wallet = wallet; + notary = System.ActorSystem.ActorOf(NotaryService.Props(System, this.wallet)); + System.ActorSystem.EventStream.Subscribe(notary, typeof(Blockchain.PersistCompleted)); + System.ActorSystem.EventStream.Subscribe(notary, typeof(Blockchain.RelayResult)); + notary.Tell(new NotaryService.Start()); + started = true; + Console.WriteLine($"Notary started"); + } + + private static bool CheckNotaryAvaiblable(DataCache snapshot, out ECPoint[] notarynodes) + { + uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + notarynodes = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Notary, height); + return notarynodes.Length > 0; + } + + private static bool CheckNotaryAccount(Wallet wallet, ECPoint[] notarynodes) + { + return notarynodes + .Select(p => wallet.GetAccount(p)) + .Any(p => p is not null && p.HasKey && !p.Lock); + } + } +} diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs new file mode 100644 index 000000000..d2e975705 --- /dev/null +++ b/src/NotaryService/NotaryService.cs @@ -0,0 +1,407 @@ +using Akka.Actor; +using Neo; +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Plugins +{ + public class NotaryService : UntypedActor + { + public class Start { } + private bool started = false; + private Wallet wallet; + private NeoSystem neoSystem; + + private readonly ConcurrentDictionary pendingQueue = new ConcurrentDictionary(); + private readonly List payloadCache = new List(); + + public NotaryService(NeoSystem neoSystem,Wallet wallet) + { + this.wallet = wallet; + this.neoSystem = neoSystem; + } + + + protected override void OnReceive(object message) + { + if (message is Start) + { + if (started) return; + OnStart(); + Console.WriteLine("Notary Service 启动"); + } + else + { + if (!started) return; + switch (message) + { + case Blockchain.PersistCompleted completed: + OnPersistCompleted(completed.Block); + break; + case Blockchain.RelayResult rr: + if (rr.Result == VerifyResult.Succeed && rr.Inventory is NotaryRequest payload) + OnNotaryPayload(payload); + break; + } + } + } + + private void OnStart() + { + Log("OnStart"); + started = true; + } + + private void OnNotaryPayload(NotaryRequest payload) + { + if (payloadCache.Count < 100) + { + payloadCache.Add(payload); + OnNewRequest(payload); + } + else + { + var oldPayload = payloadCache[0]; + payloadCache.RemoveAt(0); + OnRequestRemoval(oldPayload); + OnNewRequest(payload); + } + } + + private void OnPersistCompleted(Block block) + { + Log($"Persisted {nameof(Block)}: height={block.Index} hash={block.Hash} tx={block.Transactions.Length}"); + var currHeight = block.Index; + foreach (var item in pendingQueue) + { + var h = item.Key; + var r = item.Value; + if (!r.isSent) { + if (r.IsMainCompleted() && r.minNotValidBefore > currHeight) + { + Finalize(r.mainTx); + r.isSent = true; + continue; + } + if (r.minNotValidBefore <= currHeight) + { + var newFallbacks = new List(); + foreach (var fb in r.fallbackTxs) + { + var nvb = fb.GetAttributes().ToArray()[0].Height; + if (nvb <= currHeight) Finalize(fb); + } + r.isSent = true; + } + } + } + } + + private void OnNewRequest(NotaryRequest payload) + { + var snapshot = neoSystem.GetSnapshot(); + var nvbFallback = payload.FallbackTransaction.GetAttributes().ToArray()[0].Height; + byte nKeys = payload.MainTransaction.GetAttributes().ToArray()[0].NKeys; + VerifyIncompleteWitnesses(payload.MainTransaction, nKeys, out WitnessInfo[] newInfo, out string validationErr); + if (validationErr is not null) + Log($"verification of main notary transaction failed; fallback transaction will be completed,main hash:{payload.MainTransaction.Hash},fallback hash:{payload.FallbackTransaction.Hash},verification error:{validationErr}"); + var exists = pendingQueue.TryGetValue(payload.MainTransaction.Hash, out NotaryTask r); + if (exists) + { + foreach (var fb in r.fallbackTxs) + if (fb.Hash.Equals(payload.FallbackTransaction.Hash)) + return; + if (nvbFallback < r.minNotValidBefore) + r.minNotValidBefore = nvbFallback; + } + else + { + r = new NotaryTask() + { + mainTx = payload.MainTransaction, + minNotValidBefore = nvbFallback + }; + pendingQueue[payload.MainTransaction.Hash] = r; + } + if (r.witnessInfo is null && validationErr is null) + r.witnessInfo = newInfo; + r.fallbackTxs.Append(payload.FallbackTransaction); + r.isSent = false; + if (exists && r.IsMainCompleted() && validationErr != null) + return; + var mainHash = r.mainTx.Hash.ToArray(); + for (int i = 0; i < payload.MainTransaction.Witnesses.Length; i++) + { + if (payload.MainTransaction.Witnesses[i].InvocationScript.Length == 0 || r.witnessInfo[i].nSigsLeft == 0 && r.witnessInfo[i].typ == RequestType.Contract) + continue; + switch (r.witnessInfo[i].typ) + { + case RequestType.Contract: + if (!VerifyWitness(r.mainTx, ProtocolSettings.Default, snapshot, r.mainTx.Signers[i].Account, payload.MainTransaction.Witnesses[i], r.mainTx.NetworkFee, out _)) + continue; + r.mainTx.Witnesses[i].InvocationScript = payload.MainTransaction.Witnesses[i].InvocationScript; + break; + case RequestType.Signature: + if (Crypto.VerifySignature(mainHash, payload.MainTransaction.Witnesses[i].InvocationScript.Skip(2).ToArray(), r.witnessInfo[i].pubs[0])) + { + r.mainTx.Witnesses[i] = payload.MainTransaction.Witnesses[i]; + r.witnessInfo[i].nSigsLeft--; + } + break; + case RequestType.MultiSignature: + if (r.witnessInfo[i].sigs is null) + r.witnessInfo[i].sigs = new Dictionary(); + foreach (var pub in r.witnessInfo[i].pubs) + { + if (r.witnessInfo[i].sigs[pub] is not null) + continue; + if (Crypto.VerifySignature(mainHash, payload.MainTransaction.Witnesses[i].InvocationScript.Skip(2).ToArray(), pub)) + { + r.witnessInfo[i].sigs[pub] = payload.MainTransaction.Witnesses[i].InvocationScript; + r.witnessInfo[i].nSigsLeft--; + if (r.witnessInfo[i].nSigsLeft == 0) + { + byte[] invScript = new byte[0]; + for (int j = 0; j < r.witnessInfo[i].pubs.Length; j++) + { + if (r.witnessInfo[i].sigs.TryGetValue(r.witnessInfo[i].pubs[j], out var sig)) + invScript = invScript.Concat(sig).ToArray(); + } + r.mainTx.Witnesses[i].InvocationScript = invScript; + } + break; + } + } + break; + } + var currentHeight = NativeContract.Ledger.CurrentIndex(snapshot); + if (r.IsMainCompleted() && r.minNotValidBefore > currentHeight) { + Finalize(r.mainTx); + r.isSent = true; + } + } + } + + private void OnRequestRemoval(NotaryRequest payload) + { + var r = pendingQueue[payload.MainTransaction.Hash]; + if (r is null) return; + for (int i = 0; i < r.fallbackTxs.Length; i++) + { + var fb = r.fallbackTxs[i]; + if (fb.Hash.Equals(payload.FallbackTransaction.Hash)) + { + var tempList = r.fallbackTxs.ToList(); + tempList.RemoveAt(i); + r.fallbackTxs = tempList.ToArray(); + break; + } + } + if (r.fallbackTxs.Length == 0) pendingQueue.Remove(r.mainTx.Hash, out _); + } + + private void VerifyIncompleteWitnesses(Transaction tx, byte nKeysExpected, out WitnessInfo[] witnessInfos, out string validationErr) + { + int nKeysActual = 0; + witnessInfos = null; + validationErr = null; + if (tx.Signers.Length < 2) + { + validationErr = "transaction should have at least 2 signers"; + return; + } + if (tx.Signers.Any(p => p.Equals(NativeContract.Notary.Hash))) + { + validationErr = "P2PNotary contract should be a signer of the transaction"; + return; + } + witnessInfos = new WitnessInfo[tx.Signers.Length]; + for (int i = 0; i < tx.Witnesses.Length; i++) + { + if (tx.Witnesses[i].VerificationScript.Length == 0) + { + witnessInfos[i] = new WitnessInfo() + { + typ = RequestType.Contract, + nSigsLeft = 0 + }; + continue; + } + if (tx.Signers[i].Account.Equals(tx.Witnesses[i].VerificationScript.ToScriptHash())) + { + validationErr = string.Format("transaction should have valid verification script for signer {0}", i); + witnessInfos = null; + return; + } + if (tx.Witnesses[i].InvocationScript.Length != 0) + { + if (tx.Witnesses[i].InvocationScript.Length != 66 || (tx.Witnesses[i].InvocationScript[0] != (byte)OpCode.PUSHDATA1 && tx.Witnesses[i].InvocationScript[1] != 64)) + { + validationErr = "multisignature invocation script should have length = 66 and be of the form[PUSHDATA1, 64, signatureBytes...]"; + witnessInfos = null; + return; + } + } + if (tx.Witnesses[i].VerificationScript.IsMultiSigContract(out int nSigs, out ECPoint[] pubs)) + { + witnessInfos[i] = new WitnessInfo() + { + typ = RequestType.MultiSignature, + nSigsLeft = (byte)nSigs, + pubs = new ECPoint[pubs.Length], + }; + for (int j = 0; j < pubs.Length; j++) + { + witnessInfos[i].pubs[j] = pubs[j]; + } + nKeysActual += pubs.Length; + continue; + } + if (tx.Witnesses[i].VerificationScript.IsSignatureContract()) + { + witnessInfos[i] = new WitnessInfo() + { + typ = RequestType.Signature, + nSigsLeft = 1, + pubs = new ECPoint[pubs.Length], + }; + nKeysActual++; + continue; + } + validationErr = $"witness {i}: unable to detect witness type, only sig/multisig/contract are supported"; + witnessInfos = null; + return; + } + if (nKeysActual != nKeysExpected) + { + validationErr = $"expected and actual NKeys mismatch: {nKeysExpected} vs {nKeysActual}"; + witnessInfos = null; + return; + } + return; + } + + private bool VerifyWitness(IVerifiable verifiable, ProtocolSettings settings, DataCache snapshot, UInt160 hash, Witness witness, long gas, out long fee) + { + fee = 0; + Script invocationScript; + try + { + invocationScript = new Script(witness.InvocationScript, true); + } + catch (BadScriptException) + { + return false; + } + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.CreateSnapshot(), null, settings, gas)) + { + if (witness.VerificationScript.Length == 0) + { + ContractState cs = NativeContract.ContractManagement.GetContract(snapshot, hash); + if (cs is null) return false; + ContractMethodDescriptor md = cs.Manifest.Abi.GetMethod("verify", -1); + if (md?.ReturnType != ContractParameterType.Boolean) return false; + engine.LoadContract(cs, md, CallFlags.ReadOnly); + } + else + { + if (NativeContract.IsNative(hash)) return false; + if (hash != witness.ScriptHash) return false; + Script verificationScript; + try + { + verificationScript = new Script(witness.VerificationScript, true); + } + catch (BadScriptException) + { + return false; + } + engine.LoadScript(verificationScript, initialPosition: 0, configureState: p => + { + p.CallFlags = CallFlags.ReadOnly; + p.ScriptHash = hash; + }); + } + + engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None); + + if (engine.Execute() == VMState.FAULT) return false; + if (!engine.ResultStack.Peek().GetBoolean()) return false; + fee = engine.GasConsumed; + } + return true; + } + + private void Finalize(Transaction tx) + { + var prefix = new byte[] { (byte)OpCode.PUSHDATA1, 64 }; + var notaryWitness = new Witness() + { + InvocationScript = prefix.Concat(tx.Sign(wallet.GetAccounts().ToArray()[0].GetKey(), Settings.Default.Network)).ToArray(), + VerificationScript = new byte[0] + }; + for (int i = 0; i < tx.Signers.Length; i++) + if (tx.Signers[i].Account.Equals(NativeContract.Notary.Hash)) + { + tx.Witnesses[i] = notaryWitness; + break; + } + neoSystem.Blockchain.Tell(tx); + } + + private static void Log(string message, LogLevel level = LogLevel.Info) + { + Utility.Log(nameof(NotaryService), level, message); + } + + public static Props Props(NeoSystem neoSystem,Wallet wallet) + { + return Akka.Actor.Props.Create(() => new NotaryService(neoSystem,wallet)); + } + + public class NotaryTask + { + public bool isSent; + public Transaction mainTx; + public Transaction[] fallbackTxs; + public uint minNotValidBefore; + public WitnessInfo[] witnessInfo; + + public bool IsMainCompleted() + { + if (witnessInfo is null) return false; + foreach (var wi in witnessInfo) if (wi.nSigsLeft != 0) return false; + return true; + } + } + + public class WitnessInfo + { + public RequestType typ; + public byte nSigsLeft; + public ECPoint[] pubs; + public Dictionary sigs; + } + + public enum RequestType : byte + { + Unknown = 0x00, + Signature = 0x01, + MultiSignature = 0x02, + Contract = 0x03 + } + } +} diff --git a/src/NotaryService/NotaryService.csproj b/src/NotaryService/NotaryService.csproj new file mode 100644 index 000000000..16bba2d08 --- /dev/null +++ b/src/NotaryService/NotaryService.csproj @@ -0,0 +1,22 @@ + + + + Neo.Plugins.NotaryService + Neo.Plugins + + + + + + + + + + + + + PreserveNewest + PreserveNewest + + + diff --git a/src/NotaryService/NotaryService/config.json b/src/NotaryService/NotaryService/config.json new file mode 100644 index 000000000..9a1a313ca --- /dev/null +++ b/src/NotaryService/NotaryService/config.json @@ -0,0 +1,7 @@ +{ + "PluginConfiguration": { + "Capacity": 1000, + "Network": 5195086, + "AutoStart": false + } +} diff --git a/src/NotaryService/Settings.cs b/src/NotaryService/Settings.cs new file mode 100644 index 000000000..cfaaa2faa --- /dev/null +++ b/src/NotaryService/Settings.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Configuration; + +namespace Neo.Plugins +{ + public class Settings + { + public int Capacity { get; } + public uint Network { get; } + public bool AutoStart { get; } + + public static Settings Default { get; private set; } + + public Settings(IConfigurationSection section) + { + Capacity = section.GetValue("Capacity", 1000); + Network = section.GetValue("Network", 5195086u); + AutoStart = section.GetValue("AutoStart", false); + } + + public static void Load(IConfigurationSection section) + { + Default = new Settings(section); + } + } +} From cef729037436c153965ccf58cf57415db631f100 Mon Sep 17 00:00:00 2001 From: doubiliu Date: Wed, 9 Feb 2022 17:24:26 +0800 Subject: [PATCH 02/13] Fix bug --- src/NotaryService/NotaryService.cs | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index d2e975705..3d2b8ae98 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -4,6 +4,7 @@ using Neo.Cryptography.ECC; using Neo.IO; using Neo.Ledger; +using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -21,27 +22,25 @@ namespace Neo.Plugins public class NotaryService : UntypedActor { public class Start { } - private bool started = false; - private Wallet wallet; private NeoSystem neoSystem; + private Wallet wallet; + private bool started; private readonly ConcurrentDictionary pendingQueue = new ConcurrentDictionary(); private readonly List payloadCache = new List(); - public NotaryService(NeoSystem neoSystem,Wallet wallet) + public NotaryService(NeoSystem neoSystem, Wallet wallet) { this.wallet = wallet; this.neoSystem = neoSystem; } - protected override void OnReceive(object message) { if (message is Start) { if (started) return; OnStart(); - Console.WriteLine("Notary Service 启动"); } else { @@ -89,7 +88,8 @@ private void OnPersistCompleted(Block block) { var h = item.Key; var r = item.Value; - if (!r.isSent) { + if (!r.isSent) + { if (r.IsMainCompleted() && r.minNotValidBefore > currHeight) { Finalize(r.mainTx); @@ -132,25 +132,26 @@ private void OnNewRequest(NotaryRequest payload) r = new NotaryTask() { mainTx = payload.MainTransaction, - minNotValidBefore = nvbFallback + minNotValidBefore = nvbFallback, + fallbackTxs = new Transaction[0], }; pendingQueue[payload.MainTransaction.Hash] = r; } if (r.witnessInfo is null && validationErr is null) r.witnessInfo = newInfo; - r.fallbackTxs.Append(payload.FallbackTransaction); + r.fallbackTxs = r.fallbackTxs.Append(payload.FallbackTransaction).ToArray(); r.isSent = false; - if (exists && r.IsMainCompleted() && validationErr != null) + if (exists && r.IsMainCompleted() || validationErr is not null) return; - var mainHash = r.mainTx.Hash.ToArray(); + var mainHash = r.mainTx.GetSignData(neoSystem.Settings.Network); for (int i = 0; i < payload.MainTransaction.Witnesses.Length; i++) { - if (payload.MainTransaction.Witnesses[i].InvocationScript.Length == 0 || r.witnessInfo[i].nSigsLeft == 0 && r.witnessInfo[i].typ == RequestType.Contract) + if (payload.MainTransaction.Witnesses[i].InvocationScript.Length == 0 || r.witnessInfo[i].nSigsLeft == 0 && r.witnessInfo[i].typ != RequestType.Contract) continue; switch (r.witnessInfo[i].typ) { case RequestType.Contract: - if (!VerifyWitness(r.mainTx, ProtocolSettings.Default, snapshot, r.mainTx.Signers[i].Account, payload.MainTransaction.Witnesses[i], r.mainTx.NetworkFee, out _)) + if (!VerifyWitness(r.mainTx, neoSystem.Settings, snapshot, r.mainTx.Signers[i].Account, payload.MainTransaction.Witnesses[i], r.mainTx.NetworkFee, out _)) continue; r.mainTx.Witnesses[i].InvocationScript = payload.MainTransaction.Witnesses[i].InvocationScript; break; @@ -166,7 +167,7 @@ private void OnNewRequest(NotaryRequest payload) r.witnessInfo[i].sigs = new Dictionary(); foreach (var pub in r.witnessInfo[i].pubs) { - if (r.witnessInfo[i].sigs[pub] is not null) + if (r.witnessInfo[i].sigs.TryGetValue(pub, out _)) continue; if (Crypto.VerifySignature(mainHash, payload.MainTransaction.Witnesses[i].InvocationScript.Skip(2).ToArray(), pub)) { @@ -188,7 +189,8 @@ private void OnNewRequest(NotaryRequest payload) break; } var currentHeight = NativeContract.Ledger.CurrentIndex(snapshot); - if (r.IsMainCompleted() && r.minNotValidBefore > currentHeight) { + if (r.IsMainCompleted() && r.minNotValidBefore > currentHeight) + { Finalize(r.mainTx); r.isSent = true; } @@ -240,7 +242,7 @@ private void VerifyIncompleteWitnesses(Transaction tx, byte nKeysExpected, out W }; continue; } - if (tx.Signers[i].Account.Equals(tx.Witnesses[i].VerificationScript.ToScriptHash())) + if (!tx.Signers[i].Account.Equals(tx.Witnesses[i].VerificationScript.ToScriptHash())) { validationErr = string.Format("transaction should have valid verification script for signer {0}", i); witnessInfos = null; @@ -350,7 +352,7 @@ private void Finalize(Transaction tx) var prefix = new byte[] { (byte)OpCode.PUSHDATA1, 64 }; var notaryWitness = new Witness() { - InvocationScript = prefix.Concat(tx.Sign(wallet.GetAccounts().ToArray()[0].GetKey(), Settings.Default.Network)).ToArray(), + InvocationScript = prefix.Concat(tx.Sign(wallet.GetAccounts().ToArray()[0].GetKey(), neoSystem.Settings.Network)).ToArray(), VerificationScript = new byte[0] }; for (int i = 0; i < tx.Signers.Length; i++) @@ -367,9 +369,9 @@ private static void Log(string message, LogLevel level = LogLevel.Info) Utility.Log(nameof(NotaryService), level, message); } - public static Props Props(NeoSystem neoSystem,Wallet wallet) + public static Props Props(NeoSystem neoSystem, Wallet wallet) { - return Akka.Actor.Props.Create(() => new NotaryService(neoSystem,wallet)); + return Akka.Actor.Props.Create(() => new NotaryService(neoSystem, wallet)); } public class NotaryTask From 33dcdeed85db14d93b559aabc8c30ed240d44cbc Mon Sep 17 00:00:00 2001 From: doubiliu Date: Thu, 10 Feb 2022 14:17:59 +0800 Subject: [PATCH 03/13] Format --- neo-modules.sln | 6 ------ src/NotaryService/NotaryService.csproj | 4 ---- 2 files changed, 10 deletions(-) diff --git a/neo-modules.sln b/neo-modules.sln index d57b70653..25f89646b 100644 --- a/neo-modules.sln +++ b/neo-modules.sln @@ -41,8 +41,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Cryptography.MPTTrie.Te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotaryService", "src\NotaryService\NotaryService.csproj", "{DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "neo", "..\..\neo\src\neo\neo.csproj", "{A0E35436-F30E-4B36-B97D-D5C93790EBA7}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -117,10 +115,6 @@ Global {DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95}.Debug|Any CPU.Build.0 = Debug|Any CPU {DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC32B4DA-E8AC-4B43-8E7D-2DE77CAAAF95}.Release|Any CPU.Build.0 = Release|Any CPU - {A0E35436-F30E-4B36-B97D-D5C93790EBA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A0E35436-F30E-4B36-B97D-D5C93790EBA7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0E35436-F30E-4B36-B97D-D5C93790EBA7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A0E35436-F30E-4B36-B97D-D5C93790EBA7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/NotaryService/NotaryService.csproj b/src/NotaryService/NotaryService.csproj index 16bba2d08..43cb6b382 100644 --- a/src/NotaryService/NotaryService.csproj +++ b/src/NotaryService/NotaryService.csproj @@ -9,10 +9,6 @@ - - - - PreserveNewest From bc48474cf2096f7b100bc6924fae26d4fee048d1 Mon Sep 17 00:00:00 2001 From: doubiliu Date: Tue, 15 Feb 2022 15:53:30 +0800 Subject: [PATCH 04/13] fix bug --- src/NotaryService/NotaryService.cs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index 3d2b8ae98..0e8447fbb 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -88,23 +88,19 @@ private void OnPersistCompleted(Block block) { var h = item.Key; var r = item.Value; - if (!r.isSent) + if (!r.isSent && r.IsMainCompleted() && r.minNotValidBefore > currHeight) { - if (r.IsMainCompleted() && r.minNotValidBefore > currHeight) - { - Finalize(r.mainTx); - r.isSent = true; - continue; - } - if (r.minNotValidBefore <= currHeight) + Finalize(r.mainTx); + r.isSent = true; + continue; + } + if (r.minNotValidBefore <= currHeight) + { + var newFallbacks = new List(); + foreach (var fb in r.fallbackTxs) { - var newFallbacks = new List(); - foreach (var fb in r.fallbackTxs) - { - var nvb = fb.GetAttributes().ToArray()[0].Height; - if (nvb <= currHeight) Finalize(fb); - } - r.isSent = true; + var nvb = fb.GetAttributes().ToArray()[0].Height; + if (nvb <= currHeight) Finalize(fb); } } } @@ -140,7 +136,6 @@ private void OnNewRequest(NotaryRequest payload) if (r.witnessInfo is null && validationErr is null) r.witnessInfo = newInfo; r.fallbackTxs = r.fallbackTxs.Append(payload.FallbackTransaction).ToArray(); - r.isSent = false; if (exists && r.IsMainCompleted() || validationErr is not null) return; var mainHash = r.mainTx.GetSignData(neoSystem.Settings.Network); From e6d7df2227847b45aa4bbd2115689f52b756c551 Mon Sep 17 00:00:00 2001 From: doubiliu Date: Tue, 15 Feb 2022 16:08:52 +0800 Subject: [PATCH 05/13] Rename --- src/NotaryService/NotaryService.cs | 120 ++++++++++++++--------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index 0e8447fbb..ed2d27496 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -88,16 +88,16 @@ private void OnPersistCompleted(Block block) { var h = item.Key; var r = item.Value; - if (!r.isSent && r.IsMainCompleted() && r.minNotValidBefore > currHeight) + if (!r.IsSent && r.IsMainCompleted() && r.MinNotValidBefore > currHeight) { - Finalize(r.mainTx); - r.isSent = true; + Finalize(r.MainTx); + r.IsSent = true; continue; } - if (r.minNotValidBefore <= currHeight) + if (r.MinNotValidBefore <= currHeight) { var newFallbacks = new List(); - foreach (var fb in r.fallbackTxs) + foreach (var fb in r.FallbackTxs) { var nvb = fb.GetAttributes().ToArray()[0].Height; if (nvb <= currHeight) Finalize(fb); @@ -117,66 +117,66 @@ private void OnNewRequest(NotaryRequest payload) var exists = pendingQueue.TryGetValue(payload.MainTransaction.Hash, out NotaryTask r); if (exists) { - foreach (var fb in r.fallbackTxs) + foreach (var fb in r.FallbackTxs) if (fb.Hash.Equals(payload.FallbackTransaction.Hash)) return; - if (nvbFallback < r.minNotValidBefore) - r.minNotValidBefore = nvbFallback; + if (nvbFallback < r.MinNotValidBefore) + r.MinNotValidBefore = nvbFallback; } else { r = new NotaryTask() { - mainTx = payload.MainTransaction, - minNotValidBefore = nvbFallback, - fallbackTxs = new Transaction[0], + MainTx = payload.MainTransaction, + MinNotValidBefore = nvbFallback, + FallbackTxs = new Transaction[0], }; pendingQueue[payload.MainTransaction.Hash] = r; } - if (r.witnessInfo is null && validationErr is null) - r.witnessInfo = newInfo; - r.fallbackTxs = r.fallbackTxs.Append(payload.FallbackTransaction).ToArray(); + if (r.WitnessInfo is null && validationErr is null) + r.WitnessInfo = newInfo; + r.FallbackTxs = r.FallbackTxs.Append(payload.FallbackTransaction).ToArray(); if (exists && r.IsMainCompleted() || validationErr is not null) return; - var mainHash = r.mainTx.GetSignData(neoSystem.Settings.Network); + var mainHash = r.MainTx.GetSignData(neoSystem.Settings.Network); for (int i = 0; i < payload.MainTransaction.Witnesses.Length; i++) { - if (payload.MainTransaction.Witnesses[i].InvocationScript.Length == 0 || r.witnessInfo[i].nSigsLeft == 0 && r.witnessInfo[i].typ != RequestType.Contract) + if (payload.MainTransaction.Witnesses[i].InvocationScript.Length == 0 || r.WitnessInfo[i].NSigsLeft == 0 && r.WitnessInfo[i].Typ != RequestType.Contract) continue; - switch (r.witnessInfo[i].typ) + switch (r.WitnessInfo[i].Typ) { case RequestType.Contract: - if (!VerifyWitness(r.mainTx, neoSystem.Settings, snapshot, r.mainTx.Signers[i].Account, payload.MainTransaction.Witnesses[i], r.mainTx.NetworkFee, out _)) + if (!VerifyWitness(r.MainTx, neoSystem.Settings, snapshot, r.MainTx.Signers[i].Account, payload.MainTransaction.Witnesses[i], r.MainTx.NetworkFee, out _)) continue; - r.mainTx.Witnesses[i].InvocationScript = payload.MainTransaction.Witnesses[i].InvocationScript; + r.MainTx.Witnesses[i].InvocationScript = payload.MainTransaction.Witnesses[i].InvocationScript; break; case RequestType.Signature: - if (Crypto.VerifySignature(mainHash, payload.MainTransaction.Witnesses[i].InvocationScript.Skip(2).ToArray(), r.witnessInfo[i].pubs[0])) + if (Crypto.VerifySignature(mainHash, payload.MainTransaction.Witnesses[i].InvocationScript.Skip(2).ToArray(), r.WitnessInfo[i].Pubs[0])) { - r.mainTx.Witnesses[i] = payload.MainTransaction.Witnesses[i]; - r.witnessInfo[i].nSigsLeft--; + r.MainTx.Witnesses[i] = payload.MainTransaction.Witnesses[i]; + r.WitnessInfo[i].NSigsLeft--; } break; case RequestType.MultiSignature: - if (r.witnessInfo[i].sigs is null) - r.witnessInfo[i].sigs = new Dictionary(); - foreach (var pub in r.witnessInfo[i].pubs) + if (r.WitnessInfo[i].Sigs is null) + r.WitnessInfo[i].Sigs = new Dictionary(); + foreach (var pub in r.WitnessInfo[i].Pubs) { - if (r.witnessInfo[i].sigs.TryGetValue(pub, out _)) + if (r.WitnessInfo[i].Sigs.TryGetValue(pub, out _)) continue; if (Crypto.VerifySignature(mainHash, payload.MainTransaction.Witnesses[i].InvocationScript.Skip(2).ToArray(), pub)) { - r.witnessInfo[i].sigs[pub] = payload.MainTransaction.Witnesses[i].InvocationScript; - r.witnessInfo[i].nSigsLeft--; - if (r.witnessInfo[i].nSigsLeft == 0) + r.WitnessInfo[i].Sigs[pub] = payload.MainTransaction.Witnesses[i].InvocationScript; + r.WitnessInfo[i].NSigsLeft--; + if (r.WitnessInfo[i].NSigsLeft == 0) { byte[] invScript = new byte[0]; - for (int j = 0; j < r.witnessInfo[i].pubs.Length; j++) + for (int j = 0; j < r.WitnessInfo[i].Pubs.Length; j++) { - if (r.witnessInfo[i].sigs.TryGetValue(r.witnessInfo[i].pubs[j], out var sig)) + if (r.WitnessInfo[i].Sigs.TryGetValue(r.WitnessInfo[i].Pubs[j], out var sig)) invScript = invScript.Concat(sig).ToArray(); } - r.mainTx.Witnesses[i].InvocationScript = invScript; + r.MainTx.Witnesses[i].InvocationScript = invScript; } break; } @@ -184,10 +184,10 @@ private void OnNewRequest(NotaryRequest payload) break; } var currentHeight = NativeContract.Ledger.CurrentIndex(snapshot); - if (r.IsMainCompleted() && r.minNotValidBefore > currentHeight) + if (r.IsMainCompleted() && r.MinNotValidBefore > currentHeight) { - Finalize(r.mainTx); - r.isSent = true; + Finalize(r.MainTx); + r.IsSent = true; } } } @@ -196,18 +196,18 @@ private void OnRequestRemoval(NotaryRequest payload) { var r = pendingQueue[payload.MainTransaction.Hash]; if (r is null) return; - for (int i = 0; i < r.fallbackTxs.Length; i++) + for (int i = 0; i < r.FallbackTxs.Length; i++) { - var fb = r.fallbackTxs[i]; + var fb = r.FallbackTxs[i]; if (fb.Hash.Equals(payload.FallbackTransaction.Hash)) { - var tempList = r.fallbackTxs.ToList(); + var tempList = r.FallbackTxs.ToList(); tempList.RemoveAt(i); - r.fallbackTxs = tempList.ToArray(); + r.FallbackTxs = tempList.ToArray(); break; } } - if (r.fallbackTxs.Length == 0) pendingQueue.Remove(r.mainTx.Hash, out _); + if (r.FallbackTxs.Length == 0) pendingQueue.Remove(r.MainTx.Hash, out _); } private void VerifyIncompleteWitnesses(Transaction tx, byte nKeysExpected, out WitnessInfo[] witnessInfos, out string validationErr) @@ -232,8 +232,8 @@ private void VerifyIncompleteWitnesses(Transaction tx, byte nKeysExpected, out W { witnessInfos[i] = new WitnessInfo() { - typ = RequestType.Contract, - nSigsLeft = 0 + Typ = RequestType.Contract, + NSigsLeft = 0 }; continue; } @@ -256,13 +256,13 @@ private void VerifyIncompleteWitnesses(Transaction tx, byte nKeysExpected, out W { witnessInfos[i] = new WitnessInfo() { - typ = RequestType.MultiSignature, - nSigsLeft = (byte)nSigs, - pubs = new ECPoint[pubs.Length], + Typ = RequestType.MultiSignature, + NSigsLeft = (byte)nSigs, + Pubs = new ECPoint[pubs.Length], }; for (int j = 0; j < pubs.Length; j++) { - witnessInfos[i].pubs[j] = pubs[j]; + witnessInfos[i].Pubs[j] = pubs[j]; } nKeysActual += pubs.Length; continue; @@ -271,9 +271,9 @@ private void VerifyIncompleteWitnesses(Transaction tx, byte nKeysExpected, out W { witnessInfos[i] = new WitnessInfo() { - typ = RequestType.Signature, - nSigsLeft = 1, - pubs = new ECPoint[pubs.Length], + Typ = RequestType.Signature, + NSigsLeft = 1, + Pubs = new ECPoint[pubs.Length], }; nKeysActual++; continue; @@ -371,26 +371,26 @@ public static Props Props(NeoSystem neoSystem, Wallet wallet) public class NotaryTask { - public bool isSent; - public Transaction mainTx; - public Transaction[] fallbackTxs; - public uint minNotValidBefore; - public WitnessInfo[] witnessInfo; + public bool IsSent; + public Transaction MainTx; + public Transaction[] FallbackTxs; + public uint MinNotValidBefore; + public WitnessInfo[] WitnessInfo; public bool IsMainCompleted() { - if (witnessInfo is null) return false; - foreach (var wi in witnessInfo) if (wi.nSigsLeft != 0) return false; + if (WitnessInfo is null) return false; + foreach (var wi in WitnessInfo) if (wi.NSigsLeft != 0) return false; return true; } } public class WitnessInfo { - public RequestType typ; - public byte nSigsLeft; - public ECPoint[] pubs; - public Dictionary sigs; + public RequestType Typ; + public byte NSigsLeft; + public ECPoint[] Pubs; + public Dictionary Sigs; } public enum RequestType : byte From b37e2c358fdf7f47d827be003d253f4ec79862b6 Mon Sep 17 00:00:00 2001 From: doubiliu Date: Thu, 17 Feb 2022 16:36:53 +0800 Subject: [PATCH 06/13] Update src/NotaryService/NotaryService.cs Co-authored-by: ZhangTao --- src/NotaryService/NotaryService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index ed2d27496..5519f7a59 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -129,7 +129,7 @@ private void OnNewRequest(NotaryRequest payload) { MainTx = payload.MainTransaction, MinNotValidBefore = nvbFallback, - FallbackTxs = new Transaction[0], + FallbackTxs = Array.Empty(), }; pendingQueue[payload.MainTransaction.Hash] = r; } From e77cb9d1d9d4c904018d2d09399b8c96370a9fbc Mon Sep 17 00:00:00 2001 From: doubiliu Date: Thu, 17 Feb 2022 16:37:04 +0800 Subject: [PATCH 07/13] Update src/NotaryService/NotaryService.cs Co-authored-by: ZhangTao --- src/NotaryService/NotaryService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index 5519f7a59..47241641d 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -170,7 +170,7 @@ private void OnNewRequest(NotaryRequest payload) r.WitnessInfo[i].NSigsLeft--; if (r.WitnessInfo[i].NSigsLeft == 0) { - byte[] invScript = new byte[0]; + byte[] invScript = Array.Empty(); for (int j = 0; j < r.WitnessInfo[i].Pubs.Length; j++) { if (r.WitnessInfo[i].Sigs.TryGetValue(r.WitnessInfo[i].Pubs[j], out var sig)) From 2fe8e24db44dbf0205af71328c6659b6410d2865 Mon Sep 17 00:00:00 2001 From: doubiliu Date: Thu, 17 Feb 2022 17:13:28 +0800 Subject: [PATCH 08/13] improve --- src/NotaryService/NotaryService.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index 47241641d..7f628f0a6 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -66,7 +66,7 @@ private void OnStart() private void OnNotaryPayload(NotaryRequest payload) { - if (payloadCache.Count < 100) + if (payloadCache.Count < Settings.Default.Capacity) { payloadCache.Add(payload); OnNewRequest(payload); @@ -76,6 +76,7 @@ private void OnNotaryPayload(NotaryRequest payload) var oldPayload = payloadCache[0]; payloadCache.RemoveAt(0); OnRequestRemoval(oldPayload); + payloadCache.Add(payload); OnNewRequest(payload); } } @@ -162,7 +163,7 @@ private void OnNewRequest(NotaryRequest payload) r.WitnessInfo[i].Sigs = new Dictionary(); foreach (var pub in r.WitnessInfo[i].Pubs) { - if (r.WitnessInfo[i].Sigs.TryGetValue(pub, out _)) + if (r.WitnessInfo[i].Sigs.ContainsKey(pub)) continue; if (Crypto.VerifySignature(mainHash, payload.MainTransaction.Witnesses[i].InvocationScript.Skip(2).ToArray(), pub)) { @@ -239,7 +240,7 @@ private void VerifyIncompleteWitnesses(Transaction tx, byte nKeysExpected, out W } if (!tx.Signers[i].Account.Equals(tx.Witnesses[i].VerificationScript.ToScriptHash())) { - validationErr = string.Format("transaction should have valid verification script for signer {0}", i); + validationErr = $"transaction should have valid verification script for signer {i}"; witnessInfos = null; return; } @@ -258,12 +259,8 @@ private void VerifyIncompleteWitnesses(Transaction tx, byte nKeysExpected, out W { Typ = RequestType.MultiSignature, NSigsLeft = (byte)nSigs, - Pubs = new ECPoint[pubs.Length], + Pubs = pubs, }; - for (int j = 0; j < pubs.Length; j++) - { - witnessInfos[i].Pubs[j] = pubs[j]; - } nKeysActual += pubs.Length; continue; } From d9aca06d122899e40a63bd5506fb99f7576f3936 Mon Sep 17 00:00:00 2001 From: doubiliu Date: Thu, 24 Feb 2022 15:46:00 +0800 Subject: [PATCH 09/13] improve --- src/DBFTPlugin/Consensus/ConsensusService.cs | 2 +- src/NotaryService/NotaryPlugin.cs | 6 +- src/NotaryService/NotaryService.cs | 83 +++----------------- 3 files changed, 14 insertions(+), 77 deletions(-) diff --git a/src/DBFTPlugin/Consensus/ConsensusService.cs b/src/DBFTPlugin/Consensus/ConsensusService.cs index a6130c741..1b2bec595 100644 --- a/src/DBFTPlugin/Consensus/ConsensusService.cs +++ b/src/DBFTPlugin/Consensus/ConsensusService.cs @@ -247,7 +247,7 @@ private bool AddTransaction(Transaction tx, bool verify) { if (verify) { - VerifyResult result = tx.Verify(neoSystem.Settings, context.Snapshot, context.VerificationContext); + VerifyResult result = tx.Verify(neoSystem.Settings, context.Snapshot, context.VerificationContext, context.Transactions.Values); if (result != VerifyResult.Succeed) { Log($"Rejected tx: {tx.Hash}, {result}{Environment.NewLine}{tx.ToArray().ToHexString()}", LogLevel.Warning); diff --git a/src/NotaryService/NotaryPlugin.cs b/src/NotaryService/NotaryPlugin.cs index 29e79890f..8925c4f75 100644 --- a/src/NotaryService/NotaryPlugin.cs +++ b/src/NotaryService/NotaryPlugin.cs @@ -68,12 +68,11 @@ public void Start(Wallet wallet) Console.WriteLine("Please open wallet first!"); return; } - if (!CheckNotaryAvaiblable(System.StoreView, out ECPoint[] notarynodes)) + if (!CheckNotaryAvailable(System.StoreView, out ECPoint[] notarynodes)) { Console.WriteLine("The notary service is unavailable"); return; } - Console.WriteLine("notarynodes:" + notarynodes[0].ToString()); if (!CheckNotaryAccount(wallet, notarynodes)) { Console.WriteLine("There is no notary account in wallet"); @@ -85,10 +84,9 @@ public void Start(Wallet wallet) System.ActorSystem.EventStream.Subscribe(notary, typeof(Blockchain.RelayResult)); notary.Tell(new NotaryService.Start()); started = true; - Console.WriteLine($"Notary started"); } - private static bool CheckNotaryAvaiblable(DataCache snapshot, out ECPoint[] notarynodes) + private static bool CheckNotaryAvailable(DataCache snapshot, out ECPoint[] notarynodes) { uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; notarynodes = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Notary, height); diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index 7f628f0a6..8a7c8c64a 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -27,7 +27,7 @@ public class Start { } private bool started; private readonly ConcurrentDictionary pendingQueue = new ConcurrentDictionary(); - private readonly List payloadCache = new List(); + private readonly Queue payloadCache = new Queue(); public NotaryService(NeoSystem neoSystem, Wallet wallet) { @@ -68,15 +68,14 @@ private void OnNotaryPayload(NotaryRequest payload) { if (payloadCache.Count < Settings.Default.Capacity) { - payloadCache.Add(payload); + payloadCache.Enqueue(payload); OnNewRequest(payload); } else { - var oldPayload = payloadCache[0]; - payloadCache.RemoveAt(0); + var oldPayload = payloadCache.Dequeue(); OnRequestRemoval(oldPayload); - payloadCache.Add(payload); + payloadCache.Enqueue(payload); OnNewRequest(payload); } } @@ -85,10 +84,8 @@ private void OnPersistCompleted(Block block) { Log($"Persisted {nameof(Block)}: height={block.Index} hash={block.Hash} tx={block.Transactions.Length}"); var currHeight = block.Index; - foreach (var item in pendingQueue) + foreach (var (_, r) in pendingQueue) { - var h = item.Key; - var r = item.Value; if (!r.IsSent && r.IsMainCompleted() && r.MinNotValidBefore > currHeight) { Finalize(r.MainTx); @@ -97,7 +94,6 @@ private void OnPersistCompleted(Block block) } if (r.MinNotValidBefore <= currHeight) { - var newFallbacks = new List(); foreach (var fb in r.FallbackTxs) { var nvb = fb.GetAttributes().ToArray()[0].Height; @@ -118,9 +114,7 @@ private void OnNewRequest(NotaryRequest payload) var exists = pendingQueue.TryGetValue(payload.MainTransaction.Hash, out NotaryTask r); if (exists) { - foreach (var fb in r.FallbackTxs) - if (fb.Hash.Equals(payload.FallbackTransaction.Hash)) - return; + if (r.FallbackTxs.Any(fb => fb.Hash.Equals(payload.FallbackTransaction.Hash))) return; if (nvbFallback < r.MinNotValidBefore) r.MinNotValidBefore = nvbFallback; } @@ -147,7 +141,7 @@ private void OnNewRequest(NotaryRequest payload) switch (r.WitnessInfo[i].Typ) { case RequestType.Contract: - if (!VerifyWitness(r.MainTx, neoSystem.Settings, snapshot, r.MainTx.Signers[i].Account, payload.MainTransaction.Witnesses[i], r.MainTx.NetworkFee, out _)) + if (!r.MainTx.VerifyWitness(neoSystem.Settings, snapshot, r.MainTx.Signers[i].Account, payload.MainTransaction.Witnesses[i], r.MainTx.NetworkFee, out _)) continue; r.MainTx.Witnesses[i].InvocationScript = payload.MainTransaction.Witnesses[i].InvocationScript; break; @@ -159,8 +153,7 @@ private void OnNewRequest(NotaryRequest payload) } break; case RequestType.MultiSignature: - if (r.WitnessInfo[i].Sigs is null) - r.WitnessInfo[i].Sigs = new Dictionary(); + r.WitnessInfo[i].Sigs ??= new Dictionary(); foreach (var pub in r.WitnessInfo[i].Pubs) { if (r.WitnessInfo[i].Sigs.ContainsKey(pub)) @@ -172,9 +165,9 @@ private void OnNewRequest(NotaryRequest payload) if (r.WitnessInfo[i].NSigsLeft == 0) { byte[] invScript = Array.Empty(); - for (int j = 0; j < r.WitnessInfo[i].Pubs.Length; j++) + foreach (var t in r.WitnessInfo[i].Pubs) { - if (r.WitnessInfo[i].Sigs.TryGetValue(r.WitnessInfo[i].Pubs[j], out var sig)) + if (r.WitnessInfo[i].Sigs.TryGetValue(t, out var sig)) invScript = invScript.Concat(sig).ToArray(); } r.MainTx.Witnesses[i].InvocationScript = invScript; @@ -288,57 +281,6 @@ private void VerifyIncompleteWitnesses(Transaction tx, byte nKeysExpected, out W return; } - private bool VerifyWitness(IVerifiable verifiable, ProtocolSettings settings, DataCache snapshot, UInt160 hash, Witness witness, long gas, out long fee) - { - fee = 0; - Script invocationScript; - try - { - invocationScript = new Script(witness.InvocationScript, true); - } - catch (BadScriptException) - { - return false; - } - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.CreateSnapshot(), null, settings, gas)) - { - if (witness.VerificationScript.Length == 0) - { - ContractState cs = NativeContract.ContractManagement.GetContract(snapshot, hash); - if (cs is null) return false; - ContractMethodDescriptor md = cs.Manifest.Abi.GetMethod("verify", -1); - if (md?.ReturnType != ContractParameterType.Boolean) return false; - engine.LoadContract(cs, md, CallFlags.ReadOnly); - } - else - { - if (NativeContract.IsNative(hash)) return false; - if (hash != witness.ScriptHash) return false; - Script verificationScript; - try - { - verificationScript = new Script(witness.VerificationScript, true); - } - catch (BadScriptException) - { - return false; - } - engine.LoadScript(verificationScript, initialPosition: 0, configureState: p => - { - p.CallFlags = CallFlags.ReadOnly; - p.ScriptHash = hash; - }); - } - - engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None); - - if (engine.Execute() == VMState.FAULT) return false; - if (!engine.ResultStack.Peek().GetBoolean()) return false; - fee = engine.GasConsumed; - } - return true; - } - private void Finalize(Transaction tx) { var prefix = new byte[] { (byte)OpCode.PUSHDATA1, 64 }; @@ -376,9 +318,7 @@ public class NotaryTask public bool IsMainCompleted() { - if (WitnessInfo is null) return false; - foreach (var wi in WitnessInfo) if (wi.NSigsLeft != 0) return false; - return true; + return WitnessInfo is not null && WitnessInfo.All(wi => wi.NSigsLeft == 0); } } @@ -392,7 +332,6 @@ public class WitnessInfo public enum RequestType : byte { - Unknown = 0x00, Signature = 0x01, MultiSignature = 0x02, Contract = 0x03 From a32b8ade77345ca828f1fd431c9b9185adbb7e2c Mon Sep 17 00:00:00 2001 From: ZhangTao1596 Date: Mon, 7 Mar 2022 14:44:15 +0800 Subject: [PATCH 10/13] add notary request pool --- .../FallbackVerificationContext.cs | 38 +++++++ src/NotaryService/NotaryPlugin.cs | 5 +- src/NotaryService/NotaryRequestComparer.cs | 17 +++ src/NotaryService/NotaryRequestPool.cs | 106 ++++++++++++++++++ src/NotaryService/NotaryService.cs | 26 ++--- 5 files changed, 172 insertions(+), 20 deletions(-) create mode 100644 src/NotaryService/FallbackVerificationContext.cs create mode 100644 src/NotaryService/NotaryRequestComparer.cs create mode 100644 src/NotaryService/NotaryRequestPool.cs diff --git a/src/NotaryService/FallbackVerificationContext.cs b/src/NotaryService/FallbackVerificationContext.cs new file mode 100644 index 000000000..d486dd145 --- /dev/null +++ b/src/NotaryService/FallbackVerificationContext.cs @@ -0,0 +1,38 @@ +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract.Native; +using System.Collections.Generic; +using System.Numerics; + +namespace Neo.Plugins +{ + public class FallbackVerificationContext + { + private readonly Dictionary signerFee = new(); + + public void AddFallback(Transaction tx) + { + var signer = tx.Signers[1].Account; + if (signerFee.TryGetValue(signer, out var value)) + signerFee[signer] = value + tx.SystemFee + tx.NetworkFee; + else + signerFee.Add(signer, tx.SystemFee + tx.NetworkFee); + } + + public bool CheckFallback(Transaction tx, DataCache snapshot) + { + var signer = tx.Signers[1].Account; + BigInteger balance = NativeContract.Notary.BalanceOf(snapshot, signer); + signerFee.TryGetValue(signer, out var totalSenderFeeFromPool); + BigInteger fee = tx.SystemFee + tx.NetworkFee + totalSenderFeeFromPool; + if (balance < fee) return false; + return true; + } + + public void RemoveFallback(Transaction tx) + { + if ((signerFee[tx.Sender] -= tx.SystemFee + tx.NetworkFee) == 0) + signerFee.Remove(tx.Sender); + } + } +} diff --git a/src/NotaryService/NotaryPlugin.cs b/src/NotaryService/NotaryPlugin.cs index 8925c4f75..732a71ac2 100644 --- a/src/NotaryService/NotaryPlugin.cs +++ b/src/NotaryService/NotaryPlugin.cs @@ -2,16 +2,13 @@ using Neo.ConsoleService; using Neo.Cryptography.ECC; using Neo.Ledger; -using Neo.Network.P2P; using Neo.Persistence; -using Neo.Plugins; using Neo.SmartContract.Native; using Neo.Wallets; using System; using System.Linq; -using Settings = Neo.Plugins.Settings; -namespace Neo.Consensus +namespace Neo.Plugins { public class NotaryPlugin : Plugin { diff --git a/src/NotaryService/NotaryRequestComparer.cs b/src/NotaryService/NotaryRequestComparer.cs new file mode 100644 index 000000000..98da86a5f --- /dev/null +++ b/src/NotaryService/NotaryRequestComparer.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Neo.Network.P2P.Payloads; + +namespace Neo.Plugins +{ + public class NotaryRequestComparer : IComparer + { + public int Compare(NotaryRequest x, NotaryRequest y) + { + var r = x.FallbackTransaction.FeePerByte.CompareTo(y.FallbackTransaction.FeePerByte); + if (r != 0) return r; + r = x.FallbackTransaction.NetworkFee.CompareTo(y.FallbackTransaction.NetworkFee); + if (r != 0) return r; + return x.FallbackTransaction.Hash.CompareTo(y.FallbackTransaction.Hash); + } + } +} diff --git a/src/NotaryService/NotaryRequestPool.cs b/src/NotaryService/NotaryRequestPool.cs new file mode 100644 index 000000000..f07b47c67 --- /dev/null +++ b/src/NotaryService/NotaryRequestPool.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Linq; +using Neo.Network.P2P.Payloads; + +namespace Neo.Plugins +{ + public class NotaryRequestPool + { + private readonly int cap; + private readonly NeoSystem neoSystem; + private FallbackVerificationContext context = new(); + private readonly Dictionary> tasks = new(); + private readonly Dictionary requests = new(); + private readonly SortedSet sortedRequests = new(new NotaryRequestComparer()); + + public NotaryRequestPool(NeoSystem system, int capacity) + { + neoSystem = system; + cap = capacity; + } + + public bool TryAdd(NotaryRequest request, out NotaryRequest removed) + { + removed = null; + if (requests.ContainsKey(request.FallbackTransaction.Hash)) return false; + if (!request.VerifyStateDependent(neoSystem.Settings, neoSystem.StoreView)) return false; + if (!context.CheckFallback(request.FallbackTransaction, neoSystem.StoreView)) return false; + if (!CheckConflicts(request)) return false; + context.AddFallback(request.FallbackTransaction); + requests[request.FallbackTransaction.Hash] = request; + sortedRequests.Add(request); + if (tasks.TryGetValue(request.MainTransaction.Hash, out var fallbacks)) + fallbacks.Add(request.FallbackTransaction.Hash); + else + tasks[request.MainTransaction.Hash] = new() { request.FallbackTransaction.Hash }; + if (requests.Count > cap) removed = RemoveOverCapacity(); + return true; + } + + private bool CheckConflicts(NotaryRequest request) + { + var fallbackTx = request.FallbackTransaction; + var mainTx = request.MainTransaction; + foreach (var r in tasks.Values) + { + var tx = requests[r.First()].MainTransaction; + foreach (var conflict in tx.GetAttributes()) + { + if (conflict.Hash == fallbackTx.Hash && tx.Signers.Any(p => p.Account == fallbackTx.Signers[1].Account)) return false; + if (conflict.Hash == mainTx.Hash && tx.Signers.Any(p => p.Account == mainTx.Sender)) return false; + } + } + return true; + } + + private NotaryRequest RemoveOverCapacity() + { + var r = sortedRequests.First(); + if (r is not null) + { + requests.Remove(r.FallbackTransaction.Hash); + tasks[r.MainTransaction.Hash].Remove(r.FallbackTransaction.Hash); + context.RemoveFallback(r.FallbackTransaction); + sortedRequests.Remove(r); + } + return r; + } + + public List ReVerify(Transaction[] txs) + { + List unverified, removed = new(); + lock (requests) + { + foreach (var tx in txs) + { + if (tasks.TryGetValue(tx.Hash, out var fallbacks)) + { + foreach (var fb in fallbacks) + { + removed.Add(requests[fb]); + requests.Remove(fb); + } + tasks.Remove(tx.Hash); + } + else if (requests.ContainsKey(tx.Hash)) + { + tasks[requests[tx.Hash].MainTransaction.Hash].Remove(tx.Hash); + removed.Add(requests[tx.Hash]); + requests.Remove(tx.Hash); + } + } + unverified = requests.Values.ToList(); + requests.Clear(); + tasks.Clear(); + sortedRequests.Clear(); + context = new FallbackVerificationContext(); + } + foreach (var r in unverified) + { + if (!TryAdd(r, out _)) + removed.Add(r); + } + return removed; + } + } +} diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index 8a7c8c64a..8a45d43b9 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -6,9 +6,7 @@ using Neo.Ledger; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; -using Neo.Persistence; using Neo.SmartContract; -using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; using Neo.VM; using Neo.Wallets; @@ -22,17 +20,18 @@ namespace Neo.Plugins public class NotaryService : UntypedActor { public class Start { } - private NeoSystem neoSystem; - private Wallet wallet; + private readonly NeoSystem neoSystem; + private readonly Wallet wallet; private bool started; - - private readonly ConcurrentDictionary pendingQueue = new ConcurrentDictionary(); - private readonly Queue payloadCache = new Queue(); + private readonly NotaryRequestPool pool; + private readonly ConcurrentDictionary pendingQueue = new(); + private readonly Queue payloadCache = new(); public NotaryService(NeoSystem neoSystem, Wallet wallet) { this.wallet = wallet; this.neoSystem = neoSystem; + pool = new(neoSystem, Settings.Default.Capacity); } protected override void OnReceive(object message) @@ -66,17 +65,10 @@ private void OnStart() private void OnNotaryPayload(NotaryRequest payload) { - if (payloadCache.Count < Settings.Default.Capacity) - { - payloadCache.Enqueue(payload); - OnNewRequest(payload); - } - else + if (pool.TryAdd(payload, out var removed)) { - var oldPayload = payloadCache.Dequeue(); - OnRequestRemoval(oldPayload); - payloadCache.Enqueue(payload); OnNewRequest(payload); + if (removed is not null) OnRequestRemoval(removed); } } @@ -101,6 +93,8 @@ private void OnPersistCompleted(Block block) } } } + foreach (var n in pool.ReVerify(block.Transactions)) + OnRequestRemoval(n); } private void OnNewRequest(NotaryRequest payload) From 325470b98b42bfb80fd715fb569770fa70a69e00 Mon Sep 17 00:00:00 2001 From: ZhangTao1596 Date: Mon, 7 Mar 2022 17:15:58 +0800 Subject: [PATCH 11/13] fix --- src/NotaryService/NotaryService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index 8a45d43b9..30a85a852 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -75,6 +75,8 @@ private void OnNotaryPayload(NotaryRequest payload) private void OnPersistCompleted(Block block) { Log($"Persisted {nameof(Block)}: height={block.Index} hash={block.Hash} tx={block.Transactions.Length}"); + foreach (var n in pool.ReVerify(block.Transactions)) + OnRequestRemoval(n); var currHeight = block.Index; foreach (var (_, r) in pendingQueue) { @@ -93,8 +95,6 @@ private void OnPersistCompleted(Block block) } } } - foreach (var n in pool.ReVerify(block.Transactions)) - OnRequestRemoval(n); } private void OnNewRequest(NotaryRequest payload) From 0f88bc993fd67578468fe2e6975a7b9f2acd3b90 Mon Sep 17 00:00:00 2001 From: ZhangTao1596 Date: Tue, 8 Mar 2022 10:54:48 +0800 Subject: [PATCH 12/13] clean --- src/NotaryService/NotaryService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NotaryService/NotaryService.cs b/src/NotaryService/NotaryService.cs index 30a85a852..81887cd85 100644 --- a/src/NotaryService/NotaryService.cs +++ b/src/NotaryService/NotaryService.cs @@ -25,7 +25,6 @@ public class Start { } private bool started; private readonly NotaryRequestPool pool; private readonly ConcurrentDictionary pendingQueue = new(); - private readonly Queue payloadCache = new(); public NotaryService(NeoSystem neoSystem, Wallet wallet) { From da0a40e352cf7fca05ef9c95abafc3e30bdb320b Mon Sep 17 00:00:00 2001 From: ZhangTao1596 Date: Wed, 23 Mar 2022 17:36:18 +0800 Subject: [PATCH 13/13] fix remove fallback --- src/NotaryService/FallbackVerificationContext.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NotaryService/FallbackVerificationContext.cs b/src/NotaryService/FallbackVerificationContext.cs index d486dd145..ba14db5eb 100644 --- a/src/NotaryService/FallbackVerificationContext.cs +++ b/src/NotaryService/FallbackVerificationContext.cs @@ -31,7 +31,8 @@ public bool CheckFallback(Transaction tx, DataCache snapshot) public void RemoveFallback(Transaction tx) { - if ((signerFee[tx.Sender] -= tx.SystemFee + tx.NetworkFee) == 0) + var signer = tx.Signers[1].Account; + if ((signerFee[signer] -= tx.SystemFee + tx.NetworkFee) == 0) signerFee.Remove(tx.Sender); } }