diff --git a/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs b/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs index 5f80114dc9..a87fd7c3e1 100644 --- a/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs +++ b/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs @@ -15,9 +15,9 @@ public static class Nep5NativeContractExtensions { internal class ManualWitness : IVerifiable { - private readonly UInt160 _hashForVerify; + private readonly UInt160[] _hashForVerify; - public Witness Witness + public Witness[] Witnesses { get => throw new NotImplementedException(); set => throw new NotImplementedException(); @@ -25,19 +25,16 @@ public Witness Witness public int Size => 0; - public ManualWitness(UInt160 hashForVerify) + public ManualWitness(params UInt160[] hashForVerify) { - _hashForVerify = hashForVerify; + _hashForVerify = hashForVerify ?? new UInt160[0]; } public void Deserialize(BinaryReader reader) { } public void DeserializeUnsigned(BinaryReader reader) { } - public UInt160 GetScriptHashForVerification(Persistence.Snapshot snapshot) - { - return _hashForVerify; - } + public UInt160[] GetScriptHashesForVerifying(Persistence.Snapshot snapshot) => _hashForVerify; public void Serialize(BinaryWriter writer) { } diff --git a/neo.UnitTests/TestUtils.cs b/neo.UnitTests/TestUtils.cs index 8660328bf7..9bf06acbf7 100644 --- a/neo.UnitTests/TestUtils.cs +++ b/neo.UnitTests/TestUtils.cs @@ -28,11 +28,11 @@ public static Transaction GetTransaction() Script = new byte[1], Sender = UInt160.Zero, Attributes = new TransactionAttribute[0], - Witness = new Witness + Witnesses = new Witness[]{ new Witness { InvocationScript = new byte[0], VerificationScript = new byte[0] - } + } } }; } @@ -61,7 +61,8 @@ public static void SetupBlockWithValues(Block block, UInt256 val256, out UInt256 private static void setupBlockBaseWithValues(BlockBase bb, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal) { bb.PrevHash = val256; - merkRootVal = new UInt256(new byte[] { 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197 }); + merkRootVal = UInt256.Parse("0xd841af3d6bd7adb4bca24306725f9aec363edb10de3cafc5f8cca948d7b0290f"); + bb.MerkleRoot = merkRootVal; timestampVal = new DateTime(1968, 06, 01, 0, 0, 0, DateTimeKind.Utc).ToTimestamp(); bb.Timestamp = timestampVal; @@ -86,10 +87,13 @@ public static Transaction CreateRandomHashTransaction() Script = randomBytes, Sender = UInt160.Zero, Attributes = new TransactionAttribute[0], - Witness = new Witness + Witnesses = new[] { - InvocationScript = new byte[0], - VerificationScript = new byte[0] + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } } }; } diff --git a/neo.UnitTests/TestVerifiable.cs b/neo.UnitTests/TestVerifiable.cs index 78d3d314c0..dfe21c2d0f 100644 --- a/neo.UnitTests/TestVerifiable.cs +++ b/neo.UnitTests/TestVerifiable.cs @@ -7,9 +7,13 @@ namespace Neo.UnitTests { public class TestVerifiable : IVerifiable { - private string testStr = "testStr"; + private readonly string testStr = "testStr"; - public Witness Witness { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public Witness[] Witnesses + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } public int Size => throw new NotImplementedException(); @@ -23,7 +27,7 @@ public void DeserializeUnsigned(BinaryReader reader) throw new NotImplementedException(); } - public UInt160 GetScriptHashForVerification(Snapshot snapshot) + public UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { throw new NotImplementedException(); } @@ -35,7 +39,7 @@ public void Serialize(BinaryWriter writer) public void SerializeUnsigned(BinaryWriter writer) { - writer.Write((string) testStr); + writer.Write((string)testStr); } } } \ No newline at end of file diff --git a/neo.UnitTests/UT_Block.cs b/neo.UnitTests/UT_Block.cs index f7acf254be..886f5099f0 100644 --- a/neo.UnitTests/UT_Block.cs +++ b/neo.UnitTests/UT_Block.cs @@ -28,12 +28,7 @@ public void Transactions_Get() public void Header_Get() { UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 0); + TestUtils.SetupBlockWithValues(uut, val256, out var merkRootVal, out var val160, out var timestampVal, out var indexVal, out var scriptVal, out var transactionsVal, 0); uut.Header.Should().NotBeNull(); uut.Header.PrevHash.Should().Be(val256); @@ -47,46 +42,31 @@ public void Header_Get() public void Size_Get() { UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 0); - // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 3 + TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, 0); + // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 4 // block 9 + 1 - uut.Size.Should().Be(109); + uut.Size.Should().Be(110); } [TestMethod] public void Size_Get_1_Transaction() { UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 0); + TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, 0); uut.Transactions = new[] { TestUtils.GetTransaction() }; - uut.Size.Should().Be(159); + uut.Size.Should().Be(161); } [TestMethod] public void Size_Get_3_Transaction() { UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 0); + TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, 0); uut.Transactions = new[] { @@ -95,19 +75,14 @@ public void Size_Get_3_Transaction() TestUtils.GetTransaction() }; - uut.Size.Should().Be(259); + uut.Size.Should().Be(263); } [TestMethod] public void Serialize() { UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 1); + TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, 1); byte[] data; using (MemoryStream stream = new MemoryStream()) @@ -119,7 +94,7 @@ public void Serialize() } } - byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 41, 176, 215, 72, 169, 204, 248, 197, 175, 60, 222, 16, 219, 62, 54, 236, 154, 95, 114, 6, 67, 162, 188, 180, 173, 215, 107, 61, 175, 65, 216, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0 }; data.Length.Should().Be(requiredData.Length); for (int i = 0; i < data.Length; i++) @@ -132,16 +107,11 @@ public void Serialize() public void Deserialize() { UInt256 val256 = UInt256.Zero; - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(new Block(), val256, out merkRoot, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 1); + TestUtils.SetupBlockWithValues(new Block(), val256, out var merkRoot, out var val160, out var timestampVal, out var indexVal, out var scriptVal, out var transactionsVal, 1); uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid - byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 41, 176, 215, 72, 169, 204, 248, 197, 175, 60, 222, 16, 219, 62, 54, 236, 154, 95, 114, 6, 67, 162, 188, 180, 173, 215, 107, 61, 175, 65, 216, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }; int index = 0; using (MemoryStream ms = new MemoryStream(data, index, data.Length - index, false)) { @@ -244,23 +214,23 @@ public void ToJson() JObject jObj = uut.ToJson(); jObj.Should().NotBeNull(); - jObj["hash"].AsString().Should().Be("0xe0b482b6e4c176c9af520dd7caa6d80a9aeaeb80d016e788c702b05f5ac5ba4b"); - jObj["size"].AsNumber().Should().Be(159); + jObj["hash"].AsString().Should().Be("0x1d8642796276c8ce3c5c03b8984a1b593d99b49a63d830bb06f800b8c953be77"); + jObj["size"].AsNumber().Should().Be(161); jObj["version"].AsNumber().Should().Be(0); jObj["previousblockhash"].AsString().Should().Be("0x0000000000000000000000000000000000000000000000000000000000000000"); - jObj["merkleroot"].AsString().Should().Be("0xc59eab3a287f74405b80325bc26c66ac7ef18d94c434a18d60950d3f098280f2"); + jObj["merkleroot"].AsString().Should().Be("0xd841af3d6bd7adb4bca24306725f9aec363edb10de3cafc5f8cca948d7b0290f"); jObj["time"].AsNumber().Should().Be(4244941696); jObj["index"].AsNumber().Should().Be(0); jObj["nextconsensus"].AsString().Should().Be("AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM"); - JObject scObj = jObj["witness"]; + JObject scObj = ((JArray)jObj["witnesses"])[0]; scObj["invocation"].AsString().Should().Be(""); scObj["verification"].AsString().Should().Be("51"); jObj["tx"].Should().NotBeNull(); JArray txObj = (JArray)jObj["tx"]; - txObj[0]["hash"].AsString().Should().Be("0x7647acf1ef8a841b87f2369398a0e9f0ccde0ed9e835d980657103da6da59580"); - txObj[0]["size"].AsNumber().Should().Be(50); + txObj[0]["hash"].AsString().Should().Be("0x64ed4e0d79407c60bde534feb44fbbd19bd065282d27ecd3a1a7a647f66affa6"); + txObj[0]["size"].AsNumber().Should().Be(51); txObj[0]["version"].AsNumber().Should().Be(0); ((JArray)txObj[0]["attributes"]).Count.Should().Be(0); txObj[0]["net_fee"].AsString().Should().Be("0"); diff --git a/neo.UnitTests/UT_Consensus.cs b/neo.UnitTests/UT_Consensus.cs index 4ea314238b..d410bb5142 100644 --- a/neo.UnitTests/UT_Consensus.cs +++ b/neo.UnitTests/UT_Consensus.cs @@ -75,7 +75,7 @@ public void ConsensusService_Primary_Sends_PrepareRequest_After_OnStart() // Creating proposed block Header header = new Header(); TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 merkRootVal, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal); - header.Size.Should().Be(100); + header.Size.Should().Be(101); Console.WriteLine($"header {header} hash {header.Hash} timstamp {timestampVal}"); diff --git a/neo.UnitTests/UT_Header.cs b/neo.UnitTests/UT_Header.cs index e3df13e14d..82744a7f8b 100644 --- a/neo.UnitTests/UT_Header.cs +++ b/neo.UnitTests/UT_Header.cs @@ -22,9 +22,9 @@ public void Size_Get() { UInt256 val256 = UInt256.Zero; TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _); - // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 3 + // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 4 // header 1 - uut.Size.Should().Be(100); + uut.Size.Should().Be(101); } [TestMethod] @@ -35,7 +35,7 @@ public void Deserialize() uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid - byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 81, 0 }; + byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 41, 176, 215, 72, 169, 204, 248, 197, 175, 60, 222, 16, 219, 62, 54, 236, 154, 95, 114, 6, 67, 162, 188, 180, 173, 215, 107, 61, 175, 65, 216, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 0 }; int index = 0; using (MemoryStream ms = new MemoryStream(data, index, data.Length - index, false)) { @@ -106,7 +106,7 @@ public void Serialize() } } - byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 128, 130, 9, 63, 13, 149, 96, 141, 161, 52, 196, 148, 141, 241, 126, 172, 102, 108, 194, 91, 50, 128, 91, 64, 116, 127, 40, 58, 171, 158, 197, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 81, 0 }; + byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 41, 176, 215, 72, 169, 204, 248, 197, 175, 60, 222, 16, 219, 62, 54, 236, 154, 95, 114, 6, 67, 162, 188, 180, 173, 215, 107, 61, 175, 65, 216, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 0 }; data.Length.Should().Be(requiredData.Length); for (int i = 0; i < data.Length; i++) diff --git a/neo.UnitTests/UT_MemoryPool.cs b/neo.UnitTests/UT_MemoryPool.cs index a35c85fcc7..90cd43d5d5 100644 --- a/neo.UnitTests/UT_MemoryPool.cs +++ b/neo.UnitTests/UT_MemoryPool.cs @@ -53,173 +53,87 @@ private Transaction CreateTransactionWithFee(long fee) mock.Object.Sender = UInt160.Zero; mock.Object.NetworkFee = fee; mock.Object.Attributes = new TransactionAttribute[0]; - mock.Object.Witness = new Witness + mock.Object.Witnesses = new[] { - InvocationScript = new byte[0], - VerificationScript = new byte[0] + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } }; return mock.Object; } - private Transaction CreateHighPriorityTransaction() + private Transaction CreateTransaction() { return CreateTransactionWithFee(LongRandom(100000, 100000000, TestUtils.TestRandom)); } - private Transaction CreateLowPriorityTransaction() - { - long rNetFee = LongRandom(0, 10000, TestUtils.TestRandom); - // [0,0.001] GAS a fee lower than the threshold of 0.001 GAS (not enough to be a high priority TX) - return CreateTransactionWithFee(rNetFee); - } - - private bool IsLowPriority(Transaction tx) - { - return tx.FeePerByte < 1000; - } - - private void AddTransactions(int count, bool isHighPriority = false) + private void AddTransactions(int count) { for (int i = 0; i < count; i++) { - var txToAdd = isHighPriority ? CreateHighPriorityTransaction() : CreateLowPriorityTransaction(); + var txToAdd = CreateTransaction(); Console.WriteLine($"created tx: {txToAdd.Hash}"); _unit.TryAdd(txToAdd.Hash, txToAdd); } } - private void AddLowPriorityTransactions(int count) => AddTransactions(count); - public void AddHighPriorityTransactions(int count) => AddTransactions(count, true); [TestMethod] - public void LowPriorityCapacityTest() + public void CapacityTest() { // Add over the capacity items, verify that the verified count increases each time - AddLowPriorityTransactions(50); - _unit.VerifiedCount.ShouldBeEquivalentTo(50); - AddLowPriorityTransactions(51); - Console.WriteLine($"VerifiedCount: {_unit.VerifiedCount} LowPrioCount {_unit.SortedLowPrioTxCount} HighPrioCount {_unit.SortedHighPrioTxCount}"); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(100); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(0); + AddTransactions(101); - _unit.VerifiedCount.ShouldBeEquivalentTo(100); - _unit.UnVerifiedCount.ShouldBeEquivalentTo(0); - _unit.Count.ShouldBeEquivalentTo(100); - } - - [TestMethod] - public void HighPriorityCapacityTest() - { - // Add over the capacity items, verify that the verified count increases each time - AddHighPriorityTransactions(101); - - Console.WriteLine($"VerifiedCount: {_unit.VerifiedCount} LowPrioCount {_unit.SortedLowPrioTxCount} HighPrioCount {_unit.SortedHighPrioTxCount}"); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(0); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(100); + Console.WriteLine($"VerifiedCount: {_unit.VerifiedCount} Count {_unit.SortedTxCount}"); + _unit.SortedTxCount.ShouldBeEquivalentTo(100); _unit.VerifiedCount.ShouldBeEquivalentTo(100); _unit.UnVerifiedCount.ShouldBeEquivalentTo(0); _unit.Count.ShouldBeEquivalentTo(100); } - [TestMethod] - public void HighPriorityPushesOutLowPriority() - { - // Add over the capacity items, verify that the verified count increases each time - AddLowPriorityTransactions(70); - AddHighPriorityTransactions(40); - - Console.WriteLine($"VerifiedCount: {_unit.VerifiedCount} LowPrioCount {_unit.SortedLowPrioTxCount} HighPrioCount {_unit.SortedHighPrioTxCount}"); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(60); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(40); - _unit.Count.ShouldBeEquivalentTo(100); - } - - [TestMethod] - public void LowPriorityDoesNotPushOutHighPrority() - { - AddHighPriorityTransactions(70); - AddLowPriorityTransactions(40); - - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(30); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(70); - _unit.Count.ShouldBeEquivalentTo(100); - } - [TestMethod] public void BlockPersistMovesTxToUnverifiedAndReverification() { - AddHighPriorityTransactions(70); - AddLowPriorityTransactions(30); + AddTransactions(70); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(70); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(30); + _unit.SortedTxCount.ShouldBeEquivalentTo(70); var block = new Block { Transactions = _unit.GetSortedVerifiedTransactions().Take(10) - .Concat(_unit.GetSortedVerifiedTransactions().Where(x => IsLowPriority(x)).Take(5)).ToArray() + .Concat(_unit.GetSortedVerifiedTransactions().Take(5)).ToArray() }; _unit.UpdatePoolForBlockPersisted(block, Blockchain.Singleton.GetSnapshot()); _unit.InvalidateVerifiedTransactions(); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(0); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(0); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(60); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(25); + _unit.SortedTxCount.ShouldBeEquivalentTo(0); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(60); _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(9); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(1); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(51); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(24); + _unit.SortedTxCount.ShouldBeEquivalentTo(10); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(50); _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(18); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(2); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(42); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(23); + _unit.SortedTxCount.ShouldBeEquivalentTo(20); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(40); _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(27); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(3); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(33); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(22); + _unit.SortedTxCount.ShouldBeEquivalentTo(30); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(30); _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(36); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(4); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(24); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(21); + _unit.SortedTxCount.ShouldBeEquivalentTo(40); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(20); _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(45); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(5); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(15); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(20); + _unit.SortedTxCount.ShouldBeEquivalentTo(50); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(10); _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(54); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(6); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(6); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(19); - - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(60); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(10); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(0); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(15); - - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(60); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(20); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(0); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(5); - - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(60); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(25); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(0); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(0); + _unit.SortedTxCount.ShouldBeEquivalentTo(60); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(0); } private void verifyTransactionsSortedDescending(IEnumerable transactions) @@ -248,8 +162,7 @@ private void verifyTransactionsSortedDescending(IEnumerable transac [TestMethod] public void VerifySortOrderAndThatHighetFeeTransactionsAreReverifiedFirst() { - AddLowPriorityTransactions(50); - AddHighPriorityTransactions(50); + AddTransactions(100); var sortedVerifiedTxs = _unit.GetSortedVerifiedTransactions().ToList(); // verify all 100 transactions are returned in sorted order @@ -260,37 +173,31 @@ public void VerifySortOrderAndThatHighetFeeTransactionsAreReverifiedFirst() var block = new Block { Transactions = new Transaction[0] }; _unit.UpdatePoolForBlockPersisted(block, Blockchain.Singleton.GetSnapshot()); _unit.InvalidateVerifiedTransactions(); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(0); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(0); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(50); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(50); + _unit.SortedTxCount.ShouldBeEquivalentTo(0); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(100); // We can verify the order they are re-verified by reverifying 2 at a time while (_unit.UnVerifiedCount > 0) { - _unit.GetVerifiedAndUnverifiedTransactions(out IEnumerable sortedVerifiedTransactions, - out IEnumerable sortedUnverifiedTransactions); + _unit.GetVerifiedAndUnverifiedTransactions(out var sortedVerifiedTransactions, out var sortedUnverifiedTransactions); sortedVerifiedTransactions.Count().ShouldBeEquivalentTo(0); var sortedUnverifiedArray = sortedUnverifiedTransactions.ToArray(); verifyTransactionsSortedDescending(sortedUnverifiedArray); - var maxHighPriorityTransaction = sortedUnverifiedArray.First(); - var maxLowPriorityTransaction = sortedUnverifiedArray.First(tx => IsLowPriority(tx)); + var maxTransaction = sortedUnverifiedArray.First(); + var minTransaction = sortedUnverifiedArray.Last(); // reverify 1 high priority and 1 low priority transaction - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(2, Blockchain.Singleton.GetSnapshot()); + _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(1, Blockchain.Singleton.GetSnapshot()); var verifiedTxs = _unit.GetSortedVerifiedTransactions().ToArray(); - verifiedTxs.Length.ShouldBeEquivalentTo(2); - verifiedTxs[0].ShouldBeEquivalentTo(maxHighPriorityTransaction); - verifiedTxs[1].ShouldBeEquivalentTo(maxLowPriorityTransaction); - var blockWith2Tx = new Block { Transactions = new[] { maxHighPriorityTransaction, maxLowPriorityTransaction } }; + verifiedTxs.Length.ShouldBeEquivalentTo(1); + verifiedTxs[0].ShouldBeEquivalentTo(maxTransaction); + var blockWith2Tx = new Block { Transactions = new[] { maxTransaction, minTransaction } }; // verify and remove the 2 transactions from the verified pool _unit.UpdatePoolForBlockPersisted(blockWith2Tx, Blockchain.Singleton.GetSnapshot()); _unit.InvalidateVerifiedTransactions(); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(0); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(0); + _unit.SortedTxCount.ShouldBeEquivalentTo(0); } - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(0); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(0); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(0); } void VerifyCapacityThresholdForAttemptingToAddATransaction() @@ -306,11 +213,11 @@ void VerifyCapacityThresholdForAttemptingToAddATransaction() [TestMethod] public void VerifyCanTransactionFitInPoolWorksAsIntended() { - AddLowPriorityTransactions(100); + AddTransactions(100); VerifyCapacityThresholdForAttemptingToAddATransaction(); - AddHighPriorityTransactions(50); + AddTransactions(50); VerifyCapacityThresholdForAttemptingToAddATransaction(); - AddHighPriorityTransactions(50); + AddTransactions(50); VerifyCapacityThresholdForAttemptingToAddATransaction(); } @@ -321,31 +228,27 @@ public void CapacityTestWithUnverifiedHighProirtyTransactions() // low priority transactions // Fill pool with high priority transactions - AddHighPriorityTransactions(99); + AddTransactions(99); // move all to unverified var block = new Block { Transactions = new Transaction[0] }; _unit.UpdatePoolForBlockPersisted(block, Blockchain.Singleton.GetSnapshot()); - _unit.CanTransactionFitInPool(CreateLowPriorityTransaction()).ShouldBeEquivalentTo(true); - AddHighPriorityTransactions(1); - _unit.CanTransactionFitInPool(CreateLowPriorityTransaction()).ShouldBeEquivalentTo(false); + _unit.CanTransactionFitInPool(CreateTransaction()).ShouldBeEquivalentTo(true); + AddTransactions(1); + _unit.CanTransactionFitInPool(CreateTransactionWithFee(0)).ShouldBeEquivalentTo(false); } [TestMethod] public void TestInvalidateAll() { - AddHighPriorityTransactions(30); - AddLowPriorityTransactions(60); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(0); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(0); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(30); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(60); + AddTransactions(30); + + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(0); + _unit.SortedTxCount.ShouldBeEquivalentTo(30); _unit.InvalidateAllTransactions(); - _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(30); - _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(60); - _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(0); - _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(0); + _unit.UnverifiedSortedTxCount.ShouldBeEquivalentTo(30); + _unit.SortedTxCount.ShouldBeEquivalentTo(0); } } } diff --git a/neo.UnitTests/UT_Policy.cs b/neo.UnitTests/UT_Policy.cs index da93eca368..09a09b20ba 100644 --- a/neo.UnitTests/UT_Policy.cs +++ b/neo.UnitTests/UT_Policy.cs @@ -32,20 +32,12 @@ public void Check_Initialize() NativeContract.Policy.Initialize(new ApplicationEngine(TriggerType.Application, null, snapshot, 0)).Should().BeTrue(); - (keyCount + 5).Should().Be(snapshot.Storages.GetChangeSet().Count()); + (keyCount + 3).Should().Be(snapshot.Storages.GetChangeSet().Count()); var ret = NativeContract.Policy.Call(snapshot, "getMaxTransactionsPerBlock"); ret.Should().BeOfType(); ret.GetBigInteger().Should().Be(512); - ret = NativeContract.Policy.Call(snapshot, "getMaxLowPriorityTransactionsPerBlock"); - ret.Should().BeOfType(); - ret.GetBigInteger().Should().Be(20); - - ret = NativeContract.Policy.Call(snapshot, "getMaxLowPriorityTransactionSize"); - ret.Should().BeOfType(); - ret.GetBigInteger().Should().Be(256); - ret = NativeContract.Policy.Call(snapshot, "getFeePerByte"); ret.Should().BeOfType(); ret.GetBigInteger().Should().Be(1000); @@ -90,76 +82,6 @@ public void Check_SetMaxTransactionsPerBlock() ret.GetBigInteger().Should().Be(1); } - [TestMethod] - public void Check_SetMaxLowPriorityTransactionsPerBlock() - { - var snapshot = Store.GetSnapshot().Clone(); - - // Fake blockchain - - snapshot.PersistingBlock = new Block() { Index = 1000, PrevHash = UInt256.Zero }; - snapshot.Blocks.Add(UInt256.Zero, new Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero }); - - NativeContract.Policy.Initialize(new ApplicationEngine(TriggerType.Application, null, snapshot, 0)).Should().BeTrue(); - - // Without signature - - var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), - "setMaxLowPriorityTransactionsPerBlock", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); - ret.Should().BeOfType(); - ret.GetBoolean().Should().BeFalse(); - - ret = NativeContract.Policy.Call(snapshot, "getMaxLowPriorityTransactionsPerBlock"); - ret.Should().BeOfType(); - ret.GetBigInteger().Should().Be(20); - - // With signature - - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), - "setMaxLowPriorityTransactionsPerBlock", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); - ret.Should().BeOfType(); - ret.GetBoolean().Should().BeTrue(); - - ret = NativeContract.Policy.Call(snapshot, "getMaxLowPriorityTransactionsPerBlock"); - ret.Should().BeOfType(); - ret.GetBigInteger().Should().Be(1); - } - - [TestMethod] - public void Check_SetMaxLowPriorityTransactionSize() - { - var snapshot = Store.GetSnapshot().Clone(); - - // Fake blockchain - - snapshot.PersistingBlock = new Block() { Index = 1000, PrevHash = UInt256.Zero }; - snapshot.Blocks.Add(UInt256.Zero, new Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero }); - - NativeContract.Policy.Initialize(new ApplicationEngine(TriggerType.Application, null, snapshot, 0)).Should().BeTrue(); - - // Without signature - - var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), - "setMaxLowPriorityTransactionSize", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); - ret.Should().BeOfType(); - ret.GetBoolean().Should().BeFalse(); - - ret = NativeContract.Policy.Call(snapshot, "getMaxLowPriorityTransactionSize"); - ret.Should().BeOfType(); - ret.GetBigInteger().Should().Be(256); - - // With signature - - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), - "setMaxLowPriorityTransactionSize", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); - ret.Should().BeOfType(); - ret.GetBoolean().Should().BeTrue(); - - ret = NativeContract.Policy.Call(snapshot, "getMaxLowPriorityTransactionSize"); - ret.Should().BeOfType(); - ret.GetBigInteger().Should().Be(1); - } - [TestMethod] public void Check_SetFeePerByte() { diff --git a/neo.UnitTests/UT_PoolItem.cs b/neo.UnitTests/UT_PoolItem.cs index 272dcf4084..cedf7f3fa9 100644 --- a/neo.UnitTests/UT_PoolItem.cs +++ b/neo.UnitTests/UT_PoolItem.cs @@ -122,17 +122,20 @@ public static Transaction GenerateTx(long networkFee, int size, byte[] overrideS Sender = UInt160.Zero, NetworkFee = networkFee, Attributes = new TransactionAttribute[0], - Witness = new Witness + Witnesses = new[] { - InvocationScript = new byte[0], - VerificationScript = new byte[0] + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } } }; int diff = size - tx.Size; if (diff < 0) throw new ArgumentException(); if (diff > 0) - tx.Witness.VerificationScript = new byte[diff]; + tx.Witnesses[0].VerificationScript = new byte[diff]; return tx; } } diff --git a/neo.UnitTests/UT_Syscalls.cs b/neo.UnitTests/UT_Syscalls.cs index 82c857b6d9..c6642c4e88 100644 --- a/neo.UnitTests/UT_Syscalls.cs +++ b/neo.UnitTests/UT_Syscalls.cs @@ -27,7 +27,9 @@ public void System_Runtime_GetInvocationCounter() var contractB = new ContractState() { Script = new byte[] { (byte)OpCode.DROP, (byte)OpCode.DROP, (byte)OpCode.NOP }.Concat(script.ToArray()).ToArray() }; var contractC = new ContractState() { Script = new byte[] { (byte)OpCode.DROP, (byte)OpCode.DROP, (byte)OpCode.NOP, (byte)OpCode.NOP }.Concat(script.ToArray()).ToArray() }; - contracts.DeleteWhere((a, b) => true); + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contractA.ScriptHash.ToArray())); + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contractB.ScriptHash.ToArray())); + contracts.DeleteWhere((a, b) => a.ToArray().SequenceEqual(contractC.ScriptHash.ToArray())); contracts.Add(contractA.ScriptHash, contractA); contracts.Add(contractB.ScriptHash, contractB); contracts.Add(contractC.ScriptHash, contractC); diff --git a/neo.UnitTests/UT_Transaction.cs b/neo.UnitTests/UT_Transaction.cs index b192a2479d..18aafb31bb 100644 --- a/neo.UnitTests/UT_Transaction.cs +++ b/neo.UnitTests/UT_Transaction.cs @@ -1,8 +1,17 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; using Neo.IO; using Neo.IO.Json; +using Neo.Ledger; using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.SmartContract.Native.Tokens; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; namespace Neo.UnitTests { @@ -10,11 +19,13 @@ namespace Neo.UnitTests public class UT_Transaction { Transaction uut; + Store store; [TestInitialize] public void TestSetup() { uut = new Transaction(); + store = TestBlockchain.GetStore(); } [TestMethod] @@ -38,15 +49,15 @@ public void Script_Set() [TestMethod] public void Gas_Get() { - uut.Gas.Should().Be(0); + uut.SystemFee.Should().Be(0); } [TestMethod] public void Gas_Set() { long val = 4200000000; - uut.Gas = val; - uut.Gas.Should().Be(val); + uut.SystemFee = val; + uut.SystemFee.Should().Be(val); } [TestMethod] @@ -55,16 +66,187 @@ public void Size_Get() uut.Script = TestUtils.GetByteArray(32, 0x42); uut.Sender = UInt160.Zero; uut.Attributes = new TransactionAttribute[0]; - uut.Witness = new Witness + uut.Witnesses = new[] { - InvocationScript = new byte[0], - VerificationScript = new byte[0] + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } }; uut.Version.Should().Be(0); uut.Script.Length.Should().Be(32); uut.Script.GetVarSize().Should().Be(33); - uut.Size.Should().Be(81); + uut.Size.Should().Be(82); + } + + private NEP6Wallet GenerateTestWallet() + { + JObject wallet = new JObject(); + wallet["name"] = "noname"; + wallet["version"] = new System.Version().ToString(); + wallet["scrypt"] = ScryptParameters.Default.ToJson(); + wallet["accounts"] = new JArray(); + wallet["extra"] = null; + wallet.ToString().Should().Be("{\"name\":\"noname\",\"version\":\"0.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[],\"extra\":null}"); + return new NEP6Wallet(wallet); + } + + [TestMethod] + public void FeeIsMultiSigContract() + { + var store = TestBlockchain.GetStore(); + var walletA = GenerateTestWallet(); + var walletB = GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + using (var unlockA = walletA.Unlock("123")) + using (var unlockB = walletB.Unlock("123")) + { + var a = walletA.CreateAccount(); + var b = walletB.CreateAccount(); + + var multiSignContract = Contract.CreateMultiSigContract(2, + new ECPoint[] + { + a.GetKey().PublicKey, + b.GetKey().PublicKey + }); + + var acc = walletA.CreateAccount(multiSignContract, a.GetKey()); + acc = walletB.CreateAccount(multiSignContract, b.GetKey()); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + + var tx = walletA.MakeTransaction(new TransferOutput[] + { + new TransferOutput() + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = acc.ScriptHash, + Value = new BigDecimal(1,8) + } + }, acc.ScriptHash); + + Assert.IsNotNull(tx); + + // Sign + + var data = new ContractParametersContext(tx); + Assert.IsTrue(walletA.Sign(data)); + Assert.IsTrue(walletB.Sign(data)); + Assert.IsTrue(data.Completed); + + tx.Witnesses = data.GetWitnesses(); + + // Fast check + + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + + // Check + + long verificationGas = 0; + foreach (var witness in tx.Witnesses) + { + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) + { + engine.LoadScript(witness.VerificationScript); + engine.LoadScript(witness.InvocationScript); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + verificationGas += engine.GasConsumed; + } + } + + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); + } + } + + [TestMethod] + public void FeeIsSignatureContract() + { + var wallet = GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + using (var unlock = wallet.Unlock("123")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + + var tx = wallet.MakeTransaction(new TransferOutput[] + { + new TransferOutput() + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = acc.ScriptHash, + Value = new BigDecimal(1,8) + } + }, acc.ScriptHash); + + Assert.IsNotNull(tx); + + // Sign + + var data = new ContractParametersContext(tx); + Assert.IsTrue(wallet.Sign(data)); + tx.Witnesses = data.GetWitnesses(); + + // Fast check + + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + + // Check + + long verificationGas = 0; + foreach (var witness in tx.Witnesses) + { + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) + { + engine.LoadScript(witness.VerificationScript); + engine.LoadScript(witness.InvocationScript); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + verificationGas += engine.GasConsumed; + } + } + + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); + } } [TestMethod] @@ -72,23 +254,26 @@ public void ToJson() { uut.Script = TestUtils.GetByteArray(32, 0x42); uut.Sender = UInt160.Zero; - uut.Gas = 4200000000; + uut.SystemFee = 4200000000; uut.Attributes = new TransactionAttribute[0]; - uut.Witness = new Witness + uut.Witnesses = new[] { - InvocationScript = new byte[0], - VerificationScript = new byte[0] + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } }; JObject jObj = uut.ToJson(); jObj.Should().NotBeNull(); - jObj["hash"].AsString().Should().Be("0x38274692538dfecaae36f8fd518d92bae25607d491c40a8f927cc06bd97ab2c8"); - jObj["size"].AsNumber().Should().Be(81); + jObj["hash"].AsString().Should().Be("0xee00d595ccd48a650f62adaccbb9c979e2dc7ef66fb5b1413f0f74d563a2d9c6"); + jObj["size"].AsNumber().Should().Be(82); jObj["version"].AsNumber().Should().Be(0); ((JArray)jObj["attributes"]).Count.Should().Be(0); jObj["net_fee"].AsString().Should().Be("0"); jObj["script"].AsString().Should().Be("4220202020202020202020202020202020202020202020202020202020202020"); - jObj["gas"].AsNumber().Should().Be(42); + jObj["sys_fee"].AsNumber().Should().Be(42); } } } diff --git a/neo/Consensus/ConsensusContext.cs b/neo/Consensus/ConsensusContext.cs index 3affe821f5..5fa53ce567 100644 --- a/neo/Consensus/ConsensusContext.cs +++ b/neo/Consensus/ConsensusContext.cs @@ -85,7 +85,7 @@ public Block CreateBlock() sc.AddSignature(contract, Validators[i], CommitPayloads[i].GetDeserializedMessage().Signature); j++; } - Block.Witness = sc.GetWitness(); + Block.Witness = sc.GetWitnesses()[0]; Block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray(); return Block; } @@ -202,7 +202,7 @@ private void SignPayload(ConsensusPayload payload) { return; } - payload.Witness = sc.GetWitness(); + payload.Witness = sc.GetWitnesses()[0]; } public ConsensusPayload MakePrepareRequest() diff --git a/neo/Cryptography/Helper.cs b/neo/Cryptography/Helper.cs index f46aad0121..48410049f1 100644 --- a/neo/Cryptography/Helper.cs +++ b/neo/Cryptography/Helper.cs @@ -117,7 +117,8 @@ public static byte[] Sha256(this byte[] value, int offset, int count) internal static bool Test(this BloomFilter filter, Transaction tx) { if (filter.Check(tx.Hash.ToArray())) return true; - if (filter.Check(tx.Sender.ToArray())) return true; + if (tx.Witnesses.Any(p => filter.Check(p.ScriptHash.ToArray()))) + return true; return false; } diff --git a/neo/Ledger/Blockchain.cs b/neo/Ledger/Blockchain.cs index 4f887b4817..059560573b 100644 --- a/neo/Ledger/Blockchain.cs +++ b/neo/Ledger/Blockchain.cs @@ -152,12 +152,15 @@ private static Transaction DeployNativeContracts() Version = 0, Script = script, Sender = (new[] { (byte)OpCode.PUSHT }).ToScriptHash(), - Gas = 0, + SystemFee = 0, Attributes = new TransactionAttribute[0], - Witness = new Witness + Witnesses = new[] { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSHT } + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new[] { (byte)OpCode.PUSHT } + } } }; } @@ -280,7 +283,7 @@ private RelayResultReason OnNewBlock(Block block) block_cache_unverified.Remove(blockToPersist.Index); Persist(blockToPersist); - if (blocksPersisted++ < blocksToPersistList.Count - (2 + Math.Max(0,(15 - SecondsPerBlock)))) continue; + if (blocksPersisted++ < blocksToPersistList.Count - (2 + Math.Max(0, (15 - SecondsPerBlock)))) continue; // Empirically calibrated for relaying the most recent 2 blocks persisted with 15s network // Increase in the rate of 1 block per second in configurations with faster blocks @@ -433,7 +436,7 @@ private void Persist(Block block) BlockIndex = block.Index, Transaction = tx }); - using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, tx, snapshot.Clone(), tx.Gas)) + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, tx, snapshot.Clone(), tx.SystemFee)) { engine.LoadScript(tx.Script); if (!engine.Execute().HasFlag(VMState.FAULT)) diff --git a/neo/Ledger/MemoryPool.cs b/neo/Ledger/MemoryPool.cs index c3748ef7ec..f4fee9bdf2 100644 --- a/neo/Ledger/MemoryPool.cs +++ b/neo/Ledger/MemoryPool.cs @@ -1,15 +1,15 @@ -using Neo.Network.P2P.Payloads; +using Akka.Util.Internal; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins; +using Neo.SmartContract.Native; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; -using Akka.Util.Internal; -using Neo.Network.P2P; -using Neo.Persistence; -using Neo.Plugins; -using Neo.SmartContract.Native; namespace Neo.Ledger { @@ -20,12 +20,10 @@ public class MemoryPool : IReadOnlyCollection private const int BlocksTillRebroadcastHighPriorityPoolTx = 10; private int RebroadcastMultiplierThreshold => Capacity / 10; - private static readonly double MaxSecondsToReverifyHighPrioTx = (double) Blockchain.SecondsPerBlock / 3; - private static readonly double MaxSecondsToReverifyLowPrioTx = (double) Blockchain.SecondsPerBlock / 5; + private static readonly double MaxSecondsToReverifyTx = (double)Blockchain.SecondsPerBlock / 3; // These two are not expected to be hit, they are just safegaurds. - private static readonly double MaxSecondsToReverifyHighPrioTxPerIdle = (double) Blockchain.SecondsPerBlock / 15; - private static readonly double MaxSecondsToReverifyLowPrioTxPerIdle = (double) Blockchain.SecondsPerBlock / 30; + private static readonly double MaxSecondsToReverifyTxPerIdle = (double)Blockchain.SecondsPerBlock / 15; private readonly NeoSystem _system; @@ -44,34 +42,25 @@ public class MemoryPool : IReadOnlyCollection /// private readonly Dictionary _unsortedTransactions = new Dictionary(); /// - /// Stores the verified high priority sorted transactins currently in the pool. - /// - private readonly SortedSet _sortedHighPrioTransactions = new SortedSet(); - /// - /// Stores the verified low priority sorted transactions currently in the pool. + /// Stores the verified sorted transactins currently in the pool. /// - private readonly SortedSet _sortedLowPrioTransactions = new SortedSet(); + private readonly SortedSet _sortedTransactions = new SortedSet(); /// /// Store the unverified transactions currently in the pool. /// /// Transactions in this data structure were valid in some prior block, but may no longer be valid. /// The top ones that could make it into the next block get verified and moved into the verified data structures - /// (_unsortedTransactions, _sortedLowPrioTransactions, and _sortedHighPrioTransactions) after each block. + /// (_unsortedTransactions, and _sortedTransactions) after each block. /// private readonly Dictionary _unverifiedTransactions = new Dictionary(); - private readonly SortedSet _unverifiedSortedHighPriorityTransactions = new SortedSet(); - private readonly SortedSet _unverifiedSortedLowPriorityTransactions = new SortedSet(); + private readonly SortedSet _unverifiedSortedTransactions = new SortedSet(); // Internal methods to aid in unit testing - internal int SortedHighPrioTxCount => _sortedHighPrioTransactions.Count; - internal int SortedLowPrioTxCount => _sortedLowPrioTransactions.Count; - internal int UnverifiedSortedHighPrioTxCount => _unverifiedSortedHighPriorityTransactions.Count; - internal int UnverifiedSortedLowPrioTxCount => _unverifiedSortedLowPriorityTransactions.Count; + internal int SortedTxCount => _sortedTransactions.Count; + internal int UnverifiedSortedTxCount => _unverifiedSortedTransactions.Count; private int _maxTxPerBlock; - private int _maxLowPriorityTxPerBlock; - private long _feePerByte; /// /// Total maximum capacity of transactions the pool can hold. @@ -113,8 +102,6 @@ public MemoryPool(NeoSystem system, int capacity) internal void LoadPolicy(Snapshot snapshot) { _maxTxPerBlock = (int)NativeContract.Policy.GetMaxTransactionsPerBlock(snapshot); - _maxLowPriorityTxPerBlock = (int)NativeContract.Policy.GetMaxLowPriorityTransactionsPerBlock(snapshot); - _feePerByte = NativeContract.Policy.GetFeePerByte(snapshot); } /// @@ -129,8 +116,7 @@ public bool ContainsKey(UInt256 hash) _txRwLock.EnterReadLock(); try { - return _unsortedTransactions.ContainsKey(hash) - || _unverifiedTransactions.ContainsKey(hash); + return _unsortedTransactions.ContainsKey(hash) || _unverifiedTransactions.ContainsKey(hash); } finally { @@ -192,10 +178,8 @@ public void GetVerifiedAndUnverifiedTransactions(out IEnumerable ve _txRwLock.EnterReadLock(); try { - verifiedTransactions = _sortedHighPrioTransactions.Reverse().Select(p => p.Tx) - .Concat(_sortedLowPrioTransactions.Reverse().Select(p => p.Tx)).ToArray(); - unverifiedTransactions = _unverifiedSortedHighPriorityTransactions.Reverse().Select(p => p.Tx) - .Concat(_unverifiedSortedLowPriorityTransactions.Reverse().Select(p => p.Tx)).ToArray(); + verifiedTransactions = _sortedTransactions.Reverse().Select(p => p.Tx).ToArray(); + unverifiedTransactions = _unverifiedSortedTransactions.Reverse().Select(p => p.Tx).ToArray(); } finally { @@ -208,9 +192,7 @@ public IEnumerable GetSortedVerifiedTransactions() _txRwLock.EnterReadLock(); try { - return _sortedHighPrioTransactions.Reverse().Select(p => p.Tx) - .Concat(_sortedLowPrioTransactions.Reverse().Select(p => p.Tx)) - .ToArray(); + return _sortedTransactions.Reverse().Select(p => p.Tx).ToArray(); } finally { @@ -239,25 +221,16 @@ private PoolItem GetLowestFeeTransaction(SortedSet verifiedTxSorted, private PoolItem GetLowestFeeTransaction(out Dictionary unsortedTxPool, out SortedSet sortedPool) { - var minItem = GetLowestFeeTransaction(_sortedLowPrioTransactions, _unverifiedSortedLowPriorityTransactions, - out sortedPool); - - if (minItem != null) - { - unsortedTxPool = Object.ReferenceEquals(sortedPool, _unverifiedSortedLowPriorityTransactions) - ? _unverifiedTransactions : _unsortedTransactions; - return minItem; - } + sortedPool = null; try { - return GetLowestFeeTransaction(_sortedHighPrioTransactions, _unverifiedSortedHighPriorityTransactions, - out sortedPool); + return GetLowestFeeTransaction(_sortedTransactions, _unverifiedSortedTransactions, out sortedPool); } finally { - unsortedTxPool = Object.ReferenceEquals(sortedPool, _unverifiedSortedHighPriorityTransactions) - ? _unverifiedTransactions : _unsortedTransactions; + unsortedTxPool = Object.ReferenceEquals(sortedPool, _unverifiedSortedTransactions) + ? _unverifiedTransactions : _unsortedTransactions; } } @@ -269,12 +242,6 @@ internal bool CanTransactionFitInPool(Transaction tx) return GetLowestFeeTransaction(out _, out _).CompareTo(tx) <= 0; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsLowPriority(Transaction tx) - { - return tx.FeePerByte < _feePerByte; - } - /// /// Adds an already verified transaction to the memory pool. /// @@ -295,9 +262,8 @@ internal bool TryAdd(UInt256 hash, Transaction tx) try { _unsortedTransactions.Add(hash, poolItem); + _sortedTransactions.Add(poolItem); - SortedSet pool = IsLowPriority(tx) ? _sortedLowPrioTransactions : _sortedHighPrioTransactions; - pool.Add(poolItem); if (Count > Capacity) removedTransactions = RemoveOverCapacity(); } @@ -338,9 +304,8 @@ private bool TryRemoveVerified(UInt256 hash, out PoolItem item) return false; _unsortedTransactions.Remove(hash); - SortedSet pool = IsLowPriority(item.Tx) - ? _sortedLowPrioTransactions : _sortedHighPrioTransactions; - pool.Remove(item); + _sortedTransactions.Remove(item); + return true; } @@ -351,31 +316,22 @@ internal bool TryRemoveUnVerified(UInt256 hash, out PoolItem item) return false; _unverifiedTransactions.Remove(hash); - SortedSet pool = IsLowPriority(item.Tx) - ? _unverifiedSortedLowPriorityTransactions : _unverifiedSortedHighPriorityTransactions; - pool.Remove(item); + _unverifiedSortedTransactions.Remove(item); return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void InvalidateVerifiedTransactions() { - foreach (PoolItem item in _sortedHighPrioTransactions) + foreach (PoolItem item in _sortedTransactions) { if (_unverifiedTransactions.TryAdd(item.Tx.Hash, item)) - _unverifiedSortedHighPriorityTransactions.Add(item); - } - - foreach (PoolItem item in _sortedLowPrioTransactions) - { - if (_unverifiedTransactions.TryAdd(item.Tx.Hash, item)) - _unverifiedSortedLowPriorityTransactions.Add(item); + _unverifiedSortedTransactions.Add(item); } // Clear the verified transactions now, since they all must be reverified. _unsortedTransactions.Clear(); - _sortedHighPrioTransactions.Clear(); - _sortedLowPrioTransactions.Clear(); + _sortedTransactions.Clear(); } // Note: this must only be called from a single thread (the Blockchain actor) @@ -405,11 +361,8 @@ internal void UpdatePoolForBlockPersisted(Block block, Snapshot snapshot) return; LoadPolicy(snapshot); - - ReverifyTransactions(_sortedHighPrioTransactions, _unverifiedSortedHighPriorityTransactions, - _maxTxPerBlock, MaxSecondsToReverifyHighPrioTx, snapshot); - ReverifyTransactions(_sortedLowPrioTransactions, _unverifiedSortedLowPriorityTransactions, - _maxLowPriorityTxPerBlock, MaxSecondsToReverifyLowPrioTx, snapshot); + ReverifyTransactions(_sortedTransactions, _unverifiedSortedTransactions, + _maxTxPerBlock, MaxSecondsToReverifyTx, snapshot); } internal void InvalidateAllTransactions() @@ -446,7 +399,7 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, _txRwLock.EnterWriteLock(); try { - int blocksTillRebroadcast = Object.ReferenceEquals(unverifiedSortedTxPool, _sortedHighPrioTransactions) + int blocksTillRebroadcast = Object.ReferenceEquals(unverifiedSortedTxPool, _sortedTransactions) ? BlocksTillRebroadcastHighPriorityPoolTx : BlocksTillRebroadcastLowPriorityPoolTx; if (Count > RebroadcastMultiplierThreshold) @@ -504,23 +457,11 @@ internal bool ReVerifyTopUnverifiedTransactionsIfNeeded(int maxToVerify, Snapsho if (Blockchain.Singleton.Height < Blockchain.Singleton.HeaderHeight) return false; - if (_unverifiedSortedHighPriorityTransactions.Count > 0) - { - // Always leave at least 1 tx for low priority tx - int verifyCount = _sortedHighPrioTransactions.Count > _maxTxPerBlock || maxToVerify == 1 - ? 1 : maxToVerify - 1; - maxToVerify -= ReverifyTransactions(_sortedHighPrioTransactions, _unverifiedSortedHighPriorityTransactions, - verifyCount, MaxSecondsToReverifyHighPrioTxPerIdle, snapshot); - - if (maxToVerify == 0) maxToVerify++; - } - - if (_unverifiedSortedLowPriorityTransactions.Count > 0) + if (_unverifiedSortedTransactions.Count > 0) { - int verifyCount = _sortedLowPrioTransactions.Count > _maxLowPriorityTxPerBlock - ? 1 : maxToVerify; - ReverifyTransactions(_sortedLowPrioTransactions, _unverifiedSortedLowPriorityTransactions, - verifyCount, MaxSecondsToReverifyLowPrioTxPerIdle, snapshot); + int verifyCount = _sortedTransactions.Count > _maxTxPerBlock ? 1 : maxToVerify; + ReverifyTransactions(_sortedTransactions, _unverifiedSortedTransactions, + verifyCount, MaxSecondsToReverifyTxPerIdle, snapshot); } return _unverifiedTransactions.Count > 0; diff --git a/neo/Network/P2P/Payloads/BlockBase.cs b/neo/Network/P2P/Payloads/BlockBase.cs index 769b457e9e..9f56ca0d97 100644 --- a/neo/Network/P2P/Payloads/BlockBase.cs +++ b/neo/Network/P2P/Payloads/BlockBase.cs @@ -17,7 +17,7 @@ public abstract class BlockBase : IVerifiable public uint Timestamp; public uint Index; public UInt160 NextConsensus; - public Witness Witness { get; set; } + public Witness Witness; private UInt256 _hash = null; public UInt256 Hash @@ -32,11 +32,25 @@ public UInt256 Hash } } - public virtual int Size => sizeof(uint) + PrevHash.Size + MerkleRoot.Size + sizeof(uint) + sizeof(uint) + NextConsensus.Size + Witness.Size; + public virtual int Size => sizeof(uint) + PrevHash.Size + MerkleRoot.Size + sizeof(uint) + sizeof(uint) + NextConsensus.Size + 1 + Witness.Size; + + Witness[] IVerifiable.Witnesses + { + get + { + return new[] { Witness }; + } + set + { + if (value.Length != 1) throw new ArgumentException(); + Witness = value[0]; + } + } public virtual void Deserialize(BinaryReader reader) { ((IVerifiable)this).DeserializeUnsigned(reader); + if (reader.ReadByte() != 1) throw new FormatException(); Witness = reader.ReadSerializable(); } @@ -50,18 +64,18 @@ void IVerifiable.DeserializeUnsigned(BinaryReader reader) NextConsensus = reader.ReadSerializable(); } - UInt160 IVerifiable.GetScriptHashForVerification(Snapshot snapshot) + UInt160[] IVerifiable.GetScriptHashesForVerifying(Snapshot snapshot) { - if (PrevHash == UInt256.Zero) return Witness.ScriptHash; + if (PrevHash == UInt256.Zero) return new[] { Witness.ScriptHash }; Header prev_header = snapshot.GetHeader(PrevHash); if (prev_header == null) throw new InvalidOperationException(); - return prev_header.NextConsensus; + return new[] { prev_header.NextConsensus }; } public virtual void Serialize(BinaryWriter writer) { ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write(Witness); + writer.Write((byte)1); writer.Write(Witness); } void IVerifiable.SerializeUnsigned(BinaryWriter writer) @@ -85,7 +99,7 @@ public virtual JObject ToJson() json["time"] = Timestamp; json["index"] = Index; json["nextconsensus"] = NextConsensus.ToAddress(); - json["witness"] = Witness.ToJson(); + json["witnesses"] = new JArray(Witness.ToJson()); return json; } @@ -95,7 +109,7 @@ public virtual bool Verify(Snapshot snapshot) if (prev_header == null) return false; if (prev_header.Index + 1 != Index) return false; if (prev_header.Timestamp >= Timestamp) return false; - if (!this.VerifyWitness(snapshot, 1_00000000)) return false; + if (!this.VerifyWitnesses(snapshot, 1_00000000)) return false; return true; } } diff --git a/neo/Network/P2P/Payloads/ConsensusPayload.cs b/neo/Network/P2P/Payloads/ConsensusPayload.cs index cdbf459418..7a20e7c46b 100644 --- a/neo/Network/P2P/Payloads/ConsensusPayload.cs +++ b/neo/Network/P2P/Payloads/ConsensusPayload.cs @@ -17,7 +17,7 @@ public class ConsensusPayload : IInventory public uint BlockIndex; public ushort ValidatorIndex; public byte[] Data; - public Witness Witness { get; set; } + public Witness Witness; private ConsensusMessage _deserializedMessage = null; public ConsensusMessage ConsensusMessage @@ -60,7 +60,20 @@ public UInt256 Hash sizeof(ushort) + //ValidatorIndex sizeof(uint) + //Timestamp Data.GetVarSize() + //Data - Witness.Size; //Witness + 1 + Witness.Size; //Witness + + Witness[] IVerifiable.Witnesses + { + get + { + return new[] { Witness }; + } + set + { + if (value.Length != 1) throw new ArgumentException(); + Witness = value[0]; + } + } public T GetDeserializedMessage() where T : ConsensusMessage { @@ -70,6 +83,7 @@ public T GetDeserializedMessage() where T : ConsensusMessage void ISerializable.Deserialize(BinaryReader reader) { ((IVerifiable)this).DeserializeUnsigned(reader); + if (reader.ReadByte() != 1) throw new FormatException(); Witness = reader.ReadSerializable(); } @@ -82,18 +96,18 @@ void IVerifiable.DeserializeUnsigned(BinaryReader reader) Data = reader.ReadVarBytes(); } - UInt160 IVerifiable.GetScriptHashForVerification(Snapshot snapshot) + UInt160[] IVerifiable.GetScriptHashesForVerifying(Snapshot snapshot) { ECPoint[] validators = NativeContract.NEO.GetNextBlockValidators(snapshot); if (validators.Length <= ValidatorIndex) throw new InvalidOperationException(); - return Contract.CreateSignatureRedeemScript(validators[ValidatorIndex]).ToScriptHash(); + return new[] { Contract.CreateSignatureRedeemScript(validators[ValidatorIndex]).ToScriptHash() }; } void ISerializable.Serialize(BinaryWriter writer) { ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write(Witness); + writer.Write((byte)1); writer.Write(Witness); } void IVerifiable.SerializeUnsigned(BinaryWriter writer) @@ -109,7 +123,7 @@ public bool Verify(Snapshot snapshot) { if (BlockIndex <= snapshot.Height) return false; - return this.VerifyWitness(snapshot, 0_02000000); + return this.VerifyWitnesses(snapshot, 0_02000000); } } } diff --git a/neo/Network/P2P/Payloads/IVerifiable.cs b/neo/Network/P2P/Payloads/IVerifiable.cs index 87a7dcb701..50651ad9ff 100644 --- a/neo/Network/P2P/Payloads/IVerifiable.cs +++ b/neo/Network/P2P/Payloads/IVerifiable.cs @@ -6,11 +6,11 @@ namespace Neo.Network.P2P.Payloads { public interface IVerifiable : ISerializable { - Witness Witness { get; set; } + Witness[] Witnesses { get; set; } void DeserializeUnsigned(BinaryReader reader); - UInt160 GetScriptHashForVerification(Snapshot snapshot); + UInt160[] GetScriptHashesForVerifying(Snapshot snapshot); void SerializeUnsigned(BinaryWriter writer); } diff --git a/neo/Network/P2P/Payloads/Transaction.cs b/neo/Network/P2P/Payloads/Transaction.cs index b21f02ccbb..152ac12b59 100644 --- a/neo/Network/P2P/Payloads/Transaction.cs +++ b/neo/Network/P2P/Payloads/Transaction.cs @@ -1,11 +1,9 @@ using Neo.Cryptography; using Neo.IO; using Neo.IO.Json; -using Neo.Ledger; using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; -using Neo.VM; using Neo.Wallets; using System; using System.Collections.Generic; @@ -23,17 +21,22 @@ public class Transaction : IEquatable, IInventory /// Maximum number of attributes that can be contained within a transaction /// private const int MaxTransactionAttributes = 16; - private const long VerificationGasLimited = 0_10000000; public byte Version; public uint Nonce; - public byte[] Script; public UInt160 Sender; - public long Gas; + /// + /// Distributed to NEO holders. + /// + public long SystemFee; + /// + /// Distributed to consensus nodes. + /// public long NetworkFee; public uint ValidUntilBlock; public TransactionAttribute[] Attributes; - public Witness Witness { get; set; } + public byte[] Script; + public Witness[] Witnesses { get; set; } /// /// The NetworkFee for the transaction divided by its Size. @@ -56,63 +59,23 @@ public UInt256 Hash InventoryType IInventory.InventoryType => InventoryType.TX; - public int Size => - sizeof(byte) + //Version - sizeof(uint) + //Nonce - Script.GetVarSize() + //Script - Sender.Size + //Sender - sizeof(long) + //Gas - sizeof(long) + //NetworkFee - sizeof(uint) + //ValidUntilBlock - Attributes.GetVarSize() + //Attributes - Witness.Size; //Witnesses + public const int HeaderSize = + sizeof(byte) + //Version + sizeof(uint) + //Nonce + 20 + //Sender + sizeof(long) + //Gas + sizeof(long) + //NetworkFee + sizeof(uint); //ValidUntilBlock - public void CalculateFees() - { - if (Sender is null) Sender = UInt160.Zero; - if (Attributes is null) Attributes = new TransactionAttribute[0]; - if (Witness is null) Witness = new Witness - { - InvocationScript = new byte[65], - VerificationScript = new byte[39] - }; - _hash = null; - long consumed; - using (ApplicationEngine engine = ApplicationEngine.Run(Script, this)) - { - if (engine.State.HasFlag(VMState.FAULT)) - throw new InvalidOperationException(); - consumed = engine.GasConsumed; - } - _hash = null; - long d = (long)NativeContract.GAS.Factor; - Gas = consumed - ApplicationEngine.GasFree; - if (Gas <= 0) - { - Gas = 0; - } - else - { - long remainder = Gas % d; - if (remainder == 0) return; - if (remainder > 0) - Gas += d - remainder; - else - Gas -= remainder; - } - using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) - { - long feeperbyte = NativeContract.Policy.GetFeePerByte(snapshot); - long fee = feeperbyte * Size; - if (fee > NetworkFee) - NetworkFee = fee; - } - } + public int Size => HeaderSize + + Attributes.GetVarSize() + //Attributes + Script.GetVarSize() + //Script + Witnesses.GetVarSize(); //Witnesses void ISerializable.Deserialize(BinaryReader reader) { DeserializeUnsigned(reader); - Witness = reader.ReadSerializable(); + Witnesses = reader.ReadSerializableArray(); } public void DeserializeUnsigned(BinaryReader reader) @@ -120,17 +83,19 @@ public void DeserializeUnsigned(BinaryReader reader) Version = reader.ReadByte(); if (Version > 0) throw new FormatException(); Nonce = reader.ReadUInt32(); - Script = reader.ReadVarBytes(ushort.MaxValue); - if (Script.Length == 0) throw new FormatException(); Sender = reader.ReadSerializable(); - Gas = reader.ReadInt64(); - if (Gas < 0) throw new FormatException(); - if (Gas % NativeContract.GAS.Factor != 0) throw new FormatException(); + SystemFee = reader.ReadInt64(); + if (SystemFee < 0) throw new FormatException(); + if (SystemFee % NativeContract.GAS.Factor != 0) throw new FormatException(); NetworkFee = reader.ReadInt64(); if (NetworkFee < 0) throw new FormatException(); - if (Gas + NetworkFee < Gas) throw new FormatException(); + if (SystemFee + NetworkFee < SystemFee) throw new FormatException(); ValidUntilBlock = reader.ReadUInt32(); Attributes = reader.ReadSerializableArray(MaxTransactionAttributes); + var cosigners = Attributes.Where(p => p.Usage == TransactionAttributeUsage.Cosigner).Select(p => new UInt160(p.Data)).ToArray(); + if (cosigners.Distinct().Count() != cosigners.Length) throw new FormatException(); + Script = reader.ReadVarBytes(ushort.MaxValue); + if (Script.Length == 0) throw new FormatException(); } public bool Equals(Transaction other) @@ -150,27 +115,29 @@ public override int GetHashCode() return Hash.GetHashCode(); } - public UInt160 GetScriptHashForVerification(Snapshot snapshot) + public UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { - return Sender; + var hashes = new HashSet { Sender }; + hashes.UnionWith(Attributes.Where(p => p.Usage == TransactionAttributeUsage.Cosigner).Select(p => new UInt160(p.Data))); + return hashes.OrderBy(p => p).ToArray(); } void ISerializable.Serialize(BinaryWriter writer) { ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write(Witness); + writer.Write(Witnesses); } void IVerifiable.SerializeUnsigned(BinaryWriter writer) { writer.Write(Version); writer.Write(Nonce); - writer.WriteVarBytes(Script); writer.Write(Sender); - writer.Write(Gas); + writer.Write(SystemFee); writer.Write(NetworkFee); writer.Write(ValidUntilBlock); writer.Write(Attributes); + writer.WriteVarBytes(Script); } public JObject ToJson() @@ -180,13 +147,13 @@ public JObject ToJson() json["size"] = Size; json["version"] = Version; json["nonce"] = Nonce; - json["script"] = Script.ToHexString(); json["sender"] = Sender.ToAddress(); - json["gas"] = new BigDecimal(Gas, (byte)NativeContract.GAS.Decimals).ToString(); - json["net_fee"] = new BigDecimal(NetworkFee, (byte)NativeContract.GAS.Decimals).ToString(); + json["sys_fee"] = new BigDecimal(SystemFee, NativeContract.GAS.Decimals).ToString(); + json["net_fee"] = new BigDecimal(NetworkFee, NativeContract.GAS.Decimals).ToString(); json["valid_until_block"] = ValidUntilBlock; json["attributes"] = Attributes.Select(p => p.ToJson()).ToArray(); - json["witness"] = Witness.ToJson(); + json["script"] = Script.ToHexString(); + json["witnesses"] = Witnesses.Select(p => p.ToJson()).ToArray(); return json; } @@ -201,16 +168,16 @@ public virtual bool Verify(Snapshot snapshot, IEnumerable mempool) return false; int size = Size; if (size > MaxTransactionSize) return false; - if (size > NativeContract.Policy.GetMaxLowPriorityTransactionSize(snapshot) && NetworkFee / size < NativeContract.Policy.GetFeePerByte(snapshot)) - return false; - if (NativeContract.Policy.GetBlockedAccounts(snapshot).Contains(Sender)) + long net_fee = NetworkFee - size * NativeContract.Policy.GetFeePerByte(snapshot); + if (net_fee < 0) return false; + if (NativeContract.Policy.GetBlockedAccounts(snapshot).Intersect(GetScriptHashesForVerifying(snapshot)).Count() > 0) return false; BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, Sender); - BigInteger fee = Gas + NetworkFee; + BigInteger fee = SystemFee + NetworkFee; if (balance < fee) return false; - fee += mempool.Where(p => p != this && p.Sender.Equals(Sender)).Sum(p => p.Gas + p.NetworkFee); + fee += mempool.Where(p => p != this && p.Sender.Equals(Sender)).Select(p => (BigInteger)(p.SystemFee + p.NetworkFee)).Sum(); if (balance < fee) return false; - return this.VerifyWitness(snapshot, VerificationGasLimited); + return this.VerifyWitnesses(snapshot, net_fee); } } } diff --git a/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs b/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs index 89c1a1cec2..d878348c28 100644 --- a/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs +++ b/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs @@ -2,6 +2,7 @@ { public enum TransactionAttributeUsage : byte { + Cosigner = 0x20, Url = 0x81 } } diff --git a/neo/SmartContract/ContractParametersContext.cs b/neo/SmartContract/ContractParametersContext.cs index 8af153119d..8fda2c8450 100644 --- a/neo/SmartContract/ContractParametersContext.cs +++ b/neo/SmartContract/ContractParametersContext.cs @@ -15,65 +15,101 @@ namespace Neo.SmartContract { public class ContractParametersContext { + private class ContextItem + { + public byte[] Script; + public ContractParameter[] Parameters; + public Dictionary Signatures; + + private ContextItem() { } + + public ContextItem(Contract contract) + { + this.Script = contract.Script; + this.Parameters = contract.ParameterList.Select(p => new ContractParameter { Type = p }).ToArray(); + } + + public static ContextItem FromJson(JObject json) + { + return new ContextItem + { + Script = json["script"]?.AsString().HexToBytes(), + Parameters = ((JArray)json["parameters"]).Select(p => ContractParameter.FromJson(p)).ToArray(), + Signatures = json["signatures"]?.Properties.Select(p => new + { + PublicKey = ECPoint.Parse(p.Key, ECCurve.Secp256r1), + Signature = p.Value.AsString().HexToBytes() + }).ToDictionary(p => p.PublicKey, p => p.Signature) + }; + } + + public JObject ToJson() + { + JObject json = new JObject(); + if (Script != null) + json["script"] = Script.ToHexString(); + json["parameters"] = new JArray(Parameters.Select(p => p.ToJson())); + if (Signatures != null) + { + json["signatures"] = new JObject(); + foreach (var signature in Signatures) + json["signatures"][signature.Key.ToString()] = signature.Value.ToHexString(); + } + return json; + } + } + public readonly IVerifiable Verifiable; - private byte[] Script; - private ContractParameter[] Parameters; - private Dictionary Signatures; + private readonly Dictionary ContextItems; public bool Completed { get { - if (Parameters is null) return false; - return Parameters.All(p => p.Value != null); + if (ContextItems.Count < ScriptHashes.Count) + return false; + return ContextItems.Values.All(p => p != null && p.Parameters.All(q => q.Value != null)); } } - private UInt160 _ScriptHash = null; - public UInt160 ScriptHash + private UInt160[] _ScriptHashes = null; + public IReadOnlyList ScriptHashes { get { - if (_ScriptHash == null) + if (_ScriptHashes == null) using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) { - _ScriptHash = Verifiable.GetScriptHashForVerification(snapshot); + _ScriptHashes = Verifiable.GetScriptHashesForVerifying(snapshot); } - return _ScriptHash; + return _ScriptHashes; } } public ContractParametersContext(IVerifiable verifiable) { this.Verifiable = verifiable; + this.ContextItems = new Dictionary(); } public bool Add(Contract contract, int index, object parameter) { - if (!ScriptHash.Equals(contract.ScriptHash)) return false; - if (Parameters is null) - { - Script = contract.Script; - Parameters = contract.ParameterList.Select(p => new ContractParameter { Type = p }).ToArray(); - } - Parameters[index].Value = parameter; + ContextItem item = CreateItem(contract); + if (item == null) return false; + item.Parameters[index].Value = parameter; return true; } public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) { - if (contract.Script.IsMultiSigContract()) + if (contract.Script.IsMultiSigContract(out _, out _)) { - if (!ScriptHash.Equals(contract.ScriptHash)) return false; - if (Parameters is null) - { - Script = contract.Script; - Parameters = contract.ParameterList.Select(p => new ContractParameter { Type = p }).ToArray(); - } - if (Parameters.All(p => p.Value != null)) return false; - if (Signatures == null) - Signatures = new Dictionary(); - else if (Signatures.ContainsKey(pubkey)) + ContextItem item = CreateItem(contract); + if (item == null) return false; + if (item.Parameters.All(p => p.Value != null)) return false; + if (item.Signatures == null) + item.Signatures = new Dictionary(); + else if (item.Signatures.ContainsKey(pubkey)) return false; List points = new List(); { @@ -94,15 +130,15 @@ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) } } if (!points.Contains(pubkey)) return false; - Signatures.Add(pubkey, signature); - if (Signatures.Count == contract.ParameterList.Length) + item.Signatures.Add(pubkey, signature); + if (item.Signatures.Count == contract.ParameterList.Length) { Dictionary dic = points.Select((p, i) => new { PublicKey = p, Index = i }).ToDictionary(p => p.PublicKey, p => p.Index); - byte[][] sigs = Signatures.Select(p => new + byte[][] sigs = item.Signatures.Select(p => new { Signature = p.Value, Index = dic[p.Key] @@ -110,7 +146,7 @@ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) for (int i = 0; i < sigs.Length; i++) if (!Add(contract, i, sigs[i])) throw new InvalidOperationException(); - Signatures = null; + item.Signatures = null; } return true; } @@ -134,52 +170,69 @@ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) } } + private ContextItem CreateItem(Contract contract) + { + if (ContextItems.TryGetValue(contract.ScriptHash, out ContextItem item)) + return item; + if (!ScriptHashes.Contains(contract.ScriptHash)) + return null; + item = new ContextItem(contract); + ContextItems.Add(contract.ScriptHash, item); + return item; + } + public static ContractParametersContext FromJson(JObject json) { - IVerifiable verifiable = typeof(ContractParametersContext).GetTypeInfo().Assembly.CreateInstance(json["type"].AsString()) as IVerifiable; - if (verifiable == null) throw new FormatException(); + var type = typeof(ContractParametersContext).GetTypeInfo().Assembly.GetType(json["type"].AsString()); + if (!typeof(IVerifiable).IsAssignableFrom(type)) throw new FormatException(); + + var verifiable = (IVerifiable)Activator.CreateInstance(type); using (MemoryStream ms = new MemoryStream(json["hex"].AsString().HexToBytes(), false)) using (BinaryReader reader = new BinaryReader(ms, Encoding.UTF8)) { verifiable.DeserializeUnsigned(reader); } - return new ContractParametersContext(verifiable) + ContractParametersContext context = new ContractParametersContext(verifiable); + foreach (var property in json["items"].Properties) { - Script = json["script"]?.AsString().HexToBytes(), - Parameters = ((JArray)json["parameters"])?.Select(p => ContractParameter.FromJson(p)).ToArray(), - Signatures = json["signatures"]?.Properties.Select(p => new - { - PublicKey = ECPoint.Parse(p.Key, ECCurve.Secp256r1), - Signature = p.Value.AsString().HexToBytes() - }).ToDictionary(p => p.PublicKey, p => p.Signature) - }; + context.ContextItems.Add(UInt160.Parse(property.Key), ContextItem.FromJson(property.Value)); + } + return context; } - public ContractParameter GetParameter(int index) + public ContractParameter GetParameter(UInt160 scriptHash, int index) { - return GetParameters()?[index]; + return GetParameters(scriptHash)?[index]; } - public IReadOnlyList GetParameters() + public IReadOnlyList GetParameters(UInt160 scriptHash) { - return Parameters; + if (!ContextItems.TryGetValue(scriptHash, out ContextItem item)) + return null; + return item.Parameters; } - public Witness GetWitness() + public Witness[] GetWitnesses() { if (!Completed) throw new InvalidOperationException(); - using (ScriptBuilder sb = new ScriptBuilder()) + Witness[] witnesses = new Witness[ScriptHashes.Count]; + for (int i = 0; i < ScriptHashes.Count; i++) { - foreach (ContractParameter parameter in Parameters.Reverse()) + ContextItem item = ContextItems[ScriptHashes[i]]; + using (ScriptBuilder sb = new ScriptBuilder()) { - sb.EmitPush(parameter); + foreach (ContractParameter parameter in item.Parameters.Reverse()) + { + sb.EmitPush(parameter); + } + witnesses[i] = new Witness + { + InvocationScript = sb.ToArray(), + VerificationScript = item.Script ?? new byte[0] + }; } - return new Witness - { - InvocationScript = sb.ToArray(), - VerificationScript = Script ?? new byte[0] - }; } + return witnesses; } public static ContractParametersContext Parse(string value) @@ -198,16 +251,9 @@ public JObject ToJson() writer.Flush(); json["hex"] = ms.ToArray().ToHexString(); } - if (Script != null) - json["script"] = Script.ToHexString(); - if (Parameters != null) - json["parameters"] = new JArray(Parameters.Select(p => p.ToJson())); - if (Signatures != null) - { - json["signatures"] = new JObject(); - foreach (var signature in Signatures) - json["signatures"][signature.Key.ToString()] = signature.Value.ToHexString(); - } + json["items"] = new JObject(); + foreach (var item in ContextItems) + json["items"][item.Key.ToString()] = item.Value.ToJson(); return json; } diff --git a/neo/SmartContract/Helper.cs b/neo/SmartContract/Helper.cs index ddd16de13e..f40dad212e 100644 --- a/neo/SmartContract/Helper.cs +++ b/neo/SmartContract/Helper.cs @@ -108,9 +108,9 @@ private static StackItem DeserializeStackItem(BinaryReader reader, uint maxArray return stack_temp.Peek(); } - public static bool IsMultiSigContract(this byte[] script) + public static bool IsMultiSigContract(this byte[] script, out int m, out int n) { - int m, n = 0; + m = 0; n = 0; int i = 0; if (script.Length < 41) return false; if (script[i] > (byte)OpCode.PUSH16) return false; @@ -170,7 +170,7 @@ public static bool IsSignatureContract(this byte[] script) public static bool IsStandardContract(this byte[] script) { - return script.IsSignatureContract() || script.IsMultiSigContract(); + return script.IsSignatureContract() || script.IsMultiSigContract(out _, out _); } public static byte[] Serialize(this StackItem item) @@ -246,33 +246,39 @@ public static UInt160 ToScriptHash(this byte[] script) return new UInt160(Crypto.Default.Hash160(script)); } - internal static bool VerifyWitness(this IVerifiable verifiable, Snapshot snapshot, long gas) + internal static bool VerifyWitnesses(this IVerifiable verifiable, Snapshot snapshot, long gas) { - UInt160 hash; + if (gas < 0) return false; + + UInt160[] hashes; try { - hash = verifiable.GetScriptHashForVerification(snapshot); + hashes = verifiable.GetScriptHashesForVerifying(snapshot); } catch (InvalidOperationException) { return false; } - byte[] verification = verifiable.Witness.VerificationScript; - if (verification.Length == 0) - { - verification = snapshot.Contracts.TryGet(hash)?.Script; - if (verification is null) return false; - } - else + if (hashes.Length != verifiable.Witnesses.Length) return false; + for (int i = 0; i < hashes.Length; i++) { - if (hash != verifiable.Witness.ScriptHash) return false; - } - using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, gas)) - { - engine.LoadScript(verification); - engine.LoadScript(verifiable.Witness.InvocationScript); - if (engine.Execute().HasFlag(VMState.FAULT)) return false; - if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) return false; + byte[] verification = verifiable.Witnesses[i].VerificationScript; + if (verification.Length == 0) + { + verification = snapshot.Contracts.TryGet(hashes[i])?.Script; + if (verification is null) return false; + } + else + { + if (hashes[i] != verifiable.Witnesses[i].ScriptHash) return false; + } + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, gas)) + { + engine.LoadScript(verification); + engine.LoadScript(verifiable.Witnesses[i].InvocationScript); + if (engine.Execute().HasFlag(VMState.FAULT)) return false; + if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) return false; + } } return true; } diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index d1e1d50aa3..a496497962 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -25,7 +25,8 @@ static partial class InteropService public static readonly uint Neo_Header_GetMerkleRoot = Register("Neo.Header.GetMerkleRoot", Header_GetMerkleRoot, 0_00000400); public static readonly uint Neo_Header_GetNextConsensus = Register("Neo.Header.GetNextConsensus", Header_GetNextConsensus, 0_00000400); public static readonly uint Neo_Transaction_GetScript = Register("Neo.Transaction.GetScript", Transaction_GetScript, 0_00000400); - public static readonly uint Neo_Transaction_GetWitnessScript = Register("Neo.Transaction.GetWitnessScript", Transaction_GetWitnessScript, 0_00000400); + public static readonly uint Neo_Transaction_GetWitnesses = Register("Neo.Transaction.GetWitnesses", Transaction_GetWitnesses, 0_00010000); + public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 0_00000400); public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 0_00030000); public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice); public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice); @@ -206,16 +207,27 @@ private static bool Transaction_GetScript(ApplicationEngine engine) return false; } - private static bool Transaction_GetWitnessScript(ApplicationEngine engine) + private static bool Transaction_GetWitnesses(ApplicationEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { Transaction tx = _interface.GetInterface(); if (tx == null) return false; - byte[] script = tx.Witness.VerificationScript; - if (script.Length == 0) - script = engine.Snapshot.Contracts[tx.Sender].Script; - engine.CurrentContext.EvaluationStack.Push(script); + if (tx.Witnesses.Length > engine.MaxArraySize) + return false; + engine.CurrentContext.EvaluationStack.Push(WitnessWrapper.Create(tx, engine.Snapshot).Select(p => StackItem.FromInterface(p)).ToArray()); + return true; + } + return false; + } + + private static bool Witness_GetVerificationScript(ApplicationEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + WitnessWrapper witness = _interface.GetInterface(); + if (witness == null) return false; + engine.CurrentContext.EvaluationStack.Push(witness.VerificationScript); return true; } return false; diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index ac75c421dc..2574bd3753 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -147,7 +147,8 @@ private static bool Runtime_GetTrigger(ApplicationEngine engine) internal static bool CheckWitness(ApplicationEngine engine, UInt160 hash) { - return hash.Equals(engine.ScriptContainer.GetScriptHashForVerification(engine.Snapshot)); + var _hashes_for_verifying = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); + return _hashes_for_verifying.Contains(hash); } private static bool CheckWitness(ApplicationEngine engine, ECPoint pubkey) diff --git a/neo/SmartContract/Native/NativeContract.cs b/neo/SmartContract/Native/NativeContract.cs index 22c54ac127..41a700ee9c 100644 --- a/neo/SmartContract/Native/NativeContract.cs +++ b/neo/SmartContract/Native/NativeContract.cs @@ -79,7 +79,7 @@ protected StorageKey CreateStorageKey(byte prefix, byte[] key = null) return storageKey; } - protected StorageKey CreateStorageKey(byte prefix, ISerializable key) + internal protected StorageKey CreateStorageKey(byte prefix, ISerializable key) { return CreateStorageKey(prefix, key.ToArray()); } diff --git a/neo/SmartContract/Native/PolicyContract.cs b/neo/SmartContract/Native/PolicyContract.cs index e5f8dbeefe..03781a5cb7 100644 --- a/neo/SmartContract/Native/PolicyContract.cs +++ b/neo/SmartContract/Native/PolicyContract.cs @@ -18,8 +18,6 @@ public sealed class PolicyContract : NativeContract public override string ServiceName => "Neo.Native.Policy"; private const byte Prefix_MaxTransactionsPerBlock = 23; - private const byte Prefix_MaxLowPriorityTransactionsPerBlock = 34; - private const byte Prefix_MaxLowPriorityTransactionSize = 29; private const byte Prefix_FeePerByte = 10; private const byte Prefix_BlockedAccounts = 15; @@ -42,14 +40,6 @@ internal override bool Initialize(ApplicationEngine engine) { Value = BitConverter.GetBytes(512u) }); - engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_MaxLowPriorityTransactionsPerBlock), new StorageItem - { - Value = BitConverter.GetBytes(20u) - }); - engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_MaxLowPriorityTransactionSize), new StorageItem - { - Value = BitConverter.GetBytes(256u) - }); engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem { Value = BitConverter.GetBytes(1000L) @@ -72,28 +62,6 @@ public uint GetMaxTransactionsPerBlock(Snapshot snapshot) return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxTransactionsPerBlock)].Value, 0); } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] - private StackItem GetMaxLowPriorityTransactionsPerBlock(ApplicationEngine engine, VMArray args) - { - return GetMaxLowPriorityTransactionsPerBlock(engine.Snapshot); - } - - public uint GetMaxLowPriorityTransactionsPerBlock(Snapshot snapshot) - { - return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxLowPriorityTransactionsPerBlock)].Value, 0); - } - - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] - private StackItem GetMaxLowPriorityTransactionSize(ApplicationEngine engine, VMArray args) - { - return GetMaxLowPriorityTransactionSize(engine.Snapshot); - } - - public uint GetMaxLowPriorityTransactionSize(Snapshot snapshot) - { - return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxLowPriorityTransactionSize)].Value, 0); - } - [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] private StackItem GetFeePerByte(ApplicationEngine engine, VMArray args) { @@ -126,26 +94,6 @@ private StackItem SetMaxTransactionsPerBlock(ApplicationEngine engine, VMArray a return true; } - [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "value" }, AllowedTriggers = TriggerType.Application)] - private StackItem SetMaxLowPriorityTransactionsPerBlock(ApplicationEngine engine, VMArray args) - { - if (!CheckValidators(engine)) return false; - uint value = (uint)args[0].GetBigInteger(); - StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxLowPriorityTransactionsPerBlock)); - storage.Value = BitConverter.GetBytes(value); - return true; - } - - [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "value" }, AllowedTriggers = TriggerType.Application)] - private StackItem SetMaxLowPriorityTransactionSize(ApplicationEngine engine, VMArray args) - { - if (!CheckValidators(engine)) return false; - uint value = (uint)args[0].GetBigInteger(); - StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxLowPriorityTransactionSize)); - storage.Value = BitConverter.GetBytes(value); - return true; - } - [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "value" }, AllowedTriggers = TriggerType.Application)] private StackItem SetFeePerByte(ApplicationEngine engine, VMArray args) { diff --git a/neo/SmartContract/Native/Tokens/GasToken.cs b/neo/SmartContract/Native/Tokens/GasToken.cs index 0bcc303fe7..ca74a41855 100644 --- a/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/neo/SmartContract/Native/Tokens/GasToken.cs @@ -29,11 +29,11 @@ protected override bool OnPersist(ApplicationEngine engine) { if (!base.OnPersist(engine)) return false; foreach (Transaction tx in engine.Snapshot.PersistingBlock.Transactions) - Burn(engine, tx.Sender, tx.Gas + tx.NetworkFee); + Burn(engine, tx.Sender, tx.SystemFee + tx.NetworkFee); ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot); UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.Snapshot.PersistingBlock.ConsensusData.PrimaryIndex]).ToScriptHash(); Mint(engine, primary, engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.NetworkFee)); - BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.Gas); + BigInteger sys_fee = GetSysFeeAmount(engine.Snapshot, engine.Snapshot.PersistingBlock.Index - 1) + engine.Snapshot.PersistingBlock.Transactions.Sum(p => p.SystemFee); StorageKey key = CreateStorageKey(Prefix_SystemFeeAmount, BitConverter.GetBytes(engine.Snapshot.PersistingBlock.Index)); engine.Snapshot.Storages.Add(key, new StorageItem { @@ -52,7 +52,7 @@ private StackItem GetSysFeeAmount(ApplicationEngine engine, VMArray args) public BigInteger GetSysFeeAmount(Snapshot snapshot, uint index) { - if (index == 0) return Blockchain.GenesisBlock.Transactions.Sum(p => p.Gas); + if (index == 0) return Blockchain.GenesisBlock.Transactions.Sum(p => p.SystemFee); StorageKey key = CreateStorageKey(Prefix_SystemFeeAmount, BitConverter.GetBytes(index)); StorageItem storage = snapshot.Storages.TryGet(key); if (storage is null) return BigInteger.Zero; diff --git a/neo/SmartContract/WitnessWrapper.cs b/neo/SmartContract/WitnessWrapper.cs new file mode 100644 index 0000000000..f67690cad5 --- /dev/null +++ b/neo/SmartContract/WitnessWrapper.cs @@ -0,0 +1,27 @@ +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using System.Linq; + +namespace Neo.SmartContract +{ + internal class WitnessWrapper + { + public byte[] VerificationScript; + + public static WitnessWrapper[] Create(IVerifiable verifiable, Snapshot snapshot) + { + WitnessWrapper[] wrappers = verifiable.Witnesses.Select(p => new WitnessWrapper + { + VerificationScript = p.VerificationScript + }).ToArray(); + if (wrappers.Any(p => p.VerificationScript.Length == 0)) + { + UInt160[] hashes = verifiable.GetScriptHashesForVerifying(snapshot); + for (int i = 0; i < wrappers.Length; i++) + if (wrappers[i].VerificationScript.Length == 0) + wrappers[i].VerificationScript = snapshot.Contracts[hashes[i]].Script; + } + return wrappers; + } + } +} diff --git a/neo/Wallets/Wallet.cs b/neo/Wallets/Wallet.cs index a7578651d0..6358a02995 100644 --- a/neo/Wallets/Wallet.cs +++ b/neo/Wallets/Wallet.cs @@ -1,4 +1,5 @@ using Neo.Cryptography; +using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -18,8 +19,6 @@ namespace Neo.Wallets { public abstract class Wallet { - private static readonly Random rand = new Random(); - public abstract string Name { get; } public abstract Version Version { get; } @@ -49,27 +48,62 @@ public WalletAccount CreateAccount(Contract contract, byte[] privateKey) return CreateAccount(contract, new KeyPair(privateKey)); } - public void FillTransaction(Transaction tx, UInt160 sender = null) + private List<(UInt160 Account, BigInteger Value)> FindPayingAccounts(List<(UInt160 Account, BigInteger Value)> orderedAccounts, BigInteger amount) { - if (tx.Nonce == 0) - tx.Nonce = (uint)rand.Next(); - if (tx.ValidUntilBlock == 0) - using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) - tx.ValidUntilBlock = snapshot.Height + Transaction.MaxValidUntilBlockIncrement; - tx.CalculateFees(); - UInt160[] accounts = sender is null ? GetAccounts().Where(p => !p.Lock && !p.WatchOnly).Select(p => p.ScriptHash).ToArray() : new[] { sender }; - BigInteger fee = tx.Gas + tx.NetworkFee; - using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) - foreach (UInt160 account in accounts) + var result = new List<(UInt160 Account, BigInteger Value)>(); + BigInteger sum_balance = orderedAccounts.Select(p => p.Value).Sum(); + if (sum_balance == amount) + { + result.AddRange(orderedAccounts); + orderedAccounts.Clear(); + } + else + { + for (int i = 0; i < orderedAccounts.Count; i++) { - BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, account); - if (balance >= fee) + if (orderedAccounts[i].Value < amount) + continue; + if (orderedAccounts[i].Value == amount) { - tx.Sender = account; - return; + result.Add(orderedAccounts[i]); + orderedAccounts.RemoveAt(i); } + else + { + result.Add((orderedAccounts[i].Account, amount)); + orderedAccounts[i] = (orderedAccounts[i].Account, orderedAccounts[i].Value - amount); + } + break; } - throw new InvalidOperationException(); + if (result.Count == 0) + { + int i = orderedAccounts.Count - 1; + while (orderedAccounts[i].Value <= amount) + { + result.Add(orderedAccounts[i]); + amount -= orderedAccounts[i].Value; + orderedAccounts.RemoveAt(i); + i--; + } + for (i = 0; i < orderedAccounts.Count; i++) + { + if (orderedAccounts[i].Value < amount) + continue; + if (orderedAccounts[i].Value == amount) + { + result.Add(orderedAccounts[i]); + orderedAccounts.RemoveAt(i); + } + else + { + result.Add((orderedAccounts[i].Account, amount)); + orderedAccounts[i] = (orderedAccounts[i].Account, orderedAccounts[i].Value - amount); + } + break; + } + } + } + return result; } public WalletAccount GetAccount(ECPoint pubkey) @@ -120,7 +154,7 @@ public static byte[] GetPrivateKeyFromNEP2(string nep2, string passphrase, int N byte[] encryptedkey = new byte[32]; Buffer.BlockCopy(data, 7, encryptedkey, 0, 32); byte[] prikey = XOR(encryptedkey.AES256Decrypt(derivedhalf2), derivedhalf1); - Cryptography.ECC.ECPoint pubkey = Cryptography.ECC.ECCurve.Secp256r1.G * prikey; + ECPoint pubkey = Cryptography.ECC.ECCurve.Secp256r1.G * prikey; UInt160 script_hash = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash(); string address = script_hash.ToAddress(); if (!Encoding.ASCII.GetBytes(address).Sha256().Sha256().Take(4).SequenceEqual(addresshash)) @@ -168,10 +202,8 @@ public virtual WalletAccount Import(string nep2, string passphrase) return account; } - public Transaction MakeTransaction(IEnumerable attributes, TransferOutput[] outputs, UInt160 from = null) + public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null) { - uint nonce = (uint)rand.Next(); - var totalPay = outputs.GroupBy(p => p.AssetId, (k, g) => (k, g.Select(p => p.Value.Value).Sum())).ToArray(); UInt160[] accounts; if (from is null) { @@ -179,80 +211,153 @@ public Transaction MakeTransaction(IEnumerable attributes, } else { - if (!Contains(from)) return null; + if (!Contains(from)) + throw new ArgumentException($"The address {from.ToString()} was not found in the wallet"); accounts = new[] { from }; } - TransactionAttribute[] attr = attributes?.ToArray() ?? new TransactionAttribute[0]; using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) { - uint validUntilBlock = snapshot.Height + Transaction.MaxValidUntilBlockIncrement; - foreach (UInt160 account in accounts) + HashSet cosigners = new HashSet(); + byte[] script; + List<(UInt160 Account, BigInteger Value)> balances_gas = null; + using (ScriptBuilder sb = new ScriptBuilder()) { - Transaction tx = MakeTransaction(snapshot, 0, nonce, totalPay, outputs, account, validUntilBlock, attr); - if (tx != null) return tx; + foreach (var (assetId, group, sum) in outputs.GroupBy(p => p.AssetId, (k, g) => (k, g, g.Select(p => p.Value.Value).Sum()))) + { + var balances = new List<(UInt160 Account, BigInteger Value)>(); + foreach (UInt160 account in accounts) + using (ScriptBuilder sb2 = new ScriptBuilder()) + { + sb2.EmitAppCall(assetId, "balanceOf", account); + using (ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot, testMode: true)) + { + if (engine.State.HasFlag(VMState.FAULT)) + throw new InvalidOperationException($"Execution for {assetId.ToString()}.balanceOf('{account.ToString()}' fault"); + BigInteger value = engine.ResultStack.Pop().GetBigInteger(); + if (value.Sign > 0) balances.Add((account, value)); + } + } + BigInteger sum_balance = balances.Select(p => p.Value).Sum(); + if (sum_balance < sum) + throw new InvalidOperationException($"It does not have enough balance, expected: {sum.ToString()} found: {sum_balance.ToString()}"); + foreach (TransferOutput output in group) + { + balances = balances.OrderBy(p => p.Value).ToList(); + var balances_used = FindPayingAccounts(balances, output.Value.Value); + cosigners.UnionWith(balances_used.Select(p => p.Account)); + foreach (var (account, value) in balances_used) + { + sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + } + } + if (assetId.Equals(NativeContract.GAS.Hash)) + balances_gas = balances; + } + script = sb.ToArray(); } + if (balances_gas is null) + balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); + TransactionAttribute[] attributes = cosigners.Select(p => new TransactionAttribute { Usage = TransactionAttributeUsage.Cosigner, Data = p.ToArray() }).ToArray(); + return MakeTransaction(snapshot, attributes, script, balances_gas); } - return null; } - private Transaction MakeTransaction(Snapshot snapshot, byte version, uint nonce, (UInt160, BigInteger)[] totalPay, TransferOutput[] outputs, UInt160 sender, uint validUntilBlock, TransactionAttribute[] attributes) + public Transaction MakeTransaction(TransactionAttribute[] attributes, byte[] script, UInt160 sender = null) { - BigInteger balance_gas = BigInteger.Zero; - foreach (var (assetId, amount) in totalPay) - using (ScriptBuilder sb = new ScriptBuilder()) - { - sb.EmitAppCall(assetId, "balanceOf", sender); - ApplicationEngine engine = ApplicationEngine.Run(sb.ToArray()); - if (engine.State.HasFlag(VMState.FAULT)) return null; - BigInteger balance = engine.ResultStack.Peek().GetBigInteger(); - if (balance < amount) return null; - if (assetId.Equals(NativeContract.GAS.Hash)) - { - balance_gas = balance - amount; - if (balance_gas.Sign <= 0) return null; - } - } - byte[] script; - using (ScriptBuilder sb = new ScriptBuilder()) + UInt160[] accounts; + if (sender is null) { - foreach (var output in outputs) - { - sb.EmitAppCall(output.AssetId, "transfer", sender, output.ScriptHash, output.Value.Value); - sb.Emit(OpCode.THROWIFNOT); - } - script = sb.ToArray(); + accounts = GetAccounts().Where(p => !p.Lock && !p.WatchOnly).Select(p => p.ScriptHash).ToArray(); } - Transaction tx = new Transaction + else { - Version = version, - Nonce = nonce, - Script = script, - Sender = sender, - ValidUntilBlock = validUntilBlock, - Attributes = attributes - }; - try + if (!Contains(sender)) + throw new ArgumentException($"The address {sender.ToString()} was not found in the wallet"); + accounts = new[] { sender }; + } + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) { - tx.CalculateFees(); + var balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); + return MakeTransaction(snapshot, attributes, script, balances_gas); } - catch (InvalidOperationException) + } + + private Transaction MakeTransaction(Snapshot snapshot, TransactionAttribute[] attributes, byte[] script, List<(UInt160 Account, BigInteger Value)> balances_gas) + { + Random rand = new Random(); + foreach (var (account, value) in balances_gas) { - return null; + Transaction tx = new Transaction + { + Version = 0, + Nonce = (uint)rand.Next(), + Script = script, + Sender = account, + ValidUntilBlock = snapshot.Height + Transaction.MaxValidUntilBlockIncrement, + Attributes = attributes + }; + using (ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, tx, testMode: true)) + { + if (engine.State.HasFlag(VMState.FAULT)) + throw new InvalidOperationException($"Failed execution for '{script.ToHexString()}'"); + tx.SystemFee = Math.Max(engine.GasConsumed - ApplicationEngine.GasFree, 0); + if (tx.SystemFee > 0) + { + long d = (long)NativeContract.GAS.Factor; + long remainder = tx.SystemFee % d; + if (remainder > 0) + tx.SystemFee += d - remainder; + else if (remainder < 0) + tx.SystemFee -= remainder; + } + } + UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); + int size = Transaction.HeaderSize + attributes.GetVarSize() + script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); + foreach (UInt160 hash in hashes) + { + script = GetAccount(hash)?.Contract?.Script ?? snapshot.Contracts.TryGet(hash)?.Script; + if (script is null) continue; + if (script.IsSignatureContract()) + { + size += 66 + script.GetVarSize(); + tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null); + } + else if (script.IsMultiSigContract(out int m, out int n)) + { + int size_inv = 65 * m; + size += IO.Helper.GetVarSize(size_inv) + size_inv + script.GetVarSize(); + tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m; + using (ScriptBuilder sb = new ScriptBuilder()) + tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]]; + tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n; + using (ScriptBuilder sb = new ScriptBuilder()) + tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; + tx.NetworkFee += InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null) * n; + } + else + { + //We can support more contract types in the future. + } + } + tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot); + if (value >= tx.SystemFee + tx.NetworkFee) return tx; } - BigInteger fee = tx.Gas + tx.NetworkFee; - if (balance_gas == BigInteger.Zero) - balance_gas = NativeContract.GAS.BalanceOf(snapshot, sender); - if (balance_gas < fee) return null; - return tx; + throw new InvalidOperationException("Insufficient GAS"); } public bool Sign(ContractParametersContext context) { - WalletAccount account = GetAccount(context.ScriptHash); - if (account?.HasKey != true) return false; - KeyPair key = account.GetKey(); - byte[] signature = context.Verifiable.Sign(key); - return context.AddSignature(account.Contract, key.PublicKey, signature); + bool fSuccess = false; + foreach (UInt160 scriptHash in context.ScriptHashes) + { + WalletAccount account = GetAccount(scriptHash); + if (account?.HasKey != true) continue; + KeyPair key = account.GetKey(); + byte[] signature = context.Verifiable.Sign(key); + fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature); + } + return fSuccess; } public abstract bool VerifyPassword(string password);