From 6da4ae28163d84f93321919a51e6bb4927a674ca Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 11:17:19 +0300 Subject: [PATCH 01/10] Implement NotaryAssisted transaction attribute Close #2896. Use a stub for native Notary contract hash since this contract is not implemented yet. Thus, technically, NotaryAssisted attribute verification will always fail on real network until native Notary is implemented. Signed-off-by: Anna Shaleva --- src/Neo.CLI/CLI/MainService.Blockchain.cs | 4 + .../Network/P2P/Payloads/NotaryAssisted.cs | 63 +++++++++++++ .../P2P/Payloads/TransactionAttributeType.cs | 8 +- .../SmartContract/Native/PolicyContract.cs | 6 ++ .../Network/P2P/Payloads/UT_NotaryAssisted.cs | 88 +++++++++++++++++++ 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/Neo/Network/P2P/Payloads/NotaryAssisted.cs create mode 100644 tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs diff --git a/src/Neo.CLI/CLI/MainService.Blockchain.cs b/src/Neo.CLI/CLI/MainService.Blockchain.cs index 4f896d63e3..9f5d40daa7 100644 --- a/src/Neo.CLI/CLI/MainService.Blockchain.cs +++ b/src/Neo.CLI/CLI/MainService.Blockchain.cs @@ -212,6 +212,10 @@ public void OnShowTransactionCommand(UInt256 hash) ConsoleHelper.Info("", " Type: ", $"{n.Type}"); ConsoleHelper.Info("", " Height: ", $"{n.Height}"); break; + case NotaryAssisted n: + ConsoleHelper.Info("", " Type: ", $"{n.Type}"); + ConsoleHelper.Info("", " NKeys: ", $"{n.NKeys}"); + break; default: ConsoleHelper.Info("", " Type: ", $"{attribute.Type}"); ConsoleHelper.Info("", " Size: ", $"{attribute.Size} Byte(s)"); diff --git a/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs b/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs new file mode 100644 index 0000000000..51ff6959dc --- /dev/null +++ b/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs @@ -0,0 +1,63 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NotaryAssisted.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository 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; +using Neo.Json; +using Neo.Persistence; +using Neo.SmartContract.Native; +using System.IO; +using System.Linq; + +namespace Neo.Network.P2P.Payloads +{ + public class NotaryAssisted : TransactionAttribute + { + /// + /// Indicates the number of keys participating in the transaction (main or fallback) signing process. + /// + public byte NKeys; + + public override TransactionAttributeType Type => TransactionAttributeType.NotaryAssisted; + + public override bool AllowMultiple => false; + + public override int Size => base.Size + sizeof(byte); + + protected override void DeserializeWithoutType(ref MemoryReader reader) + { + NKeys = reader.ReadByte(); + } + + protected override void SerializeWithoutType(BinaryWriter writer) + { + writer.Write(NKeys); + } + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["nkeys"] = NKeys; + return json; + } + + public override bool Verify(DataCache snapshot, Transaction tx) + { + // Stub native Notary contract related check until the contract is implemented. + UInt160 notaryH = new UInt160(); + return tx.Signers.Any(p => p.Account.Equals(notaryH)); + } + + public override long CalculateNetworkFee(DataCache snapshot, Transaction tx) + { + return (NKeys + 1) * base.CalculateNetworkFee(snapshot, tx); + } + } +} diff --git a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs index 116f136c07..192c90fdd5 100644 --- a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs +++ b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs @@ -40,6 +40,12 @@ public enum TransactionAttributeType : byte /// Indicates that the transaction conflicts with . /// [ReflectionCache(typeof(Conflicts))] - Conflicts = 0x21 + Conflicts = 0x21, + + /// + /// Indicates that the transaction is aimed to service notary request with . + /// + [ReflectionCache(typeof(NotaryAssisted))] + NotaryAssisted = 0x22 } } diff --git a/src/Neo/SmartContract/Native/PolicyContract.cs b/src/Neo/SmartContract/Native/PolicyContract.cs index 5b00af92ad..3ee5151aa8 100644 --- a/src/Neo/SmartContract/Native/PolicyContract.cs +++ b/src/Neo/SmartContract/Native/PolicyContract.cs @@ -43,6 +43,11 @@ public sealed class PolicyContract : NativeContract /// public const uint DefaultAttributeFee = 0; + /// + /// The default fee for NotaryAssisted attribute. + /// + public const uint DefaultNotaryAssistedAttributeFee = 1000_0000; + /// /// The maximum execution fee factor that the committee can set. /// @@ -73,6 +78,7 @@ internal override ContractTask Initialize(ApplicationEngine engine, Hardfork? ha engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); + engine.Snapshot.Add(CreateStorageKey(Prefix_AttributeFee).Add((byte)TransactionAttributeType.NotaryAssisted), new StorageItem(DefaultNotaryAssistedAttributeFee)); } return ContractTask.CompletedTask; } diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs new file mode 100644 index 0000000000..b5894548f2 --- /dev/null +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs @@ -0,0 +1,88 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NotaryAssisted.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository 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 FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using System; + +namespace Neo.UnitTests.Network.P2P.Payloads +{ + [TestClass] + public class UT_NotaryAssisted + { + [TestMethod] + public void Size_Get() + { + var attr = new NotaryAssisted() { NKeys = 4 }; + attr.Size.Should().Be(1 + 1); + } + + [TestMethod] + public void ToJson() + { + var attr = new NotaryAssisted() { NKeys = 4 }; + var json = attr.ToJson().ToString(); + Assert.AreEqual(@"{""type"":""NotaryAssisted"",""nkeys"":4}", json); + } + + [TestMethod] + public void DeserializeAndSerialize() + { + var attr = new NotaryAssisted() { NKeys = 4 }; + + var clone = attr.ToArray().AsSerializable(); + Assert.AreEqual(clone.Type, attr.Type); + + // As transactionAttribute + byte[] buffer = attr.ToArray(); + var reader = new MemoryReader(buffer); + clone = TransactionAttribute.DeserializeFrom(ref reader) as NotaryAssisted; + Assert.AreEqual(clone.Type, attr.Type); + + // Wrong type + buffer[0] = 0xff; + Assert.ThrowsException(() => + { + var reader = new MemoryReader(buffer); + TransactionAttribute.DeserializeFrom(ref reader); + }); + } + + [TestMethod] + public void Verify() + { + var attr = new NotaryAssisted() { NKeys = 4 }; + + // Temporary use Notary contract hash stub for valid transaction. + var txGood = new Transaction { Signers = new Signer[] { new Signer() { Account = UInt160.Zero } } }; + var txBad = new Transaction { Signers = new Signer[] { new Signer() { Account = UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01") } } }; + var snapshot = TestBlockchain.GetTestSnapshot(); + + Assert.IsTrue(attr.Verify(snapshot, txGood)); + Assert.IsFalse(attr.Verify(snapshot, txBad)); + } + + [TestMethod] + public void CalculateNetworkFee() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + var attr = new NotaryAssisted() { NKeys = 4 }; + var tx = new Transaction { Signers = new Signer[] { new Signer() { Account = UInt160.Zero } } }; + + Assert.AreEqual((4 + 1) * 1000_0000, attr.CalculateNetworkFee(snapshot, tx)); + } + } +} From acec1b02509d2fed9499eade0354490d226ee876 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 17:54:51 +0300 Subject: [PATCH 02/10] Payloads: add doc to CalculateNetworkFee method of NotaryAssisted attribute Signed-off-by: Anna Shaleva --- src/Neo/Network/P2P/Payloads/NotaryAssisted.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs b/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs index 51ff6959dc..3ae10de198 100644 --- a/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs +++ b/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs @@ -55,6 +55,16 @@ public override bool Verify(DataCache snapshot, Transaction tx) return tx.Signers.Any(p => p.Account.Equals(notaryH)); } + /// + /// Calculates the network fee needed to pay for NotaryAssisted attribute. According to the + /// https://github.com/neo-project/neo/issues/1573#issuecomment-704874472, network fee consists of + /// the base Notary service fee per key multiplied by the expected number of transactions that should + /// be collected by the service to complete Notary request increased by one (for Notary node witness + /// itself). + /// + /// The snapshot used to read data. + /// The transaction to calculate. + /// The network fee of the NotaryAssisted attribute. public override long CalculateNetworkFee(DataCache snapshot, Transaction tx) { return (NKeys + 1) * base.CalculateNetworkFee(snapshot, tx); From 1508d4f48f6a0ec894d83c8b2097935bc1bdff7d Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 7 Mar 2024 12:27:04 +0300 Subject: [PATCH 03/10] Native: add NotaryAssisted attributes handler to Gas OnPersist Transactions network fee should be split between Primary node and Notary nodes. Signed-off-by: Anna Shaleva --- src/Neo/SmartContract/Native/GasToken.cs | 8 ++ .../SmartContract/Native/UT_GasToken.cs | 74 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/Neo/SmartContract/Native/GasToken.cs b/src/Neo/SmartContract/Native/GasToken.cs index ce2f77ad2c..fede070d2e 100644 --- a/src/Neo/SmartContract/Native/GasToken.cs +++ b/src/Neo/SmartContract/Native/GasToken.cs @@ -43,6 +43,14 @@ internal override async ContractTask OnPersist(ApplicationEngine engine) { await Burn(engine, tx.Sender, tx.SystemFee + tx.NetworkFee); totalNetworkFee += tx.NetworkFee; + + // Reward for NotaryAssisted attribute will be minted to designated notary nodes + // by Notary contract. + var notaryAssisted = tx.GetAttribute(); + if (notaryAssisted is not null) + { + totalNetworkFee -= (notaryAssisted.NKeys + 1) * Policy.GetAttributeFee(engine.Snapshot, (byte)notaryAssisted.Type); + } } ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot, engine.ProtocolSettings.ValidatorsCount); UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.PersistingBlock.PrimaryIndex]).ToScriptHash(); diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs index 51617b6744..b1a1e38b79 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs @@ -10,17 +10,35 @@ // modifications are permitted. using FluentAssertions; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.IO; using Neo.IO; using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.Persistence; using Neo.SmartContract; +using Neo.SmartContract; +using Neo.SmartContract.Native; using Neo.SmartContract.Native; using Neo.UnitTests.Extensions; +using Neo.UnitTests.Extensions; +using Neo.VM; +using Neo.Wallets; +using System; +using System; using System; using System.Linq; +using System.Linq; +using System.Numerics; +using System.Numerics; using System.Numerics; using System.Threading.Tasks; +using VMTypes = Neo.VM.Types; +// using VMArray = Neo.VM.Types.Array; namespace Neo.UnitTests.SmartContract.Native { @@ -151,5 +169,61 @@ internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) Key = buffer }; } + + [TestMethod] + public void Check_OnPersist_NotaryAssisted() + { + // Hardcode test values. + const uint defaultNotaryssestedFeePerKey = 1000_0000; + const byte NKeys1 = 4; + const byte NKeys2 = 6; + + // Generate two transactions with NotaryAssisted attributes with hardcoded NKeys values. + var from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators); + var tx1 = TestUtils.GetTransaction(from); + tx1.Attributes = new TransactionAttribute[] { new NotaryAssisted() { NKeys = NKeys1 } }; + var netFee1 = 1_0000_0000; + tx1.NetworkFee = netFee1; + var tx2 = TestUtils.GetTransaction(from); + tx2.Attributes = new TransactionAttribute[] { new NotaryAssisted() { NKeys = NKeys2 } }; + var netFee2 = 2_0000_0000; + tx2.NetworkFee = netFee2; + + // Calculate expected Notary nodes reward. + var expectedNotaryReward = (NKeys1 + 1) * defaultNotaryssestedFeePerKey + (NKeys2 + 1) * defaultNotaryssestedFeePerKey; + + // Build block to check transaction fee distribution during Gas OnPersist. + var persistingBlock = new Block + { + Header = new Header + { + Index = (uint)TestProtocolSettings.Default.CommitteeMembersCount, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + }, + Transactions = new Transaction[] { tx1, tx2 } + }; + var snapshot = _snapshot.CreateSnapshot(); + var script = new ScriptBuilder(); + script.EmitSysCall(ApplicationEngine.System_Contract_NativeOnPersist); + var engine = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + + // Check that block's Primary balance is 0. + ECPoint[] validators = NativeContract.NEO.GetNextBlockValidators(engine.Snapshot, engine.ProtocolSettings.ValidatorsCount); + var primary = Contract.CreateSignatureRedeemScript(validators[engine.PersistingBlock.PrimaryIndex]).ToScriptHash(); + NativeContract.GAS.BalanceOf(engine.Snapshot, primary).Should().Be(0); + + // Execute OnPersist script. + engine.LoadScript(script.ToArray()); + Assert.IsTrue(engine.Execute() == VMState.HALT); + + // Check that proper amount of GAS was minted to block's Primary and the rest + // will be minted to Notary nodes as a reward once Notary contract is implemented. + Assert.AreEqual(2 + 1, engine.Notifications.Count()); // burn tx1 and tx2 network fee + mint primary reward + Assert.AreEqual(netFee1 + netFee2 - expectedNotaryReward, engine.Notifications[2].State[2]); + NativeContract.GAS.BalanceOf(engine.Snapshot, primary).Should().Be(netFee1 + netFee2 - expectedNotaryReward); + } } } From 8b547c9c59cc4d32a87a6fc1e35b8a1d97907f15 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 7 Mar 2024 12:31:16 +0300 Subject: [PATCH 04/10] Payloads: adjust comment to NotaryAssisted attribute Signed-off-by: Anna Shaleva --- src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs index 192c90fdd5..276ef7710f 100644 --- a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs +++ b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs @@ -43,7 +43,7 @@ public enum TransactionAttributeType : byte Conflicts = 0x21, /// - /// Indicates that the transaction is aimed to service notary request with . + /// Indicates that the transaction uses notary request service with . /// [ReflectionCache(typeof(NotaryAssisted))] NotaryAssisted = 0x22 From b16a28c4348857acd3e10f2379c3463a3da90f8e Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 7 Mar 2024 12:38:59 +0300 Subject: [PATCH 05/10] Payloads: temporary use hard-coded Notary contract hash Once Notary contract is implemented, this hash will be replaced by a proper Notary contract hash. The exact value won't be changed since Notary contract has constant hash as any other native contract. Signed-off-by: Anna Shaleva --- src/Neo/Network/P2P/Payloads/NotaryAssisted.cs | 9 ++++++--- .../Network/P2P/Payloads/UT_NotaryAssisted.cs | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs b/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs index 3ae10de198..bdf7fb7552 100644 --- a/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs +++ b/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs @@ -20,6 +20,11 @@ namespace Neo.Network.P2P.Payloads { public class NotaryAssisted : TransactionAttribute { + /// + /// Native Notary contract hash stub used until native Notary contract is properly implemented. + /// + private static readonly UInt160 notaryHash = UInt160.Parse("0xc1e14f19c3e60d0b9244d06dd7ba9b113135ec3b"); + /// /// Indicates the number of keys participating in the transaction (main or fallback) signing process. /// @@ -50,9 +55,7 @@ public override JObject ToJson() public override bool Verify(DataCache snapshot, Transaction tx) { - // Stub native Notary contract related check until the contract is implemented. - UInt160 notaryH = new UInt160(); - return tx.Signers.Any(p => p.Account.Equals(notaryH)); + return tx.Signers.Any(p => p.Account.Equals(notaryHash)); } /// diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs index b5894548f2..939a368c33 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs @@ -23,6 +23,8 @@ namespace Neo.UnitTests.Network.P2P.Payloads [TestClass] public class UT_NotaryAssisted { + private static readonly UInt160 notaryHash = UInt160.Parse("0xc1e14f19c3e60d0b9244d06dd7ba9b113135ec3b"); + [TestMethod] public void Size_Get() { @@ -67,7 +69,7 @@ public void Verify() var attr = new NotaryAssisted() { NKeys = 4 }; // Temporary use Notary contract hash stub for valid transaction. - var txGood = new Transaction { Signers = new Signer[] { new Signer() { Account = UInt160.Zero } } }; + var txGood = new Transaction { Signers = new Signer[] { new Signer() { Account = notaryHash } } }; var txBad = new Transaction { Signers = new Signer[] { new Signer() { Account = UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01") } } }; var snapshot = TestBlockchain.GetTestSnapshot(); @@ -80,7 +82,7 @@ public void CalculateNetworkFee() { var snapshot = TestBlockchain.GetTestSnapshot(); var attr = new NotaryAssisted() { NKeys = 4 }; - var tx = new Transaction { Signers = new Signer[] { new Signer() { Account = UInt160.Zero } } }; + var tx = new Transaction { Signers = new Signer[] { new Signer() { Account = notaryHash } } }; Assert.AreEqual((4 + 1) * 1000_0000, attr.CalculateNetworkFee(snapshot, tx)); } From 24135ff59ceb3554f39ca30f5cfec68624e2571b Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 12 Mar 2024 22:14:29 +0300 Subject: [PATCH 06/10] Payloads: replace hard-coded Notary hash value with calculated one No functional changes, just a refactoring for better code readability. Signed-off-by: Anna Shaleva --- src/Neo/Network/P2P/Payloads/NotaryAssisted.cs | 2 +- tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs b/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs index bdf7fb7552..0a110688a8 100644 --- a/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs +++ b/src/Neo/Network/P2P/Payloads/NotaryAssisted.cs @@ -23,7 +23,7 @@ public class NotaryAssisted : TransactionAttribute /// /// Native Notary contract hash stub used until native Notary contract is properly implemented. /// - private static readonly UInt160 notaryHash = UInt160.Parse("0xc1e14f19c3e60d0b9244d06dd7ba9b113135ec3b"); + private static readonly UInt160 notaryHash = Neo.SmartContract.Helper.GetContractHash(UInt160.Zero, 0, "Notary"); /// /// Indicates the number of keys participating in the transaction (main or fallback) signing process. diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs index 939a368c33..778fe7a53d 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotaryAssisted.cs @@ -23,6 +23,7 @@ namespace Neo.UnitTests.Network.P2P.Payloads [TestClass] public class UT_NotaryAssisted { + // Use the hard-coded Notary hash value from NeoGo to ensure hashes are compatible. private static readonly UInt160 notaryHash = UInt160.Parse("0xc1e14f19c3e60d0b9244d06dd7ba9b113135ec3b"); [TestMethod] From fbf5eae88b3cb3b2dd2fe9f7eae36cb977648d50 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 5 Jun 2024 19:54:43 +0300 Subject: [PATCH 07/10] NeoModules: integrate NotaryAssisted attribute Port the https://github.com/neo-project/neo-modules/pull/884. Signed-off-by: Anna Shaleva --- src/Plugins/RpcClient/Utility.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Plugins/RpcClient/Utility.cs b/src/Plugins/RpcClient/Utility.cs index 659942f8f8..eba6f0e7c9 100644 --- a/src/Plugins/RpcClient/Utility.cs +++ b/src/Plugins/RpcClient/Utility.cs @@ -206,6 +206,10 @@ public static TransactionAttribute TransactionAttributeFromJson(JObject json) { Hash = UInt256.Parse(json["hash"].AsString()) }, + TransactionAttributeType.NotaryAssisted => new NotaryAssisted() + { + NKeys = (byte)json["nkeys"].AsNumber() + }, _ => throw new FormatException(), }; } From 151f859ea0a9089d1efad15ba1620e4fbacc37b6 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 10 Jun 2024 19:41:26 +0300 Subject: [PATCH 08/10] Payloads: fix XML comment formatting Signed-off-by: Anna Shaleva --- src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs index 276ef7710f..7e7f4f4252 100644 --- a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs +++ b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs @@ -43,7 +43,7 @@ public enum TransactionAttributeType : byte Conflicts = 0x21, /// - /// Indicates that the transaction uses notary request service with . + /// Indicates that the transaction uses notary request service with number of keys. /// [ReflectionCache(typeof(NotaryAssisted))] NotaryAssisted = 0x22 From c229cd5939ed2f535a73fe6fa9e9bac97ea3a988 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 11 Jun 2024 09:37:07 +0300 Subject: [PATCH 09/10] P2P: move NotaryAssisted transaction attribute under D hardfork Signed-off-by: Anna Shaleva --- src/Neo/Network/P2P/Payloads/Transaction.cs | 7 +- .../SmartContract/Native/PolicyContract.cs | 76 +++++++++++++++++-- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs index b922674d91..8fc771ce7c 100644 --- a/src/Neo/Network/P2P/Payloads/Transaction.cs +++ b/src/Neo/Network/P2P/Payloads/Transaction.cs @@ -370,10 +370,13 @@ public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, Data if (!(context?.CheckTransaction(this, conflictsList, snapshot) ?? true)) return VerifyResult.InsufficientFunds; long attributesFee = 0; foreach (TransactionAttribute attribute in Attributes) + { if (!attribute.Verify(snapshot, this)) return VerifyResult.InvalidAttribute; - else - attributesFee += attribute.CalculateNetworkFee(snapshot, this); + if (attribute.Type == TransactionAttributeType.NotaryAssisted && !settings.IsHardforkEnabled(Hardfork.HF_Domovoi, height)) + return VerifyResult.InvalidAttribute; + attributesFee += attribute.CalculateNetworkFee(snapshot, this); + } long netFeeDatoshi = NetworkFee - (Size * NativeContract.Policy.GetFeePerByte(snapshot)) - attributesFee; if (netFeeDatoshi < 0) return VerifyResult.InsufficientFunds; diff --git a/src/Neo/SmartContract/Native/PolicyContract.cs b/src/Neo/SmartContract/Native/PolicyContract.cs index adeb2f1c19..7dcabe2771 100644 --- a/src/Neo/SmartContract/Native/PolicyContract.cs +++ b/src/Neo/SmartContract/Native/PolicyContract.cs @@ -79,6 +79,9 @@ internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfor engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); + } + if (hardfork == Hardfork.HF_Domovoi) + { engine.Snapshot.Add(CreateStorageKey(Prefix_AttributeFee).Add((byte)TransactionAttributeType.NotaryAssisted), new StorageItem(DefaultNotaryAssistedAttributeFee)); } return ContractTask.CompletedTask; @@ -118,15 +121,41 @@ public uint GetStoragePrice(DataCache snapshot) } /// - /// Gets the fee for attribute. + /// Gets the fee for attribute before Domovoi hardfork. + /// + /// The snapshot used to read data. + /// Attribute type excluding + /// The fee for attribute. + [ContractMethod(Hardfork.HF_Domovoi, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates, Name = "getAttributeFee")] + public uint GetAttributeFeeV0(DataCache snapshot, byte attributeType) + { + return GetAttributeFee(snapshot, attributeType, false); + } + + /// + /// Gets the fee for attribute after Domovoi hardfork. /// /// The snapshot used to read data. /// Attribute type /// The fee for attribute. - [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] + [ContractMethod(true, Hardfork.HF_Domovoi, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint GetAttributeFee(DataCache snapshot, byte attributeType) { - if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType)) throw new InvalidOperationException(); + return GetAttributeFee(snapshot, attributeType, true); + } + + /// + /// Generic handler for GetAttributeFeeV0 and GetAttributeFeeV1 that + /// gets the fee for attribute. + /// + /// The snapshot used to read data. + /// Attribute type + /// Whether to support attribute type. + /// The fee for attribute. + private uint GetAttributeFee(DataCache snapshot, byte attributeType, bool allowNotaryAssisted) + { + if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType) || (!allowNotaryAssisted && attributeType == (byte)(TransactionAttributeType.NotaryAssisted))) + throw new InvalidOperationException(); StorageItem entry = snapshot.TryGet(CreateStorageKey(Prefix_AttributeFee).Add(attributeType)); if (entry == null) return DefaultAttributeFee; @@ -145,10 +174,45 @@ public bool IsBlocked(DataCache snapshot, UInt160 account) return snapshot.Contains(CreateStorageKey(Prefix_BlockedAccount).Add(account)); } - [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] - private void SetAttributeFee(ApplicationEngine engine, byte attributeType, uint value) + /// + /// Sets the fee for attribute before Domovoi hardfork. + /// + /// The engine used to check committee witness and read data. + /// Attribute type excluding + /// Attribute fee value + /// The fee for attribute. + [ContractMethod(Hardfork.HF_Domovoi, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States, Name = "setAttributeFee")] + private void SetAttributeFeeV0(ApplicationEngine engine, byte attributeType, uint value) + { + SetAttributeFee(engine, attributeType, value, false); + } + + /// + /// Sets the fee for attribute after Domovoi hardfork. + /// + /// The engine used to check committee witness and read data. + /// Attribute type excluding + /// Attribute fee value + /// The fee for attribute. + [ContractMethod(true, Hardfork.HF_Domovoi, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States, Name = "setAttributeFee")] + private void SetAttributeFeeV1(ApplicationEngine engine, byte attributeType, uint value) + { + SetAttributeFee(engine, attributeType, value, true); + } + + /// + /// Generic handler for SetAttributeFeeV0 and SetAttributeFeeV1 that + /// gets the fee for attribute. + /// + /// The engine used to check committee witness and read data. + /// Attribute type + /// Attribute fee value + /// Whether to support attribute type. + /// The fee for attribute. + private void SetAttributeFee(ApplicationEngine engine, byte attributeType, uint value, bool allowNotaryAssisted) { - if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType)) throw new InvalidOperationException(); + if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType) || (!allowNotaryAssisted && attributeType == (byte)(TransactionAttributeType.NotaryAssisted))) + throw new InvalidOperationException(); if (value > MaxAttributeFee) throw new ArgumentOutOfRangeException(nameof(value)); if (!CheckCommittee(engine)) throw new InvalidOperationException(); From 4d5cee903fa77965d590a131243136b227c75c4b Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 24 Jun 2024 19:20:40 +0300 Subject: [PATCH 10/10] P2P: move NotaryAssisted transaction attribute under E hardfork D hardfork was occupied by 3.7.5, thus use the next available one. Signed-off-by: Anna Shaleva --- src/Neo/Hardfork.cs | 3 ++- src/Neo/Network/P2P/Payloads/Transaction.cs | 4 ++-- src/Neo/SmartContract/Native/PolicyContract.cs | 18 +++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Neo/Hardfork.cs b/src/Neo/Hardfork.cs index 7bd3cc0aef..cc88a154d1 100644 --- a/src/Neo/Hardfork.cs +++ b/src/Neo/Hardfork.cs @@ -16,6 +16,7 @@ public enum Hardfork : byte HF_Aspidochelone, HF_Basilisk, HF_Cockatrice, - HF_Domovoi + HF_Domovoi, + HF_Echidna, } } diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs index 8fc771ce7c..d3e3b06a8f 100644 --- a/src/Neo/Network/P2P/Payloads/Transaction.cs +++ b/src/Neo/Network/P2P/Payloads/Transaction.cs @@ -371,9 +371,9 @@ public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, Data long attributesFee = 0; foreach (TransactionAttribute attribute in Attributes) { - if (!attribute.Verify(snapshot, this)) + if (attribute.Type == TransactionAttributeType.NotaryAssisted && !settings.IsHardforkEnabled(Hardfork.HF_Echidna, height)) return VerifyResult.InvalidAttribute; - if (attribute.Type == TransactionAttributeType.NotaryAssisted && !settings.IsHardforkEnabled(Hardfork.HF_Domovoi, height)) + if (!attribute.Verify(snapshot, this)) return VerifyResult.InvalidAttribute; attributesFee += attribute.CalculateNetworkFee(snapshot, this); } diff --git a/src/Neo/SmartContract/Native/PolicyContract.cs b/src/Neo/SmartContract/Native/PolicyContract.cs index 7dcabe2771..755dd3a30f 100644 --- a/src/Neo/SmartContract/Native/PolicyContract.cs +++ b/src/Neo/SmartContract/Native/PolicyContract.cs @@ -80,7 +80,7 @@ internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfor engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); } - if (hardfork == Hardfork.HF_Domovoi) + if (hardfork == Hardfork.HF_Echidna) { engine.Snapshot.Add(CreateStorageKey(Prefix_AttributeFee).Add((byte)TransactionAttributeType.NotaryAssisted), new StorageItem(DefaultNotaryAssistedAttributeFee)); } @@ -121,24 +121,24 @@ public uint GetStoragePrice(DataCache snapshot) } /// - /// Gets the fee for attribute before Domovoi hardfork. + /// Gets the fee for attribute before Echidna hardfork. /// /// The snapshot used to read data. /// Attribute type excluding /// The fee for attribute. - [ContractMethod(Hardfork.HF_Domovoi, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates, Name = "getAttributeFee")] + [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates, Name = "getAttributeFee")] public uint GetAttributeFeeV0(DataCache snapshot, byte attributeType) { return GetAttributeFee(snapshot, attributeType, false); } /// - /// Gets the fee for attribute after Domovoi hardfork. + /// Gets the fee for attribute after Echidna hardfork. /// /// The snapshot used to read data. /// Attribute type /// The fee for attribute. - [ContractMethod(true, Hardfork.HF_Domovoi, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] + [ContractMethod(true, Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint GetAttributeFee(DataCache snapshot, byte attributeType) { return GetAttributeFee(snapshot, attributeType, true); @@ -175,26 +175,26 @@ public bool IsBlocked(DataCache snapshot, UInt160 account) } /// - /// Sets the fee for attribute before Domovoi hardfork. + /// Sets the fee for attribute before Echidna hardfork. /// /// The engine used to check committee witness and read data. /// Attribute type excluding /// Attribute fee value /// The fee for attribute. - [ContractMethod(Hardfork.HF_Domovoi, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States, Name = "setAttributeFee")] + [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States, Name = "setAttributeFee")] private void SetAttributeFeeV0(ApplicationEngine engine, byte attributeType, uint value) { SetAttributeFee(engine, attributeType, value, false); } /// - /// Sets the fee for attribute after Domovoi hardfork. + /// Sets the fee for attribute after Echidna hardfork. /// /// The engine used to check committee witness and read data. /// Attribute type excluding /// Attribute fee value /// The fee for attribute. - [ContractMethod(true, Hardfork.HF_Domovoi, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States, Name = "setAttributeFee")] + [ContractMethod(true, Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States, Name = "setAttributeFee")] private void SetAttributeFeeV1(ApplicationEngine engine, byte attributeType, uint value) { SetAttributeFee(engine, attributeType, value, true);