diff --git a/.editorconfig b/.editorconfig index 5acd074d26..175ec7b14e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,3 +15,5 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 end_of_line = lf + +dotnet_diagnostic.CS1591.severity = silent diff --git a/src/neo/BigDecimal.cs b/src/neo/BigDecimal.cs index 45e2ae770c..45e41ce124 100644 --- a/src/neo/BigDecimal.cs +++ b/src/neo/BigDecimal.cs @@ -3,20 +3,34 @@ namespace Neo { + /// + /// Represents a fixed-point number of arbitrary precision. + /// public struct BigDecimal { private readonly BigInteger value; private readonly byte decimals; + /// + /// The value of the number. + /// public BigInteger Value => value; + + /// + /// The number of decimal places for this number. + /// public byte Decimals => decimals; + + /// + /// The sign of the number. + /// public int Sign => value.Sign; /// - /// Create BigDecimal from BigInteger + /// Initializes a new instance of the struct. /// - /// Value - /// Decimals + /// The value of the number. + /// The number of decimal places for this number. public BigDecimal(BigInteger value, byte decimals) { this.value = value; @@ -24,29 +38,34 @@ public BigDecimal(BigInteger value, byte decimals) } /// - /// Create BigDecimal from decimal + /// Initializes a new instance of the struct with the value of . /// - /// Value + /// The value of the number. public unsafe BigDecimal(decimal value) { - ReadOnlySpan buffer = new ReadOnlySpan(&value, sizeof(decimal)); + ReadOnlySpan buffer = new(&value, sizeof(decimal)); this.decimals = buffer[14]; this.value = new BigInteger(decimal.Multiply((decimal)Math.Pow(10, decimals), value)); } /// - /// Create BigDecimal from decimal + /// Initializes a new instance of the struct with the value of . /// - /// Value - /// Decimals + /// The value of the number. + /// The number of decimal places for this number. public unsafe BigDecimal(decimal value, byte decimals) { - ReadOnlySpan buffer = new ReadOnlySpan(&value, sizeof(decimal)); + ReadOnlySpan buffer = new(&value, sizeof(decimal)); if (buffer[14] > decimals) throw new ArgumentException(null, nameof(value)); this.value = new BigInteger(decimal.Multiply((decimal)Math.Pow(10, decimals), value)); this.decimals = decimals; } + /// + /// Changes the decimals of the . + /// + /// The new decimals field. + /// The that has the new number of decimal places. public BigDecimal ChangeDecimals(byte decimals) { if (this.decimals == decimals) return this; @@ -60,11 +79,18 @@ public BigDecimal ChangeDecimals(byte decimals) BigInteger divisor = BigInteger.Pow(10, this.decimals - decimals); value = BigInteger.DivRem(this.value, divisor, out BigInteger remainder); if (remainder > BigInteger.Zero) - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(decimals)); } return new BigDecimal(value, decimals); } + /// + /// Parses a from the specified . + /// + /// A number represented by a . + /// The number of decimal places for this number. + /// The parsed . + /// is not in the correct format. public static BigDecimal Parse(string s, byte decimals) { if (!TryParse(s, decimals, out BigDecimal result)) @@ -72,6 +98,10 @@ public static BigDecimal Parse(string s, byte decimals) return result; } + /// + /// Gets a representing the number. + /// + /// The representing the number. public override string ToString() { BigInteger divisor = BigInteger.Pow(10, decimals); @@ -80,15 +110,22 @@ public override string ToString() return $"{result}.{remainder.ToString("d" + decimals)}".TrimEnd('0'); } + /// + /// Parses a from the specified . + /// + /// A number represented by a . + /// The number of decimal places for this number. + /// The parsed . + /// if a number is successfully parsed; otherwise, . public static bool TryParse(string s, byte decimals, out BigDecimal result) { int e = 0; int index = s.IndexOfAny(new[] { 'e', 'E' }); if (index >= 0) { - if (!sbyte.TryParse(s.Substring(index + 1), out sbyte e_temp)) + if (!sbyte.TryParse(s[(index + 1)..], out sbyte e_temp)) { - result = default(BigDecimal); + result = default; return false; } e = e_temp; @@ -104,14 +141,14 @@ public static bool TryParse(string s, byte decimals, out BigDecimal result) int ds = e + decimals; if (ds < 0) { - result = default(BigDecimal); + result = default; return false; } if (ds > 0) s += new string('0', ds); if (!BigInteger.TryParse(s, out BigInteger value)) { - result = default(BigDecimal); + result = default; return false; } result = new BigDecimal(value, decimals); diff --git a/src/neo/Cryptography/Base58.cs b/src/neo/Cryptography/Base58.cs index f571f9540d..7e05a7141e 100644 --- a/src/neo/Cryptography/Base58.cs +++ b/src/neo/Cryptography/Base58.cs @@ -6,10 +6,21 @@ namespace Neo.Cryptography { + /// + /// A helper class for base-58 encoder. + /// public static class Base58 { + /// + /// Represents the alphabet of the base-58 encoder. + /// public const string Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + /// + /// Converts the specified , which encodes binary data as base-58 digits, to an equivalent byte array. The encoded contains the checksum of the binary data. + /// + /// The to convert. + /// A byte array that is equivalent to . public static byte[] Base58CheckDecode(this string input) { if (input is null) throw new ArgumentNullException(nameof(input)); @@ -23,6 +34,11 @@ public static byte[] Base58CheckDecode(this string input) return ret; } + /// + /// Converts a byte array to its equivalent representation that is encoded with base-58 digits. The encoded contains the checksum of the binary data. + /// + /// The byte array to convert. + /// The representation, in base-58, of the contents of . public static string Base58CheckEncode(this ReadOnlySpan data) { byte[] checksum = data.Sha256().Sha256(); @@ -34,6 +50,11 @@ public static string Base58CheckEncode(this ReadOnlySpan data) return ret; } + /// + /// Converts the specified , which encodes binary data as base-58 digits, to an equivalent byte array. + /// + /// The to convert. + /// A byte array that is equivalent to . public static byte[] Decode(string input) { // Decode Base58 string to BigInteger @@ -55,10 +76,15 @@ public static byte[] Decode(string input) return Concat(leadingZeros, bytesWithoutLeadingZeros); } + /// + /// Converts a byte array to its equivalent representation that is encoded with base-58 digits. + /// + /// The byte array to convert. + /// The representation, in base-58, of the contents of . public static string Encode(ReadOnlySpan input) { // Decode byte[] to BigInteger - BigInteger value = new BigInteger(input, isUnsigned: true, isBigEndian: true); + BigInteger value = new(input, isUnsigned: true, isBigEndian: true); // Encode BigInteger to Base58 string var sb = new StringBuilder(); diff --git a/src/neo/Cryptography/BloomFilter.cs b/src/neo/Cryptography/BloomFilter.cs index 32cfa50dd4..67af4c2102 100644 --- a/src/neo/Cryptography/BloomFilter.cs +++ b/src/neo/Cryptography/BloomFilter.cs @@ -4,17 +4,36 @@ namespace Neo.Cryptography { + /// + /// Represents a bloom filter. + /// public class BloomFilter { private readonly uint[] seeds; private readonly BitArray bits; + /// + /// The number of hash functions used by the bloom filter. + /// public int K => seeds.Length; + /// + /// The size of the bit array used by the bloom filter. + /// public int M => bits.Length; + /// + /// Used to generate the seeds of the murmur hash functions. + /// public uint Tweak { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// The size of the bit array used by the bloom filter. + /// The number of hash functions used by the bloom filter. + /// Used to generate the seeds of the murmur hash functions. + /// The initial elements contained in this object. public BloomFilter(int m, int k, uint nTweak, byte[] elements = null) { if (k < 0 || m < 0) throw new ArgumentOutOfRangeException(); @@ -24,12 +43,21 @@ public BloomFilter(int m, int k, uint nTweak, byte[] elements = null) this.Tweak = nTweak; } + /// + /// Adds an element to the . + /// + /// The object to add to the . public void Add(byte[] element) { foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s))) bits.Set((int)(i % (uint)bits.Length), true); } + /// + /// Determines whether the contains a specific element. + /// + /// The object to locate in the . + /// if is found in the ; otherwise, . public bool Check(byte[] element) { foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s))) @@ -38,6 +66,10 @@ public bool Check(byte[] element) return true; } + /// + /// Gets the bit array in this . + /// + /// The byte array to store the bits. public void GetBits(byte[] newBits) { bits.CopyTo(newBits, 0); diff --git a/src/neo/Cryptography/Crypto.cs b/src/neo/Cryptography/Crypto.cs index ddd010cc63..aec25ecd09 100644 --- a/src/neo/Cryptography/Crypto.cs +++ b/src/neo/Cryptography/Crypto.cs @@ -1,24 +1,43 @@ using System; -using System.Numerics; using System.Security.Cryptography; namespace Neo.Cryptography { + /// + /// A cryptographic helper class. + /// public static class Crypto { + /// + /// Calculates the 160-bit hash value of the specified message. + /// + /// The message to be hashed. + /// 160-bit hash value. public static byte[] Hash160(ReadOnlySpan message) { return message.Sha256().RIPEMD160(); } + /// + /// Calculates the 256-bit hash value of the specified message. + /// + /// The message to be hashed. + /// 256-bit hash value. public static byte[] Hash256(ReadOnlySpan message) { return message.Sha256().Sha256(); } + /// + /// Signs the specified message using the ECDSA algorithm. + /// + /// The message to be signed. + /// The private key to be used. + /// The public key to be used. + /// The ECDSA signature for the specified message. public static byte[] Sign(byte[] message, byte[] prikey, byte[] pubkey) { - using (var ecdsa = ECDsa.Create(new ECParameters + using var ecdsa = ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, D = prikey, @@ -27,82 +46,47 @@ public static byte[] Sign(byte[] message, byte[] prikey, byte[] pubkey) X = pubkey[..32], Y = pubkey[32..] } - })) - { - return ecdsa.SignData(message, HashAlgorithmName.SHA256); - } + }); + return ecdsa.SignData(message, HashAlgorithmName.SHA256); } + /// + /// Verifies that a digital signature is appropriate for the provided key and message. + /// + /// The signed message. + /// The signature to be verified. + /// The public key to be used. + /// if the signature is valid; otherwise, . public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ECC.ECPoint pubkey) { - if (pubkey.Curve == ECC.ECCurve.Secp256r1) + ECCurve curve = + pubkey.Curve == ECC.ECCurve.Secp256r1 ? ECCurve.NamedCurves.nistP256 : + pubkey.Curve == ECC.ECCurve.Secp256k1 ? ECCurve.CreateFromFriendlyName("secP256k1") : + throw new NotSupportedException(); + byte[] buffer = pubkey.EncodePoint(false); + using var ecdsa = ECDsa.Create(new ECParameters { - byte[] buffer = pubkey.EncodePoint(false); - using (var ecdsa = ECDsa.Create(new ECParameters - { - Curve = ECCurve.NamedCurves.nistP256, - Q = new ECPoint - { - X = buffer[1..33], - Y = buffer[33..] - } - })) + Curve = curve, + Q = new ECPoint { - return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256); + X = buffer[1..33], + Y = buffer[33..] } - } - else - { - var ecdsa = new ECC.ECDsa(pubkey); - var r = new BigInteger(signature[..32], true, true); - var s = new BigInteger(signature[32..], true, true); - return ecdsa.VerifySignature(message.Sha256(), r, s); - } + }); + return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256); } + /// + /// Verifies that a digital signature is appropriate for the provided key and message. + /// + /// The signed message. + /// The signature to be verified. + /// The public key to be used. + /// The curve to be used by the ECDSA algorithm. + /// if the signature is valid; otherwise, . public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ReadOnlySpan pubkey, ECC.ECCurve curve) { - if (curve == ECC.ECCurve.Secp256r1) - { - if (pubkey.Length == 33 && (pubkey[0] == 0x02 || pubkey[0] == 0x03)) - { - try - { - pubkey = ECC.ECPoint.DecodePoint(pubkey, curve).EncodePoint(false).AsSpan(1); - } - catch - { - return false; - } - } - else if (pubkey.Length == 65 && pubkey[0] == 0x04) - { - pubkey = pubkey[1..]; - } - else - { - throw new ArgumentException(); - } - using (var ecdsa = ECDsa.Create(new ECParameters - { - Curve = ECCurve.NamedCurves.nistP256, - Q = new ECPoint - { - X = pubkey[..32].ToArray(), - Y = pubkey[32..].ToArray() - } - })) - { - return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256); - } - } - else - { - var ecdsa = new ECC.ECDsa(ECC.ECPoint.DecodePoint(pubkey, curve)); - var r = new BigInteger(signature[..32], true, true); - var s = new BigInteger(signature[32..], true, true); - return ecdsa.VerifySignature(message.Sha256(), r, s); - } + return VerifySignature(message, signature, ECC.ECPoint.DecodePoint(pubkey, curve)); } } } diff --git a/src/neo/Cryptography/ECC/ECCurve.cs b/src/neo/Cryptography/ECC/ECCurve.cs index 546c794070..ed4f954396 100644 --- a/src/neo/Cryptography/ECC/ECCurve.cs +++ b/src/neo/Cryptography/ECC/ECCurve.cs @@ -3,16 +3,25 @@ namespace Neo.Cryptography.ECC { + /// + /// Represents an elliptic curve. + /// public class ECCurve { internal readonly BigInteger Q; internal readonly ECFieldElement A; internal readonly ECFieldElement B; internal readonly BigInteger N; + /// + /// The point at infinity. + /// public readonly ECPoint Infinity; + /// + /// The generator, or base point, for operations on the curve. + /// public readonly ECPoint G; - public readonly int ExpectedECPointLength; + internal readonly int ExpectedECPointLength; private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G) { @@ -25,7 +34,10 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G this.G = ECPoint.DecodePoint(G, this); } - public static readonly ECCurve Secp256k1 = new ECCurve + /// + /// Represents a secp256k1 named curve. + /// + public static readonly ECCurve Secp256k1 = new ( BigInteger.Parse("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", NumberStyles.AllowHexSpecifier), BigInteger.Zero, @@ -34,7 +46,10 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G ("04" + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8").HexToBytes() ); - public static readonly ECCurve Secp256r1 = new ECCurve + /// + /// Represents a secp256r1 named curve. + /// + public static readonly ECCurve Secp256r1 = new ( BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", NumberStyles.AllowHexSpecifier), BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", NumberStyles.AllowHexSpecifier), diff --git a/src/neo/Cryptography/ECC/ECDsa.cs b/src/neo/Cryptography/ECC/ECDsa.cs deleted file mode 100644 index 1c0a17d7a9..0000000000 --- a/src/neo/Cryptography/ECC/ECDsa.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Numerics; -using System.Security.Cryptography; - -namespace Neo.Cryptography.ECC -{ - public class ECDsa - { - private readonly byte[] privateKey; - private readonly ECPoint publicKey; - private readonly ECCurve curve; - - public ECDsa(byte[] privateKey, ECCurve curve) - : this(curve.G * privateKey) - { - this.privateKey = privateKey; - } - - public ECDsa(ECPoint publicKey) - { - this.publicKey = publicKey; - this.curve = publicKey.Curve; - } - - private BigInteger CalculateE(BigInteger n, ReadOnlySpan message) - { - if (n.GetBitLength() != (message.Length * 8)) - { - throw new ArgumentException($"Message must be {n.GetBitLength()} bit length"); - } - return new BigInteger(message, isUnsigned: true, isBigEndian: true); - } - - public BigInteger[] GenerateSignature(ReadOnlySpan message) - { - if (privateKey == null) throw new InvalidOperationException(); - BigInteger e = CalculateE(curve.N, message); - BigInteger d = new BigInteger(privateKey, isUnsigned: true, isBigEndian: true); - BigInteger r, s; - using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) - { - do - { - BigInteger k; - do - { - do - { - k = rng.NextBigInteger((int)curve.N.GetBitLength()); - } - while (k.Sign == 0 || k.CompareTo(curve.N) >= 0); - ECPoint p = ECPoint.Multiply(curve.G, k); - BigInteger x = p.X.Value; - r = x.Mod(curve.N); - } - while (r.Sign == 0); - s = (k.ModInverse(curve.N) * (e + d * r)).Mod(curve.N); - if (s > curve.N / 2) - { - s = curve.N - s; - } - } - while (s.Sign == 0); - } - return new BigInteger[] { r, s }; - } - - private static ECPoint SumOfTwoMultiplies(ECPoint P, BigInteger k, ECPoint Q, BigInteger l) - { - int m = (int)Math.Max(k.GetBitLength(), l.GetBitLength()); - ECPoint Z = P + Q; - ECPoint R = P.Curve.Infinity; - for (int i = m - 1; i >= 0; --i) - { - R = R.Twice(); - if (k.TestBit(i)) - { - if (l.TestBit(i)) - R = R + Z; - else - R = R + P; - } - else - { - if (l.TestBit(i)) - R = R + Q; - } - } - return R; - } - - public bool VerifySignature(ReadOnlySpan message, BigInteger r, BigInteger s) - { - if (r.Sign < 1 || s.Sign < 1 || r.CompareTo(curve.N) >= 0 || s.CompareTo(curve.N) >= 0) - return false; - BigInteger e = CalculateE(curve.N, message); - BigInteger c = s.ModInverse(curve.N); - BigInteger u1 = (e * c).Mod(curve.N); - BigInteger u2 = (r * c).Mod(curve.N); - ECPoint point = SumOfTwoMultiplies(curve.G, u1, publicKey, u2); - BigInteger v = point.X.Value.Mod(curve.N); - return v.Equals(r); - } - } -} diff --git a/src/neo/Cryptography/ECC/ECFieldElement.cs b/src/neo/Cryptography/ECC/ECFieldElement.cs index edfdc497b0..f0cf95430d 100644 --- a/src/neo/Cryptography/ECC/ECFieldElement.cs +++ b/src/neo/Cryptography/ECC/ECFieldElement.cs @@ -30,7 +30,7 @@ public override bool Equals(object obj) if (obj == this) return true; - if (!(obj is ECFieldElement other)) + if (obj is not ECFieldElement other) return false; return Equals(other); @@ -97,7 +97,7 @@ public ECFieldElement Sqrt() { if (curve.Q.TestBit(1)) { - ECFieldElement z = new ECFieldElement(BigInteger.ModPow(Value, (curve.Q >> 2) + 1, curve.Q), curve); + ECFieldElement z = new(BigInteger.ModPow(Value, (curve.Q >> 2) + 1, curve.Q), curve); return z.Square().Equals(this) ? z : null; } BigInteger qMinusOne = curve.Q - 1; @@ -111,7 +111,7 @@ public ECFieldElement Sqrt() BigInteger U, V; do { - Random rand = new Random(); + Random rand = new(); BigInteger P; do { diff --git a/src/neo/Cryptography/ECC/ECPoint.cs b/src/neo/Cryptography/ECC/ECPoint.cs index 49a3c16074..92346376c4 100644 --- a/src/neo/Cryptography/ECC/ECPoint.cs +++ b/src/neo/Cryptography/ECC/ECPoint.cs @@ -6,12 +6,18 @@ namespace Neo.Cryptography.ECC { + /// + /// Represents a (X,Y) coordinate pair for elliptic curve cryptography (ECC) structures. + /// public class ECPoint : IComparable, IEquatable, ISerializable { internal ECFieldElement X, Y; internal readonly ECCurve Curve; private byte[] _compressedPoint, _uncompressedPoint; + /// + /// Indicates whether it is a point at infinity. + /// public bool IsInfinity { get { return X == null && Y == null; } @@ -19,6 +25,9 @@ public bool IsInfinity public int Size => IsInfinity ? 1 : 33; + /// + /// Initializes a new instance of the class with the secp256r1 curve. + /// public ECPoint() : this(null, null, ECCurve.Secp256r1) { } internal ECPoint(ECFieldElement x, ECFieldElement y, ECCurve curve) @@ -39,6 +48,12 @@ public int CompareTo(ECPoint other) return Y.CompareTo(other.Y); } + /// + /// Decode an object from a sequence of byte. + /// + /// The sequence of byte to be decoded. + /// The object used to construct the . + /// The decoded point. public static ECPoint DecodePoint(ReadOnlySpan encoded, ECCurve curve) { ECPoint p = null; @@ -50,7 +65,7 @@ public static ECPoint DecodePoint(ReadOnlySpan encoded, ECCurve curve) if (encoded.Length != (curve.ExpectedECPointLength + 1)) throw new FormatException("Incorrect length for compressed encoding"); int yTilde = encoded[0] & 1; - BigInteger X1 = new BigInteger(encoded[1..], isUnsigned: true, isBigEndian: true); + BigInteger X1 = new(encoded[1..], isUnsigned: true, isBigEndian: true); p = DecompressPoint(yTilde, X1, curve); p._compressedPoint = encoded.ToArray(); break; @@ -59,8 +74,8 @@ public static ECPoint DecodePoint(ReadOnlySpan encoded, ECCurve curve) { if (encoded.Length != (2 * curve.ExpectedECPointLength + 1)) throw new FormatException("Incorrect length for uncompressed/hybrid encoding"); - BigInteger X1 = new BigInteger(encoded[1..(1 + curve.ExpectedECPointLength)], isUnsigned: true, isBigEndian: true); - BigInteger Y1 = new BigInteger(encoded[(1 + curve.ExpectedECPointLength)..], isUnsigned: true, isBigEndian: true); + BigInteger X1 = new(encoded[1..(1 + curve.ExpectedECPointLength)], isUnsigned: true, isBigEndian: true); + BigInteger Y1 = new(encoded[(1 + curve.ExpectedECPointLength)..], isUnsigned: true, isBigEndian: true); p = new ECPoint(new ECFieldElement(X1, curve), new ECFieldElement(Y1, curve), curve) { _uncompressedPoint = encoded.ToArray() @@ -75,7 +90,7 @@ public static ECPoint DecodePoint(ReadOnlySpan encoded, ECCurve curve) private static ECPoint DecompressPoint(int yTilde, BigInteger X1, ECCurve curve) { - ECFieldElement x = new ECFieldElement(X1, curve); + ECFieldElement x = new(X1, curve); ECFieldElement alpha = x * (x.Square() + curve.A) + curve.B; ECFieldElement beta = alpha.Sqrt(); @@ -105,6 +120,12 @@ void ISerializable.Deserialize(BinaryReader reader) Y = p.Y; } + /// + /// Deserializes an object from a . + /// + /// The for reading data. + /// The object used to construct the . + /// The deserialized point. public static ECPoint DeserializeFrom(BinaryReader reader, ECCurve curve) { Span buffer = stackalloc byte[1 + curve.ExpectedECPointLength * 2]; @@ -134,11 +155,11 @@ public static ECPoint DeserializeFrom(BinaryReader reader, ECCurve curve) } /// - /// Encode ECPoint to byte array - /// Note: The return should't be modified because it could be cached + /// Encodes an object to a byte array. /// - /// Compressed - /// Encoded point + /// Indicates whether to encode it in a compressed format. + /// The encoded point. + /// Note: The return should't be modified because it could be cached. public byte[] EncodePoint(bool commpressed) { if (IsInfinity) return new byte[1]; @@ -166,7 +187,7 @@ public byte[] EncodePoint(bool commpressed) public bool Equals(ECPoint other) { if (ReferenceEquals(this, other)) return true; - if (ReferenceEquals(null, other)) return false; + if (other is null) return false; if (IsInfinity && other.IsInfinity) return true; if (IsInfinity || other.IsInfinity) return false; return X.Equals(other.X) && Y.Equals(other.Y); @@ -177,22 +198,21 @@ public override bool Equals(object obj) return Equals(obj as ECPoint); } - public static ECPoint FromBytes(byte[] pubkey, ECCurve curve) + /// + /// Constructs an object from a byte array. + /// + /// The byte array to be used to construct the object. + /// The object used to construct the . + /// The decoded point. + public static ECPoint FromBytes(byte[] bytes, ECCurve curve) { - switch (pubkey.Length) + return bytes.Length switch { - case 33: - case 65: - return DecodePoint(pubkey, curve); - case 64: - case 72: - return DecodePoint(Concat(new byte[] { 0x04 }, pubkey[^64..]), curve); - case 96: - case 104: - return DecodePoint(Concat(new byte[] { 0x04 }, pubkey[^96..^32]), curve); - default: - throw new FormatException(); - } + 33 or 65 => DecodePoint(bytes, curve), + 64 or 72 => DecodePoint(Concat(new byte[] { 0x04 }, bytes[^64..]), curve), + 96 or 104 => DecodePoint(Concat(new byte[] { 0x04 }, bytes[^96..^32]), curve), + _ => throw new FormatException(), + }; } public override int GetHashCode() @@ -299,6 +319,12 @@ internal static ECPoint Multiply(ECPoint p, BigInteger k) return q; } + /// + /// Parse the object from a . + /// + /// The to be parsed. + /// The object used to construct the . + /// The parsed point. public static ECPoint Parse(string value, ECCurve curve) { return DecodePoint(value.HexToBytes(), curve); @@ -314,6 +340,13 @@ public override string ToString() return EncodePoint(true).ToHexString(); } + /// + /// Try parse the object from a . + /// + /// The to be parsed. + /// The object used to construct the . + /// The parsed point. + /// if was converted successfully; otherwise, . public static bool TryParse(string value, ECCurve curve, out ECPoint point) { try @@ -334,8 +367,8 @@ internal ECPoint Twice() return this; if (this.Y.Value.Sign == 0) return Curve.Infinity; - ECFieldElement TWO = new ECFieldElement(2, Curve); - ECFieldElement THREE = new ECFieldElement(3, Curve); + ECFieldElement TWO = new(2, Curve); + ECFieldElement THREE = new(3, Curve); ECFieldElement gamma = (this.X.Square() * THREE + Curve.A) / (Y * TWO); ECFieldElement x3 = gamma.Square() - this.X * TWO; ECFieldElement y3 = gamma * (this.X - x3) - this.Y; @@ -387,10 +420,10 @@ private static sbyte[] WindowNaf(sbyte width, BigInteger k) if (p == null || n == null) throw new ArgumentNullException(); if (n.Length != 32) - throw new ArgumentException(); + throw new ArgumentException(null, nameof(n)); if (p.IsInfinity) return p; - BigInteger k = new BigInteger(n, isUnsigned: true, isBigEndian: true); + BigInteger k = new(n, isUnsigned: true, isBigEndian: true); if (k.Sign == 0) return p.Curve.Infinity; return Multiply(p, k); diff --git a/src/neo/Cryptography/Helper.cs b/src/neo/Cryptography/Helper.cs index d39236c33d..deb6063186 100644 --- a/src/neo/Cryptography/Helper.cs +++ b/src/neo/Cryptography/Helper.cs @@ -11,96 +11,74 @@ namespace Neo.Cryptography { + /// + /// A helper class for cryptography + /// public static class Helper { - internal static byte[] AES256Decrypt(this byte[] block, byte[] key) - { - using (Aes aes = Aes.Create()) - { - aes.Key = key; - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; - using (ICryptoTransform decryptor = aes.CreateDecryptor()) - { - return decryptor.TransformFinalBlock(block, 0, block.Length); - } - } - } - - internal static byte[] AES256Encrypt(this byte[] block, byte[] key) - { - using (Aes aes = Aes.Create()) - { - aes.Key = key; - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; - using (ICryptoTransform encryptor = aes.CreateEncryptor()) - { - return encryptor.TransformFinalBlock(block, 0, block.Length); - } - } - } - - internal static byte[] AesDecrypt(this byte[] data, byte[] key, byte[] iv) - { - if (data == null || key == null || iv == null) throw new ArgumentNullException(); - if (data.Length % 16 != 0 || key.Length != 32 || iv.Length != 16) throw new ArgumentException(); - using (Aes aes = Aes.Create()) - { - aes.Padding = PaddingMode.None; - using (ICryptoTransform decryptor = aes.CreateDecryptor(key, iv)) - { - return decryptor.TransformFinalBlock(data, 0, data.Length); - } - } - } - - internal static byte[] AesEncrypt(this byte[] data, byte[] key, byte[] iv) - { - if (data == null || key == null || iv == null) throw new ArgumentNullException(); - if (data.Length % 16 != 0 || key.Length != 32 || iv.Length != 16) throw new ArgumentException(); - using (Aes aes = Aes.Create()) - { - aes.Padding = PaddingMode.None; - using (ICryptoTransform encryptor = aes.CreateEncryptor(key, iv)) - { - return encryptor.TransformFinalBlock(data, 0, data.Length); - } - } - } - + /// + /// Computes the hash value for the specified byte array using the ripemd160 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. public static byte[] RIPEMD160(this byte[] value) { using var ripemd160 = new RIPEMD160Managed(); return ripemd160.ComputeHash(value); } + /// + /// Computes the hash value for the specified byte array using the ripemd160 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. public static byte[] RIPEMD160(this ReadOnlySpan value) { byte[] source = value.ToArray(); return source.RIPEMD160(); } + /// + /// Computes the hash value for the specified byte array using the murmur algorithm. + /// + /// The input to compute the hash code for. + /// The seed used by the murmur algorithm. + /// The computed hash code. public static uint Murmur32(this byte[] value, uint seed) { - using (Murmur3 murmur = new Murmur3(seed)) - { - return BinaryPrimitives.ReadUInt32LittleEndian(murmur.ComputeHash(value)); - } + using Murmur3 murmur = new(seed); + return BinaryPrimitives.ReadUInt32LittleEndian(murmur.ComputeHash(value)); } + /// + /// Computes the hash value for the specified byte array using the sha256 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. public static byte[] Sha256(this byte[] value) { using var sha256 = SHA256.Create(); return sha256.ComputeHash(value); } + /// + /// Computes the hash value for the specified region of the specified byte array using the sha256 algorithm. + /// + /// The input to compute the hash code for. + /// The offset into the byte array from which to begin using data. + /// The number of bytes in the array to use as data. + /// The computed hash code. public static byte[] Sha256(this byte[] value, int offset, int count) { using var sha256 = SHA256.Create(); return sha256.ComputeHash(value, offset, count); } + /// + /// Computes the hash value for the specified byte array using the sha256 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. public static byte[] Sha256(this ReadOnlySpan value) { byte[] buffer = new byte[32]; @@ -109,6 +87,11 @@ public static byte[] Sha256(this ReadOnlySpan value) return buffer; } + /// + /// Computes the hash value for the specified byte array using the sha256 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. public static byte[] Sha256(this Span value) { return Sha256((ReadOnlySpan)value); @@ -124,28 +107,24 @@ internal static bool Test(this BloomFilter filter, Transaction tx) internal static byte[] ToAesKey(this string password) { - using (SHA256 sha256 = SHA256.Create()) - { - byte[] passwordBytes = Encoding.UTF8.GetBytes(password); - byte[] passwordHash = sha256.ComputeHash(passwordBytes); - byte[] passwordHash2 = sha256.ComputeHash(passwordHash); - Array.Clear(passwordBytes, 0, passwordBytes.Length); - Array.Clear(passwordHash, 0, passwordHash.Length); - return passwordHash2; - } + using SHA256 sha256 = SHA256.Create(); + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + byte[] passwordHash = sha256.ComputeHash(passwordBytes); + byte[] passwordHash2 = sha256.ComputeHash(passwordHash); + Array.Clear(passwordBytes, 0, passwordBytes.Length); + Array.Clear(passwordHash, 0, passwordHash.Length); + return passwordHash2; } internal static byte[] ToAesKey(this SecureString password) { - using (SHA256 sha256 = SHA256.Create()) - { - byte[] passwordBytes = password.ToArray(); - byte[] passwordHash = sha256.ComputeHash(passwordBytes); - byte[] passwordHash2 = sha256.ComputeHash(passwordHash); - Array.Clear(passwordBytes, 0, passwordBytes.Length); - Array.Clear(passwordHash, 0, passwordHash.Length); - return passwordHash2; - } + using SHA256 sha256 = SHA256.Create(); + byte[] passwordBytes = password.ToArray(); + byte[] passwordHash = sha256.ComputeHash(passwordBytes); + byte[] passwordHash2 = sha256.ComputeHash(passwordHash); + Array.Clear(passwordBytes, 0, passwordBytes.Length); + Array.Clear(passwordHash, 0, passwordHash.Length); + return passwordHash2; } internal static byte[] ToArray(this SecureString s) @@ -154,7 +133,7 @@ internal static byte[] ToArray(this SecureString s) throw new NullReferenceException(); if (s.Length == 0) return Array.Empty(); - List result = new List(); + List result = new(); IntPtr ptr = SecureStringMarshal.SecureStringToGlobalAllocAnsi(s); try { diff --git a/src/neo/Cryptography/MerkleTree.cs b/src/neo/Cryptography/MerkleTree.cs index 870eaabbb9..d7d1ff8145 100644 --- a/src/neo/Cryptography/MerkleTree.cs +++ b/src/neo/Cryptography/MerkleTree.cs @@ -7,16 +7,22 @@ namespace Neo.Cryptography { + /// + /// Represents a merkle tree. + /// public class MerkleTree { private readonly MerkleTreeNode root; - public int Depth { get; private set; } + /// + /// The depth of the tree. + /// + public int Depth { get; } internal MerkleTree(UInt256[] hashes) { - if (hashes is null || hashes.Length == 0) throw new ArgumentException(); this.root = Build(hashes.Select(p => new MerkleTreeNode { Hash = p }).ToArray()); + if (root is null) return; int depth = 1; for (MerkleTreeNode i = root; i.LeftChild != null; i = i.LeftChild) depth++; @@ -25,15 +31,17 @@ internal MerkleTree(UInt256[] hashes) private static MerkleTreeNode Build(MerkleTreeNode[] leaves) { - if (leaves.Length == 0) throw new ArgumentException(); + if (leaves.Length == 0) return null; if (leaves.Length == 1) return leaves[0]; Span buffer = stackalloc byte[64]; MerkleTreeNode[] parents = new MerkleTreeNode[(leaves.Length + 1) / 2]; for (int i = 0; i < parents.Length; i++) { - parents[i] = new MerkleTreeNode(); - parents[i].LeftChild = leaves[i * 2]; + parents[i] = new MerkleTreeNode + { + LeftChild = leaves[i * 2] + }; leaves[i * 2].Parent = parents[i]; if (i * 2 + 1 == leaves.Length) { @@ -58,11 +66,16 @@ private static UInt256 Concat(Span buffer, UInt256 hash1, UInt256 hash2) return new UInt256(Crypto.Hash256(buffer)); } + /// + /// Computes the root of the hash tree. + /// + /// The leaves of the hash tree. + /// The root of the hash tree. public static UInt256 ComputeRoot(UInt256[] hashes) { - if (hashes.Length == 0) throw new ArgumentException(); + if (hashes.Length == 0) return UInt256.Zero; if (hashes.Length == 1) return hashes[0]; - MerkleTree tree = new MerkleTree(hashes); + MerkleTree tree = new(hashes); return tree.root.Hash; } @@ -80,18 +93,29 @@ private static void DepthFirstSearch(MerkleTreeNode node, IList hashes) } } - // depth-first order + /// + /// Gets all nodes of the hash tree in depth-first order. + /// + /// All nodes of the hash tree. public UInt256[] ToHashArray() { - List hashes = new List(); + if (root is null) return Array.Empty(); + List hashes = new(); DepthFirstSearch(root, hashes); return hashes.ToArray(); } + /// + /// Trims the hash tree using the specified bit array. + /// + /// The bit array to be used. public void Trim(BitArray flags) { - flags = new BitArray(flags); - flags.Length = 1 << (Depth - 1); + if (root is null) return; + flags = new BitArray(flags) + { + Length = 1 << (Depth - 1) + }; Trim(root, 0, Depth, flags); } diff --git a/src/neo/Cryptography/Murmur3.cs b/src/neo/Cryptography/Murmur3.cs index 82642baf7f..08c1e6e6fd 100644 --- a/src/neo/Cryptography/Murmur3.cs +++ b/src/neo/Cryptography/Murmur3.cs @@ -5,6 +5,9 @@ namespace Neo.Cryptography { + /// + /// Computes the murmur hash for the input data. + /// public sealed class Murmur3 : HashAlgorithm { private const uint c1 = 0xcc9e2d51; @@ -20,6 +23,10 @@ public sealed class Murmur3 : HashAlgorithm public override int HashSize => 32; + /// + /// Initializes a new instance of the class with the specified seed. + /// + /// The seed to be used. public Murmur3(uint seed) { this.seed = seed; @@ -65,7 +72,10 @@ protected override byte[] HashFinal() hash ^= hash >> 13; hash *= 0xc2b2ae35; hash ^= hash >> 16; - return BitConverter.GetBytes(hash); + + byte[] buffer = new byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32LittleEndian(buffer, hash); + return buffer; } public override void Initialize() diff --git a/src/neo/Cryptography/RIPEMD160Managed.cs b/src/neo/Cryptography/RIPEMD160Managed.cs index 89a4de23c8..9c505ce104 100644 --- a/src/neo/Cryptography/RIPEMD160Managed.cs +++ b/src/neo/Cryptography/RIPEMD160Managed.cs @@ -5,20 +5,22 @@ namespace Neo.Cryptography { + /// + /// Computes the ripemd160 hash for the input data. + /// [ComVisible(true)] public class RIPEMD160Managed : HashAlgorithm { - private byte[] _buffer; + private readonly byte[] _buffer; private long _count; // Number of bytes in the hashed message - private uint[] _stateMD160; - private uint[] _blockDWords; + private readonly uint[] _stateMD160; + private readonly uint[] _blockDWords; public override int HashSize => 160; - // - // public constructors - // - + /// + /// Initializes a new instance of the class. + /// public RIPEMD160Managed() { _stateMD160 = new uint[5]; @@ -28,10 +30,6 @@ public RIPEMD160Managed() InitializeState(); } - // - // public methods - // - public override void Initialize() { InitializeState(); @@ -41,22 +39,18 @@ public override void Initialize() Array.Clear(_buffer, 0, _buffer.Length); } - [System.Security.SecuritySafeCritical] // auto-generated + [SecuritySafeCritical] protected override void HashCore(byte[] rgb, int ibStart, int cbSize) { - _HashData(rgb, ibStart, cbSize); + HashData(rgb, ibStart, cbSize); } - [System.Security.SecuritySafeCritical] // auto-generated + [SecuritySafeCritical] protected override byte[] HashFinal() { - return _EndHash(); + return EndHash(); } - // - // private methods - // - private void InitializeState() { _count = 0; @@ -70,8 +64,8 @@ private void InitializeState() _stateMD160[4] = 0xc3d2e1f0; } - [System.Security.SecurityCritical] // auto-generated - private unsafe void _HashData(byte[] partIn, int ibStart, int cbSize) + [SecurityCritical] + private unsafe void HashData(byte[] partIn, int ibStart, int cbSize) { int bufferLen; int partInLen = cbSize; @@ -116,8 +110,8 @@ private unsafe void _HashData(byte[] partIn, int ibStart, int cbSize) } } - [SecurityCritical] // auto-generated - private byte[] _EndHash() + [SecurityCritical] + private byte[] EndHash() { byte[] pad; int padLen; @@ -148,7 +142,7 @@ private byte[] _EndHash() pad[padLen - 8] = (byte)((bitCount >> 0) & 0xff); /* Digest padding */ - _HashData(pad, 0, pad.Length); + HashData(pad, 0, pad.Length); /* Store digest */ DWORDToLittleEndian(hash, _stateMD160, 5); @@ -156,7 +150,7 @@ private byte[] _EndHash() return hash; } - [System.Security.SecurityCritical] // auto-generated + [SecurityCritical] private static unsafe void MDTransform(uint* blockDWords, uint* state, byte* block) { uint aa = state[0]; @@ -999,7 +993,6 @@ As we don't have macros in C# and we don't want to pay the cost of a function ca state[0] = ddd; } - // The five basic functions private static uint F(uint x, uint y, uint z) { return (x ^ y ^ z); @@ -1025,7 +1018,7 @@ private static uint J(uint x, uint y, uint z) return (x ^ (y | ~z)); } - [SecurityCritical] // auto-generated + [SecurityCritical] private unsafe static void DWORDFromLittleEndian(uint* x, int digits, byte* block) { int i; diff --git a/src/neo/Helper.cs b/src/neo/Helper.cs index 69fd7ddba4..83560f5a06 100644 --- a/src/neo/Helper.cs +++ b/src/neo/Helper.cs @@ -7,14 +7,16 @@ using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; -using System.Security.Cryptography; using System.Text; namespace Neo { + /// + /// A helper class that provides common functions. + /// public static class Helper { - private static readonly DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly DateTime unixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int BitLen(int w) @@ -34,6 +36,11 @@ private static int BitLen(int w) : (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31))))); } + /// + /// Concatenates the specified byte arrays. + /// + /// The byte arrays to concatenate. + /// The concatenated byte array. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] Concat(params byte[][] buffers) { @@ -50,6 +57,12 @@ public static byte[] Concat(params byte[][] buffers) return dst; } + /// + /// Concatenates two byte arrays. + /// + /// The first byte array to concatenate. + /// The second byte array to concatenate. + /// The concatenated byte array. public static byte[] Concat(ReadOnlySpan a, ReadOnlySpan b) { byte[] buffer = new byte[a.Length + b.Length]; @@ -116,6 +129,11 @@ internal static string GetVersion(this Assembly assembly) return (string)attribute.ConstructorArguments[0].Value; } + /// + /// Converts a hex to byte array. + /// + /// The hex to convert. + /// The converted byte array. public static byte[] HexToBytes(this string value) { if (value == null || value.Length == 0) @@ -163,27 +181,17 @@ internal static BigInteger NextBigInteger(this Random rand, int sizeInBits) Span b = stackalloc byte[sizeInBits / 8 + 1]; rand.NextBytes(b); if (sizeInBits % 8 == 0) - b[b.Length - 1] = 0; + b[^1] = 0; else - b[b.Length - 1] &= (byte)((1 << sizeInBits % 8) - 1); - return new BigInteger(b); - } - - internal static BigInteger NextBigInteger(this RandomNumberGenerator rng, int sizeInBits) - { - if (sizeInBits < 0) - throw new ArgumentException("sizeInBits must be non-negative"); - if (sizeInBits == 0) - return 0; - Span b = stackalloc byte[sizeInBits / 8 + 1]; - rng.GetBytes(b); - if (sizeInBits % 8 == 0) - b[b.Length - 1] = 0; - else - b[b.Length - 1] &= (byte)((1 << sizeInBits % 8) - 1); + b[^1] &= (byte)((1 << sizeInBits % 8) - 1); return new BigInteger(b); } + /// + /// Finds the sum of the specified integers. + /// + /// The specified integers. + /// The sum of the integers. public static BigInteger Sum(this IEnumerable source) { var sum = BigInteger.Zero; @@ -197,6 +205,11 @@ internal static bool TestBit(this BigInteger i, int index) return (i & (BigInteger.One << index)) > BigInteger.Zero; } + /// + /// Converts a to byte array and eliminates all the leading zeros. + /// + /// The to convert. + /// The converted byte array. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] ToByteArrayStandard(this BigInteger i) { @@ -204,35 +217,61 @@ public static byte[] ToByteArrayStandard(this BigInteger i) return i.ToByteArray(); } + /// + /// Converts a byte array to hex . + /// + /// The byte array to convert. + /// The converted hex . public static string ToHexString(this byte[] value) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); foreach (byte b in value) sb.AppendFormat("{0:x2}", b); return sb.ToString(); } + /// + /// Converts a byte array to hex . + /// + /// The byte array to convert. + /// Indicates whether it should be converted in the reversed byte order. + /// The converted hex . public static string ToHexString(this byte[] value, bool reverse = false) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); for (int i = 0; i < value.Length; i++) sb.AppendFormat("{0:x2}", value[reverse ? value.Length - i - 1 : i]); return sb.ToString(); } + /// + /// Converts a byte array to hex . + /// + /// The byte array to convert. + /// The converted hex . public static string ToHexString(this ReadOnlySpan value) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); foreach (byte b in value) sb.AppendFormat("{0:x2}", b); return sb.ToString(); } + /// + /// Converts a to timestamp. + /// + /// The to convert. + /// The converted timestamp. public static uint ToTimestamp(this DateTime time) { return (uint)(time.ToUniversalTime() - unixEpoch).TotalSeconds; } + /// + /// Converts a to timestamp in milliseconds. + /// + /// The to convert. + /// The converted timestamp. public static ulong ToTimestampMS(this DateTime time) { return (ulong)(time.ToUniversalTime() - unixEpoch).TotalMilliseconds; diff --git a/src/neo/IO/Actors/PriorityMessageQueue.cs b/src/neo/IO/Actors/PriorityMessageQueue.cs index b7820b065a..f07d85c8ab 100644 --- a/src/neo/IO/Actors/PriorityMessageQueue.cs +++ b/src/neo/IO/Actors/PriorityMessageQueue.cs @@ -11,8 +11,8 @@ namespace Neo.IO.Actors { internal class PriorityMessageQueue : IMessageQueue, IUnboundedMessageQueueSemantics { - private readonly ConcurrentQueue high = new ConcurrentQueue(); - private readonly ConcurrentQueue low = new ConcurrentQueue(); + private readonly ConcurrentQueue high = new(); + private readonly ConcurrentQueue low = new(); private readonly Func dropper; private readonly Func priority_generator; private int idle = 1; diff --git a/src/neo/IO/ByteArrayComparer.cs b/src/neo/IO/ByteArrayComparer.cs index 8e9b2573c0..8c3f417089 100644 --- a/src/neo/IO/ByteArrayComparer.cs +++ b/src/neo/IO/ByteArrayComparer.cs @@ -6,8 +6,8 @@ namespace Neo.IO { internal class ByteArrayComparer : IComparer { - public static readonly ByteArrayComparer Default = new ByteArrayComparer(1); - public static readonly ByteArrayComparer Reverse = new ByteArrayComparer(-1); + public static readonly ByteArrayComparer Default = new(1); + public static readonly ByteArrayComparer Reverse = new(-1); private readonly int direction; diff --git a/src/neo/IO/ByteArrayEqualityComparer.cs b/src/neo/IO/ByteArrayEqualityComparer.cs index 2a3c64b57a..c6fab99539 100644 --- a/src/neo/IO/ByteArrayEqualityComparer.cs +++ b/src/neo/IO/ByteArrayEqualityComparer.cs @@ -4,7 +4,7 @@ namespace Neo.IO { internal class ByteArrayEqualityComparer : IEqualityComparer { - public static readonly ByteArrayEqualityComparer Default = new ByteArrayEqualityComparer(); + public static readonly ByteArrayEqualityComparer Default = new(); public unsafe bool Equals(byte[] x, byte[] y) { diff --git a/src/neo/IO/Caching/Cache.cs b/src/neo/IO/Caching/Cache.cs index b5095231f3..0af4296f7b 100644 --- a/src/neo/IO/Caching/Cache.cs +++ b/src/neo/IO/Caching/Cache.cs @@ -22,8 +22,8 @@ public CacheItem(TKey key, TValue value) } } - protected readonly ReaderWriterLockSlim RwSyncRootLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - protected readonly Dictionary InnerDictionary = new Dictionary(); + protected readonly ReaderWriterLockSlim RwSyncRootLock = new(LockRecursionPolicy.SupportsRecursion); + protected readonly Dictionary InnerDictionary = new(); private readonly int max_capacity; public TValue this[TKey key] diff --git a/src/neo/IO/Caching/HashSetCache.cs b/src/neo/IO/Caching/HashSetCache.cs index af34cd76fa..f682b2e5ae 100644 --- a/src/neo/IO/Caching/HashSetCache.cs +++ b/src/neo/IO/Caching/HashSetCache.cs @@ -4,12 +4,12 @@ namespace Neo.IO.Caching { - public class HashSetCache : IReadOnlyCollection where T : IEquatable + class HashSetCache : IReadOnlyCollection where T : IEquatable { /// /// Sets where the Hashes are stored /// - private readonly LinkedList> sets = new LinkedList>(); + private readonly LinkedList> sets = new(); /// /// Maximum capacity of each bucket inside each HashSet of . diff --git a/src/neo/IO/Caching/OrderedDictionary.cs b/src/neo/IO/Caching/OrderedDictionary.cs index 4cbf33392f..3ce4b0ab91 100644 --- a/src/neo/IO/Caching/OrderedDictionary.cs +++ b/src/neo/IO/Caching/OrderedDictionary.cs @@ -21,7 +21,7 @@ protected override TKey GetKeyForItem(TItem item) } } - private readonly InternalCollection collection = new InternalCollection(); + private readonly InternalCollection collection = new(); public int Count => collection.Count; public bool IsReadOnly => false; diff --git a/src/neo/IO/Caching/ReflectionCache.cs b/src/neo/IO/Caching/ReflectionCache.cs index 5709ccd8df..b09ec0fe37 100644 --- a/src/neo/IO/Caching/ReflectionCache.cs +++ b/src/neo/IO/Caching/ReflectionCache.cs @@ -6,7 +6,7 @@ namespace Neo.IO.Caching { internal static class ReflectionCache where T : Enum { - private static readonly Dictionary dictionary = new Dictionary(); + private static readonly Dictionary dictionary = new(); public static int Count => dictionary.Count; diff --git a/src/neo/IO/Helper.cs b/src/neo/IO/Helper.cs index e871bbaedf..5e3f2cb5b1 100644 --- a/src/neo/IO/Helper.cs +++ b/src/neo/IO/Helper.cs @@ -10,61 +10,96 @@ namespace Neo.IO { + /// + /// A helper class for serialization of NEO objects. + /// public static class Helper { + /// + /// Converts a byte array to an object. + /// + /// The type to convert to. + /// The byte array to be converted. + /// The offset into the byte array from which to begin using data. + /// The converted object. public static T AsSerializable(this byte[] value, int start = 0) where T : ISerializable, new() { - using (MemoryStream ms = new MemoryStream(value, start, value.Length - start, false)) - using (BinaryReader reader = new BinaryReader(ms, Utility.StrictUTF8)) - { - return reader.ReadSerializable(); - } + using MemoryStream ms = new(value, start, value.Length - start, false); + using BinaryReader reader = new(ms, Utility.StrictUTF8, true); + return reader.ReadSerializable(); } + /// + /// Converts a byte array to an object. + /// + /// The type to convert to. + /// The byte array to be converted. + /// The converted object. public static unsafe T AsSerializable(this ReadOnlySpan value) where T : ISerializable, new() { if (value.IsEmpty) throw new FormatException(); fixed (byte* pointer = value) { - using UnmanagedMemoryStream ms = new UnmanagedMemoryStream(pointer, value.Length); - using BinaryReader reader = new BinaryReader(ms, Utility.StrictUTF8); + using UnmanagedMemoryStream ms = new(pointer, value.Length); + using BinaryReader reader = new(ms, Utility.StrictUTF8, true); return reader.ReadSerializable(); } } + /// + /// Converts a byte array to an object. + /// + /// The byte array to be converted. + /// The type to convert to. + /// The converted object. public static ISerializable AsSerializable(this byte[] value, Type type) { if (!typeof(ISerializable).GetTypeInfo().IsAssignableFrom(type)) throw new InvalidCastException(); ISerializable serializable = (ISerializable)Activator.CreateInstance(type); - using (MemoryStream ms = new MemoryStream(value, false)) - using (BinaryReader reader = new BinaryReader(ms, Utility.StrictUTF8)) - { - serializable.Deserialize(reader); - } + using MemoryStream ms = new(value, false); + using BinaryReader reader = new(ms, Utility.StrictUTF8, true); + serializable.Deserialize(reader); return serializable; } + /// + /// Converts a byte array to an array. + /// + /// The type of the array element. + /// The byte array to be converted. + /// The maximum number of elements contained in the converted array. + /// The converted array. public static T[] AsSerializableArray(this byte[] value, int max = 0x1000000) where T : ISerializable, new() { - using (MemoryStream ms = new MemoryStream(value, false)) - using (BinaryReader reader = new BinaryReader(ms, Utility.StrictUTF8)) - { - return reader.ReadSerializableArray(max); - } + using MemoryStream ms = new(value, false); + using BinaryReader reader = new(ms, Utility.StrictUTF8, true); + return reader.ReadSerializableArray(max); } + /// + /// Converts a byte array to an array. + /// + /// The type of the array element. + /// The byte array to be converted. + /// The maximum number of elements contained in the converted array. + /// The converted array. public static unsafe T[] AsSerializableArray(this ReadOnlySpan value, int max = 0x1000000) where T : ISerializable, new() { if (value.IsEmpty) throw new FormatException(); fixed (byte* pointer = value) { - using UnmanagedMemoryStream ms = new UnmanagedMemoryStream(pointer, value.Length); - using BinaryReader reader = new BinaryReader(ms, Utility.StrictUTF8); + using UnmanagedMemoryStream ms = new(pointer, value.Length); + using BinaryReader reader = new(ms, Utility.StrictUTF8, true); return reader.ReadSerializableArray(max); } } + /// + /// Compresses the specified data using the LZ4 algorithm. + /// + /// The data to be compressed. + /// The compressed data. public static byte[] CompressLz4(this byte[] data) { int maxLength = LZ4Codec.MaximumOutputSize(data.Length); @@ -76,6 +111,12 @@ public static byte[] CompressLz4(this byte[] data) return result; } + /// + /// Decompresses the specified data using the LZ4 algorithm. + /// + /// The compressed data. + /// The maximum data size after decompression. + /// The original data. public static byte[] DecompressLz4(this byte[] data, int maxOutput) { int length = BinaryPrimitives.ReadInt32LittleEndian(data); @@ -86,6 +127,11 @@ public static byte[] DecompressLz4(this byte[] data, int maxOutput) return result; } + /// + /// Fills the buffer with the data in the specified . + /// + /// The to be used. + /// The buffer used to store data. public static void FillBuffer(this BinaryReader reader, Span buffer) { while (!buffer.IsEmpty) @@ -96,6 +142,11 @@ public static void FillBuffer(this BinaryReader reader, Span buffer) } } + /// + /// Gets the size of variable-length of the data. + /// + /// The length of the data. + /// The size of variable-length of the data. public static int GetVarSize(int value) { if (value < 0xFD) @@ -106,6 +157,12 @@ public static int GetVarSize(int value) return sizeof(byte) + sizeof(uint); } + /// + /// Gets the size of the specified array encoded in variable-length encoding. + /// + /// The type of the array element. + /// The specified array. + /// The size of the array. public static int GetVarSize(this IReadOnlyCollection value) { int value_size; @@ -135,12 +192,23 @@ public static int GetVarSize(this IReadOnlyCollection value) return GetVarSize(value.Count) + value_size; } + /// + /// Gets the size of the specified encoded in variable-length encoding. + /// + /// The specified . + /// The size of the . public static int GetVarSize(this string value) { int size = Utility.StrictUTF8.GetByteCount(value); return GetVarSize(size) + size; } + /// + /// Reads a byte array of the specified size from a . + /// + /// The for reading data. + /// The size of the byte array. + /// The byte array read from the . public static byte[] ReadFixedBytes(this BinaryReader reader, int size) { var index = 0; @@ -162,12 +230,25 @@ public static byte[] ReadFixedBytes(this BinaryReader reader, int size) return data; } + /// + /// Reads a of the specified length from a . + /// + /// The for reading data. + /// The length of the . + /// The read from the . public static string ReadFixedString(this BinaryReader reader, int length) { byte[] data = reader.ReadFixedBytes(length); return Utility.StrictUTF8.GetString(data.TakeWhile(p => p != 0).ToArray()); } + /// + /// Reads an array from a . + /// + /// The type of the array element. + /// The for reading data. + /// The maximum number of elements in the array. + /// The array read from the . public static T[] ReadNullableArray(this BinaryReader reader, int max = 0x1000000) where T : class, ISerializable, new() { T[] array = new T[reader.ReadVarInt((ulong)max)]; @@ -176,13 +257,26 @@ public static string ReadFixedString(this BinaryReader reader, int length) return array; } + /// + /// Reads an object from a . + /// + /// The type of the object. + /// The for reading data. + /// The object read from the . public static T ReadSerializable(this BinaryReader reader) where T : ISerializable, new() { - T obj = new T(); + T obj = new(); obj.Deserialize(reader); return obj; } + /// + /// Reads an array from a . + /// + /// The type of the array element. + /// The for reading data. + /// The maximum number of elements in the array. + /// The array read from the . public static T[] ReadSerializableArray(this BinaryReader reader, int max = 0x1000000) where T : ISerializable, new() { T[] array = new T[reader.ReadVarInt((ulong)max)]; @@ -194,11 +288,23 @@ public static string ReadFixedString(this BinaryReader reader, int length) return array; } + /// + /// Reads a byte array from a . + /// + /// The for reading data. + /// The maximum size of the byte array. + /// The byte array read from the . public static byte[] ReadVarBytes(this BinaryReader reader, int max = 0x1000000) { return reader.ReadFixedBytes((int)reader.ReadVarInt((ulong)max)); } + /// + /// Reads an integer from a . + /// + /// The for reading data. + /// The maximum value of the integer. + /// The integer read from the . public static ulong ReadVarInt(this BinaryReader reader, ulong max = ulong.MaxValue) { byte fb = reader.ReadByte(); @@ -215,38 +321,62 @@ public static ulong ReadVarInt(this BinaryReader reader, ulong max = ulong.MaxVa return value; } + /// + /// Reads a from a . + /// + /// The for reading data. + /// The maximum size of the . + /// The read from the . public static string ReadVarString(this BinaryReader reader, int max = 0x1000000) { return Utility.StrictUTF8.GetString(reader.ReadVarBytes(max)); } + /// + /// Converts an object to a byte array. + /// + /// The object to be converted. + /// The converted byte array. public static byte[] ToArray(this ISerializable value) { - using (MemoryStream ms = new MemoryStream()) - using (BinaryWriter writer = new BinaryWriter(ms, Utility.StrictUTF8)) - { - value.Serialize(writer); - writer.Flush(); - return ms.ToArray(); - } + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); + value.Serialize(writer); + writer.Flush(); + return ms.ToArray(); } + /// + /// Converts an array to a byte array. + /// + /// The type of the array element. + /// The array to be converted. + /// The converted byte array. public static byte[] ToByteArray(this IReadOnlyCollection value) where T : ISerializable { - using (MemoryStream ms = new MemoryStream()) - using (BinaryWriter writer = new BinaryWriter(ms, Utility.StrictUTF8)) - { - writer.Write(value); - writer.Flush(); - return ms.ToArray(); - } + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); + writer.Write(value); + writer.Flush(); + return ms.ToArray(); } + /// + /// Writes an object into a . + /// + /// The for writing data. + /// The object to be written. public static void Write(this BinaryWriter writer, ISerializable value) { value.Serialize(writer); } + /// + /// Writes an array into a . + /// + /// The type of the array element. + /// The for writing data. + /// The array to be written. public static void Write(this BinaryWriter writer, IReadOnlyCollection value) where T : ISerializable { writer.WriteVarInt(value.Count); @@ -256,20 +386,32 @@ public static void Write(this BinaryWriter writer, IReadOnlyCollection val } } + /// + /// Writes a into a . + /// + /// The for writing data. + /// The to be written. + /// The fixed size of the . public static void WriteFixedString(this BinaryWriter writer, string value, int length) { if (value == null) throw new ArgumentNullException(nameof(value)); if (value.Length > length) - throw new ArgumentException(); + throw new ArgumentException(null, nameof(value)); byte[] bytes = Utility.StrictUTF8.GetBytes(value); if (bytes.Length > length) - throw new ArgumentException(); + throw new ArgumentException(null, nameof(value)); writer.Write(bytes); if (bytes.Length < length) writer.Write(stackalloc byte[length - bytes.Length]); } + /// + /// Writes an array into a . + /// + /// The type of the array element. + /// The for writing data. + /// The array to be written. public static void WriteNullableArray(this BinaryWriter writer, T[] value) where T : class, ISerializable { writer.WriteVarInt(value.Length); @@ -282,16 +424,26 @@ public static void WriteNullableArray(this BinaryWriter writer, T[] value) wh } } + /// + /// Writes a byte array into a . + /// + /// The for writing data. + /// The byte array to be written. public static void WriteVarBytes(this BinaryWriter writer, ReadOnlySpan value) { writer.WriteVarInt(value.Length); writer.Write(value); } + /// + /// Writes an integer into a . + /// + /// The for writing data. + /// The integer to be written. public static void WriteVarInt(this BinaryWriter writer, long value) { if (value < 0) - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(value)); if (value < 0xFD) { writer.Write((byte)value); @@ -313,6 +465,11 @@ public static void WriteVarInt(this BinaryWriter writer, long value) } } + /// + /// Writes a into a . + /// + /// The for writing data. + /// The to be written. public static void WriteVarString(this BinaryWriter writer, string value) { writer.WriteVarBytes(Utility.StrictUTF8.GetBytes(value)); diff --git a/src/neo/IO/ISerializable.cs b/src/neo/IO/ISerializable.cs index d03540deb2..6167608c07 100644 --- a/src/neo/IO/ISerializable.cs +++ b/src/neo/IO/ISerializable.cs @@ -2,11 +2,26 @@ namespace Neo.IO { + /// + /// Represents NEO objects that can be serialized. + /// public interface ISerializable { + /// + /// The size of the object in bytes after serialization. + /// int Size { get; } + /// + /// Serializes the object using the specified . + /// + /// The for writing data. void Serialize(BinaryWriter writer); + + /// + /// Deserializes the object using the specified . + /// + /// The for reading data. void Deserialize(BinaryReader reader); } } diff --git a/src/neo/IO/Json/JArray.cs b/src/neo/IO/Json/JArray.cs index 9519c98b3e..c7359afbc1 100644 --- a/src/neo/IO/Json/JArray.cs +++ b/src/neo/IO/Json/JArray.cs @@ -5,14 +5,25 @@ namespace Neo.IO.Json { + /// + /// Represents a JSON array. + /// public class JArray : JObject, IList { - private readonly List items = new List(); + private readonly List items = new(); + /// + /// Initializes a new instance of the class. + /// + /// The initial items in the array. public JArray(params JObject[] items) : this((IEnumerable)items) { } + /// + /// Initializes a new instance of the class. + /// + /// The initial items in the array. public JArray(IEnumerable items) { this.items.AddRange(items); @@ -71,6 +82,8 @@ public void CopyTo(JObject[] array, int arrayIndex) items.CopyTo(array, arrayIndex); } + public override JArray GetArray() => this; + public IEnumerator GetEnumerator() { return items.GetEnumerator(); diff --git a/src/neo/IO/Json/JBoolean.cs b/src/neo/IO/Json/JBoolean.cs index 7cfd7ee025..29be911c2f 100644 --- a/src/neo/IO/Json/JBoolean.cs +++ b/src/neo/IO/Json/JBoolean.cs @@ -2,10 +2,20 @@ namespace Neo.IO.Json { + /// + /// Represents a JSON boolean value. + /// public class JBoolean : JObject { - public bool Value { get; private set; } + /// + /// Gets the value of the JSON object. + /// + public bool Value { get; } + /// + /// Initializes a new instance of the class with the specified value. + /// + /// The value of the JSON object. public JBoolean(bool value = false) { this.Value = value; @@ -16,6 +26,10 @@ public override bool AsBoolean() return Value; } + /// + /// Converts the current JSON object to a floating point number. + /// + /// The number 1 if value is ; otherwise, 0. public override double AsNumber() { return Value ? 1 : 0; @@ -26,6 +40,8 @@ public override string AsString() return Value.ToString().ToLowerInvariant(); } + public override bool GetBoolean() => Value; + public override string ToString() { return AsString(); diff --git a/src/neo/IO/Json/JNumber.cs b/src/neo/IO/Json/JNumber.cs index 165d8138ec..00d07c1715 100644 --- a/src/neo/IO/Json/JNumber.cs +++ b/src/neo/IO/Json/JNumber.cs @@ -4,19 +4,40 @@ namespace Neo.IO.Json { + /// + /// Represents a JSON number. + /// public class JNumber : JObject { + /// + /// Represents the largest safe integer in JSON. + /// public static readonly long MAX_SAFE_INTEGER = (long)Math.Pow(2, 53) - 1; + + /// + /// Represents the smallest safe integer in JSON. + /// public static readonly long MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; - public double Value { get; private set; } + /// + /// Gets the value of the JSON object. + /// + public double Value { get; } + /// + /// Initializes a new instance of the class with the specified value. + /// + /// The value of the JSON object. public JNumber(double value = 0) { if (!double.IsFinite(value)) throw new FormatException(); this.Value = value; } + /// + /// Converts the current JSON object to a boolean value. + /// + /// if value is not zero; otherwise, . public override bool AsBoolean() { return Value != 0; @@ -32,6 +53,8 @@ public override string AsString() return Value.ToString(CultureInfo.InvariantCulture); } + public override double GetNumber() => Value; + public override string ToString() { return AsString(); diff --git a/src/neo/IO/Json/JObject.cs b/src/neo/IO/Json/JObject.cs index 9b725381af..9f5534753e 100644 --- a/src/neo/IO/Json/JObject.cs +++ b/src/neo/IO/Json/JObject.cs @@ -6,11 +6,26 @@ namespace Neo.IO.Json { + /// + /// Represents a JSON object. + /// public class JObject { + /// + /// Represents a object. + /// public static readonly JObject Null = null; + + /// + /// Gets or sets the properties of the JSON object. + /// public IDictionary Properties { get; } = new OrderedDictionary(); + /// + /// Gets or sets the properties of the JSON object. + /// + /// The name of the property to get or set. + /// The property with the specified name. public JObject this[string name] { get @@ -25,41 +40,94 @@ public JObject this[string name] } } + /// + /// Converts the current JSON object to a boolean value. + /// + /// The converted value. public virtual bool AsBoolean() { return true; } + /// + /// Converts the current JSON object to a floating point number. + /// + /// The converted value. public virtual double AsNumber() { return double.NaN; } + /// + /// Converts the current JSON object to a . + /// + /// The converted value. public virtual string AsString() { return ToString(); } + /// + /// Determines whether the JSON object contains a property with the specified name. + /// + /// The property name to locate in the JSON object. + /// if the JSON object contains a property with the name; otherwise, . public bool ContainsProperty(string key) { return Properties.ContainsKey(key); } - private static string GetString(ref Utf8JsonReader reader) + /// + /// Converts the current JSON object to a object. + /// + /// The converted value. + /// The JSON object is not a . + public virtual JArray GetArray() => throw new InvalidCastException(); + + /// + /// Converts the current JSON object to a boolean value. + /// + /// The converted value. + /// The JSON object is not a . + public virtual bool GetBoolean() => throw new InvalidCastException(); + + /// + /// Converts the current JSON object to a 32-bit signed integer. + /// + /// The converted value. + /// The JSON object is not a . + /// The JSON object cannot be converted to an integer. + /// The JSON object cannot be converted to a 32-bit signed integer. + public int GetInt32() { - try - { - return reader.GetString(); - } - catch (InvalidOperationException ex) - { - throw new FormatException(ex.Message, ex); - } + double d = GetNumber(); + if (d % 1 != 0) throw new InvalidCastException(); + return checked((int)d); } + /// + /// Converts the current JSON object to a floating point number. + /// + /// The converted value. + /// The JSON object is not a . + public virtual double GetNumber() => throw new InvalidCastException(); + + /// + /// Converts the current JSON object to a . + /// + /// The converted value. + /// The JSON object is not a . + public virtual string GetString() => throw new InvalidCastException(); + + /// + /// Parses a JSON object from a byte array. + /// + /// The byte array that contains the JSON object. + /// The maximum nesting depth when parsing the JSON object. + /// The parsed JSON object. public static JObject Parse(ReadOnlySpan value, int max_nest = 100) { - Utf8JsonReader reader = new Utf8JsonReader(value, new JsonReaderOptions + Utf8JsonReader reader = new(value, new JsonReaderOptions { AllowTrailingCommas = false, CommentHandling = JsonCommentHandling.Skip, @@ -77,6 +145,12 @@ public static JObject Parse(ReadOnlySpan value, int max_nest = 100) } } + /// + /// Parses a JSON object from a . + /// + /// The that contains the JSON object. + /// The maximum nesting depth when parsing the JSON object. + /// The parsed JSON object. public static JObject Parse(string value, int max_nest = 100) { return Parse(Utility.StrictUTF8.GetBytes(value), max_nest); @@ -92,7 +166,7 @@ private static JObject Read(ref Utf8JsonReader reader, bool skipReading = false) JsonTokenType.Number => reader.GetDouble(), JsonTokenType.StartArray => ReadArray(ref reader), JsonTokenType.StartObject => ReadObject(ref reader), - JsonTokenType.String => GetString(ref reader), + JsonTokenType.String => ReadString(ref reader), JsonTokenType.True => true, _ => throw new FormatException(), }; @@ -100,7 +174,7 @@ private static JObject Read(ref Utf8JsonReader reader, bool skipReading = false) private static JArray ReadArray(ref Utf8JsonReader reader) { - JArray array = new JArray(); + JArray array = new(); while (reader.Read()) { switch (reader.TokenType) @@ -117,7 +191,7 @@ private static JArray ReadArray(ref Utf8JsonReader reader) private static JObject ReadObject(ref Utf8JsonReader reader) { - JObject obj = new JObject(); + JObject obj = new(); while (reader.Read()) { switch (reader.TokenType) @@ -125,7 +199,7 @@ private static JObject ReadObject(ref Utf8JsonReader reader) case JsonTokenType.EndObject: return obj; case JsonTokenType.PropertyName: - string name = GetString(ref reader); + string name = ReadString(ref reader); if (obj.Properties.ContainsKey(name)) throw new FormatException(); JObject value = Read(ref reader); obj.Properties.Add(name, value); @@ -137,10 +211,27 @@ private static JObject ReadObject(ref Utf8JsonReader reader) throw new FormatException(); } + private static string ReadString(ref Utf8JsonReader reader) + { + try + { + return reader.GetString(); + } + catch (InvalidOperationException ex) + { + throw new FormatException(ex.Message, ex); + } + } + + /// + /// Encode the current JSON object into a byte array. + /// + /// Indicates whether indentation is required. + /// The encoded JSON object. public byte[] ToByteArray(bool indented) { - using MemoryStream ms = new MemoryStream(); - using Utf8JsonWriter writer = new Utf8JsonWriter(ms, new JsonWriterOptions + using MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms, new JsonWriterOptions { Indented = indented, SkipValidation = true @@ -150,16 +241,32 @@ public byte[] ToByteArray(bool indented) return ms.ToArray(); } + /// + /// Encode the current JSON object into a . + /// + /// The encoded JSON object. public override string ToString() { return ToString(false); } + /// + /// Encode the current JSON object into a . + /// + /// Indicates whether indentation is required. + /// The encoded JSON object. public string ToString(bool indented) { return Utility.StrictUTF8.GetString(ToByteArray(indented)); } + /// + /// Converts the current JSON object to an . + /// + /// The type of the . + /// If the current JSON object cannot be converted to type , then the default value is returned. + /// Indicates whether case should be ignored during conversion. + /// The converted value. public virtual T TryGetEnum(T defaultValue = default, bool ignoreCase = false) where T : Enum { return defaultValue; @@ -204,6 +311,10 @@ public static implicit operator JObject(string value) return (JString)value; } + /// + /// Creates a copy of the current JSON object. + /// + /// A copy of the current JSON object. public virtual JObject Clone() { var cloned = new JObject(); diff --git a/src/neo/IO/Json/JString.cs b/src/neo/IO/Json/JString.cs index 7c625875b3..834f32de24 100644 --- a/src/neo/IO/Json/JString.cs +++ b/src/neo/IO/Json/JString.cs @@ -4,15 +4,29 @@ namespace Neo.IO.Json { + /// + /// Represents a JSON string. + /// public class JString : JObject { - public string Value { get; private set; } + /// + /// Gets the value of the JSON object. + /// + public string Value { get; } + /// + /// Initializes a new instance of the class with the specified value. + /// + /// The value of the JSON object. public JString(string value) { - this.Value = value ?? throw new ArgumentNullException(); + this.Value = value ?? throw new ArgumentNullException(nameof(value)); } + /// + /// Converts the current JSON object to a boolean value. + /// + /// if value is not empty; otherwise, . public override bool AsBoolean() { return !string.IsNullOrEmpty(Value); @@ -29,6 +43,8 @@ public override string AsString() return Value; } + public override string GetString() => Value; + public override T TryGetEnum(T defaultValue = default, bool ignoreCase = false) { try diff --git a/src/neo/Ledger/Blockchain.ApplicationExecuted.cs b/src/neo/Ledger/Blockchain.ApplicationExecuted.cs index 698fffeadd..3a5f0d5aa0 100644 --- a/src/neo/Ledger/Blockchain.ApplicationExecuted.cs +++ b/src/neo/Ledger/Blockchain.ApplicationExecuted.cs @@ -11,13 +11,40 @@ partial class Blockchain { partial class ApplicationExecuted { - public Transaction Transaction; - public TriggerType Trigger { get; internal set; } - public VMState VMState { get; internal set; } - public Exception Exception { get; internal set; } - public long GasConsumed { get; internal set; } - public StackItem[] Stack { get; internal set; } - public NotifyEventArgs[] Notifications { get; internal set; } + /// + /// The transaction that contains the executed script. This field could be if the contract is invoked by system. + /// + public Transaction Transaction { get; } + + /// + /// The trigger of the execution. + /// + public TriggerType Trigger { get; } + + /// + /// The state of the virtual machine after the contract is executed. + /// + public VMState VMState { get; } + + /// + /// The exception that caused the execution to terminate abnormally. This field could be if the execution ends normally. + /// + public Exception Exception { get; } + + /// + /// GAS spent to execute. + /// + public long GasConsumed { get; } + + /// + /// Items on the stack of the virtual machine after execution. + /// + public StackItem[] Stack { get; } + + /// + /// The notifications sent during the execution. + /// + public NotifyEventArgs[] Notifications { get; } internal ApplicationExecuted(ApplicationEngine engine) { diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index 8a923e87d0..e135194687 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -1,10 +1,7 @@ using Akka.Actor; using Akka.Configuration; using Akka.IO; -using Neo.Cryptography.ECC; -using Neo.IO; using Neo.IO.Actors; -using Neo.IO.Caching; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -13,145 +10,139 @@ using Neo.SmartContract.Native; using Neo.VM; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Threading; namespace Neo.Ledger { + /// + /// Actor used to verify and relay . + /// public sealed partial class Blockchain : UntypedActor { + /// + /// Sent by the when a smart contract is executed. + /// public partial class ApplicationExecuted { } - public class PersistCompleted { public Block Block; } - public class Import { public IEnumerable Blocks; public bool Verify = true; } - public class ImportCompleted { } - public class FillMemoryPool { public IEnumerable Transactions; } - public class FillCompleted { } - internal class PreverifyCompleted { public Transaction Transaction; public VerifyResult Result; } - public class RelayResult { public IInventory Inventory; public VerifyResult Result; } - private class UnverifiedBlocksList { public LinkedList Blocks = new LinkedList(); public HashSet Nodes = new HashSet(); } - public static readonly uint MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock; - public static readonly TimeSpan TimePerBlock = TimeSpan.FromMilliseconds(MillisecondsPerBlock); - public static readonly ECPoint[] StandbyCommittee = ProtocolSettings.Default.StandbyCommittee.Select(p => ECPoint.DecodePoint(p.HexToBytes(), ECCurve.Secp256r1)).ToArray(); - public static readonly ECPoint[] StandbyValidators = StandbyCommittee[0..ProtocolSettings.Default.ValidatorsCount]; + /// + /// Sent by the when a is persisted. + /// + public class PersistCompleted + { + /// + /// The that is persisted. + /// + public Block Block { get; init; } + } - public static readonly Block GenesisBlock = new Block + /// + /// Sent to the when importing blocks. + /// + public class Import { - PrevHash = UInt256.Zero, - Timestamp = (new DateTime(2016, 7, 15, 15, 8, 21, DateTimeKind.Utc)).ToTimestampMS(), - Index = 0, - NextConsensus = Contract.GetBFTAddress(StandbyValidators), - Witness = new Witness - { - InvocationScript = Array.Empty(), - VerificationScript = new[] { (byte)OpCode.PUSH1 } - }, - ConsensusData = new ConsensusData - { - PrimaryIndex = 0, - Nonce = 2083236893 - }, - Transactions = Array.Empty() - }; + /// + /// The blocks to be imported. + /// + public IEnumerable Blocks { get; init; } + + /// + /// Indicates whether the blocks need to be verified when importing. + /// + public bool Verify { get; init; } = true; + } - private readonly static Script onPersistScript, postPersistScript; - private const int MaxTxToReverifyPerIdle = 10; - private static readonly object lockObj = new object(); - private readonly NeoSystem system; - private readonly IActorRef txrouter; - private readonly ConcurrentDictionary block_cache = new ConcurrentDictionary(); - private readonly Dictionary block_cache_unverified = new Dictionary(); - internal readonly RelayCache RelayCache = new RelayCache(100); - private ImmutableHashSet extensibleWitnessWhiteList; + /// + /// Sent by the when the import is complete. + /// + public class ImportCompleted { } - public IStore Store { get; } /// - /// A readonly view of the blockchain store. - /// Note: It doesn't need to be disposed because the inside it is null. + /// Sent to the when the consensus is filling the memory pool. /// - public DataCache View => new SnapshotCache(Store); - public MemoryPool MemPool { get; } - public HeaderCache HeaderCache { get; } = new HeaderCache(); + public class FillMemoryPool + { + /// + /// The transactions to be sent. + /// + public IEnumerable Transactions { get; init; } + } + + /// + /// Sent by the when the memory pool is filled. + /// + public class FillCompleted { } - private static Blockchain singleton; - public static Blockchain Singleton + /// + /// Sent to the when inventories need to be re-verified. + /// + public class Reverify { - get - { - while (singleton == null) Thread.Sleep(10); - return singleton; - } + /// + /// The inventories to be re-verified. + /// + public IReadOnlyList Inventories { get; init; } } - static Blockchain() + /// + /// Sent by the when an is relayed. + /// + public class RelayResult { - GenesisBlock.RebuildMerkleRoot(); + /// + /// The that is relayed. + /// + public IInventory Inventory { get; init; } + /// + /// The result. + /// + public VerifyResult Result { get; init; } + } + + internal class Initialize { } + private class UnverifiedBlocksList { public LinkedList Blocks = new(); public HashSet Nodes = new(); } + + private readonly static Script onPersistScript, postPersistScript; + private const int MaxTxToReverifyPerIdle = 10; + private readonly NeoSystem system; + private readonly Dictionary block_cache = new(); + private readonly Dictionary block_cache_unverified = new(); + private ImmutableHashSet extensibleWitnessWhiteList; - using (ScriptBuilder sb = new ScriptBuilder()) + static Blockchain() + { + using (ScriptBuilder sb = new()) { sb.EmitSysCall(ApplicationEngine.System_Contract_NativeOnPersist); onPersistScript = sb.ToArray(); } - using (ScriptBuilder sb = new ScriptBuilder()) + using (ScriptBuilder sb = new()) { sb.EmitSysCall(ApplicationEngine.System_Contract_NativePostPersist); postPersistScript = sb.ToArray(); } } - public Blockchain(NeoSystem system, IStore store) + /// + /// Initializes a new instance of the class. + /// + /// The object that contains the . + public Blockchain(NeoSystem system) { this.system = system; - this.txrouter = Context.ActorOf(TransactionRouter.Props(system)); - this.MemPool = new MemoryPool(system, ProtocolSettings.Default.MemoryPoolMaxTransactions); - this.Store = store; - lock (lockObj) - { - if (singleton != null) - throw new InvalidOperationException(); - DataCache snapshot = View; - if (!NativeContract.Ledger.Initialized(snapshot)) - { - Persist(GenesisBlock); - } - else - { - UpdateExtensibleWitnessWhiteList(snapshot); - } - singleton = this; - } - } - - protected override void PostStop() - { - base.PostStop(); - HeaderCache.Dispose(); - } - - private bool ContainsTransaction(UInt256 hash) - { - if (MemPool.ContainsKey(hash)) return true; - return NativeContract.Ledger.ContainsTransaction(View, hash); - } - - public SnapshotCache GetSnapshot() - { - return new SnapshotCache(Store.GetSnapshot()); } private void OnImport(IEnumerable blocks, bool verify) { - uint currentHeight = NativeContract.Ledger.CurrentIndex(View); + uint currentHeight = NativeContract.Ledger.CurrentIndex(system.StoreView); foreach (Block block in blocks) { if (block.Index <= currentHeight) continue; if (block.Index != currentHeight + 1) throw new InvalidOperationException(); - if (verify && !block.Verify(View)) + if (verify && !block.Verify(system.Settings, system.StoreView)) throw new InvalidOperationException(); Persist(block); ++currentHeight; @@ -191,9 +182,9 @@ private void AddUnverifiedBlockToCache(Block block) private void OnFillMemoryPool(IEnumerable transactions) { // Invalidate all the transactions in the memory pool, to avoid any failures when adding new transactions. - MemPool.InvalidateAllTransactions(); + system.MemPool.InvalidateAllTransactions(); - DataCache snapshot = View; + DataCache snapshot = system.StoreView; // Add the transactions to the memory pool foreach (var tx in transactions) @@ -201,22 +192,30 @@ private void OnFillMemoryPool(IEnumerable transactions) if (NativeContract.Ledger.ContainsTransaction(snapshot, tx.Hash)) continue; // First remove the tx if it is unverified in the pool. - MemPool.TryRemoveUnVerified(tx.Hash, out _); + system.MemPool.TryRemoveUnVerified(tx.Hash, out _); // Add to the memory pool - MemPool.TryAdd(tx, snapshot); + system.MemPool.TryAdd(tx, snapshot); } // Transactions originally in the pool will automatically be reverified based on their priority. Sender.Tell(new FillCompleted()); } + private void OnInitialize() + { + if (!NativeContract.Ledger.Initialized(system.StoreView)) + Persist(system.GenesisBlock); + Sender.Tell(new object()); + } + private void OnInventory(IInventory inventory, bool relay = true) { VerifyResult result = inventory switch { Block block => OnNewBlock(block), Transaction transaction => OnNewTransaction(transaction), - _ => OnNewInventory(inventory) + ExtensiblePayload payload => OnNewExtensiblePayload(payload), + _ => throw new NotSupportedException() }; if (result == VerifyResult.Succeed && relay) { @@ -227,9 +226,9 @@ private void OnInventory(IInventory inventory, bool relay = true) private VerifyResult OnNewBlock(Block block) { - DataCache snapshot = View; + DataCache snapshot = system.StoreView; uint currentHeight = NativeContract.Ledger.CurrentIndex(snapshot); - uint headerHeight = HeaderCache.Last?.Index ?? NativeContract.Ledger.CurrentIndex(snapshot); + uint headerHeight = system.HeaderCache.Last?.Index ?? currentHeight; if (block.Index <= currentHeight) return VerifyResult.AlreadyExists; if (block.Index - 1 > headerHeight) @@ -239,30 +238,30 @@ private VerifyResult OnNewBlock(Block block) } if (block.Index == headerHeight + 1) { - if (!block.Verify(snapshot)) + if (!block.Verify(system.Settings, snapshot, system.HeaderCache)) return VerifyResult.Invalid; } else { - if (!block.Hash.Equals(HeaderCache[block.Index].Hash)) + if (!block.Hash.Equals(system.HeaderCache[block.Index].Hash)) return VerifyResult.Invalid; } block_cache.TryAdd(block.Hash, block); if (block.Index == currentHeight + 1) { Block block_persist = block; - List blocksToPersistList = new List(); + List blocksToPersistList = new(); while (true) { blocksToPersistList.Add(block_persist); if (block_persist.Index + 1 > headerHeight) break; - UInt256 hash = HeaderCache[block_persist.Index + 1].Hash; + UInt256 hash = system.HeaderCache[block_persist.Index + 1].Hash; if (!block_cache.TryGetValue(hash, out block_persist)) break; } int blocksPersisted = 0; // 15000 is the default among of seconds per block, while MilliSecondsPerBlock is the current - uint extraBlocks = (15000 - MillisecondsPerBlock) / 1000; + uint extraBlocks = (15000 - system.Settings.MillisecondsPerBlock) / 1000; foreach (Block blockToPersist in blocksToPersistList) { block_cache_unverified.Remove(blockToPersist.Index); @@ -287,43 +286,45 @@ private VerifyResult OnNewBlock(Block block) if (block.Index + 99 >= headerHeight) system.LocalNode.Tell(new LocalNode.RelayDirectly { Inventory = block }); if (block.Index == headerHeight + 1) - HeaderCache.Add(block.Header); + system.HeaderCache.Add(block.Header); } return VerifyResult.Succeed; } private void OnNewHeaders(Header[] headers) { - if (HeaderCache.Full) return; - DataCache snapshot = View; - uint headerHeight = HeaderCache.Last?.Index ?? NativeContract.Ledger.CurrentIndex(snapshot); + if (system.HeaderCache.Full) return; + DataCache snapshot = system.StoreView; + uint headerHeight = system.HeaderCache.Last?.Index ?? NativeContract.Ledger.CurrentIndex(snapshot); foreach (Header header in headers) { if (header.Index > headerHeight + 1) break; if (header.Index < headerHeight + 1) continue; - if (!header.Verify(snapshot)) break; - HeaderCache.Add(header); + if (!header.Verify(system.Settings, snapshot, system.HeaderCache)) break; + system.HeaderCache.Add(header); ++headerHeight; } } - private VerifyResult OnNewInventory(IInventory inventory) + private VerifyResult OnNewExtensiblePayload(ExtensiblePayload payload) { - if (!inventory.Verify(View)) return VerifyResult.Invalid; - RelayCache.Add(inventory); + DataCache snapshot = system.StoreView; + extensibleWitnessWhiteList ??= UpdateExtensibleWitnessWhiteList(system.Settings, snapshot); + if (!payload.Verify(system.Settings, snapshot, extensibleWitnessWhiteList)) return VerifyResult.Invalid; + system.RelayCache.Add(payload); return VerifyResult.Succeed; } private VerifyResult OnNewTransaction(Transaction transaction) { - if (ContainsTransaction(transaction.Hash)) return VerifyResult.AlreadyExists; - return MemPool.TryAdd(transaction, View); + if (system.ContainsTransaction(transaction.Hash)) return VerifyResult.AlreadyExists; + return system.MemPool.TryAdd(transaction, system.StoreView); } - private void OnPreverifyCompleted(PreverifyCompleted task) + private void OnPreverifyCompleted(TransactionRouter.PreverifyCompleted task) { if (task.Result == VerifyResult.Succeed) - OnInventory(task.Transaction, true); + OnInventory(task.Transaction, task.Relay); else SendRelayResult(task.Transaction, task.Result); } @@ -332,6 +333,9 @@ protected override void OnReceive(object message) { switch (message) { + case Initialize: + OnInitialize(); + break; case Import import: OnImport(import.Blocks, import.Verify); break; @@ -350,11 +354,15 @@ protected override void OnReceive(object message) case IInventory inventory: OnInventory(inventory); break; - case PreverifyCompleted task: + case TransactionRouter.PreverifyCompleted task: OnPreverifyCompleted(task); break; + case Reverify reverify: + foreach (IInventory inventory in reverify.Inventories) + OnInventory(inventory, false); + break; case Idle _: - if (MemPool.ReVerifyTopUnverifiedTransactionsIfNeeded(MaxTxToReverifyPerIdle, View)) + if (system.MemPool.ReVerifyTopUnverifiedTransactionsIfNeeded(MaxTxToReverifyPerIdle, system.StoreView)) Self.Tell(Idle.Instance, ActorRefs.NoSender); break; } @@ -362,22 +370,22 @@ protected override void OnReceive(object message) private void OnTransaction(Transaction tx) { - if (ContainsTransaction(tx.Hash)) + if (system.ContainsTransaction(tx.Hash)) SendRelayResult(tx, VerifyResult.AlreadyExists); else - txrouter.Tell(tx, Sender); + system.TxRouter.Forward(new TransactionRouter.Preverify(tx, true)); } private void Persist(Block block) { - using (SnapshotCache snapshot = GetSnapshot()) + using (SnapshotCache snapshot = system.GetSnapshot()) { - List all_application_executed = new List(); - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, block)) + List all_application_executed = new(); + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, block, system.Settings, 0)) { engine.LoadScript(onPersistScript); if (engine.Execute() != VMState.HALT) throw new InvalidOperationException(); - ApplicationExecuted application_executed = new ApplicationExecuted(engine); + ApplicationExecuted application_executed = new(engine); Context.System.EventStream.Publish(application_executed); all_application_executed.Add(application_executed); } @@ -385,39 +393,37 @@ private void Persist(Block block) // Warning: Do not write into variable snapshot directly. Write into variable clonedSnapshot and commit instead. foreach (Transaction tx in block.Transactions) { - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, tx, clonedSnapshot, block, tx.SystemFee)) + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, tx, clonedSnapshot, block, system.Settings, tx.SystemFee); + engine.LoadScript(tx.Script); + if (engine.Execute() == VMState.HALT) { - engine.LoadScript(tx.Script); - if (engine.Execute() == VMState.HALT) - { - clonedSnapshot.Commit(); - } - else - { - clonedSnapshot = snapshot.CreateSnapshot(); - } - ApplicationExecuted application_executed = new ApplicationExecuted(engine); - Context.System.EventStream.Publish(application_executed); - all_application_executed.Add(application_executed); + clonedSnapshot.Commit(); + } + else + { + clonedSnapshot = snapshot.CreateSnapshot(); } + ApplicationExecuted application_executed = new(engine); + Context.System.EventStream.Publish(application_executed); + all_application_executed.Add(application_executed); } - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.PostPersist, null, snapshot, block)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.PostPersist, null, snapshot, block, system.Settings, 0)) { engine.LoadScript(postPersistScript); if (engine.Execute() != VMState.HALT) throw new InvalidOperationException(); - ApplicationExecuted application_executed = new ApplicationExecuted(engine); + ApplicationExecuted application_executed = new(engine); Context.System.EventStream.Publish(application_executed); all_application_executed.Add(application_executed); } foreach (IPersistencePlugin plugin in Plugin.PersistencePlugins) - plugin.OnPersist(block, snapshot, all_application_executed); + plugin.OnPersist(system, block, snapshot, all_application_executed); snapshot.Commit(); List commitExceptions = null; foreach (IPersistencePlugin plugin in Plugin.PersistencePlugins) { try { - plugin.OnCommit(block, snapshot); + plugin.OnCommit(system, block, snapshot); } catch (Exception ex) { @@ -431,23 +437,28 @@ private void Persist(Block block) } } if (commitExceptions != null) throw new AggregateException(commitExceptions); - UpdateExtensibleWitnessWhiteList(snapshot); - MemPool.UpdatePoolForBlockPersisted(block, snapshot); + system.MemPool.UpdatePoolForBlockPersisted(block, snapshot); } - block_cache.TryRemove(block.PrevHash, out _); + extensibleWitnessWhiteList = null; + block_cache.Remove(block.PrevHash); Context.System.EventStream.Publish(new PersistCompleted { Block = block }); - if (HeaderCache.TryRemoveFirst(out Header header)) + if (system.HeaderCache.TryRemoveFirst(out Header header)) Debug.Assert(header.Index == block.Index); } - public static Props Props(NeoSystem system, IStore store) + /// + /// Gets a object used for creating the actor. + /// + /// The object that contains the . + /// The object used for creating the actor. + public static Props Props(NeoSystem system) { - return Akka.Actor.Props.Create(() => new Blockchain(system, store)).WithMailbox("blockchain-mailbox"); + return Akka.Actor.Props.Create(() => new Blockchain(system)).WithMailbox("blockchain-mailbox"); } private void SendRelayResult(IInventory inventory, VerifyResult result) { - RelayResult rr = new RelayResult + RelayResult rr = new() { Inventory = inventory, Result = result @@ -456,12 +467,12 @@ private void SendRelayResult(IInventory inventory, VerifyResult result) Context.System.EventStream.Publish(rr); } - private void UpdateExtensibleWitnessWhiteList(DataCache snapshot) + private static ImmutableHashSet UpdateExtensibleWitnessWhiteList(ProtocolSettings settings, DataCache snapshot) { uint currentHeight = NativeContract.Ledger.CurrentIndex(snapshot); var builder = ImmutableHashSet.CreateBuilder(); builder.Add(NativeContract.NEO.GetCommitteeAddress(snapshot)); - var validators = NativeContract.NEO.GetNextBlockValidators(snapshot); + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, settings.ValidatorsCount); builder.Add(Contract.GetBFTAddress(validators)); builder.UnionWith(validators.Select(u => Contract.CreateSignatureRedeemScript(u).ToScriptHash())); var oracles = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, currentHeight); @@ -476,34 +487,24 @@ private void UpdateExtensibleWitnessWhiteList(DataCache snapshot) builder.Add(Contract.GetBFTAddress(stateValidators)); builder.UnionWith(stateValidators.Select(u => Contract.CreateSignatureRedeemScript(u).ToScriptHash())); } - extensibleWitnessWhiteList = builder.ToImmutable(); - } - - internal bool IsExtensibleWitnessWhiteListed(UInt160 address) - { - return extensibleWitnessWhiteList.Contains(address); + return builder.ToImmutable(); } } internal class BlockchainMailbox : PriorityMailbox { - public BlockchainMailbox(Akka.Actor.Settings settings, Config config) + public BlockchainMailbox(Settings settings, Config config) : base(settings, config) { } internal protected override bool IsHighPriority(object message) { - switch (message) + return message switch { - case Header[] _: - case Block _: - case ExtensiblePayload _: - case Terminated _: - return true; - default: - return false; - } + Header[] or Block or ExtensiblePayload or Terminated => true, + _ => false, + }; } } } diff --git a/src/neo/Ledger/HeaderCache.cs b/src/neo/Ledger/HeaderCache.cs index ea3380baa9..6ac75a6440 100644 --- a/src/neo/Ledger/HeaderCache.cs +++ b/src/neo/Ledger/HeaderCache.cs @@ -7,11 +7,19 @@ namespace Neo.Ledger { + /// + /// Used to cache the headers of the blocks that have not been received. + /// public sealed class HeaderCache : IDisposable, IEnumerable
{ - private readonly IndexedQueue
headers = new IndexedQueue
(); - private readonly ReaderWriterLockSlim readerWriterLock = new ReaderWriterLockSlim(); + private readonly IndexedQueue
headers = new(); + private readonly ReaderWriterLockSlim readerWriterLock = new(); + /// + /// Gets the at the specified index in the cache. + /// + /// The zero-based index of the to get. + /// The at the specified index in the cache. public Header this[uint index] { get @@ -33,8 +41,19 @@ public Header this[uint index] } } + /// + /// Gets the number of elements in the cache. + /// + public int Count => headers.Count; + + /// + /// Indicates whether the cache is full. + /// public bool Full => headers.Count >= 10000; + /// + /// Gets the last in the cache. Or if the cache is empty. + /// public Header Last { get diff --git a/src/neo/Ledger/MemoryPool.cs b/src/neo/Ledger/MemoryPool.cs index be505878b6..36f8f4a7fe 100644 --- a/src/neo/Ledger/MemoryPool.cs +++ b/src/neo/Ledger/MemoryPool.cs @@ -3,7 +3,6 @@ using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.Plugins; -using Neo.SmartContract.Native; using System; using System.Collections; using System.Collections.Generic; @@ -13,16 +12,19 @@ namespace Neo.Ledger { + /// + /// Used to cache verified transactions before being written into the block. + /// public class MemoryPool : IReadOnlyCollection { // Allow a reverified transaction to be rebroadcasted if it has been this many block times since last broadcast. private const int BlocksTillRebroadcast = 10; private int RebroadcastMultiplierThreshold => Capacity / 10; - private static readonly double MaxMillisecondsToReverifyTx = (double)Blockchain.MillisecondsPerBlock / 3; + private readonly double MaxMillisecondsToReverifyTx; // These two are not expected to be hit, they are just safegaurds. - private static readonly double MaxMillisecondsToReverifyTxPerIdle = (double)Blockchain.MillisecondsPerBlock / 15; + private readonly double MaxMillisecondsToReverifyTxPerIdle; private readonly NeoSystem _system; @@ -34,16 +36,16 @@ public class MemoryPool : IReadOnlyCollection /// performed by the blockchain actor do not need to acquire the read lock; they only need the write /// lock for write operations. ///
- private readonly ReaderWriterLockSlim _txRwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + private readonly ReaderWriterLockSlim _txRwLock = new(LockRecursionPolicy.SupportsRecursion); /// /// Store all verified unsorted transactions currently in the pool. /// - private readonly Dictionary _unsortedTransactions = new Dictionary(); + private readonly Dictionary _unsortedTransactions = new(); /// /// Stores the verified sorted transactins currently in the pool. /// - private readonly SortedSet _sortedTransactions = new SortedSet(); + private readonly SortedSet _sortedTransactions = new(); /// /// Store the unverified transactions currently in the pool. @@ -52,8 +54,8 @@ public class MemoryPool : IReadOnlyCollection /// The top ones that could make it into the next block get verified and moved into the verified data structures /// (_unsortedTransactions, and _sortedTransactions) after each block. /// - private readonly Dictionary _unverifiedTransactions = new Dictionary(); - private readonly SortedSet _unverifiedSortedTransactions = new SortedSet(); + private readonly Dictionary _unverifiedTransactions = new(); + private readonly SortedSet _unverifiedSortedTransactions = new(); // Internal methods to aid in unit testing internal int SortedTxCount => _sortedTransactions.Count; @@ -67,7 +69,7 @@ public class MemoryPool : IReadOnlyCollection /// /// Store all verified unsorted transactions' senders' fee currently in the memory pool. /// - private TransactionVerificationContext VerificationContext = new TransactionVerificationContext(); + private TransactionVerificationContext VerificationContext = new(); /// /// Total count of transactions in the pool. @@ -93,21 +95,32 @@ public int Count /// public int VerifiedCount => _unsortedTransactions.Count; // read of 32 bit type is atomic (no lock) + /// + /// Total count of unverified transactions in the pool. + /// public int UnVerifiedCount => _unverifiedTransactions.Count; - public MemoryPool(NeoSystem system, int capacity) + /// + /// Initializes a new instance of the class. + /// + /// The object that contains the . + public MemoryPool(NeoSystem system) { _system = system; - Capacity = capacity; + Capacity = system.Settings.MemoryPoolMaxTransactions; + MaxMillisecondsToReverifyTx = (double)system.Settings.MillisecondsPerBlock / 3; + MaxMillisecondsToReverifyTxPerIdle = (double)system.Settings.MillisecondsPerBlock / 15; } /// /// Determine whether the pool is holding this transaction and has at some point verified it. - /// Note: The pool may not have verified it since the last block was persisted. To get only the - /// transactions that have been verified during this block use GetVerifiedTransactions() /// - /// the transaction hash - /// true if the MemoryPool contain the transaction + /// The transaction hash. + /// if the contains the transaction; otherwise, . + /// + /// Note: The pool may not have verified it since the last block was persisted. To get only the + /// transactions that have been verified during this block use . + /// public bool ContainsKey(UInt256 hash) { _txRwLock.EnterReadLock(); @@ -121,6 +134,12 @@ public bool ContainsKey(UInt256 hash) } } + /// + /// Gets the associated with the specified hash. + /// + /// The hash of the to get. + /// When this method returns, contains the associated with the specified hash, if the hash is found; otherwise, . + /// if the contains a with the specified hash; otherwise, . public bool TryGetValue(UInt256 hash, out Transaction tx) { _txRwLock.EnterReadLock(); @@ -156,6 +175,10 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// + /// Gets the verified transactions in the . + /// + /// The verified transactions. public IEnumerable GetVerifiedTransactions() { _txRwLock.EnterReadLock(); @@ -169,6 +192,11 @@ public IEnumerable GetVerifiedTransactions() } } + /// + /// Gets both the verified and the unverified transactions in the . + /// + /// The verified transactions. + /// The unverified transactions. public void GetVerifiedAndUnverifiedTransactions(out IEnumerable verifiedTransactions, out IEnumerable unverifiedTransactions) { @@ -184,6 +212,10 @@ public void GetVerifiedAndUnverifiedTransactions(out IEnumerable ve } } + /// + /// Gets the sorted verified transactions in the . + /// + /// The sorted verified transactions. public IEnumerable GetSortedVerifiedTransactions() { _txRwLock.EnterReadLock(); @@ -239,15 +271,6 @@ internal bool CanTransactionFitInPool(Transaction tx) return GetLowestFeeTransaction(out _, out _).CompareTo(tx) <= 0; } - /// - /// Adds an already verified transaction to the memory pool. - /// - /// Note: This must only be called from a single thread (the Blockchain actor). To add a transaction to the pool - /// tell the Blockchain actor about the transaction. - /// - /// - /// - /// internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) { var poolItem = new PoolItem(tx); @@ -258,7 +281,7 @@ internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) _txRwLock.EnterWriteLock(); try { - VerifyResult result = tx.VerifyStateDependent(snapshot, VerificationContext); + VerifyResult result = tx.VerifyStateDependent(_system.Settings, snapshot, VerificationContext); if (result != VerifyResult.Succeed) return result; _unsortedTransactions.Add(tx.Hash, poolItem); @@ -275,9 +298,9 @@ internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) foreach (IMemoryPoolTxObserverPlugin plugin in Plugin.TxObserverPlugins) { - plugin.TransactionAdded(poolItem.Tx); + plugin.TransactionAdded(_system, poolItem.Tx); if (removedTransactions != null) - plugin.TransactionsRemoved(MemoryPoolTxRemovalReason.CapacityExceeded, removedTransactions); + plugin.TransactionsRemoved(_system, MemoryPoolTxRemovalReason.CapacityExceeded, removedTransactions); } if (!_unsortedTransactions.ContainsKey(tx.Hash)) return VerifyResult.OutOfMemory; @@ -286,7 +309,7 @@ internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) private List RemoveOverCapacity() { - List removedTransactions = new List(); + List removedTransactions = new(); do { PoolItem minItem = GetLowestFeeTransaction(out var unsortedPool, out var sortedPool); @@ -361,8 +384,12 @@ internal void UpdatePoolForBlockPersisted(Block block, DataCache snapshot) _txRwLock.ExitWriteLock(); } - uint _maxTxPerBlock = NativeContract.Policy.GetMaxTransactionsPerBlock(snapshot); - ReverifyTransactions(_sortedTransactions, _unverifiedSortedTransactions, (int)_maxTxPerBlock, MaxMillisecondsToReverifyTx, snapshot); + // If we know about headers of future blocks, no point in verifying transactions from the unverified tx pool + // until we get caught up. + if (block.Index > 0 && _system.HeaderCache.Count > 0) + return; + + ReverifyTransactions(_sortedTransactions, _unverifiedSortedTransactions, (int)_system.Settings.MaxTransactionsPerBlock, MaxMillisecondsToReverifyTx, snapshot); } internal void InvalidateAllTransactions() @@ -382,8 +409,8 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, SortedSet unverifiedSortedTxPool, int count, double millisecondsTimeout, DataCache snapshot) { DateTime reverifyCutOffTimeStamp = TimeProvider.Current.UtcNow.AddMilliseconds(millisecondsTimeout); - List reverifiedItems = new List(count); - List invalidItems = new List(); + List reverifiedItems = new(count); + List invalidItems = new(); _txRwLock.EnterWriteLock(); try @@ -391,7 +418,7 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, // Since unverifiedSortedTxPool is ordered in an ascending manner, we take from the end. foreach (PoolItem item in unverifiedSortedTxPool.Reverse().Take(count)) { - if (item.Tx.VerifyStateDependent(snapshot, VerificationContext) == VerifyResult.Succeed) + if (item.Tx.VerifyStateDependent(_system.Settings, snapshot, VerificationContext) == VerifyResult.Succeed) { reverifiedItems.Add(item); VerificationContext.AddTransaction(item.Tx); @@ -407,8 +434,7 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, if (Count > RebroadcastMultiplierThreshold) blocksTillRebroadcast = blocksTillRebroadcast * Count / RebroadcastMultiplierThreshold; - var rebroadcastCutOffTime = TimeProvider.Current.UtcNow.AddMilliseconds( - -Blockchain.MillisecondsPerBlock * blocksTillRebroadcast); + var rebroadcastCutOffTime = TimeProvider.Current.UtcNow.AddMilliseconds(-_system.Settings.MillisecondsPerBlock * blocksTillRebroadcast); foreach (PoolItem item in reverifiedItems) { if (_unsortedTransactions.TryAdd(item.Tx.Hash, item)) @@ -441,7 +467,7 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, var invalidTransactions = invalidItems.Select(p => p.Tx).ToArray(); foreach (IMemoryPoolTxObserverPlugin plugin in Plugin.TxObserverPlugins) - plugin.TransactionsRemoved(MemoryPoolTxRemovalReason.NoLongerValid, invalidTransactions); + plugin.TransactionsRemoved(_system, MemoryPoolTxRemovalReason.NoLongerValid, invalidTransactions); return reverifiedItems.Count; } @@ -457,10 +483,12 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, /// true if more unsorted messages exist, otherwise false internal bool ReVerifyTopUnverifiedTransactionsIfNeeded(int maxToVerify, DataCache snapshot) { + if (_system.HeaderCache.Count > 0) + return false; + if (_unverifiedSortedTransactions.Count > 0) { - uint _maxTxPerBlock = NativeContract.Policy.GetMaxTransactionsPerBlock(snapshot); - int verifyCount = _sortedTransactions.Count > _maxTxPerBlock ? 1 : maxToVerify; + int verifyCount = _sortedTransactions.Count > _system.Settings.MaxTransactionsPerBlock ? 1 : maxToVerify; ReverifyTransactions(_sortedTransactions, _unverifiedSortedTransactions, verifyCount, MaxMillisecondsToReverifyTxPerIdle, snapshot); } diff --git a/src/neo/Ledger/PoolItem.cs b/src/neo/Ledger/PoolItem.cs index e7f5fa999f..317388b5c7 100644 --- a/src/neo/Ledger/PoolItem.cs +++ b/src/neo/Ledger/PoolItem.cs @@ -6,7 +6,7 @@ namespace Neo.Ledger /// /// Represents an item in the Memory Pool. /// - // Note: PoolItem objects don't consider transaction priority (low or high) in their compare CompareTo method. + /// Note: PoolItem objects don't consider transaction priority (low or high) in their compare CompareTo method. /// This is because items of differing priority are never added to the same sorted set in MemoryPool. /// internal class PoolItem : IComparable diff --git a/src/neo/Ledger/TransactionRouter.cs b/src/neo/Ledger/TransactionRouter.cs index 786f33fe85..4ab5536874 100644 --- a/src/neo/Ledger/TransactionRouter.cs +++ b/src/neo/Ledger/TransactionRouter.cs @@ -7,21 +7,20 @@ namespace Neo.Ledger { internal class TransactionRouter : UntypedActor { - private readonly IActorRef blockchain; + public record Preverify(Transaction Transaction, bool Relay); + public record PreverifyCompleted(Transaction Transaction, bool Relay, VerifyResult Result); + + private readonly NeoSystem system; public TransactionRouter(NeoSystem system) { - this.blockchain = system.Blockchain; + this.system = system; } protected override void OnReceive(object message) { - if (!(message is Transaction tx)) return; - blockchain.Tell(new Blockchain.PreverifyCompleted - { - Transaction = tx, - Result = tx.VerifyStateIndependent() - }, Sender); + if (message is not Preverify preverify) return; + system.Blockchain.Tell(new PreverifyCompleted(preverify.Transaction, preverify.Relay, preverify.Transaction.VerifyStateIndependent(system.Settings)), Sender); } internal static Props Props(NeoSystem system) diff --git a/src/neo/Ledger/TransactionVerificationContext.cs b/src/neo/Ledger/TransactionVerificationContext.cs index cfa9dc15cd..7e6b654bbe 100644 --- a/src/neo/Ledger/TransactionVerificationContext.cs +++ b/src/neo/Ledger/TransactionVerificationContext.cs @@ -6,18 +6,25 @@ namespace Neo.Ledger { + /// + /// The context used to verify the transaction. + /// public class TransactionVerificationContext { /// /// Store all verified unsorted transactions' senders' fee currently in the memory pool. /// - private readonly Dictionary senderFee = new Dictionary(); + private readonly Dictionary senderFee = new(); /// /// Store oracle responses /// - private readonly Dictionary oracleResponses = new Dictionary(); + private readonly Dictionary oracleResponses = new(); + /// + /// Adds a verified to the context. + /// + /// The verified . public void AddTransaction(Transaction tx) { var oracle = tx.GetAttribute(); @@ -29,6 +36,12 @@ public void AddTransaction(Transaction tx) senderFee.Add(tx.Sender, tx.SystemFee + tx.NetworkFee); } + /// + /// Determine whether the specified conflicts with other transactions. + /// + /// The specified . + /// The snapshot used to verify the . + /// if the passes the check; otherwise, . public bool CheckTransaction(Transaction tx, DataCache snapshot) { BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, tx.Sender); @@ -44,6 +57,10 @@ public bool CheckTransaction(Transaction tx, DataCache snapshot) return true; } + /// + /// Removes a from the context. + /// + /// The to be removed. public void RemoveTransaction(Transaction tx) { if ((senderFee[tx.Sender] -= tx.SystemFee + tx.NetworkFee) == 0) senderFee.Remove(tx.Sender); diff --git a/src/neo/Ledger/VerifyResult.cs b/src/neo/Ledger/VerifyResult.cs index ab5c1f673e..383aca36e4 100644 --- a/src/neo/Ledger/VerifyResult.cs +++ b/src/neo/Ledger/VerifyResult.cs @@ -1,15 +1,55 @@ +using Neo.Network.P2P.Payloads; + namespace Neo.Ledger { + /// + /// Represents a verifying result of . + /// public enum VerifyResult : byte { + /// + /// Indicates that the verification was successful. + /// Succeed, + + /// + /// Indicates that an with the same hash already exists. + /// AlreadyExists, + + /// + /// Indicates that the is full and the transaction cannot be verified. + /// OutOfMemory, + + /// + /// Indicates that the previous block of the current block has not been received, so the block cannot be verified. + /// UnableToVerify, + + /// + /// Indicates that the is invalid. + /// Invalid, + + /// + /// Indicates that the has expired. + /// Expired, + + /// + /// Indicates that the failed to verify due to insufficient fees. + /// InsufficientFunds, + + /// + /// Indicates that the failed to verify because it didn't comply with the policy. + /// PolicyFail, + + /// + /// Indicates that the failed to verify due to other reasons. + /// Unknown } } diff --git a/src/neo/LogLevel.cs b/src/neo/LogLevel.cs index 0c1c84334d..79951c8988 100644 --- a/src/neo/LogLevel.cs +++ b/src/neo/LogLevel.cs @@ -2,12 +2,34 @@ namespace Neo { + /// + /// Represents the level of logs. + /// public enum LogLevel : byte { + /// + /// The debug log level. + /// Debug = DebugLevel, + + /// + /// The information log level. + /// Info = InfoLevel, + + /// + /// The warning log level. + /// Warning = WarningLevel, + + /// + /// The error log level. + /// Error = ErrorLevel, + + /// + /// The fatal log level. + /// Fatal = Error + 1 } } diff --git a/src/neo/NeoSystem.cs b/src/neo/NeoSystem.cs index 9ba6eb202d..0d530b9711 100644 --- a/src/neo/NeoSystem.cs +++ b/src/neo/NeoSystem.cs @@ -1,46 +1,150 @@ using Akka.Actor; +using Neo.IO.Caching; using Neo.Ledger; using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.Plugins; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; namespace Neo { + /// + /// Represents the basic unit that contains all the components required for running of a NEO node. + /// public class NeoSystem : IDisposable { + /// + /// Triggered when a service is added to the . + /// + public event EventHandler ServiceAdded; + + /// + /// The protocol settings of the . + /// + public ProtocolSettings Settings { get; } + + /// + /// The used to create actors for the . + /// public ActorSystem ActorSystem { get; } = ActorSystem.Create(nameof(NeoSystem), $"akka {{ log-dead-letters = off , loglevel = warning, loggers = [ \"{typeof(Utility.Logger).AssemblyQualifiedName}\" ] }}" + $"blockchain-mailbox {{ mailbox-type: \"{typeof(BlockchainMailbox).AssemblyQualifiedName}\" }}" + $"task-manager-mailbox {{ mailbox-type: \"{typeof(TaskManagerMailbox).AssemblyQualifiedName}\" }}" + $"remote-node-mailbox {{ mailbox-type: \"{typeof(RemoteNodeMailbox).AssemblyQualifiedName}\" }}"); + + /// + /// The genesis block of the NEO blockchain. + /// + public Block GenesisBlock { get; } + + /// + /// The actor of the . + /// public IActorRef Blockchain { get; } + + /// + /// The actor of the . + /// public IActorRef LocalNode { get; } + + /// + /// The actor of the . + /// public IActorRef TaskManager { get; } + /// + /// The transaction router actor of the . + /// + public IActorRef TxRouter; + + /// + /// A readonly view of the store. + /// + /// + /// It doesn't need to be disposed because the inside it is null. + /// + public DataCache StoreView => new SnapshotCache(store); + + /// + /// The memory pool of the . + /// + public MemoryPool MemPool { get; } + + /// + /// The header cache of the . + /// + public HeaderCache HeaderCache { get; } = new(); + + internal RelayCache RelayCache { get; } = new(100); + + private ImmutableList services = ImmutableList.Empty; private readonly string storage_engine; private readonly IStore store; private ChannelsConfig start_message = null; - private bool suspend = false; + private int suspend = 0; static NeoSystem() { // Unify unhandled exceptions AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + + Plugin.LoadPlugins(); } - public NeoSystem(string storageEngine = null, string storagePath = null) + /// + /// Initializes a new instance of the class. + /// + /// The protocol settings of the . + /// The storage engine used to create the objects. If this parameter is , a default in-memory storage engine will be used. + /// The path of the storage. If is the default in-memory storage engine, this parameter is ignored. + public NeoSystem(ProtocolSettings settings, string storageEngine = null, string storagePath = null) { - Plugin.LoadPlugins(this); + this.Settings = settings; + this.GenesisBlock = CreateGenesisBlock(settings); this.storage_engine = storageEngine; this.store = LoadStore(storagePath); - this.Blockchain = ActorSystem.ActorOf(Ledger.Blockchain.Props(this, store)); + this.MemPool = new MemoryPool(this); + this.Blockchain = ActorSystem.ActorOf(Ledger.Blockchain.Props(this)); this.LocalNode = ActorSystem.ActorOf(Network.P2P.LocalNode.Props(this)); this.TaskManager = ActorSystem.ActorOf(Network.P2P.TaskManager.Props(this)); + this.TxRouter = ActorSystem.ActorOf(TransactionRouter.Props(this)); foreach (var plugin in Plugin.Plugins) - plugin.OnPluginsLoaded(); + plugin.OnSystemLoaded(this); + Blockchain.Ask(new Blockchain.Initialize()).Wait(); } + /// + /// Creates the genesis block for the NEO blockchain. + /// + /// The of the NEO system. + /// The genesis block. + public static Block CreateGenesisBlock(ProtocolSettings settings) => new() + { + Header = new Header + { + PrevHash = UInt256.Zero, + MerkleRoot = UInt256.Zero, + Timestamp = (new DateTime(2016, 7, 15, 15, 8, 21, DateTimeKind.Utc)).ToTimestampMS(), + Index = 0, + PrimaryIndex = 0, + NextConsensus = Contract.GetBFTAddress(settings.StandbyValidators), + Witness = new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = new[] { (byte)OpCode.PUSH1 } + }, + }, + Transactions = Array.Empty() + }; + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Utility.Log("UnhandledException", LogLevel.Fatal, e.ExceptionObject); @@ -54,9 +158,39 @@ public void Dispose() // Dispose will call ActorSystem.Terminate() ActorSystem.Dispose(); ActorSystem.WhenTerminated.Wait(); + HeaderCache.Dispose(); store.Dispose(); } + /// + /// Adds a service to the . + /// + /// The service object to be added. + public void AddService(object service) + { + ImmutableInterlocked.Update(ref services, p => p.Add(service)); + ServiceAdded?.Invoke(this, service); + } + + /// + /// Gets a specified type of service object from the . + /// + /// The type of the service object. + /// An action used to filter the service objects. This parameter can be . + /// The service object found. + public T GetService(Func filter = null) + { + IEnumerable result = services.OfType(); + if (filter is null) + return result.FirstOrDefault(); + else + return result.FirstOrDefault(filter); + } + + /// + /// Blocks the current thread until the specified actor has stopped. + /// + /// The actor to wait. public void EnsureStoped(IActorRef actor) { using Inbox inbox = Inbox.Create(ActorSystem); @@ -65,6 +199,11 @@ public void EnsureStoped(IActorRef actor) inbox.Receive(TimeSpan.FromMinutes(5)); } + /// + /// Loads an at the specified path. + /// + /// The path of the storage. + /// The loaded . public IStore LoadStore(string path) { return string.IsNullOrEmpty(storage_engine) || storage_engine == nameof(MemoryStore) @@ -72,30 +211,63 @@ public IStore LoadStore(string path) : Plugin.Storages[storage_engine].GetStore(path); } - internal void ResumeNodeStartup() + /// + /// Resumes the startup process of . + /// + /// if the startup process is resumed; otherwise, . + public bool ResumeNodeStartup() { - suspend = false; + if (Interlocked.Decrement(ref suspend) != 0) + return false; if (start_message != null) { LocalNode.Tell(start_message); start_message = null; } + return true; } + /// + /// Starts the with the specified configuration. + /// + /// The configuration used to start the . public void StartNode(ChannelsConfig config) { start_message = config; - if (!suspend) + if (suspend == 0) { LocalNode.Tell(start_message); start_message = null; } } - internal void SuspendNodeStartup() + /// + /// Suspends the startup process of . + /// + public void SuspendNodeStartup() + { + Interlocked.Increment(ref suspend); + } + + /// + /// Gets a snapshot of the blockchain storage. + /// + /// + public SnapshotCache GetSnapshot() + { + return new SnapshotCache(store.GetSnapshot()); + } + + /// + /// Determines whether the specified transaction exists in the memory pool or storage. + /// + /// The hash of the transaction + /// if the transaction exists; otherwise, . + public bool ContainsTransaction(UInt256 hash) { - suspend = true; + if (MemPool.ContainsKey(hash)) return true; + return NativeContract.Ledger.ContainsTransaction(StoreView, hash); } } } diff --git a/src/neo/Network/P2P/Capabilities/FullNodeCapability.cs b/src/neo/Network/P2P/Capabilities/FullNodeCapability.cs index ccaabbdb5a..c1fd2e6ce1 100644 --- a/src/neo/Network/P2P/Capabilities/FullNodeCapability.cs +++ b/src/neo/Network/P2P/Capabilities/FullNodeCapability.cs @@ -2,8 +2,14 @@ namespace Neo.Network.P2P.Capabilities { + /// + /// Indicates that a node has complete block data. + /// public class FullNodeCapability : NodeCapability { + /// + /// Indicates the current block height of the node. + /// public uint StartHeight; public override int Size => @@ -11,9 +17,9 @@ public class FullNodeCapability : NodeCapability sizeof(uint); // Start Height /// - /// Constructor + /// Initializes a new instance of the class. /// - /// Start Height + /// The current block height of the node. public FullNodeCapability(uint startHeight = 0) : base(NodeCapabilityType.FullNode) { StartHeight = startHeight; diff --git a/src/neo/Network/P2P/Capabilities/NodeCapability.cs b/src/neo/Network/P2P/Capabilities/NodeCapability.cs index 034afb6be8..7a6f458c2b 100644 --- a/src/neo/Network/P2P/Capabilities/NodeCapability.cs +++ b/src/neo/Network/P2P/Capabilities/NodeCapability.cs @@ -4,19 +4,22 @@ namespace Neo.Network.P2P.Capabilities { + /// + /// Represents the capabilities of a NEO node. + /// public abstract class NodeCapability : ISerializable { /// - /// Type + /// Indicates the type of the . /// public readonly NodeCapabilityType Type; public virtual int Size => sizeof(NodeCapabilityType); // Type /// - /// Constructor + /// Initializes a new instance of the class. /// - /// Type + /// The type of the . protected NodeCapability(NodeCapabilityType type) { this.Type = type; @@ -32,26 +35,28 @@ void ISerializable.Deserialize(BinaryReader reader) DeserializeWithoutType(reader); } + /// + /// Deserializes an object from a . + /// + /// The for reading data. + /// The deserialized . public static NodeCapability DeserializeFrom(BinaryReader reader) { - NodeCapability capability; NodeCapabilityType type = (NodeCapabilityType)reader.ReadByte(); - switch (type) + NodeCapability capability = type switch { - case NodeCapabilityType.TcpServer: - case NodeCapabilityType.WsServer: - capability = new ServerCapability(type); - break; - case NodeCapabilityType.FullNode: - capability = new FullNodeCapability(); - break; - default: - throw new FormatException(); - } + NodeCapabilityType.TcpServer or NodeCapabilityType.WsServer => new ServerCapability(type), + NodeCapabilityType.FullNode => new FullNodeCapability(), + _ => throw new FormatException(), + }; capability.DeserializeWithoutType(reader); return capability; } + /// + /// Deserializes the object from a . + /// + /// The for reading data. protected abstract void DeserializeWithoutType(BinaryReader reader); void ISerializable.Serialize(BinaryWriter writer) @@ -60,6 +65,10 @@ void ISerializable.Serialize(BinaryWriter writer) SerializeWithoutType(writer); } + /// + /// Serializes the object to a . + /// + /// The for writing data. protected abstract void SerializeWithoutType(BinaryWriter writer); } } diff --git a/src/neo/Network/P2P/Capabilities/NodeCapabilityType.cs b/src/neo/Network/P2P/Capabilities/NodeCapabilityType.cs index 5ea0594d7b..a2769dd760 100644 --- a/src/neo/Network/P2P/Capabilities/NodeCapabilityType.cs +++ b/src/neo/Network/P2P/Capabilities/NodeCapabilityType.cs @@ -1,12 +1,31 @@ namespace Neo.Network.P2P.Capabilities { + /// + /// Represents the type of . + /// public enum NodeCapabilityType : byte { - //Servers + #region Servers + + /// + /// Indicates that the node is listening on a Tcp port. + /// TcpServer = 0x01, + + /// + /// Indicates that the node is listening on a WebSocket port. + /// WsServer = 0x02, - //Others + #endregion + + #region Others + + /// + /// Indicates that the node has complete block data. + /// FullNode = 0x10 + + #endregion } } diff --git a/src/neo/Network/P2P/Capabilities/ServerCapability.cs b/src/neo/Network/P2P/Capabilities/ServerCapability.cs index 0ce31e130e..e03338b321 100644 --- a/src/neo/Network/P2P/Capabilities/ServerCapability.cs +++ b/src/neo/Network/P2P/Capabilities/ServerCapability.cs @@ -3,8 +3,14 @@ namespace Neo.Network.P2P.Capabilities { + /// + /// Indicates that the node is a server. + /// public class ServerCapability : NodeCapability { + /// + /// Indicates the port that the node is listening on. + /// public ushort Port; public override int Size => @@ -12,10 +18,10 @@ public class ServerCapability : NodeCapability sizeof(ushort); // Port /// - /// Constructor + /// Initializes a new instance of the class. /// - /// Channel - /// Port + /// The type of the . It must be or + /// The port that the node is listening on. public ServerCapability(NodeCapabilityType type, ushort port = 0) : base(type) { if (type != NodeCapabilityType.TcpServer && type != NodeCapabilityType.WsServer) diff --git a/src/neo/Network/P2P/ChannelsConfig.cs b/src/neo/Network/P2P/ChannelsConfig.cs index 2882876bd2..88d01a7419 100644 --- a/src/neo/Network/P2P/ChannelsConfig.cs +++ b/src/neo/Network/P2P/ChannelsConfig.cs @@ -2,30 +2,33 @@ namespace Neo.Network.P2P { + /// + /// Represents the settings to start . + /// public class ChannelsConfig { /// - /// Tcp configuration + /// Tcp configuration. /// public IPEndPoint Tcp { get; set; } /// - /// Web socket configuration + /// Web socket configuration. /// public IPEndPoint WebSocket { get; set; } /// - /// Minimum desired connections + /// Minimum desired connections. /// public int MinDesiredConnections { get; set; } = Peer.DefaultMinDesiredConnections; /// - /// Max allowed connections + /// Max allowed connections. /// public int MaxConnections { get; set; } = Peer.DefaultMaxConnections; /// - /// Max allowed connections per address + /// Max allowed connections per address. /// public int MaxConnectionsPerAddress { get; set; } = 3; } diff --git a/src/neo/Network/P2P/Connection.cs b/src/neo/Network/P2P/Connection.cs index b12b2723a5..5bca0b5aa4 100644 --- a/src/neo/Network/P2P/Connection.cs +++ b/src/neo/Network/P2P/Connection.cs @@ -7,27 +7,45 @@ namespace Neo.Network.P2P { + /// + /// Represents a connection of the P2P network. + /// public abstract class Connection : UntypedActor { internal class Close { public bool Abort; } - internal class Ack : Tcp.Event { public static Ack Instance = new Ack(); } + internal class Ack : Tcp.Event { public static Ack Instance = new(); } /// - /// connection initial timeout (in seconds) before any package has been accepted + /// connection initial timeout (in seconds) before any package has been accepted. /// private const int connectionTimeoutLimitStart = 10; + /// - /// connection timeout (in seconds) after every `OnReceived(ByteString data)` event + /// connection timeout (in seconds) after every `OnReceived(ByteString data)` event. /// private const int connectionTimeoutLimit = 60; + /// + /// The address of the remote node. + /// public IPEndPoint Remote { get; } + + /// + /// The address of the local node. + /// public IPEndPoint Local { get; } private ICancelable timer; private readonly IActorRef tcp; private readonly WebSocket ws; private bool disconnected = false; + + /// + /// Initializes a new instance of the class. + /// + /// The underlying connection object. + /// The address of the remote node. + /// The address of the local node. protected Connection(object connection, IPEndPoint remote, IPEndPoint local) { this.Remote = remote; @@ -65,12 +83,16 @@ private void WsReceive() failure: ex => new Tcp.ErrorClosed(ex.Message)); } + /// + /// Disconnect from the remote node. + /// + /// Indicates whether the TCP ABORT command should be sent. public void Disconnect(bool abort = false) { disconnected = true; if (tcp != null) { - tcp.Tell(abort ? (Tcp.CloseCommand)Tcp.Abort.Instance : Tcp.Close.Instance); + tcp.Tell(abort ? Tcp.Abort.Instance : Tcp.Close.Instance); } else { @@ -79,10 +101,17 @@ public void Disconnect(bool abort = false) Context.Stop(Self); } + /// + /// Called when a TCP ACK message is received. + /// protected virtual void OnAck() { } + /// + /// Called when data is received. + /// + /// The received data. protected abstract void OnData(ByteString data); protected override void OnReceive(object message) @@ -127,6 +156,10 @@ protected override void PostStop() base.PostStop(); } + /// + /// Sends data to the remote node. + /// + /// protected void SendData(ByteString data) { if (tcp != null) @@ -135,7 +168,7 @@ protected void SendData(ByteString data) } else { - ArraySegment segment = new ArraySegment(data.ToArray()); + ArraySegment segment = new(data.ToArray()); ws.SendAsync(segment, WebSocketMessageType.Binary, true, CancellationToken.None).PipeTo(Self, success: () => Ack.Instance, failure: ex => new Tcp.ErrorClosed(ex.Message)); diff --git a/src/neo/Network/P2P/Helper.cs b/src/neo/Network/P2P/Helper.cs index db95362bd6..a788ee7ed3 100644 --- a/src/neo/Network/P2P/Helper.cs +++ b/src/neo/Network/P2P/Helper.cs @@ -1,36 +1,43 @@ using Neo.Cryptography; +using Neo.IO; using Neo.Network.P2P.Payloads; using System.IO; namespace Neo.Network.P2P { + /// + /// A helper class for . + /// public static class Helper { - public static byte[] GetHashData(this IVerifiable verifiable) - { - return GetHashData(verifiable, ProtocolSettings.Default.Magic); - } - - public static byte[] GetHashData(this IVerifiable verifiable, uint magic) - { - using (MemoryStream ms = new MemoryStream()) - using (BinaryWriter writer = new BinaryWriter(ms)) - { - writer.Write(magic); - verifiable.SerializeUnsigned(writer); - writer.Flush(); - return ms.ToArray(); - } - } - + /// + /// Calculates the hash of a . + /// + /// The object to hash. + /// The hash of the object. public static UInt256 CalculateHash(this IVerifiable verifiable) { - return new UInt256(Crypto.Hash256(verifiable.GetHashData(ProtocolSettings.Default.Magic))); + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms); + verifiable.SerializeUnsigned(writer); + writer.Flush(); + return new UInt256(ms.ToArray().Sha256()); } - public static UInt256 CalculateHash(this IVerifiable verifiable, uint magic) + /// + /// Gets the data of a object to be hashed. + /// + /// The object to hash. + /// The magic number of the network. + /// The data to hash. + public static byte[] GetSignData(this IVerifiable verifiable, uint magic) { - return new UInt256(Crypto.Hash256(verifiable.GetHashData(magic))); + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms); + writer.Write(magic); + writer.Write(verifiable.Hash); + writer.Flush(); + return ms.ToArray(); } } } diff --git a/src/neo/Network/P2P/LocalNode.cs b/src/neo/Network/P2P/LocalNode.cs index c2941c38bd..459fb18465 100644 --- a/src/neo/Network/P2P/LocalNode.cs +++ b/src/neo/Network/P2P/LocalNode.cs @@ -8,68 +8,89 @@ using System.Net; using System.Net.Sockets; using System.Reflection; -using System.Threading; using System.Threading.Tasks; namespace Neo.Network.P2P { + /// + /// Actor used to manage the connections of the local node. + /// public class LocalNode : Peer { + /// + /// Sent to to relay an . + /// public class RelayDirectly { public IInventory Inventory; } + + /// + /// Sent to to send an . + /// public class SendDirectly { public IInventory Inventory; } + /// + /// Sent to to request for an instance of . + /// + public class GetInstance { } + + /// + /// Indicates the protocol version of the local node. + /// public const uint ProtocolVersion = 0; + private const int MaxCountFromSeedList = 5; - private readonly IPEndPoint[] SeedList = new IPEndPoint[ProtocolSettings.Default.SeedList.Length]; + private readonly IPEndPoint[] SeedList; - private static readonly object lockObj = new object(); private readonly NeoSystem system; - internal readonly ConcurrentDictionary RemoteNodes = new ConcurrentDictionary(); + internal readonly ConcurrentDictionary RemoteNodes = new(); + /// + /// Indicates the number of connected nodes. + /// public int ConnectedCount => RemoteNodes.Count; + + /// + /// Indicates the number of unconnected nodes. When the number of connections is not enough, it will automatically connect to these nodes. + /// public int UnconnectedCount => UnconnectedPeers.Count; + + /// + /// The random number used to identify the local node. + /// public static readonly uint Nonce; - public static string UserAgent { get; set; } - private static LocalNode singleton; - public static LocalNode Singleton - { - get - { - while (singleton == null) Thread.Sleep(10); - return singleton; - } - } + /// + /// The identifier of the client software of the local node. + /// + public static string UserAgent { get; set; } static LocalNode() { - Random rand = new Random(); + Random rand = new(); Nonce = (uint)rand.Next(); UserAgent = $"/{Assembly.GetExecutingAssembly().GetName().Name}:{Assembly.GetExecutingAssembly().GetVersion()}/"; } + /// + /// Initializes a new instance of the class. + /// + /// The object that contains the . public LocalNode(NeoSystem system) { - lock (lockObj) + this.system = system; + this.SeedList = new IPEndPoint[system.Settings.SeedList.Length]; + + // Start dns resolution in parallel + string[] seedList = system.Settings.SeedList; + for (int i = 0; i < seedList.Length; i++) { - if (singleton != null) - throw new InvalidOperationException(); - this.system = system; - singleton = this; - - // Start dns resolution in parallel - string[] seedList = ProtocolSettings.Default.SeedList; - for (int i = 0; i < seedList.Length; i++) - { - int index = i; - Task.Run(() => SeedList[index] = GetIpEndPoint(seedList[index])); - } + int index = i; + Task.Run(() => SeedList[index] = GetIpEndPoint(seedList[index])); } } /// /// Packs a MessageCommand to a full Message with an optional ISerializable payload. - /// Forwards it to . + /// Forwards it to . /// /// The message command to be packed. /// Optional payload to be Serialized along the message. @@ -79,7 +100,7 @@ private void BroadcastMessage(MessageCommand command, ISerializable payload = nu } /// - /// Broadcast a message to all connected nodes, namely . + /// Broadcast a message to all connected nodes. /// /// The message to be broadcasted. private void BroadcastMessage(Message message) => SendToRemoteNodes(message); @@ -128,14 +149,15 @@ internal static IPEndPoint GetIpEndPoint(string hostAndPort) } /// - /// Check the new connection
- /// If it is equal to the Nonce of local or any remote node, it'll return false, else we'll return true and update the Listener address of the connected remote node. + /// Checks the new connection. + /// If it is equal to the nonce of local or any remote node, it'll return false, else we'll return true and update the Listener address of the connected remote node. ///
- /// Remote node actor - /// Remote node + /// Remote node actor. + /// Remote node object. + /// if the new connection is allowed; otherwise, . public bool AllowNewConnection(IActorRef actor, RemoteNode node) { - if (node.Version.Magic != ProtocolSettings.Default.Magic) return false; + if (node.Version.Magic != system.Settings.Magic) return false; if (node.Version.Nonce == Nonce) return false; // filter duplicate connections @@ -149,26 +171,33 @@ public bool AllowNewConnection(IActorRef actor, RemoteNode node) return true; } + /// + /// Gets the connected remote nodes. + /// + /// public IEnumerable GetRemoteNodes() { return RemoteNodes.Values; } + /// + /// Gets the unconnected nodes. + /// + /// public IEnumerable GetUnconnectedPeers() { return UnconnectedPeers; } /// - /// Override of abstract class that is triggered when is empty. - /// Performs a BroadcastMessage with the command `MessageCommand.GetAddr`, which, eventually, tells all known connections. - /// If there are no connected peers it will try with the default, respecting MaxCountFromSeedList limit. + /// Performs a broadcast with the command , which, eventually, tells all known connections. + /// If there are no connected peers it will try with the default, respecting limit. /// - /// The count of peers required + /// Number of peers that are being requested. protected override void NeedMorePeers(int count) { count = Math.Max(count, MaxCountFromSeedList); - if (ConnectedPeers.Count > 0) + if (!ConnectedPeers.IsEmpty) { BroadcastMessage(MessageCommand.GetAddr); } @@ -177,7 +206,7 @@ protected override void NeedMorePeers(int count) // Will call AddPeers with default SeedList set cached on . // It will try to add those, sequentially, to the list of currently unconnected ones. - Random rand = new Random(); + Random rand = new(); AddPeers(SeedList.Where(u => u != null).OrderBy(p => rand.Next()).Take(count)); } } @@ -196,6 +225,9 @@ protected override void OnReceive(object message) case SendDirectly send: OnSendDirectly(send.Inventory); break; + case GetInstance _: + Sender.Tell(this); + break; } } @@ -223,6 +255,11 @@ protected override void OnTcpConnected(IActorRef connection) connection.Tell(new RemoteNode.StartProtocol()); } + /// + /// Gets a object used for creating the actor. + /// + /// The object that contains the . + /// The object used for creating the actor. public static Props Props(NeoSystem system) { return Akka.Actor.Props.Create(() => new LocalNode(system)); @@ -230,7 +267,7 @@ public static Props Props(NeoSystem system) protected override Props ProtocolProps(object connection, IPEndPoint remote, IPEndPoint local) { - return RemoteNode.Props(system, connection, remote, local); + return RemoteNode.Props(system, this, connection, remote, local); } } } diff --git a/src/neo/Network/P2P/Message.cs b/src/neo/Network/P2P/Message.cs index 15a7e0783a..7cd7515b62 100644 --- a/src/neo/Network/P2P/Message.cs +++ b/src/neo/Network/P2P/Message.cs @@ -8,27 +8,47 @@ namespace Neo.Network.P2P { + /// + /// Represents a message on the NEO network. + /// public class Message : ISerializable { + /// + /// Indicates the maximum size of . + /// public const int PayloadMaxSize = 0x02000000; + private const int CompressionMinSize = 128; private const int CompressionThreshold = 64; /// - /// Flags that represents whether a message is compressed. - /// 0 for None, 1 for Compressed. + /// The flags of the message. /// public MessageFlags Flags; + + /// + /// The command of the message. + /// public MessageCommand Command; + + /// + /// The payload of the message. + /// public ISerializable Payload; private byte[] _payload_compressed; public int Size => sizeof(MessageFlags) + sizeof(MessageCommand) + _payload_compressed.GetVarSize(); + /// + /// Creates a new instance of the class. + /// + /// The command of the message. + /// The payload of the message. For the messages that don't require a payload, it should be . + /// public static Message Create(MessageCommand command, ISerializable payload = null) { - Message message = new Message + Message message = new() { Flags = MessageFlags.None, Command = command, diff --git a/src/neo/Network/P2P/MessageCommand.cs b/src/neo/Network/P2P/MessageCommand.cs index c66c15799a..0b0474f5de 100644 --- a/src/neo/Network/P2P/MessageCommand.cs +++ b/src/neo/Network/P2P/MessageCommand.cs @@ -1,58 +1,164 @@ +using Neo.Cryptography; using Neo.IO.Caching; using Neo.Network.P2P.Payloads; namespace Neo.Network.P2P { + /// + /// Represents the command of a message. + /// public enum MessageCommand : byte { - //handshaking + #region handshaking + + /// + /// Sent when a connection is established. + /// [ReflectionCache(typeof(VersionPayload))] Version = 0x00, + + /// + /// Sent to respond to messages. + /// Verack = 0x01, - //connectivity + #endregion + + #region connectivity + + /// + /// Sent to request for remote nodes. + /// GetAddr = 0x10, + + /// + /// Sent to respond to messages. + /// [ReflectionCache(typeof(AddrPayload))] Addr = 0x11, + + /// + /// Sent to detect whether the connection has been disconnected. + /// [ReflectionCache(typeof(PingPayload))] Ping = 0x18, + + /// + /// Sent to respond to messages. + /// [ReflectionCache(typeof(PingPayload))] Pong = 0x19, - //synchronization + #endregion + + #region synchronization + + /// + /// Sent to request for headers. + /// [ReflectionCache(typeof(GetBlockByIndexPayload))] GetHeaders = 0x20, + + /// + /// Sent to respond to messages. + /// [ReflectionCache(typeof(HeadersPayload))] Headers = 0x21, + + /// + /// Sent to request for blocks. + /// [ReflectionCache(typeof(GetBlocksPayload))] GetBlocks = 0x24, + + /// + /// Sent to request for memory pool. + /// Mempool = 0x25, + + /// + /// Sent to relay inventories. + /// [ReflectionCache(typeof(InvPayload))] Inv = 0x27, + + /// + /// Sent to request for inventories. + /// [ReflectionCache(typeof(InvPayload))] GetData = 0x28, + + /// + /// Sent to request for blocks. + /// [ReflectionCache(typeof(GetBlockByIndexPayload))] GetBlockByIndex = 0x29, + + /// + /// Sent to respond to messages when the inventories are not found. + /// [ReflectionCache(typeof(InvPayload))] NotFound = 0x2a, + + /// + /// Sent to send a transaction. + /// [ReflectionCache(typeof(Transaction))] Transaction = 0x2b, + + /// + /// Sent to send a block. + /// [ReflectionCache(typeof(Block))] Block = 0x2c, + + /// + /// Sent to send an . + /// [ReflectionCache(typeof(ExtensiblePayload))] Extensible = 0x2e, + + /// + /// Sent to reject an inventory. + /// Reject = 0x2f, - //SPV protocol + #endregion + + #region SPV protocol + + /// + /// Sent to load the . + /// [ReflectionCache(typeof(FilterLoadPayload))] FilterLoad = 0x30, + + /// + /// Sent to update the items for the . + /// [ReflectionCache(typeof(FilterAddPayload))] FilterAdd = 0x31, + + /// + /// Sent to clear the . + /// FilterClear = 0x32, + + /// + /// Sent to send a filtered block. + /// [ReflectionCache(typeof(MerkleBlockPayload))] MerkleBlock = 0x38, - //others + #endregion + + #region others + + /// + /// Sent to send an alert. + /// Alert = 0x40, + + #endregion } } diff --git a/src/neo/Network/P2P/MessageFlags.cs b/src/neo/Network/P2P/MessageFlags.cs index 4e8a34c1a7..37b56a83f9 100644 --- a/src/neo/Network/P2P/MessageFlags.cs +++ b/src/neo/Network/P2P/MessageFlags.cs @@ -2,10 +2,20 @@ namespace Neo.Network.P2P { + /// + /// Represents the flags of a message. + /// [Flags] public enum MessageFlags : byte { + /// + /// No flag is set for the message. + /// None = 0, + + /// + /// Indicates that the message is compressed. + /// Compressed = 1 << 0 } } diff --git a/src/neo/Network/P2P/Payloads/AddrPayload.cs b/src/neo/Network/P2P/Payloads/AddrPayload.cs index 0a8f09c71e..6b1458f35b 100644 --- a/src/neo/Network/P2P/Payloads/AddrPayload.cs +++ b/src/neo/Network/P2P/Payloads/AddrPayload.cs @@ -4,14 +4,28 @@ namespace Neo.Network.P2P.Payloads { + /// + /// This message is sent to respond to messages. + /// public class AddrPayload : ISerializable { + /// + /// Indicates the maximum number of nodes sent each time. + /// public const int MaxCountToSend = 200; + /// + /// The list of nodes. + /// public NetworkAddressWithTime[] AddressList; public int Size => AddressList.GetVarSize(); + /// + /// Creates a new instance of the class. + /// + /// The list of nodes. + /// The created payload. public static AddrPayload Create(params NetworkAddressWithTime[] addresses) { return new AddrPayload diff --git a/src/neo/Network/P2P/Payloads/Block.cs b/src/neo/Network/P2P/Payloads/Block.cs index 73b1ff97bd..2670a90615 100644 --- a/src/neo/Network/P2P/Payloads/Block.cs +++ b/src/neo/Network/P2P/Payloads/Block.cs @@ -1,69 +1,87 @@ using Neo.Cryptography; using Neo.IO; using Neo.IO.Json; +using Neo.Ledger; +using Neo.Persistence; using System; -using System.Collections.Generic; using System.IO; using System.Linq; namespace Neo.Network.P2P.Payloads { - public class Block : BlockBase, IInventory, IEquatable + /// + /// Represents a block. + /// + public sealed class Block : IEquatable, IInventory { - public const int MaxContentsPerBlock = ushort.MaxValue; - public const int MaxTransactionsPerBlock = MaxContentsPerBlock - 1; + /// + /// The header of the block. + /// + public Header Header; - public ConsensusData ConsensusData; + /// + /// The transaction list of the block. + /// public Transaction[] Transactions; - private Header _header = null; - public Header Header - { - get - { - if (_header == null) - { - _header = new Header - { - PrevHash = PrevHash, - MerkleRoot = MerkleRoot, - Timestamp = Timestamp, - Index = Index, - NextConsensus = NextConsensus, - Witness = Witness - }; - } - return _header; - } - } + public UInt256 Hash => Header.Hash; - InventoryType IInventory.InventoryType => InventoryType.Block; + /// + /// The version of the block. + /// + public uint Version => Header.Version; - public override int Size => base.Size - + IO.Helper.GetVarSize(Transactions.Length + 1) //Count - + ConsensusData.Size //ConsensusData - + Transactions.Sum(p => p.Size); //Transactions + /// + /// The hash of the previous block. + /// + public UInt256 PrevHash => Header.PrevHash; - public static UInt256 CalculateMerkleRoot(UInt256 consensusDataHash, IEnumerable transactionHashes) - { - return MerkleTree.ComputeRoot(transactionHashes.Prepend(consensusDataHash).ToArray()); - } + /// + /// The merkle root of the transactions. + /// + public UInt256 MerkleRoot => Header.MerkleRoot; + + /// + /// The timestamp of the block. + /// + public ulong Timestamp => Header.Timestamp; + + /// + /// The index of the block. + /// + public uint Index => Header.Index; + + /// + /// The primary index of the consensus node that generated this block. + /// + public byte PrimaryIndex => Header.PrimaryIndex; + + /// + /// The multi-signature address of the consensus nodes that generates the next block. + /// + public UInt160 NextConsensus => Header.NextConsensus; + + /// + /// The witness of the block. + /// + public Witness Witness => Header.Witness; - public override void Deserialize(BinaryReader reader) + InventoryType IInventory.InventoryType => InventoryType.Block; + public int Size => Header.Size + Transactions.GetVarSize(); + Witness[] IVerifiable.Witnesses { get => ((IVerifiable)Header).Witnesses; set => throw new NotSupportedException(); } + + public void Deserialize(BinaryReader reader) { - base.Deserialize(reader); - int count = (int)reader.ReadVarInt(MaxContentsPerBlock); - if (count == 0) throw new FormatException(); - ConsensusData = reader.ReadSerializable(); - Transactions = new Transaction[count - 1]; - for (int i = 0; i < Transactions.Length; i++) - Transactions[i] = reader.ReadSerializable(); + Header = reader.ReadSerializable
(); + Transactions = reader.ReadSerializableArray(ushort.MaxValue); if (Transactions.Distinct().Count() != Transactions.Length) throw new FormatException(); - if (CalculateMerkleRoot(ConsensusData.Hash, Transactions.Select(p => p.Hash)) != MerkleRoot) + if (MerkleTree.ComputeRoot(Transactions.Select(p => p.Hash).ToArray()) != Header.MerkleRoot) throw new FormatException(); } + void IVerifiable.DeserializeUnsigned(BinaryReader reader) => throw new NotSupportedException(); + public bool Equals(Block other) { if (ReferenceEquals(this, other)) return true; @@ -81,26 +99,37 @@ public override int GetHashCode() return Hash.GetHashCode(); } - public void RebuildMerkleRoot() + UInt160[] IVerifiable.GetScriptHashesForVerifying(DataCache snapshot) => ((IVerifiable)Header).GetScriptHashesForVerifying(snapshot); + + public void Serialize(BinaryWriter writer) + { + writer.Write(Header); + writer.Write(Transactions); + } + + void IVerifiable.SerializeUnsigned(BinaryWriter writer) => ((IVerifiable)Header).SerializeUnsigned(writer); + + /// + /// Converts the block to a JSON object. + /// + /// The used during the conversion. + /// The block represented by a JSON object. + public JObject ToJson(ProtocolSettings settings) { - MerkleRoot = CalculateMerkleRoot(ConsensusData.Hash, Transactions.Select(p => p.Hash)); + JObject json = Header.ToJson(settings); + json["size"] = Size; + json["tx"] = Transactions.Select(p => p.ToJson(settings)).ToArray(); + return json; } - public override void Serialize(BinaryWriter writer) + internal bool Verify(ProtocolSettings settings, DataCache snapshot) { - base.Serialize(writer); - writer.WriteVarInt(Transactions.Length + 1); - writer.Write(ConsensusData); - foreach (Transaction tx in Transactions) - writer.Write(tx); + return Header.Verify(settings, snapshot); } - public override JObject ToJson() + internal bool Verify(ProtocolSettings settings, DataCache snapshot, HeaderCache headerCache) { - JObject json = base.ToJson(); - json["consensusdata"] = ConsensusData.ToJson(); - json["tx"] = Transactions.Select(p => p.ToJson()).ToArray(); - return json; + return Header.Verify(settings, snapshot, headerCache); } } } diff --git a/src/neo/Network/P2P/Payloads/BlockBase.cs b/src/neo/Network/P2P/Payloads/BlockBase.cs deleted file mode 100644 index a073858cf6..0000000000 --- a/src/neo/Network/P2P/Payloads/BlockBase.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Neo.IO; -using Neo.IO.Json; -using Neo.Ledger; -using Neo.Persistence; -using Neo.SmartContract; -using Neo.SmartContract.Native; -using Neo.Wallets; -using System; -using System.IO; - -namespace Neo.Network.P2P.Payloads -{ - public abstract class BlockBase : IVerifiable - { - public uint Version; - public UInt256 PrevHash; - public UInt256 MerkleRoot; - public ulong Timestamp; - public uint Index; - public UInt160 NextConsensus; - public Witness Witness; - - private UInt256 _hash = null; - public UInt256 Hash - { - get - { - if (_hash == null) - { - _hash = this.CalculateHash(); - } - return _hash; - } - } - - public virtual int Size => - sizeof(uint) + //Version - UInt256.Length + //PrevHash - UInt256.Length + //MerkleRoot - sizeof(ulong) + //Timestamp - sizeof(uint) + //Index - UInt160.Length + //NextConsensus - 1 + //Witness array count - Witness.Size; //Witness - - 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); - Witness[] witnesses = reader.ReadSerializableArray(1); - if (witnesses.Length != 1) throw new FormatException(); - Witness = witnesses[0]; - } - - void IVerifiable.DeserializeUnsigned(BinaryReader reader) - { - Version = reader.ReadUInt32(); - PrevHash = reader.ReadSerializable(); - MerkleRoot = reader.ReadSerializable(); - Timestamp = reader.ReadUInt64(); - Index = reader.ReadUInt32(); - NextConsensus = reader.ReadSerializable(); - } - - UInt160[] IVerifiable.GetScriptHashesForVerifying(DataCache snapshot) - { - if (PrevHash == UInt256.Zero) return new[] { Witness.ScriptHash }; - BlockBase prev = Blockchain.Singleton.HeaderCache[Index - 1] ?? (BlockBase)NativeContract.Ledger.GetTrimmedBlock(snapshot, PrevHash); - if (prev is null) throw new InvalidOperationException(); - return new[] { prev.NextConsensus }; - } - - public virtual void Serialize(BinaryWriter writer) - { - ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write(new Witness[] { Witness }); - } - - void IVerifiable.SerializeUnsigned(BinaryWriter writer) - { - writer.Write(Version); - writer.Write(PrevHash); - writer.Write(MerkleRoot); - writer.Write(Timestamp); - writer.Write(Index); - writer.Write(NextConsensus); - } - - public virtual JObject ToJson() - { - JObject json = new JObject(); - json["hash"] = Hash.ToString(); - json["size"] = Size; - json["version"] = Version; - json["previousblockhash"] = PrevHash.ToString(); - json["merkleroot"] = MerkleRoot.ToString(); - json["time"] = Timestamp; - json["index"] = Index; - json["nextconsensus"] = NextConsensus.ToAddress(); - json["witnesses"] = new JArray(Witness.ToJson()); - return json; - } - - public virtual bool Verify(DataCache snapshot) - { - var prev = Blockchain.Singleton.HeaderCache[Index - 1] ?? NativeContract.Ledger.GetHeader(snapshot, Index - 1); - if (prev is null) return false; - if (prev.Hash != PrevHash) return false; - if (prev.Timestamp >= Timestamp) return false; - if (!this.VerifyWitnesses(snapshot, 1_00000000)) return false; - return true; - } - } -} diff --git a/src/neo/Network/P2P/Payloads/ConsensusData.cs b/src/neo/Network/P2P/Payloads/ConsensusData.cs deleted file mode 100644 index 72726b8b15..0000000000 --- a/src/neo/Network/P2P/Payloads/ConsensusData.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Neo.Cryptography; -using Neo.IO; -using Neo.IO.Json; -using System; -using System.IO; - -namespace Neo.Network.P2P.Payloads -{ - public class ConsensusData : ISerializable - { - public byte PrimaryIndex; - public ulong Nonce; - - private UInt256 _hash = null; - public UInt256 Hash - { - get - { - if (_hash == null) - { - _hash = new UInt256(Crypto.Hash256(this.ToArray())); - } - return _hash; - } - } - - public int Size => sizeof(byte) + sizeof(ulong); - - void ISerializable.Deserialize(BinaryReader reader) - { - PrimaryIndex = reader.ReadByte(); - if (PrimaryIndex >= ProtocolSettings.Default.ValidatorsCount) - throw new FormatException(); - Nonce = reader.ReadUInt64(); - } - - void ISerializable.Serialize(BinaryWriter writer) - { - writer.Write(PrimaryIndex); - writer.Write(Nonce); - } - - public JObject ToJson() - { - JObject json = new JObject(); - json["primary"] = PrimaryIndex; - json["nonce"] = Nonce.ToString("x16"); - return json; - } - } -} diff --git a/src/neo/Network/P2P/Payloads/ExtensiblePayload.cs b/src/neo/Network/P2P/Payloads/ExtensiblePayload.cs index 4e405d4814..36155cdc8b 100644 --- a/src/neo/Network/P2P/Payloads/ExtensiblePayload.cs +++ b/src/neo/Network/P2P/Payloads/ExtensiblePayload.cs @@ -1,20 +1,46 @@ using Neo.IO; -using Neo.Ledger; using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; using System; +using System.Collections.Generic; using System.IO; namespace Neo.Network.P2P.Payloads { + /// + /// Represents an extensible message that can be relayed. + /// public class ExtensiblePayload : IInventory { + /// + /// The category of the extension. + /// public string Category; + + /// + /// Indicates that the payload is only valid when the block height is greater than or equal to this value. + /// public uint ValidBlockStart; + + /// + /// Indicates that the payload is only valid when the block height is less than this value. + /// public uint ValidBlockEnd; + + /// + /// The sender of the payload. + /// public UInt160 Sender; + + /// + /// The data of the payload. + /// public byte[] Data; + + /// + /// The witness of the payload. It must match the . + /// public Witness Witness; private UInt256 _hash = null; @@ -67,7 +93,7 @@ void IVerifiable.DeserializeUnsigned(BinaryReader reader) ValidBlockEnd = reader.ReadUInt32(); if (ValidBlockStart >= ValidBlockEnd) throw new FormatException(); Sender = reader.ReadSerializable(); - Data = reader.ReadVarBytes(ushort.MaxValue); + Data = reader.ReadVarBytes(Message.PayloadMaxSize); } UInt160[] IVerifiable.GetScriptHashesForVerifying(DataCache snapshot) @@ -90,12 +116,12 @@ void IVerifiable.SerializeUnsigned(BinaryWriter writer) writer.WriteVarBytes(Data); } - public bool Verify(DataCache snapshot) + internal bool Verify(ProtocolSettings settings, DataCache snapshot, ISet extensibleWitnessWhiteList) { uint height = NativeContract.Ledger.CurrentIndex(snapshot); if (height < ValidBlockStart || height >= ValidBlockEnd) return false; - if (!Blockchain.Singleton.IsExtensibleWitnessWhiteListed(Sender)) return false; - return this.VerifyWitnesses(snapshot, 0_02000000); + if (!extensibleWitnessWhiteList.Contains(Sender)) return false; + return this.VerifyWitnesses(settings, snapshot, 0_02000000); } } } diff --git a/src/neo/Network/P2P/Payloads/FilterAddPayload.cs b/src/neo/Network/P2P/Payloads/FilterAddPayload.cs index 3a7a4a9e6f..73bf36bfb0 100644 --- a/src/neo/Network/P2P/Payloads/FilterAddPayload.cs +++ b/src/neo/Network/P2P/Payloads/FilterAddPayload.cs @@ -1,10 +1,17 @@ +using Neo.Cryptography; using Neo.IO; using System.IO; namespace Neo.Network.P2P.Payloads { + /// + /// This message is sent to update the items for the . + /// public class FilterAddPayload : ISerializable { + /// + /// The items to be added. + /// public byte[] Data; public int Size => Data.GetVarSize(); diff --git a/src/neo/Network/P2P/Payloads/FilterLoadPayload.cs b/src/neo/Network/P2P/Payloads/FilterLoadPayload.cs index dfba728fc0..88995e3721 100644 --- a/src/neo/Network/P2P/Payloads/FilterLoadPayload.cs +++ b/src/neo/Network/P2P/Payloads/FilterLoadPayload.cs @@ -5,14 +5,33 @@ namespace Neo.Network.P2P.Payloads { + /// + /// This message is sent to load the . + /// public class FilterLoadPayload : ISerializable { + /// + /// The data of the . + /// public byte[] Filter; + + /// + /// The number of hash functions used by the . + /// public byte K; + + /// + /// Used to generate the seeds of the murmur hash functions. + /// public uint Tweak; public int Size => Filter.GetVarSize() + sizeof(byte) + sizeof(uint); + /// + /// Creates a new instance of the class. + /// + /// The fields in the filter will be copied to the payload. + /// The created payload. public static FilterLoadPayload Create(BloomFilter filter) { byte[] buffer = new byte[filter.M / 8]; diff --git a/src/neo/Network/P2P/Payloads/GetBlockByIndexPayload.cs b/src/neo/Network/P2P/Payloads/GetBlockByIndexPayload.cs index c07a8a8c0a..9ef1482756 100644 --- a/src/neo/Network/P2P/Payloads/GetBlockByIndexPayload.cs +++ b/src/neo/Network/P2P/Payloads/GetBlockByIndexPayload.cs @@ -4,13 +4,29 @@ namespace Neo.Network.P2P.Payloads { + /// + /// This message is sent to request for blocks by index. + /// public class GetBlockByIndexPayload : ISerializable { + /// + /// The starting index of the blocks to request. + /// public uint IndexStart; + + /// + /// The number of blocks to request. + /// public short Count; public int Size => sizeof(uint) + sizeof(short); + /// + /// Creates a new instance of the class. + /// + /// The starting index of the blocks to request. + /// The number of blocks to request. Set this parameter to -1 to request as many blocks as possible. + /// The created payload. public static GetBlockByIndexPayload Create(uint index_start, short count = -1) { return new GetBlockByIndexPayload diff --git a/src/neo/Network/P2P/Payloads/GetBlocksPayload.cs b/src/neo/Network/P2P/Payloads/GetBlocksPayload.cs index eccd66ed19..fa446c2d00 100644 --- a/src/neo/Network/P2P/Payloads/GetBlocksPayload.cs +++ b/src/neo/Network/P2P/Payloads/GetBlocksPayload.cs @@ -4,13 +4,29 @@ namespace Neo.Network.P2P.Payloads { + /// + /// This message is sent to request for blocks by hash. + /// public class GetBlocksPayload : ISerializable { + /// + /// The starting hash of the blocks to request. + /// public UInt256 HashStart; + + /// + /// The number of blocks to request. + /// public short Count; public int Size => sizeof(short) + HashStart.Size; + /// + /// Creates a new instance of the class. + /// + /// The starting hash of the blocks to request. + /// The number of blocks to request. Set this parameter to -1 to request as many blocks as possible. + /// The created payload. public static GetBlocksPayload Create(UInt256 hash_start, short count = -1) { return new GetBlocksPayload diff --git a/src/neo/Network/P2P/Payloads/Header.cs b/src/neo/Network/P2P/Payloads/Header.cs index 301d1cf05b..227587362e 100644 --- a/src/neo/Network/P2P/Payloads/Header.cs +++ b/src/neo/Network/P2P/Payloads/Header.cs @@ -1,16 +1,151 @@ +using Neo.IO; +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.Wallets; using System; using System.IO; namespace Neo.Network.P2P.Payloads { - public class Header : BlockBase, IEquatable
+ /// + /// Represents the header of a block. + /// + public sealed class Header : IEquatable
, IVerifiable { - public override int Size => base.Size + 1; + private uint version; + private UInt256 prevHash; + private UInt256 merkleRoot; + private ulong timestamp; + private uint index; + private byte primaryIndex; + private UInt160 nextConsensus; - public override void Deserialize(BinaryReader reader) + /// + /// The witness of the block. + /// + public Witness Witness; + + /// + /// The version of the block. + /// + public uint Version + { + get => version; + set { version = value; _hash = null; } + } + + /// + /// The hash of the previous block. + /// + public UInt256 PrevHash + { + get => prevHash; + set { prevHash = value; _hash = null; } + } + + /// + /// The merkle root of the transactions. + /// + public UInt256 MerkleRoot + { + get => merkleRoot; + set { merkleRoot = value; _hash = null; } + } + + /// + /// The timestamp of the block. + /// + public ulong Timestamp + { + get => timestamp; + set { timestamp = value; _hash = null; } + } + + /// + /// The index of the block. + /// + public uint Index + { + get => index; + set { index = value; _hash = null; } + } + + /// + /// The primary index of the consensus node that generated this block. + /// + public byte PrimaryIndex + { + get => primaryIndex; + set { primaryIndex = value; _hash = null; } + } + + /// + /// The multi-signature address of the consensus nodes that generates the next block. + /// + public UInt160 NextConsensus + { + get => nextConsensus; + set { nextConsensus = value; _hash = null; } + } + + private UInt256 _hash = null; + public UInt256 Hash { - base.Deserialize(reader); - if (reader.ReadByte() != 0) throw new FormatException(); + get + { + if (_hash == null) + { + _hash = this.CalculateHash(); + } + return _hash; + } + } + + public int Size => + sizeof(uint) + // Version + UInt256.Length + // PrevHash + UInt256.Length + // MerkleRoot + sizeof(ulong) + // Timestamp + sizeof(uint) + // Index + sizeof(byte) + // PrimaryIndex + UInt160.Length + // NextConsensus + 1 + Witness.Size; // Witness + + Witness[] IVerifiable.Witnesses + { + get + { + return new[] { Witness }; + } + set + { + if (value.Length != 1) throw new ArgumentException(null, nameof(value)); + Witness = value[0]; + } + } + + public void Deserialize(BinaryReader reader) + { + ((IVerifiable)this).DeserializeUnsigned(reader); + Witness[] witnesses = reader.ReadSerializableArray(1); + if (witnesses.Length != 1) throw new FormatException(); + Witness = witnesses[0]; + } + + void IVerifiable.DeserializeUnsigned(BinaryReader reader) + { + _hash = null; + version = reader.ReadUInt32(); + if (version > 0) throw new FormatException(); + prevHash = reader.ReadSerializable(); + merkleRoot = reader.ReadSerializable(); + timestamp = reader.ReadUInt64(); + index = reader.ReadUInt32(); + primaryIndex = reader.ReadByte(); + nextConsensus = reader.ReadSerializable(); } public bool Equals(Header other) @@ -30,10 +165,74 @@ public override int GetHashCode() return Hash.GetHashCode(); } - public override void Serialize(BinaryWriter writer) + UInt160[] IVerifiable.GetScriptHashesForVerifying(DataCache snapshot) + { + if (prevHash == UInt256.Zero) return new[] { Witness.ScriptHash }; + TrimmedBlock prev = NativeContract.Ledger.GetTrimmedBlock(snapshot, prevHash); + if (prev is null) throw new InvalidOperationException(); + return new[] { prev.Header.nextConsensus }; + } + + public void Serialize(BinaryWriter writer) + { + ((IVerifiable)this).SerializeUnsigned(writer); + writer.Write(new Witness[] { Witness }); + } + + void IVerifiable.SerializeUnsigned(BinaryWriter writer) + { + writer.Write(version); + writer.Write(prevHash); + writer.Write(merkleRoot); + writer.Write(timestamp); + writer.Write(index); + writer.Write(primaryIndex); + writer.Write(nextConsensus); + } + + /// + /// Converts the header to a JSON object. + /// + /// The used during the conversion. + /// The header represented by a JSON object. + public JObject ToJson(ProtocolSettings settings) + { + JObject json = new(); + json["hash"] = Hash.ToString(); + json["size"] = Size; + json["version"] = version; + json["previousblockhash"] = prevHash.ToString(); + json["merkleroot"] = merkleRoot.ToString(); + json["time"] = timestamp; + json["index"] = index; + json["primary"] = primaryIndex; + json["nextconsensus"] = nextConsensus.ToAddress(settings.AddressVersion); + json["witnesses"] = new JArray(Witness.ToJson()); + return json; + } + + internal bool Verify(ProtocolSettings settings, DataCache snapshot) + { + if (primaryIndex >= settings.ValidatorsCount) + return false; + TrimmedBlock prev = NativeContract.Ledger.GetTrimmedBlock(snapshot, prevHash); + if (prev is null) return false; + if (prev.Index + 1 != index) return false; + if (prev.Header.timestamp >= timestamp) return false; + if (!this.VerifyWitnesses(settings, snapshot, 1_00000000)) return false; + return true; + } + + internal bool Verify(ProtocolSettings settings, DataCache snapshot, HeaderCache headerCache) { - base.Serialize(writer); - writer.Write((byte)0); + Header prev = headerCache.Last; + if (prev is null) return Verify(settings, snapshot); + if (primaryIndex >= settings.ValidatorsCount) + return false; + if (prev.Hash != prevHash) return false; + if (prev.index + 1 != index) return false; + if (prev.timestamp >= timestamp) return false; + return this.VerifyWitness(settings, snapshot, prev.nextConsensus, Witness, 1_00000000, out _); } } } diff --git a/src/neo/Network/P2P/Payloads/HeadersPayload.cs b/src/neo/Network/P2P/Payloads/HeadersPayload.cs index 9251a7f5a1..52858a3a54 100644 --- a/src/neo/Network/P2P/Payloads/HeadersPayload.cs +++ b/src/neo/Network/P2P/Payloads/HeadersPayload.cs @@ -4,14 +4,28 @@ namespace Neo.Network.P2P.Payloads { + /// + /// This message is sent to respond to messages. + /// public class HeadersPayload : ISerializable { + /// + /// Indicates the maximum number of headers sent each time. + /// public const int MaxHeadersCount = 2000; + /// + /// The list of headers. + /// public Header[] Headers; public int Size => Headers.GetVarSize(); + /// + /// Creates a new instance of the class. + /// + /// The list of headers. + /// The created payload. public static HeadersPayload Create(params Header[] headers) { return new HeadersPayload diff --git a/src/neo/Network/P2P/Payloads/HighPriorityAttribute.cs b/src/neo/Network/P2P/Payloads/HighPriorityAttribute.cs index d3077d3e88..8cc87e04ae 100644 --- a/src/neo/Network/P2P/Payloads/HighPriorityAttribute.cs +++ b/src/neo/Network/P2P/Payloads/HighPriorityAttribute.cs @@ -5,6 +5,9 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Indicates that the transaction is of high priority. + /// public class HighPriorityAttribute : TransactionAttribute { public override bool AllowMultiple => false; diff --git a/src/neo/Network/P2P/Payloads/IInventory.cs b/src/neo/Network/P2P/Payloads/IInventory.cs index 49256132c6..5165e28a36 100644 --- a/src/neo/Network/P2P/Payloads/IInventory.cs +++ b/src/neo/Network/P2P/Payloads/IInventory.cs @@ -1,13 +1,13 @@ -using Neo.Persistence; - namespace Neo.Network.P2P.Payloads { + /// + /// Represents a message that can be relayed on the NEO network. + /// public interface IInventory : IVerifiable { - UInt256 Hash { get; } - + /// + /// The type of the inventory. + /// InventoryType InventoryType { get; } - - bool Verify(DataCache snapshot); } } diff --git a/src/neo/Network/P2P/Payloads/IVerifiable.cs b/src/neo/Network/P2P/Payloads/IVerifiable.cs index 3e574366ed..d4d31d69d4 100644 --- a/src/neo/Network/P2P/Payloads/IVerifiable.cs +++ b/src/neo/Network/P2P/Payloads/IVerifiable.cs @@ -4,14 +4,38 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Represents an object that can be verified in the NEO network. + /// public interface IVerifiable : ISerializable { + /// + /// The hash of the object. + /// + UInt256 Hash => this.CalculateHash(); + + /// + /// The witnesses of the object. + /// Witness[] Witnesses { get; set; } + /// + /// Deserializes the part of the object other than . + /// + /// The for reading data. void DeserializeUnsigned(BinaryReader reader); + /// + /// Gets the script hashes that should be verified for this object. + /// + /// The snapshot to be used. + /// The script hashes that should be verified. UInt160[] GetScriptHashesForVerifying(DataCache snapshot); + /// + /// Serializes the part of the object other than . + /// + /// The for writing data. void SerializeUnsigned(BinaryWriter writer); } } diff --git a/src/neo/Network/P2P/Payloads/InvPayload.cs b/src/neo/Network/P2P/Payloads/InvPayload.cs index b54a834ed5..f8fbc3fbf9 100644 --- a/src/neo/Network/P2P/Payloads/InvPayload.cs +++ b/src/neo/Network/P2P/Payloads/InvPayload.cs @@ -5,15 +5,34 @@ namespace Neo.Network.P2P.Payloads { + /// + /// This message is sent to relay inventories. + /// public class InvPayload : ISerializable { + /// + /// Indicates the maximum number of inventories sent each time. + /// public const int MaxHashesCount = 500; + /// + /// The type of the inventories. + /// public InventoryType Type; + + /// + /// The hashes of the inventories. + /// public UInt256[] Hashes; public int Size => sizeof(InventoryType) + Hashes.GetVarSize(); + /// + /// Creates a new instance of the class. + /// + /// The type of the inventories. + /// The hashes of the inventories. + /// The created payload. public static InvPayload Create(InventoryType type, params UInt256[] hashes) { return new InvPayload @@ -23,6 +42,12 @@ public static InvPayload Create(InventoryType type, params UInt256[] hashes) }; } + /// + /// Creates a group of the instance. + /// + /// The type of the inventories. + /// The hashes of the inventories. + /// The created payloads. public static IEnumerable CreateGroup(InventoryType type, UInt256[] hashes) { for (int i = 0; i < hashes.Length; i += MaxHashesCount) diff --git a/src/neo/Network/P2P/Payloads/InventoryType.cs b/src/neo/Network/P2P/Payloads/InventoryType.cs index 3e6bfa423f..27eca53900 100644 --- a/src/neo/Network/P2P/Payloads/InventoryType.cs +++ b/src/neo/Network/P2P/Payloads/InventoryType.cs @@ -1,9 +1,23 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Represents the type of an inventory. + /// public enum InventoryType : byte { + /// + /// Indicates that the inventory is a . + /// TX = MessageCommand.Transaction, + + /// + /// Indicates that the inventory is a . + /// Block = MessageCommand.Block, + + /// + /// Indicates that the inventory is an . + /// Extensible = MessageCommand.Extensible } } diff --git a/src/neo/Network/P2P/Payloads/MerkleBlockPayload.cs b/src/neo/Network/P2P/Payloads/MerkleBlockPayload.cs index 8a735587cd..f906e69039 100644 --- a/src/neo/Network/P2P/Payloads/MerkleBlockPayload.cs +++ b/src/neo/Network/P2P/Payloads/MerkleBlockPayload.cs @@ -1,51 +1,72 @@ using Neo.Cryptography; using Neo.IO; +using System; using System.Collections; using System.IO; using System.Linq; namespace Neo.Network.P2P.Payloads { - public class MerkleBlockPayload : BlockBase + /// + /// Represents a block that is filtered by a . + /// + public class MerkleBlockPayload : ISerializable { - public int ContentCount; + /// + /// The header of the block. + /// + public Header Header; + + /// + /// The number of the transactions in the block. + /// + public int TxCount; + + /// + /// The nodes of the transactions hash tree. + /// public UInt256[] Hashes; + + /// + /// The data in the that filtered the block. + /// public byte[] Flags; - public override int Size => base.Size + sizeof(int) + Hashes.GetVarSize() + Flags.GetVarSize(); + public int Size => Header.Size + sizeof(int) + Hashes.GetVarSize() + Flags.GetVarSize(); + /// + /// Creates a new instance of the class. + /// + /// The original block. + /// The data in the that filtered the block. + /// The created payload. public static MerkleBlockPayload Create(Block block, BitArray flags) { - MerkleTree tree = new MerkleTree(block.Transactions.Select(p => p.Hash).Prepend(block.ConsensusData.Hash).ToArray()); + MerkleTree tree = new(block.Transactions.Select(p => p.Hash).ToArray()); + tree.Trim(flags); byte[] buffer = new byte[(flags.Length + 7) / 8]; flags.CopyTo(buffer, 0); return new MerkleBlockPayload { - Version = block.Version, - PrevHash = block.PrevHash, - MerkleRoot = block.MerkleRoot, - Timestamp = block.Timestamp, - Index = block.Index, - NextConsensus = block.NextConsensus, - Witness = block.Witness, - ContentCount = block.Transactions.Length + 1, + Header = block.Header, + TxCount = block.Transactions.Length, Hashes = tree.ToHashArray(), Flags = buffer }; } - public override void Deserialize(BinaryReader reader) + public void Deserialize(BinaryReader reader) { - base.Deserialize(reader); - ContentCount = (int)reader.ReadVarInt(Block.MaxTransactionsPerBlock + 1); - Hashes = reader.ReadSerializableArray(ContentCount); - Flags = reader.ReadVarBytes((ContentCount + 7) / 8); + Header = reader.ReadSerializable
(); + TxCount = (int)reader.ReadVarInt(ushort.MaxValue); + Hashes = reader.ReadSerializableArray(TxCount); + Flags = reader.ReadVarBytes((Math.Max(TxCount, 1) + 7) / 8); } - public override void Serialize(BinaryWriter writer) + public void Serialize(BinaryWriter writer) { - base.Serialize(writer); - writer.WriteVarInt(ContentCount); + writer.Write(Header); + writer.WriteVarInt(TxCount); writer.Write(Hashes); writer.WriteVarBytes(Flags); } diff --git a/src/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs b/src/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs index a65e28c846..6ac32c1dc8 100644 --- a/src/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs +++ b/src/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs @@ -7,15 +7,40 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Sent with an to respond to messages. + /// public class NetworkAddressWithTime : ISerializable { + /// + /// The time when connected to the node. + /// public uint Timestamp; + + /// + /// The address of the node. + /// public IPAddress Address; + + /// + /// The capabilities of the node. + /// public NodeCapability[] Capabilities; - public IPEndPoint EndPoint => new IPEndPoint(Address, Capabilities.Where(p => p.Type == NodeCapabilityType.TcpServer).Select(p => (ServerCapability)p).FirstOrDefault()?.Port ?? 0); + /// + /// The of the Tcp server. + /// + public IPEndPoint EndPoint => new(Address, Capabilities.Where(p => p.Type == NodeCapabilityType.TcpServer).Select(p => (ServerCapability)p).FirstOrDefault()?.Port ?? 0); + public int Size => sizeof(uint) + 16 + Capabilities.GetVarSize(); + /// + /// Creates a new instance of the class. + /// + /// The address of the node. + /// The time when connected to the node. + /// The capabilities of the node. + /// The created payload. public static NetworkAddressWithTime Create(IPAddress address, uint timestamp, params NodeCapability[] capabilities) { return new NetworkAddressWithTime diff --git a/src/neo/Network/P2P/Payloads/OracleResponse.cs b/src/neo/Network/P2P/Payloads/OracleResponse.cs index 81bf449292..c27e98fec2 100644 --- a/src/neo/Network/P2P/Payloads/OracleResponse.cs +++ b/src/neo/Network/P2P/Payloads/OracleResponse.cs @@ -10,14 +10,34 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Indicates that the transaction is an oracle response. + /// public class OracleResponse : TransactionAttribute { + /// + /// Indicates the maximum size of the field. + /// public const int MaxResultSize = ushort.MaxValue; + /// + /// Represents the fixed value of the field of the oracle responding transaction. + /// public static readonly byte[] FixedScript; + /// + /// The ID of the oracle request. + /// public ulong Id; + + /// + /// The response code for the oracle request. + /// public OracleResponseCode Code; + + /// + /// The result for the oracle request. + /// public byte[] Result; public override TransactionAttributeType Type => TransactionAttributeType.OracleResponse; @@ -30,7 +50,7 @@ public class OracleResponse : TransactionAttribute static OracleResponse() { - using ScriptBuilder sb = new ScriptBuilder(); + using ScriptBuilder sb = new(); sb.EmitDynamicCall(NativeContract.Oracle.Hash, "finish"); FixedScript = sb.ToArray(); } diff --git a/src/neo/Network/P2P/Payloads/OracleResponseCode.cs b/src/neo/Network/P2P/Payloads/OracleResponseCode.cs index e3ad54577a..6806c5697b 100644 --- a/src/neo/Network/P2P/Payloads/OracleResponseCode.cs +++ b/src/neo/Network/P2P/Payloads/OracleResponseCode.cs @@ -1,17 +1,53 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Represents the response code for the oracle request. + /// public enum OracleResponseCode : byte { + /// + /// Indicates that the request has been successfully completed. + /// Success = 0x00, + /// + /// Indicates that the protocol of the request is not supported. + /// ProtocolNotSupported = 0x10, + + /// + /// Indicates that the oracle nodes cannot reach a consensus on the result of the request. + /// ConsensusUnreachable = 0x12, + + /// + /// Indicates that the requested Uri does not exist. + /// NotFound = 0x14, + + /// + /// Indicates that the request was not completed within the specified time. + /// Timeout = 0x16, + + /// + /// Indicates that there is no permission to request the resource. + /// Forbidden = 0x18, + + /// + /// Indicates that the data for the response is too large. + /// ResponseTooLarge = 0x1a, + + /// + /// Indicates that the request failed due to insufficient balance. + /// InsufficientFunds = 0x1c, + /// + /// Indicates that the request failed due to other errors. + /// Error = 0xff } } diff --git a/src/neo/Network/P2P/Payloads/PingPayload.cs b/src/neo/Network/P2P/Payloads/PingPayload.cs index d5005c4e90..e2cd10b29f 100644 --- a/src/neo/Network/P2P/Payloads/PingPayload.cs +++ b/src/neo/Network/P2P/Payloads/PingPayload.cs @@ -4,10 +4,24 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Sent to detect whether the connection has been disconnected. + /// public class PingPayload : ISerializable { + /// + /// The latest block index. + /// public uint LastBlockIndex; + + /// + /// The timestamp when the message was sent. + /// public uint Timestamp; + + /// + /// A random number. This number must be the same in and messages. + /// public uint Nonce; public int Size => @@ -15,13 +29,23 @@ public class PingPayload : ISerializable sizeof(uint) + //Timestamp sizeof(uint); //Nonce - + /// + /// Creates a new instance of the class. + /// + /// The latest block index. + /// The created payload. public static PingPayload Create(uint height) { - Random rand = new Random(); + Random rand = new(); return Create(height, (uint)rand.Next()); } + /// + /// Creates a new instance of the class. + /// + /// The latest block index. + /// The random number. + /// The created payload. public static PingPayload Create(uint height, uint nonce) { return new PingPayload diff --git a/src/neo/Network/P2P/Payloads/Signer.cs b/src/neo/Network/P2P/Payloads/Signer.cs index 4be59a349f..0f3def8712 100644 --- a/src/neo/Network/P2P/Payloads/Signer.cs +++ b/src/neo/Network/P2P/Payloads/Signer.cs @@ -7,14 +7,32 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Represents a signer of a . + /// public class Signer : ISerializable { // This limits maximum number of AllowedContracts or AllowedGroups here private const int MaxSubitems = 16; + /// + /// The account of the signer. + /// public UInt160 Account; + + /// + /// The scopes of the witness. + /// public WitnessScope Scopes; + + /// + /// The contracts that allowed by the witness. Only available when the flag is set. + /// public UInt160[] AllowedContracts; + + /// + /// The groups that allowed by the witness. Only available when the flag is set. + /// public ECPoint[] AllowedGroups; public int Size => @@ -33,10 +51,10 @@ public void Deserialize(BinaryReader reader) throw new FormatException(); AllowedContracts = Scopes.HasFlag(WitnessScope.CustomContracts) ? reader.ReadSerializableArray(MaxSubitems) - : new UInt160[0]; + : Array.Empty(); AllowedGroups = Scopes.HasFlag(WitnessScope.CustomGroups) ? reader.ReadSerializableArray(MaxSubitems) - : new ECPoint[0]; + : Array.Empty(); } public void Serialize(BinaryWriter writer) @@ -49,6 +67,10 @@ public void Serialize(BinaryWriter writer) writer.Write(AllowedGroups); } + /// + /// Converts the signer to a JSON object. + /// + /// The signer represented by a JSON object. public JObject ToJson() { var json = new JObject(); diff --git a/src/neo/Network/P2P/Payloads/Transaction.cs b/src/neo/Network/P2P/Payloads/Transaction.cs index 5ec7297fb2..7608ae6d1e 100644 --- a/src/neo/Network/P2P/Payloads/Transaction.cs +++ b/src/neo/Network/P2P/Payloads/Transaction.cs @@ -12,16 +12,28 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using static Neo.SmartContract.Helper; using Array = Neo.VM.Types.Array; namespace Neo.Network.P2P.Payloads { + /// + /// Represents a transaction. + /// public class Transaction : IEquatable, IInventory, IInteroperable { + /// + /// The maximum size of a transaction. + /// public const int MaxTransactionSize = 102400; + + /// + /// The maximum increment of the field. + /// public const uint MaxValidUntilBlockIncrement = 5760; // 24 hour + /// - /// Maximum number of attributes that can be contained within a transaction + /// The maximum number of attributes that can be contained within a transaction. /// public const int MaxTransactionAttributes = 16; @@ -35,6 +47,9 @@ public class Transaction : IEquatable, IInventory, IInteroperable private byte[] script; private Witness[] witnesses; + /// + /// The size of a transaction header. + /// public const int HeaderSize = sizeof(byte) + //Version sizeof(uint) + //Nonce @@ -43,6 +58,9 @@ public class Transaction : IEquatable, IInventory, IInteroperable sizeof(uint); //ValidUntilBlock private Dictionary _attributesCache; + /// + /// The attributes of the transaction. + /// public TransactionAttribute[] Attributes { get => attributes; @@ -50,8 +68,7 @@ public TransactionAttribute[] Attributes } /// - /// The NetworkFee for the transaction divided by its Size. - /// Note that this property must be used with care. Getting the value of this property multiple times will return the same result. The value of this property can only be obtained after the transaction has been completely built (no longer modified). + /// The for the transaction divided by its . /// public long FeePerByte => NetworkFee / Size; @@ -71,20 +88,26 @@ public UInt256 Hash InventoryType IInventory.InventoryType => InventoryType.TX; /// - /// Distributed to consensus nodes. + /// The network fee of the transaction. /// - public long NetworkFee + public long NetworkFee //Distributed to consensus nodes. { get => netfee; set { netfee = value; _hash = null; } } + /// + /// The nonce of the transaction. + /// public uint Nonce { get => nonce; set { nonce = value; _hash = null; } } + /// + /// The script of the transaction. + /// public byte[] Script { get => script; @@ -92,11 +115,14 @@ public byte[] Script } /// - /// The first signer is the sender of the transaction, regardless of its WitnessScope. - /// The sender will pay the fees of the transaction. + /// The sender is the first signer of the transaction, regardless of its . /// + /// Note: The sender will pay the fees of the transaction. public UInt160 Sender => _signers[0].Account; + /// + /// The signers of the transaction. + /// public Signer[] Signers { get => _signers; @@ -121,20 +147,26 @@ public int Size } /// - /// Fee to be burned. + /// The system fee of the transaction. /// - public long SystemFee + public long SystemFee //Fee to be burned. { get => sysfee; set { sysfee = value; _hash = null; } } + /// + /// Indicates that the transaction is only valid before this block height. + /// public uint ValidUntilBlock { get => validUntilBlock; set { validUntilBlock = value; _hash = null; } } + /// + /// The version of the transaction. + /// public byte Version { get => version; @@ -153,7 +185,8 @@ void ISerializable.Deserialize(BinaryReader reader) if (reader.BaseStream.CanSeek) startPosition = (int)reader.BaseStream.Position; DeserializeUnsigned(reader); - Witnesses = reader.ReadSerializableArray(); + Witnesses = reader.ReadSerializableArray(Signers.Length); + if (Witnesses.Length != Signers.Length) throw new FormatException(); if (startPosition >= 0) _size = (int)reader.BaseStream.Position - startPosition; } @@ -161,7 +194,7 @@ void ISerializable.Deserialize(BinaryReader reader) private static IEnumerable DeserializeAttributes(BinaryReader reader, int maxCount) { int count = (int)reader.ReadVarInt((ulong)maxCount); - HashSet hashset = new HashSet(); + HashSet hashset = new(); while (count-- > 0) { TransactionAttribute attribute = TransactionAttribute.DeserializeFrom(reader); @@ -175,7 +208,7 @@ private static IEnumerable DeserializeSigners(BinaryReader reader, int m { int count = (int)reader.ReadVarInt((ulong)maxCount); if (count == 0) throw new FormatException(); - HashSet hashset = new HashSet(); + HashSet hashset = new(); for (int i = 0; i < count; i++) { Signer signer = reader.ReadSerializable(); @@ -218,11 +251,21 @@ void IInteroperable.FromStackItem(StackItem stackItem) throw new NotSupportedException(); } + /// + /// Gets the attribute of the specified type. + /// + /// The type of the attribute. + /// The first attribute of this type. Or if there is no attribute of this type. public T GetAttribute() where T : TransactionAttribute { return GetAttributes().FirstOrDefault(); } + /// + /// Gets all attributes of the specified type. + /// + /// The type of the attributes. + /// All the attributes of this type. public IEnumerable GetAttributes() where T : TransactionAttribute { _attributesCache ??= attributes.GroupBy(p => p.GetType()).ToDictionary(p => p.Key, p => p.ToArray()); @@ -259,14 +302,19 @@ void IVerifiable.SerializeUnsigned(BinaryWriter writer) writer.WriteVarBytes(Script); } - public JObject ToJson() + /// + /// Converts the transaction to a JSON object. + /// + /// The used during the conversion. + /// The transaction represented by a JSON object. + public JObject ToJson(ProtocolSettings settings) { - JObject json = new JObject(); + JObject json = new(); json["hash"] = Hash.ToString(); json["size"] = Size; json["version"] = Version; json["nonce"] = Nonce; - json["sender"] = Sender.ToAddress(); + json["sender"] = Sender.ToAddress(settings.AddressVersion); json["sysfee"] = SystemFee.ToString(); json["netfee"] = NetworkFee.ToString(); json["validuntilblock"] = ValidUntilBlock; @@ -277,40 +325,54 @@ public JObject ToJson() return json; } - bool IInventory.Verify(DataCache snapshot) + /// + /// Verifies the transaction. + /// + /// The used to verify the transaction. + /// The snapshot used to verify the transaction. + /// The used to verify the transaction. + /// The result of the verification. + public VerifyResult Verify(ProtocolSettings settings, DataCache snapshot, TransactionVerificationContext context) { - return Verify(snapshot, null) == VerifyResult.Succeed; + VerifyResult result = VerifyStateIndependent(settings); + if (result != VerifyResult.Succeed) return result; + return VerifyStateDependent(settings, snapshot, context); } - public virtual VerifyResult VerifyStateDependent(DataCache snapshot, TransactionVerificationContext context) + /// + /// Verifies the state-dependent part of the transaction. + /// + /// The used to verify the transaction. + /// The snapshot used to verify the transaction. + /// The used to verify the transaction. + /// The result of the verification. + public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, DataCache snapshot, TransactionVerificationContext context) { uint height = NativeContract.Ledger.CurrentIndex(snapshot); if (ValidUntilBlock <= height || ValidUntilBlock > height + MaxValidUntilBlockIncrement) return VerifyResult.Expired; - foreach (UInt160 hash in GetScriptHashesForVerifying(snapshot)) + UInt160[] hashes = GetScriptHashesForVerifying(snapshot); + foreach (UInt160 hash in hashes) if (NativeContract.Policy.IsBlocked(snapshot, hash)) return VerifyResult.PolicyFail; - if (NativeContract.Policy.GetMaxBlockSystemFee(snapshot) < SystemFee) - return VerifyResult.PolicyFail; if (!(context?.CheckTransaction(this, snapshot) ?? true)) return VerifyResult.InsufficientFunds; foreach (TransactionAttribute attribute in Attributes) if (!attribute.Verify(snapshot, this)) return VerifyResult.Invalid; long net_fee = NetworkFee - Size * NativeContract.Policy.GetFeePerByte(snapshot); + if (net_fee < 0) return VerifyResult.InsufficientFunds; - UInt160[] hashes = GetScriptHashesForVerifying(snapshot); - if (hashes.Length != witnesses.Length) return VerifyResult.Invalid; - + if (net_fee > MaxVerificationGas) net_fee = MaxVerificationGas; uint execFeeFactor = NativeContract.Policy.GetExecFeeFactor(snapshot); for (int i = 0; i < hashes.Length; i++) { if (witnesses[i].VerificationScript.IsSignatureContract()) - net_fee -= execFeeFactor * SmartContract.Helper.SignatureContractCost(); + net_fee -= execFeeFactor * SignatureContractCost(); else if (witnesses[i].VerificationScript.IsMultiSigContract(out int m, out int n)) - net_fee -= execFeeFactor * SmartContract.Helper.MultiSignatureContractCost(m, n); + net_fee -= execFeeFactor * MultiSignatureContractCost(m, n); else { - if (!this.VerifyWitness(snapshot, hashes[i], witnesses[i], net_fee, out long fee)) + if (!this.VerifyWitness(settings, snapshot, hashes[i], witnesses[i], net_fee, out long fee)) return VerifyResult.Invalid; net_fee -= fee; } @@ -319,7 +381,12 @@ public virtual VerifyResult VerifyStateDependent(DataCache snapshot, Transaction return VerifyResult.Succeed; } - public virtual VerifyResult VerifyStateIndependent() + /// + /// Verifies the state-independent part of the transaction. + /// + /// The used to verify the transaction. + /// The result of the verification. + public virtual VerifyResult VerifyStateIndependent(ProtocolSettings settings) { if (Size > MaxTransactionSize) return VerifyResult.Invalid; try @@ -330,23 +397,20 @@ public virtual VerifyResult VerifyStateIndependent() { return VerifyResult.Invalid; } + long net_fee = Math.Min(NetworkFee, MaxVerificationGas); UInt160[] hashes = GetScriptHashesForVerifying(null); - if (hashes.Length != witnesses.Length) return VerifyResult.Invalid; for (int i = 0; i < hashes.Length; i++) if (witnesses[i].VerificationScript.IsStandardContract()) - if (!this.VerifyWitness(null, hashes[i], witnesses[i], SmartContract.Helper.MaxVerificationGas, out _)) + if (!this.VerifyWitness(settings, null, hashes[i], witnesses[i], net_fee, out long fee)) return VerifyResult.Invalid; + else + { + net_fee -= fee; + if (net_fee < 0) return VerifyResult.InsufficientFunds; + } return VerifyResult.Succeed; } - public virtual VerifyResult Verify(DataCache snapshot, TransactionVerificationContext context) - { - VerifyResult result = VerifyStateIndependent(); - if (result != VerifyResult.Succeed) return result; - result = VerifyStateDependent(snapshot, context); - return result; - } - public StackItem ToStackItem(ReferenceCounter referenceCounter) { return new Array(referenceCounter, new StackItem[] diff --git a/src/neo/Network/P2P/Payloads/TransactionAttribute.cs b/src/neo/Network/P2P/Payloads/TransactionAttribute.cs index acd3238119..cfb3a921d5 100644 --- a/src/neo/Network/P2P/Payloads/TransactionAttribute.cs +++ b/src/neo/Network/P2P/Payloads/TransactionAttribute.cs @@ -7,10 +7,21 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Represents an attribute of a transaction. + /// public abstract class TransactionAttribute : ISerializable { + /// + /// The type of the attribute. + /// public abstract TransactionAttributeType Type { get; } + + /// + /// Indicates whether multiple instances of this attribute are allowed. + /// public abstract bool AllowMultiple { get; } + public virtual int Size => sizeof(TransactionAttributeType); public void Deserialize(BinaryReader reader) @@ -20,17 +31,30 @@ public void Deserialize(BinaryReader reader) DeserializeWithoutType(reader); } + /// + /// Deserializes an object from a . + /// + /// The for reading data. + /// The deserialized attribute. public static TransactionAttribute DeserializeFrom(BinaryReader reader) { TransactionAttributeType type = (TransactionAttributeType)reader.ReadByte(); - if (!(ReflectionCache.CreateInstance(type) is TransactionAttribute attribute)) + if (ReflectionCache.CreateInstance(type) is not TransactionAttribute attribute) throw new FormatException(); attribute.DeserializeWithoutType(reader); return attribute; } + /// + /// Deserializes the object from a . + /// + /// The for reading data. protected abstract void DeserializeWithoutType(BinaryReader reader); + /// + /// Converts the attribute to a JSON object. + /// + /// The attribute represented by a JSON object. public virtual JObject ToJson() { return new JObject @@ -45,8 +69,18 @@ public void Serialize(BinaryWriter writer) SerializeWithoutType(writer); } + /// + /// Serializes the object to a . + /// + /// The for writing data. protected abstract void SerializeWithoutType(BinaryWriter writer); + /// + /// Verifies the attribute with the transaction. + /// + /// The snapshot used to verify the attribute. + /// The that contains the attribute. + /// if the verification passes; otherwise, . public virtual bool Verify(DataCache snapshot, Transaction tx) => true; } } diff --git a/src/neo/Network/P2P/Payloads/TransactionAttributeType.cs b/src/neo/Network/P2P/Payloads/TransactionAttributeType.cs index 26679ca92b..f7b787b7ff 100644 --- a/src/neo/Network/P2P/Payloads/TransactionAttributeType.cs +++ b/src/neo/Network/P2P/Payloads/TransactionAttributeType.cs @@ -2,10 +2,20 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Represents the type of a . + /// public enum TransactionAttributeType : byte { + /// + /// Indicates that the transaction is of high priority. + /// [ReflectionCache(typeof(HighPriorityAttribute))] HighPriority = 0x01, + + /// + /// Indicates that the transaction is an oracle response. + /// [ReflectionCache(typeof(OracleResponse))] OracleResponse = 0x11 } diff --git a/src/neo/Network/P2P/Payloads/VersionPayload.cs b/src/neo/Network/P2P/Payloads/VersionPayload.cs index 16c401b7db..c8ca128da5 100644 --- a/src/neo/Network/P2P/Payloads/VersionPayload.cs +++ b/src/neo/Network/P2P/Payloads/VersionPayload.cs @@ -6,15 +6,44 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Sent when a connection is established. + /// public class VersionPayload : ISerializable { + /// + /// Indicates the maximum number of capabilities contained in a . + /// public const int MaxCapabilities = 32; + /// + /// The magic number of the network. + /// public uint Magic; + + /// + /// The protocol version of the node. + /// public uint Version; + + /// + /// The time when connected to the node. + /// public uint Timestamp; + + /// + /// A random number used to identify the node. + /// public uint Nonce; + + /// + /// A used to identify the client software of the node. + /// public string UserAgent; + + /// + /// The capabilities of the node. + /// public NodeCapability[] Capabilities; public int Size => @@ -25,11 +54,19 @@ public class VersionPayload : ISerializable UserAgent.GetVarSize() + // UserAgent Capabilities.GetVarSize(); // Capabilities - public static VersionPayload Create(uint nonce, string userAgent, params NodeCapability[] capabilities) + /// + /// Creates a new instance of the class. + /// + /// The magic number of the network. + /// The random number used to identify the node. + /// The used to identify the client software of the node. + /// The capabilities of the node. + /// + public static VersionPayload Create(uint magic, uint nonce, string userAgent, params NodeCapability[] capabilities) { return new VersionPayload { - Magic = ProtocolSettings.Default.Magic, + Magic = magic, Version = LocalNode.ProtocolVersion, Timestamp = DateTime.Now.ToTimestamp(), Nonce = nonce, diff --git a/src/neo/Network/P2P/Payloads/Witness.cs b/src/neo/Network/P2P/Payloads/Witness.cs index d6cfcf7470..f5dc6f3163 100644 --- a/src/neo/Network/P2P/Payloads/Witness.cs +++ b/src/neo/Network/P2P/Payloads/Witness.cs @@ -6,24 +6,33 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Represents a witness of an object. + /// public class Witness : ISerializable { - /// - /// This is designed to allow a MultiSig 21/11 (committee) - /// Invocation = 11 * (64 + 2) = 726 - /// + // This is designed to allow a MultiSig 21/11 (committee) + // Invocation = 11 * (64 + 2) = 726 private const int MaxInvocationScript = 1024; - /// - /// Verification = m + (PUSH_PubKey * 21) + length + null + syscall = 1 + ((2 + 33) * 21) + 2 + 1 + 5 = 744 - /// + // Verification = m + (PUSH_PubKey * 21) + length + null + syscall = 1 + ((2 + 33) * 21) + 2 + 1 + 5 = 744 private const int MaxVerificationScript = 1024; + /// + /// The invocation script of the witness. Used to pass arguments for . + /// public byte[] InvocationScript; + + /// + /// The verification script of the witness. It can be empty if the contract is deployed. + /// public byte[] VerificationScript; private UInt160 _scriptHash; - public virtual UInt160 ScriptHash + /// + /// The hash of the . + /// + public UInt160 ScriptHash { get { @@ -49,9 +58,13 @@ void ISerializable.Serialize(BinaryWriter writer) writer.WriteVarBytes(VerificationScript); } + /// + /// Converts the witness to a JSON object. + /// + /// The witness represented by a JSON object. public JObject ToJson() { - JObject json = new JObject(); + JObject json = new(); json["invocation"] = Convert.ToBase64String(InvocationScript); json["verification"] = Convert.ToBase64String(VerificationScript); return json; diff --git a/src/neo/Network/P2P/Payloads/WitnessScope.cs b/src/neo/Network/P2P/Payloads/WitnessScope.cs index 7ef499ad41..394014a8e4 100644 --- a/src/neo/Network/P2P/Payloads/WitnessScope.cs +++ b/src/neo/Network/P2P/Payloads/WitnessScope.cs @@ -2,35 +2,38 @@ namespace Neo.Network.P2P.Payloads { + /// + /// Represents the scope of a . + /// [Flags] public enum WitnessScope : byte { /// - /// No contract was witnessed. Only sign the transaction. + /// Indicates that no contract was witnessed. Only sign the transaction. /// None = 0, /// - /// CalledByEntry means that this condition must hold: EntryScriptHash == CallingScriptHash - /// No params is needed, as the witness/permission/signature given on first invocation will automatically expire if entering deeper internal invokes - /// This can be default safe choice for native NEO/GAS (previously used on Neo 2 as "attach" mode) + /// Indicates that the calling contract must be the entry contract. + /// The witness/permission/signature given on first invocation will automatically expire if entering deeper internal invokes. + /// This can be the default safe choice for native NEO/GAS (previously used on Neo 2 as "attach" mode). /// CalledByEntry = 0x01, /// - /// Custom hash for contract-specific + /// Custom hash for contract-specific. /// CustomContracts = 0x10, /// - /// Custom pubkey for group members + /// Custom pubkey for group members. /// CustomGroups = 0x20, /// - /// Global allows this witness in all contexts (default Neo2 behavior) - /// This cannot be combined with other flags + /// This allows the witness in all contexts (default Neo2 behavior). /// + /// Note: It cannot be combined with other flags. Global = 0x80 } } diff --git a/src/neo/Network/P2P/Peer.cs b/src/neo/Network/P2P/Peer.cs index 106f530b71..a7b491892a 100644 --- a/src/neo/Network/P2P/Peer.cs +++ b/src/neo/Network/P2P/Peer.cs @@ -18,46 +18,114 @@ namespace Neo.Network.P2P { + /// + /// Actor used to manage the connections of the local node. + /// public abstract class Peer : UntypedActor { - public class Peers { public IEnumerable EndPoints; } - public class Connect { public IPEndPoint EndPoint; public bool IsTrusted = false; } + /// + /// Sent to to add more unconnected peers. + /// + public class Peers + { + /// + /// The unconnected peers to be added. + /// + public IEnumerable EndPoints { get; init; } + } + + /// + /// Sent to to connect to a remote node. + /// + public class Connect + { + /// + /// The address of the remote node. + /// + public IPEndPoint EndPoint { get; init; } + + /// + /// Indicates whether the remote node is trusted. A trusted node will always be connected. + /// + public bool IsTrusted { get; init; } + } + private class Timer { } private class WsConnected { public WebSocket Socket; public IPEndPoint Remote; public IPEndPoint Local; } + /// + /// The default minimum number of desired connections. + /// public const int DefaultMinDesiredConnections = 10; + + /// + /// The default maximum number of desired connections. + /// public const int DefaultMaxConnections = DefaultMinDesiredConnections * 4; private static readonly IActorRef tcp_manager = Context.System.Tcp(); private IActorRef tcp_listener; private IWebHost ws_host; private ICancelable timer; - protected ActorSelection Connections => Context.ActorSelection("connection_*"); - private static readonly HashSet localAddresses = new HashSet(); - private readonly Dictionary ConnectedAddresses = new Dictionary(); + private static readonly HashSet localAddresses = new(); + private readonly Dictionary ConnectedAddresses = new(); + /// /// A dictionary that stores the connected nodes. /// - protected readonly ConcurrentDictionary ConnectedPeers = new ConcurrentDictionary(); + protected readonly ConcurrentDictionary ConnectedPeers = new(); + /// - /// An ImmutableHashSet that stores the Peers received: 1) from other nodes or 2) from default file. + /// A set that stores the peers received from other nodes. /// If the number of desired connections is not enough, first try to connect with the peers from this set. /// protected ImmutableHashSet UnconnectedPeers = ImmutableHashSet.Empty; + /// - /// When a TCP connection request is sent to a peer, the peer will be added to the ImmutableHashSet. + /// When a TCP connection request is sent to a peer, the peer will be added to the set. /// If a Tcp.Connected or a Tcp.CommandFailed (with TCP.Command of type Tcp.Connect) is received, the related peer will be removed. /// protected ImmutableHashSet ConnectingPeers = ImmutableHashSet.Empty; - protected HashSet TrustedIpAddresses { get; } = new HashSet(); + /// + /// A hash set to store the trusted nodes. A trusted node will always be connected. + /// + protected HashSet TrustedIpAddresses { get; } = new(); + + /// + /// The port listened by the local Tcp server. + /// public int ListenerTcpPort { get; private set; } + + /// + /// The port listened by the local WebSocket server. + /// public int ListenerWsPort { get; private set; } + + /// + /// Indicates the maximum number of connections with the same address. + /// public int MaxConnectionsPerAddress { get; private set; } = 3; + + /// + /// Indicates the minimum number of desired connections. + /// public int MinDesiredConnections { get; private set; } = DefaultMinDesiredConnections; + + /// + /// Indicates the maximum number of connections. + /// public int MaxConnections { get; private set; } = DefaultMaxConnections; + + /// + /// Indicates the maximum number of unconnected peers stored in . + /// protected int UnconnectedMax { get; } = 1000; + + /// + /// Indicates the maximum number of pending connections. + /// protected virtual int ConnectingMax { get @@ -89,6 +157,11 @@ protected void AddPeers(IEnumerable peers) } } + /// + /// Tries to connect the a remote peer. + /// + /// The address of the remote peer. + /// Indicates whether the remote node is trusted. A trusted node will always be connected. protected void ConnectToPeer(IPEndPoint endPoint, bool isTrusted = false) { endPoint = endPoint.Unmap(); @@ -116,7 +189,7 @@ private static bool IsIntranetAddress(IPAddress address) } /// - /// Abstract method for asking for more peers. Currently triggered when UnconnectedPeers is empty. + /// Called for asking for more peers. /// /// Number of peers that are being requested. protected abstract void NeedMorePeers(int count); @@ -230,6 +303,10 @@ private void OnTcpConnected(IPEndPoint remote, IPEndPoint local) } } + /// + /// Called when a Tcp connection is established. + /// + /// The connection actor. protected virtual void OnTcpConnected(IActorRef connection) { } @@ -271,7 +348,7 @@ private void OnTimer() if (UnconnectedPeers.Count == 0) NeedMorePeers(MinDesiredConnections - ConnectedPeers.Count); - Random rand = new Random(); + Random rand = new(); IPEndPoint[] endpoints = UnconnectedPeers.OrderBy(u => rand.Next()).Take(MinDesiredConnections - ConnectedPeers.Count).ToArray(); ImmutableInterlocked.Update(ref UnconnectedPeers, p => p.Except(endpoints)); foreach (IPEndPoint endpoint in endpoints) @@ -314,6 +391,13 @@ private async Task ProcessWebSocketAsync(HttpContext context) }); } + /// + /// Gets a object used for creating the protocol actor. + /// + /// The underlying connection object. + /// The address of the remote node. + /// The address of the local node. + /// The object used for creating the protocol actor. protected abstract Props ProtocolProps(object connection, IPEndPoint remote, IPEndPoint local); } } diff --git a/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs b/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs index 9c3749d415..ed85d130a2 100644 --- a/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs +++ b/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs @@ -27,9 +27,9 @@ protected override UInt256 GetKeyForItem((UInt256, DateTime) item) } } - private readonly PendingKnownHashesCollection pendingKnownHashes = new PendingKnownHashesCollection(); - private readonly HashSetCache knownHashes = new HashSetCache(Blockchain.Singleton.MemPool.Capacity * 2 / 5); - private readonly HashSetCache sentHashes = new HashSetCache(Blockchain.Singleton.MemPool.Capacity * 2 / 5); + private readonly PendingKnownHashesCollection pendingKnownHashes = new(); + private readonly HashSetCache knownHashes; + private readonly HashSetCache sentHashes; private bool verack = false; private BloomFilter bloom_filter; @@ -39,7 +39,7 @@ protected override UInt256 GetKeyForItem((UInt256, DateTime) item) private void OnMessage(Message msg) { foreach (IP2PPlugin plugin in Plugin.P2PPlugins) - if (!plugin.OnP2PMessage(msg)) + if (!plugin.OnP2PMessage(system, msg)) return; if (Version == null) { @@ -151,8 +151,8 @@ private void OnFilterLoadMessageReceived(FilterLoadPayload payload) /// private void OnGetAddrMessageReceived() { - Random rand = new Random(); - IEnumerable peers = LocalNode.Singleton.RemoteNodes.Values + Random rand = new(); + IEnumerable peers = localNode.RemoteNodes.Values .Where(p => p.ListenerTcpPort > 0) .GroupBy(p => p.Remote.Address, (k, g) => g.First()) .OrderBy(p => rand.Next()) @@ -172,12 +172,12 @@ private void OnGetBlocksMessageReceived(GetBlocksPayload payload) { // The default value of payload.Count is -1 int count = payload.Count < 0 || payload.Count > InvPayload.MaxHashesCount ? InvPayload.MaxHashesCount : payload.Count; - DataCache snapshot = Blockchain.Singleton.View; + DataCache snapshot = system.StoreView; UInt256 hash = payload.HashStart; TrimmedBlock state = NativeContract.Ledger.GetTrimmedBlock(snapshot, hash); if (state == null) return; uint currentHeight = NativeContract.Ledger.CurrentIndex(snapshot); - List hashes = new List(); + List hashes = new(); for (uint i = 1; i <= count; i++) { uint index = state.Index + i; @@ -196,7 +196,7 @@ private void OnGetBlockByIndexMessageReceived(GetBlockByIndexPayload payload) uint count = payload.Count == -1 ? InvPayload.MaxHashesCount : Math.Min((uint)payload.Count, InvPayload.MaxHashesCount); for (uint i = payload.IndexStart, max = payload.IndexStart + count; i < max; i++) { - Block block = NativeContract.Ledger.GetBlock(Blockchain.Singleton.View, i); + Block block = NativeContract.Ledger.GetBlock(system.StoreView, i); if (block == null) break; @@ -206,7 +206,7 @@ private void OnGetBlockByIndexMessageReceived(GetBlockByIndexPayload payload) } else { - BitArray flags = new BitArray(block.Transactions.Select(p => bloom_filter.Test(p)).ToArray()); + BitArray flags = new(block.Transactions.Select(p => bloom_filter.Test(p)).ToArray()); EnqueueMessage(Message.Create(MessageCommand.MerkleBlock, MerkleBlockPayload.Create(block, flags))); } } @@ -226,13 +226,13 @@ private void OnGetDataMessageReceived(InvPayload payload) switch (payload.Type) { case InventoryType.TX: - if (Blockchain.Singleton.MemPool.TryGetValue(hash, out Transaction tx)) + if (system.MemPool.TryGetValue(hash, out Transaction tx)) EnqueueMessage(Message.Create(MessageCommand.Transaction, tx)); else notFound.Add(hash); break; case InventoryType.Block: - Block block = NativeContract.Ledger.GetBlock(Blockchain.Singleton.View, hash); + Block block = NativeContract.Ledger.GetBlock(system.StoreView, hash); if (block != null) { if (bloom_filter == null) @@ -241,7 +241,7 @@ private void OnGetDataMessageReceived(InvPayload payload) } else { - BitArray flags = new BitArray(block.Transactions.Select(p => bloom_filter.Test(p)).ToArray()); + BitArray flags = new(block.Transactions.Select(p => bloom_filter.Test(p)).ToArray()); EnqueueMessage(Message.Create(MessageCommand.MerkleBlock, MerkleBlockPayload.Create(block, flags))); } } @@ -251,7 +251,7 @@ private void OnGetDataMessageReceived(InvPayload payload) } break; default: - if (Blockchain.Singleton.RelayCache.TryGet(hash, out IInventory inventory)) + if (system.RelayCache.TryGet(hash, out IInventory inventory)) EnqueueMessage(Message.Create((MessageCommand)payload.Type, inventory)); break; } @@ -272,9 +272,9 @@ private void OnGetDataMessageReceived(InvPayload payload) /// A GetBlockByIndexPayload including start block index and number of blocks' headers requested. private void OnGetHeadersMessageReceived(GetBlockByIndexPayload payload) { - DataCache snapshot = Blockchain.Singleton.View; + DataCache snapshot = system.StoreView; if (payload.IndexStart > NativeContract.Ledger.CurrentIndex(snapshot)) return; - List
headers = new List
(); + List
headers = new(); uint count = payload.Count == -1 ? HeadersPayload.MaxHeadersCount : (uint)payload.Count; for (uint i = 0; i < count; i++) { @@ -295,15 +295,24 @@ private void OnHeadersMessageReceived(HeadersPayload payload) private void OnInventoryReceived(IInventory inventory) { + knownHashes.Add(inventory.Hash); pendingKnownHashes.Remove(inventory.Hash); - if (inventory is Block block) + switch (inventory) { - UpdateLastBlockIndex(block.Index); - if (block.Index > NativeContract.Ledger.CurrentIndex(Blockchain.Singleton.View) + InvPayload.MaxHashesCount) return; + case Transaction transaction: + if (!system.ContainsTransaction(transaction.Hash)) + system.TxRouter.Tell(new TransactionRouter.Preverify(transaction, true)); + break; + case Block block: + UpdateLastBlockIndex(block.Index); + if (block.Index > NativeContract.Ledger.CurrentIndex(system.StoreView) + InvPayload.MaxHashesCount) return; + system.Blockchain.Tell(inventory); + break; + default: + system.Blockchain.Tell(inventory); + break; } - knownHashes.Add(inventory.Hash); system.TaskManager.Tell(inventory); - system.Blockchain.Tell(inventory); } private void OnInvMessageReceived(InvPayload payload) @@ -314,13 +323,13 @@ private void OnInvMessageReceived(InvPayload payload) { case InventoryType.Block: { - DataCache snapshot = Blockchain.Singleton.View; + DataCache snapshot = system.StoreView; hashes = hashes.Where(p => !NativeContract.Ledger.ContainsBlock(snapshot, p)).ToArray(); } break; case InventoryType.TX: { - DataCache snapshot = Blockchain.Singleton.View; + DataCache snapshot = system.StoreView; hashes = hashes.Where(p => !NativeContract.Ledger.ContainsTransaction(snapshot, p)).ToArray(); } break; @@ -333,14 +342,14 @@ private void OnInvMessageReceived(InvPayload payload) private void OnMemPoolMessageReceived() { - foreach (InvPayload payload in InvPayload.CreateGroup(InventoryType.TX, Blockchain.Singleton.MemPool.GetVerifiedTransactions().Select(p => p.Hash).ToArray())) + foreach (InvPayload payload in InvPayload.CreateGroup(InventoryType.TX, system.MemPool.GetVerifiedTransactions().Select(p => p.Hash).ToArray())) EnqueueMessage(Message.Create(MessageCommand.Inv, payload)); } private void OnPingMessageReceived(PingPayload payload) { UpdateLastBlockIndex(payload.LastBlockIndex); - EnqueueMessage(Message.Create(MessageCommand.Pong, PingPayload.Create(NativeContract.Ledger.CurrentIndex(Blockchain.Singleton.View), payload.Nonce))); + EnqueueMessage(Message.Create(MessageCommand.Pong, PingPayload.Create(NativeContract.Ledger.CurrentIndex(system.StoreView), payload.Nonce))); } private void OnPongMessageReceived(PingPayload payload) @@ -372,7 +381,7 @@ private void OnVersionMessageReceived(VersionPayload payload) break; } } - if (!LocalNode.Singleton.AllowNewConnection(Self, this)) + if (!localNode.AllowNewConnection(Self, this)) { Disconnect(true); return; @@ -390,7 +399,7 @@ private void OnTimer() pendingKnownHashes.RemoveAt(0); } if (oneMinuteAgo > lastSent) - EnqueueMessage(Message.Create(MessageCommand.Ping, PingPayload.Create(NativeContract.Ledger.CurrentIndex(Blockchain.Singleton.View)))); + EnqueueMessage(Message.Create(MessageCommand.Ping, PingPayload.Create(NativeContract.Ledger.CurrentIndex(system.StoreView)))); } private void UpdateLastBlockIndex(uint lastBlockIndex) diff --git a/src/neo/Network/P2P/RemoteNode.cs b/src/neo/Network/P2P/RemoteNode.cs index dee2b893f3..04f9a25ef6 100644 --- a/src/neo/Network/P2P/RemoteNode.cs +++ b/src/neo/Network/P2P/RemoteNode.cs @@ -4,7 +4,7 @@ using Neo.Cryptography; using Neo.IO; using Neo.IO.Actors; -using Neo.Ledger; +using Neo.IO.Caching; using Neo.Network.P2P.Capabilities; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; @@ -16,31 +16,65 @@ namespace Neo.Network.P2P { + /// + /// Represents a connection of the NEO network. + /// public partial class RemoteNode : Connection { internal class StartProtocol { } internal class Relay { public IInventory Inventory; } private readonly NeoSystem system; - private readonly Queue message_queue_high = new Queue(); - private readonly Queue message_queue_low = new Queue(); + private readonly LocalNode localNode; + private readonly Queue message_queue_high = new(); + private readonly Queue message_queue_low = new(); private DateTime lastSent = TimeProvider.Current.UtcNow; private readonly bool[] sentCommands = new bool[1 << (sizeof(MessageCommand) * 8)]; private ByteString msg_buffer = ByteString.Empty; private bool ack = true; + private uint lastHeightSent = 0; - public IPEndPoint Listener => new IPEndPoint(Remote.Address, ListenerTcpPort); + /// + /// The address of the remote Tcp server. + /// + public IPEndPoint Listener => new(Remote.Address, ListenerTcpPort); + + /// + /// The port listened by the remote Tcp server. If the remote node is not a server, this field is 0. + /// public int ListenerTcpPort { get; private set; } = 0; + + /// + /// The sent by the remote node. + /// public VersionPayload Version { get; private set; } + + /// + /// The index of the last block sent by the remote node. + /// public uint LastBlockIndex { get; private set; } = 0; - public uint LastHeightSent { get; private set; } = 0; + + /// + /// Indicates whether the remote node is a full node. + /// public bool IsFullNode { get; private set; } = false; - public RemoteNode(NeoSystem system, object connection, IPEndPoint remote, IPEndPoint local) + /// + /// Initializes a new instance of the class. + /// + /// The object that contains the . + /// The that manages the . + /// The underlying connection object. + /// The address of the remote node. + /// The address of the local node. + public RemoteNode(NeoSystem system, LocalNode localNode, object connection, IPEndPoint remote, IPEndPoint local) : base(connection, remote, local) { this.system = system; - LocalNode.Singleton.RemoteNodes.TryAdd(Self, this); + this.localNode = localNode; + this.knownHashes = new HashSetCache(system.MemPool.Capacity * 2 / 5); + this.sentHashes = new HashSetCache(system.MemPool.Capacity * 2 / 5); + localNode.RemoteNodes.TryAdd(Self, this); } /// @@ -72,35 +106,16 @@ private void EnqueueMessage(MessageCommand command, ISerializable payload = null /// The message to be added. private void EnqueueMessage(Message message) { - bool is_single = false; - switch (message.Command) + bool is_single = message.Command switch { - case MessageCommand.Addr: - case MessageCommand.GetAddr: - case MessageCommand.GetBlocks: - case MessageCommand.GetHeaders: - case MessageCommand.Mempool: - case MessageCommand.Ping: - case MessageCommand.Pong: - is_single = true; - break; - } - Queue message_queue; - switch (message.Command) + MessageCommand.Addr or MessageCommand.GetAddr or MessageCommand.GetBlocks or MessageCommand.GetHeaders or MessageCommand.Mempool or MessageCommand.Ping or MessageCommand.Pong => true, + _ => false, + }; + Queue message_queue = message.Command switch { - case MessageCommand.Alert: - case MessageCommand.Extensible: - case MessageCommand.FilterAdd: - case MessageCommand.FilterClear: - case MessageCommand.FilterLoad: - case MessageCommand.GetAddr: - case MessageCommand.Mempool: - message_queue = message_queue_high; - break; - default: - message_queue = message_queue_low; - break; - } + MessageCommand.Alert or MessageCommand.Extensible or MessageCommand.FilterAdd or MessageCommand.FilterClear or MessageCommand.FilterLoad or MessageCommand.GetAddr or MessageCommand.Mempool => message_queue_high, + _ => message_queue_low, + }; if (!is_single || message_queue.All(p => p.Command != message.Command)) { message_queue.Enqueue(message); @@ -134,8 +149,8 @@ protected override void OnReceive(object message) case Message msg: if (msg.Payload is PingPayload payload) { - if (payload.LastBlockIndex > LastHeightSent) - LastHeightSent = payload.LastBlockIndex; + if (payload.LastBlockIndex > lastHeightSent) + lastHeightSent = payload.LastBlockIndex; else if (msg.Command == MessageCommand.Ping) break; } @@ -179,25 +194,25 @@ private void OnStartProtocol() { var capabilities = new List { - new FullNodeCapability(NativeContract.Ledger.CurrentIndex(Blockchain.Singleton.View)) + new FullNodeCapability(NativeContract.Ledger.CurrentIndex(system.StoreView)) }; - if (LocalNode.Singleton.ListenerTcpPort > 0) capabilities.Add(new ServerCapability(NodeCapabilityType.TcpServer, (ushort)LocalNode.Singleton.ListenerTcpPort)); - if (LocalNode.Singleton.ListenerWsPort > 0) capabilities.Add(new ServerCapability(NodeCapabilityType.WsServer, (ushort)LocalNode.Singleton.ListenerWsPort)); + if (localNode.ListenerTcpPort > 0) capabilities.Add(new ServerCapability(NodeCapabilityType.TcpServer, (ushort)localNode.ListenerTcpPort)); + if (localNode.ListenerWsPort > 0) capabilities.Add(new ServerCapability(NodeCapabilityType.WsServer, (ushort)localNode.ListenerWsPort)); - SendMessage(Message.Create(MessageCommand.Version, VersionPayload.Create(LocalNode.Nonce, LocalNode.UserAgent, capabilities.ToArray()))); + SendMessage(Message.Create(MessageCommand.Version, VersionPayload.Create(system.Settings.Magic, LocalNode.Nonce, LocalNode.UserAgent, capabilities.ToArray()))); } protected override void PostStop() { timer.CancelIfNotNull(); - LocalNode.Singleton.RemoteNodes.TryRemove(Self, out _); + localNode.RemoteNodes.TryRemove(Self, out _); base.PostStop(); } - internal static Props Props(NeoSystem system, object connection, IPEndPoint remote, IPEndPoint local) + internal static Props Props(NeoSystem system, LocalNode localNode, object connection, IPEndPoint remote, IPEndPoint local) { - return Akka.Actor.Props.Create(() => new RemoteNode(system, connection, remote, local)).WithMailbox("remote-node-mailbox"); + return Akka.Actor.Props.Create(() => new RemoteNode(system, localNode, connection, remote, local)).WithMailbox("remote-node-mailbox"); } private void SendMessage(Message message) @@ -223,44 +238,26 @@ public RemoteNodeMailbox(Settings settings, Config config) : base(settings, conf internal protected override bool IsHighPriority(object message) { - switch (message) + return message switch { - case Message msg: - switch (msg.Command) - { - case MessageCommand.Extensible: - case MessageCommand.FilterAdd: - case MessageCommand.FilterClear: - case MessageCommand.FilterLoad: - case MessageCommand.Verack: - case MessageCommand.Version: - case MessageCommand.Alert: - return true; - default: - return false; - } - case Tcp.ConnectionClosed _: - case Connection.Close _: - case Connection.Ack _: - return true; - default: - return false; - } + Message msg => msg.Command switch + { + MessageCommand.Extensible or MessageCommand.FilterAdd or MessageCommand.FilterClear or MessageCommand.FilterLoad or MessageCommand.Verack or MessageCommand.Version or MessageCommand.Alert => true, + _ => false, + }, + Tcp.ConnectionClosed _ or Connection.Close _ or Connection.Ack _ => true, + _ => false, + }; } internal protected override bool ShallDrop(object message, IEnumerable queue) { - if (!(message is Message msg)) return false; - switch (msg.Command) + if (message is not Message msg) return false; + return msg.Command switch { - case MessageCommand.GetAddr: - case MessageCommand.GetBlocks: - case MessageCommand.GetHeaders: - case MessageCommand.Mempool: - return queue.OfType().Any(p => p.Command == msg.Command); - default: - return false; - } + MessageCommand.GetAddr or MessageCommand.GetBlocks or MessageCommand.GetHeaders or MessageCommand.Mempool => queue.OfType().Any(p => p.Command == msg.Command), + _ => false, + }; } } } diff --git a/src/neo/Network/P2P/TaskManager.cs b/src/neo/Network/P2P/TaskManager.cs index 8ea8457a72..01efadc650 100644 --- a/src/neo/Network/P2P/TaskManager.cs +++ b/src/neo/Network/P2P/TaskManager.cs @@ -15,12 +15,26 @@ namespace Neo.Network.P2P { + /// + /// Actor used to manage the tasks of inventories. + /// public class TaskManager : UntypedActor { internal class Register { public VersionPayload Version; } internal class Update { public uint LastBlockIndex; } internal class NewTasks { public InvPayload Payload; } - public class RestartTasks { public InvPayload Payload; } + + /// + /// Sent to to restart tasks for inventories. + /// + public class RestartTasks + { + /// + /// The inventories that need to restart. + /// + public InvPayload Payload { get; init; } + } + private class Timer { } private static readonly TimeSpan TimerInterval = TimeSpan.FromSeconds(30); @@ -34,17 +48,21 @@ private class Timer { } /// A set of known hashes, of inventories or payloads, already received. /// private readonly HashSetCache knownHashes; - private readonly Dictionary globalInvTasks = new Dictionary(); - private readonly Dictionary globalIndexTasks = new Dictionary(); - private readonly Dictionary sessions = new Dictionary(); + private readonly Dictionary globalInvTasks = new(); + private readonly Dictionary globalIndexTasks = new(); + private readonly Dictionary sessions = new(); private readonly ICancelable timer = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(TimerInterval, TimerInterval, Context.Self, new Timer(), ActorRefs.NoSender); private bool HasHeaderTask => globalInvTasks.ContainsKey(HeaderTaskHash); + /// + /// Initializes a new instance of the class. + /// + /// The object that contains the . public TaskManager(NeoSystem system) { this.system = system; - this.knownHashes = new HashSetCache(Blockchain.Singleton.MemPool.Capacity * 2 / 5); + this.knownHashes = new HashSetCache(system.MemPool.Capacity * 2 / 5); Context.System.EventStream.Subscribe(Self, typeof(Blockchain.PersistCompleted)); Context.System.EventStream.Subscribe(Self, typeof(Blockchain.RelayResult)); } @@ -72,15 +90,15 @@ private void OnNewTasks(InvPayload payload) return; // Do not accept payload of type InventoryType.TX if not synced on HeaderHeight - uint currentHeight = NativeContract.Ledger.CurrentIndex(Blockchain.Singleton.View); - uint headerHeight = Blockchain.Singleton.HeaderCache.Last?.Index ?? currentHeight; + uint currentHeight = NativeContract.Ledger.CurrentIndex(system.StoreView); + uint headerHeight = system.HeaderCache.Last?.Index ?? currentHeight; if (currentHeight < headerHeight && (payload.Type == InventoryType.TX || (payload.Type == InventoryType.Block && currentHeight < session.LastBlockIndex - InvPayload.MaxHashesCount))) { RequestTasks(Sender, session); return; } - HashSet hashes = new HashSet(payload.Hashes); + HashSet hashes = new(payload.Hashes); // Remove all previously processed knownHashes from the list that is being requested hashes.Remove(knownHashes); // Add to AvailableTasks the ones, of type InventoryType.Block, that are global (already under process by other sessions) @@ -159,7 +177,7 @@ protected override void OnReceive(object message) private void OnRegister(VersionPayload version) { Context.Watch(Sender); - TaskSession session = new TaskSession(version); + TaskSession session = new(version); sessions.Add(Sender, session); RequestTasks(Sender, session); } @@ -304,6 +322,11 @@ protected override void PostStop() base.PostStop(); } + /// + /// Gets a object used for creating the actor. + /// + /// The object that contains the . + /// The object used for creating the actor. public static Props Props(NeoSystem system) { return Akka.Actor.Props.Create(() => new TaskManager(system)).WithMailbox("task-manager-mailbox"); @@ -313,7 +336,7 @@ private void RequestTasks(IActorRef remoteNode, TaskSession session) { if (session.HasTooManyTasks) return; - DataCache snapshot = Blockchain.Singleton.View; + DataCache snapshot = system.StoreView; // If there are pending tasks of InventoryType.Block we should process them if (session.AvailableTasks.Count > 0) @@ -321,7 +344,7 @@ private void RequestTasks(IActorRef remoteNode, TaskSession session) session.AvailableTasks.Remove(knownHashes); // Search any similar hash that is on Singleton's knowledge, which means, on the way or already processed session.AvailableTasks.RemoveWhere(p => NativeContract.Ledger.ContainsBlock(snapshot, p)); - HashSet hashes = new HashSet(session.AvailableTasks); + HashSet hashes = new(session.AvailableTasks); if (hashes.Count > 0) { foreach (UInt256 hash in hashes.ToArray()) @@ -339,10 +362,10 @@ private void RequestTasks(IActorRef remoteNode, TaskSession session) } uint currentHeight = NativeContract.Ledger.CurrentIndex(snapshot); - uint headerHeight = Blockchain.Singleton.HeaderCache.Last?.Index ?? currentHeight; + uint headerHeight = system.HeaderCache.Last?.Index ?? currentHeight; // When the number of AvailableTasks is no more than 0, no pending tasks of InventoryType.Block, it should process pending the tasks of headers // If not HeaderTask pending to be processed it should ask for more Blocks - if ((!HasHeaderTask || globalInvTasks[HeaderTaskHash] < MaxConncurrentTasks) && headerHeight < session.LastBlockIndex && !Blockchain.Singleton.HeaderCache.Full) + if ((!HasHeaderTask || globalInvTasks[HeaderTaskHash] < MaxConncurrentTasks) && headerHeight < session.LastBlockIndex && !system.HeaderCache.Full) { session.InvTasks[HeaderTaskHash] = DateTime.UtcNow; IncrementGlobalTask(HeaderTaskHash); @@ -397,7 +420,7 @@ internal protected override bool IsHighPriority(object message) internal protected override bool ShallDrop(object message, IEnumerable queue) { - if (!(message is TaskManager.NewTasks tasks)) return false; + if (message is not TaskManager.NewTasks tasks) return false; // Remove duplicate tasks if (queue.OfType().Any(x => x.Payload.Type == tasks.Payload.Type && x.Payload.Hashes.SequenceEqual(tasks.Payload.Hashes))) return true; return false; diff --git a/src/neo/Network/UPnP.cs b/src/neo/Network/UPnP.cs index 1a5eec4a29..afa6b4590b 100644 --- a/src/neo/Network/UPnP.cs +++ b/src/neo/Network/UPnP.cs @@ -8,81 +8,89 @@ namespace Neo.Network { - public class UPnP + /// + /// Provides methods for interacting with UPnP devices. + /// + public static class UPnP { private static string _serviceUrl; + /// + /// Gets or sets the timeout for discovering the UPnP device. + /// public static TimeSpan TimeOut { get; set; } = TimeSpan.FromSeconds(3); + /// + /// Sends an Udp broadcast message to discover the UPnP device. + /// + /// if the UPnP device is successfully discovered; otherwise, . public static bool Discover() { - using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) + using Socket s = new(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + s.ReceiveTimeout = (int)TimeOut.TotalMilliseconds; + s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); + string req = "M-SEARCH * HTTP/1.1\r\n" + + "HOST: 239.255.255.250:1900\r\n" + + "ST:upnp:rootdevice\r\n" + + "MAN:\"ssdp:discover\"\r\n" + + "MX:3\r\n\r\n"; + byte[] data = Encoding.ASCII.GetBytes(req); + IPEndPoint ipe = new(IPAddress.Broadcast, 1900); + + DateTime start = DateTime.Now; + + try { - s.ReceiveTimeout = (int)TimeOut.TotalMilliseconds; - s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); - string req = "M-SEARCH * HTTP/1.1\r\n" + - "HOST: 239.255.255.250:1900\r\n" + - "ST:upnp:rootdevice\r\n" + - "MAN:\"ssdp:discover\"\r\n" + - "MX:3\r\n\r\n"; - byte[] data = Encoding.ASCII.GetBytes(req); - IPEndPoint ipe = new IPEndPoint(IPAddress.Broadcast, 1900); + s.SendTo(data, ipe); + s.SendTo(data, ipe); + s.SendTo(data, ipe); + } + catch + { + return false; + } - DateTime start = DateTime.Now; + Span buffer = stackalloc byte[0x1000]; + do + { + int length; try { - s.SendTo(data, ipe); - s.SendTo(data, ipe); - s.SendTo(data, ipe); - } - catch - { - return false; - } - - Span buffer = stackalloc byte[0x1000]; + length = s.Receive(buffer); - do - { - int length; - try + string resp = Encoding.ASCII.GetString(buffer[..length]).ToLowerInvariant(); + if (resp.Contains("upnp:rootdevice")) { - length = s.Receive(buffer); - - string resp = Encoding.ASCII.GetString(buffer[..length]).ToLowerInvariant(); - if (resp.Contains("upnp:rootdevice")) + resp = resp[(resp.IndexOf("location:") + 9)..]; + resp = resp.Substring(0, resp.IndexOf("\r")).Trim(); + if (!string.IsNullOrEmpty(_serviceUrl = GetServiceUrl(resp))) { - resp = resp.Substring(resp.IndexOf("location:") + 9); - resp = resp.Substring(0, resp.IndexOf("\r")).Trim(); - if (!string.IsNullOrEmpty(_serviceUrl = GetServiceUrl(resp))) - { - return true; - } + return true; } } - catch - { - continue; - } } - while (DateTime.Now - start < TimeOut); - - return false; + catch + { + continue; + } } + while (DateTime.Now - start < TimeOut); + + return false; } private static string GetServiceUrl(string resp) { try { - XmlDocument desc = new XmlDocument() { XmlResolver = null }; + XmlDocument desc = new() { XmlResolver = null }; HttpWebRequest request = WebRequest.CreateHttp(resp); using (WebResponse response = request.GetResponse()) { desc.Load(response.GetResponseStream()); } - XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable); + XmlNamespaceManager nsMgr = new(desc.NameTable); nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr); if (!typen.Value.Contains("InternetGatewayDevice")) @@ -103,6 +111,12 @@ private static string CombineUrls(string resp, string p) return resp.Substring(0, n) + p; } + /// + /// Attempt to create a port forwarding. + /// + /// The port to forward. + /// The of the port. + /// The description of the forward. public static void ForwardPort(int port, ProtocolType protocol, string description) { if (string.IsNullOrEmpty(_serviceUrl)) @@ -114,6 +128,11 @@ public static void ForwardPort(int port, ProtocolType protocol, string descripti "0", "AddPortMapping"); } + /// + /// Attempt to delete a port forwarding. + /// + /// The port to forward. + /// The of the port. public static void DeleteForwardingRule(int port, ProtocolType protocol) { if (string.IsNullOrEmpty(_serviceUrl)) @@ -127,13 +146,17 @@ public static void DeleteForwardingRule(int port, ProtocolType protocol) "", "DeletePortMapping"); } + /// + /// Attempt to get the external IP address of the local host. + /// + /// The external IP address of the local host. public static IPAddress GetExternalIP() { if (string.IsNullOrEmpty(_serviceUrl)) throw new Exception("No UPnP service available or Discover() has not been called"); XmlDocument xdoc = SOAPRequest(_serviceUrl, "" + "", "GetExternalIPAddress"); - XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable); + XmlNamespaceManager nsMgr = new(xdoc.NameTable); nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); string IP = xdoc.SelectSingleNode("//NewExternalIPAddress/text()", nsMgr).Value; return IPAddress.Parse(IP); @@ -152,17 +175,13 @@ private static XmlDocument SOAPRequest(string url, string soap, string function) byte[] b = Encoding.UTF8.GetBytes(req); r.Headers["SOAPACTION"] = "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + function + "\""; r.ContentType = "text/xml; charset=\"utf-8\""; - using (Stream reqs = r.GetRequestStream()) - { - reqs.Write(b, 0, b.Length); - XmlDocument resp = new XmlDocument() { XmlResolver = null }; - WebResponse wres = r.GetResponse(); - using (Stream ress = wres.GetResponseStream()) - { - resp.Load(ress); - return resp; - } - } + using Stream reqs = r.GetRequestStream(); + reqs.Write(b, 0, b.Length); + XmlDocument resp = new() { XmlResolver = null }; + WebResponse wres = r.GetResponse(); + using Stream ress = wres.GetResponseStream(); + resp.Load(ress); + return resp; } } } diff --git a/src/neo/Persistence/DataCache.cs b/src/neo/Persistence/DataCache.cs index a5b96fc5fe..fbd81e3c39 100644 --- a/src/neo/Persistence/DataCache.cs +++ b/src/neo/Persistence/DataCache.cs @@ -6,18 +6,41 @@ namespace Neo.Persistence { + /// + /// Represents a cache for the underlying storage of the NEO blockchain. + /// public abstract class DataCache { + /// + /// Represents an entry in the cache. + /// public class Trackable { + /// + /// The key of the entry. + /// public StorageKey Key; + + /// + /// The data of the entry. + /// public StorageItem Item; + + /// + /// The state of the entry. + /// public TrackState State; } - private readonly Dictionary dictionary = new Dictionary(); - private readonly HashSet changeSet = new HashSet(); + private readonly Dictionary dictionary = new(); + private readonly HashSet changeSet = new(); + /// + /// Reads a specified entry from the cache. If the entry is not in the cache, it will be automatically loaded from the underlying storage. + /// + /// The key of the entry. + /// The cached data. + /// If the entry doesn't exist. public StorageItem this[StorageKey key] { get @@ -45,15 +68,12 @@ public StorageItem this[StorageKey key] } /// - /// Try to Add a specific key, with associated value, to the current cached dictionary. - /// It will not read from internal state. - /// However, if previously cached into Dictionary, request may fail. + /// Adds a new entry to the cache. /// - /// Key to be possible added. - /// Key will not be added if value exists cached and the modification was not a Deleted one. - /// - /// Corresponding value to be added, in the case of sucess. - /// If cached on dictionary, with any state rather than `Deleted`, an Exception will be raised. + /// The key of the entry. + /// The data of the entry. + /// The entry has already been cached. + /// Note: This method does not read the internal storage to check whether the record already exists. public void Add(StorageKey key, StorageItem value) { lock (dictionary) @@ -70,14 +90,19 @@ public void Add(StorageKey key, StorageItem value) } } + /// + /// Adds a new entry to the underlying storage. + /// + /// The key of the entry. + /// The data of the entry. protected abstract void AddInternal(StorageKey key, StorageItem value); /// - /// Update internals with all changes cached on Dictionary which are not None. + /// Commits all changes in the cache to the underlying storage. /// public virtual void Commit() { - LinkedList deletedItem = new LinkedList(); + LinkedList deletedItem = new(); foreach (Trackable trackable in GetChangeSet()) switch (trackable.State) { @@ -101,15 +126,19 @@ public virtual void Commit() changeSet.Clear(); } + /// + /// Creates a snapshot, which uses this instance as the underlying storage. + /// + /// The snapshot of this instance. public DataCache CreateSnapshot() { return new ClonedCache(this); } /// - /// Delete key from cached Dictionary or search in Internal. + /// Deletes an entry from the cache. /// - /// Key to be deleted. + /// The key of the entry. public void Delete(StorageKey key) { lock (dictionary) @@ -142,13 +171,17 @@ public void Delete(StorageKey key) } } + /// + /// Deletes an entry from the underlying storage. + /// + /// The key of the entry. protected abstract void DeleteInternal(StorageKey key); /// - /// Find the entries that start with the `key_prefix` + /// Finds the entries starting with the specified prefix. /// - /// Must maintain the deserialized format of StorageKey - /// Entries found with the desired prefix + /// The prefix of the key. + /// The entries found with the desired prefix. public IEnumerable<(StorageKey Key, StorageItem Value)> Find(byte[] key_prefix = null) { foreach (var (key, value) in Seek(key_prefix, SeekDirection.Forward)) @@ -159,12 +192,12 @@ public void Delete(StorageKey key) } /// - /// Find the entries that between [start, end) + /// Finds the entries that between [start, end). /// - /// Start key (inclusive) - /// End key (exclusive) + /// The start key (inclusive). + /// The end key (exclusive). /// The search direction. - /// Entries found with the desired range + /// The entries found with the desired range. public IEnumerable<(StorageKey Key, StorageItem Value)> FindRange(byte[] start, byte[] end, SeekDirection direction = SeekDirection.Forward) { ByteArrayComparer comparer = direction == SeekDirection.Forward @@ -177,6 +210,10 @@ public void Delete(StorageKey key) yield break; } + /// + /// Gets the change set in the cache. + /// + /// The change set. public IEnumerable GetChangeSet() { lock (dictionary) @@ -186,6 +223,11 @@ public IEnumerable GetChangeSet() } } + /// + /// Determines whether the cache contains the specified entry. + /// + /// The key of the entry. + /// if the cache contains an entry with the specified key; otherwise, . public bool Contains(StorageKey key) { lock (dictionary) @@ -199,18 +241,26 @@ public bool Contains(StorageKey key) } } + /// + /// Determines whether the underlying storage contains the specified entry. + /// + /// The key of the entry. + /// if the underlying storage contains an entry with the specified key; otherwise, . protected abstract bool ContainsInternal(StorageKey key); + /// + /// Reads a specified entry from the underlying storage. + /// + /// The key of the entry. + /// The data of the entry. Or if the entry doesn't exist. protected abstract StorageItem GetInternal(StorageKey key); /// - /// Try to Get a specific key from current cached dictionary. - /// Otherwise, tries to get from internal data with TryGetInternal. + /// Reads a specified entry from the cache, and mark it as . If the entry is not in the cache, it will be automatically loaded from the underlying storage. /// - /// Key to be searched. - /// Function that may replace current object stored. - /// If object already exists the factory passed as parameter will not be used. - /// + /// The key of the entry. + /// A delegate used to create the entry if it doesn't exist. If the entry already exists, the factory will not be used. + /// The cached data. Or if it doesn't exist and the is not provided. public StorageItem GetAndChange(StorageKey key, Func factory = null) { lock (dictionary) @@ -253,6 +303,12 @@ public StorageItem GetAndChange(StorageKey key, Func factory = null } } + /// + /// Reads a specified entry from the cache. If the entry is not in the cache, it will be automatically loaded from the underlying storage. If the entry doesn't exist, the factory will be used to create a new one. + /// + /// The key of the entry. + /// A delegate used to create the entry if it doesn't exist. If the entry already exists, the factory will not be used. + /// The cached data. public StorageItem GetOrAdd(StorageKey key, Func factory) { lock (dictionary) @@ -289,10 +345,10 @@ public StorageItem GetOrAdd(StorageKey key, Func factory) } /// - /// Seek to the entry with specific key + /// Seeks to the entry with the specified key. /// - /// The key to be sought - /// The direction of seek + /// The key to be sought. + /// The direction of seek. /// An enumerator containing all the entries after seeking. public IEnumerable<(StorageKey Key, StorageItem Value)> Seek(byte[] keyOrPrefix = null, SeekDirection direction = SeekDirection.Forward) { @@ -321,34 +377,43 @@ public StorageItem GetOrAdd(StorageKey key, Func factory) p.Key, p.Value )); - using (var e1 = cached.GetEnumerator()) - using (var e2 = uncached.GetEnumerator()) + using var e1 = cached.GetEnumerator(); + using var e2 = uncached.GetEnumerator(); + (byte[] KeyBytes, StorageKey Key, StorageItem Item) i1, i2; + bool c1 = e1.MoveNext(); + bool c2 = e2.MoveNext(); + i1 = c1 ? e1.Current : default; + i2 = c2 ? e2.Current : default; + while (c1 || c2) { - (byte[] KeyBytes, StorageKey Key, StorageItem Item) i1, i2; - bool c1 = e1.MoveNext(); - bool c2 = e2.MoveNext(); - i1 = c1 ? e1.Current : default; - i2 = c2 ? e2.Current : default; - while (c1 || c2) + if (!c2 || (c1 && comparer.Compare(i1.KeyBytes, i2.KeyBytes) < 0)) { - if (!c2 || (c1 && comparer.Compare(i1.KeyBytes, i2.KeyBytes) < 0)) - { - yield return (i1.Key, i1.Item); - c1 = e1.MoveNext(); - i1 = c1 ? e1.Current : default; - } - else - { - yield return (i2.Key, i2.Item); - c2 = e2.MoveNext(); - i2 = c2 ? e2.Current : default; - } + yield return (i1.Key, i1.Item); + c1 = e1.MoveNext(); + i1 = c1 ? e1.Current : default; + } + else + { + yield return (i2.Key, i2.Item); + c2 = e2.MoveNext(); + i2 = c2 ? e2.Current : default; } } } + /// + /// Seeks to the entry with the specified key in the underlying storage. + /// + /// The key to be sought. + /// The direction of seek. + /// An enumerator containing all the entries after seeking. protected abstract IEnumerable<(StorageKey Key, StorageItem Value)> SeekInternal(byte[] keyOrPrefix, SeekDirection direction); + /// + /// Reads a specified entry from the cache. If the entry is not in the cache, it will be automatically loaded from the underlying storage. + /// + /// The key of the entry. + /// The cached data. Or if it is neither in the cache nor in the underlying storage. public StorageItem TryGet(StorageKey key) { lock (dictionary) @@ -370,8 +435,18 @@ public StorageItem TryGet(StorageKey key) } } + /// + /// Reads a specified entry from the underlying storage. + /// + /// The key of the entry. + /// The data of the entry. Or if it doesn't exist. protected abstract StorageItem TryGetInternal(StorageKey key); + /// + /// Updates an entry in the underlying storage. + /// + /// The key of the entry. + /// The data of the entry. protected abstract void UpdateInternal(StorageKey key, StorageItem value); } } diff --git a/src/neo/Persistence/IReadOnlyStore.cs b/src/neo/Persistence/IReadOnlyStore.cs index 7200691084..d04c74e4cc 100644 --- a/src/neo/Persistence/IReadOnlyStore.cs +++ b/src/neo/Persistence/IReadOnlyStore.cs @@ -7,8 +7,26 @@ namespace Neo.Persistence /// public interface IReadOnlyStore { + /// + /// Seeks to the entry with the specified key. + /// + /// The key to be sought. + /// The direction of seek. + /// An enumerator containing all the entries after seeking. IEnumerable<(byte[] Key, byte[] Value)> Seek(byte[] key, SeekDirection direction); + + /// + /// Reads a specified entry from the database. + /// + /// The key of the entry. + /// The data of the entry. Or if it doesn't exist. byte[] TryGet(byte[] key); + + /// + /// Determines whether the database contains the specified entry. + /// + /// The key of the entry. + /// if the database contains an entry with the specified key; otherwise, . bool Contains(byte[] key); } } diff --git a/src/neo/Persistence/ISnapshot.cs b/src/neo/Persistence/ISnapshot.cs index 12bb0780af..ff2afb158c 100644 --- a/src/neo/Persistence/ISnapshot.cs +++ b/src/neo/Persistence/ISnapshot.cs @@ -7,8 +7,22 @@ namespace Neo.Persistence /// public interface ISnapshot : IDisposable, IReadOnlyStore { + /// + /// Commits all changes in the snapshot to the database. + /// void Commit(); + + /// + /// Deletes an entry from the snapshot. + /// + /// The key of the entry. void Delete(byte[] key); + + /// + /// Puts an entry to the snapshot. + /// + /// The key of the entry. + /// The data of the entry. void Put(byte[] key, byte[] value); } } diff --git a/src/neo/Persistence/IStore.cs b/src/neo/Persistence/IStore.cs index dbf2087760..1a9e1bb271 100644 --- a/src/neo/Persistence/IStore.cs +++ b/src/neo/Persistence/IStore.cs @@ -7,9 +7,30 @@ namespace Neo.Persistence /// public interface IStore : IDisposable, IReadOnlyStore { + /// + /// Deletes an entry from the database. + /// + /// The key of the entry. void Delete(byte[] key); + + /// + /// Creates a snapshot of the database. + /// + /// A snapshot of the database. ISnapshot GetSnapshot(); + + /// + /// Puts an entry to the database. + /// + /// The key of the entry. + /// The data of the entry. void Put(byte[] key, byte[] value); + + /// + /// Puts an entry to the database synchronously. + /// + /// The key of the entry. + /// The data of the entry. void PutSync(byte[] key, byte[] value) => Put(key, value); } } diff --git a/src/neo/Persistence/MemoryStore.cs b/src/neo/Persistence/MemoryStore.cs index 1cf51dde51..3023a8398d 100644 --- a/src/neo/Persistence/MemoryStore.cs +++ b/src/neo/Persistence/MemoryStore.cs @@ -5,9 +5,12 @@ namespace Neo.Persistence { + /// + /// An in-memory implementation that uses ConcurrentDictionary as the underlying storage. + /// public class MemoryStore : IStore { - private readonly ConcurrentDictionary innerData = new ConcurrentDictionary(ByteArrayEqualityComparer.Default); + private readonly ConcurrentDictionary innerData = new(ByteArrayEqualityComparer.Default); public void Delete(byte[] key) { diff --git a/src/neo/Persistence/SeekDirection.cs b/src/neo/Persistence/SeekDirection.cs index ca48561ab3..2868f5a132 100644 --- a/src/neo/Persistence/SeekDirection.cs +++ b/src/neo/Persistence/SeekDirection.cs @@ -1,8 +1,18 @@ namespace Neo.Persistence { + /// + /// Represents the direction when searching from the database. + /// public enum SeekDirection : sbyte { + /// + /// Indicates that the search should be performed in ascending order. + /// Forward = 1, + + /// + /// Indicates that the search should be performed in descending order. + /// Backward = -1 } } diff --git a/src/neo/Persistence/SnapshotCache.cs b/src/neo/Persistence/SnapshotCache.cs index a6e6bf9452..5d61207952 100644 --- a/src/neo/Persistence/SnapshotCache.cs +++ b/src/neo/Persistence/SnapshotCache.cs @@ -6,11 +6,18 @@ namespace Neo.Persistence { + /// + /// Represents a cache for the snapshot or database of the NEO blockchain. + /// public class SnapshotCache : DataCache, IDisposable { private readonly IReadOnlyStore store; private readonly ISnapshot snapshot; + /// + /// Initializes a new instance of the class. + /// + /// An to create a readonly cache; or an to create a snapshot cache. public SnapshotCache(IReadOnlyStore store) { this.store = store; diff --git a/src/neo/Persistence/TrackState.cs b/src/neo/Persistence/TrackState.cs index 30bc210539..22e7643c7f 100644 --- a/src/neo/Persistence/TrackState.cs +++ b/src/neo/Persistence/TrackState.cs @@ -1,10 +1,28 @@ namespace Neo.Persistence { + /// + /// Represents the state of a cached entry. + /// public enum TrackState : byte { + /// + /// Indicates that the entry has been loaded from the underlying storage, but has not been modified. + /// None, + + /// + /// Indicates that this is a newly added record. + /// Added, + + /// + /// Indicates that the entry has been loaded from the underlying storage, and has been modified. + /// Changed, + + /// + /// Indicates that the entry should be deleted from the underlying storage when committing. + /// Deleted } } diff --git a/src/neo/Plugins/IApplicationEngineProvider.cs b/src/neo/Plugins/IApplicationEngineProvider.cs index 43f50daa3a..ae8f9b2a19 100644 --- a/src/neo/Plugins/IApplicationEngineProvider.cs +++ b/src/neo/Plugins/IApplicationEngineProvider.cs @@ -4,8 +4,21 @@ namespace Neo.Plugins { + /// + /// A provider for creating instances. + /// public interface IApplicationEngineProvider { - ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, long gas); + /// + /// Creates a new instance of the class or its subclass. This method will be called by . + /// + /// The trigger of the execution. + /// The container of the script. + /// The snapshot used by the engine during execution. + /// The block being persisted. It should be if the is . + /// The used by the engine. + /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + /// The engine instance created. + ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas); } } diff --git a/src/neo/Plugins/IConsensusProvider.cs b/src/neo/Plugins/IConsensusProvider.cs deleted file mode 100644 index a7e021863a..0000000000 --- a/src/neo/Plugins/IConsensusProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Neo.Wallets; - -namespace Neo.Plugins -{ - public interface IConsensusProvider - { - void Start(Wallet wallet); - } -} diff --git a/src/neo/Plugins/ILogPlugin.cs b/src/neo/Plugins/ILogPlugin.cs index 0934cc414a..98da0ca7ae 100644 --- a/src/neo/Plugins/ILogPlugin.cs +++ b/src/neo/Plugins/ILogPlugin.cs @@ -1,7 +1,16 @@ namespace Neo.Plugins { + /// + /// A plug-in interface for logs. + /// public interface ILogPlugin { + /// + /// Writes a new log to the plugin. + /// + /// The source of the log. Used to identify the producer of the log. + /// The level of the log. + /// The message of the log. void Log(string source, LogLevel level, object message); } } diff --git a/src/neo/Plugins/IMemoryPoolTxObserverPlugin.cs b/src/neo/Plugins/IMemoryPoolTxObserverPlugin.cs index 2d7e0b2d63..531eca9dda 100644 --- a/src/neo/Plugins/IMemoryPoolTxObserverPlugin.cs +++ b/src/neo/Plugins/IMemoryPoolTxObserverPlugin.cs @@ -3,9 +3,24 @@ namespace Neo.Plugins { + /// + /// An interface that allows plugins to observe changes in the memory pool. + /// public interface IMemoryPoolTxObserverPlugin { - void TransactionAdded(Transaction tx); - void TransactionsRemoved(MemoryPoolTxRemovalReason reason, IEnumerable transactions); + /// + /// Called when a transaction is added to the memory pool. + /// + /// The object that contains the memory pool. + /// The transaction added. + void TransactionAdded(NeoSystem system, Transaction tx); + + /// + /// Called when transactions are removed from the memory pool. + /// + /// The object that contains the memory pool. + /// The reason the transactions were removed. + /// The removed transactions. + void TransactionsRemoved(NeoSystem system, MemoryPoolTxRemovalReason reason, IEnumerable transactions); } } diff --git a/src/neo/Plugins/IP2PPlugin.cs b/src/neo/Plugins/IP2PPlugin.cs index 5479373ad4..4378b8a4d5 100644 --- a/src/neo/Plugins/IP2PPlugin.cs +++ b/src/neo/Plugins/IP2PPlugin.cs @@ -2,8 +2,17 @@ namespace Neo.Plugins { + /// + /// An interface that allows plugins to observe the messages on the network. + /// public interface IP2PPlugin { - bool OnP2PMessage(Message message) => true; + /// + /// Called when a message is received from a remote node. + /// + /// The object that contains the local node. + /// The received message. + /// if the should be dropped; otherwise, . + bool OnP2PMessage(NeoSystem system, Message message) => true; } } diff --git a/src/neo/Plugins/IPersistencePlugin.cs b/src/neo/Plugins/IPersistencePlugin.cs index 76bb126cc5..0b3efac727 100644 --- a/src/neo/Plugins/IPersistencePlugin.cs +++ b/src/neo/Plugins/IPersistencePlugin.cs @@ -6,10 +6,33 @@ namespace Neo.Plugins { + /// + /// An interface that allows plugins to observe the persisted blocks. + /// public interface IPersistencePlugin { - void OnPersist(Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { } - void OnCommit(Block block, DataCache snapshot) { } + /// + /// Called when a block is being persisted. + /// + /// The object that contains the blockchain. + /// The block being persisted. + /// The snapshot used for persistence. + /// The execution result of the contracts in the block. + void OnPersist(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { } + + /// + /// Called when a block has been persisted. + /// + /// The object that contains the blockchain. + /// The block being persisted. + /// The snapshot used for persistence. + void OnCommit(NeoSystem system, Block block, DataCache snapshot) { } + + /// + /// Indicates whether to allow exceptions to be thrown from the plugin when committing. + /// + /// The exception to be thrown. + /// if the exception should be thrown; otherwise, . bool ShouldThrowExceptionFromCommit(Exception ex) => false; } } diff --git a/src/neo/Plugins/IStorageProvider.cs b/src/neo/Plugins/IStorageProvider.cs index b8b7197b6c..d5f1fa0fed 100644 --- a/src/neo/Plugins/IStorageProvider.cs +++ b/src/neo/Plugins/IStorageProvider.cs @@ -2,8 +2,16 @@ namespace Neo.Plugins { + /// + /// A provider used to create instances. + /// public interface IStorageProvider { + /// + /// Creates a new instance of the interface. + /// + /// The path of the database. + /// The created instance. IStore GetStore(string path); } } diff --git a/src/neo/Plugins/IWalletProvider.cs b/src/neo/Plugins/IWalletProvider.cs index 361ba3d9eb..e9a29b671d 100644 --- a/src/neo/Plugins/IWalletProvider.cs +++ b/src/neo/Plugins/IWalletProvider.cs @@ -3,10 +3,20 @@ namespace Neo.Plugins { + /// + /// A provider for obtaining wallet instance. + /// public interface IWalletProvider { - event EventHandler WalletOpened; + /// + /// Triggered when a wallet is opened or closed. + /// + event EventHandler WalletChanged; + /// + /// Get the currently opened instance. + /// + /// The opened wallet. Or if no wallet is opened. Wallet GetWallet(); } } diff --git a/src/neo/Plugins/MemoryPoolTxRemovalReason.cs b/src/neo/Plugins/MemoryPoolTxRemovalReason.cs index ddc7e10981..8e9b05f170 100644 --- a/src/neo/Plugins/MemoryPoolTxRemovalReason.cs +++ b/src/neo/Plugins/MemoryPoolTxRemovalReason.cs @@ -1,11 +1,15 @@ namespace Neo.Plugins { + /// + /// The reason a transaction was removed. + /// public enum MemoryPoolTxRemovalReason : byte { /// - /// The transaction was ejected since it was the lowest priority transaction and the MemoryPool capacity was exceeded. + /// The transaction was ejected since it was the lowest priority transaction and the memory pool capacity was exceeded. /// CapacityExceeded, + /// /// The transaction was ejected due to failing re-validation after a block was persisted. /// diff --git a/src/neo/Plugins/Plugin.cs b/src/neo/Plugins/Plugin.cs index ddcdd54036..ca01e396ac 100644 --- a/src/neo/Plugins/Plugin.cs +++ b/src/neo/Plugins/Plugin.cs @@ -2,34 +2,59 @@ using Neo.SmartContract; using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; -using System.Threading; using static System.IO.Path; namespace Neo.Plugins { + /// + /// Represents the base class of all plugins. Any plugin should inherit this class. The plugins are automatically loaded when the process starts. + /// public abstract class Plugin : IDisposable { - public static readonly List Plugins = new List(); - internal static readonly List Loggers = new List(); - internal static readonly Dictionary Storages = new Dictionary(); - internal static readonly List PersistencePlugins = new List(); - internal static readonly List P2PPlugins = new List(); - internal static readonly List TxObserverPlugins = new List(); - - private static ImmutableList services = ImmutableList.Empty; + /// + /// A list of all loaded plugins. + /// + public static readonly List Plugins = new(); + + internal static readonly List Loggers = new(); + internal static readonly Dictionary Storages = new(); + internal static readonly List PersistencePlugins = new(); + internal static readonly List P2PPlugins = new(); + internal static readonly List TxObserverPlugins = new(); + + /// + /// The directory containing the plugin dll files. Files can be contained in any subdirectory. + /// public static readonly string PluginsDirectory = Combine(GetDirectoryName(Assembly.GetEntryAssembly().Location), "Plugins"); + private static readonly FileSystemWatcher configWatcher; - private static int suspend = 0; + /// + /// Indicates the location of the plugin configuration file. + /// public virtual string ConfigFile => Combine(PluginsDirectory, GetType().Assembly.GetName().Name, "config.json"); + + /// + /// Indicates the name of the plugin. + /// public virtual string Name => GetType().Name; + + /// + /// Indicates the description of the plugin. + /// public virtual string Description => ""; + + /// + /// Indicates the location of the plugin dll file. + /// public virtual string Path => Combine(PluginsDirectory, GetType().Assembly.ManifestModule.ScopeName); - protected static NeoSystem System { get; private set; } + + /// + /// Indicates the version of the plugin. + /// public virtual Version Version => GetType().Assembly.GetName().Version; static Plugin() @@ -48,6 +73,9 @@ static Plugin() } } + /// + /// Initializes a new instance of the class. + /// protected Plugin() { Plugins.Add(this); @@ -62,21 +90,9 @@ protected Plugin() Configure(); } - public static void AddService(object service) - { - ImmutableInterlocked.Update(ref services, p => p.Add(service)); - } - - private static bool CheckRequiredServices(Type type) - { - RequiredServicesAttribute attribute = type.GetCustomAttribute(); - if (attribute is null) return true; - foreach (Type rt in attribute.RequiredServices) - if (services.All(p => !rt.IsAssignableFrom(p.GetType()))) - return false; - return true; - } - + /// + /// Called when the plugin is loaded and need to load the configure file, or the configuration file has been modified and needs to be reconfigured. + /// protected virtual void Configure() { } @@ -109,7 +125,7 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven if (args.Name.Contains(".resources")) return null; - AssemblyName an = new AssemblyName(args.Name); + AssemblyName an = new(args.Name); Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name); if (assembly is null) @@ -138,23 +154,21 @@ public virtual void Dispose() { } + /// + /// Loads the configuration file from the path of . + /// + /// The content of the configuration file read. protected IConfigurationSection GetConfiguration() { return new ConfigurationBuilder().AddJsonFile(ConfigFile, optional: true).Build().GetSection("PluginConfiguration"); } - protected static T GetService() - { - return services.OfType().FirstOrDefault(); - } - private static void LoadPlugin(Assembly assembly) { foreach (Type type in assembly.ExportedTypes) { if (!type.IsSubclassOf(typeof(Plugin))) continue; if (type.IsAbstract) continue; - if (!CheckRequiredServices(type)) continue; ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes); try @@ -168,11 +182,10 @@ private static void LoadPlugin(Assembly assembly) } } - internal static void LoadPlugins(NeoSystem system) + internal static void LoadPlugins() { - System = system; if (!Directory.Exists(PluginsDirectory)) return; - List assemblies = new List(); + List assemblies = new(); foreach (string filename in Directory.EnumerateFiles(PluginsDirectory, "*.dll", SearchOption.TopDirectoryOnly)) { try @@ -187,28 +200,40 @@ internal static void LoadPlugins(NeoSystem system) } } + /// + /// Write a log for the plugin. + /// + /// The message of the log. + /// The level of the log. protected void Log(object message, LogLevel level = LogLevel.Info) { Utility.Log($"{nameof(Plugin)}:{Name}", level, message); } + /// + /// Called when a message to the plugins is received. The messnage is sent by calling . + /// + /// The received message. + /// if the has been handled; otherwise, . + /// If a message has been handled by a plugin, the other plugins won't receive it anymore. protected virtual bool OnMessage(object message) { return false; } - internal protected virtual void OnPluginsLoaded() - { - } - - protected static bool ResumeNodeStartup() + /// + /// Called when a is loaded. + /// + /// The loaded . + internal protected virtual void OnSystemLoaded(NeoSystem system) { - if (Interlocked.Decrement(ref suspend) != 0) - return false; - System.ResumeNodeStartup(); - return true; } + /// + /// Sends a message to all plugins. It can be handled by . + /// + /// The message to send. + /// if the is handled by a plugin; otherwise, . public static bool SendMessage(object message) { foreach (Plugin plugin in Plugins) @@ -216,11 +241,5 @@ public static bool SendMessage(object message) return true; return false; } - - protected static void SuspendNodeStartup() - { - Interlocked.Increment(ref suspend); - System.SuspendNodeStartup(); - } } } diff --git a/src/neo/Plugins/RequiredServicesAttribute.cs b/src/neo/Plugins/RequiredServicesAttribute.cs deleted file mode 100644 index ae0f227115..0000000000 --- a/src/neo/Plugins/RequiredServicesAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Neo.Plugins -{ - [AttributeUsage(AttributeTargets.Class)] - public class RequiredServicesAttribute : Attribute - { - public Type[] RequiredServices { get; } - - public RequiredServicesAttribute(params Type[] requiredServices) - { - this.RequiredServices = requiredServices; - } - } -} diff --git a/src/neo/ProtocolSettings.cs b/src/neo/ProtocolSettings.cs index 4e4cd3decc..767170a42d 100644 --- a/src/neo/ProtocolSettings.cs +++ b/src/neo/ProtocolSettings.cs @@ -1,109 +1,183 @@ using Microsoft.Extensions.Configuration; +using Neo.Cryptography.ECC; +using Neo.SmartContract.Native; using System; using System.Collections.Generic; using System.Linq; -using System.Threading; namespace Neo { - public class ProtocolSettings + /// + /// Represents the protocol settings of the NEO system. + /// + public record ProtocolSettings { - public uint Magic { get; } - public byte AddressVersion { get; } - public string[] StandbyCommittee { get; } - public int CommitteeMembersCount { get; } - public int ValidatorsCount { get; } - public string[] SeedList { get; } - public uint MillisecondsPerBlock { get; } - public int MemoryPoolMaxTransactions { get; } - public uint MaxTraceableBlocks { get; } - public IReadOnlyDictionary NativeActivations { get; } - - static ProtocolSettings _default; - - static bool UpdateDefault(IConfiguration configuration) - { - var settings = new ProtocolSettings(configuration.GetSection("ProtocolConfiguration")); - return null == Interlocked.CompareExchange(ref _default, settings, null); - } + /// + /// The magic number of the NEO network. + /// + public uint Magic { get; init; } - public static bool Initialize(IConfiguration configuration) - { - return UpdateDefault(configuration); - } + /// + /// The address version of the NEO system. + /// + public byte AddressVersion { get; init; } + + /// + /// The public keys of the standby committee members. + /// + public IReadOnlyList StandbyCommittee { get; init; } + + /// + /// The number of members of the committee in NEO system. + /// + public int CommitteeMembersCount => StandbyCommittee.Count; + + /// + /// The number of the validators in NEO system. + /// + public int ValidatorsCount { get; init; } + + /// + /// The default seed nodes list. + /// + public string[] SeedList { get; init; } + + /// + /// Indicates the time in milliseconds between two blocks. + /// + public uint MillisecondsPerBlock { get; init; } + + /// + /// Indicates the time between two blocks. + /// + public TimeSpan TimePerBlock => TimeSpan.FromMilliseconds(MillisecondsPerBlock); + + /// + /// Indicates the maximum number of transactions that can be contained in a block. + /// + public uint MaxTransactionsPerBlock { get; init; } - public static ProtocolSettings Default + /// + /// Indicates the maximum number of transactions that can be contained in the memory pool. + /// + public int MemoryPoolMaxTransactions { get; init; } + + /// + /// Indicates the maximum number of blocks that can be traced in the smart contract. + /// + public uint MaxTraceableBlocks { get; init; } + + /// + /// Contains the update history of all native contracts. + /// + public IReadOnlyDictionary NativeUpdateHistory { get; init; } + + private IReadOnlyList _standbyValidators; + /// + /// The public keys of the standby validators. + /// + public IReadOnlyList StandbyValidators => _standbyValidators ??= StandbyCommittee.Take(ValidatorsCount).ToArray(); + + /// + /// The default protocol settings for NEO MainNet. + /// + public static ProtocolSettings Default { get; } = new ProtocolSettings { - get + Magic = 0x4F454Eu, + AddressVersion = 0x35, + StandbyCommittee = new[] { - if (_default == null) - { - var configuration = Utility.LoadConfig("protocol"); - UpdateDefault(configuration); - } - - return _default; + //Validators + ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1), + ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", ECCurve.Secp256r1), + ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", ECCurve.Secp256r1), + ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", ECCurve.Secp256r1), + ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", ECCurve.Secp256r1), + ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", ECCurve.Secp256r1), + ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", ECCurve.Secp256r1), + //Other Members + ECPoint.Parse("023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", ECCurve.Secp256r1), + ECPoint.Parse("03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", ECCurve.Secp256r1), + ECPoint.Parse("03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", ECCurve.Secp256r1), + ECPoint.Parse("03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", ECCurve.Secp256r1), + ECPoint.Parse("02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", ECCurve.Secp256r1), + ECPoint.Parse("03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", ECCurve.Secp256r1), + ECPoint.Parse("0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", ECCurve.Secp256r1), + ECPoint.Parse("020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", ECCurve.Secp256r1), + ECPoint.Parse("0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", ECCurve.Secp256r1), + ECPoint.Parse("03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", ECCurve.Secp256r1), + ECPoint.Parse("02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", ECCurve.Secp256r1), + ECPoint.Parse("0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", ECCurve.Secp256r1), + ECPoint.Parse("03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", ECCurve.Secp256r1), + ECPoint.Parse("02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a", ECCurve.Secp256r1) + }, + ValidatorsCount = 7, + SeedList = new[] + { + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" + }, + MillisecondsPerBlock = 15000, + MaxTransactionsPerBlock = 512, + MemoryPoolMaxTransactions = 50_000, + MaxTraceableBlocks = 2_102_400, + NativeUpdateHistory = new Dictionary + { + [nameof(ContractManagement)] = new[] { 0u }, + [nameof(StdLib)] = new[] { 0u }, + [nameof(CryptoLib)] = new[] { 0u }, + [nameof(LedgerContract)] = new[] { 0u }, + [nameof(NeoToken)] = new[] { 0u }, + [nameof(GasToken)] = new[] { 0u }, + [nameof(PolicyContract)] = new[] { 0u }, + [nameof(RoleManagement)] = new[] { 0u }, + [nameof(OracleContract)] = new[] { 0u }, + [nameof(NameService)] = new[] { 0u } } - } + }; - private ProtocolSettings(IConfigurationSection section) + /// + /// Loads the at the specified path. + /// + /// The path of the settings file. + /// Indicates whether the file is optional. + /// The loaded . + public static ProtocolSettings Load(string path, bool optional = true) { - this.Magic = section.GetValue("Magic", 0x4F454Eu); - this.AddressVersion = section.GetValue("AddressVersion", (byte)0x35); - IConfigurationSection section_sc = section.GetSection("StandbyCommittee"); - if (section_sc.Exists()) - this.StandbyCommittee = section_sc.GetChildren().Select(p => p.Get()).ToArray(); - else - this.StandbyCommittee = new[] - { - //Validators - "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", - "02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", - "03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", - "02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", - "024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", - "02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", - "02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", - - //Other Members - "023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", - "03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", - "03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", - "03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", - "02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", - "03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", - "0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", - "020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", - "0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", - "03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", - "02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", - "0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", - "03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", - "02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a" - }; - this.CommitteeMembersCount = StandbyCommittee.Length; - this.ValidatorsCount = section.GetValue("ValidatorsCount", (byte)7); - IConfigurationSection section_sl = section.GetSection("SeedList"); - if (section_sl.Exists()) - this.SeedList = section_sl.GetChildren().Select(p => p.Get()).ToArray(); - else - this.SeedList = new[] - { - "seed1.neo.org:10333", - "seed2.neo.org:10333", - "seed3.neo.org:10333", - "seed4.neo.org:10333", - "seed5.neo.org:10333" - }; - this.MillisecondsPerBlock = section.GetValue("MillisecondsPerBlock", 15000u); - this.MemoryPoolMaxTransactions = Math.Max(1, section.GetValue("MemoryPoolMaxTransactions", 50_000)); - this.MaxTraceableBlocks = section.GetValue("MaxTraceableBlocks", 2_102_400u);// 365 days - IConfigurationSection section_na = section.GetSection("NativeActivations"); - if (section_na.Exists()) - this.NativeActivations = section_na.GetChildren().ToDictionary((a) => a.Key, b => uint.Parse(b.Value)); - else - this.NativeActivations = new Dictionary(); + IConfigurationRoot config = new ConfigurationBuilder().AddJsonFile(path, optional).Build(); + IConfigurationSection section = config.GetSection("ProtocolConfiguration"); + return Load(section); + } + /// + /// Loads the with the specified . + /// + /// The to be loaded. + /// The loaded . + public static ProtocolSettings Load(IConfigurationSection section) + { + return new ProtocolSettings + { + Magic = section.GetValue("Magic", Default.Magic), + AddressVersion = section.GetValue("AddressVersion", Default.AddressVersion), + StandbyCommittee = section.GetSection("StandbyCommittee").Exists() + ? section.GetSection("StandbyCommittee").GetChildren().Select(p => ECPoint.Parse(p.Get(), ECCurve.Secp256r1)).ToArray() + : Default.StandbyCommittee, + ValidatorsCount = section.GetValue("ValidatorsCount", Default.ValidatorsCount), + SeedList = section.GetSection("SeedList").Exists() + ? section.GetSection("SeedList").GetChildren().Select(p => p.Get()).ToArray() + : Default.SeedList, + MillisecondsPerBlock = section.GetValue("MillisecondsPerBlock", Default.MillisecondsPerBlock), + MaxTransactionsPerBlock = section.GetValue("MaxTransactionsPerBlock", Default.MaxTransactionsPerBlock), + MemoryPoolMaxTransactions = section.GetValue("MemoryPoolMaxTransactions", Default.MemoryPoolMaxTransactions), + MaxTraceableBlocks = section.GetValue("MaxTraceableBlocks", Default.MaxTraceableBlocks), + NativeUpdateHistory = section.GetSection("NativeUpdateHistory").Exists() + ? section.GetSection("NativeUpdateHistory").GetChildren().ToDictionary(p => p.Key, p => p.GetChildren().Select(q => uint.Parse(q.Value)).ToArray()) + : Default.NativeUpdateHistory + }; } } } diff --git a/src/neo/SmartContract/ApplicationEngine.Binary.cs b/src/neo/SmartContract/ApplicationEngine.Binary.cs deleted file mode 100644 index cfc8197bd6..0000000000 --- a/src/neo/SmartContract/ApplicationEngine.Binary.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Neo.Cryptography; -using Neo.VM.Types; -using System; -using System.Globalization; -using System.Numerics; -using static System.Convert; - -namespace Neo.SmartContract -{ - partial class ApplicationEngine - { - public static readonly InteropDescriptor System_Binary_Serialize = Register("System.Binary.Serialize", nameof(BinarySerialize), 1 << 12, CallFlags.None); - public static readonly InteropDescriptor System_Binary_Deserialize = Register("System.Binary.Deserialize", nameof(BinaryDeserialize), 1 << 14, CallFlags.None); - public static readonly InteropDescriptor System_Binary_Base64Encode = Register("System.Binary.Base64Encode", nameof(Base64Encode), 1 << 12, CallFlags.None); - public static readonly InteropDescriptor System_Binary_Base64Decode = Register("System.Binary.Base64Decode", nameof(Base64Decode), 1 << 12, CallFlags.None); - public static readonly InteropDescriptor System_Binary_Base58Encode = Register("System.Binary.Base58Encode", nameof(Base58Encode), 1 << 12, CallFlags.None); - public static readonly InteropDescriptor System_Binary_Base58Decode = Register("System.Binary.Base58Decode", nameof(Base58Decode), 1 << 12, CallFlags.None); - public static readonly InteropDescriptor System_Binary_Itoa = Register("System.Binary.Itoa", nameof(Itoa), 1 << 12, CallFlags.None); - public static readonly InteropDescriptor System_Binary_Atoi = Register("System.Binary.Atoi", nameof(Atoi), 1 << 12, CallFlags.None); - - protected internal byte[] BinarySerialize(StackItem item) - { - return BinarySerializer.Serialize(item, Limits.MaxItemSize); - } - - protected internal StackItem BinaryDeserialize(byte[] data) - { - return BinarySerializer.Deserialize(data, Limits.MaxStackSize, Limits.MaxItemSize, ReferenceCounter); - } - - protected internal string Itoa(BigInteger value, int @base) - { - return @base switch - { - 10 => value.ToString(), - 16 => value.ToString("x"), - _ => throw new ArgumentOutOfRangeException(nameof(@base)) - }; - } - - protected internal BigInteger Atoi(string value, int @base) - { - return @base switch - { - 10 => BigInteger.Parse(value), - 16 => BigInteger.Parse(value, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture), - _ => throw new ArgumentOutOfRangeException(nameof(@base)) - }; - } - - protected internal string Base64Encode(byte[] data) - { - return ToBase64String(data); - } - - protected internal byte[] Base64Decode(string s) - { - return FromBase64String(s); - } - - protected internal string Base58Encode(byte[] data) - { - return Base58.Encode(data); - } - - protected internal byte[] Base58Decode(string s) - { - return Base58.Decode(s); - } - } -} diff --git a/src/neo/SmartContract/ApplicationEngine.Contract.cs b/src/neo/SmartContract/ApplicationEngine.Contract.cs index c52527bb6b..4a9fdc0788 100644 --- a/src/neo/SmartContract/ApplicationEngine.Contract.cs +++ b/src/neo/SmartContract/ApplicationEngine.Contract.cs @@ -1,5 +1,4 @@ using Neo.Cryptography.ECC; -using Neo.Network.P2P.Payloads; using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; using Neo.VM.Types; @@ -10,18 +9,56 @@ namespace Neo.SmartContract { partial class ApplicationEngine { - public static readonly InteropDescriptor System_Contract_Call = Register("System.Contract.Call", nameof(CallContract), 1 << 15, CallFlags.AllowCall); + /// + /// The of System.Contract.Call. + /// Use it to call another contract dynamically. + /// + public static readonly InteropDescriptor System_Contract_Call = Register("System.Contract.Call", nameof(CallContract), 1 << 15, CallFlags.ReadStates | CallFlags.AllowCall); + + /// + /// The of System.Contract.CallNative. + /// + /// Note: It is for internal use only. Do not use it directly in smart contracts. public static readonly InteropDescriptor System_Contract_CallNative = Register("System.Contract.CallNative", nameof(CallNativeContract), 0, CallFlags.None); - public static readonly InteropDescriptor System_Contract_IsStandard = Register("System.Contract.IsStandard", nameof(IsStandardContract), 1 << 10, CallFlags.ReadStates); + + /// + /// The of System.Contract.GetCallFlags. + /// Gets the of the current context. + /// public static readonly InteropDescriptor System_Contract_GetCallFlags = Register("System.Contract.GetCallFlags", nameof(GetCallFlags), 1 << 10, CallFlags.None); + /// - /// Calculate corresponding account scripthash for given public key - /// Warning: check first that input public key is valid, before creating the script. + /// The of System.Contract.CreateStandardAccount. + /// Calculates corresponding account scripthash for the given public key. /// public static readonly InteropDescriptor System_Contract_CreateStandardAccount = Register("System.Contract.CreateStandardAccount", nameof(CreateStandardAccount), 1 << 8, CallFlags.None); - public static readonly InteropDescriptor System_Contract_NativeOnPersist = Register("System.Contract.NativeOnPersist", nameof(NativeOnPersist), 0, CallFlags.WriteStates); - public static readonly InteropDescriptor System_Contract_NativePostPersist = Register("System.Contract.NativePostPersist", nameof(NativePostPersist), 0, CallFlags.WriteStates); + /// + /// The of System.Contract.CreateMultisigAccount. + /// Calculates corresponding multisig account scripthash for the given public keys. + /// + public static readonly InteropDescriptor System_Contract_CreateMultisigAccount = Register("System.Contract.CreateMultisigAccount", nameof(CreateMultisigAccount), 1 << 8, CallFlags.None); + + /// + /// The of System.Contract.NativeOnPersist. + /// + /// Note: It is for internal use only. Do not use it directly in smart contracts. + public static readonly InteropDescriptor System_Contract_NativeOnPersist = Register("System.Contract.NativeOnPersist", nameof(NativeOnPersist), 0, CallFlags.States); + + /// + /// The of System.Contract.NativePostPersist. + /// + /// Note: It is for internal use only. Do not use it directly in smart contracts. + public static readonly InteropDescriptor System_Contract_NativePostPersist = Register("System.Contract.NativePostPersist", nameof(NativePostPersist), 0, CallFlags.States); + + /// + /// The implementation of System.Contract.Call. + /// Use it to call another contract dynamically. + /// + /// The hash of the contract to be called. + /// The method of the contract to be called. + /// The to be used to call the contract. + /// The arguments to be used. protected internal void CallContract(UInt160 contractHash, string method, CallFlags callFlags, Array args) { if (method.StartsWith('_')) throw new ArgumentException($"Invalid Method Name: {method}"); @@ -38,67 +75,104 @@ protected internal void CallContract(UInt160 contractHash, string method, CallFl CallContractInternal(contract, md, callFlags, hasReturnValue, args); } - protected internal void CallNativeContract(int id) - { - NativeContract contract = NativeContract.GetContract(id); - if (contract is null || contract.ActiveBlockIndex > NativeContract.Ledger.CurrentIndex(Snapshot)) - throw new InvalidOperationException(); - contract.Invoke(this); - } - - protected internal bool IsStandardContract(UInt160 hash) + /// + /// The implementation of System.Contract.CallNative. + /// Calls to a native contract. + /// + /// The version of the native contract to be called. + protected internal void CallNativeContract(byte version) { - ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, hash); - - // It's a stored contract - - if (contract != null) return contract.Script.IsStandardContract(); - - // Try to find it in the transaction - - if (ScriptContainer is Transaction tx) - { - foreach (var witness in tx.Witnesses) - { - if (witness.ScriptHash == hash) - { - return witness.VerificationScript.IsStandardContract(); - } - } - } - - // It's not possible to determine if it's standard - - return false; + NativeContract contract = NativeContract.GetContract(CurrentScriptHash); + if (contract is null) + throw new InvalidOperationException("It is not allowed to use \"System.Contract.CallNative\" directly."); + uint[] updates = ProtocolSettings.NativeUpdateHistory[contract.Name]; + if (updates.Length == 0) + throw new InvalidOperationException($"The native contract {contract.Name} is not active."); + if (updates[0] > NativeContract.Ledger.CurrentIndex(Snapshot)) + throw new InvalidOperationException($"The native contract {contract.Name} is not active."); + contract.Invoke(this, version); } + /// + /// The implementation of System.Contract.GetCallFlags. + /// Gets the of the current context. + /// + /// The of the current context. protected internal CallFlags GetCallFlags() { var state = CurrentContext.GetState(); return state.CallFlags; } - protected internal UInt160 CreateStandardAccount(ECPoint pubKey) + /// + /// The implementation of System.Contract.CreateStandardAccount. + /// Calculates corresponding account scripthash for the given public key. + /// + /// The public key of the account. + /// The hash of the account. + internal protected static UInt160 CreateStandardAccount(ECPoint pubKey) { return Contract.CreateSignatureRedeemScript(pubKey).ToScriptHash(); } - protected internal void NativeOnPersist() + /// + /// The implementation of System.Contract.CreateMultisigAccount. + /// Calculates corresponding multisig account scripthash for the given public keys. + /// + /// The minimum number of correct signatures that need to be provided in order for the verification to pass. + /// The public keys of the account. + /// The hash of the account. + internal protected static UInt160 CreateMultisigAccount(int m, ECPoint[] pubKeys) + { + return Contract.CreateMultiSigRedeemScript(m, pubKeys).ToScriptHash(); + } + + /// + /// The implementation of System.Contract.NativeOnPersist. + /// Calls to the of all native contracts. + /// + protected internal async void NativeOnPersist() { - if (Trigger != TriggerType.OnPersist) - throw new InvalidOperationException(); - foreach (NativeContract contract in NativeContract.Contracts) - if (contract.ActiveBlockIndex <= PersistingBlock.Index) - contract.OnPersist(this); + try + { + if (Trigger != TriggerType.OnPersist) + throw new InvalidOperationException(); + foreach (NativeContract contract in NativeContract.Contracts) + { + uint[] updates = ProtocolSettings.NativeUpdateHistory[contract.Name]; + if (updates.Length == 0) continue; + if (updates[0] <= PersistingBlock.Index) + await contract.OnPersist(this); + } + } + catch (Exception ex) + { + Throw(ex); + } } - protected internal void NativePostPersist() + /// + /// The implementation of System.Contract.NativePostPersist. + /// Calls to the of all native contracts. + /// + protected internal async void NativePostPersist() { - if (Trigger != TriggerType.PostPersist) - throw new InvalidOperationException(); - foreach (NativeContract contract in NativeContract.Contracts) - if (contract.ActiveBlockIndex <= PersistingBlock.Index) - contract.PostPersist(this); + try + { + if (Trigger != TriggerType.PostPersist) + throw new InvalidOperationException(); + foreach (NativeContract contract in NativeContract.Contracts) + { + uint[] updates = ProtocolSettings.NativeUpdateHistory[contract.Name]; + if (updates.Length == 0) continue; + if (updates[0] <= PersistingBlock.Index) + await contract.PostPersist(this); + } + } + catch (Exception ex) + { + Throw(ex); + } } } } diff --git a/src/neo/SmartContract/ApplicationEngine.Crypto.cs b/src/neo/SmartContract/ApplicationEngine.Crypto.cs index e3988534cf..688f985d60 100644 --- a/src/neo/SmartContract/ApplicationEngine.Crypto.cs +++ b/src/neo/SmartContract/ApplicationEngine.Crypto.cs @@ -1,66 +1,41 @@ using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.Network.P2P; -using Neo.Network.P2P.Payloads; -using Neo.VM.Types; using System; namespace Neo.SmartContract { partial class ApplicationEngine { - public const long ECDsaVerifyPrice = 1 << 15; + /// + /// The price of Neo.Crypto.CheckSig. + /// + public const long CheckSigPrice = 1 << 15; - public static readonly InteropDescriptor Neo_Crypto_RIPEMD160 = Register("Neo.Crypto.RIPEMD160", nameof(RIPEMD160), 1 << 15, CallFlags.None); - public static readonly InteropDescriptor Neo_Crypto_SHA256 = Register("Neo.Crypto.SHA256", nameof(Sha256), 1 << 15, CallFlags.None); - public static readonly InteropDescriptor Neo_Crypto_VerifyWithECDsaSecp256r1 = Register("Neo.Crypto.VerifyWithECDsaSecp256r1", nameof(VerifyWithECDsaSecp256r1), ECDsaVerifyPrice, CallFlags.None); - public static readonly InteropDescriptor Neo_Crypto_VerifyWithECDsaSecp256k1 = Register("Neo.Crypto.VerifyWithECDsaSecp256k1", nameof(VerifyWithECDsaSecp256k1), ECDsaVerifyPrice, CallFlags.None); - public static readonly InteropDescriptor Neo_Crypto_CheckMultisigWithECDsaSecp256r1 = Register("Neo.Crypto.CheckMultisigWithECDsaSecp256r1", nameof(CheckMultisigWithECDsaSecp256r1), 0, CallFlags.None); - public static readonly InteropDescriptor Neo_Crypto_CheckMultisigWithECDsaSecp256k1 = Register("Neo.Crypto.CheckMultisigWithECDsaSecp256k1", nameof(CheckMultisigWithECDsaSecp256k1), 0, CallFlags.None); + /// + /// The of Neo.Crypto.CheckSig. + /// Checks the signature for the current script container. + /// + public static readonly InteropDescriptor Neo_Crypto_CheckSig = Register("Neo.Crypto.CheckSig", nameof(CheckSig), CheckSigPrice, CallFlags.None); - protected internal byte[] RIPEMD160(StackItem item) - { - ReadOnlySpan value = item switch - { - InteropInterface _interface => _interface.GetInterface().GetHashData(), - Null _ => ScriptContainer.GetHashData(), - _ => item.GetSpan() - }; - return value.RIPEMD160(); - } + /// + /// The of Neo.Crypto.CheckMultisig. + /// Checks the signatures for the current script container. + /// + public static readonly InteropDescriptor Neo_Crypto_CheckMultisig = Register("Neo.Crypto.CheckMultisig", nameof(CheckMultisig), 0, CallFlags.None); - protected internal byte[] Sha256(StackItem item) + /// + /// The implementation of Neo.Crypto.CheckSig. + /// Checks the signature for the current script container. + /// + /// The public key of the account. + /// The signature of the current script container. + /// if the signature is valid; otherwise, . + protected internal bool CheckSig(byte[] pubkey, byte[] signature) { - ReadOnlySpan value = item switch - { - InteropInterface _interface => _interface.GetInterface().GetHashData(), - Null _ => ScriptContainer.GetHashData(), - _ => item.GetSpan() - }; - return value.Sha256(); - } - - protected internal bool VerifyWithECDsaSecp256r1(StackItem item, byte[] pubkey, byte[] signature) - { - return VerifyWithECDsa(item, pubkey, signature, ECCurve.Secp256r1); - } - - protected internal bool VerifyWithECDsaSecp256k1(StackItem item, byte[] pubkey, byte[] signature) - { - return VerifyWithECDsa(item, pubkey, signature, ECCurve.Secp256k1); - } - - private bool VerifyWithECDsa(StackItem item, byte[] pubkey, byte[] signature, ECCurve curve) - { - ReadOnlySpan message = item switch - { - InteropInterface _interface => _interface.GetInterface().GetHashData(), - Null _ => ScriptContainer.GetHashData(), - _ => item.GetSpan() - }; try { - return Crypto.VerifySignature(message, signature, pubkey, curve); + return Crypto.VerifySignature(ScriptContainer.GetSignData(ProtocolSettings.Magic), signature, pubkey, ECCurve.Secp256r1); } catch (ArgumentException) { @@ -68,32 +43,24 @@ private bool VerifyWithECDsa(StackItem item, byte[] pubkey, byte[] signature, EC } } - protected internal bool CheckMultisigWithECDsaSecp256r1(StackItem item, byte[][] pubkeys, byte[][] signatures) - { - return CheckMultiSigWithECDsa(item, pubkeys, signatures, ECCurve.Secp256r1); - } - - protected internal bool CheckMultisigWithECDsaSecp256k1(StackItem item, byte[][] pubkeys, byte[][] signatures) - { - return CheckMultiSigWithECDsa(item, pubkeys, signatures, ECCurve.Secp256k1); - } - - private bool CheckMultiSigWithECDsa(StackItem item0, byte[][] pubkeys, byte[][] signatures, ECCurve curve) + /// + /// The implementation of Neo.Crypto.CheckMultisig. + /// Checks the signatures for the current script container. + /// + /// The public keys of the account. + /// The signatures of the current script container. + /// if the signatures are valid; otherwise, . + protected internal bool CheckMultisig(byte[][] pubkeys, byte[][] signatures) { + byte[] message = ScriptContainer.GetSignData(ProtocolSettings.Magic); int m = signatures.Length, n = pubkeys.Length; - ReadOnlySpan message = item0 switch - { - InteropInterface _interface => _interface.GetInterface().GetHashData(), - Null _ => ScriptContainer.GetHashData(), - _ => item0.GetSpan() - }; if (n == 0 || m == 0 || m > n) throw new ArgumentException(); - AddGas(ECDsaVerifyPrice * n * exec_fee_factor); + AddGas(CheckSigPrice * n * exec_fee_factor); try { for (int i = 0, j = 0; i < m && j < n;) { - if (Crypto.VerifySignature(message, signatures[i], pubkeys[j], curve)) + if (Crypto.VerifySignature(message, signatures[i], pubkeys[j], ECCurve.Secp256r1)) i++; j++; if (m - i > n - j) diff --git a/src/neo/SmartContract/ApplicationEngine.Iterator.cs b/src/neo/SmartContract/ApplicationEngine.Iterator.cs index f8f6be2af7..60efbde6e2 100644 --- a/src/neo/SmartContract/ApplicationEngine.Iterator.cs +++ b/src/neo/SmartContract/ApplicationEngine.Iterator.cs @@ -7,10 +7,30 @@ namespace Neo.SmartContract { partial class ApplicationEngine { + /// + /// The of System.Iterator.Create. + /// Creates an with the specified . + /// public static readonly InteropDescriptor System_Iterator_Create = Register("System.Iterator.Create", nameof(CreateIterator), 1 << 4, CallFlags.None); + + /// + /// The of System.Iterator.Next. + /// Advances the iterator to the next element of the collection. + /// public static readonly InteropDescriptor System_Iterator_Next = Register("System.Iterator.Next", nameof(IteratorNext), 1 << 15, CallFlags.None); + + /// + /// The of System.Iterator.Value. + /// Gets the element in the collection at the current position of the iterator. + /// public static readonly InteropDescriptor System_Iterator_Value = Register("System.Iterator.Value", nameof(IteratorValue), 1 << 4, CallFlags.None); + /// + /// The implementation of System.Iterator.Create. + /// Creates an with the specified . + /// + /// The wrapped by the iterator. + /// The created iterator. protected internal IIterator CreateIterator(StackItem item) { return item switch @@ -19,16 +39,28 @@ protected internal IIterator CreateIterator(StackItem item) Map map => new MapWrapper(map, ReferenceCounter), VM.Types.Buffer buffer => new ByteArrayWrapper(buffer), PrimitiveType primitive => new ByteArrayWrapper(primitive), - _ => throw new ArgumentException() + _ => throw new ArgumentException(null, nameof(item)) }; } - protected internal bool IteratorNext(IIterator iterator) + /// + /// The implementation of System.Iterator.Next. + /// Advances the iterator to the next element of the collection. + /// + /// The iterator to be advanced. + /// if the iterator was successfully advanced to the next element; if the iterator has passed the end of the collection. + internal protected static bool IteratorNext(IIterator iterator) { return iterator.Next(); } - protected internal StackItem IteratorValue(IIterator iterator) + /// + /// The implementation of System.Iterator.Value. + /// Gets the element in the collection at the current position of the iterator. + /// + /// The iterator to be used. + /// The element in the collection at the current position of the iterator. + internal protected static StackItem IteratorValue(IIterator iterator) { return iterator.Value(); } diff --git a/src/neo/SmartContract/ApplicationEngine.Json.cs b/src/neo/SmartContract/ApplicationEngine.Json.cs deleted file mode 100644 index 40676ed580..0000000000 --- a/src/neo/SmartContract/ApplicationEngine.Json.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Neo.IO.Json; -using Neo.VM.Types; - -namespace Neo.SmartContract -{ - partial class ApplicationEngine - { - public static readonly InteropDescriptor System_Json_Serialize = Register("System.Json.Serialize", nameof(JsonSerialize), 1 << 12, CallFlags.None); - public static readonly InteropDescriptor System_Json_Deserialize = Register("System.Json.Deserialize", nameof(JsonDeserialize), 1 << 14, CallFlags.None); - - protected internal byte[] JsonSerialize(StackItem item) - { - return JsonSerializer.SerializeToByteArray(item, Limits.MaxItemSize); - } - - protected internal StackItem JsonDeserialize(byte[] json) - { - return JsonSerializer.Deserialize(JObject.Parse(json, 10), ReferenceCounter); - } - } -} diff --git a/src/neo/SmartContract/ApplicationEngine.OpCodePrices.cs b/src/neo/SmartContract/ApplicationEngine.OpCodePrices.cs index a31edaa6b5..f86efd2534 100644 --- a/src/neo/SmartContract/ApplicationEngine.OpCodePrices.cs +++ b/src/neo/SmartContract/ApplicationEngine.OpCodePrices.cs @@ -5,6 +5,9 @@ namespace Neo.SmartContract { partial class ApplicationEngine { + /// + /// The prices of all the opcodes. + /// public static readonly IReadOnlyDictionary OpCodePrices = new Dictionary { [OpCode.PUSHINT8] = 1 << 0, @@ -156,6 +159,8 @@ partial class ApplicationEngine [OpCode.MUL] = 1 << 3, [OpCode.DIV] = 1 << 3, [OpCode.MOD] = 1 << 3, + [OpCode.POW] = 1 << 6, + [OpCode.SQRT] = 1 << 11, [OpCode.SHL] = 1 << 3, [OpCode.SHR] = 1 << 3, [OpCode.NOT] = 1 << 2, @@ -171,8 +176,8 @@ partial class ApplicationEngine [OpCode.MIN] = 1 << 3, [OpCode.MAX] = 1 << 3, [OpCode.WITHIN] = 1 << 3, - [OpCode.PACK] = 1 << 9, - [OpCode.UNPACK] = 1 << 9, + [OpCode.PACK] = 1 << 11, + [OpCode.UNPACK] = 1 << 11, [OpCode.NEWARRAY0] = 1 << 4, [OpCode.NEWARRAY] = 1 << 9, [OpCode.NEWARRAY_T] = 1 << 9, @@ -192,7 +197,7 @@ partial class ApplicationEngine [OpCode.POPITEM] = 1 << 4, [OpCode.ISNULL] = 1 << 1, [OpCode.ISTYPE] = 1 << 1, - [OpCode.CONVERT] = 1 << 11, + [OpCode.CONVERT] = 1 << 13, }; } } diff --git a/src/neo/SmartContract/ApplicationEngine.Runtime.cs b/src/neo/SmartContract/ApplicationEngine.Runtime.cs index 5a1edd1598..cfb980c4ed 100644 --- a/src/neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/neo/SmartContract/ApplicationEngine.Runtime.cs @@ -2,6 +2,7 @@ using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; +using Neo.VM.Types; using System; using System.Collections.Generic; using System.IO; @@ -12,49 +13,147 @@ namespace Neo.SmartContract { partial class ApplicationEngine { + /// + /// The maximum length of event name. + /// public const int MaxEventName = 32; + + /// + /// The maximum size of notification objects. + /// public const int MaxNotificationSize = 1024; + /// + /// The of System.Runtime.Platform. + /// Gets the name of the current platform. + /// public static readonly InteropDescriptor System_Runtime_Platform = Register("System.Runtime.Platform", nameof(GetPlatform), 1 << 3, CallFlags.None); + + /// + /// The of System.Runtime.GetTrigger. + /// Gets the trigger of the execution. + /// public static readonly InteropDescriptor System_Runtime_GetTrigger = Register("System.Runtime.GetTrigger", nameof(Trigger), 1 << 3, CallFlags.None); + + /// + /// The of System.Runtime.GetTime. + /// Gets the timestamp of the current block. + /// public static readonly InteropDescriptor System_Runtime_GetTime = Register("System.Runtime.GetTime", nameof(GetTime), 1 << 3, CallFlags.None); + + /// + /// The of System.Runtime.GetScriptContainer. + /// Gets the current script container. + /// public static readonly InteropDescriptor System_Runtime_GetScriptContainer = Register("System.Runtime.GetScriptContainer", nameof(GetScriptContainer), 1 << 3, CallFlags.None); + + /// + /// The of System.Runtime.GetExecutingScriptHash. + /// Gets the script hash of the current context. + /// public static readonly InteropDescriptor System_Runtime_GetExecutingScriptHash = Register("System.Runtime.GetExecutingScriptHash", nameof(CurrentScriptHash), 1 << 4, CallFlags.None); + + /// + /// The of System.Runtime.GetCallingScriptHash. + /// Gets the script hash of the calling contract. + /// public static readonly InteropDescriptor System_Runtime_GetCallingScriptHash = Register("System.Runtime.GetCallingScriptHash", nameof(CallingScriptHash), 1 << 4, CallFlags.None); + + /// + /// The of System.Runtime.GetEntryScriptHash. + /// Gets the script hash of the entry context. + /// public static readonly InteropDescriptor System_Runtime_GetEntryScriptHash = Register("System.Runtime.GetEntryScriptHash", nameof(EntryScriptHash), 1 << 4, CallFlags.None); + + /// + /// The of System.Runtime.CheckWitness. + /// Determines whether the specified account has witnessed the current transaction. + /// public static readonly InteropDescriptor System_Runtime_CheckWitness = Register("System.Runtime.CheckWitness", nameof(CheckWitness), 1 << 10, CallFlags.None); + + /// + /// The of System.Runtime.GetInvocationCounter. + /// Gets the number of times the current contract has been called during the execution. + /// public static readonly InteropDescriptor System_Runtime_GetInvocationCounter = Register("System.Runtime.GetInvocationCounter", nameof(GetInvocationCounter), 1 << 4, CallFlags.None); + + /// + /// The of System.Runtime.Log. + /// Writes a log. + /// public static readonly InteropDescriptor System_Runtime_Log = Register("System.Runtime.Log", nameof(RuntimeLog), 1 << 15, CallFlags.AllowNotify); + + /// + /// The of System.Runtime.Notify. + /// Sends a notification. + /// public static readonly InteropDescriptor System_Runtime_Notify = Register("System.Runtime.Notify", nameof(RuntimeNotify), 1 << 15, CallFlags.AllowNotify); + + /// + /// The of System.Runtime.GetNotifications. + /// Gets the notifications sent by the specified contract during the execution. + /// public static readonly InteropDescriptor System_Runtime_GetNotifications = Register("System.Runtime.GetNotifications", nameof(GetNotifications), 1 << 8, CallFlags.None); + + /// + /// The of System.Runtime.GasLeft. + /// Gets the remaining GAS that can be spent in order to complete the execution. + /// public static readonly InteropDescriptor System_Runtime_GasLeft = Register("System.Runtime.GasLeft", nameof(GasLeft), 1 << 4, CallFlags.None); - protected internal string GetPlatform() + /// + /// The implementation of System.Runtime.Platform. + /// Gets the name of the current platform. + /// + /// It always returns "NEO". + internal protected static string GetPlatform() { return "NEO"; } + /// + /// The implementation of System.Runtime.GetTime. + /// Gets the timestamp of the current block. + /// + /// The timestamp of the current block. protected internal ulong GetTime() { return PersistingBlock.Timestamp; } - protected internal IInteroperable GetScriptContainer() + /// + /// The implementation of System.Runtime.GetScriptContainer. + /// Gets the current script container. + /// + /// The current script container. + protected internal StackItem GetScriptContainer() { - return ScriptContainer as IInteroperable; + if (ScriptContainer is not IInteroperable interop) throw new InvalidOperationException(); + return interop.ToStackItem(ReferenceCounter); } + /// + /// The implementation of System.Runtime.CheckWitness. + /// Determines whether the specified account has witnessed the current transaction. + /// + /// The hash or public key of the account. + /// if the account has witnessed the current transaction; otherwise, . protected internal bool CheckWitness(byte[] hashOrPubkey) { UInt160 hash = hashOrPubkey.Length switch { 20 => new UInt160(hashOrPubkey), 33 => Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(hashOrPubkey, ECCurve.Secp256r1)).ToScriptHash(), - _ => throw new ArgumentException() + _ => throw new ArgumentException(null, nameof(hashOrPubkey)) }; return CheckWitnessInternal(hash); } + /// + /// Determines whether the specified account has witnessed the current transaction. + /// + /// The hash of the account. + /// if the account has witnessed the current transaction; otherwise, . protected internal bool CheckWitnessInternal(UInt160 hash) { if (hash.Equals(CallingScriptHash)) return true; @@ -89,8 +188,7 @@ protected internal bool CheckWitnessInternal(UInt160 hash) { // Check allow state callflag - if (!CurrentContext.GetState().CallFlags.HasFlag(CallFlags.ReadStates)) - throw new InvalidOperationException($"Cannot call this SYSCALL without the flag AllowStates."); + ValidateCallFlags(CallFlags.ReadStates); var contract = NativeContract.ContractManagement.GetContract(Snapshot, CallingScriptHash); // check if current group is the required one @@ -102,8 +200,7 @@ protected internal bool CheckWitnessInternal(UInt160 hash) // Check allow state callflag - if (!CurrentContext.GetState().CallFlags.HasFlag(CallFlags.ReadStates)) - throw new InvalidOperationException($"Cannot call this SYSCALL without the flag AllowStates."); + ValidateCallFlags(CallFlags.ReadStates); // only for non-Transaction types (Block, etc) @@ -111,6 +208,11 @@ protected internal bool CheckWitnessInternal(UInt160 hash) return hashes_for_verifying.Contains(hash); } + /// + /// The implementation of System.Runtime.GetInvocationCounter. + /// Gets the number of times the current contract has been called during the execution. + /// + /// The number of times the current contract has been called during the execution. protected internal int GetInvocationCounter() { if (!invocationCounter.TryGetValue(CurrentScriptHash, out var counter)) @@ -120,32 +222,53 @@ protected internal int GetInvocationCounter() return counter; } + /// + /// The implementation of System.Runtime.Log. + /// Writes a log. + /// + /// The message of the log. protected internal void RuntimeLog(byte[] state) { - if (state.Length > MaxNotificationSize) throw new ArgumentException(); + if (state.Length > MaxNotificationSize) throw new ArgumentException(null, nameof(state)); string message = Utility.StrictUTF8.GetString(state); Log?.Invoke(this, new LogEventArgs(ScriptContainer, CurrentScriptHash, message)); } + /// + /// The implementation of System.Runtime.Notify. + /// Sends a notification. + /// + /// The name of the event. + /// The arguments of the event. protected internal void RuntimeNotify(byte[] eventName, Array state) { - if (eventName.Length > MaxEventName) throw new ArgumentException(); - using (MemoryStream ms = new MemoryStream(MaxNotificationSize)) - using (BinaryWriter writer = new BinaryWriter(ms)) - { - BinarySerializer.Serialize(writer, state, MaxNotificationSize); - } + if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName)); + using MemoryStream ms = new(MaxNotificationSize); + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); + BinarySerializer.Serialize(writer, state, MaxNotificationSize); SendNotification(CurrentScriptHash, Utility.StrictUTF8.GetString(eventName), state); } + /// + /// Sends a notification for the specified contract. + /// + /// The hash of the specified contract. + /// The name of the event. + /// The arguments of the event. protected internal void SendNotification(UInt160 hash, string eventName, Array state) { - NotifyEventArgs notification = new NotifyEventArgs(ScriptContainer, hash, eventName, (Array)state.DeepCopy()); + NotifyEventArgs notification = new(ScriptContainer, hash, eventName, (Array)state.DeepCopy()); Notify?.Invoke(this, notification); notifications ??= new List(); notifications.Add(notification); } + /// + /// The implementation of System.Runtime.GetNotifications. + /// Gets the notifications sent by the specified contract during the execution. + /// + /// The hash of the specified contract. It can be set to to get all notifications. + /// The notifications sent during the execution. protected internal NotifyEventArgs[] GetNotifications(UInt160 hash) { IEnumerable notifications = Notifications; diff --git a/src/neo/SmartContract/ApplicationEngine.Storage.cs b/src/neo/SmartContract/ApplicationEngine.Storage.cs index 17b62fba63..0b58859163 100644 --- a/src/neo/SmartContract/ApplicationEngine.Storage.cs +++ b/src/neo/SmartContract/ApplicationEngine.Storage.cs @@ -7,18 +7,63 @@ namespace Neo.SmartContract { partial class ApplicationEngine { + /// + /// The maximum size of storage keys. + /// public const int MaxStorageKeySize = 64; + + /// + /// The maximum size of storage values. + /// public const int MaxStorageValueSize = ushort.MaxValue; + /// + /// The of System.Storage.GetContext. + /// Gets the storage context for the current contract. + /// public static readonly InteropDescriptor System_Storage_GetContext = Register("System.Storage.GetContext", nameof(GetStorageContext), 1 << 4, CallFlags.ReadStates); + + /// + /// The of System.Storage.GetReadOnlyContext. + /// Gets the readonly storage context for the current contract. + /// public static readonly InteropDescriptor System_Storage_GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", nameof(GetReadOnlyContext), 1 << 4, CallFlags.ReadStates); + + /// + /// The of System.Storage.AsReadOnly. + /// Converts the specified storage context to a new readonly storage context. + /// public static readonly InteropDescriptor System_Storage_AsReadOnly = Register("System.Storage.AsReadOnly", nameof(AsReadOnly), 1 << 4, CallFlags.ReadStates); + + /// + /// The of System.Storage.Get. + /// Gets the entry with the specified key from the storage. + /// public static readonly InteropDescriptor System_Storage_Get = Register("System.Storage.Get", nameof(Get), 1 << 15, CallFlags.ReadStates); + + /// + /// The of System.Storage.Find. + /// Finds the entries from the storage. + /// public static readonly InteropDescriptor System_Storage_Find = Register("System.Storage.Find", nameof(Find), 1 << 15, CallFlags.ReadStates); - public static readonly InteropDescriptor System_Storage_Put = Register("System.Storage.Put", nameof(Put), 0, CallFlags.WriteStates); - public static readonly InteropDescriptor System_Storage_PutEx = Register("System.Storage.PutEx", nameof(PutEx), 0, CallFlags.WriteStates); - public static readonly InteropDescriptor System_Storage_Delete = Register("System.Storage.Delete", nameof(Delete), 0, CallFlags.WriteStates); + /// + /// The of System.Storage.Put. + /// Puts a new entry into the storage. + /// + public static readonly InteropDescriptor System_Storage_Put = Register("System.Storage.Put", nameof(Put), 1 << 15, CallFlags.WriteStates); + + /// + /// The of System.Storage.Delete. + /// Deletes an entry from the storage. + /// + public static readonly InteropDescriptor System_Storage_Delete = Register("System.Storage.Delete", nameof(Delete), 1 << 15, CallFlags.WriteStates); + + /// + /// The implementation of System.Storage.GetContext. + /// Gets the storage context for the current contract. + /// + /// The storage context for the current contract. protected internal StorageContext GetStorageContext() { ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); @@ -29,6 +74,11 @@ protected internal StorageContext GetStorageContext() }; } + /// + /// The implementation of System.Storage.GetReadOnlyContext. + /// Gets the readonly storage context for the current contract. + /// + /// The storage context for the current contract. protected internal StorageContext GetReadOnlyContext() { ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); @@ -39,7 +89,13 @@ protected internal StorageContext GetReadOnlyContext() }; } - protected internal StorageContext AsReadOnly(StorageContext context) + /// + /// The implementation of System.Storage.AsReadOnly. + /// Converts the specified storage context to a new readonly storage context. + /// + /// The storage context to convert. + /// The readonly storage context. + internal protected static StorageContext AsReadOnly(StorageContext context) { if (!context.IsReadOnly) context = new StorageContext @@ -50,6 +106,13 @@ protected internal StorageContext AsReadOnly(StorageContext context) return context; } + /// + /// The implementation of System.Storage.Get. + /// Gets the entry with the specified key from the storage. + /// + /// The context of the storage. + /// The key of the entry. + /// The value of the entry. Or if the entry doesn't exist. protected internal byte[] Get(StorageContext context, byte[] key) { return Snapshot.TryGet(new StorageKey @@ -59,39 +122,44 @@ protected internal byte[] Get(StorageContext context, byte[] key) })?.Value; } + /// + /// The implementation of System.Storage.Find. + /// Finds the entries from the storage. + /// + /// The context of the storage. + /// The prefix of keys to find. + /// The options of the search. + /// An iterator for the results. protected internal IIterator Find(StorageContext context, byte[] prefix, FindOptions options) { if ((options & ~FindOptions.All) != 0) throw new ArgumentOutOfRangeException(nameof(options)); if (options.HasFlag(FindOptions.KeysOnly) && (options.HasFlag(FindOptions.ValuesOnly) || options.HasFlag(FindOptions.DeserializeValues) || options.HasFlag(FindOptions.PickField0) || options.HasFlag(FindOptions.PickField1))) - throw new ArgumentException(); + throw new ArgumentException(null, nameof(options)); if (options.HasFlag(FindOptions.ValuesOnly) && (options.HasFlag(FindOptions.KeysOnly) || options.HasFlag(FindOptions.RemovePrefix))) - throw new ArgumentException(); + throw new ArgumentException(null, nameof(options)); if (options.HasFlag(FindOptions.PickField0) && options.HasFlag(FindOptions.PickField1)) - throw new ArgumentException(); + throw new ArgumentException(null, nameof(options)); if ((options.HasFlag(FindOptions.PickField0) || options.HasFlag(FindOptions.PickField1)) && !options.HasFlag(FindOptions.DeserializeValues)) - throw new ArgumentException(); + throw new ArgumentException(null, nameof(options)); byte[] prefix_key = StorageKey.CreateSearchPrefix(context.Id, prefix); return new StorageIterator(Snapshot.Find(prefix_key).GetEnumerator(), options, ReferenceCounter); } + /// + /// The implementation of System.Storage.Put. + /// Puts a new entry into the storage. + /// + /// The context of the storage. + /// The key of the entry. + /// The value of the entry. protected internal void Put(StorageContext context, byte[] key, byte[] value) - { - PutExInternal(context, key, value, StorageFlags.None); - } - - protected internal void PutEx(StorageContext context, byte[] key, byte[] value, StorageFlags flags) - { - PutExInternal(context, key, value, flags); - } - - private void PutExInternal(StorageContext context, byte[] key, byte[] value, StorageFlags flags) { if (key.Length > MaxStorageKeySize || value.Length > MaxStorageValueSize || context.IsReadOnly) throw new ArgumentException(); int newDataSize; - StorageKey skey = new StorageKey + StorageKey skey = new() { Id = context.Id, Key = key @@ -104,32 +172,34 @@ private void PutExInternal(StorageContext context, byte[] key, byte[] value, Sto } else { - if (item.IsConstant) throw new InvalidOperationException(); if (value.Length == 0) - newDataSize = 1; + newDataSize = 0; else if (value.Length <= item.Value.Length) newDataSize = (value.Length - 1) / 4 + 1; + else if (item.Value.Length == 0) + newDataSize = value.Length; else newDataSize = (item.Value.Length - 1) / 4 + 1 + value.Length - item.Value.Length; } AddGas(newDataSize * StoragePrice); item.Value = value; - item.IsConstant = flags.HasFlag(StorageFlags.Constant); } + /// + /// The implementation of System.Storage.Delete. + /// Deletes an entry from the storage. + /// + /// The context of the storage. + /// The key of the entry. protected internal void Delete(StorageContext context, byte[] key) { - if (context.IsReadOnly) throw new ArgumentException(); - AddGas(StoragePrice); - StorageKey skey = new StorageKey + if (context.IsReadOnly) throw new ArgumentException(null, nameof(context)); + Snapshot.Delete(new StorageKey { Id = context.Id, Key = key - }; - if (Snapshot.TryGet(skey)?.IsConstant == true) - throw new InvalidOperationException(); - Snapshot.Delete(skey); + }); } } } diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 39c7f048cb..87ce6688e7 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -1,6 +1,5 @@ using Neo.IO; using Neo.IO.Json; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.Plugins; @@ -19,14 +18,24 @@ namespace Neo.SmartContract { + /// + /// A virtual machine used to execute smart contracts in the NEO system. + /// public partial class ApplicationEngine : ExecutionEngine { /// - /// This constant can be used for testing scripts. + /// The maximum cost that can be spent when a contract is executed in test mode. /// public const long TestModeGas = 20_00000000; + /// + /// Triggered when a contract calls System.Runtime.Notify. + /// public static event EventHandler Notify; + + /// + /// Triggered when a contract calls System.Runtime.Log. + /// public static event EventHandler Log; private static IApplicationEngineProvider applicationEngineProvider; @@ -34,35 +43,103 @@ public partial class ApplicationEngine : ExecutionEngine private readonly long gas_amount; private List notifications; private List disposables; - private readonly Dictionary invocationCounter = new Dictionary(); + private readonly Dictionary invocationCounter = new(); + private readonly Dictionary contractTasks = new(); private readonly uint exec_fee_factor; internal readonly uint StoragePrice; + /// + /// Gets the descriptors of all interoperable services available in NEO. + /// public static IReadOnlyDictionary Services => services; + private List Disposables => disposables ??= new List(); + + /// + /// The trigger of the execution. + /// public TriggerType Trigger { get; } + + /// + /// The container that containing the executed script. This field could be if the contract is invoked by system. + /// public IVerifiable ScriptContainer { get; } + + /// + /// The snapshot used to read or write data. + /// public DataCache Snapshot { get; } + + /// + /// The block being persisted. This field could be if the is . + /// public Block PersistingBlock { get; } + + /// + /// The used by the engine. + /// + public ProtocolSettings ProtocolSettings { get; } + + /// + /// GAS spent to execute. + /// public long GasConsumed { get; private set; } = 0; + + /// + /// The remaining GAS that can be spent in order to complete the execution. + /// public long GasLeft => gas_amount - GasConsumed; + + /// + /// The exception that caused the execution to terminate abnormally. This field could be if no exception is thrown. + /// public Exception FaultException { get; private set; } + + /// + /// The script hash of the current context. This field could be if no context is loaded to the engine. + /// public UInt160 CurrentScriptHash => CurrentContext?.GetScriptHash(); + + /// + /// The script hash of the calling contract. This field could be if the current context is the entry context. + /// public UInt160 CallingScriptHash => CurrentContext?.GetState().CallingScriptHash; + + /// + /// The script hash of the entry context. This field could be if no context is loaded to the engine. + /// public UInt160 EntryScriptHash => EntryContext?.GetScriptHash(); + + /// + /// The notifications sent during the execution. + /// public IReadOnlyList Notifications => notifications ?? (IReadOnlyList)Array.Empty(); - protected ApplicationEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, long gas) + /// + /// Initializes a new instance of the class. + /// + /// The trigger of the execution. + /// The container of the script. + /// The snapshot used by the engine during execution. + /// The block being persisted. It should be if the is . + /// The used by the engine. + /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + protected ApplicationEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas) { this.Trigger = trigger; this.ScriptContainer = container; this.Snapshot = snapshot; this.PersistingBlock = persistingBlock; + this.ProtocolSettings = settings; this.gas_amount = gas; this.exec_fee_factor = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultExecFeeFactor : NativeContract.Policy.GetExecFeeFactor(Snapshot); this.StoragePrice = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultStoragePrice : NativeContract.Policy.GetStoragePrice(Snapshot); } + /// + /// Adds GAS to and checks if it has exceeded the maximum limit. + /// + /// The amount of GAS to be added. protected internal void AddGas(long gas) { GasConsumed = checked(GasConsumed + gas); @@ -70,10 +147,15 @@ protected internal void AddGas(long gas) throw new InvalidOperationException("Insufficient GAS."); } - protected override void OnFault(Exception e) + protected override void OnFault(Exception ex) + { + FaultException = ex; + base.OnFault(ex); + } + + internal void Throw(Exception ex) { - FaultException = e; - base.OnFault(e); + OnFault(ex); } private ExecutionContext CallContractInternal(UInt160 contractHash, string method, CallFlags flags, bool hasReturnValue, StackItem[] args) @@ -89,7 +171,7 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe { if (method.Safe) { - flags &= ~CallFlags.WriteStates; + flags &= ~(CallFlags.WriteStates | CallFlags.AllowNotify); } else { @@ -119,37 +201,53 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe for (int i = args.Count - 1; i >= 0; i--) context_new.EvaluationStack.Push(args[i]); - if (NativeContract.IsNative(contract.Hash)) - context_new.EvaluationStack.Push(method.Name); return context_new; } - internal void CallFromNativeContract(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) + internal ContractTask CallFromNativeContract(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) { - ExecutionContext context_current = CurrentContext; ExecutionContext context_new = CallContractInternal(hash, method, CallFlags.All, false, args); ExecutionContextState state = context_new.GetState(); state.CallingScriptHash = callingScriptHash; - while (CurrentContext != context_current) - StepOut(); + ContractTask task = new(); + contractTasks.Add(context_new, task.GetAwaiter()); + return task; } - internal T CallFromNativeContract(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) + internal ContractTask CallFromNativeContract(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) { - ExecutionContext context_current = CurrentContext; ExecutionContext context_new = CallContractInternal(hash, method, CallFlags.All, true, args); ExecutionContextState state = context_new.GetState(); state.CallingScriptHash = callingScriptHash; - while (CurrentContext != context_current) - StepOut(); - return (T)Convert(Pop(), new InteropParameterDescriptor(typeof(T))); + ContractTask task = new(); + contractTasks.Add(context_new, task.GetAwaiter()); + return task; } - public static ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock = null, long gas = TestModeGas) + protected override void ContextUnloaded(ExecutionContext context) + { + base.ContextUnloaded(context); + if (!contractTasks.Remove(context, out var awaiter)) return; + if (UncaughtException is not null) + throw new VMUnhandledException(UncaughtException); + awaiter.SetResult(this); + } + + /// + /// Use the loaded to create a new instance of the class. If no is loaded, the constructor of will be called. + /// + /// The trigger of the execution. + /// The container of the script. + /// The snapshot used by the engine during execution. + /// The block being persisted. It should be if the is . + /// The used by the engine. + /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + /// The engine instance created. + public static ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock = null, ProtocolSettings settings = null, long gas = TestModeGas) { - return applicationEngineProvider?.Create(trigger, container, snapshot, persistingBlock, gas) - ?? new ApplicationEngine(trigger, container, snapshot, persistingBlock, gas); + return applicationEngineProvider?.Create(trigger, container, snapshot, persistingBlock, settings, gas) + ?? new ApplicationEngine(trigger, container, snapshot, persistingBlock, settings, gas); } protected override void LoadContext(ExecutionContext context) @@ -163,6 +261,13 @@ protected override void LoadContext(ExecutionContext context) base.LoadContext(context); } + /// + /// Loads a deployed contract to the invocation stack. If the _initialize method is found on the contract, loads it as well. + /// + /// The contract to be loaded. + /// The method of the contract to be called. + /// The used to call the method. + /// The loaded context. public ExecutionContext LoadContract(ContractState contract, ContractMethodDescriptor method, CallFlags callFlags) { ExecutionContext context = LoadScript(contract.Script, @@ -185,6 +290,14 @@ public ExecutionContext LoadContract(ContractState contract, ContractMethodDescr return context; } + /// + /// Loads a script to the invocation stack. + /// + /// The script to be loaded. + /// The number of return values of the script. + /// The initial position of the instruction pointer. + /// The action used to configure the state of the loaded context. + /// The loaded context. public ExecutionContext LoadScript(Script script, int rvcount = -1, int initialPosition = 0, Action configureState = null) { // Create and configure context @@ -197,6 +310,7 @@ public ExecutionContext LoadScript(Script script, int rvcount = -1, int initialP protected override ExecutionContext LoadToken(ushort tokenId) { + ValidateCallFlags(CallFlags.ReadStates | CallFlags.AllowCall); ContractState contract = CurrentContext.GetState().Contract; if (contract is null || tokenId >= contract.Nef.Tokens.Length) throw new InvalidOperationException(); @@ -209,6 +323,11 @@ protected override ExecutionContext LoadToken(ushort tokenId) return CallContractInternal(token.Hash, token.Method, token.CallFlags, token.HasReturnValue, args); } + /// + /// Converts an to a that used in the virtual machine. + /// + /// The to convert. + /// The converted . protected internal StackItem Convert(object value) { if (value is IDisposable disposable) Disposables.Add(disposable); @@ -238,6 +357,12 @@ protected internal StackItem Convert(object value) }; } + /// + /// Converts a to an that to be used as an argument of an interoperable service or native contract. + /// + /// The to convert. + /// The descriptor of the parameter. + /// The converted . protected internal object Convert(StackItem item, InteropParameterDescriptor descriptor) { if (descriptor.IsArray) @@ -281,24 +406,36 @@ public override void Dispose() base.Dispose(); } - protected void ValidateCallFlags(InteropDescriptor descriptor) + /// + /// Determines whether the of the current context meets the specified requirements. + /// + /// The requirements to check. + protected void ValidateCallFlags(CallFlags requiredCallFlags) { ExecutionContextState state = CurrentContext.GetState(); - if (!state.CallFlags.HasFlag(descriptor.RequiredCallFlags)) + if (!state.CallFlags.HasFlag(requiredCallFlags)) throw new InvalidOperationException($"Cannot call this SYSCALL with the flag {state.CallFlags}."); } protected override void OnSysCall(uint method) { - InteropDescriptor descriptor = services[method]; - ValidateCallFlags(descriptor); + OnSysCall(services[method]); + } + + /// + /// Invokes the specified interoperable service. + /// + /// The descriptor of the interoperable service. + protected virtual void OnSysCall(InteropDescriptor descriptor) + { + ValidateCallFlags(descriptor.RequiredCallFlags); AddGas(descriptor.FixedPrice * exec_fee_factor); - List parameters = descriptor.Parameters.Count > 0 - ? new List() - : null; - foreach (var pd in descriptor.Parameters) - parameters.Add(Convert(Pop(), pd)); - object returnValue = descriptor.Handler.Invoke(this, parameters?.ToArray()); + + object[] parameters = new object[descriptor.Parameters.Count]; + for (int i = 0; i < parameters.Length; i++) + parameters[i] = Convert(Pop(), descriptor.Parameters[i]); + + object returnValue = descriptor.Handler.Invoke(this, parameters); if (descriptor.Handler.ReturnType != typeof(void)) Push(Convert(returnValue)); } @@ -309,42 +446,41 @@ protected override void PreExecuteInstruction() AddGas(exec_fee_factor * OpCodePrices[CurrentContext.CurrentInstruction.OpCode]); } - internal void StepOut() - { - int c = InvocationStack.Count; - while (State != VMState.HALT && State != VMState.FAULT && InvocationStack.Count >= c) - ExecuteNext(); - if (State == VMState.FAULT) - throw new InvalidOperationException("StepOut failed.", FaultException); - } - - private static Block CreateDummyBlock(DataCache snapshot) + private static Block CreateDummyBlock(DataCache snapshot, ProtocolSettings settings) { UInt256 hash = NativeContract.Ledger.CurrentHash(snapshot); - var currentBlock = NativeContract.Ledger.GetBlock(snapshot, hash); + Block currentBlock = NativeContract.Ledger.GetBlock(snapshot, hash); return new Block { - Version = 0, - PrevHash = hash, - MerkleRoot = new UInt256(), - Timestamp = currentBlock.Timestamp + Blockchain.MillisecondsPerBlock, - Index = currentBlock.Index + 1, - NextConsensus = currentBlock.NextConsensus, - Witness = new Witness + Header = new Header { - InvocationScript = Array.Empty(), - VerificationScript = Array.Empty() + Version = 0, + PrevHash = hash, + MerkleRoot = new UInt256(), + Timestamp = currentBlock.Timestamp + settings.MillisecondsPerBlock, + Index = currentBlock.Index + 1, + NextConsensus = currentBlock.NextConsensus, + Witness = new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = Array.Empty() + }, }, - ConsensusData = new ConsensusData(), - Transactions = new Transaction[0] + Transactions = Array.Empty() }; } private static InteropDescriptor Register(string name, string handler, long fixedPrice, CallFlags requiredCallFlags) { - MethodInfo method = typeof(ApplicationEngine).GetMethod(handler, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - ?? typeof(ApplicationEngine).GetProperty(handler, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetMethod; - InteropDescriptor descriptor = new InteropDescriptor(name, method, fixedPrice, requiredCallFlags); + MethodInfo method = typeof(ApplicationEngine).GetMethod(handler, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) + ?? typeof(ApplicationEngine).GetProperty(handler, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).GetMethod; + InteropDescriptor descriptor = new() + { + Name = name, + Handler = method, + FixedPrice = fixedPrice, + RequiredCallFlags = requiredCallFlags + }; services ??= new Dictionary(); services.Add(descriptor.Hash, descriptor); return descriptor; @@ -355,11 +491,21 @@ internal static void ResetApplicationEngineProvider() Exchange(ref applicationEngineProvider, null); } - public static ApplicationEngine Run(byte[] script, DataCache snapshot = null, IVerifiable container = null, Block persistingBlock = null, int offset = 0, long gas = TestModeGas) + /// + /// Creates a new instance of the class, and use it to run the specified script. + /// + /// The script to be executed. + /// The snapshot used by the engine during execution. + /// The container of the script. + /// The block being persisted. + /// The used by the engine. + /// The initial position of the instruction pointer. + /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + /// The engine instance created. + public static ApplicationEngine Run(byte[] script, DataCache snapshot, IVerifiable container = null, Block persistingBlock = null, ProtocolSettings settings = null, int offset = 0, long gas = TestModeGas) { - snapshot ??= Blockchain.Singleton.View; - persistingBlock ??= CreateDummyBlock(snapshot); - ApplicationEngine engine = Create(TriggerType.Application, container, snapshot, persistingBlock, gas); + persistingBlock ??= CreateDummyBlock(snapshot, settings ?? ProtocolSettings.Default); + ApplicationEngine engine = Create(TriggerType.Application, container, snapshot, persistingBlock, settings, gas); engine.LoadScript(script, initialPosition: offset); engine.Execute(); return engine; diff --git a/src/neo/SmartContract/BinarySerializer.cs b/src/neo/SmartContract/BinarySerializer.cs index 806c59fd08..1ba2602ec2 100644 --- a/src/neo/SmartContract/BinarySerializer.cs +++ b/src/neo/SmartContract/BinarySerializer.cs @@ -12,6 +12,9 @@ namespace Neo.SmartContract { + /// + /// A binary serializer for . + /// public static class BinarySerializer { private class ContainerPlaceholder : StackItem @@ -32,27 +35,49 @@ public ContainerPlaceholder(StackItemType type, int count) public override bool GetBoolean() => throw new NotSupportedException(); } - public static StackItem Deserialize(byte[] data, uint maxArraySize, uint maxItemSize, ReferenceCounter referenceCounter = null) + /// + /// Deserializes a from byte array. + /// + /// The byte array to parse. + /// The maximum length of arrays during the deserialization. + /// The used by the . + /// The deserialized . + public static StackItem Deserialize(byte[] data, uint maxArraySize, ReferenceCounter referenceCounter = null) { - using MemoryStream ms = new MemoryStream(data, false); - using BinaryReader reader = new BinaryReader(ms); - return Deserialize(reader, maxArraySize, maxItemSize, referenceCounter); + using MemoryStream ms = new(data, false); + using BinaryReader reader = new(ms, Utility.StrictUTF8, true); + return Deserialize(reader, maxArraySize, (uint)data.Length, referenceCounter); } - public static unsafe StackItem Deserialize(ReadOnlySpan data, uint maxArraySize, uint maxItemSize, ReferenceCounter referenceCounter = null) + /// + /// Deserializes a from byte array. + /// + /// The byte array to parse. + /// The maximum length of arrays during the deserialization. + /// The used by the . + /// The deserialized . + public static unsafe StackItem Deserialize(ReadOnlySpan data, uint maxArraySize, ReferenceCounter referenceCounter = null) { if (data.IsEmpty) throw new FormatException(); fixed (byte* pointer = data) { - using UnmanagedMemoryStream ms = new UnmanagedMemoryStream(pointer, data.Length); - using BinaryReader reader = new BinaryReader(ms); - return Deserialize(reader, maxArraySize, maxItemSize, referenceCounter); + using UnmanagedMemoryStream ms = new(pointer, data.Length); + using BinaryReader reader = new(ms, Utility.StrictUTF8, true); + return Deserialize(reader, maxArraySize, (uint)data.Length, referenceCounter); } } + /// + /// Deserializes a from . + /// + /// The for reading data. + /// The maximum length of arrays during the deserialization. + /// The maximum size of during the deserialization. + /// The used by the . + /// The deserialized . public static StackItem Deserialize(BinaryReader reader, uint maxArraySize, uint maxItemSize, ReferenceCounter referenceCounter) { - Stack deserialized = new Stack(); + Stack deserialized = new(); int undeserialized = 1; while (undeserialized-- > 0) { @@ -72,7 +97,7 @@ public static StackItem Deserialize(BinaryReader reader, uint maxArraySize, uint deserialized.Push(reader.ReadVarBytes((int)maxItemSize)); break; case StackItemType.Buffer: - Buffer buffer = new Buffer((int)reader.ReadVarInt(maxItemSize)); + Buffer buffer = new((int)reader.ReadVarInt(maxItemSize)); reader.FillBuffer(buffer.InnerBuffer); deserialized.Push(buffer); break; @@ -95,7 +120,7 @@ public static StackItem Deserialize(BinaryReader reader, uint maxArraySize, uint throw new FormatException(); } } - Stack stack_temp = new Stack(); + Stack stack_temp = new(); while (deserialized.Count > 0) { StackItem item = deserialized.Pop(); @@ -104,19 +129,19 @@ public static StackItem Deserialize(BinaryReader reader, uint maxArraySize, uint switch (placeholder.Type) { case StackItemType.Array: - Array array = new Array(referenceCounter); + Array array = new(referenceCounter); for (int i = 0; i < placeholder.ElementCount; i++) array.Add(stack_temp.Pop()); item = array; break; case StackItemType.Struct: - Struct @struct = new Struct(referenceCounter); + Struct @struct = new(referenceCounter); for (int i = 0; i < placeholder.ElementCount; i++) @struct.Add(stack_temp.Pop()); item = @struct; break; case StackItemType.Map: - Map map = new Map(referenceCounter); + Map map = new(referenceCounter); for (int i = 0; i < placeholder.ElementCount; i++) { StackItem key = stack_temp.Pop(); @@ -132,19 +157,31 @@ public static StackItem Deserialize(BinaryReader reader, uint maxArraySize, uint return stack_temp.Peek(); } + /// + /// Serializes a to byte array. + /// + /// The to be serialized. + /// The maximum size of the result. + /// The serialized byte array. public static byte[] Serialize(StackItem item, uint maxSize) { - using MemoryStream ms = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(ms); + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); Serialize(writer, item, maxSize); writer.Flush(); return ms.ToArray(); } + /// + /// Serializes a into . + /// + /// The for writing data. + /// The to be serialized. + /// The maximum size of the result. public static void Serialize(BinaryWriter writer, StackItem item, uint maxSize) { - HashSet serialized = new HashSet(ReferenceEqualityComparer.Instance); - Stack unserialized = new Stack(); + HashSet serialized = new(ReferenceEqualityComparer.Instance); + Stack unserialized = new(); unserialized.Push(item); while (unserialized.Count > 0) { diff --git a/src/neo/SmartContract/CallFlags.cs b/src/neo/SmartContract/CallFlags.cs index b56b70e585..530f5ea512 100644 --- a/src/neo/SmartContract/CallFlags.cs +++ b/src/neo/SmartContract/CallFlags.cs @@ -2,18 +2,50 @@ namespace Neo.SmartContract { + /// + /// Represents the operations allowed when a contract is called. + /// [Flags] public enum CallFlags : byte { + /// + /// No flag is set. + /// None = 0, + /// + /// Indicates that the called contract is allowed to read states. + /// ReadStates = 0b00000001, + + /// + /// Indicates that the called contract is allowed to write states. + /// WriteStates = 0b00000010, + + /// + /// Indicates that the called contract is allowed to call another contract. + /// AllowCall = 0b00000100, + + /// + /// Indicates that the called contract is allowed to send notifications. + /// AllowNotify = 0b00001000, + /// + /// Indicates that the called contract is allowed to read or write states. + /// States = ReadStates | WriteStates, + + /// + /// Indicates that the called contract is allowed to read states or call another contract. + /// ReadOnly = ReadStates | AllowCall, + + /// + /// All flags are set. + /// All = States | AllowCall | AllowNotify } } diff --git a/src/neo/SmartContract/Contract.cs b/src/neo/SmartContract/Contract.cs index 13246faf8b..005b8a0da7 100644 --- a/src/neo/SmartContract/Contract.cs +++ b/src/neo/SmartContract/Contract.cs @@ -1,30 +1,30 @@ using Neo.Cryptography.ECC; using Neo.VM; -using Neo.Wallets; using System; +using System.Collections.Generic; using System.Linq; namespace Neo.SmartContract { + /// + /// Represents a contract that can be invoked. + /// public class Contract { + /// + /// The script of the contract. + /// public byte[] Script; - public ContractParameterType[] ParameterList; - private string _address; - public string Address - { - get - { - if (_address == null) - { - _address = ScriptHash.ToAddress(); - } - return _address; - } - } + /// + /// The parameters of the contract. + /// + public ContractParameterType[] ParameterList; private UInt160 _scriptHash; + /// + /// The hash of the contract. + /// public virtual UInt160 ScriptHash { get @@ -37,6 +37,12 @@ public virtual UInt160 ScriptHash } } + /// + /// Creates a new instance of the class. + /// + /// The parameters of the contract. + /// The script of the contract. + /// The created contract. public static Contract Create(ContractParameterType[] parameterList, byte[] redeemScript) { return new Contract @@ -47,9 +53,11 @@ public static Contract Create(ContractParameterType[] parameterList, byte[] rede } /// - /// Construct special Contract with empty Script, will get the Script with scriptHash from blockchain when doing the Verify - /// verification = snapshot.Contracts.TryGet(hashes[i])?.Script; + /// Constructs a special contract with empty script, will get the script with scriptHash from blockchain when doing the verification. /// + /// The hash of the contract. + /// The parameters of the contract. + /// The created contract. public static Contract Create(UInt160 scriptHash, params ContractParameterType[] parameterList) { return new Contract @@ -60,7 +68,13 @@ public static Contract Create(UInt160 scriptHash, params ContractParameterType[] }; } - public static Contract CreateMultiSigContract(int m, params ECPoint[] publicKeys) + /// + /// Creates a multi-sig contract. + /// + /// The minimum number of correct signatures that need to be provided in order for the verification to pass. + /// The public keys of the contract. + /// The created contract. + public static Contract CreateMultiSigContract(int m, IReadOnlyCollection publicKeys) { return new Contract { @@ -69,24 +83,32 @@ public static Contract CreateMultiSigContract(int m, params ECPoint[] publicKeys }; } - public static byte[] CreateMultiSigRedeemScript(int m, params ECPoint[] publicKeys) + /// + /// Creates the script of multi-sig contract. + /// + /// The minimum number of correct signatures that need to be provided in order for the verification to pass. + /// The public keys of the contract. + /// The created script. + public static byte[] CreateMultiSigRedeemScript(int m, IReadOnlyCollection publicKeys) { - if (!(1 <= m && m <= publicKeys.Length && publicKeys.Length <= 1024)) + if (!(1 <= m && m <= publicKeys.Count && publicKeys.Count <= 1024)) throw new ArgumentException(); - using (ScriptBuilder sb = new ScriptBuilder()) + using ScriptBuilder sb = new(); + sb.EmitPush(m); + foreach (ECPoint publicKey in publicKeys.OrderBy(p => p)) { - sb.EmitPush(m); - foreach (ECPoint publicKey in publicKeys.OrderBy(p => p)) - { - sb.EmitPush(publicKey.EncodePoint(true)); - } - sb.EmitPush(publicKeys.Length); - sb.Emit(OpCode.PUSHNULL); - sb.EmitSysCall(ApplicationEngine.Neo_Crypto_CheckMultisigWithECDsaSecp256r1); - return sb.ToArray(); + sb.EmitPush(publicKey.EncodePoint(true)); } + sb.EmitPush(publicKeys.Count); + sb.EmitSysCall(ApplicationEngine.Neo_Crypto_CheckMultisig); + return sb.ToArray(); } + /// + /// Creates a signature contract. + /// + /// The public key of the contract. + /// The created contract. public static Contract CreateSignatureContract(ECPoint publicKey) { return new Contract @@ -96,20 +118,27 @@ public static Contract CreateSignatureContract(ECPoint publicKey) }; } + /// + /// Creates the script of signature contract. + /// + /// The public key of the contract. + /// The created script. public static byte[] CreateSignatureRedeemScript(ECPoint publicKey) { - using (ScriptBuilder sb = new ScriptBuilder()) - { - sb.EmitPush(publicKey.EncodePoint(true)); - sb.Emit(OpCode.PUSHNULL); - sb.EmitSysCall(ApplicationEngine.Neo_Crypto_VerifyWithECDsaSecp256r1); - return sb.ToArray(); - } + using ScriptBuilder sb = new(); + sb.EmitPush(publicKey.EncodePoint(true)); + sb.EmitSysCall(ApplicationEngine.Neo_Crypto_CheckSig); + return sb.ToArray(); } - public static UInt160 GetBFTAddress(ECPoint[] pubkeys) + /// + /// Gets the BFT address for the specified public keys. + /// + /// The public keys to be used. + /// The BFT address. + public static UInt160 GetBFTAddress(IReadOnlyCollection pubkeys) { - return CreateMultiSigRedeemScript(pubkeys.Length - (pubkeys.Length - 1) / 3, pubkeys).ToScriptHash(); + return CreateMultiSigRedeemScript(pubkeys.Count - (pubkeys.Count - 1) / 3, pubkeys).ToScriptHash(); } } } diff --git a/src/neo/SmartContract/ContractParameter.cs b/src/neo/SmartContract/ContractParameter.cs index 21318be7da..9b20476324 100644 --- a/src/neo/SmartContract/ContractParameter.cs +++ b/src/neo/SmartContract/ContractParameter.cs @@ -8,13 +8,30 @@ namespace Neo.SmartContract { + /// + /// Represents a parameter of a contract method. + /// public class ContractParameter { + /// + /// The type of the parameter. + /// public ContractParameterType Type; + + /// + /// The value of the parameter. + /// public object Value; + /// + /// Initializes a new instance of the class. + /// public ContractParameter() { } + /// + /// Initializes a new instance of the class with the specified type. + /// + /// The type of the parameter. public ContractParameter(ContractParameterType type) { this.Type = type; @@ -31,53 +48,42 @@ public ContractParameter(ContractParameterType type) ContractParameterType.String => "", ContractParameterType.Array => new List(), ContractParameterType.Map => new List>(), - _ => throw new ArgumentException(), + _ => throw new ArgumentException(null, nameof(type)), }; } + /// + /// Converts the parameter from a JSON object. + /// + /// The parameter represented by a JSON object. + /// The converted parameter. public static ContractParameter FromJson(JObject json) { - ContractParameter parameter = new ContractParameter + ContractParameter parameter = new() { - Type = json["type"].TryGetEnum() + Type = Enum.Parse(json["type"].GetString()) }; if (json["value"] != null) - switch (parameter.Type) + parameter.Value = parameter.Type switch { - case ContractParameterType.Signature: - case ContractParameterType.ByteArray: - parameter.Value = Convert.FromBase64String(json["value"].AsString()); - break; - case ContractParameterType.Boolean: - parameter.Value = json["value"].AsBoolean(); - break; - case ContractParameterType.Integer: - parameter.Value = BigInteger.Parse(json["value"].AsString()); - break; - case ContractParameterType.Hash160: - parameter.Value = UInt160.Parse(json["value"].AsString()); - break; - case ContractParameterType.Hash256: - parameter.Value = UInt256.Parse(json["value"].AsString()); - break; - case ContractParameterType.PublicKey: - parameter.Value = ECPoint.Parse(json["value"].AsString(), ECCurve.Secp256r1); - break; - case ContractParameterType.String: - parameter.Value = json["value"].AsString(); - break; - case ContractParameterType.Array: - parameter.Value = ((JArray)json["value"]).Select(p => FromJson(p)).ToList(); - break; - case ContractParameterType.Map: - parameter.Value = ((JArray)json["value"]).Select(p => new KeyValuePair(FromJson(p["key"]), FromJson(p["value"]))).ToList(); - break; - default: - throw new ArgumentException(); - } + ContractParameterType.Signature or ContractParameterType.ByteArray => Convert.FromBase64String(json["value"].AsString()), + ContractParameterType.Boolean => json["value"].AsBoolean(), + ContractParameterType.Integer => BigInteger.Parse(json["value"].AsString()), + ContractParameterType.Hash160 => UInt160.Parse(json["value"].AsString()), + ContractParameterType.Hash256 => UInt256.Parse(json["value"].AsString()), + ContractParameterType.PublicKey => ECPoint.Parse(json["value"].AsString(), ECCurve.Secp256r1), + ContractParameterType.String => json["value"].AsString(), + ContractParameterType.Array => ((JArray)json["value"]).Select(p => FromJson(p)).ToList(), + ContractParameterType.Map => ((JArray)json["value"]).Select(p => new KeyValuePair(FromJson(p["key"]), FromJson(p["value"]))).ToList(), + _ => throw new ArgumentException(null, nameof(json)), + }; return parameter; } + /// + /// Sets the value of the parameter. + /// + /// The form of the value. public void SetValue(string text) { switch (Type) @@ -113,6 +119,10 @@ public void SetValue(string text) } } + /// + /// Converts the parameter to a JSON object. + /// + /// The parameter represented by a JSON object. public JObject ToJson() { return ToJson(this, null); @@ -120,7 +130,7 @@ public JObject ToJson() private static JObject ToJson(ContractParameter parameter, HashSet context) { - JObject json = new JObject(); + JObject json = new(); json["type"] = parameter.Type; if (parameter.Value != null) switch (parameter.Type) @@ -155,7 +165,7 @@ private static JObject ToJson(ContractParameter parameter, HashSet>)parameter.Value).Select(p => { - JObject item = new JObject(); + JObject item = new(); item["key"] = ToJson(p.Key, context); item["value"] = ToJson(p.Value, context); return item; @@ -187,7 +197,7 @@ private static string ToString(ContractParameter parameter, HashSet + /// Represents the type of . + /// public enum ContractParameterType : byte { + /// + /// Indicates that the parameter can be of any type. + /// Any = 0x00, + /// + /// Indicates that the parameter is of Boolean type. + /// Boolean = 0x10, + + /// + /// Indicates that the parameter is an integer. + /// Integer = 0x11, + + /// + /// Indicates that the parameter is a byte array. + /// ByteArray = 0x12, + + /// + /// Indicates that the parameter is a string. + /// String = 0x13, + + /// + /// Indicates that the parameter is a 160-bit hash. + /// Hash160 = 0x14, + + /// + /// Indicates that the parameter is a 256-bit hash. + /// Hash256 = 0x15, + + /// + /// Indicates that the parameter is a public key. + /// PublicKey = 0x16, + + /// + /// Indicates that the parameter is a signature. + /// Signature = 0x17, + /// + /// Indicates that the parameter is an array. + /// Array = 0x20, + + /// + /// Indicates that the parameter is a map. + /// Map = 0x22, + /// + /// Indicates that the parameter is an interoperable interface. + /// InteropInterface = 0x30, + /// + /// It can be only used as the return type of a method, meaning that the method has no return value. + /// Void = 0xff } } diff --git a/src/neo/SmartContract/ContractParametersContext.cs b/src/neo/SmartContract/ContractParametersContext.cs index c68f4974b9..da47e16406 100644 --- a/src/neo/SmartContract/ContractParametersContext.cs +++ b/src/neo/SmartContract/ContractParametersContext.cs @@ -1,7 +1,7 @@ using Neo.Cryptography.ECC; using Neo.IO.Json; -using Neo.Ledger; using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.VM; using System; using System.Collections.Generic; @@ -11,55 +11,62 @@ namespace Neo.SmartContract { + /// + /// The context used to add witnesses for . + /// public class ContractParametersContext { private class ContextItem { - public byte[] Script; - public ContractParameter[] Parameters; - public Dictionary Signatures; - - private ContextItem() { } + public readonly byte[] Script; + public readonly ContractParameter[] Parameters; + public readonly Dictionary Signatures; public ContextItem(Contract contract) { this.Script = contract.Script; this.Parameters = contract.ParameterList.Select(p => new ContractParameter { Type = p }).ToArray(); + this.Signatures = new Dictionary(); } - public static ContextItem FromJson(JObject json) + public ContextItem(JObject json) { - return new ContextItem + this.Script = Convert.FromBase64String(json["script"].AsString()); + this.Parameters = ((JArray)json["parameters"]).Select(p => ContractParameter.FromJson(p)).ToArray(); + this.Signatures = json["signatures"].Properties.Select(p => new { - Script = Convert.FromBase64String(json["script"]?.AsString()), - 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 = Convert.FromBase64String(p.Value.AsString()) - }).ToDictionary(p => p.PublicKey, p => p.Signature) - }; + PublicKey = ECPoint.Parse(p.Key, ECCurve.Secp256r1), + Signature = Convert.FromBase64String(p.Value.AsString()) + }).ToDictionary(p => p.PublicKey, p => p.Signature); } public JObject ToJson() { - JObject json = new JObject(); - if (Script != null) - json["script"] = Convert.ToBase64String(Script); + JObject json = new(); + json["script"] = Convert.ToBase64String(Script); 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()] = Convert.ToBase64String(signature.Value); - } + json["signatures"] = new JObject(); + foreach (var signature in Signatures) + json["signatures"][signature.Key.ToString()] = Convert.ToBase64String(signature.Value); return json; } } + /// + /// The to add witnesses. + /// public readonly IVerifiable Verifiable; + + /// + /// The snapshot used to read data. + /// + public readonly DataCache Snapshot; + private readonly Dictionary ContextItems; + /// + /// Determines whether all witnesses are ready to be added. + /// public bool Completed { get @@ -70,37 +77,31 @@ public bool Completed } } + private UInt160[] _ScriptHashes = null; /// - /// Cache for public ScriptHashes field + /// Gets the script hashes to be verified for the . /// - private UInt160[] _ScriptHashes = null; + public IReadOnlyList ScriptHashes => _ScriptHashes ??= Verifiable.GetScriptHashesForVerifying(Snapshot); /// - /// ScriptHashes are the verifiable ScriptHashes from Verifiable element - /// Equivalent to: Verifiable.GetScriptHashesForVerifying(Blockchain.Singleton.GetSnapshot()) + /// Initializes a new instance of the class. /// - public IReadOnlyList ScriptHashes - { - get - { - if (_ScriptHashes is null) - { - // snapshot is not necessary for Transaction - if (Verifiable is Transaction) - _ScriptHashes = Verifiable.GetScriptHashesForVerifying(null); - else - _ScriptHashes = Verifiable.GetScriptHashesForVerifying(Blockchain.Singleton.View); - } - return _ScriptHashes; - } - } - - public ContractParametersContext(IVerifiable verifiable) + /// The snapshot used to read data. + /// The to add witnesses. + public ContractParametersContext(DataCache snapshot, IVerifiable verifiable) { this.Verifiable = verifiable; + this.Snapshot = snapshot; this.ContextItems = new Dictionary(); } + /// + /// Adds a parameter to the specified witness script. + /// + /// The contract contains the script. + /// The index of the parameter. + /// The value of the parameter. + /// if the parameter is added successfully; otherwise, . public bool Add(Contract contract, int index, object parameter) { ContextItem item = CreateItem(contract); @@ -109,6 +110,12 @@ public bool Add(Contract contract, int index, object parameter) return true; } + /// + /// Adds parameters to the specified witness script. + /// + /// The contract contains the script. + /// The values of the parameters. + /// if the parameters are added successfully; otherwise, . public bool Add(Contract contract, params object[] parameters) { ContextItem item = CreateItem(contract); @@ -120,19 +127,23 @@ public bool Add(Contract contract, params object[] parameters) return true; } + /// + /// Adds a signature to the specified witness script. + /// + /// The contract contains the script. + /// The public key for the signature. + /// The signature. + /// if the signature is added successfully; otherwise, . public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) { if (contract.Script.IsMultiSigContract(out _, out ECPoint[] points)) { + if (!points.Contains(pubkey)) return false; 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)) + if (!item.Signatures.TryAdd(pubkey, signature)) return false; - if (!points.Contains(pubkey)) return false; - item.Signatures.Add(pubkey, signature); if (item.Signatures.Count == contract.ParameterList.Length) { Dictionary dic = points.Select((p, i) => new @@ -148,7 +159,6 @@ 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(); - item.Signatures = null; } return true; } @@ -168,7 +178,12 @@ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) // return now to prevent array index out of bounds exception return false; } - return Add(contract, index, signature); + ContextItem item = CreateItem(contract); + if (item == null) return false; + if (!item.Signatures.TryAdd(pubkey, signature)) + return false; + item.Parameters[index].Value = signature; + return true; } } @@ -183,30 +198,47 @@ private ContextItem CreateItem(Contract contract) return item; } - public static ContractParametersContext FromJson(JObject json) + /// + /// Converts the context from a JSON object. + /// + /// The context represented by a JSON object. + /// The snapshot used to read data. + /// The converted context. + public static ContractParametersContext FromJson(JObject json, DataCache snapshot) { 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(Convert.FromBase64String(json["hex"].AsString()), false)) - using (BinaryReader reader = new BinaryReader(ms, Utility.StrictUTF8)) + using (MemoryStream ms = new(Convert.FromBase64String(json["data"].AsString()), false)) + using (BinaryReader reader = new(ms, Utility.StrictUTF8)) { verifiable.DeserializeUnsigned(reader); } - ContractParametersContext context = new ContractParametersContext(verifiable); + ContractParametersContext context = new(snapshot, verifiable); foreach (var property in json["items"].Properties) { - context.ContextItems.Add(UInt160.Parse(property.Key), ContextItem.FromJson(property.Value)); + context.ContextItems.Add(UInt160.Parse(property.Key), new ContextItem(property.Value)); } return context; } + /// + /// Gets the parameter with the specified index from the witness script. + /// + /// The hash of the witness script. + /// The specified index. + /// The parameter with the specified index. public ContractParameter GetParameter(UInt160 scriptHash, int index) { return GetParameters(scriptHash)?[index]; } + /// + /// Gets the parameters from the witness script. + /// + /// The hash of the witness script. + /// The parameters from the witness script. public IReadOnlyList GetParameters(UInt160 scriptHash) { if (!ContextItems.TryGetValue(scriptHash, out ContextItem item)) @@ -214,6 +246,23 @@ public IReadOnlyList GetParameters(UInt160 scriptHash) return item.Parameters; } + /// + /// Gets the signatures from the witness script. + /// + /// The hash of the witness script. + /// The signatures from the witness script. + public IReadOnlyDictionary GetSignatures(UInt160 scriptHash) + { + if (!ContextItems.TryGetValue(scriptHash, out ContextItem item)) + return null; + return item.Signatures; + } + + /// + /// Gets the witness script with the specified hash. + /// + /// The hash of the witness script. + /// The witness script. public byte[] GetScript(UInt160 scriptHash) { if (!ContextItems.TryGetValue(scriptHash, out ContextItem item)) @@ -221,6 +270,11 @@ public byte[] GetScript(UInt160 scriptHash) return item.Script; } + /// + /// Gets the witnesses of the . + /// + /// The witnesses of the . + /// The witnesses are not ready to be added. public Witness[] GetWitnesses() { if (!Completed) throw new InvalidOperationException(); @@ -228,37 +282,45 @@ public Witness[] GetWitnesses() for (int i = 0; i < ScriptHashes.Count; i++) { ContextItem item = ContextItems[ScriptHashes[i]]; - using (ScriptBuilder sb = new ScriptBuilder()) + using ScriptBuilder sb = new(); + for (int j = item.Parameters.Length - 1; j >= 0; j--) { - for (int j = item.Parameters.Length - 1; j >= 0; j--) - { - sb.EmitPush(item.Parameters[j]); - } - witnesses[i] = new Witness - { - InvocationScript = sb.ToArray(), - VerificationScript = item.Script ?? Array.Empty() - }; + sb.EmitPush(item.Parameters[j]); } + witnesses[i] = new Witness + { + InvocationScript = sb.ToArray(), + VerificationScript = item.Script ?? Array.Empty() + }; } return witnesses; } - public static ContractParametersContext Parse(string value) + /// + /// Parses the context from a JSON . + /// + /// The JSON . + /// The snapshot used to read data. + /// The parsed context. + public static ContractParametersContext Parse(string value, DataCache snapshot) { - return FromJson(JObject.Parse(value)); + return FromJson(JObject.Parse(value), snapshot); } + /// + /// Converts the context to a JSON object. + /// + /// The context represented by a JSON object. public JObject ToJson() { - JObject json = new JObject(); + JObject json = new(); json["type"] = Verifiable.GetType().FullName; - using (MemoryStream ms = new MemoryStream()) - using (BinaryWriter writer = new BinaryWriter(ms, Utility.StrictUTF8)) + using (MemoryStream ms = new()) + using (BinaryWriter writer = new(ms, Utility.StrictUTF8)) { Verifiable.SerializeUnsigned(writer); writer.Flush(); - json["hex"] = Convert.ToBase64String(ms.ToArray()); + json["data"] = Convert.ToBase64String(ms.ToArray()); } json["items"] = new JObject(); foreach (var item in ContextItems) diff --git a/src/neo/SmartContract/ContractState.cs b/src/neo/SmartContract/ContractState.cs index f9b7fed5d8..cc3007b717 100644 --- a/src/neo/SmartContract/ContractState.cs +++ b/src/neo/SmartContract/ContractState.cs @@ -8,14 +8,39 @@ namespace Neo.SmartContract { + /// + /// Represents a deployed contract. + /// public class ContractState : IInteroperable { + /// + /// The id of the contract. + /// public int Id; + + /// + /// Indicates the number of times the contract has been updated. + /// public ushort UpdateCounter; + + /// + /// The hash of the contract. + /// public UInt160 Hash; + + /// + /// The nef of the contract. + /// public NefFile Nef; + + /// + /// The manifest of the contract. + /// public ContractManifest Manifest; + /// + /// The script of the contract. + /// public byte[] Script => Nef.Script; void IInteroperable.FromStackItem(StackItem stackItem) @@ -29,16 +54,20 @@ void IInteroperable.FromStackItem(StackItem stackItem) } /// - /// Return true if is allowed + /// Determines whether the current contract has the permission to call the specified contract. /// - /// The contract that we are calling - /// The method that we are calling - /// Return true or false + /// The contract to be called. + /// The method to be called. + /// if the contract allows to be called; otherwise, . public bool CanCall(ContractState targetContract, string targetMethod) { return Manifest.Permissions.Any(u => u.IsAllowed(targetContract, targetMethod)); } + /// + /// Converts the contract to a JSON object. + /// + /// The contract represented by a JSON object. public JObject ToJson() { return new JObject diff --git a/src/neo/SmartContract/ContractTask.cs b/src/neo/SmartContract/ContractTask.cs new file mode 100644 index 0000000000..db654ff058 --- /dev/null +++ b/src/neo/SmartContract/ContractTask.cs @@ -0,0 +1,39 @@ +using System.Runtime.CompilerServices; + +namespace Neo.SmartContract +{ + [AsyncMethodBuilder(typeof(ContractTaskMethodBuilder))] + class ContractTask + { + private readonly ContractTaskAwaiter awaiter; + + public static ContractTask CompletedTask { get; } + + static ContractTask() + { + CompletedTask = new ContractTask(); + CompletedTask.GetAwaiter().SetResult(); + } + + public ContractTask() + { + awaiter = CreateAwaiter(); + } + + protected virtual ContractTaskAwaiter CreateAwaiter() => new(); + + public virtual ContractTaskAwaiter GetAwaiter() => awaiter; + + public virtual object GetResult() => null; + } + + [AsyncMethodBuilder(typeof(ContractTaskMethodBuilder<>))] + class ContractTask : ContractTask + { + protected override ContractTaskAwaiter CreateAwaiter() => new(); + + public override ContractTaskAwaiter GetAwaiter() => (ContractTaskAwaiter)base.GetAwaiter(); + + public override object GetResult() => GetAwaiter().GetResult(); + } +} diff --git a/src/neo/SmartContract/ContractTaskAwaiter.cs b/src/neo/SmartContract/ContractTaskAwaiter.cs new file mode 100644 index 0000000000..2590798c05 --- /dev/null +++ b/src/neo/SmartContract/ContractTaskAwaiter.cs @@ -0,0 +1,63 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Neo.SmartContract +{ + class ContractTaskAwaiter : INotifyCompletion + { + private Action continuation; + private Exception exception; + + public bool IsCompleted { get; private set; } + + public void GetResult() + { + if (exception is not null) + throw exception; + } + + public void SetResult() => RunContinuation(); + + public virtual void SetResult(ApplicationEngine engine) => SetResult(); + + public void SetException(Exception exception) + { + this.exception = exception; + RunContinuation(); + } + + public void OnCompleted(Action continuation) + { + Interlocked.CompareExchange(ref this.continuation, continuation, null); + } + + protected void RunContinuation() + { + IsCompleted = true; + continuation?.Invoke(); + } + } + + class ContractTaskAwaiter : ContractTaskAwaiter + { + private T result; + + public new T GetResult() + { + base.GetResult(); + return result; + } + + public void SetResult(T result) + { + this.result = result; + RunContinuation(); + } + + public override void SetResult(ApplicationEngine engine) + { + SetResult((T)engine.Convert(engine.Pop(), new InteropParameterDescriptor(typeof(T)))); + } + } +} diff --git a/src/neo/SmartContract/ContractTaskMethodBuilder.cs b/src/neo/SmartContract/ContractTaskMethodBuilder.cs new file mode 100644 index 0000000000..97342d948e --- /dev/null +++ b/src/neo/SmartContract/ContractTaskMethodBuilder.cs @@ -0,0 +1,89 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Neo.SmartContract +{ + sealed class ContractTaskMethodBuilder + { + private ContractTask task; + + public ContractTask Task => task ??= new ContractTask(); + + public static ContractTaskMethodBuilder Create() => new(); + + public void SetException(Exception exception) + { + Task.GetAwaiter().SetException(exception); + } + + public void SetResult() + { + Task.GetAwaiter().SetResult(); + } + + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + } + } + + sealed class ContractTaskMethodBuilder + { + private ContractTask task; + + public ContractTask Task => task ??= new ContractTask(); + + public static ContractTaskMethodBuilder Create() => new(); + + public void SetException(Exception exception) + { + Task.GetAwaiter().SetException(exception); + } + + public void SetResult(T result) + { + Task.GetAwaiter().SetResult(result); + } + + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + } + } +} diff --git a/src/neo/SmartContract/DeployedContract.cs b/src/neo/SmartContract/DeployedContract.cs index 4a5e3ba2a5..bab8f3b105 100644 --- a/src/neo/SmartContract/DeployedContract.cs +++ b/src/neo/SmartContract/DeployedContract.cs @@ -4,10 +4,17 @@ namespace Neo.SmartContract { + /// + /// Represents a deployed contract that can be invoked. + /// public class DeployedContract : Contract { public override UInt160 ScriptHash { get; } + /// + /// Initializes a new instance of the class with the specified . + /// + /// The corresponding to the contract. public DeployedContract(ContractState contract) { if (contract is null) throw new ArgumentNullException(nameof(contract)); diff --git a/src/neo/SmartContract/ExecutionContextState.cs b/src/neo/SmartContract/ExecutionContextState.cs index bbbdefbae9..a2a0f7613a 100644 --- a/src/neo/SmartContract/ExecutionContextState.cs +++ b/src/neo/SmartContract/ExecutionContextState.cs @@ -1,24 +1,29 @@ +using Neo.VM; + namespace Neo.SmartContract { + /// + /// Represents the custom state in . + /// public class ExecutionContextState { /// - /// Script hash + /// The script hash of the current context. /// public UInt160 ScriptHash { get; set; } /// - /// Calling script hash + /// The script hash of the calling contract. /// public UInt160 CallingScriptHash { get; set; } /// - /// The ContractState of the current context. + /// The of the current context. /// public ContractState Contract { get; set; } /// - /// Execution context rights + /// The of the current context. /// public CallFlags CallFlags { get; set; } = CallFlags.All; } diff --git a/src/neo/SmartContract/FindOptions.cs b/src/neo/SmartContract/FindOptions.cs index 9b4c20c2ba..a295b1319a 100644 --- a/src/neo/SmartContract/FindOptions.cs +++ b/src/neo/SmartContract/FindOptions.cs @@ -2,18 +2,50 @@ namespace Neo.SmartContract { + /// + /// Specify the options to be used during the search. + /// [Flags] public enum FindOptions : byte { + /// + /// No option is set. The results will be an iterator of (key, value). + /// None = 0, + /// + /// Indicates that only keys need to be returned. The results will be an iterator of keys. + /// KeysOnly = 1 << 0, + + /// + /// Indicates that the prefix byte of keys should be removed before return. + /// RemovePrefix = 1 << 1, + + /// + /// Indicates that only values need to be returned. The results will be an iterator of values. + /// ValuesOnly = 1 << 2, + + /// + /// Indicates that values should be deserialized before return. + /// DeserializeValues = 1 << 3, + + /// + /// Indicates that only the field 0 of the deserialized values need to be returned. This flag must be set together with . + /// PickField0 = 1 << 4, + + /// + /// Indicates that only the field 1 of the deserialized values need to be returned. This flag must be set together with . + /// PickField1 = 1 << 5, + /// + /// This value is only for internal use, and shouldn't be used in smart contracts. + /// All = KeysOnly | RemovePrefix | ValuesOnly | DeserializeValues | PickField0 | PickField1 } } diff --git a/src/neo/SmartContract/Helper.cs b/src/neo/SmartContract/Helper.cs index 7d80688934..da8cde9120 100644 --- a/src/neo/SmartContract/Helper.cs +++ b/src/neo/SmartContract/Helper.cs @@ -9,26 +9,78 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; +using System.Linq; namespace Neo.SmartContract { + /// + /// A helper class related to smart contract. + /// public static class Helper { + /// + /// The maximum GAS that can be consumed when is called. + /// public const long MaxVerificationGas = 0_50000000; + /// + /// Calculates the verification fee for a signature address. + /// + /// The calculated cost. public static long SignatureContractCost() => ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * 2 + - ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + - ApplicationEngine.ECDsaVerifyPrice; + ApplicationEngine.CheckSigPrice; - public static long MultiSignatureContractCost(int m, int n) => - ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * (m + n) + - ApplicationEngine.OpCodePrices[OpCode.PUSHINT8] * 2 + - ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + - ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + - ApplicationEngine.ECDsaVerifyPrice * n; + /// + /// Calculates the verification fee for a multi-signature address. + /// + /// The minimum number of correct signatures that need to be provided in order for the verification to pass. + /// The number of public keys in the account. + /// The calculated cost. + public static long MultiSignatureContractCost(int m, int n) + { + long fee = ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * (m + n); + using (ScriptBuilder sb = new()) + fee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]]; + using (ScriptBuilder sb = new()) + fee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; + fee += ApplicationEngine.OpCodePrices[OpCode.SYSCALL]; + fee += ApplicationEngine.CheckSigPrice * n; + return fee; + } + + /// + /// Check the correctness of the script and ABI. + /// + /// The script of the contract. + /// The ABI of the contract. + public static void Check(byte[] script, ContractAbi abi) + { + Check(new Script(script, true), abi); + } + + /// + /// Check the correctness of the script and ABI. + /// + /// The script of the contract. + /// The ABI of the contract. + /// Note: The passed to this method should be constructed with strict mode. + public static void Check(this Script script, ContractAbi abi) + { + foreach (ContractMethodDescriptor method in abi.Methods) + script.GetInstruction(method.Offset); + abi.GetMethod(string.Empty, 0); // Trigger the construction of ContractAbi.methodDictionary to check the uniqueness of the method names. + _ = abi.Events.ToDictionary(p => p.Name); // Check the uniqueness of the event names. + } + /// + /// Computes the hash of a deployed contract. + /// + /// The sender of the transaction that deployed the contract. + /// The checksum of the nef file of the contract. + /// The name of the contract. + /// The hash of the contract. public static UInt160 GetContractHash(UInt160 sender, uint nefCheckSum, string name) { using var sb = new ScriptBuilder(); @@ -40,24 +92,48 @@ public static UInt160 GetContractHash(UInt160 sender, uint nefCheckSum, string n return sb.ToArray().ToScriptHash(); } + /// + /// Gets the script hash of the specified . + /// + /// The specified . + /// The script hash of the context. public static UInt160 GetScriptHash(this ExecutionContext context) { return context.GetState().ScriptHash; } + /// + /// Determines whether the specified contract is a multi-signature contract. + /// + /// The script of the contract. + /// if the contract is a multi-signature contract; otherwise, . public static bool IsMultiSigContract(this byte[] script) { return IsMultiSigContract(script, out _, out _, null); } + /// + /// Determines whether the specified contract is a multi-signature contract. + /// + /// The script of the contract. + /// The minimum number of correct signatures that need to be provided in order for the verification to pass. + /// The number of public keys in the account. + /// if the contract is a multi-signature contract; otherwise, . public static bool IsMultiSigContract(this byte[] script, out int m, out int n) { return IsMultiSigContract(script, out m, out n, null); } + /// + /// Determines whether the specified contract is a multi-signature contract. + /// + /// The script of the contract. + /// The minimum number of correct signatures that need to be provided in order for the verification to pass. + /// The public keys in the account. + /// if the contract is a multi-signature contract; otherwise, . public static bool IsMultiSigContract(this byte[] script, out int m, out ECPoint[] points) { - List list = new List(); + List list = new(); if (IsMultiSigContract(script, out m, out _, list)) { points = list.ToArray(); @@ -74,7 +150,7 @@ private static bool IsMultiSigContract(byte[] script, out int m, out int n, List { m = 0; n = 0; int i = 0; - if (script.Length < 43) return false; + if (script.Length < 42) return false; switch (script[i]) { case (byte)OpCode.PUSHINT8: @@ -119,49 +195,81 @@ private static bool IsMultiSigContract(byte[] script, out int m, out int n, List default: return false; } - if (script.Length != i + 6) return false; - if (script[i++] != (byte)OpCode.PUSHNULL) return false; + if (script.Length != i + 5) return false; if (script[i++] != (byte)OpCode.SYSCALL) return false; - if (BitConverter.ToUInt32(script, i) != ApplicationEngine.Neo_Crypto_CheckMultisigWithECDsaSecp256r1) + if (BinaryPrimitives.ReadUInt32LittleEndian(script.AsSpan(i)) != ApplicationEngine.Neo_Crypto_CheckMultisig) return false; return true; } + /// + /// Determines whether the specified contract is a signature contract. + /// + /// The script of the contract. + /// if the contract is a signature contract; otherwise, . public static bool IsSignatureContract(this byte[] script) { - if (script.Length != 41) return false; + if (script.Length != 40) return false; if (script[0] != (byte)OpCode.PUSHDATA1 || script[1] != 33 - || script[35] != (byte)OpCode.PUSHNULL - || script[36] != (byte)OpCode.SYSCALL - || BitConverter.ToUInt32(script, 37) != ApplicationEngine.Neo_Crypto_VerifyWithECDsaSecp256r1) + || script[35] != (byte)OpCode.SYSCALL + || BinaryPrimitives.ReadUInt32LittleEndian(script.AsSpan(36)) != ApplicationEngine.Neo_Crypto_CheckSig) return false; return true; } + /// + /// Determines whether the specified contract is a standard contract. A standard contract is either a signature contract or a multi-signature contract. + /// + /// The script of the contract. + /// if the contract is a standard contract; otherwise, . public static bool IsStandardContract(this byte[] script) { return script.IsSignatureContract() || script.IsMultiSigContract(); } + /// + /// Convert the to an . + /// + /// The type of the . + /// The to convert. + /// The converted . public static T ToInteroperable(this StackItem item) where T : IInteroperable, new() { - T t = new T(); + T t = new(); t.FromStackItem(item); return t; } + /// + /// Computes the hash of the specified script. + /// + /// The specified script. + /// The hash of the script. public static UInt160 ToScriptHash(this byte[] script) { return new UInt160(Crypto.Hash160(script)); } + /// + /// Computes the hash of the specified script. + /// + /// The specified script. + /// The hash of the script. public static UInt160 ToScriptHash(this ReadOnlySpan script) { return new UInt160(Crypto.Hash160(script)); } - public static bool VerifyWitnesses(this IVerifiable verifiable, DataCache snapshot, long gas) + /// + /// Verifies the witnesses of the specified . + /// + /// The to be verified. + /// The to be used for the verification. + /// The snapshot used to read data. + /// The maximum GAS that can be used. + /// if the is verified as valid; otherwise, . + public static bool VerifyWitnesses(this IVerifiable verifiable, ProtocolSettings settings, DataCache snapshot, long gas) { if (gas < 0) return false; if (gas > MaxVerificationGas) gas = MaxVerificationGas; @@ -178,14 +286,14 @@ public static bool VerifyWitnesses(this IVerifiable verifiable, DataCache snapsh if (hashes.Length != verifiable.Witnesses.Length) return false; for (int i = 0; i < hashes.Length; i++) { - if (!verifiable.VerifyWitness(snapshot, hashes[i], verifiable.Witnesses[i], gas, out long fee)) + if (!verifiable.VerifyWitness(settings, snapshot, hashes[i], verifiable.Witnesses[i], gas, out long fee)) return false; gas -= fee; } return true; } - internal static bool VerifyWitness(this IVerifiable verifiable, DataCache snapshot, UInt160 hash, Witness witness, long gas, out long fee) + internal static bool VerifyWitness(this IVerifiable verifiable, ProtocolSettings settings, DataCache snapshot, UInt160 hash, Witness witness, long gas, out long fee) { fee = 0; Script invocationScript; @@ -197,17 +305,15 @@ internal static bool VerifyWitness(this IVerifiable verifiable, DataCache snapsh { return false; } - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.CreateSnapshot(), null, gas)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.CreateSnapshot(), null, settings, gas)) { - CallFlags callFlags = !witness.VerificationScript.IsStandardContract() ? CallFlags.ReadStates : CallFlags.None; - if (witness.VerificationScript.Length == 0) { ContractState cs = NativeContract.ContractManagement.GetContract(snapshot, hash); if (cs is null) return false; ContractMethodDescriptor md = cs.Manifest.Abi.GetMethod("verify", -1); if (md?.ReturnType != ContractParameterType.Boolean) return false; - engine.LoadContract(cs, md, callFlags); + engine.LoadContract(cs, md, CallFlags.ReadOnly); } else { @@ -224,23 +330,13 @@ internal static bool VerifyWitness(this IVerifiable verifiable, DataCache snapsh } engine.LoadScript(verificationScript, initialPosition: 0, configureState: p => { - p.CallFlags = callFlags; + p.CallFlags = CallFlags.ReadOnly; p.ScriptHash = hash; }); } engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None); - if (NativeContract.IsNative(hash)) - { - try - { - engine.StepOut(); - engine.Push("verify"); - } - catch { } - } - if (engine.Execute() == VMState.FAULT) return false; if (!engine.ResultStack.Peek().GetBoolean()) return false; fee = engine.GasConsumed; diff --git a/src/neo/SmartContract/IInteroperable.cs b/src/neo/SmartContract/IInteroperable.cs index ec53ef48c0..be495c354f 100644 --- a/src/neo/SmartContract/IInteroperable.cs +++ b/src/neo/SmartContract/IInteroperable.cs @@ -3,9 +3,22 @@ namespace Neo.SmartContract { + /// + /// Represents the object that can be converted to and from . + /// public interface IInteroperable { + /// + /// Convert a to the current object. + /// + /// The to convert. void FromStackItem(StackItem stackItem); + + /// + /// Convert the current object to a . + /// + /// The used by the . + /// The converted . StackItem ToStackItem(ReferenceCounter referenceCounter); } } diff --git a/src/neo/SmartContract/InteropDescriptor.cs b/src/neo/SmartContract/InteropDescriptor.cs index ea7acc2144..e5f1f8ada2 100644 --- a/src/neo/SmartContract/InteropDescriptor.cs +++ b/src/neo/SmartContract/InteropDescriptor.cs @@ -1,5 +1,5 @@ using Neo.Cryptography; -using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -7,25 +7,51 @@ namespace Neo.SmartContract { - public class InteropDescriptor + /// + /// Represents a descriptor of an interoperable service. + /// + public record InteropDescriptor { - public string Name { get; } - public uint Hash { get; } - public MethodInfo Handler { get; } - public IReadOnlyList Parameters { get; } - public long FixedPrice { get; } - public CallFlags RequiredCallFlags { get; } + /// + /// The name of the interoperable service. + /// + public string Name { get; init; } - internal InteropDescriptor(string name, MethodInfo handler, long fixedPrice, CallFlags requiredCallFlags) + private uint _hash; + /// + /// The hash of the interoperable service. + /// + public uint Hash { - this.Name = name; - this.Hash = BitConverter.ToUInt32(Encoding.ASCII.GetBytes(name).Sha256(), 0); - this.Handler = handler; - this.Parameters = handler.GetParameters().Select(p => new InteropParameterDescriptor(p)).ToList().AsReadOnly(); - this.FixedPrice = fixedPrice; - this.RequiredCallFlags = requiredCallFlags; + get + { + if (_hash == 0) + _hash = BinaryPrimitives.ReadUInt32LittleEndian(Encoding.ASCII.GetBytes(Name).Sha256()); + return _hash; + } } + /// + /// The used to handle the interoperable service. + /// + public MethodInfo Handler { get; init; } + + private IReadOnlyList _parameters; + /// + /// The parameters of the interoperable service. + /// + public IReadOnlyList Parameters => _parameters ??= Handler.GetParameters().Select(p => new InteropParameterDescriptor(p)).ToList().AsReadOnly(); + + /// + /// The fixed price for calling the interoperable service. It can be 0 if the interoperable service has a variable price. + /// + public long FixedPrice { get; init; } + + /// + /// The required for the interoperable service. + /// + public CallFlags RequiredCallFlags { get; init; } + public static implicit operator uint(InteropDescriptor descriptor) { return descriptor.Hash; diff --git a/src/neo/SmartContract/InteropParameterDescriptor.cs b/src/neo/SmartContract/InteropParameterDescriptor.cs index 6fce4848ae..93f949a29f 100644 --- a/src/neo/SmartContract/InteropParameterDescriptor.cs +++ b/src/neo/SmartContract/InteropParameterDescriptor.cs @@ -7,16 +7,42 @@ namespace Neo.SmartContract { + /// + /// Represents a descriptor of an interoperable service parameter. + /// public class InteropParameterDescriptor { + /// + /// The name of the parameter. + /// public string Name { get; } + + /// + /// The type of the parameter. + /// public Type Type { get; } + + /// + /// The converter to convert the parameter from to . + /// public Func Converter { get; } + + /// + /// Indicates whether the parameter is an enumeration. + /// public bool IsEnum => Type.IsEnum; + + /// + /// Indicates whether the parameter is an array. + /// public bool IsArray => Type.IsArray && Type.GetElementType() != typeof(byte); + + /// + /// Indicates whether the parameter is an . + /// public bool IsInterface { get; } - private static readonly Dictionary> converters = new Dictionary> + private static readonly Dictionary> converters = new() { [typeof(StackItem)] = p => p, [typeof(VM.Types.Pointer)] = p => p, diff --git a/src/neo/SmartContract/Iterators/IIterator.cs b/src/neo/SmartContract/Iterators/IIterator.cs index d131b75977..4e59fadf7b 100644 --- a/src/neo/SmartContract/Iterators/IIterator.cs +++ b/src/neo/SmartContract/Iterators/IIterator.cs @@ -3,9 +3,21 @@ namespace Neo.SmartContract.Iterators { + /// + /// Represents iterators in smart contract. + /// public interface IIterator : IDisposable { + /// + /// Advances the iterator to the next element of the collection. + /// + /// if the iterator was successfully advanced to the next element; if the iterator has passed the end of the collection. bool Next(); + + /// + /// Gets the element in the collection at the current position of the iterator. + /// + /// The element in the collection at the current position of the iterator. StackItem Value(); } } diff --git a/src/neo/SmartContract/Iterators/StorageIterator.cs b/src/neo/SmartContract/Iterators/StorageIterator.cs index 74dc5015cc..00b701fc5e 100644 --- a/src/neo/SmartContract/Iterators/StorageIterator.cs +++ b/src/neo/SmartContract/Iterators/StorageIterator.cs @@ -36,7 +36,7 @@ public StackItem Value() key = key[1..]; StackItem item = options.HasFlag(FindOptions.DeserializeValues) - ? BinarySerializer.Deserialize(value, 1024, (uint)value.Length, referenceCounter) + ? BinarySerializer.Deserialize(value, 1024, referenceCounter) : value; if (options.HasFlag(FindOptions.PickField0)) diff --git a/src/neo/SmartContract/JsonSerializer.cs b/src/neo/SmartContract/JsonSerializer.cs index ef772183c9..1629ae9b7f 100644 --- a/src/neo/SmartContract/JsonSerializer.cs +++ b/src/neo/SmartContract/JsonSerializer.cs @@ -13,13 +13,16 @@ namespace Neo.SmartContract { + /// + /// A JSON serializer for . + /// public static class JsonSerializer { /// - /// Convert stack item in json + /// Serializes a to a . /// - /// Item - /// Json + /// The to serialize. + /// The serialized object. public static JObject Serialize(StackItem item) { switch (item) @@ -68,15 +71,21 @@ public static JObject Serialize(StackItem item) } } + /// + /// Serializes a to JSON. + /// + /// The to convert. + /// The maximum size of the JSON output. + /// A byte array containing the JSON output. public static byte[] SerializeToByteArray(StackItem item, uint maxSize) { - using MemoryStream ms = new MemoryStream(); - using Utf8JsonWriter writer = new Utf8JsonWriter(ms, new JsonWriterOptions + using MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms, new JsonWriterOptions { Indented = false, SkipValidation = false }); - Stack stack = new Stack(); + Stack stack = new(); stack.Push(item); while (stack.Count > 0) { @@ -136,10 +145,11 @@ public static byte[] SerializeToByteArray(StackItem item, uint maxSize) } /// - /// Convert json object to stack item + /// Deserializes a from . /// - /// Json - /// Return stack item + /// The to deserialize. + /// The used by the . + /// The deserialized . public static StackItem Deserialize(JObject json, ReferenceCounter referenceCounter = null) { switch (json) diff --git a/src/neo/SmartContract/KeyBuilder.cs b/src/neo/SmartContract/KeyBuilder.cs index 80bea0e783..099de8bd52 100644 --- a/src/neo/SmartContract/KeyBuilder.cs +++ b/src/neo/SmartContract/KeyBuilder.cs @@ -4,26 +4,44 @@ namespace Neo.SmartContract { + /// + /// Used to build storage keys for native contracts. + /// public class KeyBuilder { private readonly int id; - private readonly MemoryStream stream = new MemoryStream(); + private readonly MemoryStream stream = new(); + /// + /// Initializes a new instance of the class. + /// + /// The id of the contract. + /// The prefix of the key. public KeyBuilder(int id, byte prefix) { this.id = id; this.stream.WriteByte(prefix); } + /// + /// Adds part of the key to the builder. + /// + /// Part of the key. + /// A reference to this instance after the add operation has completed. public KeyBuilder Add(ReadOnlySpan key) { stream.Write(key); return this; } + /// + /// Adds part of the key to the builder. + /// + /// Part of the key. + /// A reference to this instance after the add operation has completed. public KeyBuilder Add(ISerializable key) { - using (BinaryWriter writer = new BinaryWriter(stream, Utility.StrictUTF8, true)) + using (BinaryWriter writer = new(stream, Utility.StrictUTF8, true)) { key.Serialize(writer); writer.Flush(); @@ -31,19 +49,35 @@ public KeyBuilder Add(ISerializable key) return this; } + /// + /// Adds part of the key to the builder. + /// + /// The type of the parameter. + /// Part of the key. + /// A reference to this instance after the add operation has completed. unsafe public KeyBuilder Add(T key) where T : unmanaged { return Add(new ReadOnlySpan(&key, sizeof(T))); } + /// + /// Adds part of the key to the builder with big-endian. + /// + /// The type of the parameter. + /// Part of the key. + /// A reference to this instance after the add operation has completed. unsafe public KeyBuilder AddBigEndian(T key) where T : unmanaged { - ReadOnlySpan buffer = new ReadOnlySpan(&key, sizeof(T)); + ReadOnlySpan buffer = new(&key, sizeof(T)); for (int i = buffer.Length - 1; i >= 0; i--) stream.WriteByte(buffer[i]); return this; } + /// + /// Gets the storage key generated by the builder. + /// + /// The storage key. public byte[] ToArray() { using (stream) diff --git a/src/neo/SmartContract/LogEventArgs.cs b/src/neo/SmartContract/LogEventArgs.cs index 8c007fe101..7173ef6226 100644 --- a/src/neo/SmartContract/LogEventArgs.cs +++ b/src/neo/SmartContract/LogEventArgs.cs @@ -3,12 +3,32 @@ namespace Neo.SmartContract { + /// + /// The of . + /// public class LogEventArgs : EventArgs { + /// + /// The container that containing the executed script. + /// public IVerifiable ScriptContainer { get; } + + /// + /// The script hash of the contract that sends the log. + /// public UInt160 ScriptHash { get; } + + /// + /// The message of the log. + /// public string Message { get; } + /// + /// Initializes a new instance of the class. + /// + /// The container that containing the executed script. + /// The script hash of the contract that sends the log. + /// The message of the log. public LogEventArgs(IVerifiable container, UInt160 script_hash, string message) { this.ScriptContainer = container; diff --git a/src/neo/SmartContract/Manifest/ContractAbi.cs b/src/neo/SmartContract/Manifest/ContractAbi.cs index db4fdfe344..0213bf0b7c 100644 --- a/src/neo/SmartContract/Manifest/ContractAbi.cs +++ b/src/neo/SmartContract/Manifest/ContractAbi.cs @@ -9,19 +9,20 @@ namespace Neo.SmartContract.Manifest { /// - /// NeoContract ABI + /// Represents the ABI of a smart contract. /// + /// For more details, see NEP-14. public class ContractAbi : IInteroperable { private IReadOnlyDictionary<(string, int), ContractMethodDescriptor> methodDictionary; /// - /// Methods is an array of Method objects which describe the details of each method in the contract. + /// Gets the methods in the ABI. /// public ContractMethodDescriptor[] Methods { get; set; } /// - /// Events is an array of Event objects which describe the details of each event in the contract. + /// Gets the events in the ABI. /// public ContractEventDescriptor[] Events { get; set; } @@ -42,13 +43,13 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter) } /// - /// Parse ContractAbi from json + /// Converts the ABI from a JSON object. /// - /// Json - /// Return ContractAbi + /// The ABI represented by a JSON object. + /// The converted ABI. public static ContractAbi FromJson(JObject json) { - ContractAbi abi = new ContractAbi + ContractAbi abi = new() { Methods = ((JArray)json["methods"]).Select(u => ContractMethodDescriptor.FromJson(u)).ToArray(), Events = ((JArray)json["events"]).Select(u => ContractEventDescriptor.FromJson(u)).ToArray() @@ -57,6 +58,12 @@ public static ContractAbi FromJson(JObject json) return abi; } + /// + /// Gets the method with the specified name. + /// + /// The name of the method. + /// The number of parameters of the method. It can be set to -1 to search for the method with the specified name and any number of parameters. + /// The method that matches the specified name and number of parameters. If is set to -1, the first method with the specified name will be returned. public ContractMethodDescriptor GetMethod(string name, int pcount) { if (pcount < -1 || pcount > ushort.MaxValue) throw new ArgumentOutOfRangeException(nameof(pcount)); @@ -72,6 +79,10 @@ public ContractMethodDescriptor GetMethod(string name, int pcount) } } + /// + /// Converts the ABI to a JSON object. + /// + /// The ABI represented by a JSON object. public JObject ToJson() { var json = new JObject(); diff --git a/src/neo/SmartContract/Manifest/ContractEventDescriptor.cs b/src/neo/SmartContract/Manifest/ContractEventDescriptor.cs index 7d8afd1121..18b1415ab9 100644 --- a/src/neo/SmartContract/Manifest/ContractEventDescriptor.cs +++ b/src/neo/SmartContract/Manifest/ContractEventDescriptor.cs @@ -7,15 +7,18 @@ namespace Neo.SmartContract.Manifest { + /// + /// Represents an event in a smart contract ABI. + /// public class ContractEventDescriptor : IInteroperable { /// - /// Name is the name of the method, which can be any valid identifier. + /// The name of the event or method. /// public string Name { get; set; } /// - /// Parameters is an array of Parameter objects which describe the details of each parameter in the method. + /// The parameters of the event or method. /// public ContractParameterDefinition[] Parameters { get; set; } @@ -36,15 +39,15 @@ public virtual StackItem ToStackItem(ReferenceCounter referenceCounter) } /// - /// Parse ContractEventDescriptor from json + /// Converts the event from a JSON object. /// - /// Json - /// Return ContractEventDescriptor + /// The event represented by a JSON object. + /// The converted event. public static ContractEventDescriptor FromJson(JObject json) { - ContractEventDescriptor descriptor = new ContractEventDescriptor + ContractEventDescriptor descriptor = new() { - Name = json["name"].AsString(), + Name = json["name"].GetString(), Parameters = ((JArray)json["parameters"]).Select(u => ContractParameterDefinition.FromJson(u)).ToArray(), }; if (string.IsNullOrEmpty(descriptor.Name)) throw new FormatException(); @@ -52,6 +55,10 @@ public static ContractEventDescriptor FromJson(JObject json) return descriptor; } + /// + /// Converts the event to a JSON object. + /// + /// The event represented by a JSON object. public virtual JObject ToJson() { var json = new JObject(); diff --git a/src/neo/SmartContract/Manifest/ContractGroup.cs b/src/neo/SmartContract/Manifest/ContractGroup.cs index b39c44c0dd..3b3fd66bdd 100644 --- a/src/neo/SmartContract/Manifest/ContractGroup.cs +++ b/src/neo/SmartContract/Manifest/ContractGroup.cs @@ -9,18 +9,19 @@ namespace Neo.SmartContract.Manifest { /// - /// A group represents a set of mutually trusted contracts. A contract will trust and allow any contract in the same group to invoke it, and the user interface will not give any warnings. + /// Represents a set of mutually trusted contracts. + /// A contract will trust and allow any contract in the same group to invoke it, and the user interface will not give any warnings. /// A group is identified by a public key and must be accompanied by a signature for the contract hash to prove that the contract is indeed included in the group. /// public class ContractGroup : IInteroperable { /// - /// Pubkey represents the public key of the group. + /// The public key of the group. /// public ECPoint PubKey { get; set; } /// - /// Signature is the signature of the contract hash. + /// The signature of the contract hash which can be verified by . /// public byte[] Signature { get; set; } @@ -37,32 +38,36 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter) } /// - /// Parse ContractManifestGroup from json + /// Converts the group from a JSON object. /// - /// Json - /// Return ContractManifestGroup + /// The group represented by a JSON object. + /// The converted group. public static ContractGroup FromJson(JObject json) { - ContractGroup group = new ContractGroup + ContractGroup group = new() { - PubKey = ECPoint.Parse(json["pubkey"].AsString(), ECCurve.Secp256r1), - Signature = Convert.FromBase64String(json["signature"].AsString()), + PubKey = ECPoint.Parse(json["pubkey"].GetString(), ECCurve.Secp256r1), + Signature = Convert.FromBase64String(json["signature"].GetString()), }; if (group.Signature.Length != 64) throw new FormatException(); return group; } /// - /// Return true if the signature is valid + /// Determines whether the signature in the group is valid. /// - /// Contract Hash - /// Return true or false + /// The hash of the contract. + /// if the signature is valid; otherwise, . public bool IsValid(UInt160 hash) { return Crypto.VerifySignature(hash.ToArray(), Signature, PubKey); } - public virtual JObject ToJson() + /// + /// Converts the group to a JSON object. + /// + /// The group represented by a JSON object. + public JObject ToJson() { var json = new JObject(); json["pubkey"] = PubKey.ToString(); diff --git a/src/neo/SmartContract/Manifest/ContractManifest.cs b/src/neo/SmartContract/Manifest/ContractManifest.cs index 71d93020f2..cda156362b 100644 --- a/src/neo/SmartContract/Manifest/ContractManifest.cs +++ b/src/neo/SmartContract/Manifest/ContractManifest.cs @@ -9,49 +9,51 @@ namespace Neo.SmartContract.Manifest { /// + /// Represents the manifest of a smart contract. /// When a smart contract is deployed, it must explicitly declare the features and permissions it will use. /// When it is running, it will be limited by its declared list of features and permissions, and cannot make any behavior beyond the scope of the list. /// + /// For more details, see NEP-15. public class ContractManifest : IInteroperable { /// - /// Max length for a valid Contract Manifest + /// The maximum length of a manifest. /// public const int MaxLength = ushort.MaxValue; /// - /// Contract name + /// The name of the contract. /// public string Name { get; set; } /// - /// A group represents a set of mutually trusted contracts. A contract will trust and allow any contract in the same group to invoke it, and the user interface will not give any warnings. + /// The groups of the contract. /// public ContractGroup[] Groups { get; set; } /// - /// NEP10 - SupportedStandards + /// Indicates which standards the contract supports. It can be a list of NEPs. /// public string[] SupportedStandards { get; set; } /// - /// For technical details of ABI, please refer to NEP-3: NeoContract ABI. (https://github.com/neo-project/proposals/blob/master/nep-3.mediawiki) + /// The ABI of the contract. /// public ContractAbi Abi { get; set; } /// - /// The permissions field is an array containing a set of Permission objects. It describes which contracts may be invoked and which methods are called. + /// The permissions of the contract. /// public ContractPermission[] Permissions { get; set; } /// - /// The trusts field is an array containing a set of contract hashes or group public keys. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that it trusts any contract. + /// The trusted contracts and groups of the contract. /// If a contract is trusted, the user interface will not give any warnings when called by the contract. /// public WildcardContainer Trusts { get; set; } /// - /// Custom user data + /// Custom user data. /// public JObject Extra { get; set; } @@ -87,20 +89,20 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter) } /// - /// Parse ContractManifest from json + /// Converts the manifest from a JSON object. /// - /// Json - /// Return ContractManifest + /// The manifest represented by a JSON object. + /// The converted manifest. public static ContractManifest FromJson(JObject json) { - ContractManifest manifest = new ContractManifest + ContractManifest manifest = new() { - Name = json["name"].AsString(), + Name = json["name"].GetString(), Groups = ((JArray)json["groups"]).Select(u => ContractGroup.FromJson(u)).ToArray(), - SupportedStandards = ((JArray)json["supportedstandards"]).Select(u => u.AsString()).ToArray(), + SupportedStandards = ((JArray)json["supportedstandards"]).Select(u => u.GetString()).ToArray(), Abi = ContractAbi.FromJson(json["abi"]), Permissions = ((JArray)json["permissions"]).Select(u => ContractPermission.FromJson(u)).ToArray(), - Trusts = WildcardContainer.FromJson(json["trusts"], u => UInt160.Parse(u.AsString())), + Trusts = WildcardContainer.FromJson(json["trusts"], u => UInt160.Parse(u.GetString())), Extra = json["extra"] }; if (string.IsNullOrEmpty(manifest.Name)) @@ -115,21 +117,27 @@ public static ContractManifest FromJson(JObject json) } /// - /// Parse ContractManifest from json + /// Parse the manifest from a byte array containing JSON data. /// - /// Json - /// Return ContractManifest + /// The byte array containing JSON data. + /// The parsed manifest. public static ContractManifest Parse(ReadOnlySpan json) { if (json.Length > MaxLength) throw new ArgumentException(null, nameof(json)); return FromJson(JObject.Parse(json)); } + /// + /// Parse the manifest from a JSON . + /// + /// The JSON . + /// The parsed manifest. public static ContractManifest Parse(string json) => Parse(Utility.StrictUTF8.GetBytes(json)); - /// + /// Converts the manifest to a JSON object. /// + /// The manifest represented by a JSON object. public JObject ToJson() { return new JObject @@ -145,9 +153,10 @@ public JObject ToJson() } /// - /// Return true if is valid + /// Determines whether the manifest is valid. /// - /// Return true or false + /// The hash of the contract. + /// if the manifest is valid; otherwise, . public bool IsValid(UInt160 hash) { return Groups.All(u => u.IsValid(hash)); diff --git a/src/neo/SmartContract/Manifest/ContractMethodDescriptor.cs b/src/neo/SmartContract/Manifest/ContractMethodDescriptor.cs index b9374d98e2..b2188750b7 100644 --- a/src/neo/SmartContract/Manifest/ContractMethodDescriptor.cs +++ b/src/neo/SmartContract/Manifest/ContractMethodDescriptor.cs @@ -6,19 +6,24 @@ namespace Neo.SmartContract.Manifest { + /// + /// Represents a method in a smart contract ABI. + /// public class ContractMethodDescriptor : ContractEventDescriptor { /// - /// Returntype indicates the return type of the method. It can be one of the following values: - /// Any, Signature, Boolean, Integer, Hash160, Hash256, ByteArray, PublicKey, String, Array, Map, InteropInterface, Void. + /// Indicates the return type of the method. It can be any value of . /// public ContractParameterType ReturnType { get; set; } + /// + /// The position of the method in the contract script. + /// public int Offset { get; set; } /// - /// Determine if it's safe to call this method - /// If a method is marked as safe, the user interface will not give any warnings when it is called by any other contract. + /// Indicates whether the method is a safe method. + /// If a method is marked as safe, the user interface will not give any warnings when it is called by other contracts. /// public bool Safe { get; set; } @@ -41,19 +46,19 @@ public override StackItem ToStackItem(ReferenceCounter referenceCounter) } /// - /// Parse ContractMethodDescription from json + /// Converts the method from a JSON object. /// - /// Json - /// Return ContractMethodDescription + /// The method represented by a JSON object. + /// The converted method. public new static ContractMethodDescriptor FromJson(JObject json) { - ContractMethodDescriptor descriptor = new ContractMethodDescriptor + ContractMethodDescriptor descriptor = new() { - Name = json["name"].AsString(), + Name = json["name"].GetString(), Parameters = ((JArray)json["parameters"]).Select(u => ContractParameterDefinition.FromJson(u)).ToArray(), - ReturnType = (ContractParameterType)Enum.Parse(typeof(ContractParameterType), json["returntype"].AsString()), - Offset = (int)json["offset"].AsNumber(), - Safe = json["safe"].AsBoolean(), + ReturnType = Enum.Parse(json["returntype"].GetString()), + Offset = json["offset"].GetInt32(), + Safe = json["safe"].GetBoolean() }; if (string.IsNullOrEmpty(descriptor.Name)) throw new FormatException(); _ = descriptor.Parameters.ToDictionary(p => p.Name); @@ -62,6 +67,10 @@ public override StackItem ToStackItem(ReferenceCounter referenceCounter) return descriptor; } + /// + /// Converts the method to a JSON object. + /// + /// The method represented by a JSON object. public override JObject ToJson() { var json = base.ToJson(); diff --git a/src/neo/SmartContract/Manifest/ContractParameterDefinition.cs b/src/neo/SmartContract/Manifest/ContractParameterDefinition.cs index 4bfed05262..0393066517 100644 --- a/src/neo/SmartContract/Manifest/ContractParameterDefinition.cs +++ b/src/neo/SmartContract/Manifest/ContractParameterDefinition.cs @@ -5,16 +5,18 @@ namespace Neo.SmartContract.Manifest { + /// + /// Represents a parameter of an event or method in ABI. + /// public class ContractParameterDefinition : IInteroperable { /// - /// Name is the name of the parameter, which can be any valid identifier. + /// The name of the parameter. /// public string Name { get; set; } /// - /// Type indicates the type of the parameter. It can be one of the following values: - /// Any, Signature, Boolean, Integer, Hash160, Hash256, ByteArray, PublicKey, String, Array, Map, InteropInterface. + /// The type of the parameter. It can be any value of except . /// public ContractParameterType Type { get; set; } @@ -31,16 +33,16 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter) } /// - /// Parse ContractParameterDefinition from json + /// Converts the parameter from a JSON object. /// - /// Json - /// Return ContractParameterDefinition + /// The parameter represented by a JSON object. + /// The converted parameter. public static ContractParameterDefinition FromJson(JObject json) { - ContractParameterDefinition parameter = new ContractParameterDefinition + ContractParameterDefinition parameter = new() { - Name = json["name"].AsString(), - Type = (ContractParameterType)Enum.Parse(typeof(ContractParameterType), json["type"].AsString()), + Name = json["name"].GetString(), + Type = Enum.Parse(json["type"].GetString()) }; if (string.IsNullOrEmpty(parameter.Name)) throw new FormatException(); @@ -49,7 +51,11 @@ public static ContractParameterDefinition FromJson(JObject json) return parameter; } - public virtual JObject ToJson() + /// + /// Converts the parameter to a JSON object. + /// + /// The parameter represented by a JSON object. + public JObject ToJson() { var json = new JObject(); json["name"] = Name; diff --git a/src/neo/SmartContract/Manifest/ContractPermission.cs b/src/neo/SmartContract/Manifest/ContractPermission.cs index 14c2608349..de4e52e4df 100644 --- a/src/neo/SmartContract/Manifest/ContractPermission.cs +++ b/src/neo/SmartContract/Manifest/ContractPermission.cs @@ -9,23 +9,28 @@ namespace Neo.SmartContract.Manifest { /// - /// The permissions field is an array containing a set of Permission objects. It describes which contracts may be invoked and which methods are called. + /// Represents a permission of a contract. It describes which contracts may be invoked and which methods are called. + /// If a contract invokes a contract or method that is not declared in the manifest at runtime, the invocation will fail. /// public class ContractPermission : IInteroperable { /// - /// The contract field indicates the contract to be invoked. It can be a hash of a contract, a public key of a group, or a wildcard *. - /// If it specifies a hash of a contract, then the contract will be invoked; If it specifies a public key of a group, then any contract in this group will be invoked; If it specifies a wildcard*, then any contract will be invoked. + /// Indicates which contract to be invoked. + /// It can be a hash of a contract, a public key of a group, or a wildcard *. + /// If it specifies a hash of a contract, then the contract will be invoked; If it specifies a public key of a group, then any contract in this group may be invoked; If it specifies a wildcard *, then any contract may be invoked. /// public ContractPermissionDescriptor Contract { get; set; } /// - /// The methods field is an array containing a set of methods to be called. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that any method can be called. - /// If a contract invokes a contract or method that is not declared in the manifest at runtime, the invocation will fail. + /// Indicates which methods to be called. + /// It can also be assigned with a wildcard *. If it is a wildcard *, then it means that any method can be called. /// public WildcardContainer Methods { get; set; } - public static readonly ContractPermission DefaultPermission = new ContractPermission + /// + /// A default permission that both and fields are set to wildcard *. + /// + public static readonly ContractPermission DefaultPermission = new() { Contract = ContractPermissionDescriptor.CreateWildcard(), Methods = WildcardContainer.CreateWildcard() @@ -57,16 +62,16 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter) } /// - /// Parse ContractPermission from json + /// Converts the permission from a JSON object. /// - /// Json - /// Return ContractPermission + /// The permission represented by a JSON object. + /// The converted permission. public static ContractPermission FromJson(JObject json) { - ContractPermission permission = new ContractPermission + ContractPermission permission = new() { Contract = ContractPermissionDescriptor.FromJson(json["contract"]), - Methods = WildcardContainer.FromJson(json["methods"], u => u.AsString()), + Methods = WildcardContainer.FromJson(json["methods"], u => u.GetString()), }; if (permission.Methods.Any(p => string.IsNullOrEmpty(p))) throw new FormatException(); @@ -74,9 +79,10 @@ public static ContractPermission FromJson(JObject json) return permission; } - /// + /// Converts the permission to a JSON object. /// + /// The permission represented by a JSON object. public JObject ToJson() { var json = new JObject(); @@ -86,11 +92,11 @@ public JObject ToJson() } /// - /// Return true if is allowed + /// Determines whether the method of the specified contract can be called by this contract. /// - /// The contract that we are calling - /// The method that we are calling - /// Return true or false + /// The contract being called. + /// The method of the specified contract. + /// if the contract allows to be called; otherwise, . public bool IsAllowed(ContractState targetContract, string targetMethod) { if (Contract.IsHash) diff --git a/src/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs b/src/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs index 3ac79727d3..6bd840f2c0 100644 --- a/src/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs +++ b/src/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs @@ -5,13 +5,34 @@ namespace Neo.SmartContract.Manifest { + /// + /// Indicates which contracts are authorized to be called. + /// public class ContractPermissionDescriptor : IEquatable { + /// + /// The hash of the contract. It can't be set with . + /// public UInt160 Hash { get; } + + /// + /// The group of the contracts. It can't be set with . + /// public ECPoint Group { get; } + /// + /// Indicates whether is set. + /// public bool IsHash => Hash != null; + + /// + /// Indicates whether is set. + /// public bool IsGroup => Group != null; + + /// + /// Indicates whether it is a wildcard. + /// public bool IsWildcard => Hash is null && Group is null; private ContractPermissionDescriptor(UInt160 hash, ECPoint group) @@ -35,16 +56,30 @@ internal ContractPermissionDescriptor(ReadOnlySpan span) } } + /// + /// Creates a new instance of the class with the specified contract hash. + /// + /// The contract to be called. + /// The created permission descriptor. public static ContractPermissionDescriptor Create(UInt160 hash) { return new ContractPermissionDescriptor(hash, null); } + /// + /// Creates a new instance of the class with the specified group. + /// + /// The group of the contracts to be called. + /// The created permission descriptor. public static ContractPermissionDescriptor Create(ECPoint group) { return new ContractPermissionDescriptor(null, group); } + /// + /// Creates a new instance of the class with wildcard. + /// + /// The created permission descriptor. public static ContractPermissionDescriptor CreateWildcard() { return new ContractPermissionDescriptor(null, null); @@ -70,9 +105,14 @@ public override int GetHashCode() return HashCode.Combine(Hash, Group); } + /// + /// Converts the permission descriptor from a JSON object. + /// + /// The permission descriptor represented by a JSON object. + /// The converted permission descriptor. public static ContractPermissionDescriptor FromJson(JObject json) { - string str = json.AsString(); + string str = json.GetString(); if (str.Length == 42) return Create(UInt160.Parse(str)); if (str.Length == 66) @@ -82,6 +122,10 @@ public static ContractPermissionDescriptor FromJson(JObject json) throw new FormatException(); } + /// + /// Converts the permission descriptor to a JSON object. + /// + /// The permission descriptor represented by a JSON object. public JObject ToJson() { if (IsHash) return Hash.ToString(); diff --git a/src/neo/SmartContract/Manifest/WildCardContainer.cs b/src/neo/SmartContract/Manifest/WildCardContainer.cs index d85f23fbd8..96ed3f839d 100644 --- a/src/neo/SmartContract/Manifest/WildCardContainer.cs +++ b/src/neo/SmartContract/Manifest/WildCardContainer.cs @@ -6,44 +6,47 @@ namespace Neo.SmartContract.Manifest { + /// + /// A list that supports wildcard. + /// + /// The type of the elements. public class WildcardContainer : IReadOnlyList { private readonly T[] _data; public T this[int index] => _data[index]; - /// - /// Number of items - /// public int Count => _data?.Length ?? 0; /// - /// Is wildcard? + /// Indicates whether the list is a wildcard. /// public bool IsWildcard => _data is null; - /// - /// Constructor - /// - /// Data private WildcardContainer(T[] data) { _data = data; } /// - /// Create a new WildCardContainer + /// Creates a new instance of the class with the initial elements. /// - /// Data - /// WildCardContainer - public static WildcardContainer Create(params T[] data) => new WildcardContainer(data); + /// The initial elements. + /// The created list. + public static WildcardContainer Create(params T[] data) => new(data); /// - /// Create a wildcard + /// Creates a new instance of the class with wildcard. /// - /// WildCardContainer - public static WildcardContainer CreateWildcard() => new WildcardContainer(null); + /// The created list. + public static WildcardContainer CreateWildcard() => new(null); + /// + /// Converts the list from a JSON object. + /// + /// The list represented by a JSON object. + /// A converter for elements. + /// The converted list. public static WildcardContainer FromJson(JObject json, Func elementSelector) { switch (json) @@ -60,13 +63,17 @@ public static WildcardContainer FromJson(JObject json, Func eleme public IEnumerator GetEnumerator() { - if (_data == null) return ((IReadOnlyList)new T[0]).GetEnumerator(); + if (_data == null) return ((IReadOnlyList)Array.Empty()).GetEnumerator(); return ((IReadOnlyList)_data).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// + /// Converts the list to a JSON object. + /// + /// The list represented by a JSON object. public JObject ToJson() { if (IsWildcard) return "*"; diff --git a/src/neo/SmartContract/MethodToken.cs b/src/neo/SmartContract/MethodToken.cs index 42ba7d9c44..90955755e2 100644 --- a/src/neo/SmartContract/MethodToken.cs +++ b/src/neo/SmartContract/MethodToken.cs @@ -5,12 +5,34 @@ namespace Neo.SmartContract { + /// + /// Represents the methods that a contract will call statically. + /// public class MethodToken : ISerializable { + /// + /// The hash of the contract to be called. + /// public UInt160 Hash; + + /// + /// The name of the method to be called. + /// public string Method; + + /// + /// The number of parameters of the method to be called. + /// public ushort ParametersCount; + + /// + /// Indicates whether the method to be called has a return value. + /// public bool HasReturnValue; + + /// + /// The to be used to call the contract. + /// public CallFlags CallFlags; public int Size => @@ -40,6 +62,10 @@ void ISerializable.Serialize(BinaryWriter writer) writer.Write((byte)CallFlags); } + /// + /// Converts the token to a JSON object. + /// + /// The token represented by a JSON object. public JObject ToJson() { return new JObject diff --git a/src/neo/SmartContract/Native/AccountState.cs b/src/neo/SmartContract/Native/AccountState.cs index 491866a799..ca6108d968 100644 --- a/src/neo/SmartContract/Native/AccountState.cs +++ b/src/neo/SmartContract/Native/AccountState.cs @@ -4,8 +4,14 @@ namespace Neo.SmartContract.Native { + /// + /// The base class of account state for all native tokens. + /// public class AccountState : IInteroperable { + /// + /// The balance of the account. + /// public BigInteger Balance; public virtual void FromStackItem(StackItem stackItem) diff --git a/src/neo/SmartContract/Native/ContractManagement.cs b/src/neo/SmartContract/Native/ContractManagement.cs index 5f36991b61..135caa5b55 100644 --- a/src/neo/SmartContract/Native/ContractManagement.cs +++ b/src/neo/SmartContract/Native/ContractManagement.cs @@ -4,7 +4,6 @@ using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract.Manifest; -using Neo.VM; using Neo.VM.Types; using System; using System.Collections.Generic; @@ -13,6 +12,9 @@ namespace Neo.SmartContract.Native { + /// + /// A native contract used to manage all deployed smart contracts. + /// public sealed class ContractManagement : NativeContract { private const byte Prefix_MinimumDeploymentFee = 20; @@ -64,15 +66,6 @@ internal ContractManagement() Manifest.Abi.Events = events.ToArray(); } - private static void Check(byte[] script, ContractAbi abi) - { - Script s = new Script(script, true); - foreach (ContractMethodDescriptor method in abi.Methods) - s.GetInstruction(method.Offset); - abi.GetMethod(string.Empty, 0); // Trigger the construction of ContractAbi.methodDictionary to check the uniqueness of the method names. - _ = abi.Events.ToDictionary(p => p.Name); // Check the uniqueness of the event names. - } - private int GetNextAvailableId(DataCache snapshot) { StorageItem item = snapshot.GetAndChange(CreateStorageKey(Prefix_NextAvailableId)); @@ -81,17 +74,27 @@ private int GetNextAvailableId(DataCache snapshot) return value; } - internal override void Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine) { engine.Snapshot.Add(CreateStorageKey(Prefix_MinimumDeploymentFee), new StorageItem(10_00000000)); engine.Snapshot.Add(CreateStorageKey(Prefix_NextAvailableId), new StorageItem(1)); + return ContractTask.CompletedTask; + } + + private async ContractTask OnDeploy(ApplicationEngine engine, ContractState contract, StackItem data, bool update) + { + ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 2); + if (md is not null) + await engine.CallFromNativeContract(Hash, contract.Hash, md.Name, data, update); + engine.SendNotification(Hash, update ? "Update" : "Deploy", new VM.Types.Array { contract.Hash.ToArray() }); } - internal override void OnPersist(ApplicationEngine engine) + internal override async ContractTask OnPersist(ApplicationEngine engine) { foreach (NativeContract contract in Contracts) { - if (contract.ActiveBlockIndex != engine.PersistingBlock.Index) + uint[] updates = engine.ProtocolSettings.NativeUpdateHistory[contract.Name]; + if (updates.Length == 0 || updates[0] != engine.PersistingBlock.Index) continue; engine.Snapshot.Add(CreateStorageKey(Prefix_Contract).Add(contract.Hash), new StorageItem(new ContractState { @@ -100,17 +103,17 @@ internal override void OnPersist(ApplicationEngine engine) Hash = contract.Hash, Manifest = contract.Manifest })); - contract.Initialize(engine); + await contract.Initialize(engine); } } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] private long GetMinimumDeploymentFee(DataCache snapshot) { return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_MinimumDeploymentFee)]; } - [ContractMethod(0_03000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value) { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value)); @@ -118,26 +121,37 @@ private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value) engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_MinimumDeploymentFee)).Set(value); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the deployed contract with the specified hash. + /// + /// The snapshot used to read data. + /// The hash of the deployed contract. + /// The deployed contract. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public ContractState GetContract(DataCache snapshot, UInt160 hash) { return snapshot.TryGet(CreateStorageKey(Prefix_Contract).Add(hash))?.GetInteroperable(); } + /// + /// Gets all deployed contracts. + /// + /// The snapshot used to read data. + /// The deployed contracts. public IEnumerable ListContracts(DataCache snapshot) { byte[] listContractsPrefix = CreateStorageKey(Prefix_Contract).ToArray(); return snapshot.Find(listContractsPrefix).Select(kvp => kvp.Value.GetInteroperable()); } - [ContractMethod(0, CallFlags.WriteStates | CallFlags.AllowNotify)] - private ContractState Deploy(ApplicationEngine engine, byte[] nefFile, byte[] manifest) + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] + private ContractTask Deploy(ApplicationEngine engine, byte[] nefFile, byte[] manifest) { return Deploy(engine, nefFile, manifest, StackItem.Null); } - [ContractMethod(0, CallFlags.WriteStates | CallFlags.AllowNotify)] - private ContractState Deploy(ApplicationEngine engine, byte[] nefFile, byte[] manifest, StackItem data) + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] + private async ContractTask Deploy(ApplicationEngine engine, byte[] nefFile, byte[] manifest, StackItem data) { if (engine.ScriptContainer is not Transaction tx) throw new InvalidOperationException(); @@ -153,12 +167,12 @@ private ContractState Deploy(ApplicationEngine engine, byte[] nefFile, byte[] ma NefFile nef = nefFile.AsSerializable(); ContractManifest parsedManifest = ContractManifest.Parse(manifest); - Check(nef.Script, parsedManifest.Abi); + Helper.Check(nef.Script, parsedManifest.Abi); UInt160 hash = Helper.GetContractHash(tx.Sender, nef.CheckSum, parsedManifest.Name); StorageKey key = CreateStorageKey(Prefix_Contract).Add(hash); if (engine.Snapshot.Contains(key)) throw new InvalidOperationException($"Contract Already Exists: {hash}"); - ContractState contract = new ContractState + ContractState contract = new() { Id = GetNextAvailableId(engine.Snapshot), UpdateCounter = 0, @@ -171,25 +185,19 @@ private ContractState Deploy(ApplicationEngine engine, byte[] nefFile, byte[] ma engine.Snapshot.Add(key, new StorageItem(contract)); - // Execute _deploy - - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 2); - if (md != null) - engine.CallFromNativeContract(Hash, hash, md.Name, data, false); - - engine.SendNotification(Hash, "Deploy", new VM.Types.Array { contract.Hash.ToArray() }); + await OnDeploy(engine, contract, data, false); return contract; } - [ContractMethod(0, CallFlags.WriteStates | CallFlags.AllowNotify)] - private void Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest) + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] + private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest) { - Update(engine, nefFile, manifest, StackItem.Null); + return Update(engine, nefFile, manifest, StackItem.Null); } - [ContractMethod(0, CallFlags.WriteStates | CallFlags.AllowNotify)] - private void Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest, StackItem data) + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] + private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest, StackItem data) { if (nefFile is null && manifest is null) throw new ArgumentException(); @@ -217,18 +225,12 @@ private void Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest, S throw new InvalidOperationException($"Invalid Manifest Hash: {contract.Hash}"); contract.Manifest = manifest_new; } - Check(contract.Nef.Script, contract.Manifest.Abi); + Helper.Check(contract.Nef.Script, contract.Manifest.Abi); contract.UpdateCounter++; // Increase update counter - if (nefFile != null) - { - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 2); - if (md != null) - engine.CallFromNativeContract(Hash, contract.Hash, md.Name, data, true); - } - engine.SendNotification(Hash, "Update", new VM.Types.Array { contract.Hash.ToArray() }); + return OnDeploy(engine, contract, data, true); } - [ContractMethod(0_01000000, CallFlags.WriteStates | CallFlags.AllowNotify)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] private void Destroy(ApplicationEngine engine) { UInt160 hash = engine.CallingScriptHash; @@ -236,7 +238,7 @@ private void Destroy(ApplicationEngine engine) ContractState contract = engine.Snapshot.TryGet(ckey)?.GetInteroperable(); if (contract is null) return; engine.Snapshot.Delete(ckey); - foreach (var (key, _) in engine.Snapshot.Find(BitConverter.GetBytes(contract.Id))) + foreach (var (key, _) in engine.Snapshot.Find(StorageKey.CreateSearchPrefix(contract.Id, ReadOnlySpan.Empty))) engine.Snapshot.Delete(key); engine.SendNotification(Hash, "Destroy", new VM.Types.Array { hash.ToArray() }); } diff --git a/src/neo/SmartContract/Native/ContractMethodAttribute.cs b/src/neo/SmartContract/Native/ContractMethodAttribute.cs index 4952fc2781..393a8f187f 100644 --- a/src/neo/SmartContract/Native/ContractMethodAttribute.cs +++ b/src/neo/SmartContract/Native/ContractMethodAttribute.cs @@ -5,14 +5,9 @@ namespace Neo.SmartContract.Native [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false)] internal class ContractMethodAttribute : Attribute { - public string Name { get; set; } - public long Price { get; } - public CallFlags RequiredCallFlags { get; } - - public ContractMethodAttribute(long price, CallFlags requiredCallFlags) - { - this.Price = price; - this.RequiredCallFlags = requiredCallFlags; - } + public string Name { get; init; } + public CallFlags RequiredCallFlags { get; init; } + public long CpuFee { get; init; } + public long StorageFee { get; init; } } } diff --git a/src/neo/SmartContract/Native/ContractMethodMetadata.cs b/src/neo/SmartContract/Native/ContractMethodMetadata.cs index ffe7b7f3e3..4f4071c6cc 100644 --- a/src/neo/SmartContract/Native/ContractMethodMetadata.cs +++ b/src/neo/SmartContract/Native/ContractMethodMetadata.cs @@ -1,7 +1,13 @@ +using Neo.Cryptography.ECC; +using Neo.IO; using Neo.Persistence; +using Neo.SmartContract.Manifest; +using Neo.VM.Types; using System; using System.Linq; +using System.Numerics; using System.Reflection; +using Array = Neo.VM.Types.Array; namespace Neo.SmartContract.Native { @@ -12,8 +18,10 @@ internal class ContractMethodMetadata public InteropParameterDescriptor[] Parameters { get; } public bool NeedApplicationEngine { get; } public bool NeedSnapshot { get; } - public long Price { get; } + public long CpuFee { get; } + public long StorageFee { get; } public CallFlags RequiredCallFlags { get; } + public ContractMethodDescriptor Descriptor { get; } public ContractMethodMetadata(MemberInfo member, ContractMethodAttribute attribute) { @@ -22,7 +30,7 @@ public ContractMethodMetadata(MemberInfo member, ContractMethodAttribute attribu { MethodInfo m => m, PropertyInfo p => p.GetMethod, - _ => throw new ArgumentException(nameof(member)) + _ => throw new ArgumentException(null, nameof(member)) }; ParameterInfo[] parameterInfos = this.Handler.GetParameters(); if (parameterInfos.Length > 0) @@ -34,8 +42,53 @@ public ContractMethodMetadata(MemberInfo member, ContractMethodAttribute attribu this.Parameters = parameterInfos.Skip(1).Select(p => new InteropParameterDescriptor(p)).ToArray(); else this.Parameters = parameterInfos.Select(p => new InteropParameterDescriptor(p)).ToArray(); - this.Price = attribute.Price; + this.CpuFee = attribute.CpuFee; + this.StorageFee = attribute.StorageFee; this.RequiredCallFlags = attribute.RequiredCallFlags; + this.Descriptor = new ContractMethodDescriptor + { + Name = Name, + ReturnType = ToParameterType(Handler.ReturnType), + Parameters = Parameters.Select(p => new ContractParameterDefinition { Type = ToParameterType(p.Type), Name = p.Name }).ToArray(), + Safe = (attribute.RequiredCallFlags & ~CallFlags.ReadOnly) == 0 + }; + } + + private static ContractParameterType ToParameterType(Type type) + { + if (type.BaseType == typeof(ContractTask)) return ToParameterType(type.GenericTypeArguments[0]); + if (type == typeof(ContractTask)) return ContractParameterType.Void; + if (type == typeof(void)) return ContractParameterType.Void; + if (type == typeof(bool)) return ContractParameterType.Boolean; + if (type == typeof(sbyte)) return ContractParameterType.Integer; + if (type == typeof(byte)) return ContractParameterType.Integer; + if (type == typeof(short)) return ContractParameterType.Integer; + if (type == typeof(ushort)) return ContractParameterType.Integer; + if (type == typeof(int)) return ContractParameterType.Integer; + if (type == typeof(uint)) return ContractParameterType.Integer; + if (type == typeof(long)) return ContractParameterType.Integer; + if (type == typeof(ulong)) return ContractParameterType.Integer; + if (type == typeof(BigInteger)) return ContractParameterType.Integer; + if (type == typeof(byte[])) return ContractParameterType.ByteArray; + if (type == typeof(string)) return ContractParameterType.String; + if (type == typeof(UInt160)) return ContractParameterType.Hash160; + if (type == typeof(UInt256)) return ContractParameterType.Hash256; + if (type == typeof(ECPoint)) return ContractParameterType.PublicKey; + if (type == typeof(VM.Types.Boolean)) return ContractParameterType.Boolean; + if (type == typeof(Integer)) return ContractParameterType.Integer; + if (type == typeof(ByteString)) return ContractParameterType.ByteArray; + if (type == typeof(VM.Types.Buffer)) return ContractParameterType.ByteArray; + if (type == typeof(Array)) return ContractParameterType.Array; + if (type == typeof(Struct)) return ContractParameterType.Array; + if (type == typeof(Map)) return ContractParameterType.Map; + if (type == typeof(StackItem)) return ContractParameterType.Any; + if (type == typeof(object)) return ContractParameterType.Any; + if (typeof(IInteroperable).IsAssignableFrom(type)) return ContractParameterType.Array; + if (typeof(ISerializable).IsAssignableFrom(type)) return ContractParameterType.ByteArray; + if (type.IsArray) return ContractParameterType.Array; + if (type.IsEnum) return ContractParameterType.Integer; + if (type.IsValueType) return ContractParameterType.Array; + return ContractParameterType.InteropInterface; } } } diff --git a/src/neo/SmartContract/Native/CryptoLib.cs b/src/neo/SmartContract/Native/CryptoLib.cs new file mode 100644 index 0000000000..2511927a3e --- /dev/null +++ b/src/neo/SmartContract/Native/CryptoLib.cs @@ -0,0 +1,64 @@ +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using System; +using System.Collections.Generic; + +namespace Neo.SmartContract.Native +{ + /// + /// A native contract library that provides cryptographic algorithms. + /// + public sealed class CryptoLib : NativeContract + { + private static readonly Dictionary curves = new() + { + [NamedCurve.secp256k1] = ECCurve.Secp256k1, + [NamedCurve.secp256r1] = ECCurve.Secp256r1 + }; + + internal CryptoLib() { } + + /// + /// Computes the hash value for the specified byte array using the ripemd160 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. + [ContractMethod(CpuFee = 1 << 15, Name = "ripemd160")] + public static byte[] RIPEMD160(byte[] data) + { + return data.RIPEMD160(); + } + + /// + /// Computes the hash value for the specified byte array using the sha256 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. + [ContractMethod(CpuFee = 1 << 15)] + public static byte[] Sha256(byte[] data) + { + return data.Sha256(); + } + + /// + /// Verifies that a digital signature is appropriate for the provided key and message using the ECDSA algorithm. + /// + /// The signed message. + /// The public key to be used. + /// The signature to be verified. + /// The curve to be used by the ECDSA algorithm. + /// if the signature is valid; otherwise, . + [ContractMethod(CpuFee = 1 << 15)] + public static bool VerifyWithECDsa(byte[] message, byte[] pubkey, byte[] signature, NamedCurve curve) + { + try + { + return Crypto.VerifySignature(message, signature, pubkey, curves[curve]); + } + catch (ArgumentException) + { + return false; + } + } + } +} diff --git a/src/neo/SmartContract/Native/FungibleToken.cs b/src/neo/SmartContract/Native/FungibleToken.cs index 30f931c2c1..cd90b86ba7 100644 --- a/src/neo/SmartContract/Native/FungibleToken.cs +++ b/src/neo/SmartContract/Native/FungibleToken.cs @@ -9,18 +9,43 @@ namespace Neo.SmartContract.Native { + /// + /// The base class of all native tokens that are compatible with NEP-17. + /// + /// The type of account state. public abstract class FungibleToken : NativeContract where TState : AccountState, new() { - [ContractMethod(0, CallFlags.None)] + /// + /// The symbol of the token. + /// + [ContractMethod] public abstract string Symbol { get; } - [ContractMethod(0, CallFlags.None)] + + /// + /// The number of decimal places of the token. + /// + [ContractMethod] public abstract byte Decimals { get; } + + /// + /// The factor used when calculating the displayed value of the token value. + /// public BigInteger Factor { get; } + /// + /// The prefix for storing total supply. + /// protected const byte Prefix_TotalSupply = 11; + + /// + /// The prefix for storing account states. + /// protected const byte Prefix_Account = 20; + /// + /// Initializes a new instance of the class. + /// protected FungibleToken() { this.Factor = BigInteger.Pow(10, Decimals); @@ -56,20 +81,20 @@ protected FungibleToken() Manifest.Abi.Events = events.ToArray(); } - internal protected virtual void Mint(ApplicationEngine engine, UInt160 account, BigInteger amount, bool callOnPayment) + internal async ContractTask Mint(ApplicationEngine engine, UInt160 account, BigInteger amount, bool callOnPayment) { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (amount.IsZero) return; StorageItem storage = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Account).Add(account), () => new StorageItem(new TState())); TState state = storage.GetInteroperable(); - OnBalanceChanging(engine, account, state, amount); + await OnBalanceChanging(engine, account, state, amount); state.Balance += amount; storage = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_TotalSupply), () => new StorageItem(BigInteger.Zero)); storage.Add(amount); - PostTransfer(engine, null, account, amount, StackItem.Null, callOnPayment); + await PostTransfer(engine, null, account, amount, StackItem.Null, callOnPayment); } - internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) + internal async ContractTask Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (amount.IsZero) return; @@ -77,17 +102,22 @@ internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, StorageItem storage = engine.Snapshot.GetAndChange(key); TState state = storage.GetInteroperable(); if (state.Balance < amount) throw new InvalidOperationException(); - OnBalanceChanging(engine, account, state, -amount); + await OnBalanceChanging(engine, account, state, -amount); if (state.Balance == amount) engine.Snapshot.Delete(key); else state.Balance -= amount; storage = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_TotalSupply)); storage.Add(-amount); - PostTransfer(engine, account, null, amount, StackItem.Null, false); + await PostTransfer(engine, account, null, amount, StackItem.Null, false); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the total supply of the token. + /// + /// The snapshot used to read data. + /// The total supply of the token. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public virtual BigInteger TotalSupply(DataCache snapshot) { StorageItem storage = snapshot.TryGet(CreateStorageKey(Prefix_TotalSupply)); @@ -95,7 +125,13 @@ public virtual BigInteger TotalSupply(DataCache snapshot) return storage; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the balance of the specified account. + /// + /// The snapshot used to read data. + /// The owner of the account. + /// The balance of the account. Or 0 if the account doesn't exist. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public virtual BigInteger BalanceOf(DataCache snapshot, UInt160 account) { StorageItem storage = snapshot.TryGet(CreateStorageKey(Prefix_Account).Add(account)); @@ -103,8 +139,8 @@ public virtual BigInteger BalanceOf(DataCache snapshot, UInt160 account) return storage.GetInteroperable().Balance; } - [ContractMethod(0_09000000, CallFlags.WriteStates | CallFlags.AllowCall | CallFlags.AllowNotify)] - protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount, StackItem data) + [ContractMethod(CpuFee = 1 << 17, StorageFee = 50, RequiredCallFlags = CallFlags.States | CallFlags.AllowCall | CallFlags.AllowNotify)] + private protected async ContractTask Transfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount, StackItem data) { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (!from.Equals(engine.CallingScriptHash) && !engine.CheckWitnessInternal(from)) @@ -116,7 +152,7 @@ protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160 if (storage_from != null) { TState state_from = storage_from.GetInteroperable(); - OnBalanceChanging(engine, from, state_from, amount); + await OnBalanceChanging(engine, from, state_from, amount); } } else @@ -126,11 +162,11 @@ protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160 if (state_from.Balance < amount) return false; if (from.Equals(to)) { - OnBalanceChanging(engine, from, state_from, BigInteger.Zero); + await OnBalanceChanging(engine, from, state_from, BigInteger.Zero); } else { - OnBalanceChanging(engine, from, state_from, -amount); + await OnBalanceChanging(engine, from, state_from, -amount); if (state_from.Balance == amount) engine.Snapshot.Delete(key_from); else @@ -138,19 +174,20 @@ protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160 StorageKey key_to = CreateStorageKey(Prefix_Account).Add(to); StorageItem storage_to = engine.Snapshot.GetAndChange(key_to, () => new StorageItem(new TState())); TState state_to = storage_to.GetInteroperable(); - OnBalanceChanging(engine, to, state_to, amount); + await OnBalanceChanging(engine, to, state_to, amount); state_to.Balance += amount; } } - PostTransfer(engine, from, to, amount, data, true); + await PostTransfer(engine, from, to, amount, data, true); return true; } - protected virtual void OnBalanceChanging(ApplicationEngine engine, UInt160 account, TState state, BigInteger amount) + internal virtual ContractTask OnBalanceChanging(ApplicationEngine engine, UInt160 account, TState state, BigInteger amount) { + return ContractTask.CompletedTask; } - private void PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount, StackItem data, bool callOnPayment) + private async ContractTask PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount, StackItem data, bool callOnPayment) { // Send notification @@ -163,7 +200,7 @@ private void PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, Bi // Call onNEP17Payment method - engine.CallFromNativeContract(Hash, to, "onNEP17Payment", from?.ToArray() ?? StackItem.Null, amount, data); + await engine.CallFromNativeContract(Hash, to, "onNEP17Payment", from?.ToArray() ?? StackItem.Null, amount, data); } } } diff --git a/src/neo/SmartContract/Native/GasToken.cs b/src/neo/SmartContract/Native/GasToken.cs index a8d51122f7..12e308a942 100644 --- a/src/neo/SmartContract/Native/GasToken.cs +++ b/src/neo/SmartContract/Native/GasToken.cs @@ -1,9 +1,11 @@ using Neo.Cryptography.ECC; -using Neo.Ledger; using Neo.Network.P2P.Payloads; namespace Neo.SmartContract.Native { + /// + /// Represents the GAS token in the NEO system. + /// public sealed class GasToken : FungibleToken { public override string Symbol => "GAS"; @@ -13,23 +15,23 @@ internal GasToken() { } - internal override void Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine) { - UInt160 account = Contract.GetBFTAddress(Blockchain.StandbyValidators); - Mint(engine, account, 30_000_000 * Factor, false); + UInt160 account = Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators); + return Mint(engine, account, 30_000_000 * Factor, false); } - internal override void OnPersist(ApplicationEngine engine) + internal override async ContractTask OnPersist(ApplicationEngine engine) { long totalNetworkFee = 0; foreach (Transaction tx in engine.PersistingBlock.Transactions) { - Burn(engine, tx.Sender, tx.SystemFee + tx.NetworkFee); + await Burn(engine, tx.Sender, tx.SystemFee + tx.NetworkFee); totalNetworkFee += tx.NetworkFee; } - ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot); - UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.PersistingBlock.ConsensusData.PrimaryIndex]).ToScriptHash(); - Mint(engine, primary, totalNetworkFee, false); + ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot, engine.ProtocolSettings.ValidatorsCount); + UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.PersistingBlock.PrimaryIndex]).ToScriptHash(); + await Mint(engine, primary, totalNetworkFee, false); } } } diff --git a/src/neo/SmartContract/Native/LedgerContract.cs b/src/neo/SmartContract/Native/LedgerContract.cs index 2658584c7f..3d087e0b93 100644 --- a/src/neo/SmartContract/Native/LedgerContract.cs +++ b/src/neo/SmartContract/Native/LedgerContract.cs @@ -9,6 +9,9 @@ namespace Neo.SmartContract.Native { + /// + /// A native contract for storing all blocks and transactions. + /// public sealed class LedgerContract : NativeContract { private const byte Prefix_BlockHash = 9; @@ -20,25 +23,27 @@ internal LedgerContract() { } - internal override void OnPersist(ApplicationEngine engine) + internal override ContractTask OnPersist(ApplicationEngine engine) { - engine.Snapshot.Add(CreateStorageKey(Prefix_BlockHash).AddBigEndian(engine.PersistingBlock.Index), new StorageItem(engine.PersistingBlock.Hash.ToArray(), true)); - engine.Snapshot.Add(CreateStorageKey(Prefix_Block).Add(engine.PersistingBlock.Hash), new StorageItem(Trim(engine.PersistingBlock).ToArray(), true)); + engine.Snapshot.Add(CreateStorageKey(Prefix_BlockHash).AddBigEndian(engine.PersistingBlock.Index), new StorageItem(engine.PersistingBlock.Hash.ToArray())); + engine.Snapshot.Add(CreateStorageKey(Prefix_Block).Add(engine.PersistingBlock.Hash), new StorageItem(Trim(engine.PersistingBlock).ToArray())); foreach (Transaction tx in engine.PersistingBlock.Transactions) { engine.Snapshot.Add(CreateStorageKey(Prefix_Transaction).Add(tx.Hash), new StorageItem(new TransactionState { BlockIndex = engine.PersistingBlock.Index, Transaction = tx - }, true)); + })); } + return ContractTask.CompletedTask; } - internal override void PostPersist(ApplicationEngine engine) + internal override ContractTask PostPersist(ApplicationEngine engine) { HashIndexState state = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_CurrentBlock), () => new StorageItem(new HashIndexState())).GetInteroperable(); state.Hash = engine.PersistingBlock.Hash; state.Index = engine.PersistingBlock.Index; + return ContractTask.CompletedTask; } internal bool Initialized(DataCache snapshot) @@ -46,13 +51,19 @@ internal bool Initialized(DataCache snapshot) return snapshot.Find(CreateStorageKey(Prefix_Block).ToArray()).Any(); } - private bool IsTraceableBlock(DataCache snapshot, uint index) + private bool IsTraceableBlock(DataCache snapshot, uint index, uint maxTraceableBlocks) { uint currentIndex = CurrentIndex(snapshot); if (index > currentIndex) return false; - return index + ProtocolSettings.Default.MaxTraceableBlocks > currentIndex; + return index + maxTraceableBlocks > currentIndex; } + /// + /// Gets the hash of the specified block. + /// + /// The snapshot used to read data. + /// The index of the block. + /// The hash of the block. public UInt256 GetBlockHash(DataCache snapshot, uint index) { StorageItem item = snapshot.TryGet(CreateStorageKey(Prefix_BlockHash).AddBigEndian(index)); @@ -60,28 +71,56 @@ public UInt256 GetBlockHash(DataCache snapshot, uint index) return new UInt256(item.Value); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the hash of the current block. + /// + /// The snapshot used to read data. + /// The hash of the current block. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public UInt256 CurrentHash(DataCache snapshot) { return snapshot[CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable().Hash; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the index of the current block. + /// + /// The snapshot used to read data. + /// The index of the current block. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint CurrentIndex(DataCache snapshot) { return snapshot[CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable().Index; } + /// + /// Determine whether the specified block is contained in the blockchain. + /// + /// The snapshot used to read data. + /// The hash of the block. + /// if the blockchain contains the block; otherwise, . public bool ContainsBlock(DataCache snapshot, UInt256 hash) { return snapshot.Contains(CreateStorageKey(Prefix_Block).Add(hash)); } + /// + /// Determine whether the specified transaction is contained in the blockchain. + /// + /// The snapshot used to read data. + /// The hash of the transaction. + /// if the blockchain contains the transaction; otherwise, . public bool ContainsTransaction(DataCache snapshot, UInt256 hash) { return snapshot.Contains(CreateStorageKey(Prefix_Transaction).Add(hash)); } + /// + /// Gets a with the specified hash. + /// + /// The snapshot used to read data. + /// The hash of the block. + /// The trimmed block. public TrimmedBlock GetTrimmedBlock(DataCache snapshot, UInt256 hash) { StorageItem item = snapshot.TryGet(CreateStorageKey(Prefix_Block).Add(hash)); @@ -89,40 +128,45 @@ public TrimmedBlock GetTrimmedBlock(DataCache snapshot, UInt256 hash) return item.Value.AsSerializable(); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] - private TrimmedBlock GetBlock(DataCache snapshot, byte[] indexOrHash) + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] + private TrimmedBlock GetBlock(ApplicationEngine engine, byte[] indexOrHash) { UInt256 hash; if (indexOrHash.Length < UInt256.Length) - hash = GetBlockHash(snapshot, (uint)new BigInteger(indexOrHash)); + hash = GetBlockHash(engine.Snapshot, (uint)new BigInteger(indexOrHash)); else if (indexOrHash.Length == UInt256.Length) hash = new UInt256(indexOrHash); else throw new ArgumentException(null, nameof(indexOrHash)); if (hash is null) return null; - TrimmedBlock block = GetTrimmedBlock(snapshot, hash); - if (block is null || !IsTraceableBlock(snapshot, block.Index)) return null; + TrimmedBlock block = GetTrimmedBlock(engine.Snapshot, hash); + if (block is null || !IsTraceableBlock(engine.Snapshot, block.Index, engine.ProtocolSettings.MaxTraceableBlocks)) return null; return block; } + /// + /// Gets a block with the specified hash. + /// + /// The snapshot used to read data. + /// The hash of the block. + /// The block with the specified hash. public Block GetBlock(DataCache snapshot, UInt256 hash) { TrimmedBlock state = GetTrimmedBlock(snapshot, hash); if (state is null) return null; return new Block { - Version = state.Version, - PrevHash = state.PrevHash, - MerkleRoot = state.MerkleRoot, - Timestamp = state.Timestamp, - Index = state.Index, - NextConsensus = state.NextConsensus, - Witness = state.Witness, - ConsensusData = state.ConsensusData, - Transactions = state.Hashes.Skip(1).Select(p => GetTransaction(snapshot, p)).ToArray() + Header = state.Header, + Transactions = state.Hashes.Select(p => GetTransaction(snapshot, p)).ToArray() }; } + /// + /// Gets a block with the specified index. + /// + /// The snapshot used to read data. + /// The index of the block. + /// The block with the specified index. public Block GetBlock(DataCache snapshot, uint index) { UInt256 hash = GetBlockHash(snapshot, index); @@ -130,11 +174,23 @@ public Block GetBlock(DataCache snapshot, uint index) return GetBlock(snapshot, hash); } + /// + /// Gets a block header with the specified hash. + /// + /// The snapshot used to read data. + /// The hash of the block. + /// The block header with the specified hash. public Header GetHeader(DataCache snapshot, UInt256 hash) { return GetTrimmedBlock(snapshot, hash)?.Header; } + /// + /// Gets a block header with the specified index. + /// + /// The snapshot used to read data. + /// The index of the block. + /// The block header with the specified index. public Header GetHeader(DataCache snapshot, uint index) { UInt256 hash = GetBlockHash(snapshot, index); @@ -142,63 +198,68 @@ public Header GetHeader(DataCache snapshot, uint index) return GetHeader(snapshot, hash); } + /// + /// Gets a with the specified hash. + /// + /// The snapshot used to read data. + /// The hash of the transaction. + /// The with the specified hash. public TransactionState GetTransactionState(DataCache snapshot, UInt256 hash) { return snapshot.TryGet(CreateStorageKey(Prefix_Transaction).Add(hash))?.GetInteroperable(); } + /// + /// Gets a transaction with the specified hash. + /// + /// The snapshot used to read data. + /// The hash of the transaction. + /// The transaction with the specified hash. public Transaction GetTransaction(DataCache snapshot, UInt256 hash) { return GetTransactionState(snapshot, hash)?.Transaction; } - [ContractMethod(0_01000000, CallFlags.ReadStates, Name = "getTransaction")] - private Transaction GetTransactionForContract(DataCache snapshot, UInt256 hash) + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates, Name = "getTransaction")] + private Transaction GetTransactionForContract(ApplicationEngine engine, UInt256 hash) { - TransactionState state = GetTransactionState(snapshot, hash); - if (state is null || !IsTraceableBlock(snapshot, state.BlockIndex)) return null; + TransactionState state = GetTransactionState(engine.Snapshot, hash); + if (state is null || !IsTraceableBlock(engine.Snapshot, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return null; return state.Transaction; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] - private int GetTransactionHeight(DataCache snapshot, UInt256 hash) + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] + private int GetTransactionHeight(ApplicationEngine engine, UInt256 hash) { - TransactionState state = GetTransactionState(snapshot, hash); - if (state is null || !IsTraceableBlock(snapshot, state.BlockIndex)) return -1; + TransactionState state = GetTransactionState(engine.Snapshot, hash); + if (state is null || !IsTraceableBlock(engine.Snapshot, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return -1; return (int)state.BlockIndex; } - [ContractMethod(0_02000000, CallFlags.ReadStates)] - private Transaction GetTransactionFromBlock(DataCache snapshot, byte[] blockIndexOrHash, int txIndex) + [ContractMethod(CpuFee = 1 << 16, RequiredCallFlags = CallFlags.ReadStates)] + private Transaction GetTransactionFromBlock(ApplicationEngine engine, byte[] blockIndexOrHash, int txIndex) { UInt256 hash; if (blockIndexOrHash.Length < UInt256.Length) - hash = GetBlockHash(snapshot, (uint)new BigInteger(blockIndexOrHash)); + hash = GetBlockHash(engine.Snapshot, (uint)new BigInteger(blockIndexOrHash)); else if (blockIndexOrHash.Length == UInt256.Length) hash = new UInt256(blockIndexOrHash); else throw new ArgumentException(null, nameof(blockIndexOrHash)); if (hash is null) return null; - TrimmedBlock block = GetTrimmedBlock(snapshot, hash); - if (block is null || !IsTraceableBlock(snapshot, block.Index)) return null; - if (txIndex < 0 || txIndex >= block.Hashes.Length - 1) + TrimmedBlock block = GetTrimmedBlock(engine.Snapshot, hash); + if (block is null || !IsTraceableBlock(engine.Snapshot, block.Index, engine.ProtocolSettings.MaxTraceableBlocks)) return null; + if (txIndex < 0 || txIndex >= block.Hashes.Length) throw new ArgumentOutOfRangeException(nameof(txIndex)); - return GetTransaction(snapshot, block.Hashes[txIndex + 1]); + return GetTransaction(engine.Snapshot, block.Hashes[txIndex]); } private static TrimmedBlock Trim(Block block) { return new TrimmedBlock { - Version = block.Version, - PrevHash = block.PrevHash, - MerkleRoot = block.MerkleRoot, - Timestamp = block.Timestamp, - Index = block.Index, - NextConsensus = block.NextConsensus, - Witness = block.Witness, - Hashes = block.Transactions.Select(p => p.Hash).Prepend(block.ConsensusData.Hash).ToArray(), - ConsensusData = block.ConsensusData + Header = block.Header, + Hashes = block.Transactions.Select(p => p.Hash).ToArray() }; } } diff --git a/src/neo/SmartContract/Native/NFTState.cs b/src/neo/SmartContract/Native/NFTState.cs index 8bcb85948c..59496e5792 100644 --- a/src/neo/SmartContract/Native/NFTState.cs +++ b/src/neo/SmartContract/Native/NFTState.cs @@ -5,21 +5,34 @@ namespace Neo.SmartContract.Native { + /// + /// The base class of the token states for . + /// public abstract class NFTState : IInteroperable { + /// + /// The owner of the token. + /// public UInt160 Owner; + + /// + /// The name of the token. + /// public string Name; - public string Description; + /// + /// The id of the token. + /// public abstract byte[] Id { get; } + /// + /// Converts the token to a . + /// + /// The used by the . + /// The converted map. public virtual Map ToMap(ReferenceCounter referenceCounter) { - return new Map(referenceCounter) - { - ["name"] = Name, - ["description"] = Description - }; + return new Map(referenceCounter) { ["name"] = Name }; } public virtual void FromStackItem(StackItem stackItem) @@ -27,12 +40,11 @@ public virtual void FromStackItem(StackItem stackItem) Struct @struct = (Struct)stackItem; Owner = new UInt160(@struct[0].GetSpan()); Name = @struct[1].GetString(); - Description = @struct[2].GetString(); } public virtual StackItem ToStackItem(ReferenceCounter referenceCounter) { - return new Struct(referenceCounter) { Owner.ToArray(), Name, Description }; + return new Struct(referenceCounter) { Owner.ToArray(), Name }; } } } diff --git a/src/neo/SmartContract/Native/NameService.cs b/src/neo/SmartContract/Native/NameService.cs index 4e6bec1d36..d6ebea8ff9 100644 --- a/src/neo/SmartContract/Native/NameService.cs +++ b/src/neo/SmartContract/Native/NameService.cs @@ -15,6 +15,9 @@ namespace Neo.SmartContract.Native { + /// + /// A native name service for NEO system. + /// public sealed class NameService : NonfungibleToken { public override string Symbol => "NNS"; @@ -25,23 +28,23 @@ public sealed class NameService : NonfungibleToken private const byte Prefix_Record = 12; private const uint OneYear = 365 * 24 * 3600; - private static readonly Regex rootRegex = new Regex("^[a-z][a-z0-9]{0,15}$", RegexOptions.Singleline); - private static readonly Regex nameRegex = new Regex("^(?=.{3,255}$)([a-z0-9]{1,62}\\.)+[a-z][a-z0-9]{0,15}$", RegexOptions.Singleline); - private static readonly Regex ipv4Regex = new Regex("^(2(5[0-5]|[0-4]\\d))|1?\\d{1,2}(\\.((2(5[0-5]|[0-4]\\d))|1?\\d{1,2})){3}$", RegexOptions.Singleline); - private static readonly Regex ipv6Regex = new Regex("^([a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$", RegexOptions.Singleline | RegexOptions.IgnoreCase); + private static readonly Regex rootRegex = new("^[a-z][a-z0-9]{0,15}$", RegexOptions.Singleline); + private static readonly Regex nameRegex = new("^(?=.{3,255}$)([a-z0-9]{1,62}\\.)+[a-z][a-z0-9]{0,15}$", RegexOptions.Singleline); + private static readonly Regex ipv4Regex = new("^(?=\\d+\\.\\d+\\.\\d+\\.\\d+$)(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\\.?){4}$", RegexOptions.Singleline); + private static readonly Regex ipv6Regex = new("(?:^)(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,7}:|([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}|([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}|([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}|([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}|[0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})|:((:[0-9a-f]{1,4}){1,7}|:))(?=$)", RegexOptions.Singleline); internal NameService() { } - internal override void Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine) { - base.Initialize(engine); engine.Snapshot.Add(CreateStorageKey(Prefix_Roots), new StorageItem(new StringList())); engine.Snapshot.Add(CreateStorageKey(Prefix_DomainPrice), new StorageItem(10_00000000)); + return base.Initialize(engine); } - internal override void OnPersist(ApplicationEngine engine) + internal override async ContractTask OnPersist(ApplicationEngine engine) { uint now = (uint)(engine.PersistingBlock.Timestamp / 1000) + 1; byte[] start = CreateStorageKey(Prefix_Expiration).AddBigEndian(0).ToArray(); @@ -51,7 +54,7 @@ internal override void OnPersist(ApplicationEngine engine) engine.Snapshot.Delete(key); foreach (var (key2, _) in engine.Snapshot.Find(CreateStorageKey(Prefix_Record).Add(key.Key.AsSpan(5)).ToArray())) engine.Snapshot.Delete(key2); - Burn(engine, CreateStorageKey(Prefix_Token).Add(key.Key.AsSpan(5))); + await Burn(engine, CreateStorageKey(Prefix_Token).Add(key.Key.AsSpan(5))); } } @@ -65,7 +68,7 @@ protected override byte[] GetKey(byte[] tokenId) return Crypto.Hash160(tokenId); } - [ContractMethod(0_03000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void AddRoot(ApplicationEngine engine, string root) { if (!rootRegex.IsMatch(root)) throw new ArgumentException(null, nameof(root)); @@ -76,12 +79,17 @@ private void AddRoot(ApplicationEngine engine, string root) roots.Insert(~index, root); } + /// + /// Gets all the root names in the system. + /// + /// The snapshot used to read data. + /// All the root names in the system. public IEnumerable GetRoots(DataCache snapshot) { return snapshot[CreateStorageKey(Prefix_Roots)].GetInteroperable(); } - [ContractMethod(0_03000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetPrice(ApplicationEngine engine, long price) { if (price <= 0 || price > 10000_00000000) throw new ArgumentOutOfRangeException(nameof(price)); @@ -89,13 +97,26 @@ private void SetPrice(ApplicationEngine engine, long price) engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_DomainPrice)).Set(price); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the price for registering a name. + /// + /// The snapshot used to read data. + /// The price for registering a name. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public long GetPrice(DataCache snapshot) { return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_DomainPrice)]; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Determine whether the specified name is available to be registered. + /// + /// The snapshot used to read data. + /// The name to check. + /// if the name is available; otherwise, . + /// The format of is incorrect. + /// The root name doesn't exist. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public bool IsAvailable(DataCache snapshot, string name) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); @@ -108,8 +129,8 @@ public bool IsAvailable(DataCache snapshot, string name) return true; } - [ContractMethod(0_01000000, CallFlags.WriteStates)] - private bool Register(ApplicationEngine engine, string name, UInt160 owner) + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] + private async ContractTask Register(ApplicationEngine engine, string name, UInt160 owner) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); string[] names = name.Split('.'); @@ -120,19 +141,18 @@ private bool Register(ApplicationEngine engine, string name, UInt160 owner) StringList roots = engine.Snapshot[CreateStorageKey(Prefix_Roots)].GetInteroperable(); if (roots.BinarySearch(names[1]) < 0) throw new InvalidOperationException(); engine.AddGas(GetPrice(engine.Snapshot)); - NameState state = new NameState + NameState state = new() { Owner = owner, Name = name, - Description = "", Expiration = (uint)(engine.PersistingBlock.Timestamp / 1000) + OneYear }; - Mint(engine, state); + await Mint(engine, state); engine.Snapshot.Add(CreateStorageKey(Prefix_Expiration).AddBigEndian(state.Expiration).Add(hash), new StorageItem(new byte[] { 0 })); return true; } - [ContractMethod(0, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private uint Renew(ApplicationEngine engine, string name) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); @@ -147,7 +167,7 @@ private uint Renew(ApplicationEngine engine, string name) return state.Expiration; } - [ContractMethod(0_03000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, StorageFee = 20, RequiredCallFlags = CallFlags.States)] private void SetAdmin(ApplicationEngine engine, string name, UInt160 admin) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); @@ -166,7 +186,7 @@ private static bool CheckAdmin(ApplicationEngine engine, NameState state) return engine.CheckWitnessInternal(state.Admin); } - [ContractMethod(0_30000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, StorageFee = 200, RequiredCallFlags = CallFlags.States)] private void SetRecord(ApplicationEngine engine, string name, RecordType type, string data) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); @@ -199,7 +219,14 @@ private void SetRecord(ApplicationEngine engine, string name, RecordType type, s item.Value = Utility.StrictUTF8.GetBytes(data); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets a record for the specified name. + /// + /// The snapshot used to read data. + /// The name for the record. + /// The type of the record. + /// The record without resolved. Or if no record is found. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public string GetRecord(DataCache snapshot, string name, RecordType type) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); @@ -210,6 +237,12 @@ public string GetRecord(DataCache snapshot, string name, RecordType type) return Utility.StrictUTF8.GetString(item.Value); } + /// + /// Gets all the records for the specified name. + /// + /// The snapshot used to read data. + /// The name for the record. + /// All the records for the name. public IEnumerable<(RecordType Type, string Data)> GetRecords(DataCache snapshot, string name) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); @@ -219,7 +252,7 @@ public string GetRecord(DataCache snapshot, string name, RecordType type) yield return ((RecordType)key.Key[^1], Utility.StrictUTF8.GetString(value.Value)); } - [ContractMethod(0_01000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void DeleteRecord(ApplicationEngine engine, string name, RecordType type) { if (!nameRegex.IsMatch(name)) throw new ArgumentException(null, nameof(name)); @@ -230,7 +263,14 @@ private void DeleteRecord(ApplicationEngine engine, string name, RecordType type engine.Snapshot.Delete(CreateStorageKey(Prefix_Record).Add(hash_domain).Add(GetKey(Utility.StrictUTF8.GetBytes(name))).Add(type)); } - [ContractMethod(0_03000000, CallFlags.ReadStates)] + /// + /// Gets and resolves a record for the specified name. + /// + /// The snapshot used to read data. + /// The name for the record. + /// The type of the record. + /// The resolved record. + [ContractMethod(CpuFee = 1 << 17, RequiredCallFlags = CallFlags.ReadStates)] public string Resolve(DataCache snapshot, string name, RecordType type) { return Resolve(snapshot, name, type, 2); @@ -245,9 +285,19 @@ private string Resolve(DataCache snapshot, string name, RecordType type, int red return Resolve(snapshot, data, type, redirect - 1); } + /// + /// The token state of . + /// public class NameState : NFTState { + /// + /// Indicates when the name expires. + /// public uint Expiration; + + /// + /// The administrator of the name. + /// public UInt160 Admin; public override byte[] Id => Utility.StrictUTF8.GetBytes(Name); @@ -263,8 +313,8 @@ public override void FromStackItem(StackItem stackItem) { base.FromStackItem(stackItem); Struct @struct = (Struct)stackItem; - Expiration = (uint)@struct[3].GetInteger(); - Admin = @struct[4].IsNull ? null : new UInt160(@struct[4].GetSpan()); + Expiration = (uint)@struct[2].GetInteger(); + Admin = @struct[3].IsNull ? null : new UInt160(@struct[3].GetSpan()); } public override StackItem ToStackItem(ReferenceCounter referenceCounter) diff --git a/src/neo/SmartContract/Native/NamedCurve.cs b/src/neo/SmartContract/Native/NamedCurve.cs new file mode 100644 index 0000000000..ca5955681b --- /dev/null +++ b/src/neo/SmartContract/Native/NamedCurve.cs @@ -0,0 +1,21 @@ +namespace Neo.SmartContract.Native +{ + /// + /// Represents the named curve used in ECDSA. + /// + /// + /// https://tools.ietf.org/html/rfc4492#section-5.1.1 + /// + public enum NamedCurve : byte + { + /// + /// The secp256k1 curve. + /// + secp256k1 = 22, + + /// + /// The secp256r1 curve, which known as prime256v1 or nistP-256. + /// + secp256r1 = 23 + } +} diff --git a/src/neo/SmartContract/Native/NativeContract.cs b/src/neo/SmartContract/Native/NativeContract.cs index 4796d2a96a..cf6f6484de 100644 --- a/src/neo/SmartContract/Native/NativeContract.cs +++ b/src/neo/SmartContract/Native/NativeContract.cs @@ -1,99 +1,165 @@ using Neo.IO; using Neo.SmartContract.Manifest; using Neo.VM; -using Neo.VM.Types; using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using System.Reflection; -using Array = Neo.VM.Types.Array; namespace Neo.SmartContract.Native { + /// + /// The base class of all native contracts. + /// public abstract class NativeContract { - private static readonly List contractsList = new List(); - private static readonly Dictionary contractsIdDictionary = new Dictionary(); - private static readonly Dictionary contractsHashDictionary = new Dictionary(); - private readonly Dictionary<(string, int), ContractMethodMetadata> methods = new Dictionary<(string, int), ContractMethodMetadata>(); + private static readonly List contractsList = new(); + private static readonly Dictionary contractsDictionary = new(); + private readonly Dictionary methods = new(); private static int id_counter = 0; #region Named Native Contracts - public static ContractManagement ContractManagement { get; } = new ContractManagement(); - public static LedgerContract Ledger { get; } = new LedgerContract(); - public static NeoToken NEO { get; } = new NeoToken(); - public static GasToken GAS { get; } = new GasToken(); - public static PolicyContract Policy { get; } = new PolicyContract(); - public static RoleManagement RoleManagement { get; } = new RoleManagement(); - public static OracleContract Oracle { get; } = new OracleContract(); - public static NameService NameService { get; } = new NameService(); + + /// + /// Gets the instance of the class. + /// + public static ContractManagement ContractManagement { get; } = new(); + + /// + /// Gets the instance of the class. + /// + public static StdLib StdLib { get; } = new(); + + /// + /// Gets the instance of the class. + /// + public static CryptoLib CryptoLib { get; } = new(); + + /// + /// Gets the instance of the class. + /// + public static LedgerContract Ledger { get; } = new(); + + /// + /// Gets the instance of the class. + /// + public static NeoToken NEO { get; } = new(); + + /// + /// Gets the instance of the class. + /// + public static GasToken GAS { get; } = new(); + + /// + /// Gets the instance of the class. + /// + public static PolicyContract Policy { get; } = new(); + + /// + /// Gets the instance of the class. + /// + public static RoleManagement RoleManagement { get; } = new(); + + /// + /// Gets the instance of the class. + /// + public static OracleContract Oracle { get; } = new(); + + /// + /// Gets the instance of the class. + /// + public static NameService NameService { get; } = new(); + #endregion + /// + /// Gets all native contracts. + /// public static IReadOnlyCollection Contracts { get; } = contractsList; + + /// + /// The name of the native contract. + /// public string Name => GetType().Name; + + /// + /// The nef of the native contract. + /// public NefFile Nef { get; } - public byte[] Script => Nef.Script; + + /// + /// The hash of the native contract. + /// public UInt160 Hash { get; } - public int Id { get; } + + /// + /// The id of the native contract. + /// + public int Id { get; } = --id_counter; + + /// + /// The manifest of the native contract. + /// public ContractManifest Manifest { get; } - public uint ActiveBlockIndex { get; } + /// + /// Initializes a new instance of the class. + /// protected NativeContract() { - this.Id = --id_counter; + List descriptors = new(); + foreach (MemberInfo member in GetType().GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) + { + ContractMethodAttribute attribute = member.GetCustomAttribute(); + if (attribute is null) continue; + descriptors.Add(new ContractMethodMetadata(member, attribute)); + } + descriptors = descriptors.OrderBy(p => p.Name).ThenBy(p => p.Parameters.Length).ToList(); byte[] script; - using (ScriptBuilder sb = new ScriptBuilder()) + using (ScriptBuilder sb = new()) { - sb.EmitPush(Id); - sb.EmitSysCall(ApplicationEngine.System_Contract_CallNative); + foreach (ContractMethodMetadata method in descriptors) + { + method.Descriptor.Offset = sb.Length; + sb.EmitPush(0); //version + methods.Add(sb.Length, method); + sb.EmitSysCall(ApplicationEngine.System_Contract_CallNative); + sb.Emit(OpCode.RET); + } script = sb.ToArray(); } this.Nef = new NefFile { Compiler = "neo-core-v3.0", - Tokens = System.Array.Empty(), + Tokens = Array.Empty(), Script = script }; this.Nef.CheckSum = NefFile.ComputeChecksum(Nef); - this.Hash = Helper.GetContractHash(UInt160.Zero, this.Nef.CheckSum, Name); - List descriptors = new List(); - foreach (MemberInfo member in GetType().GetMembers(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) - { - ContractMethodAttribute attribute = member.GetCustomAttribute(); - if (attribute is null) continue; - ContractMethodMetadata metadata = new ContractMethodMetadata(member, attribute); - descriptors.Add(new ContractMethodDescriptor - { - Name = metadata.Name, - ReturnType = ToParameterType(metadata.Handler.ReturnType), - Parameters = metadata.Parameters.Select(p => new ContractParameterDefinition { Type = ToParameterType(p.Type), Name = p.Name }).ToArray(), - Safe = (attribute.RequiredCallFlags & ~CallFlags.ReadOnly) == 0 - }); - methods.Add((metadata.Name, metadata.Parameters.Length), metadata); - } + this.Hash = Helper.GetContractHash(UInt160.Zero, 0, Name); this.Manifest = new ContractManifest { Name = Name, - Groups = System.Array.Empty(), - SupportedStandards = System.Array.Empty(), + Groups = Array.Empty(), + SupportedStandards = Array.Empty(), Abi = new ContractAbi() { - Events = System.Array.Empty(), - Methods = descriptors.OrderBy(p => p.Name).ThenBy(p => p.Parameters.Length).ToArray() + Events = Array.Empty(), + Methods = descriptors.Select(p => p.Descriptor).ToArray() }, Permissions = new[] { ContractPermission.DefaultPermission }, Trusts = WildcardContainer.Create(), Extra = null }; - if (ProtocolSettings.Default.NativeActivations.TryGetValue(Name, out uint activationIndex)) - this.ActiveBlockIndex = activationIndex; contractsList.Add(this); - contractsIdDictionary.Add(Id, this); - contractsHashDictionary.Add(Hash, this); + contractsDictionary.Add(Hash, this); } - protected bool CheckCommittee(ApplicationEngine engine) + /// + /// Checks whether the committee has witnessed the current transaction. + /// + /// The that is executing the contract. + /// if the committee has witnessed the current transaction; otherwise, . + protected static bool CheckCommittee(ApplicationEngine engine) { UInt160 committeeMultiSigAddr = NEO.GetCommitteeAddress(engine.Snapshot); return engine.CheckWitnessInternal(committeeMultiSigAddr); @@ -104,95 +170,74 @@ private protected KeyBuilder CreateStorageKey(byte prefix) return new KeyBuilder(Id, prefix); } + /// + /// Gets the native contract with the specified hash. + /// + /// The hash of the native contract. + /// The native contract with the specified hash. public static NativeContract GetContract(UInt160 hash) { - contractsHashDictionary.TryGetValue(hash, out var contract); + contractsDictionary.TryGetValue(hash, out var contract); return contract; } - public static NativeContract GetContract(int id) + internal async void Invoke(ApplicationEngine engine, byte version) { - contractsIdDictionary.TryGetValue(id, out var contract); - return contract; - } - - internal void Invoke(ApplicationEngine engine) - { - if (!engine.CurrentScriptHash.Equals(Hash)) - throw new InvalidOperationException("It is not allowed to use Neo.Native.Call directly to call native contracts. System.Contract.Call should be used."); - ExecutionContext context = engine.CurrentContext; - string operation = context.EvaluationStack.Pop().GetString(); - ContractMethodMetadata method = methods[(operation, context.EvaluationStack.Count)]; - ExecutionContextState state = context.GetState(); - if (!state.CallFlags.HasFlag(method.RequiredCallFlags)) - throw new InvalidOperationException($"Cannot call this method with the flag {state.CallFlags}."); - engine.AddGas(method.Price); - List parameters = new List(); - if (method.NeedApplicationEngine) parameters.Add(engine); - if (method.NeedSnapshot) parameters.Add(engine.Snapshot); - for (int i = 0; i < method.Parameters.Length; i++) - parameters.Add(engine.Convert(context.EvaluationStack.Pop(), method.Parameters[i])); - object returnValue = method.Handler.Invoke(this, parameters.ToArray()); - if (method.Handler.ReturnType != typeof(void)) - context.EvaluationStack.Push(engine.Convert(returnValue)); + try + { + if (version != 0) + throw new InvalidOperationException($"The native contract of version {version} is not active."); + ExecutionContext context = engine.CurrentContext; + ContractMethodMetadata method = methods[context.InstructionPointer]; + ExecutionContextState state = context.GetState(); + if (!state.CallFlags.HasFlag(method.RequiredCallFlags)) + throw new InvalidOperationException($"Cannot call this method with the flag {state.CallFlags}."); + engine.AddGas(method.CpuFee * Policy.GetExecFeeFactor(engine.Snapshot) + method.StorageFee * Policy.GetStoragePrice(engine.Snapshot)); + List parameters = new(); + if (method.NeedApplicationEngine) parameters.Add(engine); + if (method.NeedSnapshot) parameters.Add(engine.Snapshot); + for (int i = 0; i < method.Parameters.Length; i++) + parameters.Add(engine.Convert(context.EvaluationStack.Pop(), method.Parameters[i])); + object returnValue = method.Handler.Invoke(this, parameters.ToArray()); + if (returnValue is ContractTask task) + { + await task; + returnValue = task.GetResult(); + } + if (method.Handler.ReturnType != typeof(void) && method.Handler.ReturnType != typeof(ContractTask)) + { + context.EvaluationStack.Push(engine.Convert(returnValue)); + } + } + catch (Exception ex) + { + engine.Throw(ex); + } } + /// + /// Determine whether the specified contract is a native contract. + /// + /// The hash of the contract. + /// if the contract is native; otherwise, . public static bool IsNative(UInt160 hash) { - return contractsHashDictionary.ContainsKey(hash); - } - - internal virtual void Initialize(ApplicationEngine engine) - { + return contractsDictionary.ContainsKey(hash); } - internal virtual void OnPersist(ApplicationEngine engine) + internal virtual ContractTask Initialize(ApplicationEngine engine) { + return ContractTask.CompletedTask; } - internal virtual void PostPersist(ApplicationEngine engine) + internal virtual ContractTask OnPersist(ApplicationEngine engine) { + return ContractTask.CompletedTask; } - public ApplicationEngine TestCall(string operation, params object[] args) - { - using (ScriptBuilder sb = new ScriptBuilder()) - { - sb.EmitDynamicCall(Hash, operation, args); - return ApplicationEngine.Run(sb.ToArray()); - } - } - - private static ContractParameterType ToParameterType(Type type) + internal virtual ContractTask PostPersist(ApplicationEngine engine) { - if (type == typeof(void)) return ContractParameterType.Void; - if (type == typeof(bool)) return ContractParameterType.Boolean; - if (type == typeof(sbyte)) return ContractParameterType.Integer; - if (type == typeof(byte)) return ContractParameterType.Integer; - if (type == typeof(short)) return ContractParameterType.Integer; - if (type == typeof(ushort)) return ContractParameterType.Integer; - if (type == typeof(int)) return ContractParameterType.Integer; - if (type == typeof(uint)) return ContractParameterType.Integer; - if (type == typeof(long)) return ContractParameterType.Integer; - if (type == typeof(ulong)) return ContractParameterType.Integer; - if (type == typeof(BigInteger)) return ContractParameterType.Integer; - if (type == typeof(byte[])) return ContractParameterType.ByteArray; - if (type == typeof(string)) return ContractParameterType.String; - if (type == typeof(UInt160)) return ContractParameterType.Hash160; - if (type == typeof(UInt256)) return ContractParameterType.Hash256; - if (type == typeof(VM.Types.Boolean)) return ContractParameterType.Boolean; - if (type == typeof(Integer)) return ContractParameterType.Integer; - if (type == typeof(ByteString)) return ContractParameterType.ByteArray; - if (type == typeof(VM.Types.Buffer)) return ContractParameterType.ByteArray; - if (type == typeof(Array)) return ContractParameterType.Array; - if (type == typeof(Struct)) return ContractParameterType.Array; - if (type == typeof(Map)) return ContractParameterType.Map; - if (type == typeof(StackItem)) return ContractParameterType.Any; - if (typeof(IInteroperable).IsAssignableFrom(type)) return ContractParameterType.Array; - if (typeof(ISerializable).IsAssignableFrom(type)) return ContractParameterType.ByteArray; - if (type.IsArray) return ContractParameterType.Array; - if (type.IsEnum) return ContractParameterType.Integer; - return ContractParameterType.Any; + return ContractTask.CompletedTask; } } } diff --git a/src/neo/SmartContract/Native/NeoToken.cs b/src/neo/SmartContract/Native/NeoToken.cs index dee56c1095..32c10a18ac 100644 --- a/src/neo/SmartContract/Native/NeoToken.cs +++ b/src/neo/SmartContract/Native/NeoToken.cs @@ -2,7 +2,6 @@ using Neo.Cryptography.ECC; using Neo.IO; -using Neo.Ledger; using Neo.Persistence; using Neo.VM; using Neo.VM.Types; @@ -14,18 +13,29 @@ namespace Neo.SmartContract.Native { + /// + /// Represents the NEO token in the NEO system. + /// public sealed class NeoToken : FungibleToken { public override string Symbol => "NEO"; public override byte Decimals => 0; + + /// + /// Indicates the total amount of NEO. + /// public BigInteger TotalAmount { get; } + /// + /// Indicates the effective voting turnout in NEO. The voted candidates will only be effective when the voting turnout exceeds this value. + /// public const decimal EffectiveVoterTurnout = 0.2M; private const byte Prefix_VotersCount = 1; private const byte Prefix_Candidate = 33; private const byte Prefix_Committee = 14; private const byte Prefix_GasPerBlock = 29; + private const byte Prefix_RegisterPrice = 13; private const byte Prefix_VoterRewardPerCommittee = 23; private const byte NeoHolderRewardRatio = 10; @@ -42,9 +52,9 @@ public override BigInteger TotalSupply(DataCache snapshot) return TotalAmount; } - protected override void OnBalanceChanging(ApplicationEngine engine, UInt160 account, NeoAccountState state, BigInteger amount) + internal override async ContractTask OnBalanceChanging(ApplicationEngine engine, UInt160 account, NeoAccountState state, BigInteger amount) { - DistributeGas(engine, account, state); + await DistributeGas(engine, account, state); if (amount.IsZero) return; if (state.VoteTo is null) return; engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_VotersCount)).Add(amount); @@ -54,14 +64,14 @@ protected override void OnBalanceChanging(ApplicationEngine engine, UInt160 acco CheckCandidate(engine.Snapshot, state.VoteTo, candidate); } - private void DistributeGas(ApplicationEngine engine, UInt160 account, NeoAccountState state) + private async ContractTask DistributeGas(ApplicationEngine engine, UInt160 account, NeoAccountState state) { // PersistingBlock is null when running under the debugger if (engine.PersistingBlock is null) return; BigInteger gas = CalculateBonus(engine.Snapshot, state.VoteTo, state.Balance, state.BalanceHeight, engine.PersistingBlock.Index); state.BalanceHeight = engine.PersistingBlock.Index; - GAS.Mint(engine, account, gas, true); + await GAS.Mint(engine, account, gas, true); } private BigInteger CalculateBonus(DataCache snapshot, ECPoint vote, BigInteger value, uint start, uint end) @@ -113,48 +123,53 @@ private void CheckCandidate(DataCache snapshot, ECPoint pubkey, CandidateState c } } - public bool ShouldRefreshCommittee(uint height) => height % ProtocolSettings.Default.CommitteeMembersCount == 0; + /// + /// Determine whether the votes should be recounted at the specified height. + /// + /// The height to be checked. + /// The number of committee members in the system. + /// if the votes should be recounted; otherwise, . + public static bool ShouldRefreshCommittee(uint height, int committeeMembersCount) => height % committeeMembersCount == 0; - internal override void Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine) { - var cachedCommittee = new CachedCommittee(Blockchain.StandbyCommittee.Select(p => (p, BigInteger.Zero))); + var cachedCommittee = new CachedCommittee(engine.ProtocolSettings.StandbyCommittee.Select(p => (p, BigInteger.Zero))); engine.Snapshot.Add(CreateStorageKey(Prefix_Committee), new StorageItem(cachedCommittee)); - engine.Snapshot.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(new byte[0])); - - // Initialize economic parameters - + engine.Snapshot.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(System.Array.Empty())); engine.Snapshot.Add(CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(0u), new StorageItem(5 * GAS.Factor)); - Mint(engine, Contract.GetBFTAddress(Blockchain.StandbyValidators), TotalAmount, false); + engine.Snapshot.Add(CreateStorageKey(Prefix_RegisterPrice), new StorageItem(1000 * GAS.Factor)); + return Mint(engine, Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators), TotalAmount, false); } - internal override void OnPersist(ApplicationEngine engine) + internal override ContractTask OnPersist(ApplicationEngine engine) { // Set next committee - if (ShouldRefreshCommittee(engine.PersistingBlock.Index)) + if (ShouldRefreshCommittee(engine.PersistingBlock.Index, engine.ProtocolSettings.CommitteeMembersCount)) { StorageItem storageItem = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Committee)); var cachedCommittee = storageItem.GetInteroperable(); cachedCommittee.Clear(); - cachedCommittee.AddRange(ComputeCommitteeMembers(engine.Snapshot)); + cachedCommittee.AddRange(ComputeCommitteeMembers(engine.Snapshot, engine.ProtocolSettings)); } + return ContractTask.CompletedTask; } - internal override void PostPersist(ApplicationEngine engine) + internal override async ContractTask PostPersist(ApplicationEngine engine) { // Distribute GAS for committee - int m = ProtocolSettings.Default.CommitteeMembersCount; - int n = ProtocolSettings.Default.ValidatorsCount; + int m = engine.ProtocolSettings.CommitteeMembersCount; + int n = engine.ProtocolSettings.ValidatorsCount; int index = (int)(engine.PersistingBlock.Index % (uint)m); var gasPerBlock = GetGasPerBlock(engine.Snapshot); var committee = GetCommitteeFromCache(engine.Snapshot); var pubkey = committee.ElementAt(index).PublicKey; var account = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash(); - GAS.Mint(engine, account, gasPerBlock * CommitteeRewardRatio / 100, false); + await GAS.Mint(engine, account, gasPerBlock * CommitteeRewardRatio / 100, false); // Record the cumulative reward of the voters of committee - if (ShouldRefreshCommittee(engine.PersistingBlock.Index)) + if (ShouldRefreshCommittee(engine.PersistingBlock.Index, m)) { BigInteger voterRewardOfEachCommittee = gasPerBlock * VoterRewardRatio * 100000000L * m / (m + n) / 100; // Zoom in 100000000 times, and the final calculation should be divided 100000000L for (index = 0; index < committee.Count; index++) @@ -174,7 +189,7 @@ internal override void PostPersist(ApplicationEngine engine) } } - [ContractMethod(0_05000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetGasPerBlock(ApplicationEngine engine, BigInteger gasPerBlock) { if (gasPerBlock < 0 || gasPerBlock > 10 * GAS.Factor) @@ -186,12 +201,37 @@ private void SetGasPerBlock(ApplicationEngine engine, BigInteger gasPerBlock) entry.Set(gasPerBlock); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the amount of GAS generated in each block. + /// + /// The snapshot used to read data. + /// The amount of GAS generated. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public BigInteger GetGasPerBlock(DataCache snapshot) { return GetSortedGasRecords(snapshot, Ledger.CurrentIndex(snapshot) + 1).First().GasPerBlock; } + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] + private void SetRegisterPrice(ApplicationEngine engine, long registerPrice) + { + if (registerPrice <= 0) + throw new ArgumentOutOfRangeException(nameof(registerPrice)); + if (!CheckCommittee(engine)) throw new InvalidOperationException(); + engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_RegisterPrice)).Set(registerPrice); + } + + /// + /// Gets the fees to be paid to register as a candidate. + /// + /// The snapshot used to read data. + /// The amount of the fees. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] + public long GetRegisterPrice(DataCache snapshot) + { + return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_RegisterPrice)]; + } + private IEnumerable<(uint Index, BigInteger GasPerBlock)> GetSortedGasRecords(DataCache snapshot, uint end) { byte[] key = CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(end).ToArray(); @@ -200,7 +240,14 @@ public BigInteger GetGasPerBlock(DataCache snapshot) .Select(u => (BinaryPrimitives.ReadUInt32BigEndian(u.Key.Key.AsSpan(^sizeof(uint))), (BigInteger)u.Value)); } - [ContractMethod(0_03000000, CallFlags.ReadStates)] + /// + /// Get the amount of unclaimed GAS in the specified account. + /// + /// The snapshot used to read data. + /// The account to check. + /// The block index used when calculating GAS. + /// The amount of unclaimed GAS. + [ContractMethod(CpuFee = 1 << 17, RequiredCallFlags = CallFlags.ReadStates)] public BigInteger UnclaimedGas(DataCache snapshot, UInt160 account, uint end) { StorageItem storage = snapshot.TryGet(CreateStorageKey(Prefix_Account).Add(account)); @@ -209,11 +256,12 @@ public BigInteger UnclaimedGas(DataCache snapshot, UInt160 account, uint end) return CalculateBonus(snapshot, state.VoteTo, state.Balance, state.BalanceHeight, end); } - [ContractMethod(1000_00000000, CallFlags.WriteStates)] + [ContractMethod(RequiredCallFlags = CallFlags.States)] private bool RegisterCandidate(ApplicationEngine engine, ECPoint pubkey) { if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash())) return false; + engine.AddGas(GetRegisterPrice(engine.Snapshot)); StorageKey key = CreateStorageKey(Prefix_Candidate).Add(pubkey); StorageItem item = engine.Snapshot.GetAndChange(key, () => new StorageItem(new CandidateState())); CandidateState state = item.GetInteroperable(); @@ -221,7 +269,7 @@ private bool RegisterCandidate(ApplicationEngine engine, ECPoint pubkey) return true; } - [ContractMethod(0_05000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 16, RequiredCallFlags = CallFlags.States)] private bool UnregisterCandidate(ApplicationEngine engine, ECPoint pubkey) { if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash())) @@ -235,8 +283,8 @@ private bool UnregisterCandidate(ApplicationEngine engine, ECPoint pubkey) return true; } - [ContractMethod(0_05000000, CallFlags.WriteStates)] - private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo) + [ContractMethod(CpuFee = 1 << 16, RequiredCallFlags = CallFlags.States)] + private async ContractTask Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo) { if (!engine.CheckWitnessInternal(account)) return false; NeoAccountState state_account = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Account).Add(account))?.GetInteroperable(); @@ -256,7 +304,7 @@ private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo) else item.Add(-state_account.Balance); } - DistributeGas(engine, account, state_account); + await DistributeGas(engine, account, state_account); if (state_account.VoteTo != null) { StorageKey key = CreateStorageKey(Prefix_Candidate).Add(state_account.VoteTo); @@ -273,7 +321,12 @@ private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo) return true; } - [ContractMethod(1_00000000, CallFlags.ReadStates)] + /// + /// Gets all registered candidates. + /// + /// The snapshot used to read data. + /// All the registered candidates. + [ContractMethod(CpuFee = 1 << 22, RequiredCallFlags = CallFlags.ReadStates)] public (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(DataCache snapshot) { byte[] prefix_key = CreateStorageKey(Prefix_Candidate).ToArray(); @@ -284,12 +337,22 @@ private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo) )).Where(p => p.Item2.Registered).Select(p => (p.Item1, p.Item2.Votes)).ToArray(); } - [ContractMethod(1_00000000, CallFlags.ReadStates)] + /// + /// Gets all the members of the committee. + /// + /// The snapshot used to read data. + /// The public keys of the members. + [ContractMethod(CpuFee = 1 << 16, RequiredCallFlags = CallFlags.ReadStates)] public ECPoint[] GetCommittee(DataCache snapshot) { return GetCommitteeFromCache(snapshot).Select(p => p.PublicKey).OrderBy(p => p).ToArray(); } + /// + /// Gets the address of the committee. + /// + /// The snapshot used to read data. + /// The address of the committee. public UInt160 GetCommitteeAddress(DataCache snapshot) { ECPoint[] committees = GetCommittee(snapshot); @@ -301,34 +364,61 @@ private CachedCommittee GetCommitteeFromCache(DataCache snapshot) return snapshot[CreateStorageKey(Prefix_Committee)].GetInteroperable(); } - public ECPoint[] ComputeNextBlockValidators(DataCache snapshot) + /// + /// Computes the validators of the next block. + /// + /// The snapshot used to read data. + /// The used during computing. + /// The public keys of the validators. + public ECPoint[] ComputeNextBlockValidators(DataCache snapshot, ProtocolSettings settings) { - return ComputeCommitteeMembers(snapshot).Select(p => p.PublicKey).Take(ProtocolSettings.Default.ValidatorsCount).OrderBy(p => p).ToArray(); + return ComputeCommitteeMembers(snapshot, settings).Select(p => p.PublicKey).Take(settings.ValidatorsCount).OrderBy(p => p).ToArray(); } - private IEnumerable<(ECPoint PublicKey, BigInteger Votes)> ComputeCommitteeMembers(DataCache snapshot) + private IEnumerable<(ECPoint PublicKey, BigInteger Votes)> ComputeCommitteeMembers(DataCache snapshot, ProtocolSettings settings) { decimal votersCount = (decimal)(BigInteger)snapshot[CreateStorageKey(Prefix_VotersCount)]; decimal voterTurnout = votersCount / (decimal)TotalAmount; var candidates = GetCandidates(snapshot); - if (voterTurnout < EffectiveVoterTurnout || candidates.Length < ProtocolSettings.Default.CommitteeMembersCount) - return Blockchain.StandbyCommittee.Select(p => (p, candidates.FirstOrDefault(k => k.PublicKey.Equals(p)).Votes)); - return candidates.OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Take(ProtocolSettings.Default.CommitteeMembersCount); + if (voterTurnout < EffectiveVoterTurnout || candidates.Length < settings.CommitteeMembersCount) + return settings.StandbyCommittee.Select(p => (p, candidates.FirstOrDefault(k => k.PublicKey.Equals(p)).Votes)); + return candidates.OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Take(settings.CommitteeMembersCount); } - [ContractMethod(1_00000000, CallFlags.ReadStates)] - public ECPoint[] GetNextBlockValidators(DataCache snapshot) + [ContractMethod(CpuFee = 1 << 16, RequiredCallFlags = CallFlags.ReadStates)] + private ECPoint[] GetNextBlockValidators(ApplicationEngine engine) + { + return GetNextBlockValidators(engine.Snapshot, engine.ProtocolSettings.ValidatorsCount); + } + + /// + /// Gets the validators of the next block. + /// + /// The snapshot used to read data. + /// The number of validators in the system. + /// The public keys of the validators. + public ECPoint[] GetNextBlockValidators(DataCache snapshot, int validatorsCount) { return GetCommitteeFromCache(snapshot) - .Take(ProtocolSettings.Default.ValidatorsCount) + .Take(validatorsCount) .Select(p => p.PublicKey) .OrderBy(p => p) .ToArray(); } + /// + /// Represents the account state of . + /// public class NeoAccountState : AccountState { + /// + /// The height of the block where the balance changed last time. + /// public uint BalanceHeight; + + /// + /// The voting target of the account. This field can be . + /// public ECPoint VoteTo; public override void FromStackItem(StackItem stackItem) @@ -366,7 +456,7 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter) } } - public class CachedCommittee : List<(ECPoint PublicKey, BigInteger Votes)>, IInteroperable + internal class CachedCommittee : List<(ECPoint PublicKey, BigInteger Votes)>, IInteroperable { public CachedCommittee() { diff --git a/src/neo/SmartContract/Native/NonfungibleToken.cs b/src/neo/SmartContract/Native/NonfungibleToken.cs index 33f039cf1b..7974aa5440 100644 --- a/src/neo/SmartContract/Native/NonfungibleToken.cs +++ b/src/neo/SmartContract/Native/NonfungibleToken.cs @@ -1,5 +1,3 @@ -#pragma warning disable IDE0051 - using Neo.IO; using Neo.Persistence; using Neo.SmartContract.Iterators; @@ -14,18 +12,36 @@ namespace Neo.SmartContract.Native { + /// + /// The base class of all native tokens that are compatible with NEP-11. + /// + /// The type of the token state. public abstract class NonfungibleToken : NativeContract where TokenState : NFTState, new() { - [ContractMethod(0, CallFlags.None)] + /// + /// The symbol of the token. + /// + [ContractMethod] public abstract string Symbol { get; } - [ContractMethod(0, CallFlags.None)] + + /// + /// The number of decimal places of the token. It always return 0 in native NFT. + /// + [ContractMethod] public byte Decimals => 0; private const byte Prefix_TotalSupply = 11; private const byte Prefix_Account = 7; + + /// + /// The prefix for storing token states. + /// protected const byte Prefix_Token = 5; + /// + /// Initializes a new instance of the class. + /// protected NonfungibleToken() { var events = new List(Manifest.Abi.Events) @@ -62,28 +78,34 @@ protected NonfungibleToken() Manifest.Abi.Events = events.ToArray(); } + /// + /// Gets the storage key of the specified token id. + /// + /// The id of the token. + /// The storage key. protected virtual byte[] GetKey(byte[] tokenId) => tokenId; - internal override void Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine) { engine.Snapshot.Add(CreateStorageKey(Prefix_TotalSupply), new StorageItem(BigInteger.Zero)); + return ContractTask.CompletedTask; } - protected void Mint(ApplicationEngine engine, TokenState token) + private protected ContractTask Mint(ApplicationEngine engine, TokenState token) { engine.Snapshot.Add(CreateStorageKey(Prefix_Token).Add(GetKey(token.Id)), new StorageItem(token)); NFTAccountState account = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Account).Add(token.Owner), () => new StorageItem(new NFTAccountState())).GetInteroperable(); account.Add(token.Id); engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_TotalSupply)).Add(1); - PostTransfer(engine, null, token.Owner, token.Id); + return PostTransfer(engine, null, token.Owner, token.Id); } - protected void Burn(ApplicationEngine engine, byte[] tokenId) + private protected ContractTask Burn(ApplicationEngine engine, byte[] tokenId) { - Burn(engine, CreateStorageKey(Prefix_Token).Add(GetKey(tokenId))); + return Burn(engine, CreateStorageKey(Prefix_Token).Add(GetKey(tokenId))); } - private protected void Burn(ApplicationEngine engine, StorageKey key) + private protected ContractTask Burn(ApplicationEngine engine, StorageKey key) { TokenState token = engine.Snapshot.TryGet(key)?.GetInteroperable(); if (token is null) throw new InvalidOperationException(); @@ -94,42 +116,76 @@ private protected void Burn(ApplicationEngine engine, StorageKey key) if (account.Balance.IsZero) engine.Snapshot.Delete(key_account); engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_TotalSupply)).Add(-1); - PostTransfer(engine, token.Owner, null, token.Id); + return PostTransfer(engine, token.Owner, null, token.Id); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the total supply of the token. + /// + /// The snapshot used to read data. + /// The total supply of the token. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public BigInteger TotalSupply(DataCache snapshot) { return snapshot[CreateStorageKey(Prefix_TotalSupply)]; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the owner of the token. + /// + /// The snapshot used to read data. + /// The id of the token. + /// The owner of the token. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public UInt160 OwnerOf(DataCache snapshot, byte[] tokenId) { return snapshot[CreateStorageKey(Prefix_Token).Add(GetKey(tokenId))].GetInteroperable().Owner; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the properties of the token. + /// + /// The that is executing the contract. + /// The id of the token. + /// + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public Map Properties(ApplicationEngine engine, byte[] tokenId) { return engine.Snapshot[CreateStorageKey(Prefix_Token).Add(GetKey(tokenId))].GetInteroperable().ToMap(engine.ReferenceCounter); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the balance of the specified account. + /// + /// The snapshot used to read data. + /// The owner of the account. + /// The balance of the account. Or 0 if the account doesn't exist. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public BigInteger BalanceOf(DataCache snapshot, UInt160 owner) { if (owner is null) throw new ArgumentNullException(nameof(owner)); return snapshot.TryGet(CreateStorageKey(Prefix_Account).Add(owner))?.GetInteroperable().Balance ?? BigInteger.Zero; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Get all the tokens that have been issued in the contract. + /// + /// The snapshot used to read data. + /// All the tokens that have been issued. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] protected IIterator Tokens(DataCache snapshot) { var results = snapshot.Find(CreateStorageKey(Prefix_Token).ToArray()).GetEnumerator(); return new StorageIterator(results, FindOptions.ValuesOnly | FindOptions.DeserializeValues | FindOptions.PickField1, null); } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets all the tokens owned by the specified account. + /// + /// The snapshot used to read data. + /// The owner of the account. + /// All the tokens owned by the specified account. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] protected IIterator TokensOf(DataCache snapshot, UInt160 owner) { NFTAccountState account = snapshot.TryGet(CreateStorageKey(Prefix_Account).Add(owner))?.GetInteroperable(); @@ -137,8 +193,8 @@ protected IIterator TokensOf(DataCache snapshot, UInt160 owner) return new ArrayWrapper(tokens.Select(p => (StackItem)p).ToArray()); } - [ContractMethod(0_09000000, CallFlags.WriteStates | CallFlags.AllowNotify)] - protected bool Transfer(ApplicationEngine engine, UInt160 to, byte[] tokenId) + [ContractMethod(CpuFee = 1 << 17, StorageFee = 50, RequiredCallFlags = CallFlags.States | CallFlags.AllowCall | CallFlags.AllowNotify)] + private protected async ContractTask Transfer(ApplicationEngine engine, UInt160 to, byte[] tokenId) { if (to is null) throw new ArgumentNullException(nameof(to)); StorageKey key_token = CreateStorageKey(Prefix_Token).Add(GetKey(tokenId)); @@ -160,26 +216,32 @@ protected bool Transfer(ApplicationEngine engine, UInt160 to, byte[] tokenId) account.Add(tokenId); OnTransferred(engine, from, token); } - PostTransfer(engine, from, to, tokenId); + await PostTransfer(engine, from, to, tokenId); return true; } + /// + /// Called when a token is transferred. + /// + /// The that is executing the contract. + /// The account where the token is transferred from. + /// The token that is transferred. protected virtual void OnTransferred(ApplicationEngine engine, UInt160 from, TokenState token) { } - private void PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, byte[] tokenId) + private async ContractTask PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, byte[] tokenId) { engine.SendNotification(Hash, "Transfer", new Array { from?.ToArray() ?? StackItem.Null, to?.ToArray() ?? StackItem.Null, 1, tokenId }); if (to is not null && ContractManagement.GetContract(engine.Snapshot, to) is not null) - engine.CallFromNativeContract(Hash, to, "onNEP11Payment", from?.ToArray() ?? StackItem.Null, 1, tokenId); + await engine.CallFromNativeContract(Hash, to, "onNEP11Payment", from?.ToArray() ?? StackItem.Null, 1, tokenId); } class NFTAccountState : AccountState { - public readonly List Tokens = new List(); + public readonly List Tokens = new(); public void Add(byte[] tokenId) { diff --git a/src/neo/SmartContract/Native/OracleContract.cs b/src/neo/SmartContract/Native/OracleContract.cs index 8bb656540c..512b38ec73 100644 --- a/src/neo/SmartContract/Native/OracleContract.cs +++ b/src/neo/SmartContract/Native/OracleContract.cs @@ -15,6 +15,9 @@ namespace Neo.SmartContract.Native { + /// + /// The native Oracle service for NEO system. + /// public sealed class OracleContract : NativeContract { private const int MaxUrlLength = 256; @@ -22,12 +25,11 @@ public sealed class OracleContract : NativeContract private const int MaxCallbackLength = 32; private const int MaxUserDataLength = 512; + private const byte Prefix_Price = 5; private const byte Prefix_RequestId = 9; private const byte Prefix_Request = 7; private const byte Prefix_IdList = 6; - private const long OracleRequestPrice = 0_50000000; - internal OracleContract() { var events = new List(Manifest.Abi.Events) @@ -81,8 +83,28 @@ internal OracleContract() Manifest.Abi.Events = events.ToArray(); } - [ContractMethod(0, CallFlags.WriteStates | CallFlags.AllowCall | CallFlags.AllowNotify)] - private void Finish(ApplicationEngine engine) + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] + private void SetPrice(ApplicationEngine engine, long price) + { + if (price <= 0) + throw new ArgumentOutOfRangeException(nameof(price)); + if (!CheckCommittee(engine)) throw new InvalidOperationException(); + engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Price)).Set(price); + } + + /// + /// Gets the price for an Oracle request. + /// + /// The snapshot used to read data. + /// The price for an Oracle request. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] + public long GetPrice(DataCache snapshot) + { + return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_Price)]; + } + + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowCall | CallFlags.AllowNotify)] + private ContractTask Finish(ApplicationEngine engine) { Transaction tx = (Transaction)engine.ScriptContainer; OracleResponse response = tx.GetAttribute(); @@ -90,8 +112,8 @@ private void Finish(ApplicationEngine engine) OracleRequest request = GetRequest(engine.Snapshot, response.Id); if (request == null) throw new ArgumentException("Oracle request was not found"); engine.SendNotification(Hash, "OracleResponse", new VM.Types.Array { response.Id, request.OriginalTxid.ToArray() }); - StackItem userData = BinarySerializer.Deserialize(request.UserData, engine.Limits.MaxStackSize, engine.Limits.MaxItemSize, engine.ReferenceCounter); - engine.CallFromNativeContract(Hash, request.CallbackContract, request.CallbackMethod, request.Url, userData, (int)response.Code, response.Result); + StackItem userData = BinarySerializer.Deserialize(request.UserData, engine.Limits.MaxStackSize, engine.ReferenceCounter); + return engine.CallFromNativeContract(Hash, request.CallbackContract, request.CallbackMethod, request.Url, userData, (int)response.Code, response.Result); } private UInt256 GetOriginalTxid(ApplicationEngine engine) @@ -103,16 +125,33 @@ private UInt256 GetOriginalTxid(ApplicationEngine engine) return request.OriginalTxid; } + /// + /// Gets a pending request with the specified id. + /// + /// The snapshot used to read data. + /// The id of the request. + /// The pending request. Or if no request with the specified id is found. public OracleRequest GetRequest(DataCache snapshot, ulong id) { return snapshot.TryGet(CreateStorageKey(Prefix_Request).AddBigEndian(id))?.GetInteroperable(); } + /// + /// Gets all the pending requests. + /// + /// The snapshot used to read data. + /// All the pending requests. public IEnumerable<(ulong, OracleRequest)> GetRequests(DataCache snapshot) { return snapshot.Find(CreateStorageKey(Prefix_Request).ToArray()).Select(p => (BinaryPrimitives.ReadUInt64BigEndian(p.Key.Key.AsSpan(1)), p.Value.GetInteroperable())); } + /// + /// Gets the requests with the specified url. + /// + /// The snapshot used to read data. + /// The url of the requests. + /// All the requests with the specified url. public IEnumerable<(ulong, OracleRequest)> GetRequestsByUrl(DataCache snapshot, string url) { IdList list = snapshot.TryGet(CreateStorageKey(Prefix_IdList).Add(GetUrlHash(url)))?.GetInteroperable(); @@ -126,12 +165,14 @@ private static byte[] GetUrlHash(string url) return Crypto.Hash160(Utility.StrictUTF8.GetBytes(url)); } - internal override void Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine) { engine.Snapshot.Add(CreateStorageKey(Prefix_RequestId), new StorageItem(BigInteger.Zero)); + engine.Snapshot.Add(CreateStorageKey(Prefix_Price), new StorageItem(0_50000000)); + return ContractTask.CompletedTask; } - internal override void PostPersist(ApplicationEngine engine) + internal override async ContractTask PostPersist(ApplicationEngine engine) { (UInt160 Account, BigInteger GAS)[] nodes = null; foreach (Transaction tx in engine.PersistingBlock.Transactions) @@ -157,20 +198,21 @@ internal override void PostPersist(ApplicationEngine engine) if (nodes.Length > 0) { int index = (int)(response.Id % (ulong)nodes.Length); - nodes[index].GAS += OracleRequestPrice; + nodes[index].GAS += GetPrice(engine.Snapshot); } } if (nodes != null) { foreach (var (account, gas) in nodes) { - if (gas.Sign > 0) GAS.Mint(engine, account, gas, false); + if (gas.Sign > 0) + await GAS.Mint(engine, account, gas, false); } } } - [ContractMethod(OracleRequestPrice, CallFlags.WriteStates | CallFlags.AllowNotify)] - private void Request(ApplicationEngine engine, string url, string filter, string callback, StackItem userData, long gasForResponse) + [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] + private async ContractTask Request(ApplicationEngine engine, string url, string filter, string callback, StackItem userData, long gasForResponse) { //Check the arguments if (Utility.StrictUTF8.GetByteCount(url) > MaxUrlLength @@ -179,9 +221,11 @@ private void Request(ApplicationEngine engine, string url, string filter, string || gasForResponse < 0_10000000) throw new ArgumentException(); + engine.AddGas(GetPrice(engine.Snapshot)); + //Mint gas for the response engine.AddGas(gasForResponse); - GAS.Mint(engine, Hash, gasForResponse, false); + await GAS.Mint(engine, Hash, gasForResponse, false); //Increase the request id StorageItem item_id = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_RequestId)); @@ -211,7 +255,7 @@ private void Request(ApplicationEngine engine, string url, string filter, string engine.SendNotification(Hash, "OracleRequest", new VM.Types.Array { id, engine.CallingScriptHash.ToArray(), url, filter ?? StackItem.Null }); } - [ContractMethod(0_01000000, CallFlags.None)] + [ContractMethod(CpuFee = 1 << 15)] private bool Verify(ApplicationEngine engine) { Transaction tx = (Transaction)engine.ScriptContainer; diff --git a/src/neo/SmartContract/Native/OracleRequest.cs b/src/neo/SmartContract/Native/OracleRequest.cs index 8066116d99..52e3eba429 100644 --- a/src/neo/SmartContract/Native/OracleRequest.cs +++ b/src/neo/SmartContract/Native/OracleRequest.cs @@ -5,14 +5,44 @@ namespace Neo.SmartContract.Native { + /// + /// Represents an Oracle request in smart contracts. + /// public class OracleRequest : IInteroperable { + /// + /// The original transaction that sent the related request. + /// public UInt256 OriginalTxid; + + /// + /// The maximum amount of GAS that can be used when executing response callback. + /// public long GasForResponse; + + /// + /// The url of the request. + /// public string Url; + + /// + /// The filter for the response. + /// public string Filter; + + /// + /// The hash of the callback contract. + /// public UInt160 CallbackContract; + + /// + /// The name of the callback method. + /// public string CallbackMethod; + + /// + /// The user-defined object that will be passed to the callback. + /// public byte[] UserData; public void FromStackItem(StackItem stackItem) diff --git a/src/neo/SmartContract/Native/PolicyContract.cs b/src/neo/SmartContract/Native/PolicyContract.cs index eb60511042..b7f11beb29 100644 --- a/src/neo/SmartContract/Native/PolicyContract.cs +++ b/src/neo/SmartContract/Native/PolicyContract.cs @@ -1,25 +1,43 @@ #pragma warning disable IDE0051 -using Neo.Network.P2P; -using Neo.Network.P2P.Payloads; using Neo.Persistence; using System; using System.Numerics; namespace Neo.SmartContract.Native { + /// + /// A native contract that manages the system policies. + /// public sealed class PolicyContract : NativeContract { + /// + /// The default execution fee factor. + /// public const uint DefaultExecFeeFactor = 30; + + /// + /// The default storage price. + /// public const uint DefaultStoragePrice = 100000; - private const uint MaxExecFeeFactor = 1000; - private const uint MaxStoragePrice = 10000000; - private const byte Prefix_MaxTransactionsPerBlock = 23; - private const byte Prefix_FeePerByte = 10; + /// + /// The default network fee per byte of transactions. + /// + public const uint DefaultFeePerByte = 1000; + + /// + /// The maximum execution fee factor that the committee can set. + /// + public const uint MaxExecFeeFactor = 1000; + + /// + /// The maximum storage price that the committee can set. + /// + public const uint MaxStoragePrice = 10000000; + private const byte Prefix_BlockedAccount = 15; - private const byte Prefix_MaxBlockSize = 12; - private const byte Prefix_MaxBlockSystemFee = 17; + private const byte Prefix_FeePerByte = 10; private const byte Prefix_ExecFeeFactor = 18; private const byte Prefix_StoragePrice = 19; @@ -27,83 +45,60 @@ internal PolicyContract() { } - internal override void Initialize(ApplicationEngine engine) + internal override ContractTask Initialize(ApplicationEngine engine) { - engine.Snapshot.Add(CreateStorageKey(Prefix_MaxTransactionsPerBlock), new StorageItem(512)); - engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(1000)); - engine.Snapshot.Add(CreateStorageKey(Prefix_MaxBlockSize), new StorageItem(1024 * 256)); - engine.Snapshot.Add(CreateStorageKey(Prefix_MaxBlockSystemFee), new StorageItem(9000 * GAS.Factor)); // For the transfer method of NEP5, the maximum persisting time is about three seconds. + engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); + return ContractTask.CompletedTask; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] - public uint GetMaxTransactionsPerBlock(DataCache snapshot) - { - return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_MaxTransactionsPerBlock)]; - } - - [ContractMethod(0_01000000, CallFlags.ReadStates)] - public uint GetMaxBlockSize(DataCache snapshot) - { - return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_MaxBlockSize)]; - } - - [ContractMethod(0_01000000, CallFlags.ReadStates)] - public long GetMaxBlockSystemFee(DataCache snapshot) - { - return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_MaxBlockSystemFee)]; - } - - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the network fee per transaction byte. + /// + /// The snapshot used to read data. + /// The network fee per transaction byte. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public long GetFeePerByte(DataCache snapshot) { return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_FeePerByte)]; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the execution fee factor. This is a multiplier that can be adjusted by the committee to adjust the system fees for transactions. + /// + /// The snapshot used to read data. + /// The execution fee factor. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint GetExecFeeFactor(DataCache snapshot) { return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_ExecFeeFactor)]; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the storage price. + /// + /// The snapshot used to read data. + /// The storage price. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint GetStoragePrice(DataCache snapshot) { return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_StoragePrice)]; } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Determines whether the specified account is blocked. + /// + /// The snapshot used to read data. + /// The account to be checked. + /// if the account is blocked; otherwise, . + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public bool IsBlocked(DataCache snapshot, UInt160 account) { return snapshot.Contains(CreateStorageKey(Prefix_BlockedAccount).Add(account)); } - [ContractMethod(0_03000000, CallFlags.WriteStates)] - private void SetMaxBlockSize(ApplicationEngine engine, uint value) - { - if (value > Message.PayloadMaxSize) throw new ArgumentOutOfRangeException(nameof(value)); - if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_MaxBlockSize)).Set(value); - } - - [ContractMethod(0_03000000, CallFlags.WriteStates)] - private void SetMaxTransactionsPerBlock(ApplicationEngine engine, uint value) - { - if (value > Block.MaxTransactionsPerBlock) throw new ArgumentOutOfRangeException(nameof(value)); - if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_MaxTransactionsPerBlock)).Set(value); - } - - [ContractMethod(0_03000000, CallFlags.WriteStates)] - private void SetMaxBlockSystemFee(ApplicationEngine engine, long value) - { - if (value <= 4007600) throw new ArgumentOutOfRangeException(nameof(value)); - if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_MaxBlockSystemFee)).Set(value); - } - - [ContractMethod(0_03000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetFeePerByte(ApplicationEngine engine, long value) { if (value < 0 || value > 1_00000000) throw new ArgumentOutOfRangeException(nameof(value)); @@ -111,7 +106,7 @@ private void SetFeePerByte(ApplicationEngine engine, long value) engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_FeePerByte)).Set(value); } - [ContractMethod(0_03000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetExecFeeFactor(ApplicationEngine engine, uint value) { if (value == 0 || value > MaxExecFeeFactor) throw new ArgumentOutOfRangeException(nameof(value)); @@ -119,7 +114,7 @@ private void SetExecFeeFactor(ApplicationEngine engine, uint value) engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_ExecFeeFactor)).Set(value); } - [ContractMethod(0_03000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetStoragePrice(ApplicationEngine engine, uint value) { if (value == 0 || value > MaxStoragePrice) throw new ArgumentOutOfRangeException(nameof(value)); @@ -127,7 +122,7 @@ private void SetStoragePrice(ApplicationEngine engine, uint value) engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_StoragePrice)).Set(value); } - [ContractMethod(0_03000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private bool BlockAccount(ApplicationEngine engine, UInt160 account) { if (!CheckCommittee(engine)) throw new InvalidOperationException(); @@ -139,7 +134,7 @@ private bool BlockAccount(ApplicationEngine engine, UInt160 account) return true; } - [ContractMethod(0_03000000, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private bool UnblockAccount(ApplicationEngine engine, UInt160 account) { if (!CheckCommittee(engine)) throw new InvalidOperationException(); diff --git a/src/neo/SmartContract/Native/RecordType.cs b/src/neo/SmartContract/Native/RecordType.cs index 9ecf067c61..086345c2b9 100644 --- a/src/neo/SmartContract/Native/RecordType.cs +++ b/src/neo/SmartContract/Native/RecordType.cs @@ -1,15 +1,36 @@ namespace Neo.SmartContract.Native { + /// + /// Represents the type of a name record. + /// public enum RecordType : byte { #region [RFC 1035](https://tools.ietf.org/html/rfc1035) + + /// + /// Address record. + /// A = 1, + + /// + /// Canonical name record. + /// CNAME = 5, + + /// + /// Text record. + /// TXT = 16, + #endregion #region [RFC 3596](https://tools.ietf.org/html/rfc3596) + + /// + /// IPv6 address record. + /// AAAA = 28, + #endregion } } diff --git a/src/neo/SmartContract/Native/Role.cs b/src/neo/SmartContract/Native/Role.cs index c3f38b7cc6..432cb9ae17 100644 --- a/src/neo/SmartContract/Native/Role.cs +++ b/src/neo/SmartContract/Native/Role.cs @@ -1,8 +1,23 @@ namespace Neo.SmartContract.Native { + /// + /// Represents the roles in the NEO system. + /// public enum Role : byte { + /// + /// The validators of state. Used to generate and sign the state root. + /// StateValidator = 4, - Oracle = 8 + + /// + /// The nodes used to process Oracle requests. + /// + Oracle = 8, + + /// + /// NeoFS Alphabet nodes. + /// + NeoFSAlphabetNode = 16 } } diff --git a/src/neo/SmartContract/Native/RoleManagement.cs b/src/neo/SmartContract/Native/RoleManagement.cs index 6c79093139..4a1b3bd300 100644 --- a/src/neo/SmartContract/Native/RoleManagement.cs +++ b/src/neo/SmartContract/Native/RoleManagement.cs @@ -10,13 +10,23 @@ namespace Neo.SmartContract.Native { + /// + /// A native contract for managing roles in NEO system. + /// public sealed class RoleManagement : NativeContract { internal RoleManagement() { } - [ContractMethod(0_01000000, CallFlags.ReadStates)] + /// + /// Gets the list of nodes for the specified role. + /// + /// The snapshot used to read data. + /// The type of the role. + /// The index of the block to be queried. + /// The public keys of the nodes. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public ECPoint[] GetDesignatedByRole(DataCache snapshot, Role role, uint index) { if (!Enum.IsDefined(typeof(Role), role)) @@ -30,11 +40,11 @@ public ECPoint[] GetDesignatedByRole(DataCache snapshot, Role role, uint index) .FirstOrDefault() ?? System.Array.Empty(); } - [ContractMethod(0, CallFlags.WriteStates)] + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void DesignateAsRole(ApplicationEngine engine, Role role, ECPoint[] nodes) { if (nodes.Length == 0 || nodes.Length > 32) - throw new ArgumentException(); + throw new ArgumentException(null, nameof(nodes)); if (!Enum.IsDefined(typeof(Role), role)) throw new ArgumentOutOfRangeException(nameof(role)); if (!CheckCommittee(engine)) @@ -45,7 +55,7 @@ private void DesignateAsRole(ApplicationEngine engine, Role role, ECPoint[] node var key = CreateStorageKey((byte)role).AddBigEndian(index); if (engine.Snapshot.Contains(key)) throw new InvalidOperationException(); - NodeList list = new NodeList(); + NodeList list = new(); list.AddRange(nodes); list.Sort(); engine.Snapshot.Add(key, new StorageItem(list)); diff --git a/src/neo/SmartContract/Native/StdLib.cs b/src/neo/SmartContract/Native/StdLib.cs new file mode 100644 index 0000000000..c3b6dbaeb7 --- /dev/null +++ b/src/neo/SmartContract/Native/StdLib.cs @@ -0,0 +1,121 @@ +#pragma warning disable IDE0051 + +using Neo.Cryptography; +using Neo.IO.Json; +using Neo.VM.Types; +using System; +using System.Globalization; +using System.Numerics; + +namespace Neo.SmartContract.Native +{ + /// + /// A native contract library that provides useful functions. + /// + public sealed class StdLib : NativeContract + { + internal StdLib() { } + + [ContractMethod(CpuFee = 1 << 12)] + private static byte[] Serialize(ApplicationEngine engine, StackItem item) + { + return BinarySerializer.Serialize(item, engine.Limits.MaxItemSize); + } + + [ContractMethod(CpuFee = 1 << 14)] + private static StackItem Deserialize(ApplicationEngine engine, byte[] data) + { + return BinarySerializer.Deserialize(data, engine.Limits.MaxStackSize, engine.ReferenceCounter); + } + + [ContractMethod(CpuFee = 1 << 12)] + private static byte[] JsonSerialize(ApplicationEngine engine, StackItem item) + { + return JsonSerializer.SerializeToByteArray(item, engine.Limits.MaxItemSize); + } + + [ContractMethod(CpuFee = 1 << 14)] + private static StackItem JsonDeserialize(ApplicationEngine engine, byte[] json) + { + return JsonSerializer.Deserialize(JObject.Parse(json, 10), engine.ReferenceCounter); + } + + /// + /// Converts an integer to a . + /// + /// The integer to convert. + /// The base of the integer. Only support 10 and 16. + /// The converted . + [ContractMethod(CpuFee = 1 << 12)] + public static string Itoa(BigInteger value, int @base) + { + return @base switch + { + 10 => value.ToString(), + 16 => value.ToString("x"), + _ => throw new ArgumentOutOfRangeException(nameof(@base)) + }; + } + + /// + /// Converts a to an integer. + /// + /// The to convert. + /// The base of the integer. Only support 10 and 16. + /// The converted integer. + [ContractMethod(CpuFee = 1 << 12)] + public static BigInteger Atoi(string value, int @base) + { + return @base switch + { + 10 => BigInteger.Parse(value), + 16 => BigInteger.Parse(value, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture), + _ => throw new ArgumentOutOfRangeException(nameof(@base)) + }; + } + + /// + /// Encodes a byte array into a base64 . + /// + /// The byte array to be encoded. + /// The encoded . + [ContractMethod(CpuFee = 1 << 12)] + public static string Base64Encode(byte[] data) + { + return Convert.ToBase64String(data); + } + + /// + /// Decodes a byte array from a base64 . + /// + /// The base64 . + /// The decoded byte array. + [ContractMethod(CpuFee = 1 << 12)] + public static byte[] Base64Decode(string s) + { + return Convert.FromBase64String(s); + } + + /// + /// Encodes a byte array into a base58 . + /// + /// The byte array to be encoded. + /// The encoded . + [ContractMethod(CpuFee = 1 << 12)] + public static string Base58Encode(byte[] data) + { + return Base58.Encode(data); + } + + /// + /// Decodes a byte array from a base58 . + /// + /// The base58 . + /// The decoded byte array. + [ContractMethod(CpuFee = 1 << 12)] + public static byte[] Base58Decode(string s) + { + return Base58.Decode(s); + } + } +} diff --git a/src/neo/SmartContract/Native/TransactionState.cs b/src/neo/SmartContract/Native/TransactionState.cs index 671146a6de..6353a57fc7 100644 --- a/src/neo/SmartContract/Native/TransactionState.cs +++ b/src/neo/SmartContract/Native/TransactionState.cs @@ -5,9 +5,19 @@ namespace Neo.SmartContract.Native { + /// + /// Represents a transaction that has been included in a block. + /// public class TransactionState : IInteroperable { + /// + /// The block containing this transaction. + /// public uint BlockIndex; + + /// + /// The transaction. + /// public Transaction Transaction; private StackItem _rawTransaction; diff --git a/src/neo/SmartContract/Native/TrimmedBlock.cs b/src/neo/SmartContract/Native/TrimmedBlock.cs index 11133fb9d7..88774a84a0 100644 --- a/src/neo/SmartContract/Native/TrimmedBlock.cs +++ b/src/neo/SmartContract/Native/TrimmedBlock.cs @@ -1,5 +1,4 @@ using Neo.IO; -using Neo.IO.Json; using Neo.Network.P2P.Payloads; using Neo.VM; using Neo.VM.Types; @@ -9,59 +8,43 @@ namespace Neo.SmartContract.Native { - public class TrimmedBlock : BlockBase, IInteroperable + /// + /// Represents a block which the transactions are trimmed. + /// + public class TrimmedBlock : IInteroperable, ISerializable { + /// + /// The header of the block. + /// + public Header Header; + + /// + /// The hashes of the transactions of the block. + /// public UInt256[] Hashes; - public ConsensusData ConsensusData; - private Header _header = null; - public Header Header - { - get - { - if (_header == null) - { - _header = new Header - { - Version = Version, - PrevHash = PrevHash, - MerkleRoot = MerkleRoot, - Timestamp = Timestamp, - Index = Index, - NextConsensus = NextConsensus, - Witness = Witness - }; - } - return _header; - } - } + /// + /// The hash of the block. + /// + public UInt256 Hash => Header.Hash; - public override int Size => base.Size - + Hashes.GetVarSize() //Hashes - + (ConsensusData?.Size ?? 0); //ConsensusData + /// + /// The index of the block. + /// + public uint Index => Header.Index; - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - Hashes = reader.ReadSerializableArray(Block.MaxContentsPerBlock); - if (Hashes.Length > 0) - ConsensusData = reader.ReadSerializable(); - } + public int Size => Header.Size + Hashes.GetVarSize(); - public override void Serialize(BinaryWriter writer) + public void Deserialize(BinaryReader reader) { - base.Serialize(writer); - writer.Write(Hashes); - if (Hashes.Length > 0) - writer.Write(ConsensusData); + Header = reader.ReadSerializable
(); + Hashes = reader.ReadSerializableArray(ushort.MaxValue); } - public override JObject ToJson() + public void Serialize(BinaryWriter writer) { - JObject json = base.ToJson(); - json["consensusdata"] = ConsensusData?.ToJson(); - json["hashes"] = Hashes.Select(p => (JObject)p.ToString()).ToArray(); - return json; + writer.Write(Header); + writer.Write(Hashes); } void IInteroperable.FromStackItem(StackItem stackItem) @@ -74,18 +57,19 @@ StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) return new VM.Types.Array(referenceCounter, new StackItem[] { // Computed properties - Hash.ToArray(), + Header.Hash.ToArray(), // BlockBase properties - Version, - PrevHash.ToArray(), - MerkleRoot.ToArray(), - Timestamp, - Index, - NextConsensus.ToArray(), + Header.Version, + Header.PrevHash.ToArray(), + Header.MerkleRoot.ToArray(), + Header.Timestamp, + Header.Index, + Header.PrimaryIndex, + Header.NextConsensus.ToArray(), // Block properties - Hashes.Length - 1 + Hashes.Length }); } } diff --git a/src/neo/SmartContract/NefFile.cs b/src/neo/SmartContract/NefFile.cs index e43bf67433..e7c64286d5 100644 --- a/src/neo/SmartContract/NefFile.cs +++ b/src/neo/SmartContract/NefFile.cs @@ -2,27 +2,31 @@ using Neo.IO; using Neo.IO.Json; using System; +using System.Buffers.Binary; using System.IO; using System.Linq; namespace Neo.SmartContract { + /* + ┌───────────────────────────────────────────────────────────────────────┐ + │ NEO Executable Format 3 (NEF3) │ + ├──────────┬───────────────┬────────────────────────────────────────────┤ + │ Field │ Type │ Comment │ + ├──────────┼───────────────┼────────────────────────────────────────────┤ + │ Magic │ uint32 │ Magic header │ + │ Compiler │ byte[64] │ Compiler name and version │ + ├──────────┼───────────────┼────────────────────────────────────────────┤ + │ Reserve │ byte[2] │ Reserved for future extensions. Must be 0. │ + │ Tokens │ MethodToken[] │ Method tokens. │ + │ Reserve │ byte[2] │ Reserved for future extensions. Must be 0. │ + │ Script │ byte[] │ Var bytes for the payload │ + ├──────────┼───────────────┼────────────────────────────────────────────┤ + │ Checksum │ uint32 │ First four bytes of double SHA256 hash │ + └──────────┴───────────────┴────────────────────────────────────────────┘ + */ /// - /// ┌───────────────────────────────────────────────────────────────────────┐ - /// │ NEO Executable Format 3 (NEF3) │ - /// ├──────────┬───────────────┬────────────────────────────────────────────┤ - /// │ Field │ Type │ Comment │ - /// ├──────────┼───────────────┼────────────────────────────────────────────┤ - /// │ Magic │ uint32 │ Magic header │ - /// │ Compiler │ byte[64] │ Compiler name and version │ - /// ├──────────┼───────────────┼────────────────────────────────────────────┤ - /// │ Reserve │ byte[2] │ Reserved for future extensions. Must be 0. │ - /// │ Tokens │ MethodToken[] │ Method tokens. │ - /// │ Reserve │ byte[2] │ Reserved for future extensions. Must be 0. │ - /// │ Script │ byte[] │ Var bytes for the payload │ - /// ├──────────┼───────────────┼────────────────────────────────────────────┤ - /// │ Checksum │ uint32 │ First four bytes of double SHA256 hash │ - /// └──────────┴───────────────┴────────────────────────────────────────────┘ + /// Represents the structure of NEO Executable Format. /// public class NefFile : ISerializable { @@ -32,25 +36,28 @@ public class NefFile : ISerializable private const uint Magic = 0x3346454E; /// - /// Compiler name and version + /// The name and version of the compiler that generated this nef file. /// public string Compiler { get; set; } /// - /// Method tokens + /// The methods that to be called statically. /// public MethodToken[] Tokens { get; set; } /// - /// Script + /// The script of the contract. /// public byte[] Script { get; set; } /// - /// Checksum + /// The checksum of the nef file. /// public uint CheckSum { get; set; } + /// + /// The maximum length of the script. + /// public const int MaxScriptLength = 512 * 1024; private const int HeaderSize = @@ -101,15 +108,19 @@ public void Deserialize(BinaryReader reader) } /// - /// Compute checksum for a file + /// Computes the checksum for the specified nef file. /// - /// File - /// Return checksum + /// The specified nef file. + /// The checksum of the nef file. public static uint ComputeChecksum(NefFile file) { - return BitConverter.ToUInt32(Crypto.Hash256(file.ToArray().AsSpan(..^sizeof(int)))); + return BinaryPrimitives.ReadUInt32LittleEndian(Crypto.Hash256(file.ToArray().AsSpan(..^sizeof(uint)))); } + /// + /// Converts the nef file to a JSON object. + /// + /// The nef file represented by a JSON object. public JObject ToJson() { return new JObject diff --git a/src/neo/SmartContract/NotifyEventArgs.cs b/src/neo/SmartContract/NotifyEventArgs.cs index 532c5080e6..9a7bbb36c4 100644 --- a/src/neo/SmartContract/NotifyEventArgs.cs +++ b/src/neo/SmartContract/NotifyEventArgs.cs @@ -7,13 +7,38 @@ namespace Neo.SmartContract { + /// + /// The of . + /// public class NotifyEventArgs : EventArgs, IInteroperable { + /// + /// The container that containing the executed script. + /// public IVerifiable ScriptContainer { get; } + + /// + /// The script hash of the contract that sends the log. + /// public UInt160 ScriptHash { get; } + + /// + /// The name of the event. + /// public string EventName { get; } + + /// + /// The arguments of the event. + /// public Array State { get; } + /// + /// Initializes a new instance of the class. + /// + /// The container that containing the executed script. + /// The script hash of the contract that sends the log. + /// The name of the event. + /// The arguments of the event. public NotifyEventArgs(IVerifiable container, UInt160 script_hash, string eventName, Array state) { this.ScriptContainer = container; diff --git a/src/neo/SmartContract/StorageContext.cs b/src/neo/SmartContract/StorageContext.cs index f9272269c8..e033b2fce1 100644 --- a/src/neo/SmartContract/StorageContext.cs +++ b/src/neo/SmartContract/StorageContext.cs @@ -1,8 +1,18 @@ namespace Neo.SmartContract { + /// + /// The storage context used to read and write data in smart contracts. + /// public class StorageContext { + /// + /// The id of the contract that owns the context. + /// public int Id; + + /// + /// Indicates whether the context is read-only. + /// public bool IsReadOnly; } } diff --git a/src/neo/SmartContract/StorageFlags.cs b/src/neo/SmartContract/StorageFlags.cs deleted file mode 100644 index 5888426e66..0000000000 --- a/src/neo/SmartContract/StorageFlags.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Neo.SmartContract -{ - [Flags] - public enum StorageFlags : byte - { - None = 0, - Constant = 0x01 - } -} diff --git a/src/neo/SmartContract/StorageItem.cs b/src/neo/SmartContract/StorageItem.cs index ff3d0d5bd1..690ec27bc7 100644 --- a/src/neo/SmartContract/StorageItem.cs +++ b/src/neo/SmartContract/StorageItem.cs @@ -7,14 +7,19 @@ namespace Neo.SmartContract { + /// + /// Represents the values in contract storage. + /// public class StorageItem : ISerializable { private byte[] value; private object cache; - public bool IsConstant; - public int Size => Value.GetVarSize() + sizeof(bool); + public int Size => Value.GetVarSize(); + /// + /// The byte array value of the . + /// public byte[] Value { get @@ -35,64 +40,95 @@ public byte[] Value } } + /// + /// Initializes a new instance of the class. + /// public StorageItem() { } - public StorageItem(byte[] value, bool isConstant = false) + /// + /// Initializes a new instance of the class. + /// + /// The byte array value of the . + public StorageItem(byte[] value) { this.value = value; - this.IsConstant = isConstant; } - public StorageItem(BigInteger value, bool isConstant = false) + /// + /// Initializes a new instance of the class. + /// + /// The integer value of the . + public StorageItem(BigInteger value) { this.cache = value; - this.IsConstant = isConstant; } - public StorageItem(IInteroperable interoperable, bool isConstant = false) + /// + /// Initializes a new instance of the class. + /// + /// The value of the . + public StorageItem(IInteroperable interoperable) { this.cache = interoperable; - this.IsConstant = isConstant; } + /// + /// Increases the integer value in the store by the specified value. + /// + /// The integer to add. public void Add(BigInteger integer) { Set(this + integer); } + /// + /// Creates a new instance of with the same value as this instance. + /// + /// The created . public StorageItem Clone() { return new StorageItem { - Value = Value, - IsConstant = IsConstant + Value = Value }; } public void Deserialize(BinaryReader reader) { - Value = reader.ReadVarBytes(); - IsConstant = reader.ReadBoolean(); + Value = reader.ReadBytes((int)(reader.BaseStream.Length)); } + /// + /// Copies the value of another instance to this instance. + /// + /// The instance to be copied. public void FromReplica(StorageItem replica) { Value = replica.Value; - IsConstant = replica.IsConstant; } + /// + /// Gets an from the storage. + /// + /// The type of the . + /// The in the storage. public T GetInteroperable() where T : IInteroperable, new() { if (cache is null) { var interoperable = new T(); - interoperable.FromStackItem(BinarySerializer.Deserialize(value, ExecutionEngineLimits.Default.MaxStackSize, ExecutionEngineLimits.Default.MaxItemSize)); + interoperable.FromStackItem(BinarySerializer.Deserialize(value, ExecutionEngineLimits.Default.MaxStackSize)); cache = interoperable; } value = null; return (T)cache; } + /// + /// Gets a list of from the storage. + /// + /// The type of the . + /// The list of the . public List GetSerializableList() where T : ISerializable, new() { cache ??= new List(value.AsSerializableArray()); @@ -102,10 +138,13 @@ public void FromReplica(StorageItem replica) public void Serialize(BinaryWriter writer) { - writer.WriteVarBytes(Value); - writer.Write(IsConstant); + writer.Write(Value); } + /// + /// Sets the integer value of the storage. + /// + /// The integer value to set. public void Set(BigInteger integer) { cache = integer; diff --git a/src/neo/SmartContract/StorageKey.cs b/src/neo/SmartContract/StorageKey.cs index 99f4c0387c..74580f20c9 100644 --- a/src/neo/SmartContract/StorageKey.cs +++ b/src/neo/SmartContract/StorageKey.cs @@ -6,13 +6,29 @@ namespace Neo.SmartContract { + /// + /// Represents the keys in contract storage. + /// public class StorageKey : IEquatable, ISerializable { + /// + /// The id of the contract. + /// public int Id; + + /// + /// The key of the storage entry. + /// public byte[] Key; int ISerializable.Size => sizeof(int) + Key.Length; + /// + /// Creates a search prefix for a contract. + /// + /// The id of the contract. + /// The prefix of the keys to search. + /// The created search prefix. public static byte[] CreateSearchPrefix(int id, ReadOnlySpan prefix) { byte[] buffer = new byte[sizeof(int) + prefix.Length]; @@ -40,7 +56,7 @@ public bool Equals(StorageKey other) public override bool Equals(object obj) { - if (!(obj is StorageKey other)) return false; + if (obj is not StorageKey other) return false; return Equals(other); } diff --git a/src/neo/SmartContract/TriggerType.cs b/src/neo/SmartContract/TriggerType.cs index 90b231d502..04b93ffc76 100644 --- a/src/neo/SmartContract/TriggerType.cs +++ b/src/neo/SmartContract/TriggerType.cs @@ -1,30 +1,42 @@ +using Neo.Network.P2P.Payloads; using System; namespace Neo.SmartContract { + /// + /// Represents the triggers for running smart contracts. + /// [Flags] public enum TriggerType : byte { + /// + /// Indicate that the contract is triggered by the system to execute the OnPersist method of the native contracts. + /// OnPersist = 0x01, + + /// + /// Indicate that the contract is triggered by the system to execute the PostPersist method of the native contracts. + /// PostPersist = 0x02, + /// - /// The verification trigger indicates that the contract is being invoked as a verification function. - /// The verification function can accept multiple parameters, and should return a boolean value that indicates the validity of the transaction or block. - /// The entry point of the contract will be invoked if the contract is triggered by Verification: - /// main(...); - /// The entry point of the contract must be able to handle this type of invocation. + /// Indicates that the contract is triggered by the verification of a . /// Verification = 0x20, + /// - /// The application trigger indicates that the contract is being invoked as an application function. - /// The application function can accept multiple parameters, change the states of the blockchain, and return any type of value. - /// The contract can have any form of entry point, but we recommend that all contracts should have the following entry point: - /// public byte[] main(string operation, params object[] args) - /// The functions can be invoked by creating an InvocationTransaction. + /// Indicates that the contract is triggered by the execution of transactions. /// Application = 0x40, + /// + /// The combination of all system triggers. + /// System = OnPersist | PostPersist, + + /// + /// The combination of all triggers. + /// All = OnPersist | PostPersist | Verification | Application } } diff --git a/src/neo/TimeProvider.cs b/src/neo/TimeProvider.cs index b3f1f0472c..8eb8817f67 100644 --- a/src/neo/TimeProvider.cs +++ b/src/neo/TimeProvider.cs @@ -2,11 +2,21 @@ namespace Neo { + /// + /// The time provider for the NEO system. + /// public class TimeProvider { - private static readonly TimeProvider Default = new TimeProvider(); + private static readonly TimeProvider Default = new(); + /// + /// The currently used instance. + /// public static TimeProvider Current { get; internal set; } = Default; + + /// + /// Gets the current time expressed as the Coordinated Universal Time (UTC). + /// public virtual DateTime UtcNow => DateTime.UtcNow; internal static void ResetToDefault() diff --git a/src/neo/UInt160.cs b/src/neo/UInt160.cs index a80ebc4a8f..344edcabbb 100644 --- a/src/neo/UInt160.cs +++ b/src/neo/UInt160.cs @@ -7,14 +7,20 @@ namespace Neo { /// - /// This class stores a 160 bit unsigned int, represented as a 20-byte little-endian byte array - /// It is composed by ulong(64) + ulong(64) + uint(32) = UInt160(160) + /// Represents a 160-bit unsigned integer. /// [StructLayout(LayoutKind.Explicit, Size = 20)] public class UInt160 : IComparable, IEquatable, ISerializable { + /// + /// The length of values. + /// public const int Length = 20; - public static readonly UInt160 Zero = new UInt160(); + + /// + /// Represents 0. + /// + public static readonly UInt160 Zero = new(); [FieldOffset(0)] private ulong value1; [FieldOffset(8)] private ulong value2; @@ -22,23 +28,26 @@ public class UInt160 : IComparable, IEquatable, ISerializable public int Size => Length; + /// + /// Initializes a new instance of the class. + /// public UInt160() { } + /// + /// Initializes a new instance of the class. + /// + /// The value of the . public unsafe UInt160(ReadOnlySpan value) { fixed (ulong* p = &value1) { - Span dst = new Span(p, Length); + Span dst = new(p, Length); value[..Length].CopyTo(dst); } } - /// - /// Method CompareTo returns 1 if this UInt160 is bigger than other UInt160; -1 if it's smaller; 0 if it's equals - /// Example: assume this is 01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4, this.CompareTo(02ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) returns 1 - /// public int CompareTo(UInt160 other) { int result = value3.CompareTo(other.value3); @@ -55,18 +64,12 @@ public void Deserialize(BinaryReader reader) value3 = reader.ReadUInt32(); } - /// - /// Method Equals returns true if objects are equal, false otherwise - /// public override bool Equals(object obj) { if (ReferenceEquals(obj, this)) return true; return Equals(obj as UInt160); } - /// - /// Method Equals returns true if objects are equal, false otherwise - /// public bool Equals(UInt160 other) { if (other is null) return false; @@ -81,9 +84,11 @@ public override int GetHashCode() } /// - /// Method Parse receives a big-endian hex string and stores as a UInt160 little-endian 20-bytes array - /// Example: Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01") should create UInt160 01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4 + /// Parses an from the specified . /// + /// An represented by a . + /// The parsed . + /// is not in the correct format. public static UInt160 Parse(string value) { if (!TryParse(value, out var result)) throw new FormatException(); @@ -103,9 +108,11 @@ public override string ToString() } /// - /// Method TryParse tries to parse a big-endian hex string and store it as a UInt160 little-endian 20-bytes array - /// Example: TryParse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01", result) should create result UInt160 01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4 + /// Parses an from the specified . /// + /// An represented by a . + /// The parsed . + /// if an is successfully parsed; otherwise, . public static bool TryParse(string s, out UInt160 result) { if (s == null) @@ -114,7 +121,7 @@ public static bool TryParse(string s, out UInt160 result) return false; } if (s.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) - s = s.Substring(2); + s = s[2..]; if (s.Length != Length * 2) { result = null; @@ -131,9 +138,6 @@ public static bool TryParse(string s, out UInt160 result) return true; } - /// - /// Returns true if left UInt160 is equals to right UInt160 - /// public static bool operator ==(UInt160 left, UInt160 right) { if (ReferenceEquals(left, right)) return true; @@ -141,45 +145,26 @@ public static bool TryParse(string s, out UInt160 result) return left.Equals(right); } - /// - /// Returns true if left UIntBase is not equals to right UIntBase - /// public static bool operator !=(UInt160 left, UInt160 right) { return !(left == right); } - /// - /// Operator > returns true if left UInt160 is bigger than right UInt160 - /// Example: UInt160(01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4) > UInt160 (02ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) is true - /// public static bool operator >(UInt160 left, UInt160 right) { return left.CompareTo(right) > 0; } - /// - /// Operator > returns true if left UInt160 is bigger or equals to right UInt160 - /// Example: UInt160(01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4) >= UInt160 (02ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) is true - /// public static bool operator >=(UInt160 left, UInt160 right) { return left.CompareTo(right) >= 0; } - /// - /// Operator > returns true if left UInt160 is less than right UInt160 - /// Example: UInt160(02ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) < UInt160 (01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4) is true - /// public static bool operator <(UInt160 left, UInt160 right) { return left.CompareTo(right) < 0; } - /// - /// Operator > returns true if left UInt160 is less or equals to right UInt160 - /// Example: UInt160(02ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) < UInt160 (01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4) is true - /// public static bool operator <=(UInt160 left, UInt160 right) { return left.CompareTo(right) <= 0; diff --git a/src/neo/UInt256.cs b/src/neo/UInt256.cs index 920a293551..72ac18b017 100644 --- a/src/neo/UInt256.cs +++ b/src/neo/UInt256.cs @@ -7,14 +7,20 @@ namespace Neo { /// - /// This class stores a 256 bit unsigned int, represented as a 32-byte little-endian byte array - /// Composed by ulong(64) + ulong(64) + ulong(64) + ulong(64) = UInt256(256) + /// Represents a 256-bit unsigned integer. /// [StructLayout(LayoutKind.Explicit, Size = 32)] public class UInt256 : IComparable, IEquatable, ISerializable { + /// + /// The length of values. + /// public const int Length = 32; - public static readonly UInt256 Zero = new UInt256(); + + /// + /// Represents 0. + /// + public static readonly UInt256 Zero = new(); [FieldOffset(0)] private ulong value1; [FieldOffset(8)] private ulong value2; @@ -23,23 +29,26 @@ public class UInt256 : IComparable, IEquatable, ISerializable public int Size => Length; + /// + /// Initializes a new instance of the class. + /// public UInt256() { } + /// + /// Initializes a new instance of the class. + /// + /// The value of the . public unsafe UInt256(ReadOnlySpan value) { fixed (ulong* p = &value1) { - Span dst = new Span(p, Length); + Span dst = new(p, Length); value[..Length].CopyTo(dst); } } - /// - /// Method CompareTo returns 1 if this UInt256 is bigger than other UInt256; -1 if it's smaller; 0 if it's equals - /// Example: assume this is 01ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a4, this.CompareTo(02ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) returns 1 - /// public int CompareTo(UInt256 other) { int result = value4.CompareTo(other.value4); @@ -59,18 +68,12 @@ public void Deserialize(BinaryReader reader) value4 = reader.ReadUInt64(); } - /// - /// Method Equals returns true if objects are equal, false otherwise - /// public override bool Equals(object obj) { if (ReferenceEquals(obj, this)) return true; return Equals(obj as UInt256); } - /// - /// Method Equals returns true if objects are equal, false otherwise - /// public bool Equals(UInt256 other) { if (other is null) return false; @@ -86,9 +89,11 @@ public override int GetHashCode() } /// - /// Method Parse receives a big-endian hex string and stores as a UInt256 little-endian 32-bytes array - /// Example: Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01") should create UInt256 01ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a4 + /// Parses an from the specified . /// + /// An represented by a . + /// The parsed . + /// is not in the correct format. public static UInt256 Parse(string value) { if (!TryParse(value, out var result)) throw new FormatException(); @@ -109,9 +114,11 @@ public override string ToString() } /// - /// Method TryParse tries to parse a big-endian hex string and store it as a UInt256 little-endian 32-bytes array - /// Example: TryParse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01", result) should create result UInt256 01ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a4 + /// Parses an from the specified . /// + /// An represented by a . + /// The parsed . + /// if an is successfully parsed; otherwise, . public static bool TryParse(string s, out UInt256 result) { if (s == null) @@ -120,7 +127,7 @@ public static bool TryParse(string s, out UInt256 result) return false; } if (s.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) - s = s.Substring(2); + s = s[2..]; if (s.Length != Length * 2) { result = null; @@ -137,9 +144,6 @@ public static bool TryParse(string s, out UInt256 result) return true; } - /// - /// Returns true if left UInt256 is equals to right UInt256 - /// public static bool operator ==(UInt256 left, UInt256 right) { if (ReferenceEquals(left, right)) return true; @@ -147,45 +151,26 @@ public static bool TryParse(string s, out UInt256 result) return left.Equals(right); } - /// - /// Returns true if left UIntBase is not equals to right UIntBase - /// public static bool operator !=(UInt256 left, UInt256 right) { return !(left == right); } - /// - /// Operator > returns true if left UInt256 is bigger than right UInt256 - /// Example: UInt256(01ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a4) > UInt256(02ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) is true - /// public static bool operator >(UInt256 left, UInt256 right) { return left.CompareTo(right) > 0; } - /// - /// Operator >= returns true if left UInt256 is bigger or equals to right UInt256 - /// Example: UInt256(01ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a4) >= UInt256(02ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) is true - /// public static bool operator >=(UInt256 left, UInt256 right) { return left.CompareTo(right) >= 0; } - /// - /// Operator < returns true if left UInt256 is less than right UInt256 - /// Example: UInt256(02ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) < UInt256(01ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a4) is true - /// public static bool operator <(UInt256 left, UInt256 right) { return left.CompareTo(right) < 0; } - /// - /// Operator <= returns true if left UInt256 is less or equals to right UInt256 - /// Example: UInt256(02ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a3) <= UInt256(01ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00a4) is true - /// public static bool operator <=(UInt256 left, UInt256 right) { return left.CompareTo(right) <= 0; diff --git a/src/neo/Utility.cs b/src/neo/Utility.cs index 7ec4a23d43..a43c0a5ba3 100644 --- a/src/neo/Utility.cs +++ b/src/neo/Utility.cs @@ -1,14 +1,13 @@ using Akka.Actor; using Akka.Event; -using Microsoft.Extensions.Configuration; using Neo.Plugins; -using System; -using System.IO; -using System.Reflection; using System.Text; namespace Neo { + /// + /// A utility class that provides common functions. + /// public static class Utility { internal class Logger : ReceiveActor @@ -20,6 +19,9 @@ public Logger() } } + /// + /// A strict UTF8 encoding used in NEO system. + /// public static Encoding StrictUTF8 { get; } static Utility() @@ -30,38 +32,11 @@ static Utility() } /// - /// Load configuration with different Environment Variable + /// Writes a log. /// - /// Configuration - /// IConfigurationRoot - public static IConfigurationRoot LoadConfig(string config) - { - var env = Environment.GetEnvironmentVariable("NEO_NETWORK"); - var configFile = string.IsNullOrWhiteSpace(env) ? $"{config}.json" : $"{config}.{env}.json"; - - // Working directory - var file = Path.Combine(Environment.CurrentDirectory, configFile); - if (!File.Exists(file)) - { - // EntryPoint folder - file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), configFile); - if (!File.Exists(file)) - { - // neo.dll folder - file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), configFile); - if (!File.Exists(file)) - { - // default config - return new ConfigurationBuilder().Build(); - } - } - } - - return new ConfigurationBuilder() - .AddJsonFile(file, true) - .Build(); - } - + /// The source of the log. Used to identify the producer of the log. + /// The level of the log. + /// The message of the log. public static void Log(string source, LogLevel level, object message) { foreach (ILogPlugin plugin in Plugin.Loggers) diff --git a/src/neo/VM/Helper.cs b/src/neo/VM/Helper.cs index 4eed45e1d2..7eae7e5d3a 100644 --- a/src/neo/VM/Helper.cs +++ b/src/neo/VM/Helper.cs @@ -13,212 +13,262 @@ namespace Neo.VM { + /// + /// A helper class related to NeoVM. + /// public static class Helper { - public static ScriptBuilder CreateArray(this ScriptBuilder sb, IReadOnlyList list = null) + /// + /// Emits the opcodes for creating an array. + /// + /// The type of the elements of the array. + /// The to be used. + /// The elements of the array. + /// The same instance as . + public static ScriptBuilder CreateArray(this ScriptBuilder builder, IReadOnlyList list = null) { if (list is null || list.Count == 0) - return sb.Emit(OpCode.NEWARRAY0); + return builder.Emit(OpCode.NEWARRAY0); for (int i = list.Count - 1; i >= 0; i--) - sb.EmitPush(list[i]); - sb.EmitPush(list.Count); - return sb.Emit(OpCode.PACK); + builder.EmitPush(list[i]); + builder.EmitPush(list.Count); + return builder.Emit(OpCode.PACK); } - public static ScriptBuilder CreateMap(this ScriptBuilder sb, IEnumerable> map = null) + /// + /// Emits the opcodes for creating a map. + /// + /// The type of the key of the map. + /// The type of the value of the map. + /// The to be used. + /// The key/value pairs of the map. + /// The same instance as . + public static ScriptBuilder CreateMap(this ScriptBuilder builder, IEnumerable> map = null) { - sb.Emit(OpCode.NEWMAP); + builder.Emit(OpCode.NEWMAP); if (map != null) foreach (var p in map) { - sb.Emit(OpCode.DUP); - sb.EmitPush(p.Key); - sb.EmitPush(p.Value); - sb.Emit(OpCode.SETITEM); + builder.Emit(OpCode.DUP); + builder.EmitPush(p.Key); + builder.EmitPush(p.Value); + builder.Emit(OpCode.SETITEM); } - return sb; + return builder; } - public static ScriptBuilder Emit(this ScriptBuilder sb, params OpCode[] ops) + /// + /// Emits the specified opcodes. + /// + /// The to be used. + /// The opcodes to emit. + /// The same instance as . + public static ScriptBuilder Emit(this ScriptBuilder builder, params OpCode[] ops) { foreach (OpCode op in ops) - sb.Emit(op); - return sb; + builder.Emit(op); + return builder; } - public static ScriptBuilder EmitDynamicCall(this ScriptBuilder sb, UInt160 scriptHash, string operation) - { - sb.Emit(OpCode.NEWARRAY0); - sb.EmitPush(CallFlags.All); - sb.EmitPush(operation); - sb.EmitPush(scriptHash); - sb.EmitSysCall(ApplicationEngine.System_Contract_Call); - return sb; - } - - public static ScriptBuilder EmitDynamicCall(this ScriptBuilder sb, UInt160 scriptHash, string operation, params ContractParameter[] args) + /// + /// Emits the opcodes for calling a contract dynamically. + /// + /// The to be used. + /// The hash of the contract to be called. + /// The method to be called in the contract. + /// The arguments for calling the contract. + /// The same instance as . + public static ScriptBuilder EmitDynamicCall(this ScriptBuilder builder, UInt160 scriptHash, string method, params object[] args) { - for (int i = args.Length - 1; i >= 0; i--) - sb.EmitPush(args[i]); - sb.EmitPush(args.Length); - sb.Emit(OpCode.PACK); - sb.EmitPush(CallFlags.All); - sb.EmitPush(operation); - sb.EmitPush(scriptHash); - sb.EmitSysCall(ApplicationEngine.System_Contract_Call); - return sb; + return EmitDynamicCall(builder, scriptHash, method, CallFlags.All, args); } - public static ScriptBuilder EmitDynamicCall(this ScriptBuilder sb, UInt160 scriptHash, string operation, params object[] args) + /// + /// Emits the opcodes for calling a contract dynamically. + /// + /// The to be used. + /// The hash of the contract to be called. + /// The method to be called in the contract. + /// The for calling the contract. + /// The arguments for calling the contract. + /// The same instance as . + public static ScriptBuilder EmitDynamicCall(this ScriptBuilder builder, UInt160 scriptHash, string method, CallFlags flags, params object[] args) { - for (int i = args.Length - 1; i >= 0; i--) - sb.EmitPush(args[i]); - sb.EmitPush(args.Length); - sb.Emit(OpCode.PACK); - sb.EmitPush(CallFlags.All); - sb.EmitPush(operation); - sb.EmitPush(scriptHash); - sb.EmitSysCall(ApplicationEngine.System_Contract_Call); - return sb; + builder.CreateArray(args); + builder.EmitPush(flags); + builder.EmitPush(method); + builder.EmitPush(scriptHash); + builder.EmitSysCall(ApplicationEngine.System_Contract_Call); + return builder; } - public static ScriptBuilder EmitPush(this ScriptBuilder sb, ISerializable data) + /// + /// Emits the opcodes for pushing the specified data onto the stack. + /// + /// The to be used. + /// The data to be pushed. + /// The same instance as . + public static ScriptBuilder EmitPush(this ScriptBuilder builder, ISerializable data) { - return sb.EmitPush(data.ToArray()); + return builder.EmitPush(data.ToArray()); } - public static ScriptBuilder EmitPush(this ScriptBuilder sb, ContractParameter parameter) + /// + /// Emits the opcodes for pushing the specified data onto the stack. + /// + /// The to be used. + /// The data to be pushed. + /// The same instance as . + public static ScriptBuilder EmitPush(this ScriptBuilder builder, ContractParameter parameter) { if (parameter.Value is null) - sb.Emit(OpCode.PUSHNULL); + builder.Emit(OpCode.PUSHNULL); else switch (parameter.Type) { case ContractParameterType.Signature: case ContractParameterType.ByteArray: - sb.EmitPush((byte[])parameter.Value); + builder.EmitPush((byte[])parameter.Value); break; case ContractParameterType.Boolean: - sb.EmitPush((bool)parameter.Value); + builder.EmitPush((bool)parameter.Value); break; case ContractParameterType.Integer: if (parameter.Value is BigInteger bi) - sb.EmitPush(bi); + builder.EmitPush(bi); else - sb.EmitPush((BigInteger)typeof(BigInteger).GetConstructor(new[] { parameter.Value.GetType() }).Invoke(new[] { parameter.Value })); + builder.EmitPush((BigInteger)typeof(BigInteger).GetConstructor(new[] { parameter.Value.GetType() }).Invoke(new[] { parameter.Value })); break; case ContractParameterType.Hash160: - sb.EmitPush((UInt160)parameter.Value); + builder.EmitPush((UInt160)parameter.Value); break; case ContractParameterType.Hash256: - sb.EmitPush((UInt256)parameter.Value); + builder.EmitPush((UInt256)parameter.Value); break; case ContractParameterType.PublicKey: - sb.EmitPush((ECPoint)parameter.Value); + builder.EmitPush((ECPoint)parameter.Value); break; case ContractParameterType.String: - sb.EmitPush((string)parameter.Value); + builder.EmitPush((string)parameter.Value); break; case ContractParameterType.Array: { IList parameters = (IList)parameter.Value; for (int i = parameters.Count - 1; i >= 0; i--) - sb.EmitPush(parameters[i]); - sb.EmitPush(parameters.Count); - sb.Emit(OpCode.PACK); + builder.EmitPush(parameters[i]); + builder.EmitPush(parameters.Count); + builder.Emit(OpCode.PACK); } break; case ContractParameterType.Map: { var pairs = (IList>)parameter.Value; - sb.CreateMap(pairs); + builder.CreateMap(pairs); } break; default: - throw new ArgumentException(); + throw new ArgumentException(null, nameof(parameter)); } - return sb; + return builder; } - public static ScriptBuilder EmitPush(this ScriptBuilder sb, object obj) + /// + /// Emits the opcodes for pushing the specified data onto the stack. + /// + /// The to be used. + /// The data to be pushed. + /// The same instance as . + public static ScriptBuilder EmitPush(this ScriptBuilder builder, object obj) { switch (obj) { case bool data: - sb.EmitPush(data); + builder.EmitPush(data); break; case byte[] data: - sb.EmitPush(data); + builder.EmitPush(data); break; case string data: - sb.EmitPush(data); + builder.EmitPush(data); break; case BigInteger data: - sb.EmitPush(data); + builder.EmitPush(data); break; case ISerializable data: - sb.EmitPush(data); + builder.EmitPush(data); break; case sbyte data: - sb.EmitPush(data); + builder.EmitPush(data); break; case byte data: - sb.EmitPush(data); + builder.EmitPush(data); break; case short data: - sb.EmitPush(data); + builder.EmitPush(data); break; case ushort data: - sb.EmitPush(data); + builder.EmitPush(data); break; case int data: - sb.EmitPush(data); + builder.EmitPush(data); break; case uint data: - sb.EmitPush(data); + builder.EmitPush(data); break; case long data: - sb.EmitPush(data); + builder.EmitPush(data); break; case ulong data: - sb.EmitPush(data); + builder.EmitPush(data); break; case Enum data: - sb.EmitPush(BigInteger.Parse(data.ToString("d"))); + builder.EmitPush(BigInteger.Parse(data.ToString("d"))); break; case ContractParameter data: - sb.EmitPush(data); + builder.EmitPush(data); break; case null: - sb.Emit(OpCode.PUSHNULL); + builder.Emit(OpCode.PUSHNULL); break; default: - throw new ArgumentException(); + throw new ArgumentException(null, nameof(obj)); } - return sb; + return builder; } - public static ScriptBuilder EmitSysCall(this ScriptBuilder sb, uint method, params object[] args) + /// + /// Emits the opcodes for invoking an interoperable service. + /// + /// The to be used. + /// The hash of the interoperable service. + /// The arguments for calling the interoperable service. + /// The same instance as . + public static ScriptBuilder EmitSysCall(this ScriptBuilder builder, uint method, params object[] args) { for (int i = args.Length - 1; i >= 0; i--) - EmitPush(sb, args[i]); - return sb.EmitSysCall(method); + EmitPush(builder, args[i]); + return builder.EmitSysCall(method); } /// - /// Generate scripts to call a specific method from a specific contract. + /// Generates the script for calling a contract dynamically. /// - /// contract script hash - /// contract operation - /// operation arguments - /// - public static byte[] MakeScript(this UInt160 scriptHash, string operation, params object[] args) + /// The hash of the contract to be called. + /// The method to be called in the contract. + /// The arguments for calling the contract. + /// The generated script. + public static byte[] MakeScript(this UInt160 scriptHash, string method, params object[] args) { - using ScriptBuilder sb = new ScriptBuilder(); - sb.EmitDynamicCall(scriptHash, operation, args); + using ScriptBuilder sb = new(); + sb.EmitDynamicCall(scriptHash, method, args); return sb.ToArray(); } + /// + /// Converts the to a JSON object. + /// + /// The to convert. + /// The represented by a JSON object. public static JObject ToJson(this StackItem item) { return ToJson(item, null); @@ -226,7 +276,7 @@ public static JObject ToJson(this StackItem item) private static JObject ToJson(StackItem item, HashSet context) { - JObject json = new JObject(); + JObject json = new(); json["type"] = item.Type; switch (item) { @@ -250,7 +300,7 @@ private static JObject ToJson(StackItem item, HashSet context) if (!context.Add(map)) throw new InvalidOperationException(); json["value"] = new JArray(map.Select(p => { - JObject item = new JObject(); + JObject item = new(); item["key"] = ToJson(p.Key, context); item["value"] = ToJson(p.Value, context); return item; @@ -263,6 +313,11 @@ private static JObject ToJson(StackItem item, HashSet context) return json; } + /// + /// Converts the to a . + /// + /// The to convert. + /// The converted . public static ContractParameter ToParameter(this StackItem item) { return ToParameter(item, null); @@ -270,7 +325,7 @@ public static ContractParameter ToParameter(this StackItem item) private static ContractParameter ToParameter(StackItem item, List<(StackItem, ContractParameter)> context) { - if (item is null) throw new ArgumentNullException(); + if (item is null) throw new ArgumentNullException(nameof(item)); ContractParameter parameter = null; switch (item) { @@ -337,6 +392,11 @@ private static ContractParameter ToParameter(StackItem item, List<(StackItem, Co return parameter; } + /// + /// Converts the to a . + /// + /// The to convert. + /// The converted . public static StackItem ToStackItem(this ContractParameter parameter) { return ToStackItem(parameter, null); @@ -344,7 +404,7 @@ public static StackItem ToStackItem(this ContractParameter parameter) private static StackItem ToStackItem(ContractParameter parameter, List<(StackItem, ContractParameter)> context) { - if (parameter is null) throw new ArgumentNullException(); + if (parameter is null) throw new ArgumentNullException(nameof(parameter)); if (parameter.Value is null) return StackItem.Null; StackItem stackItem = null; switch (parameter.Type) @@ -367,7 +427,7 @@ private static StackItem ToStackItem(ContractParameter parameter, List<(StackIte (stackItem, _) = context.FirstOrDefault(p => ReferenceEquals(p.Item2, parameter)); if (stackItem is null) { - Map map = new Map(); + Map map = new(); foreach (var pair in (IList>)parameter.Value) map[(PrimitiveType)ToStackItem(pair.Key, context)] = ToStackItem(pair.Value, context); stackItem = map; diff --git a/src/neo/Wallets/AssetDescriptor.cs b/src/neo/Wallets/AssetDescriptor.cs index 15430e9a2b..baf3fa102a 100644 --- a/src/neo/Wallets/AssetDescriptor.cs +++ b/src/neo/Wallets/AssetDescriptor.cs @@ -1,4 +1,3 @@ -using Neo.Ledger; using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -7,28 +6,51 @@ namespace Neo.Wallets { + /// + /// Represents the descriptor of an asset. + /// public class AssetDescriptor { + /// + /// The id of the asset. + /// public UInt160 AssetId { get; } + + /// + /// The name of the asset. + /// public string AssetName { get; } + + /// + /// The symbol of the asset. + /// public string Symbol { get; } + + /// + /// The number of decimal places of the token. + /// public byte Decimals { get; } - public AssetDescriptor(UInt160 asset_id) + /// + /// Initializes a new instance of the class. + /// + /// The snapshot used to read data. + /// The used by the . + /// The id of the asset. + public AssetDescriptor(DataCache snapshot, ProtocolSettings settings, UInt160 asset_id) { - DataCache snapshot = Blockchain.Singleton.View; var contract = NativeContract.ContractManagement.GetContract(snapshot, asset_id); - if (contract is null) throw new ArgumentException(); + if (contract is null) throw new ArgumentException(null, nameof(asset_id)); byte[] script; - using (ScriptBuilder sb = new ScriptBuilder()) + using (ScriptBuilder sb = new()) { - sb.EmitDynamicCall(asset_id, "decimals"); - sb.EmitDynamicCall(asset_id, "symbol"); + sb.EmitDynamicCall(asset_id, "decimals", CallFlags.ReadOnly); + sb.EmitDynamicCall(asset_id, "symbol", CallFlags.ReadOnly); script = sb.ToArray(); } - using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, gas: 0_10000000); - if (engine.State.HasFlag(VMState.FAULT)) throw new ArgumentException(); + using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: settings, gas: 0_10000000); + if (engine.State != VMState.HALT) throw new ArgumentException(null, nameof(asset_id)); this.AssetId = asset_id; this.AssetName = contract.Manifest.Name; this.Symbol = engine.ResultStack.Pop().GetString(); diff --git a/src/neo/Wallets/Helper.cs b/src/neo/Wallets/Helper.cs index 4dd06b39e5..3183a13600 100644 --- a/src/neo/Wallets/Helper.cs +++ b/src/neo/Wallets/Helper.cs @@ -6,32 +6,49 @@ namespace Neo.Wallets { + /// + /// A helper class related to wallets. + /// public static class Helper { - public static byte[] Sign(this IVerifiable verifiable, KeyPair key) - { - return Sign(verifiable, key, ProtocolSettings.Default.Magic); - } - + /// + /// Signs an with the specified private key. + /// + /// The to sign. + /// The private key to be used. + /// The magic number of the NEO network. + /// The signature for the . public static byte[] Sign(this IVerifiable verifiable, KeyPair key, uint magic) { - return Crypto.Sign(verifiable.GetHashData(magic), key.PrivateKey, key.PublicKey.EncodePoint(false)[1..]); + return Crypto.Sign(verifiable.GetSignData(magic), key.PrivateKey, key.PublicKey.EncodePoint(false)[1..]); } - public static string ToAddress(this UInt160 scriptHash) + /// + /// Converts the specified script hash to an address. + /// + /// The script hash to convert. + /// The address version. + /// The converted address. + public static string ToAddress(this UInt160 scriptHash, byte version) { Span data = stackalloc byte[21]; - data[0] = ProtocolSettings.Default.AddressVersion; + data[0] = version; scriptHash.ToArray().CopyTo(data[1..]); return Base58.Base58CheckEncode(data); } - public static UInt160 ToScriptHash(this string address) + /// + /// Converts the specified address to a script hash. + /// + /// The address to convert. + /// The address version. + /// The converted script hash. + public static UInt160 ToScriptHash(this string address, byte version) { byte[] data = address.Base58CheckDecode(); if (data.Length != 21) throw new FormatException(); - if (data[0] != ProtocolSettings.Default.AddressVersion) + if (data[0] != version) throw new FormatException(); return new UInt160(data.AsSpan(1)); } diff --git a/src/neo/Wallets/KeyPair.cs b/src/neo/Wallets/KeyPair.cs index 8acf280afe..6718e59f56 100644 --- a/src/neo/Wallets/KeyPair.cs +++ b/src/neo/Wallets/KeyPair.cs @@ -1,23 +1,42 @@ using Neo.Cryptography; using Neo.SmartContract; +using Neo.Wallets.NEP6; using Org.BouncyCastle.Crypto.Generators; using System; +using System.Security.Cryptography; using System.Text; using static Neo.Wallets.Helper; namespace Neo.Wallets { + /// + /// Represents a private/public key pair in wallets. + /// public class KeyPair : IEquatable { + /// + /// The private key. + /// public readonly byte[] PrivateKey; + + /// + /// The public key. + /// public readonly Cryptography.ECC.ECPoint PublicKey; + /// + /// The hash of the public key. + /// public UInt160 PublicKeyHash => PublicKey.EncodePoint(true).ToScriptHash(); + /// + /// Initializes a new instance of the class. + /// + /// The private key in the . public KeyPair(byte[] privateKey) { if (privateKey.Length != 32 && privateKey.Length != 96 && privateKey.Length != 104) - throw new ArgumentException(); + throw new ArgumentException(null, nameof(privateKey)); this.PrivateKey = privateKey[^32..]; if (privateKey.Length == 32) { @@ -41,6 +60,10 @@ public override bool Equals(object obj) return Equals(obj as KeyPair); } + /// + /// Exports the private key in WIF format. + /// + /// The private key in WIF format. public string Export() { Span data = stackalloc byte[34]; @@ -52,15 +75,24 @@ public string Export() return wif; } - public string Export(string passphrase, int N = 16384, int r = 8, int p = 8) + /// + /// Exports the private key in NEP-2 format. + /// + /// The passphrase of the private key. + /// The address version. + /// The N field of the to be used. + /// The R field of the to be used. + /// The P field of the to be used. + /// The private key in NEP-2 format. + public string Export(string passphrase, byte version, int N = 16384, int r = 8, int p = 8) { UInt160 script_hash = Contract.CreateSignatureRedeemScript(PublicKey).ToScriptHash(); - string address = script_hash.ToAddress(); + string address = script_hash.ToAddress(version); byte[] addresshash = Encoding.ASCII.GetBytes(address).Sha256().Sha256()[..4]; byte[] derivedkey = SCrypt.Generate(Encoding.UTF8.GetBytes(passphrase), addresshash, N, r, p, 64); byte[] derivedhalf1 = derivedkey[..32]; byte[] derivedhalf2 = derivedkey[32..]; - byte[] encryptedkey = XOR(PrivateKey, derivedhalf1).AES256Encrypt(derivedhalf2); + byte[] encryptedkey = Encrypt(XOR(PrivateKey, derivedhalf1), derivedhalf2); Span buffer = stackalloc byte[39]; buffer[0] = 0x01; buffer[1] = 0x42; @@ -70,6 +102,16 @@ public string Export(string passphrase, int N = 16384, int r = 8, int p = 8) return Base58.Base58CheckEncode(buffer); } + private static byte[] Encrypt(byte[] data, byte[] key) + { + using Aes aes = Aes.Create(); + aes.Key = key; + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + using ICryptoTransform encryptor = aes.CreateEncryptor(); + return encryptor.TransformFinalBlock(data, 0, data.Length); + } + public override int GetHashCode() { return PublicKey.GetHashCode(); diff --git a/src/neo/Wallets/NEP6/NEP6Account.cs b/src/neo/Wallets/NEP6/NEP6Account.cs index 870b449400..1820fc305f 100644 --- a/src/neo/Wallets/NEP6/NEP6Account.cs +++ b/src/neo/Wallets/NEP6/NEP6Account.cs @@ -4,7 +4,7 @@ namespace Neo.Wallets.NEP6 { - internal class NEP6Account : WalletAccount + sealed class NEP6Account : WalletAccount { private readonly NEP6Wallet wallet; private string nep2key; @@ -16,25 +16,25 @@ internal class NEP6Account : WalletAccount public override bool HasKey => nep2key != null; public NEP6Account(NEP6Wallet wallet, UInt160 scriptHash, string nep2key = null) - : base(scriptHash) + : base(scriptHash, wallet.ProtocolSettings) { this.wallet = wallet; this.nep2key = nep2key; } public NEP6Account(NEP6Wallet wallet, UInt160 scriptHash, KeyPair key, string password) - : this(wallet, scriptHash, key.Export(password, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P)) + : this(wallet, scriptHash, key.Export(password, wallet.ProtocolSettings.AddressVersion, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P)) { this.key = key; } public static NEP6Account FromJson(JObject json, NEP6Wallet wallet) { - return new NEP6Account(wallet, json["address"].AsString().ToScriptHash(), json["key"]?.AsString()) + return new NEP6Account(wallet, json["address"].GetString().ToScriptHash(wallet.ProtocolSettings.AddressVersion), json["key"]?.GetString()) { - Label = json["label"]?.AsString(), - IsDefault = json["isdefault"].AsBoolean(), - Lock = json["lock"].AsBoolean(), + Label = json["label"]?.GetString(), + IsDefault = json["isdefault"].GetBoolean(), + Lock = json["lock"].GetBoolean(), Contract = NEP6Contract.FromJson(json["contract"]), Extra = json["extra"] }; @@ -55,15 +55,15 @@ public KeyPair GetKey(string password) if (nep2key == null) return null; if (key == null) { - key = new KeyPair(Wallet.GetPrivateKeyFromNEP2(nep2key, password, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P)); + key = new KeyPair(Wallet.GetPrivateKeyFromNEP2(nep2key, password, ProtocolSettings.AddressVersion, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P)); } return key; } public JObject ToJson() { - JObject account = new JObject(); - account["address"] = ScriptHash.ToAddress(); + JObject account = new(); + account["address"] = ScriptHash.ToAddress(ProtocolSettings.AddressVersion); account["label"] = Label; account["isdefault"] = IsDefault; account["lock"] = Lock; @@ -77,7 +77,7 @@ public bool VerifyPassword(string password) { try { - Wallet.GetPrivateKeyFromNEP2(nep2key, password, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P); + Wallet.GetPrivateKeyFromNEP2(nep2key, password, ProtocolSettings.AddressVersion, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P); return true; } catch (FormatException) @@ -104,14 +104,14 @@ internal bool ChangePasswordPrepare(string password_old, string password_new) { try { - keyTemplate = new KeyPair(Wallet.GetPrivateKeyFromNEP2(nep2key, password_old, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P)); + keyTemplate = new KeyPair(Wallet.GetPrivateKeyFromNEP2(nep2key, password_old, ProtocolSettings.AddressVersion, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P)); } catch { return false; } } - nep2KeyNew = keyTemplate.Export(password_new, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P); + nep2KeyNew = keyTemplate.Export(password_new, ProtocolSettings.AddressVersion, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P); return true; } diff --git a/src/neo/Wallets/NEP6/NEP6Contract.cs b/src/neo/Wallets/NEP6/NEP6Contract.cs index af27db6c4c..970e0b8055 100644 --- a/src/neo/Wallets/NEP6/NEP6Contract.cs +++ b/src/neo/Wallets/NEP6/NEP6Contract.cs @@ -24,11 +24,11 @@ public static NEP6Contract FromJson(JObject json) public JObject ToJson() { - JObject contract = new JObject(); + JObject contract = new(); contract["script"] = Convert.ToBase64String(Script); contract["parameters"] = new JArray(ParameterList.Zip(ParameterNames, (type, name) => { - JObject parameter = new JObject(); + JObject parameter = new(); parameter["name"] = name; parameter["type"] = type; return parameter; diff --git a/src/neo/Wallets/NEP6/NEP6Wallet.cs b/src/neo/Wallets/NEP6/NEP6Wallet.cs index 3a5af6d7af..f1440420ae 100644 --- a/src/neo/Wallets/NEP6/NEP6Wallet.cs +++ b/src/neo/Wallets/NEP6/NEP6Wallet.cs @@ -11,6 +11,10 @@ namespace Neo.Wallets.NEP6 { + /// + /// An implementation of the NEP-6 wallet standard. + /// + /// https://github.com/neo-project/proposals/blob/master/nep-6.mediawiki public class NEP6Wallet : Wallet { private string password; @@ -19,11 +23,25 @@ public class NEP6Wallet : Wallet private readonly Dictionary accounts; private readonly JObject extra; + /// + /// The parameters of the SCrypt algorithm used for encrypting and decrypting the private keys in the wallet. + /// public readonly ScryptParameters Scrypt; + public override string Name => name; + + /// + /// The version of the wallet standard. It is currently fixed at 1.0 and will be used for functional upgrades in the future. + /// public override Version Version => version; - public NEP6Wallet(string path, string name = null) : base(path) + /// + /// Loads or creates a wallet at the specified path. + /// + /// The path of the wallet file. + /// The to be used by the wallet. + /// The name of the wallet. If the wallet is loaded from an existing file, this parameter is ignored. + public NEP6Wallet(string path, ProtocolSettings settings, string name = null) : base(path, settings) { if (File.Exists(path)) { @@ -33,23 +51,27 @@ public NEP6Wallet(string path, string name = null) : base(path) else { this.name = name; - this.version = Version.Parse("3.0"); + this.version = Version.Parse("1.0"); this.Scrypt = ScryptParameters.Default; this.accounts = new Dictionary(); this.extra = JObject.Null; } } - internal NEP6Wallet(JObject wallet) : base(null) + /// + /// Loads the wallet with the specified JSON string. + /// + /// The path of the wallet. + /// The to be used by the wallet. + /// The JSON object representing the wallet. + public NEP6Wallet(string path, ProtocolSettings settings, JObject json) : base(path, settings) { - LoadFromJson(wallet, out Scrypt, out accounts, out extra); + LoadFromJson(json, out Scrypt, out accounts, out extra); } private void LoadFromJson(JObject wallet, out ScryptParameters scrypt, out Dictionary accounts, out JObject extra) { this.version = Version.Parse(wallet["version"].AsString()); - if (this.version.Major < 3) throw new FormatException(); - this.name = wallet["name"]?.AsString(); scrypt = ScryptParameters.FromJson(wallet["scrypt"]); accounts = ((JArray)wallet["accounts"]).Select(p => NEP6Account.FromJson(p, this)).ToDictionary(p => p.ScriptHash); @@ -95,15 +117,15 @@ public override bool Contains(UInt160 scriptHash) public override WalletAccount CreateAccount(byte[] privateKey) { - KeyPair key = new KeyPair(privateKey); - NEP6Contract contract = new NEP6Contract + KeyPair key = new(privateKey); + NEP6Contract contract = new() { Script = Contract.CreateSignatureRedeemScript(key.PublicKey), ParameterList = new[] { ContractParameterType.Signature }, ParameterNames = new[] { "signature" }, Deployed = false }; - NEP6Account account = new NEP6Account(this, contract.ScriptHash, key, password) + NEP6Account account = new(this, contract.ScriptHash, key, password) { Contract = contract }; @@ -113,8 +135,7 @@ public override WalletAccount CreateAccount(byte[] privateKey) public override WalletAccount CreateAccount(Contract contract, KeyPair key = null) { - NEP6Contract nep6contract = contract as NEP6Contract; - if (nep6contract == null) + if (contract is not NEP6Contract nep6contract) { nep6contract = new NEP6Contract { @@ -136,14 +157,19 @@ public override WalletAccount CreateAccount(Contract contract, KeyPair key = nul public override WalletAccount CreateAccount(UInt160 scriptHash) { - NEP6Account account = new NEP6Account(this, scriptHash); + NEP6Account account = new(this, scriptHash); AddAccount(account, true); return account; } + /// + /// Decrypts the specified NEP-2 string with the password of the wallet. + /// + /// The NEP-2 string to decrypt. + /// The decrypted private key. public KeyPair DecryptKey(string nep2key) { - return new KeyPair(GetPrivateKeyFromNEP2(nep2key, password, Scrypt.N, Scrypt.R, Scrypt.P)); + return new KeyPair(GetPrivateKeyFromNEP2(nep2key, password, ProtocolSettings.AddressVersion, Scrypt.N, Scrypt.R, Scrypt.P)); } public override bool DeleteAccount(UInt160 scriptHash) @@ -179,14 +205,14 @@ public override WalletAccount Import(X509Certificate2 cert) { key = new KeyPair(ecdsa.ExportParameters(true).D); } - NEP6Contract contract = new NEP6Contract + NEP6Contract contract = new() { Script = Contract.CreateSignatureRedeemScript(key.PublicKey), ParameterList = new[] { ContractParameterType.Signature }, ParameterNames = new[] { "signature" }, Deployed = false }; - NEP6Account account = new NEP6Account(this, contract.ScriptHash, key, password) + NEP6Account account = new(this, contract.ScriptHash, key, password) { Contract = contract }; @@ -196,15 +222,15 @@ public override WalletAccount Import(X509Certificate2 cert) public override WalletAccount Import(string wif) { - KeyPair key = new KeyPair(GetPrivateKeyFromWIF(wif)); - NEP6Contract contract = new NEP6Contract + KeyPair key = new(GetPrivateKeyFromWIF(wif)); + NEP6Contract contract = new() { Script = Contract.CreateSignatureRedeemScript(key.PublicKey), ParameterList = new[] { ContractParameterType.Signature }, ParameterNames = new[] { "signature" }, Deployed = false }; - NEP6Account account = new NEP6Account(this, contract.ScriptHash, key, password) + NEP6Account account = new(this, contract.ScriptHash, key, password) { Contract = contract }; @@ -214,8 +240,8 @@ public override WalletAccount Import(string wif) public override WalletAccount Import(string nep2, string passphrase, int N = 16384, int r = 8, int p = 8) { - KeyPair key = new KeyPair(GetPrivateKeyFromNEP2(nep2, passphrase, N, r, p)); - NEP6Contract contract = new NEP6Contract + KeyPair key = new(GetPrivateKeyFromNEP2(nep2, passphrase, ProtocolSettings.AddressVersion, N, r, p)); + NEP6Contract contract = new() { Script = Contract.CreateSignatureRedeemScript(key.PublicKey), ParameterList = new[] { ContractParameterType.Signature }, @@ -237,10 +263,18 @@ internal void Lock() password = null; } - public static NEP6Wallet Migrate(string path, string db3path, string password) + /// + /// Migrates the accounts from to a new . + /// + /// The path of the new wallet file. + /// The path of the db3 wallet file. + /// The password of the wallets. + /// The to be used by the wallet. + /// The created new wallet. + public static NEP6Wallet Migrate(string path, string db3path, string password, ProtocolSettings settings) { - UserWallet wallet_old = UserWallet.Open(db3path, password); - NEP6Wallet wallet_new = new NEP6Wallet(path, wallet_old.Name); + UserWallet wallet_old = UserWallet.Open(db3path, password, settings); + NEP6Wallet wallet_new = new(path, settings, wallet_old.Name); using (wallet_new.Unlock(password)) { foreach (WalletAccount account in wallet_old.GetAccounts()) @@ -251,9 +285,12 @@ public static NEP6Wallet Migrate(string path, string db3path, string password) return wallet_new; } + /// + /// Saves the wallet to the file. + /// public void Save() { - JObject wallet = new JObject(); + JObject wallet = new(); wallet["name"] = name; wallet["version"] = version.ToString(); wallet["scrypt"] = Scrypt.ToJson(); @@ -262,6 +299,11 @@ public void Save() File.WriteAllText(Path, wallet.ToString()); } + /// + /// Unlocks the wallet with the specified password. + /// + /// The password of the wallet. + /// The object that can be disposed to lock the wallet again. public IDisposable Unlock(string password) { if (!VerifyPassword(password)) diff --git a/src/neo/Wallets/NEP6/ScryptParameters.cs b/src/neo/Wallets/NEP6/ScryptParameters.cs index a987159ad6..f90c17ec68 100644 --- a/src/neo/Wallets/NEP6/ScryptParameters.cs +++ b/src/neo/Wallets/NEP6/ScryptParameters.cs @@ -2,12 +2,37 @@ namespace Neo.Wallets.NEP6 { + /// + /// Represents the parameters of the SCrypt algorithm. + /// public class ScryptParameters { + /// + /// The default parameters used by . + /// public static ScryptParameters Default { get; } = new ScryptParameters(16384, 8, 8); - public readonly int N, R, P; + /// + /// CPU/Memory cost parameter. Must be larger than 1, a power of 2 and less than 2^(128 * r / 8). + /// + public readonly int N; + /// + /// The block size, must be >= 1. + /// + public readonly int R; + + /// + /// Parallelization parameter. Must be a positive integer less than or equal to Int32.MaxValue / (128 * r * 8). + /// + public readonly int P; + + /// + /// Initializes a new instance of the class. + /// + /// CPU/Memory cost parameter. + /// The block size. + /// Parallelization parameter. public ScryptParameters(int n, int r, int p) { this.N = n; @@ -15,14 +40,23 @@ public ScryptParameters(int n, int r, int p) this.P = p; } + /// + /// Converts the parameters from a JSON object. + /// + /// The parameters represented by a JSON object. + /// The converted parameters. public static ScryptParameters FromJson(JObject json) { return new ScryptParameters((int)json["n"].AsNumber(), (int)json["r"].AsNumber(), (int)json["p"].AsNumber()); } + /// + /// Converts the parameters to a JSON object. + /// + /// The parameters represented by a JSON object. public JObject ToJson() { - JObject json = new JObject(); + JObject json = new(); json["n"] = N; json["r"] = R; json["p"] = P; diff --git a/src/neo/Wallets/NEP6/WalletLocker.cs b/src/neo/Wallets/NEP6/WalletLocker.cs index 45574b5cfe..63ef0baf1b 100644 --- a/src/neo/Wallets/NEP6/WalletLocker.cs +++ b/src/neo/Wallets/NEP6/WalletLocker.cs @@ -4,7 +4,7 @@ namespace Neo.Wallets.NEP6 { internal class WalletLocker : IDisposable { - private NEP6Wallet wallet; + private readonly NEP6Wallet wallet; public WalletLocker(NEP6Wallet wallet) { diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index 8d69d941c4..7dec2a93e5 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -15,9 +15,12 @@ namespace Neo.Wallets.SQLite { + /// + /// A wallet implementation that uses SQLite as the underlying storage. + /// public class UserWallet : Wallet { - private readonly object db_lock = new object(); + private readonly object db_lock = new(); private readonly byte[] iv; private readonly byte[] salt; private readonly byte[] masterKey; @@ -40,35 +43,24 @@ public override Version Version } } - /// - /// Open an existing wallet - /// - /// Path - /// Password Key - private UserWallet(string path, byte[] passwordKey) : base(path) + private UserWallet(string path, byte[] passwordKey, ProtocolSettings settings) : base(path, settings) { this.salt = LoadStoredData("Salt"); byte[] passwordHash = LoadStoredData("PasswordHash"); if (passwordHash != null && !passwordHash.SequenceEqual(passwordKey.Concat(salt).ToArray().Sha256())) throw new CryptographicException(); this.iv = LoadStoredData("IV"); - this.masterKey = LoadStoredData("MasterKey").AesDecrypt(passwordKey, iv); + this.masterKey = Decrypt(LoadStoredData("MasterKey"), passwordKey, iv); this.scrypt = new ScryptParameters ( - BitConverter.ToInt32(LoadStoredData("ScryptN")), - BitConverter.ToInt32(LoadStoredData("ScryptR")), - BitConverter.ToInt32(LoadStoredData("ScryptP")) + BinaryPrimitives.ReadInt32LittleEndian(LoadStoredData("ScryptN")), + BinaryPrimitives.ReadInt32LittleEndian(LoadStoredData("ScryptR")), + BinaryPrimitives.ReadInt32LittleEndian(LoadStoredData("ScryptP")) ); this.accounts = LoadAccounts(); } - /// - /// Create a new wallet - /// - /// Path - /// Password Key - /// Scrypt initialization value - private UserWallet(string path, byte[] passwordKey, ScryptParameters scrypt) : base(path) + private UserWallet(string path, byte[] passwordKey, ProtocolSettings settings, ScryptParameters scrypt) : base(path, settings) { this.iv = new byte[16]; this.salt = new byte[20]; @@ -82,15 +74,20 @@ private UserWallet(string path, byte[] passwordKey, ScryptParameters scrypt) : b rng.GetBytes(masterKey); } Version version = Assembly.GetExecutingAssembly().GetName().Version; + byte[] versionBuffer = new byte[sizeof(int) * 4]; + BinaryPrimitives.WriteInt32LittleEndian(versionBuffer, version.Major); + BinaryPrimitives.WriteInt32LittleEndian(versionBuffer.AsSpan(4), version.Minor); + BinaryPrimitives.WriteInt32LittleEndian(versionBuffer.AsSpan(8), version.Build); + BinaryPrimitives.WriteInt32LittleEndian(versionBuffer.AsSpan(12), version.Revision); BuildDatabase(); SaveStoredData("IV", iv); SaveStoredData("Salt", salt); SaveStoredData("PasswordHash", passwordKey.Concat(salt).ToArray().Sha256()); - SaveStoredData("MasterKey", masterKey.AesEncrypt(passwordKey, iv)); - SaveStoredData("Version", new[] { version.Major, version.Minor, version.Build, version.Revision }.Select(p => BitConverter.GetBytes(p)).SelectMany(p => p).ToArray()); - SaveStoredData("ScryptN", BitConverter.GetBytes(this.scrypt.N)); - SaveStoredData("ScryptR", BitConverter.GetBytes(this.scrypt.R)); - SaveStoredData("ScryptP", BitConverter.GetBytes(this.scrypt.P)); + SaveStoredData("MasterKey", Encrypt(masterKey, passwordKey, iv)); + SaveStoredData("Version", versionBuffer); + SaveStoredData("ScryptN", this.scrypt.N); + SaveStoredData("ScryptR", this.scrypt.R); + SaveStoredData("ScryptP", this.scrypt.P); } private void AddAccount(UserWalletAccount account) @@ -107,64 +104,62 @@ private void AddAccount(UserWalletAccount account) accounts[account.ScriptHash] = account; } lock (db_lock) - using (WalletDataContext ctx = new WalletDataContext(Path)) + { + using WalletDataContext ctx = new(Path); + if (account.HasKey) { - if (account.HasKey) + string passphrase = Encoding.UTF8.GetString(masterKey); + Account db_account = ctx.Accounts.FirstOrDefault(p => p.PublicKeyHash == account.Key.PublicKeyHash.ToArray()); + if (db_account == null) { - string passphrase = Encoding.UTF8.GetString(masterKey); - Account db_account = ctx.Accounts.FirstOrDefault(p => p.PublicKeyHash == account.Key.PublicKeyHash.ToArray()); - if (db_account == null) + db_account = ctx.Accounts.Add(new Account { - db_account = ctx.Accounts.Add(new Account - { - Nep2key = account.Key.Export(passphrase, scrypt.N, scrypt.R, scrypt.P), - PublicKeyHash = account.Key.PublicKeyHash.ToArray() - }).Entity; - } - else - { - db_account.Nep2key = account.Key.Export(passphrase, scrypt.N, scrypt.R, scrypt.P); - } + Nep2key = account.Key.Export(passphrase, ProtocolSettings.AddressVersion, scrypt.N, scrypt.R, scrypt.P), + PublicKeyHash = account.Key.PublicKeyHash.ToArray() + }).Entity; } - if (account.Contract != null) + else { - Contract db_contract = ctx.Contracts.FirstOrDefault(p => p.ScriptHash == account.Contract.ScriptHash.ToArray()); - if (db_contract != null) - { - db_contract.PublicKeyHash = account.Key.PublicKeyHash.ToArray(); - } - else + db_account.Nep2key = account.Key.Export(passphrase, ProtocolSettings.AddressVersion, scrypt.N, scrypt.R, scrypt.P); + } + } + if (account.Contract != null) + { + Contract db_contract = ctx.Contracts.FirstOrDefault(p => p.ScriptHash == account.Contract.ScriptHash.ToArray()); + if (db_contract != null) + { + db_contract.PublicKeyHash = account.Key.PublicKeyHash.ToArray(); + } + else + { + ctx.Contracts.Add(new Contract { - ctx.Contracts.Add(new Contract - { - RawData = ((VerificationContract)account.Contract).ToArray(), - ScriptHash = account.Contract.ScriptHash.ToArray(), - PublicKeyHash = account.Key.PublicKeyHash.ToArray() - }); - } + RawData = ((VerificationContract)account.Contract).ToArray(), + ScriptHash = account.Contract.ScriptHash.ToArray(), + PublicKeyHash = account.Key.PublicKeyHash.ToArray() + }); } - //add address + } + //add address + { + Address db_address = ctx.Addresses.FirstOrDefault(p => p.ScriptHash == account.ScriptHash.ToArray()); + if (db_address == null) { - Address db_address = ctx.Addresses.FirstOrDefault(p => p.ScriptHash == account.ScriptHash.ToArray()); - if (db_address == null) + ctx.Addresses.Add(new Address { - ctx.Addresses.Add(new Address - { - ScriptHash = account.ScriptHash.ToArray() - }); - } + ScriptHash = account.ScriptHash.ToArray() + }); } - ctx.SaveChanges(); } + ctx.SaveChanges(); + } } private void BuildDatabase() { - using (WalletDataContext ctx = new WalletDataContext(Path)) - { - ctx.Database.EnsureDeleted(); - ctx.Database.EnsureCreated(); - } + using WalletDataContext ctx = new(Path); + ctx.Database.EnsureDeleted(); + ctx.Database.EnsureCreated(); } public override bool ChangePassword(string oldPassword, string newPassword) @@ -174,7 +169,7 @@ public override bool ChangePassword(string oldPassword, string newPassword) try { SaveStoredData("PasswordHash", passwordKey.Concat(salt).ToArray().Sha256()); - SaveStoredData("MasterKey", masterKey.AesEncrypt(passwordKey, iv)); + SaveStoredData("MasterKey", Encrypt(masterKey, passwordKey, iv)); return true; } finally @@ -191,25 +186,41 @@ public override bool Contains(UInt160 scriptHash) } } - public static UserWallet Create(string path, string password, ScryptParameters scrypt = null) + /// + /// Creates a new wallet at the specified path. + /// + /// The path of the wallet. + /// The password of the wallet. + /// The to be used by the wallet. + /// The parameters of the SCrypt algorithm used for encrypting and decrypting the private keys in the wallet. + /// The created wallet. + public static UserWallet Create(string path, string password, ProtocolSettings settings, ScryptParameters scrypt = null) { - return new UserWallet(path, password.ToAesKey(), scrypt ?? ScryptParameters.Default); + return new UserWallet(path, password.ToAesKey(), settings, scrypt ?? ScryptParameters.Default); } - public static UserWallet Create(string path, SecureString password, ScryptParameters scrypt = null) + /// + /// Creates a new wallet at the specified path. + /// + /// The path of the wallet. + /// The password of the wallet. + /// The to be used by the wallet. + /// The parameters of the SCrypt algorithm used for encrypting and decrypting the private keys in the wallet. + /// The created wallet. + public static UserWallet Create(string path, SecureString password, ProtocolSettings settings, ScryptParameters scrypt = null) { - return new UserWallet(path, password.ToAesKey(), scrypt ?? ScryptParameters.Default); + return new UserWallet(path, password.ToAesKey(), settings, scrypt ?? ScryptParameters.Default); } public override WalletAccount CreateAccount(byte[] privateKey) { - KeyPair key = new KeyPair(privateKey); - VerificationContract contract = new VerificationContract + KeyPair key = new(privateKey); + VerificationContract contract = new() { Script = SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), ParameterList = new[] { ContractParameterType.Signature } }; - UserWalletAccount account = new UserWalletAccount(contract.ScriptHash) + UserWalletAccount account = new(contract.ScriptHash, ProtocolSettings) { Key = key, Contract = contract @@ -220,8 +231,7 @@ public override WalletAccount CreateAccount(byte[] privateKey) public override WalletAccount CreateAccount(SmartContract.Contract contract, KeyPair key = null) { - VerificationContract verification_contract = contract as VerificationContract; - if (verification_contract == null) + if (contract is not VerificationContract verification_contract) { verification_contract = new VerificationContract { @@ -229,7 +239,7 @@ public override WalletAccount CreateAccount(SmartContract.Contract contract, Key ParameterList = contract.ParameterList }; } - UserWalletAccount account = new UserWalletAccount(verification_contract.ScriptHash) + UserWalletAccount account = new(verification_contract.ScriptHash, ProtocolSettings) { Key = key, Contract = verification_contract @@ -240,7 +250,7 @@ public override WalletAccount CreateAccount(SmartContract.Contract contract, Key public override WalletAccount CreateAccount(UInt160 scriptHash) { - UserWalletAccount account = new UserWalletAccount(scriptHash); + UserWalletAccount account = new(scriptHash, ProtocolSettings); AddAccount(account); return account; } @@ -256,25 +266,25 @@ public override bool DeleteAccount(UInt160 scriptHash) if (account != null) { lock (db_lock) - using (WalletDataContext ctx = new WalletDataContext(Path)) + { + using WalletDataContext ctx = new(Path); + if (account.HasKey) { - if (account.HasKey) - { - Account db_account = ctx.Accounts.First(p => p.PublicKeyHash == account.Key.PublicKeyHash.ToArray()); - ctx.Accounts.Remove(db_account); - } - if (account.Contract != null) - { - Contract db_contract = ctx.Contracts.First(p => p.ScriptHash == scriptHash.ToArray()); - ctx.Contracts.Remove(db_contract); - } - //delete address - { - Address db_address = ctx.Addresses.First(p => p.ScriptHash == scriptHash.ToArray()); - ctx.Addresses.Remove(db_address); - } - ctx.SaveChanges(); + Account db_account = ctx.Accounts.First(p => p.PublicKeyHash == account.Key.PublicKeyHash.ToArray()); + ctx.Accounts.Remove(db_account); } + if (account.Contract != null) + { + Contract db_contract = ctx.Contracts.First(p => p.ScriptHash == scriptHash.ToArray()); + ctx.Contracts.Remove(db_contract); + } + //delete address + { + Address db_address = ctx.Addresses.First(p => p.ScriptHash == scriptHash.ToArray()); + ctx.Addresses.Remove(db_address); + } + ctx.SaveChanges(); + } return true; } return false; @@ -300,47 +310,64 @@ public override IEnumerable GetAccounts() private Dictionary LoadAccounts() { - using (WalletDataContext ctx = new WalletDataContext(Path)) + using WalletDataContext ctx = new(Path); + string passphrase = Encoding.UTF8.GetString(masterKey); + Dictionary accounts = ctx.Addresses.Select(p => p.ScriptHash).AsEnumerable().Select(p => new UserWalletAccount(new UInt160(p), ProtocolSettings)).ToDictionary(p => p.ScriptHash); + foreach (Contract db_contract in ctx.Contracts.Include(p => p.Account)) { - string passphrase = Encoding.UTF8.GetString(masterKey); - Dictionary accounts = ctx.Addresses.Select(p => p.ScriptHash).AsEnumerable().Select(p => new UserWalletAccount(new UInt160(p))).ToDictionary(p => p.ScriptHash); - foreach (Contract db_contract in ctx.Contracts.Include(p => p.Account)) - { - VerificationContract contract = db_contract.RawData.AsSerializable(); - UserWalletAccount account = accounts[contract.ScriptHash]; - account.Contract = contract; - account.Key = new KeyPair(GetPrivateKeyFromNEP2(db_contract.Account.Nep2key, passphrase, scrypt.N, scrypt.R, scrypt.P)); - } - return accounts; + VerificationContract contract = db_contract.RawData.AsSerializable(); + UserWalletAccount account = accounts[contract.ScriptHash]; + account.Contract = contract; + account.Key = new KeyPair(GetPrivateKeyFromNEP2(db_contract.Account.Nep2key, passphrase, ProtocolSettings.AddressVersion, scrypt.N, scrypt.R, scrypt.P)); } + return accounts; } private byte[] LoadStoredData(string name) { - using (WalletDataContext ctx = new WalletDataContext(Path)) - { - return ctx.Keys.FirstOrDefault(p => p.Name == name)?.Value; - } + using WalletDataContext ctx = new(Path); + return ctx.Keys.FirstOrDefault(p => p.Name == name)?.Value; } - public static UserWallet Open(string path, string password) + /// + /// Opens a wallet at the specified path. + /// + /// The path of the wallet. + /// The password of the wallet. + /// The to be used by the wallet. + /// The opened wallet. + public static UserWallet Open(string path, string password, ProtocolSettings settings) { - return new UserWallet(path, password.ToAesKey()); + return new UserWallet(path, password.ToAesKey(), settings); } - public static UserWallet Open(string path, SecureString password) + /// + /// Opens a wallet at the specified path. + /// + /// The path of the wallet. + /// The password of the wallet. + /// The to be used by the wallet. + /// The opened wallet. + public static UserWallet Open(string path, SecureString password, ProtocolSettings settings) { - return new UserWallet(path, password.ToAesKey()); + return new UserWallet(path, password.ToAesKey(), settings); + } + + private void SaveStoredData(string name, int value) + { + byte[] data = new byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(data, value); + SaveStoredData(name, data); } private void SaveStoredData(string name, byte[] value) { lock (db_lock) - using (WalletDataContext ctx = new WalletDataContext(Path)) - { - SaveStoredData(ctx, name, value); - ctx.SaveChanges(); - } + { + using WalletDataContext ctx = new(Path); + SaveStoredData(ctx, name, value); + ctx.SaveChanges(); + } } private static void SaveStoredData(WalletDataContext ctx, string name, byte[] value) @@ -364,5 +391,25 @@ public override bool VerifyPassword(string password) { return password.ToAesKey().Concat(salt).ToArray().Sha256().SequenceEqual(LoadStoredData("PasswordHash")); } + + private static byte[] Encrypt(byte[] data, byte[] key, byte[] iv) + { + if (data == null || key == null || iv == null) throw new ArgumentNullException(); + if (data.Length % 16 != 0 || key.Length != 32 || iv.Length != 16) throw new ArgumentException(); + using Aes aes = Aes.Create(); + aes.Padding = PaddingMode.None; + using ICryptoTransform encryptor = aes.CreateEncryptor(key, iv); + return encryptor.TransformFinalBlock(data, 0, data.Length); + } + + private static byte[] Decrypt(byte[] data, byte[] key, byte[] iv) + { + if (data == null || key == null || iv == null) throw new ArgumentNullException(); + if (data.Length % 16 != 0 || key.Length != 32 || iv.Length != 16) throw new ArgumentException(); + using Aes aes = Aes.Create(); + aes.Padding = PaddingMode.None; + using ICryptoTransform decryptor = aes.CreateDecryptor(key, iv); + return decryptor.TransformFinalBlock(data, 0, data.Length); + } } } diff --git a/src/neo/Wallets/SQLite/UserWalletAccount.cs b/src/neo/Wallets/SQLite/UserWalletAccount.cs index d586f59d36..adfba104e6 100644 --- a/src/neo/Wallets/SQLite/UserWalletAccount.cs +++ b/src/neo/Wallets/SQLite/UserWalletAccount.cs @@ -1,13 +1,13 @@ namespace Neo.Wallets.SQLite { - internal class UserWalletAccount : WalletAccount + sealed class UserWalletAccount : WalletAccount { public KeyPair Key; public override bool HasKey => Key != null; - public UserWalletAccount(UInt160 scriptHash) - : base(scriptHash) + public UserWalletAccount(UInt160 scriptHash, ProtocolSettings settings) + : base(scriptHash, settings) { } diff --git a/src/neo/Wallets/SQLite/VerificationContract.cs b/src/neo/Wallets/SQLite/VerificationContract.cs index 8c325b3211..7b8df238c2 100644 --- a/src/neo/Wallets/SQLite/VerificationContract.cs +++ b/src/neo/Wallets/SQLite/VerificationContract.cs @@ -6,7 +6,7 @@ namespace Neo.Wallets.SQLite { - public class VerificationContract : SmartContract.Contract, IEquatable, ISerializable + class VerificationContract : SmartContract.Contract, IEquatable, ISerializable { public int Size => ParameterList.GetVarSize() + Script.GetVarSize(); diff --git a/src/neo/Wallets/SQLite/WalletDataContext.cs b/src/neo/Wallets/SQLite/WalletDataContext.cs index efc22b58d9..3ed9a497c4 100644 --- a/src/neo/Wallets/SQLite/WalletDataContext.cs +++ b/src/neo/Wallets/SQLite/WalletDataContext.cs @@ -20,7 +20,7 @@ public WalletDataContext(string filename) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - SqliteConnectionStringBuilder sb = new SqliteConnectionStringBuilder + SqliteConnectionStringBuilder sb = new() { DataSource = filename }; diff --git a/src/neo/Wallets/TransferOutput.cs b/src/neo/Wallets/TransferOutput.cs index ad3d202871..e8cc7d71f3 100644 --- a/src/neo/Wallets/TransferOutput.cs +++ b/src/neo/Wallets/TransferOutput.cs @@ -1,10 +1,28 @@ namespace Neo.Wallets { + /// + /// Represents an output of a transfer. + /// public class TransferOutput { + /// + /// The id of the asset to transfer. + /// public UInt160 AssetId; + + /// + /// The amount of the asset to transfer. + /// public BigDecimal Value; + + /// + /// The account to transfer to. + /// public UInt160 ScriptHash; + + /// + /// The object to be passed to the transfer method of NEP-17. + /// public object Data; } } diff --git a/src/neo/Wallets/Wallet.cs b/src/neo/Wallets/Wallet.cs index 788e0c8e16..c6ecb567ac 100644 --- a/src/neo/Wallets/Wallet.cs +++ b/src/neo/Wallets/Wallet.cs @@ -1,11 +1,11 @@ using Neo.Cryptography; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; +using Neo.Wallets.NEP6; using Org.BouncyCastle.Crypto.Generators; using System; using System.Collections.Generic; @@ -14,35 +14,109 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using static Neo.SmartContract.Helper; using static Neo.Wallets.Helper; using ECPoint = Neo.Cryptography.ECC.ECPoint; namespace Neo.Wallets { + /// + /// The base class of wallets. + /// public abstract class Wallet { + /// + /// The to be used by the wallet. + /// + public ProtocolSettings ProtocolSettings { get; } + + /// + /// The name of the wallet. + /// public abstract string Name { get; } + + /// + /// The path of the wallet. + /// public string Path { get; } + + /// + /// The version of the wallet. + /// public abstract Version Version { get; } + /// + /// Changes the password of the wallet. + /// + /// The old password of the wallet. + /// The new password to be used. + /// if the password is changed successfully; otherwise, . public abstract bool ChangePassword(string oldPassword, string newPassword); + + /// + /// Determines whether the specified account is included in the wallet. + /// + /// The hash of the account. + /// if the account is included in the wallet; otherwise, . public abstract bool Contains(UInt160 scriptHash); + + /// + /// Creates a standard account with the specified private key. + /// + /// The private key of the account. + /// The created account. public abstract WalletAccount CreateAccount(byte[] privateKey); + + /// + /// Creates a contract account for the wallet. + /// + /// The contract of the account. + /// The private key of the account. + /// The created account. public abstract WalletAccount CreateAccount(Contract contract, KeyPair key = null); + + /// + /// Creates a watch-only account for the wallet. + /// + /// The hash of the account. + /// The created account. public abstract WalletAccount CreateAccount(UInt160 scriptHash); + + /// + /// Deletes an account from the wallet. + /// + /// The hash of the account. + /// if the account is removed; otherwise, . public abstract bool DeleteAccount(UInt160 scriptHash); + + /// + /// Gets the account with the specified hash. + /// + /// The hash of the account. + /// The account with the specified hash. public abstract WalletAccount GetAccount(UInt160 scriptHash); - public abstract IEnumerable GetAccounts(); - internal Wallet() - { - } + /// + /// Gets all the accounts from the wallet. + /// + /// All accounts in the wallet. + public abstract IEnumerable GetAccounts(); - protected Wallet(string path) + /// + /// Initializes a new instance of the class. + /// + /// The path of the wallet file. + /// The to be used by the wallet. + protected Wallet(string path, ProtocolSettings settings) { + this.ProtocolSettings = settings; this.Path = path; } + /// + /// Creates a standard account for the wallet. + /// + /// The created account. public WalletAccount CreateAccount() { byte[] privateKey = new byte[32]; @@ -55,13 +129,19 @@ public WalletAccount CreateAccount() return account; } + /// + /// Creates a contract account for the wallet. + /// + /// The contract of the account. + /// The private key of the account. + /// The created account. public WalletAccount CreateAccount(Contract contract, byte[] privateKey) { if (privateKey == null) return CreateAccount(contract); return CreateAccount(contract, new KeyPair(privateKey)); } - private List<(UInt160 Account, BigInteger Value)> FindPayingAccounts(List<(UInt160 Account, BigInteger Value)> orderedAccounts, BigInteger amount) + private static List<(UInt160 Account, BigInteger Value)> FindPayingAccounts(List<(UInt160 Account, BigInteger Value)> orderedAccounts, BigInteger amount) { var result = new List<(UInt160 Account, BigInteger Value)>(); BigInteger sum_balance = orderedAccounts.Select(p => p.Value).Sum(); @@ -119,40 +199,78 @@ public WalletAccount CreateAccount(Contract contract, byte[] privateKey) return result; } + /// + /// Gets the account with the specified public key. + /// + /// The public key of the account. + /// The account with the specified public key. public WalletAccount GetAccount(ECPoint pubkey) { return GetAccount(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()); } - public BigDecimal GetAvailable(UInt160 asset_id) + /// + /// Gets the available balance for the specified asset in the wallet. + /// + /// The snapshot used to read data. + /// The id of the asset. + /// The available balance for the specified asset. + public BigDecimal GetAvailable(DataCache snapshot, UInt160 asset_id) { UInt160[] accounts = GetAccounts().Where(p => !p.WatchOnly).Select(p => p.ScriptHash).ToArray(); - return GetBalance(asset_id, accounts); + return GetBalance(snapshot, asset_id, accounts); } - public BigDecimal GetBalance(UInt160 asset_id, params UInt160[] accounts) + /// + /// Gets the balance for the specified asset in the wallet. + /// + /// The snapshot used to read data. + /// The id of the asset. + /// The accounts to be counted. + /// The balance for the specified asset. + public BigDecimal GetBalance(DataCache snapshot, UInt160 asset_id, params UInt160[] accounts) { byte[] script; - using (ScriptBuilder sb = new ScriptBuilder()) + using (ScriptBuilder sb = new()) { sb.EmitPush(0); foreach (UInt160 account in accounts) { - sb.EmitDynamicCall(asset_id, "balanceOf", account); + sb.EmitDynamicCall(asset_id, "balanceOf", CallFlags.ReadOnly, account); sb.Emit(OpCode.ADD); } - sb.EmitDynamicCall(asset_id, "decimals"); + sb.EmitDynamicCall(asset_id, "decimals", CallFlags.ReadOnly); script = sb.ToArray(); } - using ApplicationEngine engine = ApplicationEngine.Run(script, gas: 20000000L * accounts.Length); - if (engine.State.HasFlag(VMState.FAULT)) + using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: ProtocolSettings, gas: 20000000L * accounts.Length); + if (engine.State == VMState.FAULT) return new BigDecimal(BigInteger.Zero, 0); byte decimals = (byte)engine.ResultStack.Pop().GetInteger(); BigInteger amount = engine.ResultStack.Pop().GetInteger(); return new BigDecimal(amount, decimals); } - public static byte[] GetPrivateKeyFromNEP2(string nep2, string passphrase, int N = 16384, int r = 8, int p = 8) + private static byte[] Decrypt(byte[] data, byte[] key) + { + using Aes aes = Aes.Create(); + aes.Key = key; + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + using ICryptoTransform decryptor = aes.CreateDecryptor(); + return decryptor.TransformFinalBlock(data, 0, data.Length); + } + + /// + /// Decodes a private key from the specified NEP-2 string. + /// + /// The NEP-2 string to be decoded. + /// The passphrase of the private key. + /// The address version of NEO system. + /// The N field of the to be used. + /// The R field of the to be used. + /// The P field of the to be used. + /// The decoded private key. + public static byte[] GetPrivateKeyFromNEP2(string nep2, string passphrase, byte version, int N = 16384, int r = 8, int p = 8) { if (nep2 == null) throw new ArgumentNullException(nameof(nep2)); if (passphrase == null) throw new ArgumentNullException(nameof(passphrase)); @@ -170,20 +288,25 @@ public static byte[] GetPrivateKeyFromNEP2(string nep2, string passphrase, int N byte[] encryptedkey = new byte[32]; Buffer.BlockCopy(data, 7, encryptedkey, 0, 32); Array.Clear(data, 0, data.Length); - byte[] prikey = XOR(encryptedkey.AES256Decrypt(derivedhalf2), derivedhalf1); + byte[] prikey = XOR(Decrypt(encryptedkey, derivedhalf2), derivedhalf1); Array.Clear(derivedhalf1, 0, derivedhalf1.Length); Array.Clear(derivedhalf2, 0, derivedhalf2.Length); ECPoint pubkey = Cryptography.ECC.ECCurve.Secp256r1.G * prikey; UInt160 script_hash = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash(); - string address = script_hash.ToAddress(); + string address = script_hash.ToAddress(version); if (!Encoding.ASCII.GetBytes(address).Sha256().Sha256().AsSpan(0, 4).SequenceEqual(addresshash)) throw new FormatException(); return prikey; } + /// + /// Decodes a private key from the specified WIF string. + /// + /// The WIF string to be decoded. + /// The decoded private key. public static byte[] GetPrivateKeyFromWIF(string wif) { - if (wif == null) throw new ArgumentNullException(); + if (wif is null) throw new ArgumentNullException(nameof(wif)); byte[] data = wif.Base58CheckDecode(); if (data.Length != 34 || data[0] != 0x80 || data[33] != 0x01) throw new FormatException(); @@ -200,7 +323,7 @@ private static Signer[] GetSigners(UInt160 sender, Signer[] cosigners) if (cosigners[i].Account.Equals(sender)) { if (i == 0) return cosigners; - List list = new List(cosigners); + List list = new(cosigners); list.RemoveAt(i); list.Insert(0, cosigners[i]); return list.ToArray(); @@ -213,6 +336,11 @@ private static Signer[] GetSigners(UInt160 sender, Signer[] cosigners) }).ToArray(); } + /// + /// Imports an account from a . + /// + /// The to import. + /// The imported account. public virtual WalletAccount Import(X509Certificate2 cert) { byte[] privateKey; @@ -225,6 +353,11 @@ public virtual WalletAccount Import(X509Certificate2 cert) return account; } + /// + /// Imports an account from the specified WIF string. + /// + /// The WIF string to import. + /// The imported account. public virtual WalletAccount Import(string wif) { byte[] privateKey = GetPrivateKeyFromWIF(wif); @@ -233,15 +366,32 @@ public virtual WalletAccount Import(string wif) return account; } + /// + /// Imports an account from the specified NEP-2 string. + /// + /// The NEP-2 string to import. + /// The passphrase of the private key. + /// The N field of the to be used. + /// The R field of the to be used. + /// The P field of the to be used. + /// The imported account. public virtual WalletAccount Import(string nep2, string passphrase, int N = 16384, int r = 8, int p = 8) { - byte[] privateKey = GetPrivateKeyFromNEP2(nep2, passphrase, N, r, p); + byte[] privateKey = GetPrivateKeyFromNEP2(nep2, passphrase, ProtocolSettings.AddressVersion, N, r, p); WalletAccount account = CreateAccount(privateKey); Array.Clear(privateKey, 0, privateKey.Length); return account; } - public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null, Signer[] cosigners = null) + /// + /// Makes a transaction to transfer assets. + /// + /// The snapshot used to read data. + /// The array of that contain the asset, amount, and targets of the transfer. + /// The account to transfer from. + /// The cosigners to be added to the transction. + /// The created transction. + public Transaction MakeTransaction(DataCache snapshot, TransferOutput[] outputs, UInt160 from = null, Signer[] cosigners = null) { UInt160[] accounts; if (from is null) @@ -252,27 +402,24 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null { accounts = new[] { from }; } - DataCache snapshot = Blockchain.Singleton.View; Dictionary cosignerList = cosigners?.ToDictionary(p => p.Account) ?? new Dictionary(); byte[] script; List<(UInt160 Account, BigInteger Value)> balances_gas = null; - using (ScriptBuilder sb = new ScriptBuilder()) + using (ScriptBuilder sb = new()) { 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.EmitDynamicCall(assetId, "balanceOf", account); - using (ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot)) - { - if (engine.State.HasFlag(VMState.FAULT)) - throw new InvalidOperationException($"Execution for {assetId}.balanceOf('{account}' fault"); - BigInteger value = engine.ResultStack.Pop().GetInteger(); - if (value.Sign > 0) balances.Add((account, value)); - } - } + { + using ScriptBuilder sb2 = new(); + sb2.EmitDynamicCall(assetId, "balanceOf", CallFlags.ReadOnly, account); + using ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot, settings: ProtocolSettings); + if (engine.State != VMState.HALT) + throw new InvalidOperationException($"Execution for {assetId}.balanceOf('{account}' fault"); + BigInteger value = engine.ResultStack.Pop().GetInteger(); + 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} found: {sum_balance}"); @@ -310,7 +457,17 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null return MakeTransaction(snapshot, script, cosignerList.Values.ToArray(), Array.Empty(), balances_gas); } - public Transaction MakeTransaction(byte[] script, UInt160 sender = null, Signer[] cosigners = null, TransactionAttribute[] attributes = null, long maxGas = ApplicationEngine.TestModeGas) + /// + /// Makes a transaction to run a smart contract. + /// + /// The snapshot used to read data. + /// The script to be loaded in the transaction. + /// The sender of the transaction. + /// The cosigners to be added to the transction. + /// The attributes to be added to the transction. + /// The maximum gas that can be spent to execute the script. + /// The created transction. + public Transaction MakeTransaction(DataCache snapshot, byte[] script, UInt160 sender = null, Signer[] cosigners = null, TransactionAttribute[] attributes = null, long maxGas = ApplicationEngine.TestModeGas) { UInt160[] accounts; if (sender is null) @@ -321,17 +478,16 @@ public Transaction MakeTransaction(byte[] script, UInt160 sender = null, Signer[ { accounts = new[] { sender }; } - DataCache snapshot = Blockchain.Singleton.View; var balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); return MakeTransaction(snapshot, script, cosigners ?? Array.Empty(), attributes ?? Array.Empty(), balances_gas, maxGas); } private Transaction MakeTransaction(DataCache snapshot, byte[] script, Signer[] cosigners, TransactionAttribute[] attributes, List<(UInt160 Account, BigInteger Value)> balances_gas, long maxGas = ApplicationEngine.TestModeGas) { - Random rand = new Random(); + Random rand = new(); foreach (var (account, value) in balances_gas) { - Transaction tx = new Transaction + Transaction tx = new() { Version = 0, Nonce = (uint)rand.Next(), @@ -342,7 +498,7 @@ private Transaction MakeTransaction(DataCache snapshot, byte[] script, Signer[] }; // will try to execute 'transfer' script to check if it works - using (ApplicationEngine engine = ApplicationEngine.Run(script, snapshot.CreateSnapshot(), tx, gas: maxGas)) + using (ApplicationEngine engine = ApplicationEngine.Run(script, snapshot.CreateSnapshot(), tx, settings: ProtocolSettings, gas: maxGas)) { if (engine.State == VMState.FAULT) { @@ -357,6 +513,12 @@ private Transaction MakeTransaction(DataCache snapshot, byte[] script, Signer[] throw new InvalidOperationException("Insufficient GAS"); } + /// + /// Calculates the network fee for the specified transaction. + /// + /// The snapshot used to read data. + /// The transaction to calculate. + /// The network fee of the transaction. public long CalculateNetworkFee(DataCache snapshot, Transaction tx) { UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); @@ -397,9 +559,8 @@ public long CalculateNetworkFee(DataCache snapshot, Transaction tx) size += Array.Empty().GetVarSize() * 2; // Check verify cost - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot()); - engine.LoadContract(contract, md, CallFlags.None); - if (NativeContract.IsNative(hash)) engine.Push("verify"); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: ProtocolSettings); + engine.LoadContract(contract, md, CallFlags.ReadOnly); if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault."); if (!engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.Hash} returns false."); @@ -408,19 +569,13 @@ public long CalculateNetworkFee(DataCache snapshot, Transaction tx) else if (witness_script.IsSignatureContract()) { size += 67 + witness_script.GetVarSize(); - networkFee += exec_fee_factor * (ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.ECDsaVerifyPrice); + networkFee += exec_fee_factor * SignatureContractCost(); } else if (witness_script.IsMultiSigContract(out int m, out int n)) { int size_inv = 66 * m; size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize(); - networkFee += exec_fee_factor * ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * m; - using (ScriptBuilder sb = new ScriptBuilder()) - networkFee += exec_fee_factor * ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]]; - networkFee += exec_fee_factor * ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n; - using (ScriptBuilder sb = new ScriptBuilder()) - networkFee += exec_fee_factor * ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; - networkFee += exec_fee_factor * (ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.ECDsaVerifyPrice * n); + networkFee += exec_fee_factor * MultiSignatureContractCost(m, n); } else { @@ -431,6 +586,11 @@ public long CalculateNetworkFee(DataCache snapshot, Transaction tx) return networkFee; } + /// + /// Signs the in the specified with the wallet. + /// + /// The to be used. + /// if the signature is successfully added to the context; otherwise, . public bool Sign(ContractParametersContext context) { bool fSuccess = false; @@ -452,7 +612,7 @@ public bool Sign(ContractParametersContext context) account = GetAccount(point); if (account?.HasKey != true) continue; KeyPair key = account.GetKey(); - byte[] signature = context.Verifiable.Sign(key); + byte[] signature = context.Verifiable.Sign(key, ProtocolSettings.Magic); fSuccess |= context.AddSignature(multiSigContract, key.PublicKey, signature); if (fSuccess) m--; if (context.Completed || m <= 0) break; @@ -463,7 +623,7 @@ public bool Sign(ContractParametersContext context) { // Try to sign with regular accounts KeyPair key = account.GetKey(); - byte[] signature = context.Verifiable.Sign(key); + byte[] signature = context.Verifiable.Sign(key, ProtocolSettings.Magic); fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature); continue; } @@ -471,7 +631,7 @@ public bool Sign(ContractParametersContext context) // Try Smart contract verification - var contract = NativeContract.ContractManagement.GetContract(Blockchain.Singleton.View, scriptHash); + var contract = NativeContract.ContractManagement.GetContract(context.Snapshot, scriptHash); if (contract != null) { @@ -489,6 +649,11 @@ public bool Sign(ContractParametersContext context) return fSuccess; } + /// + /// Checks that the specified password is correct for the wallet. + /// + /// The password to be checked. + /// if the password is correct; otherwise, . public abstract bool VerifyPassword(string password); } } diff --git a/src/neo/Wallets/WalletAccount.cs b/src/neo/Wallets/WalletAccount.cs index 5e2408a1d0..e00f07934b 100644 --- a/src/neo/Wallets/WalletAccount.cs +++ b/src/neo/Wallets/WalletAccount.cs @@ -2,22 +2,70 @@ namespace Neo.Wallets { + /// + /// Represents an account in a wallet. + /// public abstract class WalletAccount { + /// + /// The to be used by the wallet. + /// + protected readonly ProtocolSettings ProtocolSettings; + + /// + /// The hash of the account. + /// public readonly UInt160 ScriptHash; + + /// + /// The label of the account. + /// public string Label; + + /// + /// Indicates whether the account is the default account in the wallet. + /// public bool IsDefault; + + /// + /// Indicates whether the account is locked. + /// public bool Lock; + + /// + /// The contract of the account. + /// public Contract Contract; - public string Address => ScriptHash.ToAddress(); + /// + /// The address of the account. + /// + public string Address => ScriptHash.ToAddress(ProtocolSettings.AddressVersion); + + /// + /// Indicates whether the account contains a private key. + /// public abstract bool HasKey { get; } + + /// + /// Indicates whether the account is a watch-only account. + /// public bool WatchOnly => Contract == null; + /// + /// Gets the private key of the account. + /// + /// The private key of the account. Or if there is no private key in the account. public abstract KeyPair GetKey(); - protected WalletAccount(UInt160 scriptHash) + /// + /// Initializes a new instance of the class. + /// + /// The hash of the account. + /// The to be used by the wallet. + protected WalletAccount(UInt160 scriptHash, ProtocolSettings settings) { + this.ProtocolSettings = settings; this.ScriptHash = scriptHash; } } diff --git a/src/neo/neo.csproj b/src/neo/neo.csproj index 73789a7ad7..97862f6edc 100644 --- a/src/neo/neo.csproj +++ b/src/neo/neo.csproj @@ -4,7 +4,7 @@ 2015-2021 The Neo Project Neo 3.0.0 - preview5 + rc1 The Neo Project net5.0 true @@ -18,6 +18,7 @@ Neo The Neo Project Neo + true @@ -25,11 +26,11 @@ - + - - + + diff --git a/tests/neo.UnitTests/Cryptography/ECC/UT_ECDSA.cs b/tests/neo.UnitTests/Cryptography/ECC/UT_ECDSA.cs deleted file mode 100644 index b776fd4eff..0000000000 --- a/tests/neo.UnitTests/Cryptography/ECC/UT_ECDSA.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Cryptography.ECC; -using Neo.IO; -using System; -using ECCurve = Neo.Cryptography.ECC.ECCurve; - -namespace Neo.UnitTests.Cryptography.ECC -{ - [TestClass] - public class UT_ECDSA - { - [TestMethod] - public void GenerateSignature() - { - var ecdsa = new ECDsa(ECCurve.Secp256k1.Infinity); - Assert.ThrowsException(() => ecdsa.GenerateSignature(UInt256.Zero.ToArray())); - Assert.ThrowsException(() => ecdsa.VerifySignature(new byte[0], 1, 2)); - - var pk = new byte[32]; - for (int x = 0; x < pk.Length; x++) pk[x] = (byte)x; - - ecdsa = new ECDsa(pk, ECCurve.Secp256k1); - - var zero = UInt256.Zero.ToArray(); - var one = UInt256.Parse("0100000000000000000000000000000000000000000000000000000000000000").ToArray(); - var two = UInt256.Parse("0200000000000000000000000000000000000000000000000000000000000000").ToArray(); - var sig = ecdsa.GenerateSignature(one); - - Assert.IsTrue(ecdsa.VerifySignature(one, sig[0], sig[1])); - Assert.IsFalse(ecdsa.VerifySignature(two, sig[0], sig[1])); - Assert.IsFalse(ecdsa.VerifySignature(one, sig[0] + 1, sig[1])); - Assert.IsFalse(ecdsa.VerifySignature(one, sig[0], sig[1] + 1)); - Assert.IsFalse(ecdsa.VerifySignature(zero, sig[0], sig[1])); - } - } -} diff --git a/tests/neo.UnitTests/Cryptography/UT_Crypto.cs b/tests/neo.UnitTests/Cryptography/UT_Crypto.cs index 0154fea65a..b4f0862b8d 100644 --- a/tests/neo.UnitTests/Cryptography/UT_Crypto.cs +++ b/tests/neo.UnitTests/Cryptography/UT_Crypto.cs @@ -52,11 +52,12 @@ public void TestVerifySignature() wrongKey[0] = 0x03; for (int i = 1; i < 33; i++) wrongKey[i] = byte.MaxValue; - Crypto.VerifySignature(message, signature, wrongKey, Neo.Cryptography.ECC.ECCurve.Secp256r1).Should().BeFalse(); - - wrongKey = new byte[36]; Action action = () => Crypto.VerifySignature(message, signature, wrongKey, Neo.Cryptography.ECC.ECCurve.Secp256r1); action.Should().Throw(); + + wrongKey = new byte[36]; + action = () => Crypto.VerifySignature(message, signature, wrongKey, Neo.Cryptography.ECC.ECCurve.Secp256r1); + action.Should().Throw(); } [TestMethod] diff --git a/tests/neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs b/tests/neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs index 1b69ed11a2..5689dce488 100644 --- a/tests/neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs +++ b/tests/neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs @@ -13,101 +13,6 @@ namespace Neo.UnitTests.Cryptography [TestClass] public class UT_Cryptography_Helper { - [TestMethod] - public void TestAES256Encrypt() - { - byte[] block = Encoding.ASCII.GetBytes("00000000000000000000000000000000"); - byte[] key = Encoding.ASCII.GetBytes("1234567812345678"); - byte[] result = block.AES256Encrypt(key); - string encryptString = result.ToHexString(); - encryptString.Should().Be("f69e0923d8247eef417d6a78944a4b39f69e0923d8247eef417d6a78944a4b39"); - } - - [TestMethod] - public void TestAES256Decrypt() - { - byte[] block = new byte[32]; - byte[] key = Encoding.ASCII.GetBytes("1234567812345678"); - string decryptString = "f69e0923d8247eef417d6a78944a4b39f69e0923d8247eef417d6a78944a4b399ae8fd02b340288a0e7bbff0f0ba54d6"; - for (int i = 0; i < 32; i++) - block[i] = Convert.ToByte(decryptString.Substring(i * 2, 2), 16); - string str = System.Text.Encoding.Default.GetString(block.AES256Decrypt(key)); - str.Should().Be("00000000000000000000000000000000"); - } - - [TestMethod] - public void TestAesEncrypt() - { - byte[] data = Encoding.ASCII.GetBytes("00000000000000000000000000000000"); - byte[] key = Encoding.ASCII.GetBytes("12345678123456781234567812345678"); - byte[] iv = Encoding.ASCII.GetBytes("1234567812345678"); - byte[] result = data.AesEncrypt(key, iv); - - string encryptString = result.ToHexString(); - encryptString.Should().Be("07c748cf7d326782f82e60ebe60e2fac289e84e9ce91c1bc41565d14ecb53640"); - - byte[] nullData = null; - Action action = () => nullData.AesEncrypt(key, iv); - action.Should().Throw(); - - byte[] nullKey = null; - action = () => data.AesEncrypt(nullKey, iv); - action.Should().Throw(); - - byte[] nullIv = null; - action = () => data.AesEncrypt(key, nullIv); - action.Should().Throw(); - - byte[] wrongData = Encoding.ASCII.GetBytes("000000000000000000000000000000001"); ; - action = () => wrongData.AesEncrypt(key, iv); - action.Should().Throw(); - - byte[] wrongKey = Encoding.ASCII.GetBytes("123456781234567812345678123456780"); ; - action = () => data.AesEncrypt(wrongKey, iv); - action.Should().Throw(); - - byte[] wrongIv = Encoding.ASCII.GetBytes("12345678123456780"); ; - action = () => data.AesEncrypt(key, wrongIv); - action.Should().Throw(); - } - - [TestMethod] - public void TestAesDecrypt() - { - byte[] data = new byte[32]; - byte[] key = Encoding.ASCII.GetBytes("12345678123456781234567812345678"); - byte[] iv = Encoding.ASCII.GetBytes("1234567812345678"); - string decryptString = "07c748cf7d326782f82e60ebe60e2fac289e84e9ce91c1bc41565d14ecb5364073f28c9aa7bd6b069e44d8a97beb2b58"; - for (int i = 0; i < 32; i++) - data[i] = Convert.ToByte(decryptString.Substring(i * 2, 2), 16); - string str = System.Text.Encoding.Default.GetString(data.AesDecrypt(key, iv)); - str.Should().Be("00000000000000000000000000000000"); - - byte[] nullData = null; - Action action = () => nullData.AesDecrypt(key, iv); - action.Should().Throw(); - - byte[] nullKey = null; - action = () => data.AesDecrypt(nullKey, iv); - action.Should().Throw(); - - byte[] nullIv = null; - action = () => data.AesDecrypt(key, nullIv); - action.Should().Throw(); - - byte[] wrongData = Encoding.ASCII.GetBytes("00000000000000001"); ; - action = () => wrongData.AesDecrypt(key, iv); - action.Should().Throw(); - - byte[] wrongKey = Encoding.ASCII.GetBytes("123456781234567812345678123456780"); ; - action = () => data.AesDecrypt(wrongKey, iv); - action.Should().Throw(); - - byte[] wrongIv = Encoding.ASCII.GetBytes("12345678123456780"); ; - action = () => data.AesDecrypt(key, wrongIv); - action.Should().Throw(); - } - [TestMethod] public void TestBase58CheckDecode() { diff --git a/tests/neo.UnitTests/Cryptography/UT_MerkleTree.cs b/tests/neo.UnitTests/Cryptography/UT_MerkleTree.cs index 600e43faaf..612a013923 100644 --- a/tests/neo.UnitTests/Cryptography/UT_MerkleTree.cs +++ b/tests/neo.UnitTests/Cryptography/UT_MerkleTree.cs @@ -21,10 +21,6 @@ public UInt256 GetByteArrayHash(byte[] byteArray) [TestMethod] public void TestBuildAndDepthFirstSearch() { - UInt256[] hashNull = new UInt256[0]; - Action action = () => new MerkleTree(hashNull); - action.Should().Throw(); - byte[] array1 = { 0x01 }; var hash1 = GetByteArrayHash(array1); diff --git a/tests/neo.UnitTests/Extensions/NativeContractExtensions.cs b/tests/neo.UnitTests/Extensions/NativeContractExtensions.cs index 9faadd2ee4..bf5f84070c 100644 --- a/tests/neo.UnitTests/Extensions/NativeContractExtensions.cs +++ b/tests/neo.UnitTests/Extensions/NativeContractExtensions.cs @@ -16,7 +16,7 @@ public static ContractState DeployContract(this DataCache snapshot, UInt160 send script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", nefFile, manifest, null); var engine = ApplicationEngine.Create(TriggerType.Application, - sender != null ? new Transaction() { Signers = new Signer[] { new Signer() { Account = sender } } } : null, snapshot, null, gas); + sender != null ? new Transaction() { Signers = new Signer[] { new Signer() { Account = sender } } } : null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: gas); engine.LoadScript(script.ToArray()); if (engine.Execute() != VMState.HALT) @@ -36,7 +36,7 @@ public static void UpdateContract(this DataCache snapshot, UInt160 callingScript var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "update", nefFile, manifest, null); - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); // Fake calling script hash @@ -59,7 +59,7 @@ public static void DestroyContract(this DataCache snapshot, UInt160 callingScrip var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "destroy"); - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); // Fake calling script hash @@ -80,7 +80,7 @@ public static void DestroyContract(this DataCache snapshot, UInt160 callingScrip public static void AddContract(this DataCache snapshot, UInt160 hash, ContractState state) { var key = new KeyBuilder(NativeContract.ContractManagement.Id, 8).Add(hash); - snapshot.Add(key, new StorageItem(state, false)); + snapshot.Add(key, new StorageItem(state)); } public static void DeleteContract(this DataCache snapshot, UInt160 hash) @@ -96,18 +96,9 @@ public static StackItem Call(this NativeContract contract, DataCache snapshot, s public static StackItem Call(this NativeContract contract, DataCache snapshot, IVerifiable container, Block persistingBlock, string method, params ContractParameter[] args) { - var engine = ApplicationEngine.Create(TriggerType.Application, container, snapshot, persistingBlock); - var contractState = NativeContract.ContractManagement.GetContract(snapshot, contract.Hash); - if (contractState == null) throw new InvalidOperationException(); - var md = contract.Manifest.Abi.GetMethod(method, args.Length); - - var script = new ScriptBuilder(); - - for (var i = args.Length - 1; i >= 0; i--) - script.EmitPush(args[i]); - - script.EmitPush(method); - engine.LoadContract(contractState, md, CallFlags.All); + using var engine = ApplicationEngine.Create(TriggerType.Application, container, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(contract.Hash, method, args); engine.LoadScript(script.ToArray()); if (engine.Execute() != VMState.HALT) diff --git a/tests/neo.UnitTests/Extensions/Nep17NativeContractExtensions.cs b/tests/neo.UnitTests/Extensions/Nep17NativeContractExtensions.cs index 4cba1e19e2..1f088849fd 100644 --- a/tests/neo.UnitTests/Extensions/Nep17NativeContractExtensions.cs +++ b/tests/neo.UnitTests/Extensions/Nep17NativeContractExtensions.cs @@ -37,17 +37,11 @@ public void SerializeUnsigned(BinaryWriter writer) { } public static bool Transfer(this NativeContract contract, DataCache snapshot, byte[] from, byte[] to, BigInteger amount, bool signFrom, Block persistingBlock) { - var engine = ApplicationEngine.Create(TriggerType.Application, - new ManualWitness(signFrom ? new UInt160(from) : null), snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.Application, + new ManualWitness(signFrom ? new UInt160(from) : null), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); - engine.LoadScript(contract.Script, configureState: p => p.ScriptHash = contract.Hash); - - var script = new ScriptBuilder(); - script.Emit(OpCode.PUSHNULL); - script.EmitPush(amount); - script.EmitPush(to); - script.EmitPush(from); - script.EmitPush("transfer"); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(contract.Hash, "transfer", from, to, amount, null); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -63,12 +57,10 @@ public static bool Transfer(this NativeContract contract, DataCache snapshot, by public static BigInteger TotalSupply(this NativeContract contract, DataCache snapshot) { - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); - - engine.LoadScript(contract.Script, configureState: p => p.ScriptHash = contract.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); - var script = new ScriptBuilder(); - script.EmitPush("totalSupply"); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(contract.Hash, "totalSupply"); engine.LoadScript(script.ToArray()); engine.Execute().Should().Be(VMState.HALT); @@ -81,13 +73,10 @@ public static BigInteger TotalSupply(this NativeContract contract, DataCache sna public static BigInteger BalanceOf(this NativeContract contract, DataCache snapshot, byte[] account) { - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); - - engine.LoadScript(contract.Script, configureState: p => p.ScriptHash = contract.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); - var script = new ScriptBuilder(); - script.EmitPush(account); - script.EmitPush("balanceOf"); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(contract.Hash, "balanceOf", account); engine.LoadScript(script.ToArray()); engine.Execute().Should().Be(VMState.HALT); @@ -100,12 +89,10 @@ public static BigInteger BalanceOf(this NativeContract contract, DataCache snaps public static BigInteger Decimals(this NativeContract contract, DataCache snapshot) { - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); - engine.LoadScript(contract.Script, configureState: p => p.ScriptHash = contract.Hash); - - var script = new ScriptBuilder(); - script.EmitPush("decimals"); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(contract.Hash, "decimals"); engine.LoadScript(script.ToArray()); engine.Execute().Should().Be(VMState.HALT); @@ -118,12 +105,10 @@ public static BigInteger Decimals(this NativeContract contract, DataCache snapsh public static string Symbol(this NativeContract contract, DataCache snapshot) { - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); - - engine.LoadScript(contract.Script, configureState: p => p.ScriptHash = contract.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); - var script = new ScriptBuilder(); - script.EmitPush("symbol"); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(contract.Hash, "symbol"); engine.LoadScript(script.ToArray()); engine.Execute().Should().Be(VMState.HALT); diff --git a/tests/neo.UnitTests/Ledger/UT_Blockchain.cs b/tests/neo.UnitTests/Ledger/UT_Blockchain.cs index 9df5c96f6f..afcebd2013 100644 --- a/tests/neo.UnitTests/Ledger/UT_Blockchain.cs +++ b/tests/neo.UnitTests/Ledger/UT_Blockchain.cs @@ -1,6 +1,6 @@ +using Akka.TestKit; using Akka.TestKit.Xunit2; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -14,42 +14,18 @@ namespace Neo.UnitTests.Ledger { - internal class TestBlock : Block - { - public override bool Verify(DataCache snapshot) - { - return true; - } - - public static TestBlock Cast(Block input) - { - return input.ToArray().AsSerializable(); - } - } - - internal class TestHeader : Header - { - public override bool Verify(DataCache snapshot) - { - return true; - } - - public static TestHeader Cast(Header input) - { - return input.ToArray().AsSerializable(); - } - } - [TestClass] public class UT_Blockchain : TestKit { private NeoSystem system; private Transaction txSample; + private TestProbe senderProbe; [TestInitialize] public void Initialize() { system = TestBlockchain.TheNeoSystem; + senderProbe = CreateTestProbe(); txSample = new Transaction() { Attributes = Array.Empty(), @@ -57,14 +33,13 @@ public void Initialize() Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, Witnesses = Array.Empty() }; - Blockchain.Singleton.MemPool.TryAdd(txSample, Blockchain.Singleton.GetSnapshot()); + system.MemPool.TryAdd(txSample, TestBlockchain.GetTestSnapshot()); } [TestMethod] public void TestValidTransaction() { - var senderProbe = CreateTestProbe(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.TheNeoSystem.GetSnapshot(); var walletA = TestUtils.GenerateTestWallet(); using var unlockA = walletA.Unlock("123"); @@ -79,7 +54,7 @@ public void TestValidTransaction() // Make transaction - var tx = CreateValidTx(walletA, acc.ScriptHash, 0); + var tx = CreateValidTx(snapshot, walletA, acc.ScriptHash, 0); senderProbe.Send(system.Blockchain, tx); senderProbe.ExpectMsg(p => p.Result == VerifyResult.Succeed); @@ -100,9 +75,9 @@ internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) return storageKey; } - private Transaction CreateValidTx(NEP6Wallet wallet, UInt160 account, uint nonce) + private static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, UInt160 account, uint nonce) { - var tx = wallet.MakeTransaction(new TransferOutput[] + var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -115,9 +90,11 @@ private Transaction CreateValidTx(NEP6Wallet wallet, UInt160 account, uint nonce tx.Nonce = nonce; - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); + Assert.IsNull(data.GetSignatures(tx.Sender)); Assert.IsTrue(wallet.Sign(data)); Assert.IsTrue(data.Completed); + Assert.AreEqual(1, data.GetSignatures(tx.Sender).Count()); tx.Witnesses = data.GetWitnesses(); return tx; diff --git a/tests/neo.UnitTests/Ledger/UT_MemoryPool.cs b/tests/neo.UnitTests/Ledger/UT_MemoryPool.cs index a610c70ccf..b600db7afb 100644 --- a/tests/neo.UnitTests/Ledger/UT_MemoryPool.cs +++ b/tests/neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -1,3 +1,4 @@ +using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -20,12 +21,12 @@ namespace Neo.UnitTests.Ledger internal class TestIMemoryPoolTxObserverPlugin : Plugin, IMemoryPoolTxObserverPlugin { protected override void Configure() { } - public void TransactionAdded(Transaction tx) { } - public void TransactionsRemoved(MemoryPoolTxRemovalReason reason, IEnumerable transactions) { } + public void TransactionAdded(NeoSystem system, Transaction tx) { } + public void TransactionsRemoved(NeoSystem system, MemoryPoolTxRemovalReason reason, IEnumerable transactions) { } } [TestClass] - public class UT_MemoryPool + public class UT_MemoryPool : TestKit { private static NeoSystem testBlockchain; @@ -42,16 +43,19 @@ public static void TestSetup(TestContext ctx) testBlockchain = TestBlockchain.TheNeoSystem; } + private DataCache GetSnapshot() + { + return testBlockchain.StoreView.CreateSnapshot(); + } + [TestInitialize] public void TestSetup() { // protect against external changes on TimeProvider TimeProvider.ResetToDefault(); - TestBlockchain.InitializeMockNeoSystem(); - // Create a MemoryPool with capacity of 100 - _unit = new MemoryPool(TestBlockchain.TheNeoSystem, 100); + _unit = new MemoryPool(new NeoSystem(ProtocolSettings.Default with { MemoryPoolMaxTransactions = 100 })); // Verify capacity equals the amount specified _unit.Capacity.Should().Be(100); @@ -59,7 +63,7 @@ public void TestSetup() _unit.VerifiedCount.Should().Be(0); _unit.UnVerifiedCount.Should().Be(0); _unit.Count.Should().Be(0); - _unit2 = new MemoryPool(TestBlockchain.TheNeoSystem, 0); + _unit2 = new MemoryPool(new NeoSystem(ProtocolSettings.Default with { MemoryPoolMaxTransactions = 0 })); plugin = new TestIMemoryPoolTxObserverPlugin(); } @@ -82,9 +86,8 @@ private Transaction CreateTransactionWithFee(long fee) var randomBytes = new byte[16]; random.NextBytes(randomBytes); Mock mock = new Mock(); - mock.Setup(p => p.Verify(It.IsAny(), It.IsAny())).Returns(VerifyResult.Succeed); - mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny())).Returns(VerifyResult.Succeed); - mock.Setup(p => p.VerifyStateIndependent()).Returns(VerifyResult.Succeed); + mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny(), It.IsAny())).Returns(VerifyResult.Succeed); + mock.Setup(p => p.VerifyStateIndependent(It.IsAny())).Returns(VerifyResult.Succeed); mock.Object.Script = randomBytes; mock.Object.NetworkFee = fee; mock.Object.Attributes = Array.Empty(); @@ -93,8 +96,8 @@ private Transaction CreateTransactionWithFee(long fee) { new Witness { - InvocationScript = new byte[0], - VerificationScript = new byte[0] + InvocationScript = Array.Empty(), + VerificationScript = Array.Empty() } }; return mock.Object; @@ -107,9 +110,8 @@ private Transaction CreateTransactionWithFeeAndBalanceVerify(long fee) random.NextBytes(randomBytes); Mock mock = new Mock(); UInt160 sender = senderAccount; - mock.Setup(p => p.Verify(It.IsAny(), It.IsAny())).Returns(VerifyResult.Succeed); - mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny())).Returns((DataCache snapshot, TransactionVerificationContext context) => context.CheckTransaction(mock.Object, snapshot) ? VerifyResult.Succeed : VerifyResult.InsufficientFunds); - mock.Setup(p => p.VerifyStateIndependent()).Returns(VerifyResult.Succeed); + mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny(), It.IsAny())).Returns((ProtocolSettings settings, DataCache snapshot, TransactionVerificationContext context) => context.CheckTransaction(mock.Object, snapshot) ? VerifyResult.Succeed : VerifyResult.InsufficientFunds); + mock.Setup(p => p.VerifyStateIndependent(It.IsAny())).Returns(VerifyResult.Succeed); mock.Object.Script = randomBytes; mock.Object.NetworkFee = fee; mock.Object.Attributes = Array.Empty(); @@ -134,7 +136,7 @@ private Transaction CreateTransaction(long fee = -1) private void AddTransactions(int count) { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = GetSnapshot(); for (int i = 0; i < count; i++) { var txToAdd = CreateTransaction(); @@ -146,7 +148,7 @@ private void AddTransactions(int count) private void AddTransaction(Transaction txToAdd) { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = GetSnapshot(); _unit.TryAdd(txToAdd, snapshot); } @@ -184,35 +186,36 @@ public void BlockPersistMovesTxToUnverifiedAndReverification() var block = new Block { + Header = new Header(), Transactions = _unit.GetSortedVerifiedTransactions().Take(10) .Concat(_unit.GetSortedVerifiedTransactions().Take(5)).ToArray() }; - _unit.UpdatePoolForBlockPersisted(block, Blockchain.Singleton.GetSnapshot()); + _unit.UpdatePoolForBlockPersisted(block, GetSnapshot()); _unit.InvalidateVerifiedTransactions(); _unit.SortedTxCount.Should().Be(0); _unit.UnverifiedSortedTxCount.Should().Be(60); - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); + _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, GetSnapshot()); _unit.SortedTxCount.Should().Be(10); _unit.UnverifiedSortedTxCount.Should().Be(50); - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); + _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, GetSnapshot()); _unit.SortedTxCount.Should().Be(20); _unit.UnverifiedSortedTxCount.Should().Be(40); - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); + _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, GetSnapshot()); _unit.SortedTxCount.Should().Be(30); _unit.UnverifiedSortedTxCount.Should().Be(30); - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); + _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, GetSnapshot()); _unit.SortedTxCount.Should().Be(40); _unit.UnverifiedSortedTxCount.Should().Be(20); - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); + _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, GetSnapshot()); _unit.SortedTxCount.Should().Be(50); _unit.UnverifiedSortedTxCount.Should().Be(10); - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, Blockchain.Singleton.GetSnapshot()); + _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(10, GetSnapshot()); _unit.SortedTxCount.Should().Be(60); _unit.UnverifiedSortedTxCount.Should().Be(0); } @@ -220,11 +223,11 @@ public void BlockPersistMovesTxToUnverifiedAndReverification() [TestMethod] public void BlockPersistAndReverificationWillAbandonTxAsBalanceTransfered() { - using SnapshotCache snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = GetSnapshot(); BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, senderAccount); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, long.MaxValue); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); NativeContract.GAS.Burn(engine, UInt160.Zero, balance); - NativeContract.GAS.Mint(engine, UInt160.Zero, 70, true); + _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 70, true); long txFee = 1; AddTransactionsWithBalanceVerify(70, txFee, snapshot); @@ -233,15 +236,16 @@ public void BlockPersistAndReverificationWillAbandonTxAsBalanceTransfered() var block = new Block { + Header = new Header(), Transactions = _unit.GetSortedVerifiedTransactions().Take(10).ToArray() }; // Simulate the transfer process in tx by burning the balance UInt160 sender = block.Transactions[0].Sender; - ApplicationEngine applicationEngine = ApplicationEngine.Create(TriggerType.All, block, snapshot, block, (long)balance); + ApplicationEngine applicationEngine = ApplicationEngine.Create(TriggerType.All, block, snapshot, block, settings: TestBlockchain.TheNeoSystem.Settings, gas: (long)balance); NativeContract.GAS.Burn(applicationEngine, sender, NativeContract.GAS.BalanceOf(snapshot, sender)); - NativeContract.GAS.Mint(applicationEngine, sender, txFee * 30, true); // Set the balance to meet 30 txs only + _ = NativeContract.GAS.Mint(applicationEngine, sender, txFee * 30, true); // Set the balance to meet 30 txs only // Persist block and reverify all the txs in mempool, but half of the txs will be discarded _unit.UpdatePoolForBlockPersisted(block, snapshot); @@ -250,7 +254,7 @@ public void BlockPersistAndReverificationWillAbandonTxAsBalanceTransfered() // Revert the balance NativeContract.GAS.Burn(applicationEngine, sender, txFee * 30); - NativeContract.GAS.Mint(applicationEngine, sender, balance, true); + _ = NativeContract.GAS.Mint(applicationEngine, sender, balance, true); } private void VerifyTransactionsSortedDescending(IEnumerable transactions) @@ -287,8 +291,12 @@ public void VerifySortOrderAndThatHighetFeeTransactionsAreReverifiedFirst() VerifyTransactionsSortedDescending(sortedVerifiedTxs); // move all to unverified - var block = new Block { Transactions = new Transaction[0] }; - _unit.UpdatePoolForBlockPersisted(block, Blockchain.Singleton.GetSnapshot()); + var block = new Block + { + Header = new Header(), + Transactions = Array.Empty() + }; + _unit.UpdatePoolForBlockPersisted(block, GetSnapshot()); _unit.InvalidateVerifiedTransactions(); _unit.SortedTxCount.Should().Be(0); _unit.UnverifiedSortedTxCount.Should().Be(100); @@ -304,13 +312,17 @@ public void VerifySortOrderAndThatHighetFeeTransactionsAreReverifiedFirst() var minTransaction = sortedUnverifiedArray.Last(); // reverify 1 high priority and 1 low priority transaction - _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(1, Blockchain.Singleton.GetSnapshot()); + _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(1, GetSnapshot()); var verifiedTxs = _unit.GetSortedVerifiedTransactions().ToArray(); verifiedTxs.Length.Should().Be(1); verifiedTxs[0].Should().BeEquivalentTo(maxTransaction); - var blockWith2Tx = new Block { Transactions = new[] { maxTransaction, minTransaction } }; + var blockWith2Tx = new Block + { + Header = new Header(), + Transactions = new[] { maxTransaction, minTransaction } + }; // verify and remove the 2 transactions from the verified pool - _unit.UpdatePoolForBlockPersisted(blockWith2Tx, Blockchain.Singleton.GetSnapshot()); + _unit.UpdatePoolForBlockPersisted(blockWith2Tx, GetSnapshot()); _unit.InvalidateVerifiedTransactions(); _unit.SortedTxCount.Should().Be(0); } @@ -348,8 +360,12 @@ public void CapacityTestWithUnverifiedHighProirtyTransactions() AddTransactions(99); // move all to unverified - var block = new Block { Transactions = new Transaction[0] }; - _unit.UpdatePoolForBlockPersisted(block, Blockchain.Singleton.GetSnapshot()); + var block = new Block + { + Header = new Header(), + Transactions = Array.Empty() + }; + _unit.UpdatePoolForBlockPersisted(block, GetSnapshot()); _unit.CanTransactionFitInPool(CreateTransaction()).Should().Be(true); AddTransactions(1); @@ -371,7 +387,7 @@ public void TestInvalidateAll() [TestMethod] public void TestContainsKey() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = GetSnapshot(); AddTransactions(10); var txToAdd = CreateTransaction(); @@ -411,7 +427,7 @@ public void TestIEnumerableGetEnumerator() [TestMethod] public void TestGetVerifiedTransactions() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = GetSnapshot(); var tx1 = CreateTransaction(); var tx2 = CreateTransaction(); _unit.TryAdd(tx1, snapshot); @@ -427,7 +443,8 @@ public void TestGetVerifiedTransactions() [TestMethod] public void TestReVerifyTopUnverifiedTransactionsIfNeeded() { - _unit = new MemoryPool(TestBlockchain.TheNeoSystem, 600); + _unit = new MemoryPool(new NeoSystem(ProtocolSettings.Default with { MemoryPoolMaxTransactions = 600 })); + AddTransaction(CreateTransaction(100000001)); AddTransaction(CreateTransaction(100000001)); AddTransaction(CreateTransaction(100000001)); @@ -443,17 +460,17 @@ public void TestReVerifyTopUnverifiedTransactionsIfNeeded() _unit.VerifiedCount.Should().Be(511); _unit.UnVerifiedCount.Should().Be(4); - var result = _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(1, Blockchain.Singleton.GetSnapshot()); + var result = _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(1, GetSnapshot()); result.Should().BeTrue(); _unit.VerifiedCount.Should().Be(512); _unit.UnVerifiedCount.Should().Be(3); - result = _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(2, Blockchain.Singleton.GetSnapshot()); + result = _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(2, GetSnapshot()); result.Should().BeTrue(); _unit.VerifiedCount.Should().Be(514); _unit.UnVerifiedCount.Should().Be(1); - result = _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(3, Blockchain.Singleton.GetSnapshot()); + result = _unit.ReVerifyTopUnverifiedTransactionsIfNeeded(3, GetSnapshot()); result.Should().BeFalse(); _unit.VerifiedCount.Should().Be(515); _unit.UnVerifiedCount.Should().Be(0); @@ -462,7 +479,7 @@ public void TestReVerifyTopUnverifiedTransactionsIfNeeded() [TestMethod] public void TestTryAdd() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = GetSnapshot(); var tx1 = CreateTransaction(); _unit.TryAdd(tx1, snapshot).Should().Be(VerifyResult.Succeed); _unit.TryAdd(tx1, snapshot).Should().NotBe(VerifyResult.Succeed); @@ -472,7 +489,7 @@ public void TestTryAdd() [TestMethod] public void TestTryGetValue() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = GetSnapshot(); var tx1 = CreateTransaction(); _unit.TryAdd(tx1, snapshot); _unit.TryGetValue(tx1.Hash, out Transaction tx).Should().BeTrue(); @@ -489,7 +506,7 @@ public void TestTryGetValue() [TestMethod] public void TestUpdatePoolForBlockPersisted() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = GetSnapshot(); byte[] transactionsPerBlock = { 0x18, 0x00, 0x00, 0x00 }; // 24 byte[] feePerByte = { 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }; // 1048576 StorageItem item1 = new StorageItem @@ -512,7 +529,11 @@ public void TestUpdatePoolForBlockPersisted() Transaction[] transactions = { tx1, tx2 }; _unit.TryAdd(tx1, snapshot); - var block = new Block { Transactions = transactions }; + var block = new Block + { + Header = new Header(), + Transactions = transactions + }; _unit.UnVerifiedCount.Should().Be(0); _unit.VerifiedCount.Should().Be(1); diff --git a/tests/neo.UnitTests/Ledger/UT_StorageItem.cs b/tests/neo.UnitTests/Ledger/UT_StorageItem.cs index f9ced186ec..e750c18782 100644 --- a/tests/neo.UnitTests/Ledger/UT_StorageItem.cs +++ b/tests/neo.UnitTests/Ledger/UT_StorageItem.cs @@ -37,14 +37,14 @@ public void Value_Set() public void Size_Get() { uut.Value = TestUtils.GetByteArray(10, 0x42); - uut.Size.Should().Be(12); // 2 + 10 + uut.Size.Should().Be(11); // 1 + 10 } [TestMethod] public void Size_Get_Larger() { uut.Value = TestUtils.GetByteArray(88, 0x42); - uut.Size.Should().Be(90); // 2 + 88 + uut.Size.Should().Be(89); // 1 + 88 } [TestMethod] @@ -64,7 +64,7 @@ public void Clone() [TestMethod] public void Deserialize() { - byte[] data = new byte[] { 10, 66, 32, 32, 32, 32, 32, 32, 32, 32, 32, 0 }; + byte[] data = new byte[] { 66, 32, 32, 32, 32, 32, 32, 32, 32, 32 }; int index = 0; using (MemoryStream ms = new MemoryStream(data, index, data.Length - index, false)) { @@ -96,7 +96,7 @@ public void Serialize() } } - byte[] requiredData = new byte[] { 10, 66, 32, 32, 32, 32, 32, 32, 32, 32, 32, 0 }; + byte[] requiredData = new byte[] { 66, 32, 32, 32, 32, 32, 32, 32, 32, 32 }; data.Length.Should().Be(requiredData.Length); for (int i = 0; i < requiredData.Length; i++) @@ -109,11 +109,9 @@ public void Serialize() public void TestFromReplica() { uut.Value = TestUtils.GetByteArray(10, 0x42); - uut.IsConstant = true; StorageItem dest = new StorageItem(); dest.FromReplica(uut); dest.Value.Should().BeEquivalentTo(uut.Value); - dest.IsConstant.Should().Be(uut.IsConstant); } } } diff --git a/tests/neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs b/tests/neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs index 291d9b7d1a..b0a150a154 100644 --- a/tests/neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs +++ b/tests/neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs @@ -28,9 +28,8 @@ private Transaction CreateTransactionWithFee(long networkFee, long systemFee) var randomBytes = new byte[16]; random.NextBytes(randomBytes); Mock mock = new Mock(); - mock.Setup(p => p.Verify(It.IsAny(), It.IsAny())).Returns(VerifyResult.Succeed); - mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny())).Returns(VerifyResult.Succeed); - mock.Setup(p => p.VerifyStateIndependent()).Returns(VerifyResult.Succeed); + mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny(), It.IsAny())).Returns(VerifyResult.Succeed); + mock.Setup(p => p.VerifyStateIndependent(It.IsAny())).Returns(VerifyResult.Succeed); mock.Object.Script = randomBytes; mock.Object.NetworkFee = networkFee; mock.Object.SystemFee = systemFee; @@ -51,12 +50,12 @@ private Transaction CreateTransactionWithFee(long networkFee, long systemFee) public void TestDuplicateOracle() { // Fake balance - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, long.MaxValue); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, UInt160.Zero); NativeContract.GAS.Burn(engine, UInt160.Zero, balance); - NativeContract.GAS.Mint(engine, UInt160.Zero, 8, false); + _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 8, false); // Test TransactionVerificationContext verificationContext = new TransactionVerificationContext(); @@ -73,11 +72,11 @@ public void TestDuplicateOracle() [TestMethod] public void TestTransactionSenderFee() { - var snapshot = Blockchain.Singleton.GetSnapshot(); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, long.MaxValue); + var snapshot = TestBlockchain.GetTestSnapshot(); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, UInt160.Zero); NativeContract.GAS.Burn(engine, UInt160.Zero, balance); - NativeContract.GAS.Mint(engine, UInt160.Zero, 8, true); + _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 8, true); TransactionVerificationContext verificationContext = new TransactionVerificationContext(); var tx = CreateTransactionWithFee(1, 2); diff --git a/tests/neo.UnitTests/Ledger/UT_TrimmedBlock.cs b/tests/neo.UnitTests/Ledger/UT_TrimmedBlock.cs index c27413f1d3..3bef6f9bb5 100644 --- a/tests/neo.UnitTests/Ledger/UT_TrimmedBlock.cs +++ b/tests/neo.UnitTests/Ledger/UT_TrimmedBlock.cs @@ -1,6 +1,5 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; using Neo.UnitTests.SmartContract; @@ -17,31 +16,27 @@ public static TrimmedBlock GetTrimmedBlockWithNoTransaction() { return new TrimmedBlock { - ConsensusData = new ConsensusData(), - MerkleRoot = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02"), - PrevHash = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"), - Timestamp = new DateTime(1988, 06, 01, 0, 0, 0, DateTimeKind.Utc).ToTimestamp(), - Index = 1, - NextConsensus = UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01"), - Witness = new Witness + Header = new Header { - InvocationScript = Array.Empty(), - VerificationScript = new[] { (byte)OpCode.PUSH1 } + MerkleRoot = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02"), + PrevHash = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"), + Timestamp = new DateTime(1988, 06, 01, 0, 0, 0, DateTimeKind.Utc).ToTimestamp(), + Index = 1, + NextConsensus = UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01"), + Witness = new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = new[] { (byte)OpCode.PUSH1 } + }, }, Hashes = Array.Empty() }; } - [TestInitialize] - public void Init() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void TestGetBlock() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var tx1 = TestUtils.GetTransaction(UInt160.Zero); tx1.Script = new byte[] { 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, @@ -72,10 +67,10 @@ public void TestGetBlock() block.Index.Should().Be(1); block.MerkleRoot.Should().Be(UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02")); - block.Transactions.Length.Should().Be(1); - block.Transactions[0].Hash.Should().Be(tx2.Hash); - block.Witness.InvocationScript.ToHexString().Should().Be(tblock.Witness.InvocationScript.ToHexString()); - block.Witness.VerificationScript.ToHexString().Should().Be(tblock.Witness.VerificationScript.ToHexString()); + block.Transactions.Length.Should().Be(2); + block.Transactions[0].Hash.Should().Be(tx1.Hash); + block.Witness.InvocationScript.ToHexString().Should().Be(tblock.Header.Witness.InvocationScript.ToHexString()); + block.Witness.VerificationScript.ToHexString().Should().Be(tblock.Header.Witness.VerificationScript.ToHexString()); } [TestMethod] @@ -92,7 +87,7 @@ public void TestGetSize() { TrimmedBlock tblock = GetTrimmedBlockWithNoTransaction(); tblock.Hashes = new UInt256[] { TestUtils.GetTransaction(UInt160.Zero).Hash }; - tblock.Size.Should().Be(146); + tblock.Size.Should().Be(138); } [TestMethod] @@ -109,12 +104,8 @@ public void TestDeserialize() ms.Seek(0, SeekOrigin.Begin); newBlock.Deserialize(reader); } - tblock.MerkleRoot.Should().Be(newBlock.MerkleRoot); - tblock.PrevHash.Should().Be(newBlock.PrevHash); - tblock.Timestamp.Should().Be(newBlock.Timestamp); tblock.Hashes.Length.Should().Be(newBlock.Hashes.Length); - tblock.Witness.ScriptHash.Should().Be(newBlock.Witness.ScriptHash); - tblock.ToJson().ToString().Should().Be(newBlock.ToJson().ToString()); + tblock.Header.ToJson(ProtocolSettings.Default).ToString().Should().Be(newBlock.Header.ToJson(ProtocolSettings.Default).ToString()); } } } diff --git a/tests/neo.UnitTests/Network/P2P/Payloads/UT_Block.cs b/tests/neo.UnitTests/Network/P2P/Payloads/UT_Block.cs index a1ea5635c1..c6d457859b 100644 --- a/tests/neo.UnitTests/Network/P2P/Payloads/UT_Block.cs +++ b/tests/neo.UnitTests/Network/P2P/Payloads/UT_Block.cs @@ -43,9 +43,9 @@ public void Size_Get() { UInt256 val256 = UInt256.Zero; TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, 0); - // blockbase 4 + 64 + 1 + 32 + 4 + 4 + 20 + 4 - // block 9 + 1 - uut.Size.Should().Be(114); + // header 4 + 32 + 32 + 8 + 4 + 1 + 20 + 4 + // tx 1 + uut.Size.Should().Be(106); } [TestMethod] @@ -59,7 +59,7 @@ public void Size_Get_1_Transaction() TestUtils.GetTransaction(UInt160.Zero) }; - uut.Size.Should().Be(167); + uut.Size.Should().Be(159); } [TestMethod] @@ -75,7 +75,7 @@ public void Size_Get_3_Transaction() TestUtils.GetTransaction(UInt160.Zero) }; - uut.Size.Should().Be(273); + uut.Size.Should().Be(265); } [TestMethod] @@ -84,7 +84,7 @@ public void Serialize() UInt256 val256 = UInt256.Zero; TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, 1); - var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007ee5991fa69cf4d7902430f5fad89ba13b253b5680cb13167f80bfc3593947e7e913ff854c00000000000000000000000000000000000000000000000000000001000111020000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000112010000"; + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000006c23be5d32679baa9c5c2aa0d329fd2a2441d7875d0f34d42f58f70428fbbbb9e913ff854c0000000000000000000000000000000000000000000000000000000001000111010000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000112010000"; uut.ToArray().ToHexString().Should().Be(hex); } @@ -94,7 +94,7 @@ public void Deserialize() UInt256 val256 = UInt256.Zero; TestUtils.SetupBlockWithValues(new Block(), val256, out var merkRoot, out var val160, out var timestampVal, out var indexVal, out var scriptVal, out var transactionsVal, 1); - var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007ee5991fa69cf4d7902430f5fad89ba13b253b5680cb13167f80bfc3593947e7e913ff854c00000000000000000000000000000000000000000000000000000001000111020000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000112010000"; + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000006c23be5d32679baa9c5c2aa0d329fd2a2441d7875d0f34d42f58f70428fbbbb9e913ff854c0000000000000000000000000000000000000000000000000000000001000111010000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000112010000"; using (MemoryStream ms = new MemoryStream(hex.HexToBytes(), false)) using (BinaryReader reader = new BinaryReader(ms)) @@ -158,31 +158,19 @@ public void Equals_SameHash() uut.Equals(newBlock).Should().BeTrue(); } - [TestMethod] - public void RebuildMerkleRoot_Updates() - { - UInt256 val256 = UInt256.Zero; - TestUtils.SetupBlockWithValues(uut, val256, out UInt256 merkRoot, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, 1); - UInt256 merkleRoot = uut.MerkleRoot; - - TestUtils.SetupBlockWithValues(uut, val256, out merkRoot, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 3); - uut.RebuildMerkleRoot(); - uut.MerkleRoot.Should().NotBe(merkleRoot); - } - [TestMethod] public void ToJson() { UInt256 val256 = UInt256.Zero; TestUtils.SetupBlockWithValues(uut, val256, out var merkRoot, out var val160, out var timestampVal, out var indexVal, out var scriptVal, out var transactionsVal, 1); - JObject jObj = uut.ToJson(); + JObject jObj = uut.ToJson(ProtocolSettings.Default); jObj.Should().NotBeNull(); - jObj["hash"].AsString().Should().Be("0xaf156a193ba2d7f05a37dd4e6fdfd1167cb6db3df5474df43a71a645a449fbc8"); - jObj["size"].AsNumber().Should().Be(167); + jObj["hash"].AsString().Should().Be("0x54b3a829333d9bb352eef69942317fe011251370c2212ad0d20d7c2a3974b26e"); + jObj["size"].AsNumber().Should().Be(159); jObj["version"].AsNumber().Should().Be(0); jObj["previousblockhash"].AsString().Should().Be("0x0000000000000000000000000000000000000000000000000000000000000000"); - jObj["merkleroot"].AsString().Should().Be("0xe7473959c3bf807f1613cb80563b253ba19bd8faf5302490d7f49ca61f99e57e"); + jObj["merkleroot"].AsString().Should().Be("0xb9bbfb2804f7582fd4340f5d87d741242afd29d3a02a5c9caa9b67325dbe236c"); jObj["time"].AsNumber().Should().Be(328665601001); jObj["index"].AsNumber().Should().Be(0); jObj["nextconsensus"].AsString().Should().Be("NKuyBkoGdZZSLyPbJEetheRhMjeznFZszf"); @@ -193,7 +181,7 @@ public void ToJson() jObj["tx"].Should().NotBeNull(); JArray txObj = (JArray)jObj["tx"]; - txObj[0]["hash"].AsString().Should().Be("0x0ee830a3b3e93679e23a9ee7e2a55287d79b6d67d056b03aa7ca917bd3d2923c"); + txObj[0]["hash"].AsString().Should().Be("0xb9bbfb2804f7582fd4340f5d87d741242afd29d3a02a5c9caa9b67325dbe236c"); txObj[0]["size"].AsNumber().Should().Be(53); txObj[0]["version"].AsNumber().Should().Be(0); ((JArray)txObj[0]["attributes"]).Count.Should().Be(0); diff --git a/tests/neo.UnitTests/Network/P2P/Payloads/UT_Header.cs b/tests/neo.UnitTests/Network/P2P/Payloads/UT_Header.cs index fcc9c3e1ee..c9d83d73e5 100644 --- a/tests/neo.UnitTests/Network/P2P/Payloads/UT_Header.cs +++ b/tests/neo.UnitTests/Network/P2P/Payloads/UT_Header.cs @@ -1,10 +1,10 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; using Neo.UnitTests.SmartContract; +using System; using System.IO; namespace Neo.UnitTests.Network.P2P.Payloads @@ -17,7 +17,6 @@ public class UT_Header [TestInitialize] public void TestSetup() { - TestBlockchain.InitializeMockNeoSystem(); uut = new Header(); } @@ -43,30 +42,33 @@ public void GetHashCodeTest() public void TrimTest() { UInt256 val256 = UInt256.Zero; - var snapshot = Blockchain.Singleton.GetSnapshot().CreateSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot().CreateSnapshot(); TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _); uut.Witness = new Witness() { InvocationScript = new byte[0], VerificationScript = new byte[0] }; UT_SmartContractHelper.BlocksAdd(snapshot, uut.Hash, new TrimmedBlock() { - Timestamp = uut.Timestamp, - PrevHash = uut.PrevHash, - MerkleRoot = uut.MerkleRoot, - ConsensusData = new ConsensusData(), - Hashes = new UInt256[0], - NextConsensus = uut.NextConsensus, - Witness = uut.Witness + Header = new Header + { + Timestamp = uut.Timestamp, + PrevHash = uut.PrevHash, + MerkleRoot = uut.MerkleRoot, + NextConsensus = uut.NextConsensus, + Witness = uut.Witness + }, + Hashes = Array.Empty() }); var trim = NativeContract.Ledger.GetTrimmedBlock(snapshot, uut.Hash); - - trim.Version.Should().Be(uut.Version); - trim.PrevHash.Should().Be(uut.PrevHash); - trim.MerkleRoot.Should().Be(uut.MerkleRoot); - trim.Timestamp.Should().Be(uut.Timestamp); - trim.Index.Should().Be(uut.Index); - trim.NextConsensus.Should().Be(uut.NextConsensus); - trim.Witness.Should().BeEquivalentTo(uut.Witness); + var header = trim.Header; + + header.Version.Should().Be(uut.Version); + header.PrevHash.Should().Be(uut.PrevHash); + header.MerkleRoot.Should().Be(uut.MerkleRoot); + header.Timestamp.Should().Be(uut.Timestamp); + header.Index.Should().Be(uut.Index); + header.NextConsensus.Should().Be(uut.NextConsensus); + header.Witness.Should().BeEquivalentTo(uut.Witness); trim.Hashes.Length.Should().Be(0); } @@ -78,7 +80,7 @@ public void Deserialize() uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid - var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007227ba7b747f1a98f68679d4a98b68927646ab195a6f56b542ca5a0e6a412662e913ff854c0000000000000000000000000000000000000000000000000000000100011100"; + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007227ba7b747f1a98f68679d4a98b68927646ab195a6f56b542ca5a0e6a412662e913ff854c0000000000000000000000000000000000000000000000000000000001000111"; using (MemoryStream ms = new MemoryStream(hex.HexToBytes(), false)) { @@ -137,7 +139,7 @@ public void Serialize() UInt256 val256 = UInt256.Zero; TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _); - var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007227ba7b747f1a98f68679d4a98b68927646ab195a6f56b542ca5a0e6a412662e913ff854c0000000000000000000000000000000000000000000000000000000100011100"; + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007227ba7b747f1a98f68679d4a98b68927646ab195a6f56b542ca5a0e6a412662e913ff854c0000000000000000000000000000000000000000000000000000000001000111"; uut.ToArray().ToHexString().Should().Be(hex); } } diff --git a/tests/neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs b/tests/neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs index 645e599cce..8b9bb42ca7 100644 --- a/tests/neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs +++ b/tests/neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs @@ -1,7 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; using System; @@ -12,12 +11,6 @@ namespace Neo.UnitTests.Network.P2P.Payloads [TestClass] public class UT_HighPriorityAttribute { - [TestInitialize] - public void Init() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void Size_Get() { @@ -70,7 +63,7 @@ public void DeserializeAndSerialize() public void Verify() { var test = new HighPriorityAttribute(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); Assert.IsFalse(test.Verify(snapshot, new Transaction() { Signers = new Signer[] { } })); Assert.IsFalse(test.Verify(snapshot, new Transaction() { Signers = new Signer[] { new Signer() { Account = UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01") } } })); diff --git a/tests/neo.UnitTests/Network/P2P/Payloads/UT_MerkleBlockPayload.cs b/tests/neo.UnitTests/Network/P2P/Payloads/UT_MerkleBlockPayload.cs index 146a272f04..ff10d1ac86 100644 --- a/tests/neo.UnitTests/Network/P2P/Payloads/UT_MerkleBlockPayload.cs +++ b/tests/neo.UnitTests/Network/P2P/Payloads/UT_MerkleBlockPayload.cs @@ -1,7 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using System.Collections; @@ -10,30 +9,24 @@ namespace Neo.UnitTests.Network.P2P.Payloads [TestClass] public class UT_MerkleBlockPayload { - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void Size_Get() { - var test = MerkleBlockPayload.Create(Blockchain.GenesisBlock, new BitArray(1024, false)); - test.Size.Should().Be(270); + var test = MerkleBlockPayload.Create(TestBlockchain.TheNeoSystem.GenesisBlock, new BitArray(1024, false)); + test.Size.Should().Be(239); - test = MerkleBlockPayload.Create(Blockchain.GenesisBlock, new BitArray(0, false)); - test.Size.Should().Be(142); + test = MerkleBlockPayload.Create(TestBlockchain.TheNeoSystem.GenesisBlock, new BitArray(0, false)); + test.Size.Should().Be(111); } [TestMethod] public void DeserializeAndSerialize() { - var test = MerkleBlockPayload.Create(Blockchain.GenesisBlock, new BitArray(2, false)); + var test = MerkleBlockPayload.Create(TestBlockchain.TheNeoSystem.GenesisBlock, new BitArray(2, false)); var clone = test.ToArray().AsSerializable(); - Assert.AreEqual(test.ContentCount, clone.ContentCount); - Assert.AreEqual(test.Hashes.Length, clone.ContentCount); + Assert.AreEqual(test.TxCount, clone.TxCount); + Assert.AreEqual(test.Hashes.Length, clone.TxCount); CollectionAssert.AreEqual(test.Hashes, clone.Hashes); CollectionAssert.AreEqual(test.Flags, clone.Flags); } diff --git a/tests/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs b/tests/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs index d9e4cab9b8..a8821acdd3 100644 --- a/tests/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs +++ b/tests/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs @@ -23,7 +23,6 @@ public class UT_Transaction [TestInitialize] public void TestSetup() { - TestBlockchain.InitializeMockNeoSystem(); uut = new Transaction(); } @@ -104,7 +103,7 @@ public void FeeIsMultiSigContract() { var walletA = TestUtils.GenerateTestWallet(); var walletB = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); using (var unlockA = walletA.Unlock("123")) using (var unlockB = walletB.Unlock("123")) @@ -133,7 +132,7 @@ public void FeeIsMultiSigContract() // Make transaction - var tx = walletA.MakeTransaction(new TransferOutput[] + var tx = walletA.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -147,7 +146,7 @@ public void FeeIsMultiSigContract() // Sign - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); Assert.IsTrue(walletA.Sign(data)); Assert.IsTrue(walletB.Sign(data)); Assert.IsTrue(data.Completed); @@ -156,14 +155,14 @@ public void FeeIsMultiSigContract() // Fast check - Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, null, tx.NetworkFee)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee)) { engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); @@ -175,9 +174,9 @@ public void FeeIsMultiSigContract() } var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); - Assert.AreEqual(1967130, verificationGas); - Assert.AreEqual(349000, sizeGas); - Assert.AreEqual(2316130, tx.NetworkFee); + Assert.AreEqual(1967100, verificationGas); + Assert.AreEqual(348000, sizeGas); + Assert.AreEqual(2315100, tx.NetworkFee); } } @@ -185,7 +184,7 @@ public void FeeIsMultiSigContract() public void FeeIsSignatureContractDetailed() { var wallet = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); using (var unlock = wallet.Unlock("123")) { @@ -204,7 +203,7 @@ public void FeeIsSignatureContractDetailed() // Make transaction // self-transfer of 1e-8 GAS - var tx = wallet.MakeTransaction(new TransferOutput[] + var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -218,13 +217,13 @@ public void FeeIsSignatureContractDetailed() Assert.IsNull(tx.Witnesses); // check pre-computed network fee (already guessing signature sizes) - tx.NetworkFee.Should().Be(1229550L); + tx.NetworkFee.Should().Be(1228520L); // ---- // Sign // ---- - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); // 'from' is always required as witness // if not included on cosigner with a scope, its scope should be considered 'CalledByEntry' data.ScriptHashes.Count.Should().Be(1); @@ -238,14 +237,14 @@ public void FeeIsSignatureContractDetailed() // Fast check - Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, null, tx.NetworkFee)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee)) { engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); @@ -259,7 +258,7 @@ public void FeeIsSignatureContractDetailed() // ------------------ // check tx_size cost // ------------------ - Assert.AreEqual(246, tx.Size); + Assert.AreEqual(245, tx.Size); // will verify tx size, step by step @@ -274,16 +273,16 @@ public void FeeIsSignatureContractDetailed() // Part III Assert.AreEqual(88, tx.Script.GetVarSize()); // Part IV - Assert.AreEqual(110, tx.Witnesses.GetVarSize()); + Assert.AreEqual(109, tx.Witnesses.GetVarSize()); // I + II + III + IV - Assert.AreEqual(25 + 22 + 1 + 88 + 110, tx.Size); + Assert.AreEqual(25 + 22 + 1 + 88 + 109, tx.Size); Assert.AreEqual(1000, NativeContract.Policy.GetFeePerByte(snapshot)); var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); // final check: verification_cost and tx_size - Assert.AreEqual(246000, sizeGas); - Assert.AreEqual(983550, verificationGas); + Assert.AreEqual(245000, sizeGas); + Assert.AreEqual(983520, verificationGas); // final assert Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); @@ -294,7 +293,7 @@ public void FeeIsSignatureContractDetailed() public void FeeIsSignatureContract_TestScope_Global() { var wallet = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); // no password on this wallet using (var unlock = wallet.Unlock("")) @@ -333,7 +332,7 @@ public void FeeIsSignatureContract_TestScope_Global() // using this... - var tx = wallet.MakeTransaction(script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -342,7 +341,7 @@ public void FeeIsSignatureContract_TestScope_Global() // Sign // ---- - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -351,13 +350,13 @@ public void FeeIsSignatureContract_TestScope_Global() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, null, tx.NetworkFee)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee)) { engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); @@ -370,7 +369,7 @@ public void FeeIsSignatureContract_TestScope_Global() // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); // final check on sum: verification_cost + tx_size - Assert.AreEqual(1229550, verificationGas + sizeGas); + Assert.AreEqual(1228520, verificationGas + sizeGas); // final assert Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); } @@ -380,7 +379,7 @@ public void FeeIsSignatureContract_TestScope_Global() public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() { var wallet = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); // no password on this wallet using (var unlock = wallet.Unlock("")) @@ -420,7 +419,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() // using this... - var tx = wallet.MakeTransaction(script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -429,7 +428,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() // Sign // ---- - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -438,13 +437,13 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, null, tx.NetworkFee)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee)) { engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); @@ -457,7 +456,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); // final check on sum: verification_cost + tx_size - Assert.AreEqual(1250550, verificationGas + sizeGas); + Assert.AreEqual(1249520, verificationGas + sizeGas); // final assert Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); } @@ -467,7 +466,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() { var wallet = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); // no password on this wallet using (var unlock = wallet.Unlock("")) @@ -510,7 +509,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() // using this... - var tx = wallet.MakeTransaction(script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -519,7 +518,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() // Sign // ---- - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -528,13 +527,13 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, null, tx.NetworkFee)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee)) { engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); @@ -547,7 +546,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); // final check on sum: verification_cost + tx_size - Assert.AreEqual(1250550, verificationGas + sizeGas); + Assert.AreEqual(1249520, verificationGas + sizeGas); // final assert Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); } @@ -557,7 +556,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT() { var wallet = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); // no password on this wallet using (var unlock = wallet.Unlock("")) @@ -598,7 +597,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT() // expects FAULT on execution of 'transfer' Application script // due to lack of a valid witness validation Transaction tx = null; - Assert.ThrowsException(() => tx = wallet.MakeTransaction(script, acc.ScriptHash, signers)); + Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers)); Assert.IsNull(tx); } } @@ -607,7 +606,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT() public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() { var wallet = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); // no password on this wallet using (var unlock = wallet.Unlock("")) @@ -647,7 +646,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() // using this... - var tx = wallet.MakeTransaction(script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -656,7 +655,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() // Sign // ---- - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -670,13 +669,13 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() tx.Signers.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, null, tx.NetworkFee)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee)) { engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); @@ -689,7 +688,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); // final check on sum: verification_cost + tx_size - Assert.AreEqual(1270550, verificationGas + sizeGas); + Assert.AreEqual(1269520, verificationGas + sizeGas); // final assert Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); } @@ -699,7 +698,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() public void FeeIsSignatureContract_TestScope_NoScopeFAULT() { var wallet = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); // no password on this wallet using (var unlock = wallet.Unlock("")) @@ -742,7 +741,7 @@ public void FeeIsSignatureContract_TestScope_NoScopeFAULT() // expects FAULT on execution of 'transfer' Application script // due to lack of a valid witness validation Transaction tx = null; - Assert.ThrowsException(() => tx = wallet.MakeTransaction(script, acc.ScriptHash, signers, attributes)); + Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers, attributes)); Assert.IsNull(tx); } } @@ -750,7 +749,7 @@ public void FeeIsSignatureContract_TestScope_NoScopeFAULT() [TestMethod] public void Transaction_Reverify_Hashes_Length_Unequal_To_Witnesses_Length() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); Transaction txSimple = new Transaction { Version = 0x00, @@ -771,7 +770,7 @@ public void Transaction_Reverify_Hashes_Length_Unequal_To_Witnesses_Length() }; UInt160[] hashes = txSimple.GetScriptHashesForVerifying(snapshot); Assert.AreEqual(1, hashes.Length); - Assert.AreNotEqual(VerifyResult.Succeed, txSimple.VerifyStateDependent(snapshot, new TransactionVerificationContext())); + Assert.AreNotEqual(VerifyResult.Succeed, txSimple.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext())); } [TestMethod] @@ -788,7 +787,7 @@ public void Transaction_Serialize_Deserialize_Simple() Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, Attributes = Array.Empty(), Script = new byte[] { (byte)OpCode.PUSH1 }, - Witnesses = new Witness[0] { } + Witnesses = new Witness[] { new Witness() { InvocationScript = new byte[0], VerificationScript = Array.Empty() } } }; byte[] sTx = txSimple.ToArray(); @@ -803,7 +802,7 @@ public void Transaction_Serialize_Deserialize_Simple() "01000000000000000000000000000000000000000000" + // empty signer "00" + // no attributes "0111" + // push1 script - "00"); // no witnesses + "010000"); // empty witnesses // try to deserialize Transaction tx2 = Neo.IO.Helper.AsSerializable(sTx); @@ -824,7 +823,7 @@ public void Transaction_Serialize_Deserialize_Simple() } ); tx2.Script.Should().BeEquivalentTo(new byte[] { (byte)OpCode.PUSH1 }); - tx2.Witnesses.Should().BeEquivalentTo(new Witness[0] { }); + tx2.Witnesses.Should().BeEquivalentTo(new Witness[] { new Witness() { InvocationScript = new byte[0], VerificationScript = Array.Empty() } }); } [TestMethod] @@ -854,13 +853,13 @@ public void Transaction_Serialize_Deserialize_DistinctCosigners() } }, Script = new byte[] { (byte)OpCode.PUSH1 }, - Witnesses = new Witness[0] { } + Witnesses = new Witness[] { new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } } }; byte[] sTx = txDoubleCosigners.ToArray(); // no need for detailed hexstring here (see basic tests for it) - sTx.ToHexString().Should().Be("000403020100e1f505000000000100000000000000040302010209080706050403020100090807060504030201008009080706050403020100090807060504030201000100011100"); + sTx.ToHexString().Should().Be("000403020100e1f5050000000001000000000000000403020102090807060504030201000908070605040302010080090807060504030201000908070605040302010001000111010000"); // back to transaction (should fail, due to non-distinct cosigners) Transaction tx2 = null; @@ -904,14 +903,13 @@ public void Transaction_Serialize_Deserialize_MaxSizeCosigners() Attributes = Array.Empty(), Signers = cosigners1, // max + 1 (should fail) Script = new byte[] { (byte)OpCode.PUSH1 }, - Witnesses = new Witness[0] { } + Witnesses = new Witness[] { new Witness() { InvocationScript = new byte[0], VerificationScript = Array.Empty() } } }; byte[] sTx1 = txCosigners1.ToArray(); // back to transaction (should fail, due to non-distinct cosigners) - Transaction tx1 = Neo.IO.Helper.AsSerializable(sTx1); - Assert.IsNotNull(tx1); + Assert.ThrowsException(() => Neo.IO.Helper.AsSerializable(sTx1)); // ---------------------------- // this should fail (max + 1) @@ -938,7 +936,7 @@ public void Transaction_Serialize_Deserialize_MaxSizeCosigners() Attributes = Array.Empty(), Signers = cosigners, // max + 1 (should fail) Script = new byte[] { (byte)OpCode.PUSH1 }, - Witnesses = new Witness[0] { } + Witnesses = new Witness[] { new Witness() { InvocationScript = new byte[0], VerificationScript = Array.Empty() } } }; byte[] sTx2 = txCosigners.ToArray(); @@ -960,7 +958,7 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() cosigner.Scopes.Should().Be(WitnessScope.None); var wallet = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); // no password on this wallet using (var unlock = wallet.Unlock("")) @@ -997,12 +995,12 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() Scopes = WitnessScope.None } }; - Assert.ThrowsException(() => wallet.MakeTransaction(script, acc.ScriptHash, signers)); + Assert.ThrowsException(() => wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers)); // change to global scope signers[0].Scopes = WitnessScope.Global; - var tx = wallet.MakeTransaction(script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -1011,7 +1009,7 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() // Sign // ---- - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -1020,13 +1018,13 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, null, tx.NetworkFee)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee)) { engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); @@ -1039,7 +1037,7 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); // final check on sum: verification_cost + tx_size - Assert.AreEqual(1229550, verificationGas + sizeGas); + Assert.AreEqual(1228520, verificationGas + sizeGas); // final assert Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); } @@ -1061,9 +1059,9 @@ public void ToJson() } }; - JObject jObj = uut.ToJson(); + JObject jObj = uut.ToJson(ProtocolSettings.Default); jObj.Should().NotBeNull(); - jObj["hash"].AsString().Should().Be("0xe17382d26702bde77b00a9f23ea156b77c418764cbc45b2692088b5fde0336e3"); + jObj["hash"].AsString().Should().Be("0x0ab073429086d9e48fc87386122917989705d1c81fe4a60bf90e2fc228de3146"); jObj["size"].AsNumber().Should().Be(84); jObj["version"].AsNumber().Should().Be(0); ((JArray)jObj["attributes"]).Count.Should().Be(0); @@ -1110,15 +1108,22 @@ public void Test_VerifyStateIndependent() SystemFee = 0, ValidUntilBlock = 0, Version = 0, - Witnesses = new Witness[0], + Witnesses = new[] + { + new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = Array.Empty() + } + } }; - tx.VerifyStateIndependent().Should().Be(VerifyResult.Invalid); + tx.VerifyStateIndependent(ProtocolSettings.Default).Should().Be(VerifyResult.Invalid); tx.Script = new byte[0]; - tx.VerifyStateIndependent().Should().Be(VerifyResult.Invalid); + tx.VerifyStateIndependent(ProtocolSettings.Default).Should().Be(VerifyResult.Succeed); var walletA = TestUtils.GenerateTestWallet(); var walletB = TestUtils.GenerateTestWallet(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); using (var unlockA = walletA.Unlock("123")) using (var unlockB = walletB.Unlock("123")) @@ -1147,7 +1152,7 @@ public void Test_VerifyStateIndependent() // Make transaction - tx = walletA.MakeTransaction(new TransferOutput[] + tx = walletA.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -1159,106 +1164,47 @@ public void Test_VerifyStateIndependent() // Sign - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); Assert.IsTrue(walletA.Sign(data)); Assert.IsTrue(walletB.Sign(data)); Assert.IsTrue(data.Completed); tx.Witnesses = data.GetWitnesses(); - tx.VerifyStateIndependent().Should().Be(VerifyResult.Succeed); + tx.VerifyStateIndependent(ProtocolSettings.Default).Should().Be(VerifyResult.Succeed); } } [TestMethod] public void Test_VerifyStateDependent() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var height = NativeContract.Ledger.CurrentIndex(snapshot); var tx = new Transaction() { Attributes = Array.Empty(), - NetworkFee = 0, + NetworkFee = 55000, Nonce = (uint)Environment.TickCount, - Script = new byte[0], + Script = Array.Empty(), Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, SystemFee = 0, ValidUntilBlock = height + 1, Version = 0, - Witnesses = new Witness[0], + Witnesses = new Witness[] { + new Witness() { InvocationScript = Array.Empty(), VerificationScript = new byte[0] }, + new Witness() { InvocationScript = Array.Empty(), VerificationScript = new byte[1] } + } }; - tx.VerifyStateDependent(snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.Invalid); - tx.SystemFee = 10; - tx.VerifyStateDependent(snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.InsufficientFunds); - - var walletA = TestUtils.GenerateTestWallet(); - var walletB = TestUtils.GenerateTestWallet(); - - 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 - }); - - walletA.CreateAccount(multiSignContract, a.GetKey()); - var acc = walletB.CreateAccount(multiSignContract, b.GetKey()); - - // Fake balance - - var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); - - entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - - snapshot.Commit(); - // Make transaction + // Fake balance - tx = walletA.MakeTransaction(new TransferOutput[] - { - new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = acc.ScriptHash, - Value = new BigDecimal(BigInteger.One,8) - } - }, acc.ScriptHash); - - // Sign + var key = NativeContract.GAS.CreateStorageKey(20, tx.Sender); + var balance = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + balance.GetInteroperable().Balance = tx.NetworkFee; - var data = new ContractParametersContext(tx); - Assert.IsTrue(walletA.Sign(data)); - Assert.IsTrue(walletB.Sign(data)); - Assert.IsTrue(data.Completed); - - tx.Witnesses = data.GetWitnesses(); - tx.VerifyStateDependent(snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.Succeed); - } - } - - [TestMethod] - public void Test_Verify() - { - var snapshot = Blockchain.Singleton.GetSnapshot(); - var tx = new Transaction() - { - Attributes = Array.Empty(), - NetworkFee = 0, - Nonce = (uint)Environment.TickCount, - Script = new byte[Transaction.MaxTransactionSize], - Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, - SystemFee = 0, - ValidUntilBlock = 0, - Version = 0, - Witnesses = new Witness[0], - }; - tx.Verify(snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.Invalid); + tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.Invalid); + balance.GetInteroperable().Balance = 0; + tx.SystemFee = 10; + tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.InsufficientFunds); var walletA = TestUtils.GenerateTestWallet(); var walletB = TestUtils.GenerateTestWallet(); @@ -1281,16 +1227,15 @@ public void Test_Verify() // Fake balance - var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); - - entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - - snapshot.Commit(); + snapshot = TestBlockchain.GetTestSnapshot(); + key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + balance = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + balance.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; // Make transaction - tx = walletA.MakeTransaction(new TransferOutput[] + snapshot.Commit(); + tx = walletA.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -1302,13 +1247,13 @@ public void Test_Verify() // Sign - var data = new ContractParametersContext(tx); + var data = new ContractParametersContext(snapshot, tx); Assert.IsTrue(walletA.Sign(data)); Assert.IsTrue(walletB.Sign(data)); Assert.IsTrue(data.Completed); tx.Witnesses = data.GetWitnesses(); - tx.Verify(snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.Succeed); + tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.Succeed); } } } diff --git a/tests/neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs b/tests/neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs index ebe39427d1..ccd61f4718 100644 --- a/tests/neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs +++ b/tests/neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs @@ -16,14 +16,14 @@ public void SizeAndEndPoint_Get() var test = new VersionPayload() { Capabilities = new NodeCapability[0], UserAgent = "neo3" }; test.Size.Should().Be(22); - test = VersionPayload.Create(123, "neo3", new NodeCapability[] { new ServerCapability(NodeCapabilityType.TcpServer, 22) }); + test = VersionPayload.Create(123, 456, "neo3", new NodeCapability[] { new ServerCapability(NodeCapabilityType.TcpServer, 22) }); test.Size.Should().Be(25); } [TestMethod] public void DeserializeAndSerialize() { - var test = VersionPayload.Create(123, "neo3", new NodeCapability[] { new ServerCapability(NodeCapabilityType.TcpServer, 22) }); + var test = VersionPayload.Create(123, 456, "neo3", new NodeCapability[] { new ServerCapability(NodeCapabilityType.TcpServer, 22) }); var clone = test.ToArray().AsSerializable(); CollectionAssert.AreEqual(test.Capabilities.ToByteArray(), clone.Capabilities.ToByteArray()); @@ -32,7 +32,7 @@ public void DeserializeAndSerialize() Assert.AreEqual(test.Timestamp, clone.Timestamp); CollectionAssert.AreEqual(test.Capabilities.ToByteArray(), clone.Capabilities.ToByteArray()); - Assert.ThrowsException(() => VersionPayload.Create(123, "neo3", + Assert.ThrowsException(() => VersionPayload.Create(123, 456, "neo3", new NodeCapability[] { new ServerCapability(NodeCapabilityType.TcpServer, 22) , new ServerCapability(NodeCapabilityType.TcpServer, 22) diff --git a/tests/neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs b/tests/neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs index 9681276a1c..5f08973d2e 100644 --- a/tests/neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs +++ b/tests/neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs @@ -33,6 +33,7 @@ private Witness PrepareDummyWitness(int pubKeys, int m) var address = new WalletAccount[pubKeys]; var wallets = new NEP6Wallet[pubKeys]; var walletsUnlocks = new IDisposable[pubKeys]; + var snapshot = TestBlockchain.GetTestSnapshot(); for (int x = 0; x < pubKeys; x++) { @@ -52,7 +53,7 @@ private Witness PrepareDummyWitness(int pubKeys, int m) // Sign - var data = new ContractParametersContext(new Transaction() + var data = new ContractParametersContext(snapshot, new Transaction() { Attributes = Array.Empty(), Signers = new[] {new Signer() @@ -85,11 +86,9 @@ public void MaxSize_OK() // Check max size - witness.Size.Should().Be(1024); + witness.Size.Should().Be(1023); witness.InvocationScript.GetVarSize().Should().Be(663); - witness.VerificationScript.GetVarSize().Should().Be(361); - - Assert.IsTrue(witness.Size <= 1024); + witness.VerificationScript.GetVarSize().Should().Be(360); var copy = witness.ToArray().AsSerializable(); diff --git a/tests/neo.UnitTests/Network/P2P/UT_LocalNode.cs b/tests/neo.UnitTests/Network/P2P/UT_LocalNode.cs index 81b610ab14..1e4da7590b 100644 --- a/tests/neo.UnitTests/Network/P2P/UT_LocalNode.cs +++ b/tests/neo.UnitTests/Network/P2P/UT_LocalNode.cs @@ -1,3 +1,4 @@ +using Akka.TestKit.Xunit2; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P; using System; @@ -7,7 +8,7 @@ namespace Neo.UnitTests.Network.P2P { [TestClass] - public class UT_LocalNode + public class UT_LocalNode : TestKit { private static NeoSystem testBlockchain; @@ -20,15 +21,19 @@ public void Init() [TestMethod] public void TestDefaults() { - Assert.AreEqual(0, LocalNode.Singleton.ListenerTcpPort); - Assert.AreEqual(0, LocalNode.Singleton.ListenerWsPort); - Assert.AreEqual(3, LocalNode.Singleton.MaxConnectionsPerAddress); - Assert.AreEqual(10, LocalNode.Singleton.MinDesiredConnections); - Assert.AreEqual(40, LocalNode.Singleton.MaxConnections); - Assert.AreEqual(0, LocalNode.Singleton.UnconnectedCount); + var senderProbe = CreateTestProbe(); + senderProbe.Send(testBlockchain.LocalNode, new LocalNode.GetInstance()); + var localnode = senderProbe.ExpectMsg(); - CollectionAssert.AreEqual(Array.Empty(), LocalNode.Singleton.GetRemoteNodes().ToArray()); - CollectionAssert.AreEqual(Array.Empty(), LocalNode.Singleton.GetUnconnectedPeers().ToArray()); + Assert.AreEqual(0, localnode.ListenerTcpPort); + Assert.AreEqual(0, localnode.ListenerWsPort); + Assert.AreEqual(3, localnode.MaxConnectionsPerAddress); + Assert.AreEqual(10, localnode.MinDesiredConnections); + Assert.AreEqual(40, localnode.MaxConnections); + Assert.AreEqual(0, localnode.UnconnectedCount); + + CollectionAssert.AreEqual(Array.Empty(), localnode.GetRemoteNodes().ToArray()); + CollectionAssert.AreEqual(Array.Empty(), localnode.GetUnconnectedPeers().ToArray()); } } } diff --git a/tests/neo.UnitTests/Network/P2P/UT_Message.cs b/tests/neo.UnitTests/Network/P2P/UT_Message.cs index e688806a75..5ad392f4b5 100644 --- a/tests/neo.UnitTests/Network/P2P/UT_Message.cs +++ b/tests/neo.UnitTests/Network/P2P/UT_Message.cs @@ -131,16 +131,16 @@ public void Compression() { Nonce = 1, Version = 0, - Attributes = new TransactionAttribute[0], + Attributes = Array.Empty(), Script = new byte[] { (byte)OpCode.PUSH1 }, Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, - Witnesses = new Witness[0], + Witnesses = new Witness[] { new Witness() { InvocationScript = Array.Empty(), VerificationScript = new byte[0] } }, }; var msg = Message.Create(MessageCommand.Transaction, payload); var buffer = msg.ToArray(); - buffer.Length.Should().Be(54); + buffer.Length.Should().Be(56); payload.Script = new byte[100]; for (int i = 0; i < payload.Script.Length; i++) payload.Script[i] = (byte)OpCode.PUSH2; diff --git a/tests/neo.UnitTests/Network/P2P/UT_RemoteNode.cs b/tests/neo.UnitTests/Network/P2P/UT_RemoteNode.cs index 8f5b9c8000..9698474460 100644 --- a/tests/neo.UnitTests/Network/P2P/UT_RemoteNode.cs +++ b/tests/neo.UnitTests/Network/P2P/UT_RemoteNode.cs @@ -30,7 +30,7 @@ public static void TestSetup(TestContext ctx) public void RemoteNode_Test_Abort_DifferentMagic() { var connectionTestProbe = CreateTestProbe(); - var remoteNodeActor = ActorOfAsTestActorRef(() => new RemoteNode(testBlockchain, connectionTestProbe, null, null)); + var remoteNodeActor = ActorOfAsTestActorRef(() => new RemoteNode(testBlockchain, new LocalNode(testBlockchain), connectionTestProbe, null, null)); var msg = Message.Create(MessageCommand.Version, new VersionPayload { @@ -55,7 +55,7 @@ public void RemoteNode_Test_Abort_DifferentMagic() public void RemoteNode_Test_Accept_IfSameMagic() { var connectionTestProbe = CreateTestProbe(); - var remoteNodeActor = ActorOfAsTestActorRef(() => new RemoteNode(testBlockchain, connectionTestProbe, new IPEndPoint(IPAddress.Parse("192.168.1.2"), 8080), new IPEndPoint(IPAddress.Parse("192.168.1.1"), 8080))); + var remoteNodeActor = ActorOfAsTestActorRef(() => new RemoteNode(testBlockchain, new LocalNode(testBlockchain), connectionTestProbe, new IPEndPoint(IPAddress.Parse("192.168.1.2"), 8080), new IPEndPoint(IPAddress.Parse("192.168.1.1"), 8080))); var msg = Message.Create(MessageCommand.Version, new VersionPayload() { diff --git a/tests/neo.UnitTests/Plugins/TestLogPlugin.cs b/tests/neo.UnitTests/Plugins/TestLogPlugin.cs index 90d9d2c4df..cb73ce4106 100644 --- a/tests/neo.UnitTests/Plugins/TestLogPlugin.cs +++ b/tests/neo.UnitTests/Plugins/TestLogPlugin.cs @@ -32,20 +32,5 @@ public IConfigurationSection TestGetConfiguration() } protected override bool OnMessage(object message) => true; - - public static bool TestResumeNodeStartup() - { - return ResumeNodeStartup(); - } - - public static void TestSuspendNodeStartup() - { - SuspendNodeStartup(); - } - - public static void TestLoadPlugins(NeoSystem system) - { - LoadPlugins(system); - } } } diff --git a/tests/neo.UnitTests/Plugins/UT_Plugin.cs b/tests/neo.UnitTests/Plugins/UT_Plugin.cs index 97085860b5..17758c92ca 100644 --- a/tests/neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/neo.UnitTests/Plugins/UT_Plugin.cs @@ -21,7 +21,7 @@ private class dummyPersistencePlugin : IPersistencePlugin { } public void TestIP2PPlugin() { var pp = new DummyP2PPlugin() as IP2PPlugin; - Assert.IsTrue(pp.OnP2PMessage(null)); + Assert.IsTrue(pp.OnP2PMessage(null, null)); } [TestMethod] @@ -33,8 +33,8 @@ public void TestIPersistencePlugin() // With empty default implementation - pp.OnCommit(null, null); - pp.OnPersist(null, null, null); + pp.OnCommit(null, null, null); + pp.OnPersist(null, null, null, null); } [TestMethod] @@ -81,16 +81,6 @@ public void TestSendMessage() } } - [TestMethod] - public void TestResumeNodeStartupAndSuspendNodeStartup() - { - TestLogPlugin.TestLoadPlugins(TestBlockchain.TheNeoSystem); - TestLogPlugin.TestSuspendNodeStartup(); - TestLogPlugin.TestSuspendNodeStartup(); - TestLogPlugin.TestResumeNodeStartup().Should().BeFalse(); - TestLogPlugin.TestResumeNodeStartup().Should().BeTrue(); - } - [TestMethod] public void TestGetConfiguration() { diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs b/tests/neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs index fdf93436f4..2a10a91a76 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs @@ -1,86 +1,18 @@ using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Ledger; -using Neo.SmartContract; using Neo.SmartContract.Native; -using System; -using System.Numerics; namespace Neo.UnitTests.SmartContract.Native { [TestClass] public class UT_FungibleToken : TestKit { - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - - protected const byte Prefix_TotalSupply = 11; - private static readonly TestNep17Token test = new TestNep17Token(); - - [TestMethod] - public void TestName() - { - Assert.AreEqual(test.Name, test.Manifest.Name); - } - [TestMethod] public void TestTotalSupply() { - var snapshot = Blockchain.Singleton.GetSnapshot(); - - StorageItem item = new StorageItem - { - Value = new byte[] { 0x01 } - }; - var key = CreateStorageKey(Prefix_TotalSupply); - - key.Id = test.Id; - - snapshot.Add(key, item); - test.TotalSupply(snapshot).Should().Be(1); - } - - [TestMethod] - public void TestTotalSupplyDecimal() - { - var snapshot = Blockchain.Singleton.GetSnapshot(); - - BigInteger totalSupply = 100_000_000; - totalSupply *= test.Factor; - - StorageItem item = new StorageItem - { - Value = totalSupply.ToByteArrayStandard() - }; - var key = CreateStorageKey(Prefix_TotalSupply); - - key.Id = test.Id; - - snapshot.Add(key, item); - - test.TotalSupply(snapshot).Should().Be(10_000_000_000_000_000); + var snapshot = TestBlockchain.GetTestSnapshot(); + NativeContract.GAS.TotalSupply(snapshot).Should().Be(3000000050000000); } - - public StorageKey CreateStorageKey(byte prefix, byte[] key = null) - { - StorageKey storageKey = new StorageKey - { - Id = 0, - Key = new byte[sizeof(byte) + (key?.Length ?? 0)] - }; - storageKey.Key[0] = prefix; - key?.CopyTo(storageKey.Key.AsSpan(1)); - return storageKey; - } - } - - public class TestNep17Token : FungibleToken - { - public override string Symbol => throw new NotImplementedException(); - public override byte Decimals => 8; } } diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_GasToken.cs b/tests/neo.UnitTests/SmartContract/Native/UT_GasToken.cs index ada89b0973..dd3b036bd8 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_GasToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_GasToken.cs @@ -1,16 +1,15 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.UnitTests.Extensions; -using Neo.VM; using System; using System.Linq; using System.Numerics; +using System.Threading.Tasks; namespace Neo.UnitTests.SmartContract.Native { @@ -23,10 +22,8 @@ public class UT_GasToken [TestInitialize] public void TestSetup() { - TestBlockchain.InitializeMockNeoSystem(); - - _snapshot = Blockchain.Singleton.GetSnapshot(); - _persistingBlock = new Block() { Index = 0 }; + _snapshot = TestBlockchain.GetTestSnapshot(); + _persistingBlock = new Block { Header = new Header() }; } [TestMethod] @@ -39,11 +36,11 @@ public void TestSetup() public void Check_Decimals() => NativeContract.GAS.Decimals(_snapshot).Should().Be(8); [TestMethod] - public void Check_BalanceOfTransferAndBurn() + public async Task Check_BalanceOfTransferAndBurn() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000 }; - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; var keyCount = snapshot.GetChangeSet().Count(); var supply = NativeContract.GAS.TotalSupply(snapshot); @@ -92,20 +89,20 @@ public void Check_BalanceOfTransferAndBurn() // Burn - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, 0); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings, gas: 0); keyCount = snapshot.GetChangeSet().Count(); - Assert.ThrowsException(() => - NativeContract.GAS.Burn(engine, new UInt160(to), BigInteger.MinusOne)); + await Assert.ThrowsExceptionAsync(async () => + await NativeContract.GAS.Burn(engine, new UInt160(to), BigInteger.MinusOne)); // Burn more than expected - Assert.ThrowsException(() => - NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(30000500_00000001))); + await Assert.ThrowsExceptionAsync(async () => + await NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(30000500_00000001))); // Real burn - NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(1)); + await NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(1)); NativeContract.GAS.BalanceOf(snapshot, to).Should().Be(3000049999999999); @@ -113,7 +110,7 @@ public void Check_BalanceOfTransferAndBurn() // Burn all - NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(3000049999999999)); + await NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(3000049999999999)); (keyCount - 1).Should().Be(snapshot.GetChangeSet().Count()); @@ -124,18 +121,6 @@ public void Check_BalanceOfTransferAndBurn() NativeContract.GAS.Transfer(snapshot, from, new byte[19], BigInteger.One, false, persistingBlock).Should().BeFalse(); } - [TestMethod] - public void Check_BadScript() - { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, Blockchain.Singleton.GetSnapshot(), _persistingBlock, 0); - - using var script = new ScriptBuilder(); - script.Emit(OpCode.NOP); - engine.LoadScript(script.ToArray()); - - Assert.ThrowsException(() => NativeContract.GAS.Invoke(engine)); - } - internal static StorageKey CreateStorageKey(byte prefix, uint key) { return CreateStorageKey(prefix, BitConverter.GetBytes(key)); diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NNS.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NNS.cs index 2f2ba3e5c1..a2adb573b0 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NNS.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NNS.cs @@ -3,7 +3,6 @@ using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -31,9 +30,12 @@ public class UT_NNS [TestInitialize] public void TestSetup() { - TestBlockchain.InitializeMockNeoSystem(); - _snapshot = Blockchain.Singleton.GetSnapshot(); - _persistingBlock = new Block() { Index = 0, Transactions = Array.Empty(), ConsensusData = new ConsensusData() }; + _snapshot = TestBlockchain.GetTestSnapshot(); + _persistingBlock = new Block + { + Header = new Header(), + Transactions = Array.Empty() + }; } [TestMethod] @@ -43,17 +45,17 @@ public void TestSetup() public void Check_Symbol() => NativeContract.NameService.Symbol(_snapshot).Should().Be("NNS"); [TestMethod] - public void Test_SetRecord_IPV4() + public void Test_SetRecord_A_AAAA() { var snapshot = _snapshot.CreateSnapshot(); // Fake blockchain - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; UInt160 committeeAddress = NativeContract.NEO.GetCommitteeAddress(snapshot); // committee member,add a new root and then register, setrecord string validroot = "testroot"; - var ret = Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock); + Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock).Should().BeTrue(); string name = "testname"; string domain = name + "." + validroot; @@ -63,82 +65,70 @@ public void Test_SetRecord_IPV4() checkAvail_ret.Result.Should().BeTrue(); checkAvail_ret.State.Should().BeTrue(); - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var register_ret = Check_Register(snapshot, domain, from, persistingBlock); register_ret.Result.Should().BeTrue(); register_ret.State.Should().BeTrue(); //check NFT token - Assert.AreEqual(NativeContract.NameService.BalanceOf(snapshot, from), (BigInteger)1); + Assert.AreEqual(NativeContract.NameService.BalanceOf(snapshot, from), 1); //after register checkAvail_ret = Check_IsAvailable(snapshot, UInt160.Zero, domain, persistingBlock); checkAvail_ret.Result.Should().BeTrue(); checkAvail_ret.State.Should().BeFalse(); - //set As IPv4 Address - string testA = "10.10.10.10"; - var setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, testA, from, persistingBlock); - setRecord_ret.Should().BeTrue(); - - var getRecord_ret = Check_GetRecord(snapshot, domain, RecordType.A, persistingBlock); - getRecord_ret.State.Should().BeTrue(); - getRecord_ret.Result.Should().Equals(testA); - - testA = "0.0.0.0"; - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, testA, from, persistingBlock); - setRecord_ret.Should().BeTrue(); - getRecord_ret = Check_GetRecord(snapshot, domain, RecordType.A, persistingBlock); - getRecord_ret.State.Should().BeTrue(); - getRecord_ret.Result.Should().Equals(testA); - - testA = "255.255.255.255"; - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, testA, from, persistingBlock); - setRecord_ret.Should().BeTrue(); - getRecord_ret = Check_GetRecord(snapshot, domain, RecordType.A, persistingBlock); - getRecord_ret.State.Should().BeTrue(); - getRecord_ret.Result.Should().Equals(testA); - - //invalid case - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "1a", from, persistingBlock); - setRecord_ret.Should().BeFalse(); - - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "256.0.0.0", from, persistingBlock); - setRecord_ret.Should().BeFalse(); - - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "0.0.0.-1", from, persistingBlock); - setRecord_ret.Should().BeFalse(); - - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "0.0.0.0.1", from, persistingBlock); - setRecord_ret.Should().BeFalse(); - - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "11111111.11111111.11111111.11111111", from, persistingBlock); - setRecord_ret.Should().BeFalse(); - - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "11111111.11111111.11111111.11111111", from, persistingBlock); - setRecord_ret.Should().BeFalse(); - - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "ff.ff.ff.ff", from, persistingBlock); - setRecord_ret.Should().BeFalse(); - - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "0.0.256", from, persistingBlock); - setRecord_ret.Should().BeFalse(); - - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "0.0.0", from, persistingBlock); - setRecord_ret.Should().BeFalse(); + //test As IPv4 Address + bool setRecord_ret; + (bool State, string Result) getRecord_ret; - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "0.257", from, persistingBlock); - setRecord_ret.Should().BeFalse(); - - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "1.1", from, persistingBlock); - setRecord_ret.Should().BeFalse(); + foreach (var testTrue in new string[] { + "0.0.0.0", "10.10.10.10", "255.255.255.255","192.168.1.1" + }) + { + setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, testTrue, from, persistingBlock); + setRecord_ret.Should().BeTrue(); + getRecord_ret = Check_GetRecord(snapshot, domain, RecordType.A, persistingBlock); + getRecord_ret.State.Should().BeTrue(); + getRecord_ret.Result.Should().Equals(testTrue); + } - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "257", from, persistingBlock); - setRecord_ret.Should().BeFalse(); + //test As IPv6 Address + foreach (var testTrue in new string[] { + "2001:db8::8:800:200c:417a", "ff01::101", "ff01::101", "::1", "::", + "2001:db8:0:0:8:800:200c:417a", "ff01:0:0:0:0:0:0:101", "0:0:0:0:0:0:0:1", "0:0:0:0:0:0:0:0" + }) + { + setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.AAAA, testTrue, from, persistingBlock); + setRecord_ret.Should().BeTrue(); + getRecord_ret = Check_GetRecord(snapshot, domain, RecordType.AAAA, persistingBlock); + getRecord_ret.State.Should().BeTrue(); + getRecord_ret.Result.Should().Equals(testTrue); + } - setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, "1", from, persistingBlock); - setRecord_ret.Should().BeFalse(); + //invalid A case + foreach (var testFalse in new string[] { + "1a", "256.0.0.0", "01.01.01.01", "00.0.0.0", "0.0.0.-1", + "0.0.0.0.1", "11111111.11111111.11111111.11111111", + "11111111.11111111.11111111.11111111", "ff.ff.ff.ff", "0.0.256", + "0.0.0", "0.257", "1.1", "257", + "1" + }) + { + setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.A, testFalse, from, persistingBlock); + setRecord_ret.Should().BeFalse(); + } + //invalid AAAA case + foreach (var testFalse in new string[] { + "2001:DB8::8:800:200C:417A", "FF01::101", "fF01::101", + "2001:DB8:0:0:8:800:200C:417A", "FF01:0:0:0:0:0:0:101", + "::ffff:1.01.1.01", "2001:DB8:0:0:8:800:200C:4Z", "::13.1.68.3", + }) + { + setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.AAAA, testFalse, from, persistingBlock); + setRecord_ret.Should().BeFalse(); + } } [TestMethod] @@ -147,18 +137,18 @@ public void Test_SetRecord_CNAME() var snapshot = _snapshot.CreateSnapshot(); // Fake blockchain - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; UInt160 committeeAddress = NativeContract.NEO.GetCommitteeAddress(snapshot); // committee member,add a new root and then register, setrecord string validroot = "testroot"; - var ret = Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock); + Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock).Should().BeTrue(); string name = "testname"; string domain = name + "." + validroot; - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var register_ret = Check_Register(snapshot, domain, from, persistingBlock); register_ret.Result.Should().BeTrue(); register_ret.State.Should().BeTrue(); @@ -224,12 +214,12 @@ public void Test_SetRecord_TxT() var snapshot = _snapshot.CreateSnapshot(); // Fake blockchain - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; UInt160 committeeAddress = NativeContract.NEO.GetCommitteeAddress(snapshot); // committee member,add a new root and then register, setrecord string validroot = "testroot"; - var ret = Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock); + Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock).Should().BeTrue(); string name = "testname"; string domain = name + "." + validroot; @@ -239,7 +229,7 @@ public void Test_SetRecord_TxT() checkAvail_ret.Result.Should().BeTrue(); checkAvail_ret.State.Should().BeTrue(); - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var register_ret = Check_Register(snapshot, domain, from, persistingBlock); register_ret.Result.Should().BeTrue(); register_ret.State.Should().BeTrue(); @@ -315,12 +305,12 @@ public void Test_SetRecord_IPV6() var snapshot = _snapshot.CreateSnapshot(); // Fake blockchain - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; UInt160 committeeAddress = NativeContract.NEO.GetCommitteeAddress(snapshot); // committee member,add a new root and then register, setrecord string validroot = "testroot"; - var ret = Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock); + Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock).Should().BeTrue(); string name = "testname"; string domain = name + "." + validroot; @@ -330,13 +320,13 @@ public void Test_SetRecord_IPV6() checkAvail_ret.Result.Should().BeTrue(); checkAvail_ret.State.Should().BeTrue(); - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var register_ret = Check_Register(snapshot, domain, from, persistingBlock); register_ret.Result.Should().BeTrue(); register_ret.State.Should().BeTrue(); //set as IPV6 address - string testAAAA = "2001:0000:1F1F:0000:0000:0100:11A0:ADDF"; + string testAAAA = "2001:0000:1f1f:0000:0000:0100:11a0:addf"; var setRecord_ret = Check_SetRecord(snapshot, domain, RecordType.AAAA, testAAAA, from, persistingBlock); setRecord_ret.Should().BeTrue(); @@ -403,7 +393,7 @@ public void Test_AddRootInvalid() var persistingBlock = _persistingBlock; //non-committee member string validroot = "testroot"; - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var ret = Check_AddRoot(snapshot, new UInt160(from), validroot, persistingBlock); ret.Should().BeFalse(); @@ -447,12 +437,12 @@ public void Test_SetAdmin() var snapshot = _snapshot.CreateSnapshot(); // Fake blockchain - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; UInt160 committeeAddress = NativeContract.NEO.GetCommitteeAddress(snapshot); // committee member,add a new root and then register, setrecord string validroot = "testroot"; - var ret = Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock); + Check_AddRoot(snapshot, committeeAddress, validroot, persistingBlock).Should().BeTrue(); string name = "testname"; string domain = name + "." + validroot; @@ -462,7 +452,7 @@ public void Test_SetAdmin() checkAvail_ret.Result.Should().BeTrue(); checkAvail_ret.State.Should().BeTrue(); - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var register_ret = Check_Register(snapshot, domain, from, persistingBlock); register_ret.Result.Should().BeTrue(); register_ret.State.Should().BeTrue(); @@ -506,12 +496,10 @@ public void Test_SetAdmin() internal static bool Check_AddRoot(DataCache snapshot, UInt160 account, string root, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(account), snapshot, persistingBlock); - engine.LoadScript(NativeContract.NameService.Script, configureState: p => p.ScriptHash = NativeContract.NameService.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(account), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); - var script = new ScriptBuilder(); - script.EmitPush(root); - script.EmitPush("addRoot"); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "addRoot", root); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -524,13 +512,10 @@ internal static bool Check_AddRoot(DataCache snapshot, UInt160 account, string r internal static bool Check_Transfer(DataCache snapshot, UInt160 to, string domain, UInt160 owner, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(owner), snapshot, persistingBlock); - engine.LoadScript(NativeContract.NameService.Script, configureState: p => p.ScriptHash = NativeContract.NameService.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(owner), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); - var script = new ScriptBuilder(); - script.EmitPush(domain); - script.EmitPush(to); - script.EmitPush("transfer"); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "transfer", to, domain); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -543,13 +528,10 @@ internal static bool Check_Transfer(DataCache snapshot, UInt160 to, string domai internal static (bool State, bool Result) Check_IsAvailable(DataCache snapshot, UInt160 account, string name, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(account), snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NameService.Script, configureState: p => p.ScriptHash = NativeContract.NameService.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(account), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); - var script = new ScriptBuilder(); - script.EmitPush(name); - script.EmitPush("isAvailable"); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.NameService.Hash, "isAvailable", name); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -566,13 +548,10 @@ internal static (bool State, bool Result) Check_IsAvailable(DataCache snapshot, internal static (bool State, bool Result) Check_SetPrice(DataCache snapshot, byte[] pubkey, long price, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NameService.Script, configureState: p => p.ScriptHash = NativeContract.NameService.Hash); + new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush(price); - script.EmitPush("setPrice"); + script.EmitDynamicCall(NativeContract.NameService.Hash, "setPrice", price); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -588,12 +567,10 @@ internal static (bool State, bool Result) Check_SetPrice(DataCache snapshot, byt internal static BigDecimal Check_GetPrice(DataCache snapshot, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NameService.Script, configureState: p => p.ScriptHash = NativeContract.NameService.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush("getPrice"); + script.EmitDynamicCall(NativeContract.NameService.Hash, "getPrice"); engine.LoadScript(script.ToArray()); engine.Execute().Should().Be(VMState.HALT); @@ -607,14 +584,10 @@ internal static BigDecimal Check_GetPrice(DataCache snapshot, Block persistingBl internal static (bool State, bool Result) Check_Register(DataCache snapshot, string name, byte[] owner, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(new UInt160(owner)), snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NameService.Script, configureState: p => p.ScriptHash = NativeContract.NameService.Hash); + new Nep17NativeContractExtensions.ManualWitness(new UInt160(owner)), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush(new UInt160(owner)); - script.EmitPush(name); - script.EmitPush("register"); + script.EmitDynamicCall(NativeContract.NameService.Hash, "register", name, owner); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -631,15 +604,10 @@ internal static (bool State, bool Result) Check_Register(DataCache snapshot, str internal static bool Check_SetRecord(DataCache snapshot, string name, RecordType type, string data, byte[] pubkey, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(new UInt160(pubkey)), snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NameService.Script, configureState: p => p.ScriptHash = NativeContract.NameService.Hash); + new Nep17NativeContractExtensions.ManualWitness(new UInt160(pubkey)), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush(data); - script.EmitPush(type); - script.EmitPush(name); - script.EmitPush("setRecord"); + script.EmitDynamicCall(NativeContract.NameService.Hash, "setRecord", name, type, data); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -653,14 +621,10 @@ internal static bool Check_SetRecord(DataCache snapshot, string name, RecordType internal static (bool State, string Result) Check_GetRecord(DataCache snapshot, string name, RecordType type, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(UInt160.Zero), snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NameService.Script, configureState: p => p.ScriptHash = NativeContract.NameService.Hash); + new Nep17NativeContractExtensions.ManualWitness(UInt160.Zero), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush(type); - script.EmitPush(name); - script.EmitPush("getRecord"); + script.EmitDynamicCall(NativeContract.NameService.Hash, "getRecord", name, type); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -677,14 +641,10 @@ internal static (bool State, string Result) Check_GetRecord(DataCache snapshot, internal static bool Check_SetAdmin(DataCache snapshot, string name, UInt160 admin, byte[] pubkey, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(new UInt160[] { admin, new UInt160(pubkey) }), snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NameService.Script, configureState: p => p.ScriptHash = NativeContract.NameService.Hash); + new Nep17NativeContractExtensions.ManualWitness(new UInt160[] { admin, new UInt160(pubkey) }), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush(admin); - script.EmitPush(name); - script.EmitPush("setAdmin"); + script.EmitDynamicCall(NativeContract.NameService.Hash, "setAdmin", name, admin); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs index 8e87e36c4c..f0f342af13 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NameService.cs @@ -2,7 +2,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -23,8 +22,7 @@ public class UT_NameService : TestKit [TestInitialize] public void TestSetup() { - TestBlockchain.InitializeMockNeoSystem(); - _snapshot = Blockchain.Singleton.GetSnapshot(); + _snapshot = TestBlockchain.GetTestSnapshot(); } [TestMethod] @@ -38,7 +36,7 @@ public void TestInfo() public void TestRoots() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); // no match @@ -63,7 +61,7 @@ public void TestRoots() public void TestPrice() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); // unsigned @@ -88,7 +86,7 @@ public void TestPrice() public void TestRegister() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000, Timestamp = 0 }; + var persistingBlock = new Block { Header = new Header { Index = 1000, Timestamp = 0 } }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); // add root @@ -109,7 +107,7 @@ public void TestRegister() // good register - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); Assert.IsTrue(NativeContract.NameService.IsAvailable(snapshot, "neo.com")); result = Check_Register(snapshot, "neo.com", UInt160.Zero, persistingBlock); @@ -127,7 +125,7 @@ public void TestRegister() public void TestSetRecord() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000, Timestamp = 0 }; + var persistingBlock = new Block { Header = new Header { Index = 1000, Timestamp = 0 } }; var from = NativeContract.NEO.GetCommitteeAddress(snapshot); // add root @@ -160,7 +158,7 @@ public void TestSetRecord() internal static bool Check_DeleteRecord(DataCache snapshot, string name, RecordType type, UInt160 signedBy, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "deleteRecord", new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name }, @@ -178,7 +176,7 @@ internal static bool Check_DeleteRecord(DataCache snapshot, string name, RecordT internal static bool Check_SetRecord(DataCache snapshot, string name, RecordType type, string data, UInt160 signedBy, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "setRecord", new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name }, @@ -197,7 +195,7 @@ internal static bool Check_SetRecord(DataCache snapshot, string name, RecordType internal static BigInteger Check_Renew(DataCache snapshot, string name, UInt160 signedBy, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "renew", new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name } @@ -217,7 +215,7 @@ internal static BigInteger Check_Renew(DataCache snapshot, string name, UInt160 internal static bool Check_SetAdmin(DataCache snapshot, string name, UInt160 admin, UInt160 signedBy, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(admin, signedBy), snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(admin, signedBy), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "setAdmin", new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name }, @@ -235,7 +233,7 @@ internal static bool Check_SetAdmin(DataCache snapshot, string name, UInt160 adm internal static bool Check_Register(DataCache snapshot, string name, UInt160 owner, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(owner), snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(owner), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "register", new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = name }, @@ -256,7 +254,7 @@ internal static bool Check_Register(DataCache snapshot, string name, UInt160 own internal static bool Check_SetPrice(DataCache snapshot, UInt160 signedBy, long price, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "setPrice", new ContractParameter[] { new ContractParameter(ContractParameterType.Integer) { Value = price } }); engine.LoadScript(script.ToArray()); @@ -271,7 +269,7 @@ internal static bool Check_SetPrice(DataCache snapshot, UInt160 signedBy, long p internal static bool Check_AddRoot(DataCache snapshot, UInt160 signedBy, string root, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(signedBy), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NameService.Hash, "addRoot", new ContractParameter[] { new ContractParameter(ContractParameterType.String) { Value = root } }); engine.LoadScript(script.ToArray()); diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index 4f378c13e1..710502bef5 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -1,140 +1,15 @@ -using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.IO; -using Neo.Ledger; -using Neo.SmartContract; using Neo.SmartContract.Native; -using Neo.VM.Types; -using System; -using System.Collections.Generic; -using System.Numerics; namespace Neo.UnitTests.SmartContract.Native { [TestClass] public class UT_NativeContract { - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - - private static readonly TestNativeContract testNativeContract = new TestNativeContract(); - - [TestMethod] - public void TestInitialize() - { - ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, null, null, 0); - testNativeContract.Initialize(ae); - } - - private class DummyNative : NativeContract - { - [ContractMethod(0, CallFlags.None)] - public void NetTypes( - bool p1, sbyte p2, byte p3, short p4, ushort p5, int p6, uint p7, long p8, ulong p9, BigInteger p10, - byte[] p11, string p12, IInteroperable p13, ISerializable p14, int[] p15, ContractParameterType p16, - object p17) - { } - - [ContractMethod(0, CallFlags.None)] - public void VMTypes( - VM.Types.Boolean p1, VM.Types.Integer p2, VM.Types.ByteString p3, VM.Types.Buffer p4, - VM.Types.Array p5, VM.Types.Struct p6, VM.Types.Map p7, VM.Types.StackItem p8 - ) - { } - } - - [TestMethod] - public void TestToParameter() - { - var manifest = new DummyNative().Manifest; - var netTypes = manifest.Abi.GetMethod("netTypes", 17); - - Assert.AreEqual(netTypes.ReturnType, ContractParameterType.Void); - Assert.AreEqual(netTypes.Parameters[0].Type, ContractParameterType.Boolean); - Assert.AreEqual(netTypes.Parameters[1].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[2].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[3].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[4].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[5].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[6].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[7].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[8].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[9].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[10].Type, ContractParameterType.ByteArray); - Assert.AreEqual(netTypes.Parameters[11].Type, ContractParameterType.String); - Assert.AreEqual(netTypes.Parameters[12].Type, ContractParameterType.Array); - Assert.AreEqual(netTypes.Parameters[13].Type, ContractParameterType.ByteArray); - Assert.AreEqual(netTypes.Parameters[14].Type, ContractParameterType.Array); - Assert.AreEqual(netTypes.Parameters[15].Type, ContractParameterType.Integer); - Assert.AreEqual(netTypes.Parameters[16].Type, ContractParameterType.Any); - - var vmTypes = manifest.Abi.GetMethod("vMTypes", 8); - - Assert.AreEqual(vmTypes.ReturnType, ContractParameterType.Void); - Assert.AreEqual(vmTypes.Parameters[0].Type, ContractParameterType.Boolean); - Assert.AreEqual(vmTypes.Parameters[1].Type, ContractParameterType.Integer); - Assert.AreEqual(vmTypes.Parameters[2].Type, ContractParameterType.ByteArray); - Assert.AreEqual(vmTypes.Parameters[3].Type, ContractParameterType.ByteArray); - Assert.AreEqual(vmTypes.Parameters[4].Type, ContractParameterType.Array); - Assert.AreEqual(vmTypes.Parameters[5].Type, ContractParameterType.Array); - Assert.AreEqual(vmTypes.Parameters[6].Type, ContractParameterType.Map); - Assert.AreEqual(vmTypes.Parameters[7].Type, ContractParameterType.Any); - } - [TestMethod] public void TestGetContract() { - Assert.IsTrue(NativeContract.NEO == NativeContract.GetContract(NativeContract.NEO.Id)); Assert.IsTrue(NativeContract.NEO == NativeContract.GetContract(NativeContract.NEO.Hash)); } - - [TestMethod] - public void TestInvoke() - { - var snapshot = Blockchain.Singleton.GetSnapshot(); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, null, 0); - engine.LoadScript(testNativeContract.Script, configureState: p => p.ScriptHash = testNativeContract.Hash); - - ByteString method1 = new ByteString(System.Text.Encoding.Default.GetBytes("wrongMethod")); - engine.CurrentContext.EvaluationStack.Push(method1); - Assert.ThrowsException(() => testNativeContract.Invoke(engine)); - - ByteString method2 = new ByteString(System.Text.Encoding.Default.GetBytes("helloWorld")); - engine.CurrentContext.EvaluationStack.Push(method2); - testNativeContract.Invoke(engine); - } - - [TestMethod] - public void TestTrigger() - { - var snapshot = Blockchain.Singleton.GetSnapshot(); - - ApplicationEngine engine1 = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, 0); - Assert.ThrowsException(() => testNativeContract.TestTrigger(engine1)); - - ApplicationEngine engine2 = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, null, 0); - testNativeContract.TestTrigger(engine2); - } - - [TestMethod] - public void TestTestCall() - { - ApplicationEngine engine = testNativeContract.TestCall("System.Blockchain.GetHeight", 0); - engine.ResultStack.Should().BeEmpty(); - } - } - - public class TestNativeContract : NativeContract - { - [ContractMethod(0, CallFlags.None)] - public string HelloWorld => "hello world"; - - public void TestTrigger(ApplicationEngine engine) - { - if (engine.Trigger != TriggerType.OnPersist) throw new InvalidOperationException(); - } } } diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_NeoToken.cs b/tests/neo.UnitTests/SmartContract/Native/UT_NeoToken.cs index 63adec3fb4..a2831cfcc4 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_NeoToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_NeoToken.cs @@ -3,7 +3,6 @@ using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -27,9 +26,12 @@ public class UT_NeoToken [TestInitialize] public void TestSetup() { - TestBlockchain.InitializeMockNeoSystem(); - _snapshot = Blockchain.Singleton.GetSnapshot(); - _persistingBlock = new Block() { Index = 0, Transactions = Array.Empty(), ConsensusData = new ConsensusData() }; + _snapshot = TestBlockchain.GetTestSnapshot(); + _persistingBlock = new Block + { + Header = new Header(), + Transactions = Array.Empty() + }; } [TestMethod] @@ -45,9 +47,9 @@ public void TestSetup() public void Check_Vote() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); // No signature @@ -99,9 +101,9 @@ public void Check_Vote() public void Check_Vote_Sameaccounts() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var accountState = snapshot.TryGet(CreateStorageKey(20, from)).GetInteroperable(); accountState.Balance = 100; snapshot.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState())); @@ -126,10 +128,10 @@ public void Check_Vote_Sameaccounts() public void Check_Vote_ChangeVote() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; //from vote to G - byte[] from = Blockchain.StandbyValidators[0].ToArray(); - var from_Account = Contract.CreateSignatureContract(Blockchain.StandbyValidators[0]).ScriptHash.ToArray(); + byte[] from = ProtocolSettings.Default.StandbyValidators[0].ToArray(); + var from_Account = Contract.CreateSignatureContract(ProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); snapshot.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); var accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.Balance = 100; @@ -157,10 +159,10 @@ public void Check_Vote_ChangeVote() public void Check_Vote_VoteToNull() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; - byte[] from = Blockchain.StandbyValidators[0].ToArray(); - var from_Account = Contract.CreateSignatureContract(Blockchain.StandbyValidators[0]).ScriptHash.ToArray(); + byte[] from = ProtocolSettings.Default.StandbyValidators[0].ToArray(); + var from_Account = Contract.CreateSignatureContract(ProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); snapshot.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); var accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.Balance = 100; @@ -187,9 +189,9 @@ public void Check_Vote_VoteToNull() public void Check_UnclaimedGas() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var unclaim = Check_UnclaimedGas(snapshot, from, persistingBlock); unclaim.Value.Should().Be(new BigInteger(0.5 * 1000 * 100000000L)); @@ -206,7 +208,7 @@ public void Check_RegisterValidator() var snapshot = _snapshot.CreateSnapshot(); var keyCount = snapshot.GetChangeSet().Count(); - var point = Blockchain.StandbyValidators[0].EncodePoint(true).Clone() as byte[]; + var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true).Clone() as byte[]; var ret = Check_RegisterValidator(snapshot, point, _persistingBlock); // Exists ret.State.Should().BeTrue(); @@ -234,7 +236,7 @@ public void Check_UnregisterCandidate() var snapshot = _snapshot.CreateSnapshot(); var keyCount = snapshot.GetChangeSet().Count(); - var point = Blockchain.StandbyValidators[0].EncodePoint(true); + var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); //without register var ret = Check_UnregisterCandidate(snapshot, point, _persistingBlock); @@ -269,7 +271,7 @@ public void Check_UnregisterCandidate() snapshot.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState())); var accountState = snapshot.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); accountState.Balance = 100; - Check_Vote(snapshot, G_Account, Blockchain.StandbyValidators[0].ToArray(), true, _persistingBlock); + Check_Vote(snapshot, G_Account, ProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); ret = Check_UnregisterCandidate(snapshot, point, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); @@ -280,10 +282,10 @@ public void Check_UnregisterCandidate() pointState.Votes.Should().Be(100); //vote fail - ret = Check_Vote(snapshot, G_Account, Blockchain.StandbyValidators[0].ToArray(), true, _persistingBlock); + ret = Check_Vote(snapshot, G_Account, ProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeFalse(); - accountState.VoteTo.Should().Be(Blockchain.StandbyValidators[0]); + accountState.VoteTo.Should().Be(ProtocolSettings.Default.StandbyValidators[0]); } [TestMethod] @@ -291,7 +293,7 @@ public void Check_GetCommittee() { var snapshot = _snapshot.CreateSnapshot(); var keyCount = snapshot.GetChangeSet().Count(); - var point = Blockchain.StandbyValidators[0].EncodePoint(true); + var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); var persistingBlock = _persistingBlock; //register with votes with 20000000 @@ -307,7 +309,7 @@ public void Check_GetCommittee() ret.Result.Should().BeTrue(); var committeemembers = NativeContract.NEO.GetCommittee(snapshot); - var defaultCommittee = Blockchain.StandbyCommittee.OrderBy(p => p).ToArray(); + var defaultCommittee = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); committeemembers.GetType().Should().Be(typeof(ECPoint[])); for (int i = 0; i < ProtocolSettings.Default.CommitteeMembersCount; i++) { @@ -317,17 +319,19 @@ public void Check_GetCommittee() //register more candidates, committee member change persistingBlock = new Block { - Index = (uint)ProtocolSettings.Default.CommitteeMembersCount, - Transactions = Array.Empty(), - ConsensusData = new ConsensusData(), - MerkleRoot = UInt256.Zero, - NextConsensus = UInt160.Zero, - PrevHash = UInt256.Zero, - Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + Header = new Header + { + Index = (uint)ProtocolSettings.Default.CommitteeMembersCount, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + }, + Transactions = Array.Empty() }; for (int i = 0; i < ProtocolSettings.Default.CommitteeMembersCount - 1; i++) { - ret = Check_RegisterValidator(snapshot, Blockchain.StandbyCommittee[i].ToArray(), persistingBlock); + ret = Check_RegisterValidator(snapshot, ProtocolSettings.Default.StandbyCommittee[i].ToArray(), persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); } @@ -339,18 +343,18 @@ public void Check_GetCommittee() committeemembers.Contains(ECCurve.Secp256r1.G).Should().BeTrue(); for (int i = 0; i < ProtocolSettings.Default.CommitteeMembersCount - 1; i++) { - committeemembers.Contains(Blockchain.StandbyCommittee[i]).Should().BeTrue(); + committeemembers.Contains(ProtocolSettings.Default.StandbyCommittee[i]).Should().BeTrue(); } - committeemembers.Contains(Blockchain.StandbyCommittee[ProtocolSettings.Default.CommitteeMembersCount - 1]).Should().BeFalse(); + committeemembers.Contains(ProtocolSettings.Default.StandbyCommittee[ProtocolSettings.Default.CommitteeMembersCount - 1]).Should().BeFalse(); } [TestMethod] public void Check_Transfer() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block() { Index = 1000 }; + var persistingBlock = new Block { Header = new Header { Index = 1000 } }; - byte[] from = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; var keyCount = snapshot.GetChangeSet().Count(); @@ -399,7 +403,7 @@ public void Check_Transfer() public void Check_BalanceOf() { var snapshot = _snapshot.CreateSnapshot(); - byte[] account = Contract.GetBFTAddress(Blockchain.StandbyValidators).ToArray(); + byte[] account = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); NativeContract.NEO.BalanceOf(snapshot, account).Should().Be(100_000_000); @@ -414,18 +418,20 @@ public void Check_CommitteeBonus() var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { - Index = 1, - Transactions = Array.Empty(), - Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, - ConsensusData = new ConsensusData(), - MerkleRoot = UInt256.Zero, - NextConsensus = UInt160.Zero, - PrevHash = UInt256.Zero + Header = new Header + { + Index = 1, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero + }, + Transactions = Array.Empty() }; Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); - var committee = Blockchain.StandbyCommittee; + var committee = ProtocolSettings.Default.StandbyCommittee; NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[0]).ScriptHash.ToArray()).Should().Be(50000000); NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[1]).ScriptHash.ToArray()).Should().Be(50000000); NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash.ToArray()).Should().Be(0); @@ -441,23 +447,11 @@ public void Check_Initialize() Check_GetCommittee(snapshot, null); } - [TestMethod] - public void Check_BadScript() - { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, Blockchain.Singleton.GetSnapshot(), _persistingBlock); - - var script = new ScriptBuilder(); - script.Emit(OpCode.NOP); - engine.LoadScript(script.ToArray()); - - Assert.ThrowsException(() => NativeContract.NEO.Invoke(engine)); - } - [TestMethod] public void TestCalculateBonus() { var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block { Index = 0 }; + var persistingBlock = new Block(); StorageKey key = CreateStorageKey(20, UInt160.Zero.ToArray()); @@ -515,9 +509,9 @@ public void TestCalculateBonus() snapshot.GetAndChange(key, () => new StorageItem(new NeoAccountState { Balance = 100, - VoteTo = Blockchain.StandbyCommittee[0] + VoteTo = ProtocolSettings.Default.StandbyCommittee[0] })); - snapshot.Add(new KeyBuilder(NativeContract.NEO.Id, 23).Add(Blockchain.StandbyCommittee[0]).AddBigEndian(uint.MaxValue - 50), new StorageItem() { Value = new BigInteger(50 * 10000L).ToByteArray() }); + snapshot.Add(new KeyBuilder(NativeContract.NEO.Id, 23).Add(ProtocolSettings.Default.StandbyCommittee[0]).AddBigEndian(uint.MaxValue - 50), new StorageItem() { Value = new BigInteger(50 * 10000L).ToByteArray() }); NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(50 * 100)); snapshot.Delete(key); } @@ -525,26 +519,23 @@ public void TestCalculateBonus() [TestMethod] public void TestGetNextBlockValidators1() { - using (ApplicationEngine engine = NativeContract.NEO.TestCall("getNextBlockValidators")) - { - var result = engine.ResultStack.Peek(); - result.GetType().Should().Be(typeof(VM.Types.Array)); - ((VM.Types.Array)result).Count.Should().Be(7); - ((VM.Types.Array)result)[0].GetSpan().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); - ((VM.Types.Array)result)[1].GetSpan().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); - ((VM.Types.Array)result)[2].GetSpan().ToHexString().Should().Be("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e"); - ((VM.Types.Array)result)[3].GetSpan().ToHexString().Should().Be("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"); - ((VM.Types.Array)result)[4].GetSpan().ToHexString().Should().Be("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a"); - ((VM.Types.Array)result)[5].GetSpan().ToHexString().Should().Be("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554"); - ((VM.Types.Array)result)[6].GetSpan().ToHexString().Should().Be("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093"); - } + var snapshot = TestBlockchain.GetTestSnapshot(); + var result = (VM.Types.Array)NativeContract.NEO.Call(snapshot, "getNextBlockValidators"); + result.Count.Should().Be(7); + result[0].GetSpan().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); + result[1].GetSpan().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); + result[2].GetSpan().ToHexString().Should().Be("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e"); + result[3].GetSpan().ToHexString().Should().Be("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"); + result[4].GetSpan().ToHexString().Should().Be("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a"); + result[5].GetSpan().ToHexString().Should().Be("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554"); + result[6].GetSpan().ToHexString().Should().Be("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093"); } [TestMethod] public void TestGetNextBlockValidators2() { var snapshot = _snapshot.CreateSnapshot(); - var result = NativeContract.NEO.GetNextBlockValidators(snapshot); + var result = NativeContract.NEO.GetNextBlockValidators(snapshot, 7); result.Length.Should().Be(7); result[0].ToArray().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); result[1].ToArray().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); @@ -558,8 +549,8 @@ public void TestGetNextBlockValidators2() [TestMethod] public void TestGetCandidates1() { - using ApplicationEngine engine = NativeContract.NEO.TestCall("getCandidates"); - var array = engine.ResultStack.Pop(); + var snapshot = TestBlockchain.GetTestSnapshot(); + var array = (VM.Types.Array)NativeContract.NEO.Call(snapshot, "getCandidates"); array.Count.Should().Be(0); } @@ -596,13 +587,15 @@ public void TestCheckCandidate() // Pre-persist var persistingBlock = new Block { - Index = 21, - ConsensusData = new ConsensusData(), - Transactions = Array.Empty(), - Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, - MerkleRoot = UInt256.Zero, - NextConsensus = UInt160.Zero, - PrevHash = UInt256.Zero + Header = new Header + { + Index = 21, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero + }, + Transactions = Array.Empty() }; Check_OnPersist(snapshot, persistingBlock).Should().BeTrue(); @@ -628,40 +621,37 @@ public void TestCheckCandidate() [TestMethod] public void TestGetCommittee() { - using (ApplicationEngine engine = NativeContract.NEO.TestCall("getCommittee")) - { - var result = engine.ResultStack.Peek(); - result.GetType().Should().Be(typeof(VM.Types.Array)); - ((VM.Types.Array)result).Count.Should().Be(21); - ((VM.Types.Array)result)[0].GetSpan().ToHexString().Should().Be("020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639"); - ((VM.Types.Array)result)[1].GetSpan().ToHexString().Should().Be("03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0"); - ((VM.Types.Array)result)[2].GetSpan().ToHexString().Should().Be("0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30"); - ((VM.Types.Array)result)[3].GetSpan().ToHexString().Should().Be("0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d"); - ((VM.Types.Array)result)[4].GetSpan().ToHexString().Should().Be("023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe"); - ((VM.Types.Array)result)[5].GetSpan().ToHexString().Should().Be("03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0"); - ((VM.Types.Array)result)[6].GetSpan().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); - ((VM.Types.Array)result)[7].GetSpan().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); - ((VM.Types.Array)result)[8].GetSpan().ToHexString().Should().Be("02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad"); - ((VM.Types.Array)result)[9].GetSpan().ToHexString().Should().Be("03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379"); - ((VM.Types.Array)result)[10].GetSpan().ToHexString().Should().Be("0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654"); - ((VM.Types.Array)result)[11].GetSpan().ToHexString().Should().Be("02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62"); - ((VM.Types.Array)result)[12].GetSpan().ToHexString().Should().Be("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e"); - ((VM.Types.Array)result)[13].GetSpan().ToHexString().Should().Be("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"); - ((VM.Types.Array)result)[14].GetSpan().ToHexString().Should().Be("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a"); - ((VM.Types.Array)result)[15].GetSpan().ToHexString().Should().Be("03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050"); - ((VM.Types.Array)result)[16].GetSpan().ToHexString().Should().Be("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554"); - ((VM.Types.Array)result)[17].GetSpan().ToHexString().Should().Be("02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a"); - ((VM.Types.Array)result)[18].GetSpan().ToHexString().Should().Be("03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc"); - ((VM.Types.Array)result)[19].GetSpan().ToHexString().Should().Be("03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde"); - ((VM.Types.Array)result)[20].GetSpan().ToHexString().Should().Be("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093"); - } + var snapshot = TestBlockchain.GetTestSnapshot(); + var result = (VM.Types.Array)NativeContract.NEO.Call(snapshot, "getCommittee"); + result.Count.Should().Be(21); + result[0].GetSpan().ToHexString().Should().Be("020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639"); + result[1].GetSpan().ToHexString().Should().Be("03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0"); + result[2].GetSpan().ToHexString().Should().Be("0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30"); + result[3].GetSpan().ToHexString().Should().Be("0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d"); + result[4].GetSpan().ToHexString().Should().Be("023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe"); + result[5].GetSpan().ToHexString().Should().Be("03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0"); + result[6].GetSpan().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); + result[7].GetSpan().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); + result[8].GetSpan().ToHexString().Should().Be("02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad"); + result[9].GetSpan().ToHexString().Should().Be("03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379"); + result[10].GetSpan().ToHexString().Should().Be("0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654"); + result[11].GetSpan().ToHexString().Should().Be("02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62"); + result[12].GetSpan().ToHexString().Should().Be("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e"); + result[13].GetSpan().ToHexString().Should().Be("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"); + result[14].GetSpan().ToHexString().Should().Be("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a"); + result[15].GetSpan().ToHexString().Should().Be("03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050"); + result[16].GetSpan().ToHexString().Should().Be("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554"); + result[17].GetSpan().ToHexString().Should().Be("02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a"); + result[18].GetSpan().ToHexString().Should().Be("03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc"); + result[19].GetSpan().ToHexString().Should().Be("03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde"); + result[20].GetSpan().ToHexString().Should().Be("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093"); } [TestMethod] public void TestGetValidators() { var snapshot = _snapshot.CreateSnapshot(); - var result = NativeContract.NEO.ComputeNextBlockValidators(snapshot); + var result = NativeContract.NEO.ComputeNextBlockValidators(snapshot, ProtocolSettings.Default); result[0].ToArray().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); result[1].ToArray().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); result[2].ToArray().ToHexString().Should().Be("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e"); @@ -699,13 +689,13 @@ public void TestEconomicParameter() { const byte Prefix_CurrentBlock = 12; var snapshot = _snapshot.CreateSnapshot(); - var persistingBlock = new Block { Index = 0 }; + var persistingBlock = new Block { Header = new Header() }; (BigInteger, bool) result = Check_GetGasPerBlock(snapshot, persistingBlock); result.Item2.Should().BeTrue(); result.Item1.Should().Be(5 * NativeContract.GAS.Factor); - persistingBlock = new Block { Index = 10 }; + persistingBlock = new Block { Header = new Header { Index = 10 } }; (VM.Types.Boolean, bool) result1 = Check_SetGasPerBlock(snapshot, 10 * NativeContract.GAS.Factor, persistingBlock); result1.Item2.Should().BeTrue(); result1.Item1.GetBoolean().Should().BeTrue(); @@ -733,7 +723,7 @@ public void TestClaimGas() // Initialize block snapshot.Add(CreateStorageKey(1), new StorageItem(new BigInteger(30000000))); - ECPoint[] standbyCommittee = Blockchain.StandbyCommittee.OrderBy(p => p).ToArray(); + ECPoint[] standbyCommittee = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); CachedCommittee cachedCommittee = new CachedCommittee(); for (var i = 0; i < ProtocolSettings.Default.CommitteeMembersCount; i++) { @@ -752,17 +742,19 @@ public void TestClaimGas() var persistingBlock = new Block { - Index = 0, - Transactions = Array.Empty(), - Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, - ConsensusData = new ConsensusData(), - MerkleRoot = UInt256.Zero, - NextConsensus = UInt160.Zero, - PrevHash = UInt256.Zero + Header = new Header + { + Index = 0, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero + }, + Transactions = Array.Empty() }; Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); - var committee = Blockchain.StandbyCommittee.OrderBy(p => p).ToArray(); + var committee = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); var accountA = committee[0]; var accountB = committee[ProtocolSettings.Default.CommitteeMembersCount - 1]; NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(accountA).ScriptHash).Should().Be(0); @@ -776,13 +768,15 @@ public void TestClaimGas() persistingBlock = new Block { - Index = 1, - Transactions = Array.Empty(), - Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, - ConsensusData = new ConsensusData(), - MerkleRoot = UInt256.Zero, - NextConsensus = UInt160.Zero, - PrevHash = UInt256.Zero + Header = new Header + { + Index = 1, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero + }, + Transactions = Array.Empty() }; Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); @@ -795,17 +789,19 @@ public void TestClaimGas() persistingBlock = new Block { - Index = 21, - Transactions = Array.Empty(), - Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, - ConsensusData = new ConsensusData(), - MerkleRoot = UInt256.Zero, - NextConsensus = UInt160.Zero, - PrevHash = UInt256.Zero + Header = new Header + { + Index = 21, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero + }, + Transactions = Array.Empty() }; Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); - accountA = Blockchain.StandbyCommittee.OrderBy(p => p).ToArray()[2]; + accountA = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray()[2]; NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash).Should().Be(0); storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[2]).AddBigEndian(22)); @@ -868,7 +864,7 @@ public void TestVote() internal (bool State, bool Result) Transfer4TesingOnBalanceChanging(BigInteger amount, bool addVotes) { var snapshot = _snapshot.CreateSnapshot(); - var engine = ApplicationEngine.Create(TriggerType.Application, Blockchain.GenesisBlock, snapshot, Blockchain.GenesisBlock); + var engine = ApplicationEngine.Create(TriggerType.Application, TestBlockchain.TheNeoSystem.GenesisBlock, snapshot, TestBlockchain.TheNeoSystem.GenesisBlock, settings: TestBlockchain.TheNeoSystem.Settings); ScriptBuilder sb = new ScriptBuilder(); var tmp = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); UInt160 from = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot)[0]; @@ -901,7 +897,7 @@ internal static bool Check_OnPersist(DataCache snapshot, Block persistingBlock) { var script = new ScriptBuilder(); script.EmitSysCall(ApplicationEngine.System_Contract_NativeOnPersist); - var engine = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, persistingBlock); + var engine = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); return engine.Execute() == VMState.HALT; @@ -911,7 +907,7 @@ internal static bool Check_PostPersist(DataCache snapshot, Block persistingBlock { using var script = new ScriptBuilder(); script.EmitSysCall(ApplicationEngine.System_Contract_NativePostPersist); - using var engine = ApplicationEngine.Create(TriggerType.PostPersist, null, snapshot, persistingBlock); + using var engine = ApplicationEngine.Create(TriggerType.PostPersist, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); return engine.Execute() == VMState.HALT; @@ -919,11 +915,10 @@ internal static bool Check_PostPersist(DataCache snapshot, Block persistingBlock internal static (BigInteger Value, bool State) Check_GetGasPerBlock(DataCache snapshot, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock); - engine.LoadScript(NativeContract.NEO.Script, configureState: p => p.ScriptHash = NativeContract.NEO.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush("getGasPerBlock"); + script.EmitDynamicCall(NativeContract.NEO.Hash, "getGasPerBlock"); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -940,13 +935,10 @@ internal static (BigInteger Value, bool State) Check_GetGasPerBlock(DataCache sn internal static (VM.Types.Boolean Value, bool State) Check_SetGasPerBlock(DataCache snapshot, BigInteger gasPerBlock, Block persistingBlock) { UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot); - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NEO.Script, configureState: p => p.ScriptHash = NativeContract.NEO.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); var script = new ScriptBuilder(); - script.EmitPush(gasPerBlock); - script.EmitPush("setGasPerBlock"); + script.EmitDynamicCall(NativeContract.NEO.Hash, "setGasPerBlock", gasPerBlock); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -960,18 +952,10 @@ internal static (VM.Types.Boolean Value, bool State) Check_SetGasPerBlock(DataCa internal static (bool State, bool Result) Check_Vote(DataCache snapshot, byte[] account, byte[] pubkey, bool signAccount, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(signAccount ? new UInt160(account) : UInt160.Zero), snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NEO.Script, configureState: p => p.ScriptHash = NativeContract.NEO.Hash); + new Nep17NativeContractExtensions.ManualWitness(signAccount ? new UInt160(account) : UInt160.Zero), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - - if (pubkey is null) - script.Emit(OpCode.PUSHNULL); - else - script.EmitPush(pubkey); - script.EmitPush(account); - script.EmitPush("vote"); + script.EmitDynamicCall(NativeContract.NEO.Hash, "vote", account, pubkey); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -988,13 +972,10 @@ internal static (bool State, bool Result) Check_Vote(DataCache snapshot, byte[] internal static (bool State, bool Result) Check_RegisterValidator(DataCache snapshot, byte[] pubkey, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), snapshot, persistingBlock, 1100_00000000); - - engine.LoadScript(NativeContract.NEO.Script, configureState: p => p.ScriptHash = NativeContract.NEO.Hash); + new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); using var script = new ScriptBuilder(); - script.EmitPush(pubkey); - script.EmitPush("registerCandidate"); + script.EmitDynamicCall(NativeContract.NEO.Hash, "registerCandidate", pubkey); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -1010,12 +991,10 @@ internal static (bool State, bool Result) Check_RegisterValidator(DataCache snap internal static ECPoint[] Check_GetCommittee(DataCache snapshot, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NEO.Script, configureState: p => p.ScriptHash = NativeContract.NEO.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush("getCommittee"); + script.EmitDynamicCall(NativeContract.NEO.Hash, "getCommittee"); engine.LoadScript(script.ToArray()); engine.Execute().Should().Be(VMState.HALT); @@ -1028,14 +1007,10 @@ internal static ECPoint[] Check_GetCommittee(DataCache snapshot, Block persistin internal static (BigInteger Value, bool State) Check_UnclaimedGas(DataCache snapshot, byte[] address, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NEO.Script, configureState: p => p.ScriptHash = NativeContract.NEO.Hash); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush(persistingBlock.Index); - script.EmitPush(address); - script.EmitPush("unclaimedGas"); + script.EmitDynamicCall(NativeContract.NEO.Hash, "unclaimedGas", address, persistingBlock.Index); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) @@ -1055,12 +1030,11 @@ internal static void CheckValidator(ECPoint eCPoint, DataCache.Trackable trackab st.Should().Be(0); trackable.Key.Key.Should().BeEquivalentTo(new byte[] { 33 }.Concat(eCPoint.EncodePoint(true))); - trackable.Item.IsConstant.Should().Be(false); } internal static void CheckBalance(byte[] account, DataCache.Trackable trackable, BigInteger balance, BigInteger height, ECPoint voteTo) { - var st = (VM.Types.Struct)BinarySerializer.Deserialize(trackable.Item.Value, 16, 32); + var st = (VM.Types.Struct)BinarySerializer.Deserialize(trackable.Item.Value, 16); st.Count.Should().Be(3); st.Select(u => u.GetType()).ToArray().Should().BeEquivalentTo(new Type[] { typeof(VM.Types.Integer), typeof(VM.Types.Integer), typeof(VM.Types.ByteString) }); // Balance @@ -1070,7 +1044,6 @@ internal static void CheckBalance(byte[] account, DataCache.Trackable trackable, st[2].GetSpan().AsSerializable().Should().BeEquivalentTo(voteTo); // Votes trackable.Key.Key.Should().BeEquivalentTo(new byte[] { 20 }.Concat(account)); - trackable.Item.IsConstant.Should().Be(false); } internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) @@ -1088,13 +1061,10 @@ internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) internal static (bool State, bool Result) Check_UnregisterCandidate(DataCache snapshot, byte[] pubkey, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), snapshot, persistingBlock); - - engine.LoadScript(NativeContract.NEO.Script, configureState: p => p.ScriptHash = NativeContract.NEO.Hash); + new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); - script.EmitPush(pubkey); - script.EmitPush("unregisterCandidate"); + script.EmitDynamicCall(NativeContract.NEO.Hash, "unregisterCandidate", pubkey); engine.LoadScript(script.ToArray()); if (engine.Execute() == VMState.FAULT) diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs b/tests/neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs index ede06e3665..370f647bb3 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs @@ -1,7 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -20,10 +19,9 @@ public class UT_PolicyContract [TestInitialize] public void TestSetup() { - TestBlockchain.InitializeMockNeoSystem(); - _snapshot = Blockchain.Singleton.GetSnapshot(); + _snapshot = TestBlockchain.GetTestSnapshot(); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, _snapshot, new Block(), 0); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, _snapshot, new Block { Header = new Header() }, settings: TestBlockchain.TheNeoSystem.Settings, gas: 0); NativeContract.ContractManagement.OnPersist(engine); } @@ -32,145 +30,11 @@ public void Check_Default() { var snapshot = _snapshot.CreateSnapshot(); - var ret = NativeContract.Policy.Call(snapshot, "getMaxTransactionsPerBlock"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(512); - - ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(1024 * 256); - - ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSystemFee"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(9000 * 100000000L); - - ret = NativeContract.Policy.Call(snapshot, "getFeePerByte"); + var ret = NativeContract.Policy.Call(snapshot, "getFeePerByte"); ret.Should().BeOfType(); ret.GetInteger().Should().Be(1000); } - [TestMethod] - public void Check_SetMaxBlockSize() - { - var snapshot = _snapshot.CreateSnapshot(); - - // Fake blockchain - - Block block = new Block() { Index = 1000, PrevHash = UInt256.Zero }; - UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot); - - // Without signature - - Assert.ThrowsException(() => - { - NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(null), block, - "setMaxBlockSize", new ContractParameter(ContractParameterType.Integer) { Value = 1024 }); - }); - - var ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(1024 * 256); - - // More than expected - - Assert.ThrowsException(() => - { - NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, - "setMaxBlockSize", new ContractParameter(ContractParameterType.Integer) { Value = Neo.Network.P2P.Message.PayloadMaxSize + 1 }); - }); - - ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(1024 * 256); - - // With signature - - ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, - "setMaxBlockSize", new ContractParameter(ContractParameterType.Integer) { Value = 1024 }); - ret.Should().BeNull(); - - ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(1024); - } - - [TestMethod] - public void Check_SetMaxBlockSystemFee() - { - var snapshot = _snapshot.CreateSnapshot(); - - // Fake blockchain - - Block block = new Block() { Index = 1000, PrevHash = UInt256.Zero }; - UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot); - - // Without signature - - Assert.ThrowsException(() => - { - NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(null), block, - "setMaxBlockSystemFee", new ContractParameter(ContractParameterType.Integer) { Value = 1024 * (long)NativeContract.GAS.Factor }); - }); - - var ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSystemFee"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(9000 * (long)NativeContract.GAS.Factor); - - // Less than expected - - Assert.ThrowsException(() => - { - NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, - "setMaxBlockSystemFee", new ContractParameter(ContractParameterType.Integer) { Value = -1000 }); - }); - - ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSystemFee"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(9000 * (long)NativeContract.GAS.Factor); - - // With signature - - ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, - "setMaxBlockSystemFee", new ContractParameter(ContractParameterType.Integer) { Value = 1024 * (long)NativeContract.GAS.Factor }); - ret.Should().BeNull(); - - ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSystemFee"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(1024 * (long)NativeContract.GAS.Factor); - } - - [TestMethod] - public void Check_SetMaxTransactionsPerBlock() - { - var snapshot = _snapshot.CreateSnapshot(); - - // Fake blockchain - - Block block = new Block() { Index = 1000, PrevHash = UInt256.Zero }; - - // Without signature - - Assert.ThrowsException(() => - { - NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(), block, - "setMaxTransactionsPerBlock", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); - }); - - var ret = NativeContract.Policy.Call(snapshot, "getMaxTransactionsPerBlock"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(512); - - // With signature - - ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(NativeContract.NEO.GetCommitteeAddress(snapshot)), block, - "setMaxTransactionsPerBlock", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); - ret.Should().BeNull(); - - ret = NativeContract.Policy.Call(snapshot, "getMaxTransactionsPerBlock"); - ret.Should().BeOfType(); - ret.GetInteger().Should().Be(1); - } - [TestMethod] public void Check_SetFeePerByte() { @@ -178,7 +42,14 @@ public void Check_SetFeePerByte() // Fake blockchain - Block block = new Block() { Index = 1000, PrevHash = UInt256.Zero }; + Block block = new Block() + { + Header = new Header + { + Index = 1000, + PrevHash = UInt256.Zero + } + }; // Without signature @@ -196,7 +67,7 @@ public void Check_SetFeePerByte() UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot); ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, "setFeePerByte", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); - ret.Should().BeNull(); + ret.IsNull.Should().BeTrue(); ret = NativeContract.Policy.Call(snapshot, "getFeePerByte"); ret.Should().BeOfType(); @@ -210,7 +81,14 @@ public void Check_SetBaseExecFee() // Fake blockchain - Block block = new Block() { Index = 1000, PrevHash = UInt256.Zero }; + Block block = new Block() + { + Header = new Header + { + Index = 1000, + PrevHash = UInt256.Zero + } + }; // Without signature @@ -239,7 +117,7 @@ public void Check_SetBaseExecFee() // Proper set ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, "setExecFeeFactor", new ContractParameter(ContractParameterType.Integer) { Value = 50 }); - ret.Should().BeNull(); + ret.IsNull.Should().BeTrue(); ret = NativeContract.Policy.Call(snapshot, "getExecFeeFactor"); ret.Should().BeOfType(); @@ -253,7 +131,14 @@ public void Check_SetStoragePrice() // Fake blockchain - Block block = new Block() { Index = 1000, PrevHash = UInt256.Zero }; + Block block = new Block() + { + Header = new Header + { + Index = 1000, + PrevHash = UInt256.Zero + } + }; // Without signature @@ -282,7 +167,7 @@ public void Check_SetStoragePrice() // Proper set ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, "setStoragePrice", new ContractParameter(ContractParameterType.Integer) { Value = 300300 }); - ret.Should().BeNull(); + ret.IsNull.Should().BeTrue(); ret = NativeContract.Policy.Call(snapshot, "getStoragePrice"); ret.Should().BeOfType(); @@ -296,7 +181,14 @@ public void Check_BlockAccount() // Fake blockchain - Block block = new Block() { Index = 1000, PrevHash = UInt256.Zero }; + Block block = new Block() + { + Header = new Header + { + Index = 1000, + PrevHash = UInt256.Zero + } + }; // Without signature @@ -345,7 +237,14 @@ public void Check_Block_UnblockAccount() // Fake blockchain - Block block = new Block() { Index = 1000, PrevHash = UInt256.Zero }; + Block block = new Block() + { + Header = new Header + { + Index = 1000, + PrevHash = UInt256.Zero + } + }; UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot); // Block without signature diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs b/tests/neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs index d4088258f8..bcceb7f6fd 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs @@ -2,7 +2,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -21,8 +20,7 @@ public class UT_RoleManagement [TestInitialize] public void TestSetup() { - TestBlockchain.InitializeMockNeoSystem(); - _snapshot = Blockchain.Singleton.GetSnapshot(); + _snapshot = TestBlockchain.GetTestSnapshot(); } [TestMethod] @@ -30,11 +28,11 @@ public void TestSetAndGet() { var snapshot1 = _snapshot.CreateSnapshot(); UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot1); - ECPoint[] validators = NativeContract.NEO.ComputeNextBlockValidators(snapshot1); + ECPoint[] validators = NativeContract.NEO.ComputeNextBlockValidators(snapshot1, ProtocolSettings.Default); var ret = NativeContract.RoleManagement.Call( snapshot1, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), - new Block(), + new Block { Header = new Header() }, "designateAsRole", new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger((int)Role.StateValidator) }, new ContractParameter(ContractParameterType.Array) { Value = validators.Select(p => new ContractParameter(ContractParameterType.ByteArray) { Value = p.ToArray() }).ToList() } diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_StdLib.cs b/tests/neo.UnitTests/SmartContract/Native/UT_StdLib.cs new file mode 100644 index 0000000000..29b1cd6032 --- /dev/null +++ b/tests/neo.UnitTests/SmartContract/Native/UT_StdLib.cs @@ -0,0 +1,206 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using System.Collections.Generic; +using System.Numerics; + +namespace Neo.UnitTests.SmartContract.Native +{ + [TestClass] + public class UT_StdLib + { + [TestMethod] + public void TestBinary() + { + var data = System.Array.Empty(); + + CollectionAssert.AreEqual(data, StdLib.Base64Decode(StdLib.Base64Encode(data))); + CollectionAssert.AreEqual(data, StdLib.Base58Decode(StdLib.Base58Encode(data))); + + data = new byte[] { 1, 2, 3 }; + + CollectionAssert.AreEqual(data, StdLib.Base64Decode(StdLib.Base64Encode(data))); + CollectionAssert.AreEqual(data, StdLib.Base58Decode(StdLib.Base58Encode(data))); + Assert.AreEqual("AQIDBA==", StdLib.Base64Encode(new byte[] { 1, 2, 3, 4 })); + Assert.AreEqual("2VfUX", StdLib.Base58Encode(new byte[] { 1, 2, 3, 4 })); + } + + [TestMethod] + public void TestItoaAtoi() + { + Assert.AreEqual("1", StdLib.Itoa(BigInteger.One, 10)); + Assert.AreEqual("1", StdLib.Itoa(BigInteger.One, 16)); + Assert.AreEqual("-1", StdLib.Itoa(BigInteger.MinusOne, 10)); + Assert.AreEqual("f", StdLib.Itoa(BigInteger.MinusOne, 16)); + Assert.AreEqual("3b9aca00", StdLib.Itoa(1_000_000_000, 16)); + Assert.AreEqual(-1, StdLib.Atoi("-1", 10)); + Assert.AreEqual(1, StdLib.Atoi("+1", 10)); + Assert.AreEqual(-1, StdLib.Atoi("ff", 16)); + Assert.AreEqual(-1, StdLib.Atoi("FF", 16)); + Assert.ThrowsException(() => StdLib.Atoi("a", 10)); + Assert.ThrowsException(() => StdLib.Atoi("g", 16)); + Assert.ThrowsException(() => StdLib.Atoi("a", 11)); + } + + [TestMethod] + public void Json_Deserialize() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + + // Good + + using (var script = new ScriptBuilder()) + { + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonDeserialize", "123"); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonDeserialize", "null"); + + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings)) + { + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(2, engine.ResultStack.Count); + + engine.ResultStack.Pop(); + Assert.IsTrue(engine.ResultStack.Pop().GetInteger() == 123); + } + } + + // Error 1 - Wrong Json + + using (var script = new ScriptBuilder()) + { + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonDeserialize", "***"); + + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings)) + { + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.FAULT); + Assert.AreEqual(0, engine.ResultStack.Count); + } + } + + // Error 2 - No decimals + + using (var script = new ScriptBuilder()) + { + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonDeserialize", "123.45"); + + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings)) + { + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.FAULT); + Assert.AreEqual(0, engine.ResultStack.Count); + } + } + } + + [TestMethod] + public void Json_Serialize() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + + // Good + + using (var script = new ScriptBuilder()) + { + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonSerialize", 5); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonSerialize", true); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonSerialize", "test"); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonSerialize", new object[] { null }); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonSerialize", new ContractParameter(ContractParameterType.Map) + { + Value = new List>() { + { new KeyValuePair( + new ContractParameter(ContractParameterType.String){ Value="key" }, + new ContractParameter(ContractParameterType.String){ Value= "value" }) + } + } + }); + + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings)) + { + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(5, engine.ResultStack.Count); + + Assert.IsTrue(engine.ResultStack.Pop().GetString() == "{\"key\":\"value\"}"); + Assert.IsTrue(engine.ResultStack.Pop().GetString() == "null"); + Assert.IsTrue(engine.ResultStack.Pop().GetString() == "\"test\""); + Assert.IsTrue(engine.ResultStack.Pop().GetString() == "1"); + Assert.IsTrue(engine.ResultStack.Pop().GetString() == "5"); + } + } + + // Error + + using (var script = new ScriptBuilder()) + { + script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonSerialize"); + + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings)) + { + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.FAULT); + Assert.AreEqual(0, engine.ResultStack.Count); + } + } + } + + [TestMethod] + public void TestRuntime_Serialize() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + + // Good + + using (var script = new ScriptBuilder()) + { + script.EmitDynamicCall(NativeContract.StdLib.Hash, "serialize", 100); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "serialize", "test"); + + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings)) + { + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(2, engine.ResultStack.Count); + + Assert.AreEqual(engine.ResultStack.Pop().GetSpan().ToHexString(), "280474657374"); + Assert.AreEqual(engine.ResultStack.Pop().GetSpan().ToHexString(), "210164"); + } + } + } + + [TestMethod] + public void TestRuntime_Deserialize() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + + // Good + + using (var script = new ScriptBuilder()) + { + script.EmitDynamicCall(NativeContract.StdLib.Hash, "deserialize", "280474657374".HexToBytes()); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "deserialize", "210164".HexToBytes()); + + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings)) + { + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(2, engine.ResultStack.Count); + + Assert.AreEqual(engine.ResultStack.Pop().GetInteger(), 100); + Assert.AreEqual(engine.ResultStack.Pop().GetString(), "test"); + } + } + } + } +} diff --git a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Contract.cs b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Contract.cs new file mode 100644 index 0000000000..939cc282a2 --- /dev/null +++ b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Contract.cs @@ -0,0 +1,50 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.VM; +using System.Linq; + +namespace Neo.UnitTests.SmartContract +{ + public partial class UT_ApplicationEngine + { + [TestMethod] + public void TestCreateStandardAccount() + { + var settings = ProtocolSettings.Default; + using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); + + using var script = new ScriptBuilder(); + script.EmitSysCall(ApplicationEngine.System_Contract_CreateStandardAccount, settings.StandbyCommittee[0].EncodePoint(true)); + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(VMState.HALT, engine.Execute()); + + var result = engine.ResultStack.Pop(); + new UInt160(result.GetSpan()).Should().Be(Contract.CreateSignatureRedeemScript(settings.StandbyCommittee[0]).ToScriptHash()); + } + + [TestMethod] + public void TestCreateStandardMultisigAccount() + { + var settings = ProtocolSettings.Default; + using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); + + using var script = new ScriptBuilder(); + script.EmitSysCall(ApplicationEngine.System_Contract_CreateMultisigAccount, new object[] + { + 2, + 3, + settings.StandbyCommittee[0].EncodePoint(true), + settings.StandbyCommittee[1].EncodePoint(true), + settings.StandbyCommittee[2].EncodePoint(true) + }); + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(VMState.HALT, engine.Execute()); + + var result = engine.ResultStack.Pop(); + new UInt160(result.GetSpan()).Should().Be(Contract.CreateMultiSigRedeemScript(2, settings.StandbyCommittee.Take(3).ToArray()).ToScriptHash()); + } + } +} diff --git a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs index be99c15830..66f4f87e64 100644 --- a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -1,65 +1,20 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Ledger; using Neo.SmartContract; using Neo.VM.Types; -using System.Numerics; namespace Neo.UnitTests.SmartContract { [TestClass] - public class UT_ApplicationEngine + public partial class UT_ApplicationEngine { private string eventName = null; - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - - [TestMethod] - public void TestBinary() - { - using var snapshot = Blockchain.Singleton.GetSnapshot(); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); - - var data = new byte[0]; - CollectionAssert.AreEqual(data, engine.Base64Decode(engine.Base64Encode(data))); - - CollectionAssert.AreEqual(data, engine.Base58Decode(engine.Base58Encode(data))); - - data = new byte[] { 1, 2, 3 }; - CollectionAssert.AreEqual(data, engine.Base64Decode(engine.Base64Encode(data))); - - CollectionAssert.AreEqual(data, engine.Base58Decode(engine.Base58Encode(data))); - - Assert.AreEqual("AQIDBA==", engine.Base64Encode(new byte[] { 1, 2, 3, 4 })); - - Assert.AreEqual("2VfUX", engine.Base58Encode(new byte[] { 1, 2, 3, 4 })); - } - - [TestMethod] - public void TestItoaAtoi() - { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, null); - - Assert.AreEqual("1", engine.Itoa(BigInteger.One, 10)); - Assert.AreEqual("1", engine.Itoa(BigInteger.One, 16)); - Assert.AreEqual("-1", engine.Itoa(BigInteger.MinusOne, 10)); - Assert.AreEqual("f", engine.Itoa(BigInteger.MinusOne, 16)); - Assert.AreEqual(-1, engine.Atoi("-1", 10)); - Assert.AreEqual(1, engine.Atoi("+1", 10)); - Assert.AreEqual(-1, engine.Atoi("ff", 16)); - Assert.ThrowsException(() => engine.Atoi("a", 10)); - Assert.ThrowsException(() => engine.Atoi("a", 11)); - } - [TestMethod] public void TestNotify() { - using var snapshot = Blockchain.Singleton.GetSnapshot(); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + var snapshot = TestBlockchain.GetTestSnapshot(); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); ApplicationEngine.Notify += Test_Notify1; const string notifyEvent = "TestEvent"; @@ -93,11 +48,11 @@ private void Test_Notify2(object sender, NotifyEventArgs e) [TestMethod] public void TestCreateDummyBlock() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); byte[] SyscallSystemRuntimeCheckWitnessHash = new byte[] { 0x68, 0xf8, 0x27, 0xec, 0x8c }; ApplicationEngine engine = ApplicationEngine.Run(SyscallSystemRuntimeCheckWitnessHash, snapshot); engine.PersistingBlock.Version.Should().Be(0); - engine.PersistingBlock.PrevHash.Should().Be(Blockchain.GenesisBlock.Hash); + engine.PersistingBlock.PrevHash.Should().Be(TestBlockchain.TheNeoSystem.GenesisBlock.Hash); engine.PersistingBlock.MerkleRoot.Should().Be(new UInt256()); } } diff --git a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs index ce0cbf9677..fb1b8f6e4f 100644 --- a/tests/neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs +++ b/tests/neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs @@ -28,14 +28,14 @@ public void TestSetAppEngineProvider() var provider = new TestProvider(); ApplicationEngine.SetApplicationEngineProvider(provider).Should().BeTrue(); - using var appEngine = ApplicationEngine.Create(TriggerType.Application, null, null, null, 0); + using var appEngine = ApplicationEngine.Create(TriggerType.Application, null, null, gas: 0, settings: TestBlockchain.TheNeoSystem.Settings); (appEngine is TestEngine).Should().BeTrue(); } [TestMethod] public void TestDefaultAppEngineProvider() { - using var appEngine = ApplicationEngine.Create(TriggerType.Application, null, null, null, 0); + using var appEngine = ApplicationEngine.Create(TriggerType.Application, null, null, gas: 0, settings: TestBlockchain.TheNeoSystem.Settings); (appEngine is ApplicationEngine).Should().BeTrue(); } @@ -63,16 +63,16 @@ public void TestCanResetAppEngineProviderTwice() class TestProvider : IApplicationEngineProvider { - public ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, long gas) + public ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas) { - return new TestEngine(trigger, container, snapshot, persistingBlock, gas); + return new TestEngine(trigger, container, snapshot, persistingBlock, settings, gas); } } class TestEngine : ApplicationEngine { - public TestEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, long gas) - : base(trigger, container, snapshot, persistingBlock, gas) + public TestEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas) + : base(trigger, container, snapshot, persistingBlock, settings, gas) { } } diff --git a/tests/neo.UnitTests/SmartContract/UT_BinarySerializer.cs b/tests/neo.UnitTests/SmartContract/UT_BinarySerializer.cs index e5c0b0063d..d7749f499e 100644 --- a/tests/neo.UnitTests/SmartContract/UT_BinarySerializer.cs +++ b/tests/neo.UnitTests/SmartContract/UT_BinarySerializer.cs @@ -78,41 +78,41 @@ public void TestDeserializeStackItem() { StackItem stackItem1 = new ByteString(new byte[5]); byte[] byteArray1 = BinarySerializer.Serialize(stackItem1, MaxItemSize); - StackItem result1 = BinarySerializer.Deserialize(byteArray1, 2048, (uint)byteArray1.Length); + StackItem result1 = BinarySerializer.Deserialize(byteArray1, 2048); Assert.AreEqual(stackItem1, result1); StackItem stackItem2 = new VM.Types.Boolean(true); byte[] byteArray2 = BinarySerializer.Serialize(stackItem2, MaxItemSize); - StackItem result2 = BinarySerializer.Deserialize(byteArray2, 2048, (uint)byteArray2.Length); + StackItem result2 = BinarySerializer.Deserialize(byteArray2, 2048); Assert.AreEqual(stackItem2, result2); StackItem stackItem3 = new Integer(1); byte[] byteArray3 = BinarySerializer.Serialize(stackItem3, MaxItemSize); - StackItem result3 = BinarySerializer.Deserialize(byteArray3, 2048, (uint)byteArray3.Length); + StackItem result3 = BinarySerializer.Deserialize(byteArray3, 2048); Assert.AreEqual(stackItem3, result3); byte[] byteArray4 = BinarySerializer.Serialize(1, MaxItemSize); byteArray4[0] = 0x40; - Action action4 = () => BinarySerializer.Deserialize(byteArray4, 2048, (uint)byteArray4.Length); + Action action4 = () => BinarySerializer.Deserialize(byteArray4, 2048); action4.Should().Throw(); List list5 = new List { 1 }; StackItem stackItem52 = new VM.Types.Array(list5); byte[] byteArray5 = BinarySerializer.Serialize(stackItem52, MaxItemSize); - StackItem result5 = BinarySerializer.Deserialize(byteArray5, 2048, (uint)byteArray5.Length); + StackItem result5 = BinarySerializer.Deserialize(byteArray5, 2048); Assert.AreEqual(((VM.Types.Array)stackItem52).Count, ((VM.Types.Array)result5).Count); Assert.AreEqual(((VM.Types.Array)stackItem52).GetEnumerator().Current, ((VM.Types.Array)result5).GetEnumerator().Current); List list6 = new List { 1 }; StackItem stackItem62 = new Struct(list6); byte[] byteArray6 = BinarySerializer.Serialize(stackItem62, MaxItemSize); - StackItem result6 = BinarySerializer.Deserialize(byteArray6, 2048, (uint)byteArray6.Length); + StackItem result6 = BinarySerializer.Deserialize(byteArray6, 2048); Assert.AreEqual(((Struct)stackItem62).Count, ((Struct)result6).Count); Assert.AreEqual(((Struct)stackItem62).GetEnumerator().Current, ((Struct)result6).GetEnumerator().Current); StackItem stackItem72 = new Map { [2] = 1 }; byte[] byteArray7 = BinarySerializer.Serialize(stackItem72, MaxItemSize); - StackItem result7 = BinarySerializer.Deserialize(byteArray7, 2048, (uint)byteArray7.Length); + StackItem result7 = BinarySerializer.Deserialize(byteArray7, 2048); Assert.AreEqual(((Map)stackItem72).Count, ((Map)result7).Count); CollectionAssert.AreEqual(((Map)stackItem72).Keys.ToArray(), ((Map)result7).Keys.ToArray()); CollectionAssert.AreEqual(((Map)stackItem72).Values.ToArray(), ((Map)result7).Values.ToArray()); diff --git a/tests/neo.UnitTests/SmartContract/UT_Contract.cs b/tests/neo.UnitTests/SmartContract/UT_Contract.cs index cc67603991..a1e905edbc 100644 --- a/tests/neo.UnitTests/SmartContract/UT_Contract.cs +++ b/tests/neo.UnitTests/SmartContract/UT_Contract.cs @@ -14,24 +14,6 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_Contract { - [TestMethod] - public void TestGetAddress() - { - byte[] privateKey = new byte[32]; - RandomNumberGenerator rng = RandomNumberGenerator.Create(); - rng.GetBytes(privateKey); - KeyPair key = new KeyPair(privateKey); - Contract contract = Contract.CreateSignatureContract(key.PublicKey); - byte[] expectedArray = new byte[41]; - expectedArray[0] = (byte)OpCode.PUSHDATA1; - expectedArray[1] = 0x21; - Array.Copy(key.PublicKey.EncodePoint(true), 0, expectedArray, 2, 33); - expectedArray[35] = (byte)OpCode.PUSHNULL; - expectedArray[36] = (byte)OpCode.SYSCALL; - Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_VerifyWithECDsaSecp256r1), 0, expectedArray, 37, 4); - Assert.AreEqual(expectedArray.ToScriptHash().ToAddress(), contract.Address); - } - [TestMethod] public void TestGetScriptHash() { @@ -40,13 +22,12 @@ public void TestGetScriptHash() rng.GetBytes(privateKey); KeyPair key = new KeyPair(privateKey); Contract contract = Contract.CreateSignatureContract(key.PublicKey); - byte[] expectedArray = new byte[41]; + byte[] expectedArray = new byte[40]; expectedArray[0] = (byte)OpCode.PUSHDATA1; expectedArray[1] = 0x21; Array.Copy(key.PublicKey.EncodePoint(true), 0, expectedArray, 2, 33); - expectedArray[35] = (byte)OpCode.PUSHNULL; - expectedArray[36] = (byte)OpCode.SYSCALL; - Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_VerifyWithECDsaSecp256r1), 0, expectedArray, 37, 4); + expectedArray[35] = (byte)OpCode.SYSCALL; + Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_CheckSig), 0, expectedArray, 36, 4); Assert.AreEqual(expectedArray.ToScriptHash(), contract.ScriptHash); } @@ -77,7 +58,7 @@ public void TestCreateMultiSigContract() publicKeys[1] = key2.PublicKey; publicKeys = publicKeys.OrderBy(p => p).ToArray(); Contract contract = Contract.CreateMultiSigContract(2, publicKeys); - byte[] expectedArray = new byte[78]; + byte[] expectedArray = new byte[77]; expectedArray[0] = (byte)OpCode.PUSH2; expectedArray[1] = (byte)OpCode.PUSHDATA1; expectedArray[2] = 0x21; @@ -86,9 +67,8 @@ public void TestCreateMultiSigContract() expectedArray[37] = 0x21; Array.Copy(publicKeys[1].EncodePoint(true), 0, expectedArray, 38, 33); expectedArray[71] = (byte)OpCode.PUSH2; - expectedArray[72] = (byte)OpCode.PUSHNULL; - expectedArray[73] = (byte)OpCode.SYSCALL; - Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_CheckMultisigWithECDsaSecp256r1), 0, expectedArray, 74, 4); + expectedArray[72] = (byte)OpCode.SYSCALL; + Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_CheckMultisig), 0, expectedArray, 73, 4); CollectionAssert.AreEqual(expectedArray, contract.Script); Assert.AreEqual(2, contract.ParameterList.Length); Assert.AreEqual(ContractParameterType.Signature, contract.ParameterList[0]); @@ -113,7 +93,7 @@ public void TestCreateMultiSigRedeemScript() Action action = () => Contract.CreateMultiSigRedeemScript(0, publicKeys); action.Should().Throw(); byte[] script = Contract.CreateMultiSigRedeemScript(2, publicKeys); - byte[] expectedArray = new byte[78]; + byte[] expectedArray = new byte[77]; expectedArray[0] = (byte)OpCode.PUSH2; expectedArray[1] = (byte)OpCode.PUSHDATA1; expectedArray[2] = 0x21; @@ -122,9 +102,8 @@ public void TestCreateMultiSigRedeemScript() expectedArray[37] = 0x21; Array.Copy(publicKeys[1].EncodePoint(true), 0, expectedArray, 38, 33); expectedArray[71] = (byte)OpCode.PUSH2; - expectedArray[72] = (byte)OpCode.PUSHNULL; - expectedArray[73] = (byte)OpCode.SYSCALL; - Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_CheckMultisigWithECDsaSecp256r1), 0, expectedArray, 74, 4); + expectedArray[72] = (byte)OpCode.SYSCALL; + Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_CheckMultisig), 0, expectedArray, 73, 4); CollectionAssert.AreEqual(expectedArray, script); } @@ -136,13 +115,12 @@ public void TestCreateSignatureContract() rng.GetBytes(privateKey); KeyPair key = new KeyPair(privateKey); Contract contract = Contract.CreateSignatureContract(key.PublicKey); - byte[] expectedArray = new byte[41]; + byte[] expectedArray = new byte[40]; expectedArray[0] = (byte)OpCode.PUSHDATA1; expectedArray[1] = 0x21; Array.Copy(key.PublicKey.EncodePoint(true), 0, expectedArray, 2, 33); - expectedArray[35] = (byte)OpCode.PUSHNULL; - expectedArray[36] = (byte)OpCode.SYSCALL; - Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_VerifyWithECDsaSecp256r1), 0, expectedArray, 37, 4); + expectedArray[35] = (byte)OpCode.SYSCALL; + Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_CheckSig), 0, expectedArray, 36, 4); CollectionAssert.AreEqual(expectedArray, contract.Script); Assert.AreEqual(1, contract.ParameterList.Length); Assert.AreEqual(ContractParameterType.Signature, contract.ParameterList[0]); @@ -156,13 +134,12 @@ public void TestCreateSignatureRedeemScript() rng.GetBytes(privateKey); KeyPair key = new KeyPair(privateKey); byte[] script = Contract.CreateSignatureRedeemScript(key.PublicKey); - byte[] expectedArray = new byte[41]; + byte[] expectedArray = new byte[40]; expectedArray[0] = (byte)OpCode.PUSHDATA1; expectedArray[1] = 0x21; Array.Copy(key.PublicKey.EncodePoint(true), 0, expectedArray, 2, 33); - expectedArray[35] = (byte)OpCode.PUSHNULL; - expectedArray[36] = (byte)OpCode.SYSCALL; - Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_VerifyWithECDsaSecp256r1), 0, expectedArray, 37, 4); + expectedArray[35] = (byte)OpCode.SYSCALL; + Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_CheckSig), 0, expectedArray, 36, 4); CollectionAssert.AreEqual(expectedArray, script); } @@ -176,9 +153,9 @@ public void TestSignatureRedeemScriptFee() byte[] verification = Contract.CreateSignatureRedeemScript(key.PublicKey); byte[] invocation = new ScriptBuilder().EmitPush(UInt160.Zero).ToArray(); - var fee = PolicyContract.DefaultExecFeeFactor * (ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * 2 + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + ApplicationEngine.ECDsaVerifyPrice); + var fee = PolicyContract.DefaultExecFeeFactor * (ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * 2 + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + ApplicationEngine.CheckSigPrice); - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, new Transaction { Signers = Array.Empty(), Attributes = Array.Empty() }, null)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, new Transaction { Signers = Array.Empty(), Attributes = Array.Empty() }, null, settings: TestBlockchain.TheNeoSystem.Settings)) { engine.LoadScript(invocation.Concat(verification).ToArray(), configureState: p => p.CallFlags = CallFlags.None); engine.Execute(); @@ -204,9 +181,9 @@ public void TestCreateMultiSigRedeemScriptFee() byte[] verification = Contract.CreateMultiSigRedeemScript(2, publicKeys); byte[] invocation = new ScriptBuilder().EmitPush(UInt160.Zero).EmitPush(UInt160.Zero).ToArray(); - long fee = PolicyContract.DefaultExecFeeFactor * (ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * (2 + 2) + ApplicationEngine.OpCodePrices[OpCode.PUSHINT8] * 2 + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + ApplicationEngine.ECDsaVerifyPrice * 2); + long fee = PolicyContract.DefaultExecFeeFactor * (ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * (2 + 2) + ApplicationEngine.OpCodePrices[OpCode.PUSHINT8] * 2 + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + ApplicationEngine.CheckSigPrice * 2); - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, new Transaction { Signers = Array.Empty(), Attributes = Array.Empty() }, null)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, new Transaction { Signers = Array.Empty(), Attributes = Array.Empty() }, null, settings: TestBlockchain.TheNeoSystem.Settings)) { engine.LoadScript(invocation.Concat(verification).ToArray(), configureState: p => p.CallFlags = CallFlags.None); engine.Execute(); diff --git a/tests/neo.UnitTests/SmartContract/UT_ContractParameterContext.cs b/tests/neo.UnitTests/SmartContract/UT_ContractParameterContext.cs index fa6b4239db..c47a38befe 100644 --- a/tests/neo.UnitTests/SmartContract/UT_ContractParameterContext.cs +++ b/tests/neo.UnitTests/SmartContract/UT_ContractParameterContext.cs @@ -27,31 +27,33 @@ public static void ClassSetUp(TestContext context) key = new KeyPair(privateKey); contract = Contract.CreateSignatureContract(key.PublicKey); } - TestBlockchain.InitializeMockNeoSystem(); } [TestMethod] public void TestGetComplete() { + var snapshot = TestBlockchain.GetTestSnapshot(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x1bd5c777ec35768892bd3daab60fb7a1cb905066")); - var context = new ContractParametersContext(tx); + var context = new ContractParametersContext(snapshot, tx); context.Completed.Should().BeFalse(); } [TestMethod] public void TestToString() { + var snapshot = TestBlockchain.GetTestSnapshot(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x1bd5c777ec35768892bd3daab60fb7a1cb905066")); - var context = new ContractParametersContext(tx); + var context = new ContractParametersContext(snapshot, tx); context.Add(contract, 0, new byte[] { 0x01 }); string str = context.ToString(); - str.Should().Be(@"{""type"":""Neo.Network.P2P.Payloads.Transaction"",""hex"":""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI="",""items"":{}}"); + str.Should().Be(@"{""type"":""Neo.Network.P2P.Payloads.Transaction"",""data"":""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI="",""items"":{}}"); } [TestMethod] public void TestParse() { - var ret = ContractParametersContext.Parse("{\"type\":\"Neo.Network.P2P.Payloads.Transaction\",\"hex\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI=\",\"items\":{\"0xbecaad15c0ea585211faf99738a4354014f177f2\":{\"script\":\"IQJv8DuUkkHOHa3UNRnmlg4KhbQaaaBcMoEDqivOFZTKFmh0dHaq\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"AQ==\"}]}}}"); + var snapshot = TestBlockchain.GetTestSnapshot(); + var ret = ContractParametersContext.Parse("{\"type\":\"Neo.Network.P2P.Payloads.Transaction\",\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI=\",\"items\":{\"0xbecaad15c0ea585211faf99738a4354014f177f2\":{\"script\":\"IQJv8DuUkkHOHa3UNRnmlg4KhbQaaaBcMoEDqivOFZTKFmh0dHaq\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"AQ==\"}],\"signatures\":{\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\":\"AQ==\"}}}}", snapshot); ret.ScriptHashes[0].ToString().Should().Be("0x1bd5c777ec35768892bd3daab60fb7a1cb905066"); ((Transaction)ret.Verifiable).Script.ToHexString().Should().Be(new byte[] { 18 }.ToHexString()); } @@ -59,19 +61,21 @@ public void TestParse() [TestMethod] public void TestFromJson() { - Action action = () => ContractParametersContext.Parse("{\"type\":\"wrongType\",\"hex\":\"00000000007c97764845172d827d3c863743293931a691271a0000000000000000000000000000000000000000000100\",\"items\":{\"0x1bd5c777ec35768892bd3daab60fb7a1cb905066\":{\"script\":\"21026ff03b949241ce1dadd43519e6960e0a85b41a69a05c328103aa2bce1594ca1650680a906ad4\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"01\"}]}}}"); + var snapshot = TestBlockchain.GetTestSnapshot(); + Action action = () => ContractParametersContext.Parse("{\"type\":\"wrongType\",\"data\":\"00000000007c97764845172d827d3c863743293931a691271a0000000000000000000000000000000000000000000100\",\"items\":{\"0x1bd5c777ec35768892bd3daab60fb7a1cb905066\":{\"script\":\"21026ff03b949241ce1dadd43519e6960e0a85b41a69a05c328103aa2bce1594ca1650680a906ad4\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"01\"}]}}}", snapshot); action.Should().Throw(); } [TestMethod] public void TestAdd() { + var snapshot = TestBlockchain.GetTestSnapshot(); Transaction tx = TestUtils.GetTransaction(UInt160.Zero); - var context1 = new ContractParametersContext(tx); + var context1 = new ContractParametersContext(snapshot, tx); context1.Add(contract, 0, new byte[] { 0x01 }).Should().BeFalse(); - tx = TestUtils.GetTransaction(UInt160.Parse("0x282646ee0afa5508bb999318f35074b84a17c9f0")); - var context2 = new ContractParametersContext(tx); + tx = TestUtils.GetTransaction(UInt160.Parse("0x4bc1b25796f4a13fa3cc7538168d86f7d3bc5356")); + var context2 = new ContractParametersContext(snapshot, tx); context2.Add(contract, 0, new byte[] { 0x01 }).Should().BeTrue(); //test repeatlly createItem context2.Add(contract, 0, new byte[] { 0x01 }).Should().BeTrue(); @@ -80,8 +84,9 @@ public void TestAdd() [TestMethod] public void TestGetParameter() { - Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x282646ee0afa5508bb999318f35074b84a17c9f0")); - var context = new ContractParametersContext(tx); + var snapshot = TestBlockchain.GetTestSnapshot(); + Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x4bc1b25796f4a13fa3cc7538168d86f7d3bc5356")); + var context = new ContractParametersContext(snapshot, tx); context.GetParameter(tx.Sender, 0).Should().BeNull(); context.Add(contract, 0, new byte[] { 0x01 }); @@ -92,8 +97,9 @@ public void TestGetParameter() [TestMethod] public void TestGetWitnesses() { - Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x282646ee0afa5508bb999318f35074b84a17c9f0")); - var context = new ContractParametersContext(tx); + var snapshot = TestBlockchain.GetTestSnapshot(); + Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x4bc1b25796f4a13fa3cc7538168d86f7d3bc5356")); + var context = new ContractParametersContext(snapshot, tx); context.Add(contract, 0, new byte[] { 0x01 }); Witness[] witnesses = context.GetWitnesses(); witnesses.Length.Should().Be(1); @@ -104,17 +110,18 @@ public void TestGetWitnesses() [TestMethod] public void TestAddSignature() { - var singleSender = UInt160.Parse("0x282646ee0afa5508bb999318f35074b84a17c9f0"); + var snapshot = TestBlockchain.GetTestSnapshot(); + var singleSender = UInt160.Parse("0x4bc1b25796f4a13fa3cc7538168d86f7d3bc5356"); Transaction tx = TestUtils.GetTransaction(singleSender); //singleSign - var context = new ContractParametersContext(tx); + var context = new ContractParametersContext(snapshot, tx); context.AddSignature(contract, key.PublicKey, new byte[] { 0x01 }).Should().BeTrue(); var contract1 = Contract.CreateSignatureContract(key.PublicKey); contract1.ParameterList = new ContractParameterType[0]; - context = new ContractParametersContext(tx); + context = new ContractParametersContext(snapshot, tx); context.AddSignature(contract1, key.PublicKey, new byte[] { 0x01 }).Should().BeFalse(); contract1.ParameterList = new[] { ContractParameterType.Signature, ContractParameterType.Signature }; @@ -134,18 +141,18 @@ public void TestAddSignature() key.PublicKey, key2.PublicKey }); - var multiSender = UInt160.Parse("0x3593816cc1085a6328fea2b899c24d78cd0ba372"); + var multiSender = UInt160.Parse("0x7eae53e544e3c1507bfa7892d4793b8784be4c31"); tx = TestUtils.GetTransaction(multiSender); - context = new ContractParametersContext(tx); + context = new ContractParametersContext(snapshot, tx); context.AddSignature(multiSignContract, key.PublicKey, new byte[] { 0x01 }).Should().BeTrue(); context.AddSignature(multiSignContract, key2.PublicKey, new byte[] { 0x01 }).Should().BeTrue(); tx = TestUtils.GetTransaction(singleSender); - context = new ContractParametersContext(tx); + context = new ContractParametersContext(snapshot, tx); context.AddSignature(multiSignContract, key.PublicKey, new byte[] { 0x01 }).Should().BeFalse(); tx = TestUtils.GetTransaction(multiSender); - context = new ContractParametersContext(tx); + context = new ContractParametersContext(snapshot, tx); byte[] privateKey3 = new byte[] { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, diff --git a/tests/neo.UnitTests/SmartContract/UT_DeployedContract.cs b/tests/neo.UnitTests/SmartContract/UT_DeployedContract.cs index 96ae48d17b..d09da0cdf9 100644 --- a/tests/neo.UnitTests/SmartContract/UT_DeployedContract.cs +++ b/tests/neo.UnitTests/SmartContract/UT_DeployedContract.cs @@ -8,7 +8,7 @@ namespace Neo.UnitTests.SmartContract public class UT_DeployedContract { [TestMethod] - public void TestGetAddress() + public void TestGetScriptHash() { var contract = new DeployedContract(new ContractState() { @@ -31,7 +31,6 @@ public void TestGetAddress() }); Assert.AreEqual("0xb2e3fe334830b4741fa5d762f2ab36b90b86c49b", contract.ScriptHash.ToString()); - Assert.AreEqual("Na7bMBy8KWZKSFBWTxeSKth1Je9AcWTpQM", contract.Address); } [TestMethod] diff --git a/tests/neo.UnitTests/SmartContract/UT_Helper.cs b/tests/neo.UnitTests/SmartContract/UT_Helper.cs index 6f292c06e7..03f832efa2 100644 --- a/tests/neo.UnitTests/SmartContract/UT_Helper.cs +++ b/tests/neo.UnitTests/SmartContract/UT_Helper.cs @@ -1,5 +1,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; using System; namespace Neo.UnitTests.SmartContract @@ -7,6 +12,16 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_Helper { + private KeyPair _key; + + [TestInitialize] + public void Init() + { + var pk = new byte[32]; + new Random().NextBytes(pk); + _key = new KeyPair(pk); + } + [TestMethod] public void TestGetContractHash() { @@ -43,5 +58,46 @@ public void TestIsMultiSigContract() }; Assert.IsFalse(case2.IsMultiSigContract()); } + + [TestMethod] + public void TestSignatureContractCost() + { + var contract = Contract.CreateSignatureContract(_key.PublicKey); + + var tx = TestUtils.CreateRandomHashTransaction(); + tx.Signers[0].Account = contract.ScriptHash; + + using ScriptBuilder invocationScript = new(); + invocationScript.EmitPush(Neo.Wallets.Helper.Sign(tx, _key, ProtocolSettings.Default.Magic)); + tx.Witnesses = new Witness[] { new Witness() { InvocationScript = invocationScript.ToArray(), VerificationScript = contract.Script } }; + + using var engine = ApplicationEngine.Create(TriggerType.Verification, tx, null, null, ProtocolSettings.Default); + engine.LoadScript(contract.Script); + engine.LoadScript(new Script(invocationScript.ToArray(), true), configureState: p => p.CallFlags = CallFlags.None); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + + Assert.AreEqual(Neo.SmartContract.Helper.SignatureContractCost() * PolicyContract.DefaultExecFeeFactor, engine.GasConsumed); + } + + [TestMethod] + public void TestMultiSignatureContractCost() + { + var contract = Contract.CreateMultiSigContract(1, new ECPoint[] { _key.PublicKey }); + + var tx = TestUtils.CreateRandomHashTransaction(); + tx.Signers[0].Account = contract.ScriptHash; + + using ScriptBuilder invocationScript = new(); + invocationScript.EmitPush(Neo.Wallets.Helper.Sign(tx, _key, ProtocolSettings.Default.Magic)); + + using var engine = ApplicationEngine.Create(TriggerType.Verification, tx, null, null, ProtocolSettings.Default); + engine.LoadScript(contract.Script); + engine.LoadScript(new Script(invocationScript.ToArray(), true), configureState: p => p.CallFlags = CallFlags.None); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + + Assert.AreEqual(Neo.SmartContract.Helper.MultiSignatureContractCost(1, 1) * PolicyContract.DefaultExecFeeFactor, engine.GasConsumed); + } } } diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs index 24c9907029..99aa739052 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -1,6 +1,5 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Ledger; using Neo.SmartContract; using Neo.UnitTests.Extensions; using Neo.VM; @@ -10,18 +9,12 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_InteropPrices { - [TestInitialize] - public void Initialize() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void ApplicationEngineFixedPrices() { // System.Runtime.CheckWitness: f827ec8c (price is 200) byte[] SyscallSystemRuntimeCheckWitnessHash = new byte[] { 0x68, 0xf8, 0x27, 0xec, 0x8c }; - using (ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, null, null, 0)) + using (ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, null, gas: 0)) { ae.LoadScript(SyscallSystemRuntimeCheckWitnessHash); ApplicationEngine.System_Runtime_CheckWitness.FixedPrice.Should().Be(0_00001024L); @@ -29,7 +22,7 @@ public void ApplicationEngineFixedPrices() // System.Storage.GetContext: 9bf667ce (price is 1) byte[] SyscallSystemStorageGetContextHash = new byte[] { 0x68, 0x9b, 0xf6, 0x67, 0xce }; - using (ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, null, null, 0)) + using (ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, null, gas: 0)) { ae.LoadScript(SyscallSystemStorageGetContextHash); ApplicationEngine.System_Storage_GetContext.FixedPrice.Should().Be(0_00000016L); @@ -37,7 +30,7 @@ public void ApplicationEngineFixedPrices() // System.Storage.Get: 925de831 (price is 100) byte[] SyscallSystemStorageGetHash = new byte[] { 0x68, 0x92, 0x5d, 0xe8, 0x31 }; - using (ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, null, null, 0)) + using (ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, null, gas: 0)) { ae.LoadScript(SyscallSystemStorageGetHash); ApplicationEngine.System_Storage_Get.FixedPrice.Should().Be(32768L); @@ -60,7 +53,7 @@ public void ApplicationEngineRegularPut() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(new byte[0] { }); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); snapshot.Add(skey, sItem); snapshot.AddContract(script.ToScriptHash(), contractState); @@ -73,7 +66,7 @@ public void ApplicationEngineRegularPut() debugger.StepInto(); var setupPrice = ae.GasConsumed; debugger.Execute(); - (ae.GasConsumed - setupPrice).Should().Be(ae.StoragePrice * (1 + value.Length)); + (ae.GasConsumed - setupPrice).Should().Be(ae.StoragePrice * value.Length + (1 << 15) * 30); } } @@ -93,7 +86,7 @@ public void ApplicationEngineReusedStorage_FullReuse() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(value); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); snapshot.Add(skey, sItem); snapshot.AddContract(script.ToScriptHash(), contractState); @@ -106,7 +99,7 @@ public void ApplicationEngineReusedStorage_FullReuse() debugger.StepInto(); var setupPrice = applicationEngine.GasConsumed; debugger.Execute(); - (applicationEngine.GasConsumed - setupPrice).Should().Be(1 * applicationEngine.StoragePrice); + (applicationEngine.GasConsumed - setupPrice).Should().Be(1 * applicationEngine.StoragePrice + (1 << 15) * 30); } } @@ -128,7 +121,7 @@ public void ApplicationEngineReusedStorage_PartialReuse() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); snapshot.Add(skey, sItem); snapshot.AddContract(script.ToScriptHash(), contractState); @@ -142,7 +135,7 @@ public void ApplicationEngineReusedStorage_PartialReuse() var setupPrice = ae.GasConsumed; debugger.StepInto(); debugger.StepInto(); - (ae.GasConsumed - setupPrice).Should().Be((1 + (oldValue.Length / 4) + value.Length - oldValue.Length) * ae.StoragePrice); + (ae.GasConsumed - setupPrice).Should().Be((1 + (oldValue.Length / 4) + value.Length - oldValue.Length) * ae.StoragePrice + (1 << 15) * 30); } } @@ -164,7 +157,7 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); snapshot.Add(skey, sItem); snapshot.AddContract(script.ToScriptHash(), contractState); @@ -181,7 +174,7 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() debugger.StepInto(); //syscall Storage.GetContext var setupPrice = ae.GasConsumed; debugger.StepInto(); //syscall Storage.Put - (ae.GasConsumed - setupPrice).Should().Be((sItem.Value.Length / 4 + 1) * ae.StoragePrice); // = PUT basic fee + (ae.GasConsumed - setupPrice).Should().Be((sItem.Value.Length / 4 + 1) * ae.StoragePrice + (1 << 15) * 30); // = PUT basic fee } } diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropService.NEO.cs b/tests/neo.UnitTests/SmartContract/UT_InteropService.NEO.cs index 32c48095a2..204876e82c 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropService.NEO.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropService.NEO.cs @@ -3,7 +3,6 @@ using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.SmartContract; @@ -16,7 +15,6 @@ using Neo.Wallets; using System; using System.Linq; -using System.Text; using VMArray = Neo.VM.Types.Array; namespace Neo.UnitTests.SmartContract @@ -28,14 +26,15 @@ public void TestCheckSig() { var engine = GetEngine(true); IVerifiable iv = engine.ScriptContainer; - byte[] message = iv.GetHashData(); + byte[] message = iv.GetSignData(ProtocolSettings.Default.Magic); byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; KeyPair keyPair = new KeyPair(privateKey); ECPoint pubkey = keyPair.PublicKey; byte[] signature = Crypto.Sign(message, privateKey, pubkey.EncodePoint(false).Skip(1).ToArray()); - engine.VerifyWithECDsaSecp256r1(StackItem.Null, pubkey.EncodePoint(false), signature).Should().BeTrue(); - engine.VerifyWithECDsaSecp256r1(StackItem.Null, new byte[70], signature).Should().BeFalse(); + engine.CheckSig(pubkey.EncodePoint(false), signature).Should().BeTrue(); + Action action = () => engine.CheckSig(new byte[70], signature); + action.Should().Throw(); } [TestMethod] @@ -43,7 +42,7 @@ public void TestCrypto_CheckMultiSig() { var engine = GetEngine(true); IVerifiable iv = engine.ScriptContainer; - byte[] message = iv.GetHashData(); + byte[] message = iv.GetSignData(ProtocolSettings.Default.Magic); byte[] privkey1 = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; @@ -67,10 +66,10 @@ public void TestCrypto_CheckMultiSig() signature1, signature2 }; - engine.CheckMultisigWithECDsaSecp256r1(StackItem.Null, pubkeys, signatures).Should().BeTrue(); + engine.CheckMultisig(pubkeys, signatures).Should().BeTrue(); pubkeys = new byte[0][]; - Assert.ThrowsException(() => engine.CheckMultisigWithECDsaSecp256r1(StackItem.Null, pubkeys, signatures)); + Assert.ThrowsException(() => engine.CheckMultisig(pubkeys, signatures)); pubkeys = new[] { @@ -78,7 +77,7 @@ public void TestCrypto_CheckMultiSig() pubkey2.EncodePoint(false) }; signatures = new byte[0][]; - Assert.ThrowsException(() => engine.CheckMultisigWithECDsaSecp256r1(StackItem.Null, pubkeys, signatures)); + Assert.ThrowsException(() => engine.CheckMultisig(pubkeys, signatures)); pubkeys = new[] { @@ -90,7 +89,7 @@ public void TestCrypto_CheckMultiSig() signature1, new byte[64] }; - engine.CheckMultisigWithECDsaSecp256r1(StackItem.Null, pubkeys, signatures).Should().BeFalse(); + engine.CheckMultisig(pubkeys, signatures).Should().BeFalse(); pubkeys = new[] { @@ -102,36 +101,13 @@ public void TestCrypto_CheckMultiSig() signature1, signature2 }; - engine.CheckMultisigWithECDsaSecp256r1(StackItem.Null, pubkeys, signatures).Should().BeFalse(); - } - - [TestMethod] - public void TestAccount_IsStandard() - { - var engine = GetEngine(false, true); - var hash = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01 }; - engine.IsStandardContract(new UInt160(hash)).Should().BeFalse(); - - var snapshot = Blockchain.Singleton.GetSnapshot().CreateSnapshot(); - var state = TestUtils.GetContract(); - snapshot.AddContract(state.Hash, state); - engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); - engine.LoadScript(new byte[] { 0x01 }); - engine.IsStandardContract(state.Hash).Should().BeFalse(); - - state.Nef.Script = Contract.CreateSignatureRedeemScript(Blockchain.StandbyValidators[0]); - engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); - engine.LoadScript(new byte[] { 0x01 }); - engine.IsStandardContract(state.Hash).Should().BeTrue(); + Assert.ThrowsException(() => engine.CheckMultisig(pubkeys, signatures)); } [TestMethod] public void TestContract_Create() { - var snapshot = Blockchain.Singleton.GetSnapshot().CreateSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var nef = new NefFile() { Script = Enumerable.Repeat((byte)OpCode.RET, byte.MaxValue).ToArray(), @@ -174,7 +150,7 @@ public void TestContract_Create() [TestMethod] public void TestContract_Update() { - var snapshot = Blockchain.Singleton.GetSnapshot().CreateSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var nef = new NefFile() { Script = new[] { (byte)OpCode.RET }, @@ -202,8 +178,7 @@ public void TestContract_Update() var storageItem = new StorageItem { - Value = new byte[] { 0x01 }, - IsConstant = false + Value = new byte[] { 0x01 } }; var storageKey = new StorageKey @@ -234,7 +209,7 @@ public void TestContract_Update_Invalid() }; nefFile.CheckSum = NefFile.ComputeChecksum(nefFile); - var snapshot = Blockchain.Singleton.GetSnapshot().CreateSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); Assert.ThrowsException(() => snapshot.UpdateContract(null, null, new byte[] { 0x01 })); Assert.ThrowsException(() => snapshot.UpdateContract(null, nefFile.ToArray(), null)); @@ -255,13 +230,12 @@ public void TestContract_Update_Invalid() [TestMethod] public void TestStorage_Find() { - var snapshot = Blockchain.Singleton.GetSnapshot().CreateSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var state = TestUtils.GetContract(); var storageItem = new StorageItem { - Value = new byte[] { 0x01, 0x02, 0x03, 0x04 }, - IsConstant = true + Value = new byte[] { 0x01, 0x02, 0x03, 0x04 } }; var storageKey = new StorageKey { @@ -286,25 +260,23 @@ public void TestStorage_Find() [TestMethod] public void TestIterator_Next() { - var engine = GetEngine(); var arr = new VMArray { new byte[]{ 0x01 }, new byte[]{ 0x02 } }; - engine.IteratorNext(new ArrayWrapper(arr)).Should().BeTrue(); + ApplicationEngine.IteratorNext(new ArrayWrapper(arr)).Should().BeTrue(); } [TestMethod] public void TestIterator_Value() { - var engine = GetEngine(); var arr = new VMArray { new byte[]{ 0x01 }, new byte[]{ 0x02 } }; var wrapper = new ArrayWrapper(arr); wrapper.Next(); - engine.IteratorValue(wrapper).GetSpan().ToHexString().Should().Be(new byte[] { 0x01 }.ToHexString()); + ApplicationEngine.IteratorValue(wrapper).GetSpan().ToHexString().Should().Be(new byte[] { 0x01 }.ToHexString()); } [TestMethod] @@ -333,17 +305,5 @@ public void TestIterator_Create() @struct[0].GetInteger().Should().Be(1); @struct[1].GetInteger().Should().Be(2); } - - [TestMethod] - public void TestJson_Deserialize() - { - GetEngine().JsonDeserialize(new byte[] { (byte)'1' }).GetInteger().Should().Be(1); - } - - [TestMethod] - public void TestJson_Serialize() - { - Encoding.UTF8.GetString(GetEngine().JsonSerialize(1)).Should().Be("1"); - } } } diff --git a/tests/neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/neo.UnitTests/SmartContract/UT_InteropService.cs index 2ef9ac2de8..cdf22d6720 100644 --- a/tests/neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/neo.UnitTests/SmartContract/UT_InteropService.cs @@ -1,9 +1,9 @@ +using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.SmartContract; @@ -20,19 +20,13 @@ namespace Neo.UnitTests.SmartContract { [TestClass] - public partial class UT_InteropService + public partial class UT_InteropService : TestKit { - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void Runtime_GetNotifications_Test() { UInt160 scriptHash2; - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); using (var script = new ScriptBuilder()) { @@ -197,7 +191,7 @@ private static void AssertNotification(StackItem stackItem, UInt160 scriptHash, [TestMethod] public void TestExecutionEngine_GetScriptContainer() { - GetEngine(true).GetScriptContainer().Should().BeOfType(); + GetEngine(true).GetScriptContainer().Should().BeOfType(); } [TestMethod] @@ -217,7 +211,7 @@ public void TestExecutionEngine_GetCallingScriptHash() var contract = new ContractState() { - Manifest = TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer), + Manifest = TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.String, ContractParameterType.Integer), Nef = new NefFile { Script = scriptA.ToArray() }, Hash = scriptA.ToArray().ToScriptHash() }; @@ -225,7 +219,7 @@ public void TestExecutionEngine_GetCallingScriptHash() engine.Snapshot.AddContract(contract.Hash, contract); using ScriptBuilder scriptB = new ScriptBuilder(); - scriptB.EmitDynamicCall(contract.Hash, "test", 0, 1); + scriptB.EmitDynamicCall(contract.Hash, "test", "0", 1); engine.LoadScript(scriptB.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -242,7 +236,7 @@ public void TestContract_GetCallFlags() [TestMethod] public void TestRuntime_Platform() { - GetEngine().GetPlatform().Should().Be("NEO"); + ApplicationEngine.GetPlatform().Should().Be("NEO"); } [TestMethod] @@ -281,34 +275,11 @@ public void TestRuntime_Log() [TestMethod] public void TestRuntime_GetTime() { - Block block = new Block(); + Block block = new Block { Header = new Header() }; var engine = GetEngine(true, true, hasBlock: true); engine.GetTime().Should().Be(block.Timestamp); } - [TestMethod] - public void TestRuntime_Serialize() - { - var engine = GetEngine(); - engine.BinarySerialize(100).ToHexString().Should().Be(new byte[] { 0x21, 0x01, 0x64 }.ToHexString()); - - //Larger than MaxItemSize - Assert.ThrowsException(() => engine.BinarySerialize(new byte[1024 * 1024 * 2])); - - //NotSupportedException - Assert.ThrowsException(() => engine.BinarySerialize(new InteropInterface(new object()))); - } - - [TestMethod] - public void TestRuntime_Deserialize() - { - var engine = GetEngine(); - engine.BinaryDeserialize(engine.BinarySerialize(100)).GetInteger().Should().Be(100); - - //FormatException - Assert.ThrowsException(() => engine.BinaryDeserialize(new byte[] { 0xfa, 0x01 })); - } - [TestMethod] public void TestRuntime_GetInvocationCounter() { @@ -321,17 +292,17 @@ public void TestCrypto_Verify() { var engine = GetEngine(true); IVerifiable iv = engine.ScriptContainer; - byte[] message = iv.GetHashData(); + byte[] message = iv.GetSignData(ProtocolSettings.Default.Magic); byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; KeyPair keyPair = new KeyPair(privateKey); ECPoint pubkey = keyPair.PublicKey; byte[] signature = Crypto.Sign(message, privateKey, pubkey.EncodePoint(false).Skip(1).ToArray()); - engine.VerifyWithECDsaSecp256r1(message, pubkey.EncodePoint(false), signature).Should().BeTrue(); + engine.CheckSig(pubkey.EncodePoint(false), signature).Should().BeTrue(); byte[] wrongkey = pubkey.EncodePoint(false); wrongkey[0] = 5; - engine.VerifyWithECDsaSecp256r1(new InteropInterface(engine.ScriptContainer), wrongkey, signature).Should().BeFalse(); + Assert.ThrowsException(() => engine.CheckSig(wrongkey, signature)); } [TestMethod] @@ -379,11 +350,9 @@ public void TestBlockchain_GetTransactionHeight() Transaction = TestUtils.CreateRandomHashTransaction() }; UT_SmartContractHelper.TransactionAdd(engine.Snapshot, state); - engine.LoadScript(NativeContract.Ledger.Script, configureState: p => p.ScriptHash = NativeContract.Ledger.Hash); - var script = new ScriptBuilder(); - script.EmitPush(state.Transaction.Hash.ToArray()); - script.EmitPush("getTransactionHeight"); + using var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.Ledger.Hash, "getTransactionHeight", state.Transaction.Hash); engine.LoadScript(script.ToArray()); engine.Execute(); Assert.AreEqual(engine.State, VMState.HALT); @@ -403,7 +372,7 @@ public void TestBlockchain_GetContract() 0x01, 0x01, 0x01, 0x01, 0x01 }; NativeContract.ContractManagement.GetContract(engine.Snapshot, new UInt160(data1)).Should().BeNull(); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var state = TestUtils.GetContract(); snapshot.AddContract(state.Hash, state); engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); @@ -434,7 +403,7 @@ public void TestStorage_GetReadOnlyContext() [TestMethod] public void TestStorage_Get() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var state = TestUtils.GetContract(); var storageKey = new StorageKey @@ -445,8 +414,7 @@ public void TestStorage_Get() var storageItem = new StorageItem { - Value = new byte[] { 0x01, 0x02, 0x03, 0x04 }, - IsConstant = true + Value = new byte[] { 0x01, 0x02, 0x03, 0x04 } }; snapshot.AddContract(state.Hash, state); snapshot.Add(storageKey, storageItem); @@ -493,7 +461,7 @@ public void TestStorage_Put() Assert.ThrowsException(() => engine.Put(storageContext, key, value)); //storage value is constant - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var storageKey = new StorageKey { @@ -502,8 +470,7 @@ public void TestStorage_Put() }; var storageItem = new StorageItem { - Value = new byte[] { 0x01, 0x02, 0x03, 0x04 }, - IsConstant = true + Value = new byte[] { 0x01, 0x02, 0x03, 0x04 } }; snapshot.AddContract(state.Hash, state); snapshot.Add(storageKey, storageItem); @@ -512,10 +479,6 @@ public void TestStorage_Put() key = new byte[] { 0x01 }; value = new byte[] { 0x02 }; storageContext.IsReadOnly = false; - Assert.ThrowsException(() => engine.Put(storageContext, key, value)); - - //success - storageItem.IsConstant = false; engine.Put(storageContext, key, value); //value length == 0 @@ -524,40 +487,11 @@ public void TestStorage_Put() engine.Put(storageContext, key, value); } - [TestMethod] - public void TestStorage_PutEx() - { - var snapshot = Blockchain.Singleton.GetSnapshot(); - var state = TestUtils.GetContract(); - var storageKey = new StorageKey - { - Id = 0x42000000, - Key = new byte[] { 0x01 } - }; - var storageItem = new StorageItem - { - Value = new byte[] { 0x01, 0x02, 0x03, 0x04 }, - IsConstant = false - }; - snapshot.AddContract(state.Hash, state); - snapshot.Add(storageKey, storageItem); - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); - engine.LoadScript(new byte[] { 0x01 }); - var key = new byte[] { 0x01 }; - var value = new byte[] { 0x02 }; - var storageContext = new StorageContext - { - Id = state.Id, - IsReadOnly = false - }; - engine.PutEx(storageContext, key, value, StorageFlags.None); - } - [TestMethod] public void TestStorage_Delete() { var engine = GetEngine(false, true); - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var state = TestUtils.GetContract(); var storageKey = new StorageKey { @@ -566,8 +500,7 @@ public void TestStorage_Delete() }; var storageItem = new StorageItem { - Value = new byte[] { 0x01, 0x02, 0x03, 0x04 }, - IsConstant = false + Value = new byte[] { 0x01, 0x02, 0x03, 0x04 } }; snapshot.AddContract(state.Hash, state); snapshot.Add(storageKey, storageItem); @@ -589,20 +522,19 @@ public void TestStorage_Delete() [TestMethod] public void TestStorageContext_AsReadOnly() { - var engine = GetEngine(); var state = TestUtils.GetContract(); var storageContext = new StorageContext { Id = state.Id, IsReadOnly = false }; - engine.AsReadOnly(storageContext).IsReadOnly.Should().BeTrue(); + ApplicationEngine.AsReadOnly(storageContext).IsReadOnly.Should().BeTrue(); } [TestMethod] public void TestContract_Call() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); string method = "method"; var args = new VM.Types.Array { 0, 1 }; var state = TestUtils.GetContract(method, args.Count); @@ -627,13 +559,12 @@ public void TestContract_Call() [TestMethod] public void TestContract_Destroy() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var state = TestUtils.GetContract(); var scriptHash = UInt160.Parse("0xcb9f3b7c6fb1cf2c13a40637c189bdd066a272b4"); var storageItem = new StorageItem { - Value = new byte[] { 0x01, 0x02, 0x03, 0x04 }, - IsConstant = false + Value = new byte[] { 0x01, 0x02, 0x03, 0x04 } }; var storageKey = new StorageKey @@ -656,9 +587,8 @@ public void TestContract_Destroy() [TestMethod] public void TestContract_CreateStandardAccount() { - var engine = GetEngine(true, true); ECPoint pubkey = ECPoint.Parse("024b817ef37f2fc3d4a33fe36687e592d9f30fe24b3e28187dc8f12b3b3b2b839e", ECCurve.Secp256r1); - engine.CreateStandardAccount(pubkey).ToArray().ToHexString().Should().Be("a17e91aff4bb5e0ad54d7ce8de8472e17ce88bf1"); + ApplicationEngine.CreateStandardAccount(pubkey).ToArray().ToHexString().Should().Be("a78796ab56598585c80dbe95059324eabde764db"); } public static void LogEvent(object sender, LogEventArgs args) @@ -670,9 +600,9 @@ public static void LogEvent(object sender, LogEventArgs args) private static ApplicationEngine GetEngine(bool hasContainer = false, bool hasSnapshot = false, bool hasBlock = false, bool addScript = true, long gas = 20_00000000) { var tx = hasContainer ? TestUtils.GetTransaction(UInt160.Zero) : null; - var snapshot = hasSnapshot ? Blockchain.Singleton.GetSnapshot() : null; - var block = hasBlock ? new Block() : null; - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot, block, gas); + var snapshot = hasSnapshot ? TestBlockchain.GetTestSnapshot() : null; + var block = hasBlock ? new Block { Header = new Header() } : null; + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot, block, TestBlockchain.TheNeoSystem.Settings, gas: gas); if (addScript) engine.LoadScript(new byte[] { 0x01 }); return engine; } diff --git a/tests/neo.UnitTests/SmartContract/UT_SmartContractHelper.cs b/tests/neo.UnitTests/SmartContract/UT_SmartContractHelper.cs index c3d3209588..811292f35a 100644 --- a/tests/neo.UnitTests/SmartContract/UT_SmartContractHelper.cs +++ b/tests/neo.UnitTests/SmartContract/UT_SmartContractHelper.cs @@ -1,6 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -21,12 +20,6 @@ public class UT_SmartContractHelper const byte Prefix_BlockHash = 9; const byte Prefix_Transaction = 11; - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void TestIsMultiSigContract() { @@ -123,51 +116,57 @@ public void TestIsStandardContract() [TestMethod] public void TestVerifyWitnesses() { - var snapshot1 = Blockchain.Singleton.GetSnapshot().CreateSnapshot(); + var snapshot1 = TestBlockchain.GetTestSnapshot().CreateSnapshot(); UInt256 index1 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); BlocksAdd(snapshot1, index1, new TrimmedBlock() { - Timestamp = 1, - PrevHash = UInt256.Zero, - MerkleRoot = UInt256.Zero, - ConsensusData = new ConsensusData(), + Header = new Header + { + Timestamp = 1, + PrevHash = UInt256.Zero, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + Witness = new Witness() { InvocationScript = new byte[0], VerificationScript = new byte[0] } + }, Hashes = new UInt256[1] { UInt256.Zero }, - NextConsensus = UInt160.Zero, - Witness = new Witness() { InvocationScript = new byte[0], VerificationScript = new byte[0] } }); BlocksDelete(snapshot1, index1); - Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(new Header() { PrevHash = index1 }, snapshot1, 100)); + Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(new Header() { PrevHash = index1 }, ProtocolSettings.Default, snapshot1, 100)); - var snapshot2 = Blockchain.Singleton.GetSnapshot(); + var snapshot2 = TestBlockchain.GetTestSnapshot(); UInt256 index2 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); TrimmedBlock block2 = new TrimmedBlock() { - Timestamp = 2, - PrevHash = UInt256.Zero, - MerkleRoot = UInt256.Zero, - ConsensusData = new ConsensusData(), + Header = new Header + { + Timestamp = 2, + PrevHash = UInt256.Zero, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + Witness = new Witness() { InvocationScript = new byte[0], VerificationScript = new byte[0] } + }, Hashes = new UInt256[1] { UInt256.Zero }, - NextConsensus = UInt160.Zero, - Witness = new Witness() { InvocationScript = new byte[0], VerificationScript = new byte[0] } }; BlocksAdd(snapshot2, index2, block2); Header header2 = new Header() { PrevHash = index2, Witness = new Witness { InvocationScript = new byte[0], VerificationScript = new byte[0] } }; snapshot2.AddContract(UInt160.Zero, new ContractState()); snapshot2.DeleteContract(UInt160.Zero); - Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header2, snapshot2, 100)); + Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header2, ProtocolSettings.Default, snapshot2, 100)); - var snapshot3 = Blockchain.Singleton.GetSnapshot(); + var snapshot3 = TestBlockchain.GetTestSnapshot(); UInt256 index3 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); TrimmedBlock block3 = new TrimmedBlock() { - Timestamp = 3, - PrevHash = UInt256.Zero, - MerkleRoot = UInt256.Zero, - ConsensusData = new ConsensusData(), + Header = new Header + { + Timestamp = 3, + PrevHash = UInt256.Zero, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + Witness = new Witness() { InvocationScript = new byte[0], VerificationScript = new byte[0] } + }, Hashes = new UInt256[1] { UInt256.Zero }, - NextConsensus = UInt160.Zero, - Witness = new Witness() { InvocationScript = new byte[0], VerificationScript = new byte[0] } }; BlocksAdd(snapshot3, index3, block3); Header header3 = new Header() @@ -185,7 +184,7 @@ public void TestVerifyWitnesses() Hash = Array.Empty().ToScriptHash(), Manifest = TestUtils.CreateManifest("verify", ContractParameterType.Boolean, ContractParameterType.Signature), }); - Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header3, snapshot3, 100)); + Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header3, ProtocolSettings.Default, snapshot3, 100)); // Smart contract verification @@ -201,7 +200,7 @@ public void TestVerifyWitnesses() Witnesses = new Witness[] { new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } } }; - Assert.AreEqual(true, Neo.SmartContract.Helper.VerifyWitnesses(tx, snapshot3, 1000)); + Assert.AreEqual(true, Neo.SmartContract.Helper.VerifyWitnesses(tx, ProtocolSettings.Default, snapshot3, 1000)); } private void BlocksDelete(DataCache snapshot, UInt256 hash) @@ -214,14 +213,14 @@ public static void TransactionAdd(DataCache snapshot, params TransactionState[] { foreach (TransactionState tx in txs) { - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), new StorageItem(tx, true)); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), new StorageItem(tx)); } } public static void BlocksAdd(DataCache snapshot, UInt256 hash, TrimmedBlock block) { - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray(), true)); - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToArray(), true)); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToArray())); } } } diff --git a/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs b/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs index 7ce2aed12c..f960ae9a46 100644 --- a/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs +++ b/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -1,6 +1,6 @@ +using Akka.TestKit.Xunit2; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -13,14 +13,8 @@ namespace Neo.UnitTests.SmartContract { [TestClass] - public partial class UT_Syscalls + public partial class UT_Syscalls : TestKit { - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void System_Blockchain_GetBlock() { @@ -39,22 +33,24 @@ public void System_Blockchain_GetBlock() var block = new TrimmedBlock() { - Index = 0, - Timestamp = 2, - Version = 3, - Witness = new Witness() + Header = new Header { - InvocationScript = new byte[0], - VerificationScript = new byte[0] + Index = 0, + Timestamp = 2, + Witness = new Witness() + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + }, + PrevHash = UInt256.Zero, + MerkleRoot = UInt256.Zero, + PrimaryIndex = 1, + NextConsensus = UInt160.Zero, }, - PrevHash = UInt256.Zero, - MerkleRoot = UInt256.Zero, - NextConsensus = UInt160.Zero, - ConsensusData = new ConsensusData() { Nonce = 1, PrimaryIndex = 1 }, - Hashes = new UInt256[] { new ConsensusData() { Nonce = 1, PrimaryIndex = 1 }.Hash, tx.Hash } + Hashes = new[] { tx.Hash } }; - var snapshot = Blockchain.Singleton.GetSnapshot().CreateSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); using (var script = new ScriptBuilder()) { @@ -62,7 +58,7 @@ public void System_Blockchain_GetBlock() // Without block - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -82,9 +78,9 @@ public void System_Blockchain_GetBlock() { BlockIndex = block.Index, Transaction = tx - }, true)); + })); - engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -95,7 +91,7 @@ public void System_Blockchain_GetBlock() height.Index = block.Index; - engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -106,122 +102,10 @@ public void System_Blockchain_GetBlock() } } - [TestMethod] - public void Json_Deserialize() - { - // Good - - using (var script = new ScriptBuilder()) - { - script.EmitPush("123"); - script.EmitSysCall(ApplicationEngine.System_Json_Deserialize); - script.EmitPush("null"); - script.EmitSysCall(ApplicationEngine.System_Json_Deserialize); - - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, null)) - { - engine.LoadScript(script.ToArray()); - - Assert.AreEqual(engine.Execute(), VMState.HALT); - Assert.AreEqual(2, engine.ResultStack.Count); - - engine.ResultStack.Pop(); - Assert.IsTrue(engine.ResultStack.Pop().GetInteger() == 123); - } - } - - // Error 1 - Wrong Json - - using (var script = new ScriptBuilder()) - { - script.EmitPush("***"); - script.EmitSysCall(ApplicationEngine.System_Json_Deserialize); - - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, null)) - { - engine.LoadScript(script.ToArray()); - - Assert.AreEqual(engine.Execute(), VMState.FAULT); - Assert.AreEqual(0, engine.ResultStack.Count); - } - } - - // Error 2 - No decimals - - using (var script = new ScriptBuilder()) - { - script.EmitPush("123.45"); - script.EmitSysCall(ApplicationEngine.System_Json_Deserialize); - - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, null)) - { - engine.LoadScript(script.ToArray()); - - Assert.AreEqual(engine.Execute(), VMState.FAULT); - Assert.AreEqual(0, engine.ResultStack.Count); - } - } - } - - [TestMethod] - public void Json_Serialize() - { - // Good - - using (var script = new ScriptBuilder()) - { - script.EmitPush(5); - script.EmitSysCall(ApplicationEngine.System_Json_Serialize); - script.Emit(OpCode.PUSH0); - script.Emit(OpCode.NOT); - script.EmitSysCall(ApplicationEngine.System_Json_Serialize); - script.EmitPush("test"); - script.EmitSysCall(ApplicationEngine.System_Json_Serialize); - script.Emit(OpCode.PUSHNULL); - script.EmitSysCall(ApplicationEngine.System_Json_Serialize); - script.Emit(OpCode.NEWMAP); - script.Emit(OpCode.DUP); - script.EmitPush("key"); - script.EmitPush("value"); - script.Emit(OpCode.SETITEM); - script.EmitSysCall(ApplicationEngine.System_Json_Serialize); - - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, null)) - { - engine.LoadScript(script.ToArray()); - - Assert.AreEqual(engine.Execute(), VMState.HALT); - Assert.AreEqual(5, engine.ResultStack.Count); - - Assert.IsTrue(engine.ResultStack.Pop().GetString() == "{\"key\":\"value\"}"); - Assert.IsTrue(engine.ResultStack.Pop().GetString() == "null"); - Assert.IsTrue(engine.ResultStack.Pop().GetString() == "\"test\""); - Assert.IsTrue(engine.ResultStack.Pop().GetString() == "true"); - Assert.IsTrue(engine.ResultStack.Pop().GetString() == "5"); - } - } - - // Error - - using (var script = new ScriptBuilder()) - { - script.EmitSysCall(ApplicationEngine.System_Storage_GetContext); - script.EmitSysCall(ApplicationEngine.System_Json_Serialize); - - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, null)) - { - engine.LoadScript(script.ToArray()); - - Assert.AreEqual(engine.Execute(), VMState.FAULT); - Assert.AreEqual(0, engine.ResultStack.Count); - } - } - } - [TestMethod] public void System_ExecutionEngine_GetScriptContainer() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); using (var script = new ScriptBuilder()) { script.EmitSysCall(ApplicationEngine.System_Runtime_GetScriptContainer); @@ -231,9 +115,8 @@ public void System_ExecutionEngine_GetScriptContainer() var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); engine.LoadScript(script.ToArray()); - Assert.AreEqual(engine.Execute(), VMState.HALT); - Assert.AreEqual(1, engine.ResultStack.Count); - Assert.IsTrue(engine.ResultStack.Peek().IsNull); + Assert.AreEqual(engine.Execute(), VMState.FAULT); + Assert.AreEqual(0, engine.ResultStack.Count); // With tx @@ -264,7 +147,7 @@ public void System_ExecutionEngine_GetScriptContainer() [TestMethod] public void System_Runtime_GasLeft() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); using (var script = new ScriptBuilder()) { @@ -279,7 +162,7 @@ public void System_Runtime_GasLeft() // Execute - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, 100_000_000); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, gas: 100_000_000); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -316,7 +199,7 @@ public void System_Runtime_GasLeft() public void System_Runtime_GetInvocationCounter() { ContractState contractA, contractB, contractC; - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); // Create dummy contracts @@ -337,9 +220,9 @@ public void System_Runtime_GetInvocationCounter() snapshot.DeleteContract(contractA.Hash); snapshot.DeleteContract(contractB.Hash); snapshot.DeleteContract(contractC.Hash); - contractA.Manifest = TestUtils.CreateManifest("dummyMain", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer); - contractB.Manifest = TestUtils.CreateManifest("dummyMain", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer); - contractC.Manifest = TestUtils.CreateManifest("dummyMain", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer); + contractA.Manifest = TestUtils.CreateManifest("dummyMain", ContractParameterType.Any, ContractParameterType.String, ContractParameterType.Integer); + contractB.Manifest = TestUtils.CreateManifest("dummyMain", ContractParameterType.Any, ContractParameterType.String, ContractParameterType.Integer); + contractC.Manifest = TestUtils.CreateManifest("dummyMain", ContractParameterType.Any, ContractParameterType.String, ContractParameterType.Integer); snapshot.AddContract(contractA.Hash, contractA); snapshot.AddContract(contractB.Hash, contractB); snapshot.AddContract(contractC.Hash, contractC); @@ -349,10 +232,10 @@ public void System_Runtime_GetInvocationCounter() using (var script = new ScriptBuilder()) { - script.EmitDynamicCall(contractA.Hash, "dummyMain", 0, 1); - script.EmitDynamicCall(contractB.Hash, "dummyMain", 0, 1); - script.EmitDynamicCall(contractB.Hash, "dummyMain", 0, 1); - script.EmitDynamicCall(contractC.Hash, "dummyMain", 0, 1); + script.EmitDynamicCall(contractA.Hash, "dummyMain", "0", 1); + script.EmitDynamicCall(contractB.Hash, "dummyMain", "0", 1); + script.EmitDynamicCall(contractB.Hash, "dummyMain", "0", 1); + script.EmitDynamicCall(contractC.Hash, "dummyMain", "0", 1); // Execute diff --git a/tests/neo.UnitTests/TestBlockchain.cs b/tests/neo.UnitTests/TestBlockchain.cs index 18d04454d3..f1ea5eb975 100644 --- a/tests/neo.UnitTests/TestBlockchain.cs +++ b/tests/neo.UnitTests/TestBlockchain.cs @@ -1,8 +1,5 @@ -using Neo.Ledger; +using Neo.Persistence; using System; -using System.Collections.Immutable; -using System.Linq; -using System.Reflection; namespace Neo.UnitTests { @@ -14,25 +11,12 @@ public static class TestBlockchain static TestBlockchain() { Console.WriteLine("initialize NeoSystem"); - TheNeoSystem = new NeoSystem(); - - // Ensure that blockchain is loaded - - var bc = Blockchain.Singleton; - - DefaultExtensibleWitnessWhiteList = (typeof(Blockchain).GetField("extensibleWitnessWhiteList", - BindingFlags.Instance | BindingFlags.NonPublic).GetValue(bc) as ImmutableHashSet).ToArray(); - AddWhiteList(DefaultExtensibleWitnessWhiteList); // Add other address + TheNeoSystem = new NeoSystem(ProtocolSettings.Default, null, null); } - public static void InitializeMockNeoSystem() { } - - public static void AddWhiteList(params UInt160[] address) + internal static DataCache GetTestSnapshot() { - var builder = ImmutableHashSet.CreateBuilder(); - foreach (var entry in address) builder.Add(entry); - - typeof(Blockchain).GetField("extensibleWitnessWhiteList", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(Blockchain.Singleton, builder.ToImmutable()); + return TheNeoSystem.GetSnapshot().CreateSnapshot(); } } } diff --git a/tests/neo.UnitTests/TestUtils.cs b/tests/neo.UnitTests/TestUtils.cs index d68ba3a93f..bb125d3875 100644 --- a/tests/neo.UnitTests/TestUtils.cs +++ b/tests/neo.UnitTests/TestUtils.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using Neo.Cryptography; using Neo.IO; using Neo.IO.Json; using Neo.Network.P2P.Payloads; @@ -91,12 +92,12 @@ public static NEP6Wallet GenerateTestWallet() { JObject wallet = new JObject(); wallet["name"] = "noname"; - wallet["version"] = new Version("3.0").ToString(); + wallet["version"] = new Version("1.0").ToString(); wallet["scrypt"] = new ScryptParameters(2, 1, 1).ToJson(); wallet["accounts"] = new JArray(); wallet["extra"] = null; - wallet.ToString().Should().Be("{\"name\":\"noname\",\"version\":\"3.0\",\"scrypt\":{\"n\":2,\"r\":1,\"p\":1},\"accounts\":[],\"extra\":null}"); - return new NEP6Wallet(wallet); + wallet.ToString().Should().Be("{\"name\":\"noname\",\"version\":\"1.0\",\"scrypt\":{\"n\":2,\"r\":1,\"p\":1},\"accounts\":[],\"extra\":null}"); + return new NEP6Wallet(null, ProtocolSettings.Default, wallet); } public static Transaction GetTransaction(UInt160 sender) @@ -158,44 +159,36 @@ internal static StorageKey GetStorageKey(int id, byte[] keyValue) public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal) { - setupBlockBaseWithValues(header, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal); + header.PrevHash = val256; + header.MerkleRoot = merkRootVal = UInt256.Parse("0x6226416a0e5aca42b5566f5a19ab467692688ba9d47986f6981a7f747bba2772"); + header.Timestamp = timestampVal = new DateTime(1980, 06, 01, 0, 0, 1, 001, DateTimeKind.Utc).ToTimestampMS(); // GMT: Sunday, June 1, 1980 12:00:01.001 AM + header.Index = indexVal = 0; + header.NextConsensus = val160 = UInt160.Zero; + header.Witness = scriptVal = new Witness + { + InvocationScript = new byte[0], + VerificationScript = new[] { (byte)OpCode.PUSH1 } + }; } public static void SetupBlockWithValues(Block block, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, int numberOfTransactions) { - setupBlockBaseWithValues(block, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal); + Header header = new Header(); + SetupHeaderWithValues(header, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal); transactionsVal = new Transaction[numberOfTransactions]; if (numberOfTransactions > 0) { for (int i = 0; i < numberOfTransactions; i++) { - transactionsVal[i] = TestUtils.GetTransaction(UInt160.Zero); + transactionsVal[i] = GetTransaction(UInt160.Zero); } } - block.ConsensusData = new ConsensusData(); + block.Header = header; block.Transactions = transactionsVal; - block.MerkleRoot = merkRootVal = Block.CalculateMerkleRoot(block.ConsensusData.Hash, block.Transactions.Select(p => p.Hash)); - } - private static void setupBlockBaseWithValues(BlockBase bb, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal) - { - bb.PrevHash = val256; - merkRootVal = UInt256.Parse("0x6226416a0e5aca42b5566f5a19ab467692688ba9d47986f6981a7f747bba2772"); - bb.MerkleRoot = merkRootVal; - timestampVal = new DateTime(1980, 06, 01, 0, 0, 1, 001, DateTimeKind.Utc).ToTimestampMS(); // GMT: Sunday, June 1, 1980 12:00:01.001 AM - bb.Timestamp = timestampVal; - indexVal = 0; - bb.Index = indexVal; - val160 = UInt160.Zero; - bb.NextConsensus = val160; - scriptVal = new Witness - { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSH1 } - }; - bb.Witness = scriptVal; + header.MerkleRoot = merkRootVal = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); } public static Transaction CreateRandomHashTransaction() diff --git a/tests/neo.UnitTests/TestWalletAccount.cs b/tests/neo.UnitTests/TestWalletAccount.cs index b8a242c578..fdb986194a 100644 --- a/tests/neo.UnitTests/TestWalletAccount.cs +++ b/tests/neo.UnitTests/TestWalletAccount.cs @@ -13,7 +13,7 @@ class TestWalletAccount : WalletAccount public override KeyPair GetKey() => key; public TestWalletAccount(UInt160 hash) - : base(hash) + : base(hash, ProtocolSettings.Default) { var mock = new Mock(); mock.SetupGet(p => p.ScriptHash).Returns(hash); diff --git a/tests/neo.UnitTests/UT_DataCache.cs b/tests/neo.UnitTests/UT_DataCache.cs index ca15234c9b..951c2275d1 100644 --- a/tests/neo.UnitTests/UT_DataCache.cs +++ b/tests/neo.UnitTests/UT_DataCache.cs @@ -1,5 +1,4 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Ledger; using Neo.Persistence; using Neo.SmartContract; using System.Linq; @@ -9,43 +8,37 @@ namespace Neo.UnitTests [TestClass] public class UT_DataCache { - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void TestCachedFind_Between() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var storages = snapshot.CreateSnapshot(); var cache = new ClonedCache(storages); storages.Add ( new StorageKey() { Key = new byte[] { 0x01, 0x01 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); storages.Add ( new StorageKey() { Key = new byte[] { 0x00, 0x01 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); storages.Add ( new StorageKey() { Key = new byte[] { 0x00, 0x03 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); cache.Add ( new StorageKey() { Key = new byte[] { 0x01, 0x02 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); cache.Add ( new StorageKey() { Key = new byte[] { 0x00, 0x02 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); CollectionAssert.AreEqual( @@ -57,29 +50,29 @@ public void TestCachedFind_Between() [TestMethod] public void TestCachedFind_Last() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var storages = snapshot.CreateSnapshot(); var cache = new ClonedCache(storages); storages.Add ( new StorageKey() { Key = new byte[] { 0x00, 0x01 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); storages.Add ( new StorageKey() { Key = new byte[] { 0x01, 0x01 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); cache.Add ( new StorageKey() { Key = new byte[] { 0x00, 0x02 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); cache.Add ( new StorageKey() { Key = new byte[] { 0x01, 0x02 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); CollectionAssert.AreEqual(cache.Find(new byte[5]).Select(u => u.Key.Key[1]).ToArray(), new byte[] { 0x01, 0x02 } @@ -89,19 +82,19 @@ public void TestCachedFind_Last() [TestMethod] public void TestCachedFind_Empty() { - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var storages = snapshot.CreateSnapshot(); var cache = new ClonedCache(storages); cache.Add ( new StorageKey() { Key = new byte[] { 0x00, 0x02 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); cache.Add ( new StorageKey() { Key = new byte[] { 0x01, 0x02 }, Id = 0 }, - new StorageItem() { IsConstant = false, Value = new byte[] { } } + new StorageItem() { Value = new byte[] { } } ); CollectionAssert.AreEqual( diff --git a/tests/neo.UnitTests/UT_Helper.cs b/tests/neo.UnitTests/UT_Helper.cs index 9151d50c13..c22d303191 100644 --- a/tests/neo.UnitTests/UT_Helper.cs +++ b/tests/neo.UnitTests/UT_Helper.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Net; using System.Numerics; -using System.Security.Cryptography; namespace Neo.UnitTests { @@ -17,18 +16,18 @@ namespace Neo.UnitTests public class UT_Helper { [TestMethod] - public void GetHashData() + public void GetSignData() { TestVerifiable verifiable = new TestVerifiable(); - byte[] res = verifiable.GetHashData(); - res.ToHexString().Should().Be("4e454f000774657374537472"); + byte[] res = verifiable.GetSignData(ProtocolSettings.Default.Magic); + res.ToHexString().Should().Be("4e454f0050b51da6bb366be3ea50140cda45ba7df575287c0371000b2037ed3898ff8bf5"); } [TestMethod] public void Sign() { TestVerifiable verifiable = new TestVerifiable(); - byte[] res = verifiable.Sign(new KeyPair(TestUtils.GetByteArray(32, 0x42))); + byte[] res = verifiable.Sign(new KeyPair(TestUtils.GetByteArray(32, 0x42)), ProtocolSettings.Default.Magic); res.Length.Should().Be(64); } @@ -171,15 +170,12 @@ public void TestToHexString() [TestMethod] public void TestGetVersion() { - string version = typeof(TestMethodAttribute).Assembly.GetVersion(); - version.Should().Be("14.0.4908.02"); - // assembly without version var asm = AppDomain.CurrentDomain.GetAssemblies() .Where(u => u.FullName == "Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null") .FirstOrDefault(); - version = asm?.GetVersion() ?? ""; + string version = asm?.GetVersion() ?? ""; version.Should().Be("0.0.0"); } @@ -205,18 +201,6 @@ public void TestNextBigIntegerForRandom() ran.NextBigInteger(9).Should().NotBeNull(); } - [TestMethod] - public void TestNextBigIntegerForRandomNumberGenerator() - { - var ran = RandomNumberGenerator.Create(); - Action action1 = () => ran.NextBigInteger(-1); - action1.Should().Throw(); - - ran.NextBigInteger(0).Should().Be(0); - ran.NextBigInteger(8).Should().NotBeNull(); - ran.NextBigInteger(9).Should().NotBeNull(); - } - [TestMethod] public void TestUnmapForIPAddress() { diff --git a/tests/neo.UnitTests/UT_ProtocolSettings.cs b/tests/neo.UnitTests/UT_ProtocolSettings.cs index bce97cd5d8..77cd48c0f6 100644 --- a/tests/neo.UnitTests/UT_ProtocolSettings.cs +++ b/tests/neo.UnitTests/UT_ProtocolSettings.cs @@ -1,44 +1,20 @@ using FluentAssertions; -using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Native; using Neo.Wallets; -using System.Collections.Generic; -using System.IO; -using System.Reflection; namespace Neo.UnitTests { [TestClass] public class UT_ProtocolSettings { - // since ProtocolSettings.Default is designed to be writable only once, use reflection to - // reset the underlying _default field to null before and after running tests in this class. - static void ResetProtocolSettings() - { - var defaultField = typeof(ProtocolSettings) - .GetField("_default", BindingFlags.Static | BindingFlags.NonPublic); - defaultField.SetValue(null, null); - } - - [TestInitialize] - public void Initialize() - { - ResetProtocolSettings(); - } - - [TestCleanup] - public void Cleanup() - { - ResetProtocolSettings(); - } - [TestMethod] public void CheckFirstLetterOfAddresses() { UInt160 min = UInt160.Parse("0x0000000000000000000000000000000000000000"); - min.ToAddress()[0].Should().Be('N'); + min.ToAddress(ProtocolSettings.Default.AddressVersion)[0].Should().Be('N'); UInt160 max = UInt160.Parse("0xffffffffffffffffffffffffffffffffffffffff"); - max.ToAddress()[0].Should().Be('N'); + max.ToAddress(ProtocolSettings.Default.AddressVersion)[0].Should().Be('N'); } [TestMethod] @@ -48,80 +24,6 @@ public void Default_Magic_should_be_mainnet_Magic_value() ProtocolSettings.Default.Magic.Should().Be(mainNetMagic); } - [TestMethod] - public void Can_initialize_ProtocolSettings() - { - var expectedMagic = 12345u; - - var dict = new Dictionary() - { - { "ProtocolConfiguration:Magic", $"{expectedMagic}" } - }; - - var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); - ProtocolSettings.Initialize(config).Should().BeTrue(); - ProtocolSettings.Default.Magic.Should().Be(expectedMagic); - } - - [TestMethod] - public void Initialize_ProtocolSettings_NativeBlockIndex() - { - var tempFile = Path.GetTempFileName(); - File.WriteAllText(tempFile, @" -{ - ""ProtocolConfiguration"": - { - ""NativeActivations"":{ ""test"":123 } - } -} -"); - var config = new ConfigurationBuilder().AddJsonFile(tempFile).Build(); - File.Delete(tempFile); - - ProtocolSettings.Initialize(config).Should().BeTrue(); - ProtocolSettings.Default.NativeActivations.Count.Should().Be(1); - ProtocolSettings.Default.NativeActivations["test"].Should().Be(123); - } - - [TestMethod] - public void Cant_initialize_ProtocolSettings_after_default_settings_used() - { - var mainNetMagic = 0x4F454Eu; - ProtocolSettings.Default.Magic.Should().Be(mainNetMagic); - - var updatedMagic = 54321u; - var dict = new Dictionary() - { - { "ProtocolConfiguration:Magic", $"{updatedMagic}" } - }; - - var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); - ProtocolSettings.Initialize(config).Should().BeFalse(); - ProtocolSettings.Default.Magic.Should().Be(mainNetMagic); - } - - [TestMethod] - public void Cant_initialize_ProtocolSettings_twice() - { - var expectedMagic = 12345u; - var dict = new Dictionary() - { - { "ProtocolConfiguration:Magic", $"{expectedMagic}" } - }; - - var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); - ProtocolSettings.Initialize(config).Should().BeTrue(); - - var updatedMagic = 54321u; - dict = new Dictionary() - { - { "ProtocolConfiguration:Magic", $"{updatedMagic}" } - }; - config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); - ProtocolSettings.Initialize(config).Should().BeFalse(); - ProtocolSettings.Default.Magic.Should().Be(expectedMagic); - } - [TestMethod] public void TestGetMemoryPoolMaxTransactions() { @@ -131,7 +33,7 @@ public void TestGetMemoryPoolMaxTransactions() [TestMethod] public void TestGetMillisecondsPerBlock() { - ProtocolSettings.Default.MillisecondsPerBlock.Should().Be(200); + ProtocolSettings.Default.MillisecondsPerBlock.Should().Be(15000); } [TestMethod] @@ -141,9 +43,9 @@ public void TestGetSeedList() } [TestMethod] - public void TestNativeActivations() + public void TestNativeUpdateHistory() { - ProtocolSettings.Default.NativeActivations.Count.Should().Be(0); + ProtocolSettings.Default.NativeUpdateHistory.Count.Should().Be(NativeContract.Contracts.Count); } } } diff --git a/tests/neo.UnitTests/UT_Utility.cs b/tests/neo.UnitTests/UT_Utility.cs deleted file mode 100644 index 85f4c00c97..0000000000 --- a/tests/neo.UnitTests/UT_Utility.cs +++ /dev/null @@ -1,22 +0,0 @@ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.IO; - -namespace Neo.UnitTests -{ - [TestClass] - public class UT_Utility - { - [TestMethod] - public void LoadConfig() - { - Assert.IsFalse(File.Exists("test.json")); - Utility.LoadConfig("test").GetSection("test").Value.Should().BeNull(); - - File.WriteAllText("test.json", @"{""test"":1}"); - Assert.IsTrue(File.Exists("test.json")); - Utility.LoadConfig("test").GetSection("test").Value.Should().Be("1"); - File.Delete("test.json"); - } - } -} diff --git a/tests/neo.UnitTests/VM/UT_Helper.cs b/tests/neo.UnitTests/VM/UT_Helper.cs index a903fead4a..e5a1f61985 100644 --- a/tests/neo.UnitTests/VM/UT_Helper.cs +++ b/tests/neo.UnitTests/VM/UT_Helper.cs @@ -158,7 +158,7 @@ public void TestMakeScript() { byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); - Assert.AreEqual("0c14000000000000000000000000000000000000000011c01f0c0962616c616e63654f660c1428b3adab7269f9c2181db3cb741ebf551930e27041627d5b52", + Assert.AreEqual("0c14000000000000000000000000000000000000000011c01f0c0962616c616e63654f660c14cf76e28bd0062c4a478ee35561011319f3cfa4d241627d5b52", testScript.ToHexString()); } diff --git a/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs b/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs index a4f36fa5d5..fb11039d39 100644 --- a/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs +++ b/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs @@ -23,7 +23,7 @@ public static void ClassSetup(TestContext context) byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; _keyPair = new KeyPair(privateKey); - _nep2 = _keyPair.Export("Satoshi", 2, 1, 1); + _nep2 = _keyPair.Export("Satoshi", ProtocolSettings.Default.AddressVersion, 2, 1, 1); } [TestInitialize] @@ -88,7 +88,7 @@ public void TestFromJson() json["contract"] = null; json["extra"] = null; NEP6Account account = NEP6Account.FromJson(json, _wallet); - account.ScriptHash.Should().Be("NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash()); + account.ScriptHash.Should().Be("NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(ProtocolSettings.Default.AddressVersion)); account.Label.Should().BeNull(); account.IsDefault.Should().BeTrue(); account.Lock.Should().BeFalse(); diff --git a/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs b/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs index 1af95ce082..bf1783eddc 100644 --- a/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs +++ b/tests/neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO.Json; +using Neo.SmartContract; using Neo.Wallets; using Neo.Wallets.NEP6; using Neo.Wallets.SQLite; @@ -10,6 +11,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading; +using Contract = Neo.SmartContract.Contract; namespace Neo.UnitTests.Wallets.NEP6 { @@ -39,7 +41,7 @@ public static void ClassInit(TestContext context) } keyPair = new KeyPair(privateKey); testScriptHash = Neo.SmartContract.Contract.CreateSignatureContract(keyPair.PublicKey).ScriptHash; - nep2key = keyPair.Export("123", 2, 1, 1); + nep2key = keyPair.Export("123", ProtocolSettings.Default.AddressVersion, 2, 1, 1); } private NEP6Wallet CreateWallet() @@ -52,7 +54,7 @@ private string CreateWalletFile() rootPath = GetRandomPath(); if (!Directory.Exists(rootPath)) Directory.CreateDirectory(rootPath); string path = Path.Combine(rootPath, "wallet.json"); - File.WriteAllText(path, "{\"name\":\"name\",\"version\":\"3.0\",\"scrypt\":{\"n\":2,\"r\":1,\"p\":1},\"accounts\":[],\"extra\":{}}"); + File.WriteAllText(path, "{\"name\":\"name\",\"version\":\"1.0\",\"scrypt\":{\"n\":2,\"r\":1,\"p\":1},\"accounts\":[],\"extra\":{}}"); return path; } @@ -75,12 +77,12 @@ public void TestChangePassword() { JObject wallet = new JObject(); wallet["name"] = "name"; - wallet["version"] = new System.Version("3.0").ToString(); + wallet["version"] = new System.Version("1.0").ToString(); wallet["scrypt"] = new ScryptParameters(2, 1, 1).ToJson(); wallet["accounts"] = new JArray(); wallet["extra"] = new JObject(); File.WriteAllText(wPath, wallet.ToString()); - uut = new NEP6Wallet(wPath); + uut = new NEP6Wallet(wPath, ProtocolSettings.Default); uut.Unlock("123"); uut.CreateAccount(keyPair.PrivateKey); uut.ChangePassword("456", "123").Should().BeFalse(); @@ -93,14 +95,14 @@ public void TestChangePassword() [TestMethod] public void TestConstructorWithPathAndName() { - NEP6Wallet wallet = new NEP6Wallet(wPath); + NEP6Wallet wallet = new NEP6Wallet(wPath, ProtocolSettings.Default); Assert.AreEqual("name", wallet.Name); Assert.AreEqual(new ScryptParameters(2, 1, 1).ToJson().ToString(), wallet.Scrypt.ToJson().ToString()); - Assert.AreEqual(new Version("3.0").ToString(), wallet.Version.ToString()); - wallet = new NEP6Wallet("", "test"); + Assert.AreEqual(new Version("1.0").ToString(), wallet.Version.ToString()); + wallet = new NEP6Wallet("", ProtocolSettings.Default, "test"); Assert.AreEqual("test", wallet.Name); Assert.AreEqual(ScryptParameters.Default.ToJson().ToString(), wallet.Scrypt.ToJson().ToString()); - Assert.AreEqual(Version.Parse("3.0"), wallet.Version); + Assert.AreEqual(Version.Parse("1.0"), wallet.Version); } [TestMethod] @@ -108,14 +110,14 @@ public void TestConstructorWithJObject() { JObject wallet = new JObject(); wallet["name"] = "test"; - wallet["version"] = Version.Parse("3.0").ToString(); + wallet["version"] = Version.Parse("1.0").ToString(); wallet["scrypt"] = ScryptParameters.Default.ToJson(); wallet["accounts"] = new JArray(); wallet["extra"] = new JObject(); - wallet.ToString().Should().Be("{\"name\":\"test\",\"version\":\"3.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[],\"extra\":{}}"); - NEP6Wallet w = new NEP6Wallet(wallet); + wallet.ToString().Should().Be("{\"name\":\"test\",\"version\":\"1.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[],\"extra\":{}}"); + NEP6Wallet w = new NEP6Wallet(null, ProtocolSettings.Default, wallet); Assert.AreEqual("test", w.Name); - Assert.AreEqual(Version.Parse("3.0").ToString(), w.Version.ToString()); + Assert.AreEqual(Version.Parse("1.0").ToString(), w.Version.ToString()); } [TestMethod] @@ -127,7 +129,7 @@ public void TestGetName() [TestMethod] public void TestGetVersion() { - Assert.AreEqual(new System.Version("3.0").ToString(), uut.Version.ToString()); + Assert.AreEqual(new System.Version("1.0").ToString(), uut.Version.ToString()); } [TestMethod] @@ -205,7 +207,7 @@ public void TestCreateAccountWithScriptHash() [TestMethod] public void TestDecryptKey() { - string nep2key = keyPair.Export("123", 2, 1, 1); + string nep2key = keyPair.Export("123", ProtocolSettings.Default.AddressVersion, 2, 1, 1); uut.Unlock("123"); KeyPair key1 = uut.DecryptKey(nep2key); bool result = key1.Equals(keyPair); @@ -235,13 +237,13 @@ public void TestGetAccount() result = uut.Contains(testScriptHash); Assert.AreEqual(true, result); WalletAccount account = uut.GetAccount(testScriptHash); - Assert.AreEqual(Neo.SmartContract.Contract.CreateSignatureContract(keyPair.PublicKey).Address, account.Address); + Assert.AreEqual(Contract.CreateSignatureRedeemScript(keyPair.PublicKey).ToScriptHash().ToAddress(ProtocolSettings.Default.AddressVersion), account.Address); } [TestMethod] public void TestGetAccounts() { - Dictionary keys = new Dictionary(); + Dictionary keys = new Dictionary(); uut.Unlock("123"); byte[] privateKey = new byte[32]; using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) @@ -249,14 +251,13 @@ public void TestGetAccounts() rng.GetBytes(privateKey); } KeyPair key = new KeyPair(privateKey); - Neo.SmartContract.Contract contract = Neo.SmartContract.Contract.CreateSignatureContract(key.PublicKey); - keys.Add(contract.Address, key); - keys.Add(Neo.SmartContract.Contract.CreateSignatureContract(keyPair.PublicKey).Address, keyPair); + keys.Add(Contract.CreateSignatureRedeemScript(key.PublicKey).ToScriptHash(), key); + keys.Add(Contract.CreateSignatureRedeemScript(keyPair.PublicKey).ToScriptHash(), keyPair); uut.CreateAccount(key.PrivateKey); uut.CreateAccount(keyPair.PrivateKey); foreach (var account in uut.GetAccounts()) { - if (!keys.TryGetValue(account.Address, out KeyPair k)) + if (!keys.ContainsKey(account.ScriptHash)) { Assert.Fail(); } @@ -315,11 +316,11 @@ public void TestImportNep2() Assert.AreEqual(false, result); JObject wallet = new JObject(); wallet["name"] = "name"; - wallet["version"] = new Version("3.0").ToString(); + wallet["version"] = new Version("1.0").ToString(); wallet["scrypt"] = new ScryptParameters(2, 1, 1).ToJson(); wallet["accounts"] = new JArray(); wallet["extra"] = new JObject(); - uut = new NEP6Wallet(wallet); + uut = new NEP6Wallet(null, ProtocolSettings.Default, wallet); result = uut.Contains(testScriptHash); Assert.AreEqual(false, result); uut.Import(nep2key, "123", 2, 1, 1); @@ -344,10 +345,10 @@ public void TestLock() public void TestMigrate() { string path = GetRandomPath(); - UserWallet uw = UserWallet.Create(path, "123"); + UserWallet uw = UserWallet.Create(path, "123", ProtocolSettings.Default); uw.CreateAccount(keyPair.PrivateKey); string npath = CreateWalletFile(); // Scrypt test values - NEP6Wallet nw = NEP6Wallet.Migrate(npath, path, "123"); + NEP6Wallet nw = NEP6Wallet.Migrate(npath, path, "123", ProtocolSettings.Default); bool result = nw.Contains(testScriptHash); Assert.AreEqual(true, result); if (File.Exists(path)) File.Delete(path); @@ -359,12 +360,12 @@ public void TestSave() { JObject wallet = new JObject(); wallet["name"] = "name"; - wallet["version"] = new System.Version("3.0").ToString(); + wallet["version"] = new System.Version("1.0").ToString(); wallet["scrypt"] = new ScryptParameters(2, 1, 1).ToJson(); wallet["accounts"] = new JArray(); wallet["extra"] = new JObject(); File.WriteAllText(wPath, wallet.ToString()); - uut = new NEP6Wallet(wPath); + uut = new NEP6Wallet(wPath, ProtocolSettings.Default); uut.Unlock("123"); uut.CreateAccount(keyPair.PrivateKey); bool result = uut.Contains(testScriptHash); @@ -401,12 +402,12 @@ public void TestVerifyPassword() Assert.AreEqual(false, uut.Contains(testScriptHash)); JObject wallet = new JObject(); wallet["name"] = "name"; - wallet["version"] = new Version("3.0").ToString(); + wallet["version"] = new Version("1.0").ToString(); wallet["scrypt"] = new ScryptParameters(2, 1, 1).ToJson(); wallet["accounts"] = new JArray(); wallet["extra"] = new JObject(); - uut = new NEP6Wallet(wallet); - nep2key = keyPair.Export("123", 2, 1, 1); + uut = new NEP6Wallet(null, ProtocolSettings.Default, wallet); + nep2key = keyPair.Export("123", ProtocolSettings.Default.AddressVersion, 2, 1, 1); uut.Import(nep2key, "123", 2, 1, 1); Assert.IsFalse(uut.VerifyPassword("1")); Assert.IsTrue(uut.VerifyPassword("123")); @@ -416,7 +417,7 @@ public void TestVerifyPassword() public void Test_NEP6Wallet_Json() { uut.Name.Should().Be("noname"); - uut.Version.Should().Be(new Version("3.0")); + uut.Version.Should().Be(new Version("1.0")); uut.Scrypt.Should().NotBeNull(); uut.Scrypt.N.Should().Be(new ScryptParameters(2, 1, 1).N); } diff --git a/tests/neo.UnitTests/Wallets/SQLite/UT_UserWallet.cs b/tests/neo.UnitTests/Wallets/SQLite/UT_UserWallet.cs index e6cb2e821e..b56dbede0a 100644 --- a/tests/neo.UnitTests/Wallets/SQLite/UT_UserWallet.cs +++ b/tests/neo.UnitTests/Wallets/SQLite/UT_UserWallet.cs @@ -29,7 +29,7 @@ public static string GetRandomPath() public static void Setup(TestContext ctx) { path = GetRandomPath(); - wallet = UserWallet.Create(path, "123456", new ScryptParameters(2, 1, 1)); + wallet = UserWallet.Create(path, "123456", ProtocolSettings.Default, new ScryptParameters(2, 1, 1)); byte[] privateKey = new byte[32]; using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) { @@ -131,14 +131,14 @@ public void TestCreateAndOpenSecureString() ss.AppendChar('b'); ss.AppendChar('c'); - var w1 = UserWallet.Create(myPath, ss, new ScryptParameters(0, 0, 0)); + var w1 = UserWallet.Create(myPath, ss, ProtocolSettings.Default, new ScryptParameters(0, 0, 0)); w1.Should().NotBeNull(); - var w2 = UserWallet.Open(myPath, ss); + var w2 = UserWallet.Open(myPath, ss, ProtocolSettings.Default); w2.Should().NotBeNull(); ss.AppendChar('d'); - Action action = () => UserWallet.Open(myPath, ss); + Action action = () => UserWallet.Open(myPath, ss, ProtocolSettings.Default); action.Should().Throw(); TestUtils.DeleteFile(myPath); @@ -171,10 +171,10 @@ public void TestGetVersion() [TestMethod] public void TestOpen() { - var w1 = UserWallet.Open(path, "123456"); + var w1 = UserWallet.Open(path, "123456", ProtocolSettings.Default); w1.Should().NotBeNull(); - Action action = () => UserWallet.Open(path, "123"); + Action action = () => UserWallet.Open(path, "123", ProtocolSettings.Default); action.Should().Throw(); } diff --git a/tests/neo.UnitTests/Wallets/SQLite/UT_UserWalletAccount.cs b/tests/neo.UnitTests/Wallets/SQLite/UT_UserWalletAccount.cs index fe4fd48cb3..be3d756b19 100644 --- a/tests/neo.UnitTests/Wallets/SQLite/UT_UserWalletAccount.cs +++ b/tests/neo.UnitTests/Wallets/SQLite/UT_UserWalletAccount.cs @@ -9,21 +9,21 @@ public class UT_UserWalletAccount [TestMethod] public void TestGenerator() { - UserWalletAccount account = new UserWalletAccount(UInt160.Zero); + UserWalletAccount account = new UserWalletAccount(UInt160.Zero, ProtocolSettings.Default); Assert.IsNotNull(account); } [TestMethod] public void TestGetHasKey() { - UserWalletAccount account = new UserWalletAccount(UInt160.Zero); + UserWalletAccount account = new UserWalletAccount(UInt160.Zero, ProtocolSettings.Default); Assert.AreEqual(false, account.HasKey); } [TestMethod] public void TestGetKey() { - UserWalletAccount account = new UserWalletAccount(UInt160.Zero); + UserWalletAccount account = new UserWalletAccount(UInt160.Zero, ProtocolSettings.Default); Assert.AreEqual(null, account.GetKey()); } } diff --git a/tests/neo.UnitTests/Wallets/SQLite/UT_VerificationContract.cs b/tests/neo.UnitTests/Wallets/SQLite/UT_VerificationContract.cs index f89ca08e6b..879eda73b0 100644 --- a/tests/neo.UnitTests/Wallets/SQLite/UT_VerificationContract.cs +++ b/tests/neo.UnitTests/Wallets/SQLite/UT_VerificationContract.cs @@ -116,11 +116,11 @@ public void TestSerialize() }; byte[] byteArray = contract1.ToArray(); byte[] script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey); - byte[] result = new byte[44]; + byte[] result = new byte[43]; result[0] = 0x01; result[1] = (byte)ContractParameterType.Signature; - result[2] = 0x29; - Array.Copy(script, 0, result, 3, 41); + result[2] = 0x28; + Array.Copy(script, 0, result, 3, 40); CollectionAssert.AreEqual(result, byteArray); } @@ -138,7 +138,7 @@ public void TestGetSize() Script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), ParameterList = new[] { ContractParameterType.Signature } }; - Assert.AreEqual(44, contract1.Size); + Assert.AreEqual(43, contract1.Size); } } } diff --git a/tests/neo.UnitTests/Wallets/UT_AssetDescriptor.cs b/tests/neo.UnitTests/Wallets/UT_AssetDescriptor.cs index a7920ded24..d87811cf7f 100644 --- a/tests/neo.UnitTests/Wallets/UT_AssetDescriptor.cs +++ b/tests/neo.UnitTests/Wallets/UT_AssetDescriptor.cs @@ -8,18 +8,13 @@ namespace Neo.UnitTests.Wallets [TestClass] public class UT_AssetDescriptor { - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void TestConstructorWithNonexistAssetId() { + var snapshot = TestBlockchain.GetTestSnapshot(); Action action = () => { - var descriptor = new Neo.Wallets.AssetDescriptor(UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4")); + var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, ProtocolSettings.Default, UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4")); }; action.Should().Throw(); } @@ -27,7 +22,8 @@ public void TestConstructorWithNonexistAssetId() [TestMethod] public void Check_GAS() { - var descriptor = new Neo.Wallets.AssetDescriptor(NativeContract.GAS.Hash); + var snapshot = TestBlockchain.GetTestSnapshot(); + var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, ProtocolSettings.Default, NativeContract.GAS.Hash); descriptor.AssetId.Should().Be(NativeContract.GAS.Hash); descriptor.AssetName.Should().Be(nameof(GasToken)); descriptor.ToString().Should().Be(nameof(GasToken)); @@ -38,7 +34,8 @@ public void Check_GAS() [TestMethod] public void Check_NEO() { - var descriptor = new Neo.Wallets.AssetDescriptor(NativeContract.NEO.Hash); + var snapshot = TestBlockchain.GetTestSnapshot(); + var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, ProtocolSettings.Default, NativeContract.NEO.Hash); descriptor.AssetId.Should().Be(NativeContract.NEO.Hash); descriptor.AssetName.Should().Be(nameof(NeoToken)); descriptor.ToString().Should().Be(nameof(NeoToken)); diff --git a/tests/neo.UnitTests/Wallets/UT_Wallet.cs b/tests/neo.UnitTests/Wallets/UT_Wallet.cs index 75d38c25a1..2bc76c0007 100644 --- a/tests/neo.UnitTests/Wallets/UT_Wallet.cs +++ b/tests/neo.UnitTests/Wallets/UT_Wallet.cs @@ -1,7 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; -using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -21,6 +20,10 @@ internal class MyWallet : Wallet Dictionary accounts = new Dictionary(); + public MyWallet() : base(null, ProtocolSettings.Default) + { + } + public override bool ChangePassword(string oldPassword, string newPassword) { throw new NotImplementedException(); @@ -101,13 +104,7 @@ public class UT_Wallet public static void ClassInit(TestContext context) { glkey = UT_Crypto.generateCertainKey(32); - nep2Key = glkey.Export("pwd", 2, 1, 1); - } - - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); + nep2Key = glkey.Export("pwd", ProtocolSettings.Default.AddressVersion, 2, 1, 1); } [TestMethod] @@ -171,9 +168,9 @@ public void TestGetVersion() public void TestGetAccount1() { MyWallet wallet = new MyWallet(); - wallet.CreateAccount(UInt160.Parse("0xf55f6873ae944cf4ec9626e8855b8554e798a7d1")); + wallet.CreateAccount(UInt160.Parse("0xd92defaf95bf5307ffbc3ab1c5cdf7ddfc1b89b3")); WalletAccount account = wallet.GetAccount(ECCurve.Secp256r1.G); - account.ScriptHash.Should().Be(UInt160.Parse("0xf55f6873ae944cf4ec9626e8855b8554e798a7d1")); + account.ScriptHash.Should().Be(UInt160.Parse("0xd92defaf95bf5307ffbc3ab1c5cdf7ddfc1b89b3")); } [TestMethod] @@ -201,13 +198,13 @@ public void TestGetAvailable() account.Lock = false; // Fake balance - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; snapshot.Commit(); - wallet.GetAvailable(NativeContract.GAS.Hash).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); + wallet.GetAvailable(snapshot, NativeContract.GAS.Hash).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 0; @@ -223,14 +220,14 @@ public void TestGetBalance() account.Lock = false; // Fake balance - var snapshot = Blockchain.Singleton.GetSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshot(); var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; snapshot.Commit(); - wallet.GetBalance(UInt160.Zero, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(BigInteger.Zero, 0)); - wallet.GetBalance(NativeContract.GAS.Hash, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); + wallet.GetBalance(snapshot, UInt160.Zero, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(BigInteger.Zero, 0)); + wallet.GetBalance(snapshot, NativeContract.GAS.Hash, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 0; @@ -240,19 +237,19 @@ public void TestGetBalance() [TestMethod] public void TestGetPrivateKeyFromNEP2() { - Action action = () => Wallet.GetPrivateKeyFromNEP2(null, null, 2, 1, 1); + Action action = () => Wallet.GetPrivateKeyFromNEP2(null, null, ProtocolSettings.Default.AddressVersion, 2, 1, 1); action.Should().Throw(); - action = () => Wallet.GetPrivateKeyFromNEP2("TestGetPrivateKeyFromNEP2", null, 2, 1, 1); + action = () => Wallet.GetPrivateKeyFromNEP2("TestGetPrivateKeyFromNEP2", null, ProtocolSettings.Default.AddressVersion, 2, 1, 1); action.Should().Throw(); - action = () => Wallet.GetPrivateKeyFromNEP2("3vQB7B6MrGQZaxCuFg4oh", "TestGetPrivateKeyFromNEP2", 2, 1, 1); + action = () => Wallet.GetPrivateKeyFromNEP2("3vQB7B6MrGQZaxCuFg4oh", "TestGetPrivateKeyFromNEP2", ProtocolSettings.Default.AddressVersion, 2, 1, 1); action.Should().Throw(); - action = () => Wallet.GetPrivateKeyFromNEP2(nep2Key, "Test", 2, 1, 1); + action = () => Wallet.GetPrivateKeyFromNEP2(nep2Key, "Test", ProtocolSettings.Default.AddressVersion, 2, 1, 1); action.Should().Throw(); - Wallet.GetPrivateKeyFromNEP2(nep2Key, "pwd", 2, 1, 1).Should().BeEquivalentTo(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }); + Wallet.GetPrivateKeyFromNEP2(nep2Key, "pwd", ProtocolSettings.Default.AddressVersion, 2, 1, 1).Should().BeEquivalentTo(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }); } [TestMethod] @@ -284,12 +281,13 @@ public void TestImport2() [TestMethod] public void TestMakeTransaction1() { + var snapshot = TestBlockchain.GetTestSnapshot(); MyWallet wallet = new MyWallet(); Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); account.Lock = false; - Action action = () => wallet.MakeTransaction(new TransferOutput[] + Action action = () => wallet.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -301,7 +299,7 @@ public void TestMakeTransaction1() }, UInt160.Zero); action.Should().Throw(); - action = () => wallet.MakeTransaction(new TransferOutput[] + action = () => wallet.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -313,7 +311,7 @@ public void TestMakeTransaction1() }, account.ScriptHash); action.Should().Throw(); - action = () => wallet.MakeTransaction(new TransferOutput[] + action = () => wallet.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -326,7 +324,6 @@ public void TestMakeTransaction1() action.Should().Throw(); // Fake balance - var snapshot = Blockchain.Singleton.GetSnapshot(); var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); var entry1 = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry1.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; @@ -337,7 +334,7 @@ public void TestMakeTransaction1() snapshot.Commit(); - var tx = wallet.MakeTransaction(new TransferOutput[] + var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -348,7 +345,7 @@ public void TestMakeTransaction1() }); tx.Should().NotBeNull(); - tx = wallet.MakeTransaction(new TransferOutput[] + tx = wallet.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() { @@ -370,8 +367,9 @@ public void TestMakeTransaction1() [TestMethod] public void TestMakeTransaction2() { + var snapshot = TestBlockchain.GetTestSnapshot(); MyWallet wallet = new MyWallet(); - Action action = () => wallet.MakeTransaction(new byte[] { }, null, null, Array.Empty()); + Action action = () => wallet.MakeTransaction(snapshot, new byte[] { }, null, null, Array.Empty()); action.Should().Throw(); Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); @@ -379,13 +377,12 @@ public void TestMakeTransaction2() account.Lock = false; // Fake balance - var snapshot = Blockchain.Singleton.GetSnapshot(); var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 1000000 * NativeContract.GAS.Factor; snapshot.Commit(); - var tx = wallet.MakeTransaction(new byte[] { }, account.ScriptHash, new[]{ new Signer() + var tx = wallet.MakeTransaction(snapshot, new byte[] { }, account.ScriptHash, new[]{ new Signer() { Account = account.ScriptHash, Scopes = WitnessScope.CalledByEntry @@ -393,7 +390,7 @@ public void TestMakeTransaction2() tx.Should().NotBeNull(); - tx = wallet.MakeTransaction(new byte[] { }, null, null, Array.Empty()); + tx = wallet.MakeTransaction(snapshot, new byte[] { }, null, null, Array.Empty()); tx.Should().NotBeNull(); entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); diff --git a/tests/neo.UnitTests/Wallets/UT_WalletAccount.cs b/tests/neo.UnitTests/Wallets/UT_WalletAccount.cs index 42ebf6bb63..13ba4d113f 100644 --- a/tests/neo.UnitTests/Wallets/UT_WalletAccount.cs +++ b/tests/neo.UnitTests/Wallets/UT_WalletAccount.cs @@ -11,7 +11,7 @@ public class MyWalletAccount : WalletAccount public override bool HasKey => key != null; public MyWalletAccount(UInt160 scriptHash) - : base(scriptHash) + : base(scriptHash, ProtocolSettings.Default) { } diff --git a/tests/neo.UnitTests/Wallets/UT_Wallets_Helper.cs b/tests/neo.UnitTests/Wallets/UT_Wallets_Helper.cs index 9f0167c55a..8c5f23f5a4 100644 --- a/tests/neo.UnitTests/Wallets/UT_Wallets_Helper.cs +++ b/tests/neo.UnitTests/Wallets/UT_Wallets_Helper.cs @@ -15,18 +15,18 @@ public void TestToScriptHash() { byte[] array = { 0x01 }; UInt160 scriptHash = new UInt160(Crypto.Hash160(array)); - "NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash().Should().Be(scriptHash); + "NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(ProtocolSettings.Default.AddressVersion).Should().Be(scriptHash); - Action action = () => "3vQB7B6MrGQZaxCuFg4oh".ToScriptHash(); + Action action = () => "3vQB7B6MrGQZaxCuFg4oh".ToScriptHash(ProtocolSettings.Default.AddressVersion); action.Should().Throw(); - var address = scriptHash.ToAddress(); + var address = scriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); Span data = stackalloc byte[21]; // NEO version is 0x17 data[0] = 0x01; scriptHash.ToArray().CopyTo(data[1..]); address = Base58.Base58CheckEncode(data); - action = () => address.ToScriptHash(); + action = () => address.ToScriptHash(ProtocolSettings.Default.AddressVersion); action.Should().Throw(); } } diff --git a/tests/neo.UnitTests/neo.UnitTests.csproj b/tests/neo.UnitTests/neo.UnitTests.csproj index 9809eb5cbf..411a6f83cf 100644 --- a/tests/neo.UnitTests/neo.UnitTests.csproj +++ b/tests/neo.UnitTests/neo.UnitTests.csproj @@ -9,19 +9,13 @@ - - PreserveNewest - - - - - - + + - - - - + + + + diff --git a/tests/neo.UnitTests/protocol.json b/tests/neo.UnitTests/protocol.json deleted file mode 100644 index 50f802e7cf..0000000000 --- a/tests/neo.UnitTests/protocol.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ProtocolConfiguration": { - "MillisecondsPerBlock": 200 - } -}