From df1c498a1270391f989549d64d9ec32e7a2fc25f Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 19 Mar 2024 17:16:58 +0900 Subject: [PATCH 01/61] feat: Add ConsensusCryptoBackend --- src/Libplanet.Crypto/CryptoConfig.cs | 10 + .../DefaultConsensusCryptoBackend.cs | 259 ++++++++++++++++++ .../IConsensusCryptoBackend.cs | 53 ++++ 3 files changed, 322 insertions(+) create mode 100644 src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs create mode 100644 src/Libplanet.Crypto/IConsensusCryptoBackend.cs diff --git a/src/Libplanet.Crypto/CryptoConfig.cs b/src/Libplanet.Crypto/CryptoConfig.cs index dbe14798343..406f1878cbf 100644 --- a/src/Libplanet.Crypto/CryptoConfig.cs +++ b/src/Libplanet.Crypto/CryptoConfig.cs @@ -8,6 +8,7 @@ namespace Libplanet.Crypto public static class CryptoConfig { private static ICryptoBackend? _cryptoBackend; + private static IConsensusCryptoBackend? _consensusCryptoBackend; /// /// Global cryptography backend to sign and verify messages. @@ -17,5 +18,14 @@ public static ICryptoBackend CryptoBackend get => _cryptoBackend ??= new DefaultCryptoBackend(); set => _cryptoBackend = value; } + + /// + /// Global consensus cryptography backend to prove and verify messages. + /// + public static IConsensusCryptoBackend ConsensusCryptoBackend + { + get => _consensusCryptoBackend ??= new DefaultConsensusCryptoBackend(); + set => _consensusCryptoBackend = value; + } } } diff --git a/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs b/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs new file mode 100644 index 00000000000..fdd1f461eb7 --- /dev/null +++ b/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Security.Cryptography; +using Libplanet.Common; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Math; +using ECPoint = Org.BouncyCastle.Math.EC.ECPoint; + +namespace Libplanet.Crypto +{ + /// + /// Default consensus cryptography backend. + /// It implements ECVRF(Elliptic Curve Verifiable Random Function), + /// using RFC9381 as a reference. + /// + public class DefaultConsensusCryptoBackend : IConsensusCryptoBackend + { + private ECDomainParameters _eCParams; + + public DefaultConsensusCryptoBackend() + { + X9ECParameters ps = ECNamedCurveTable.GetByName("secp256k1"); + _eCParams = new ECDomainParameters(ps.Curve, ps.G, ps.N, ps.H); + SuiteBytes = new byte[] { 0 }.ToImmutableArray(); + } + + /// + /// Suite bytes that specifying the ECVRF ciphersuite. + /// Since RFC9381 does not include ECVRF under secp256k1, + /// it cannot be between 0x01 ~ 0x04. + /// To express this implementation does not belong to any of those on + /// RFC9381, left it as 0x00. + /// + public ImmutableArray SuiteBytes { get; } + + /// + public byte[] Prove(byte[] alphaBytes, PrivateKey privateKey) + { + // n : modulus + BigInteger n = _eCParams.N; + + // d : private key + BigInteger d = privateKey.KeyParam.D; + var digest = new Sha256Digest(); + + // k(nonce) generator is deterministic + var kCalculator = new HMacDsaKCalculator(digest); + kCalculator.Init(n, d, alphaBytes); + + // k : nonce + BigInteger k = kCalculator.NextK(); + + // H : message hash point + ECPoint pointH = HashToCurveTai(alphaBytes, privateKey.PublicKey); + + // dH : Gamma + ECPoint dPointH = pointH.Multiply(d); + + // kG : r + ECPoint kPointG = _eCParams.G.Multiply(k); + + // kH + ECPoint kPointH = pointH.Multiply(k); + + ECPoint[] points = new ECPoint[] { pointH, dPointH, kPointG, kPointH }; + + // c = checksum(payload) + BigInteger c = HashPoints(points); + + // s = (k + c * d) mod N + BigInteger s = k.Add(c.Multiply(d)).Mod(n); + + byte[] gammaBytes = dPointH.GetEncoded(true); + byte[] cBytes = c.ToByteArrayUnsigned(); + byte[] sBytes = s.ToByteArrayUnsigned(); + Array.Resize(ref cBytes, (_eCParams.Curve.FieldSize + 7) >> 3); + Array.Resize(ref sBytes, (_eCParams.Curve.FieldSize + 7) >> 3); + + byte[] piBytes = gammaBytes.Concat(cBytes).Concat(sBytes).ToArray(); + + return piBytes; + } + + /// + public bool VerifyProof( + byte[] alphaBytes, byte[] piBytes, PublicKey publicKey) + { + ECPublicKeyParameters pubKeyParam = publicKey.KeyParam; + ECDomainParameters eCParam = pubKeyParam.Parameters; + ECPoint dPointH; + BigInteger c; + BigInteger s; + + try + { + (dPointH, c, s) = DecodeProof(piBytes); + } + catch (Exception) + { + return false; + } + + // sG - cdG = (s-cd)G = kG + ECPoint sPointG = eCParam.G.Multiply(s); + ECPoint cdPointG = publicKey.KeyParam.Q.Multiply(c); + ECPoint scdPointG = sPointG.Subtract(cdPointG); + + // sH - cdH = (s-cd)H = kH + ECPoint pointH = HashToCurveTai(alphaBytes, publicKey); + ECPoint sPointH = pointH.Multiply(s); + ECPoint cdPointH = dPointH.Multiply(c); + ECPoint scdPointH = sPointH.Subtract(cdPointH); + + ECPoint[] points = new ECPoint[] { pointH, dPointH, scdPointG, scdPointH }; + + // check if checksum of payload is same + if (!c.Equals(HashPoints(points))) + { + return false; + } + + return true; + } + + /// + public byte[] ProofToHash(byte[] piBytes) + { + (ECPoint gamma, _, _) = DecodeProof(piBytes); + + ECPoint gammaMul = _eCParams.H.Equals(BigInteger.One) + ? gamma + : gamma.Multiply(_eCParams.H); + + // On draft-irtf-cfrg-vrf-03, it's mentioned to use EC point encoding with compression, + // but as our logic targets 64-bytes system, adoped uncompressed form to get + // payload bytes larger than 64bytes. + // If we use compressed form, source domain would be smaller than target hash domain, + // so space of generated betaBytes would get sparse. + byte[] payload + = SuiteBytes + .Concat(new byte[] { 3 }) + .Concat(gammaMul.GetEncoded(false)) + .Concat(new byte[] { 0 }).ToArray(); + + HashDigest betaHash = HashDigest.DeriveFrom(payload); + byte[] betaBytes = betaHash.ToByteArray(); + + return betaBytes; + } + + /// + /// Maps a to Elliptic curve point + /// with try-and-increment method. + /// + /// A message bytearray. + /// used for salt. + /// + /// generated from . + /// + private ECPoint HashToCurveTai(byte[] alphaBytes, PublicKey publicKey) + { + int ctr = 0; + while (true) + { + byte[] ctrBytes + = BitConverter.IsLittleEndian + ? BitConverter.GetBytes(ctr).Reverse().ToArray() + : BitConverter.GetBytes(ctr); + ctr += 1; + byte[] payload + = SuiteBytes + .Concat(new byte[] { 1 }) + .Concat(publicKey.KeyParam.Q.GetEncoded(true)) + .Concat(alphaBytes) + .Concat(ctrBytes) + .Concat(new byte[] { 0 }).ToArray(); + HashDigest hashed = HashDigest.DeriveFrom(payload); + byte[] encoded = new byte[] { 2 }.Concat(hashed.ToByteArray() + .Take((_eCParams.Curve.FieldSize + 7) >> 3)).ToArray(); + try + { + return _eCParams.Curve.DecodePoint(encoded); + } + catch (ArgumentException) + { + } + } + + throw new ArgumentException(); + } + + /// + /// Maps to + /// with SHA-512 hashing. + /// + /// A message bytearray. + /// + /// generated from . + /// + private BigInteger HashPoints(ECPoint[] points) + { + byte[] payload = new byte[] { 2 }; + foreach (ECPoint point in points) + { + payload = payload.Concat(point.GetEncoded(true)).ToArray(); + } + + payload = payload.Concat(new byte[] { 0 }).ToArray(); + HashDigest cHash = HashDigest.DeriveFrom(payload); + byte[] truncatedCBytes = cHash.ToByteArray() + .Take((_eCParams.Curve.FieldSize + 7) >> 3).ToArray(); + BigInteger c = new BigInteger(1, truncatedCBytes); + + return c; + } + + /// + /// Decomposes a to original compositions. + /// + /// Proof to verify + /// and alphaBytes. + /// + /// + /// A tuple of gamma, c and s. + /// which is used to verify . + /// + private (ECPoint, BigInteger, BigInteger) DecodeProof(byte[] piBytes) + { + int gammaBytesLen = ((_eCParams.Curve.FieldSize + 7) >> 3) + 1; + int cBytesLen = (_eCParams.Curve.FieldSize + 7) >> 3; + int sBytesLen = (_eCParams.Curve.FieldSize + 7) >> 3; + if (piBytes.Length != gammaBytesLen + cBytesLen + sBytesLen) + { + throw new ArgumentException(); + } + + byte[] gammaBytes = piBytes.Take(gammaBytesLen).ToArray(); + byte[] cBytes = piBytes.Skip(gammaBytesLen).Take(cBytesLen).ToArray(); + byte[] sBytes = piBytes.Skip(gammaBytesLen + cBytesLen).Take(sBytesLen).ToArray(); + + BigInteger c = new BigInteger(1, cBytes); + BigInteger s = new BigInteger(1, sBytes); + + if (s.CompareTo(_eCParams.N) == 1) + { + throw new ArgumentException(); + } + + // dH : Gamma + ECPoint gamma = _eCParams.Curve.DecodePoint(gammaBytes); + + return (gamma, c, s); + } + } +} diff --git a/src/Libplanet.Crypto/IConsensusCryptoBackend.cs b/src/Libplanet.Crypto/IConsensusCryptoBackend.cs new file mode 100644 index 00000000000..bb113f80d5b --- /dev/null +++ b/src/Libplanet.Crypto/IConsensusCryptoBackend.cs @@ -0,0 +1,53 @@ +using System.Security.Cryptography; + +namespace Libplanet.Crypto +{ + /// + /// Cryptography backend interface. + /// + /// A which corresponds to a digest. + /// + public interface IConsensusCryptoBackend + { + /// + /// Creates a piBytes(proof) from with the corresponding + /// . + /// + /// A message bytearray to generate piBytes. + /// + /// to prove + /// . + /// + /// + /// piBytes that is created from the + /// with the corresponding , + /// which is called as proof. + /// + byte[] Prove(byte[] alphaBytes, PrivateKey privateKey); + + /// + /// Verifies whether a was created from + /// a with the corresponding . + /// + /// A message bytearray. + /// A proof that was created from the + /// . + /// used for verification. + /// true if the was created + /// from the with the corresponding + /// , otherwise false. + /// + bool VerifyProof(byte[] alphaBytes, byte[] piBytes, PublicKey publicKey); + + /// + /// Generate betaBytes(proof hash) from a . + /// + /// Proof to generate hash. + /// + /// + /// betaBytes generated from the , + /// which is called as proof hash. + /// + byte[] ProofToHash(byte[] piBytes); + } +} From 9f092a23b3e86ed4ec4538eece1ea2fc4fa2acad Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 20 Mar 2024 16:43:49 +0900 Subject: [PATCH 02/61] feat: Introduce Proof --- src/Libplanet.Crypto/PrivateKey.cs | 41 ++++++ src/Libplanet.Crypto/Proof.cs | 228 +++++++++++++++++++++++++++++ src/Libplanet.Crypto/PublicKey.cs | 18 +++ 3 files changed, 287 insertions(+) create mode 100644 src/Libplanet.Crypto/Proof.cs diff --git a/src/Libplanet.Crypto/PrivateKey.cs b/src/Libplanet.Crypto/PrivateKey.cs index 94644818f45..63a4af0a902 100644 --- a/src/Libplanet.Crypto/PrivateKey.cs +++ b/src/Libplanet.Crypto/PrivateKey.cs @@ -230,6 +230,47 @@ public byte[] Sign(byte[] message) return CryptoConfig.CryptoBackend.Sign(hashed, this); } + /// + /// Creates a from the given . + /// + /// A created can be verified by the corresponding + /// . + /// + /// + /// A created can generate unique pseudorandom byte. + /// + /// + /// can be created by the + /// and only can be verified with corresponding . + /// + /// + /// To sum up, a is used to guarantee: + /// + /// + /// that the was created + /// by someone possessing the corresponding , + /// + /// that the possessor cannot deny having sent the + /// , + /// that the was not + /// forged in the middle of transit, and + /// that the generated pseudorandom byte was created + /// properly by someone possessing the corresponding + /// , and + /// that the generated pseudorandom byte was not + /// forged in the middle of transit. + /// + /// + /// A message s to sign. + /// A that proves the authenticity of the + /// . + /// It can be verified using method. + /// + /// + /// + public Proof Prove(IEnumerable message) + => new Proof(CryptoConfig.ConsensusCryptoBackend.Prove(message.ToArray(), this)); + /// /// Creates a signature from the given . /// A created signature can be verified by the corresponding . diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs new file mode 100644 index 00000000000..369691096ca --- /dev/null +++ b/src/Libplanet.Crypto/Proof.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Numerics; +using Bencodex; +using Bencodex.Types; +using Libplanet.Common; + +namespace Libplanet.Crypto +{ + /// + /// Represents a proof that validators submits to be a proposer. + /// Once decided, it can be a source of random seed. + /// + public struct Proof : IBencodable, IEquatable, IComparable, IComparable + { + private readonly ImmutableArray _piBytes; + private ImmutableArray? _hash; + + public Proof(IReadOnlyList piBytes) + { + _piBytes = piBytes.ToImmutableArray(); + _hash = null; + } + + public Proof(IValue bencoded) + : this(bencoded is Binary piBytes + ? piBytes + : throw new ArgumentException( + $"Given {nameof(bencoded)} must be of type " + + $"{typeof(Binary)}: {bencoded.GetType()}", + nameof(bencoded))) + { + } + + public Proof(Binary bencoded) + : this(bencoded.ByteArray) + { + } + + /// + public IValue Bencoded => new Binary(ByteArray); + + /// + /// An immutable byte array that represent this . + /// + /// This is immutable. + /// For a mutable array, call method. + /// + /// + public ImmutableArray ByteArray => _piBytes; + + /// + /// Hash of the . + /// It can be used as a random hash that can be verified. + /// + public ImmutableArray Hash + { + get + { + if (_hash is { } hash) + { + return hash; + } + else + { + _hash = CryptoConfig.ConsensusCryptoBackend + .ProofToHash(ToByteArray()).ToImmutableArray(); + return (ImmutableArray)_hash; + } + } + } + + /// + /// Integer form of . + /// It can represent the random integer that can be verified. + /// Maximum value of it follows the space of . + /// + public BigInteger HashInt => HashToInt(Hash); + + /// + /// Random seed that can be derived by . + /// It's calculated by taking 4 bytes of , + /// and converting it into int. + /// + public int Seed + { + get + { + byte[] seed = Hash.ToArray().Take(4).ToArray(); + return BitConverter.IsLittleEndian + ? BitConverter.ToInt32(seed.Reverse().ToArray(), 0) + : BitConverter.ToInt32(seed, 0); + } + } + + /// + /// Gets a mutable byte array that represent this . + /// + /// A new mutable array which represents this + /// . + /// Since it is created every time the method is called, + /// any mutation on that does not affect internal states of + /// this . + /// + public byte[] ToByteArray() => ByteArray.ToArray(); + + /// + /// Verifies with given + /// and . + /// + /// + /// corresponding to the + /// that has been used when generating the + /// by . + /// + /// + /// Payload that has been used when generating the + /// by . + /// + /// true if the proves authenticity of + /// the with the . + /// Otherwise false. + /// + /// + public bool Verify(PublicKey publicKey, byte[] payload) + => publicKey.VerifyProof(payload, this); + + /// + /// Draws expected number under given power portion. + /// It represents result of quantile function of binomial distribution + /// where quantile is portion of , n is , + /// and p is divided by . + /// + /// + /// Expected size of winnings. + /// + /// + /// Power that can be interpreted as the number of lots owns. + /// + /// + /// Total power that can be interpreted as the number of total lots. + /// + /// + /// Expected number of drawn lots under given condition. + /// + public BigInteger Draw(int expectedSize, BigInteger power, BigInteger totalPower) + { + double targetProb = (double)HashInt / (double)HashToInt(Enumerable.Repeat(byte.MaxValue, 64).ToImmutableArray()); + + return BinomialQuantileFunction(targetProb, expectedSize / (double)totalPower, power); + } + + /// + public bool Equals(Proof other) + => ByteArray.Equals(other.ByteArray); + + /// + public override bool Equals(object? obj) + => obj is Proof otherProof && Equals(otherProof); + + /// + public int CompareTo(Proof other) + => other is Proof otherProof + ? (HashInt - otherProof.HashInt).Sign + : throw new ArgumentException($"Argument {nameof(other)} is null"); + + /// + public int CompareTo(object? obj) + => obj is Proof otherProof + ? CompareTo(otherProof) + : throw new ArgumentException( + $"Argument {nameof(obj)} is not an ${nameof(Proof)}.", nameof(obj)); + + /// + public override int GetHashCode() + => ByteUtil.CalculateHashCode(ToByteArray()); + + private static BigInteger HashToInt(ImmutableArray hash) + => new BigInteger( + BitConverter.IsLittleEndian + ? hash.Reverse().Concat(new byte[] { 0 }).ToArray() + : hash.Concat(new byte[] { 0 }).ToArray()); + + private static BigInteger BinomialQuantileFunction( + double targetProb, double prob, BigInteger nSample) + { + // Cumulative binomial distribution + double cumulativePositiveProb = 0; + for (BigInteger nPositive = 0; nPositive < nSample; nPositive++) + { + // Binomial distribution + cumulativePositiveProb += BinomialProb((double)nSample, (double)nPositive, prob); + + if (targetProb <= cumulativePositiveProb) + { + return nPositive; + } + } + + return nSample; + } + + private static double BinomialProb(double nSample, double nPositive, double prob) + { + return Combination(nSample, nPositive) + * Math.Pow(prob, nPositive) + * Math.Pow(1d - prob, nSample - nPositive); + } + + private static double Combination(double n, double r) + { + double nCr = 1; + for (double i = n; i > n - r; i--) + { + nCr *= i; + } + + for (double i = 1; i <= r; i++) + { + nCr /= i; + } + + return nCr; + } + } +} diff --git a/src/Libplanet.Crypto/PublicKey.cs b/src/Libplanet.Crypto/PublicKey.cs index 65a3fca8239..d68f2825495 100644 --- a/src/Libplanet.Crypto/PublicKey.cs +++ b/src/Libplanet.Crypto/PublicKey.cs @@ -216,6 +216,24 @@ public bool Verify(IReadOnlyList message, IReadOnlyList signature) } } + /// + /// + /// Verifies whether a proves authenticity of + /// with the corresponding . + /// + /// + /// A original plaintext message that the + /// tries to prove its authenticity. I.e., an argument data passed to + /// method. + /// A which tries to authenticity of + /// . + /// I.e., a data that method returned. + /// true if the proves authenticity of + /// the with the corresponding . + /// Otherwise false. + public bool VerifyProof(byte[] message, Proof proof) + => CryptoConfig.ConsensusCryptoBackend.VerifyProof(message, proof.ToByteArray(), this); + /// /// Gets the public key's hexadecimal representation in compressed form. /// From 499d0e699bf1237e053f06e6083c50d07ae05dbf Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 21 Mar 2024 00:04:35 +0900 Subject: [PATCH 03/61] feat: Introduce Lot --- src/Libplanet.Types/Consensus/ILot.cs | 21 ++++ src/Libplanet.Types/Consensus/ILotMetadata.cs | 31 +++++ src/Libplanet.Types/Consensus/Lot.cs | 113 +++++++++++++++++ src/Libplanet.Types/Consensus/LotMetadata.cs | 118 ++++++++++++++++++ 4 files changed, 283 insertions(+) create mode 100644 src/Libplanet.Types/Consensus/ILot.cs create mode 100644 src/Libplanet.Types/Consensus/ILotMetadata.cs create mode 100644 src/Libplanet.Types/Consensus/Lot.cs create mode 100644 src/Libplanet.Types/Consensus/LotMetadata.cs diff --git a/src/Libplanet.Types/Consensus/ILot.cs b/src/Libplanet.Types/Consensus/ILot.cs new file mode 100644 index 00000000000..801324ceafc --- /dev/null +++ b/src/Libplanet.Types/Consensus/ILot.cs @@ -0,0 +1,21 @@ +using Libplanet.Crypto; + +namespace Libplanet.Types.Consensus +{ + /// + /// An for the . + /// + public interface ILot : ILotMetadata + { + /// + /// that proved an . + /// can be verified by it. + /// + public PublicKey PublicKey { get; } + + /// + /// that has been proved by . + /// + public Proof Proof { get; } + } +} diff --git a/src/Libplanet.Types/Consensus/ILotMetadata.cs b/src/Libplanet.Types/Consensus/ILotMetadata.cs new file mode 100644 index 00000000000..de58ab42f84 --- /dev/null +++ b/src/Libplanet.Types/Consensus/ILotMetadata.cs @@ -0,0 +1,31 @@ +using Libplanet.Crypto; + +namespace Libplanet.Types.Consensus +{ + /// + /// An for the . + /// + public interface ILotMetadata + { + /// + /// Height of the consensus where + /// participate in the draw of the proposer. + /// + long Height { get; } + + /// + /// Round of the consensus where + /// participate in the draw of the proposer. + /// + int Round { get; } + + /// + /// that has been decided on the previous . + /// if current is 0, it indicates + /// from the + /// of the proposing. + /// + /// + Proof LastProof { get; } + } +} diff --git a/src/Libplanet.Types/Consensus/Lot.cs b/src/Libplanet.Types/Consensus/Lot.cs new file mode 100644 index 00000000000..a0a8e439983 --- /dev/null +++ b/src/Libplanet.Types/Consensus/Lot.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using Bencodex; +using Bencodex.Misc; +using Bencodex.Types; +using Libplanet.Crypto; + +namespace Libplanet.Types.Consensus +{ + public struct Lot : ILot, IEquatable, IBencodable + { + private static readonly Binary PublicKeyKey = new Binary(new byte[] { 0x70 }); // 'p' + private static readonly Binary ProofKey = new Binary(new byte[] { 0x50 }); // 'P' + private static readonly Codec _codec = new Codec(); + private readonly LotMetadata _metadata; + + public Lot( + LotMetadata metadata, + PublicKey publicKey, + Proof proof) + { + if (!publicKey.VerifyProof(_codec.Encode(metadata.Bencoded), proof)) + { + throw new ArgumentException( + $"Given {nameof(proof)} is invalid.", + nameof(proof)); + } + + _metadata = metadata; + PublicKey = publicKey; + Proof = proof; + } + + public Lot(IValue bencoded) + : this(bencoded is Dictionary dict + ? dict + : throw new ArgumentException( + $"Given {nameof(bencoded)} must be of type " + + $"{typeof(Dictionary)}: {bencoded.GetType()}", + nameof(bencoded))) + { + } + + private Lot(Dictionary encoded) + : this( + new LotMetadata(encoded), + new PublicKey(((Binary)encoded[PublicKeyKey]).ByteArray), + new Proof(encoded[ProofKey])) + { + } + + /// + public long Height => _metadata.Height; + + /// + public int Round => _metadata.Round; + + /// + public Proof LastProof => _metadata.LastProof; + + /// + public PublicKey PublicKey { get; } + + /// + public Proof Proof { get; } + + [JsonIgnore] + public IValue Bencoded + => ((Dictionary)_metadata.Bencoded) + .Add(PublicKeyKey, PublicKey.Format(true)) + .Add(ProofKey, Proof.ByteArray); + + /// + /// Verifies whether is proved by + /// that is corresponding to . + /// + /// true if the proves authenticity of + /// the . + /// Otherwise false. + public bool Verify() + => PublicKey.VerifyProof(_codec.Encode(_metadata.Bencoded), Proof); + + /// + public bool Equals(Lot other) + => _metadata.Equals(other._metadata) + && Proof.Equals(other.Proof); + + /// + public override bool Equals(object? obj) + => obj is Lot otherLot && Equals(otherLot); + + /// + public override int GetHashCode() + => HashCode.Combine( + _metadata.GetHashCode(), + Proof.GetHashCode()); + + /// + public override string ToString() + { + var dict = new Dictionary + { + { "public_key", PublicKey.ToString() }, + { "height", Height }, + { "round", Round }, + { "lastProof", LastProof.ByteArray.Hex() }, + }; + return JsonSerializer.Serialize(dict); + } + } +} diff --git a/src/Libplanet.Types/Consensus/LotMetadata.cs b/src/Libplanet.Types/Consensus/LotMetadata.cs new file mode 100644 index 00000000000..bc21cd89597 --- /dev/null +++ b/src/Libplanet.Types/Consensus/LotMetadata.cs @@ -0,0 +1,118 @@ +using System; +using System.Text.Json.Serialization; +using Bencodex; +using Bencodex.Types; +using Libplanet.Crypto; + +namespace Libplanet.Types.Consensus +{ + /// + /// Metadata of the . + /// + public struct LotMetadata : ILotMetadata, IEquatable, IBencodable + { + private static readonly Binary HeightKey = + new Binary(new byte[] { 0x48 }); // 'H' + + private static readonly Binary RoundKey = + new Binary(new byte[] { 0x52 }); // 'R' + + private static readonly Binary LastProofKey = + new Binary(new byte[] { 0x4c }); // 'L' + + private static readonly Codec _codec = new Codec(); + + public LotMetadata( + long height, + int round, + Proof lastProof) + { + if (height < 0) + { + throw new ArgumentException( + $"Given {nameof(height)} cannot be negative: {height}"); + } + else if (round < 0) + { + throw new ArgumentException( + $"Given {nameof(round)} cannot be negative: {round}"); + } + + Height = height; + Round = round; + LastProof = lastProof; + } + + public LotMetadata(IValue bencoded) + : this(bencoded is Dictionary dict + ? dict + : throw new ArgumentException( + $"Given {nameof(bencoded)} must be of type " + + $"{typeof(Dictionary)}: {bencoded.GetType()}", + nameof(bencoded))) + { + } + + private LotMetadata(Dictionary bencoded) + : this( + (Integer)bencoded[HeightKey], + (Integer)bencoded[RoundKey], + new Proof(bencoded[LastProofKey])) + { + } + + /// + public long Height { get; } + + /// + public int Round { get; } + + /// + public Proof LastProof { get; } + + /// + [JsonIgnore] + public IValue Bencoded + { + get + { + Dictionary bencoded = Dictionary.Empty + .Add(HeightKey, Height) + .Add(RoundKey, Round) + .Add(LastProofKey, LastProof.ByteArray); + + return bencoded; + } + } + + /// + /// Proves a to create a + /// using given . + /// + /// The to prove the data with. + /// A with a . + /// + /// + public Lot Sign(PrivateKey prover) + => new Lot(this, prover.PublicKey, prover.Prove(_codec.Encode(Bencoded))); + + /// + public bool Equals(LotMetadata other) + => Height == other.Height + && Round == other.Round + && LastProof.Equals(other.LastProof); + + /// + public override bool Equals(object? obj) => + obj is LotMetadata other && Equals(other); + + /// + public override int GetHashCode() + { + return HashCode.Combine( + Height, + Round, + LastProof); + } + } +} From 3885f08ba55f2e827ebdb0c0338b4cc9bc2e88ea Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 21 Mar 2024 00:26:05 +0900 Subject: [PATCH 04/61] chore: Clean with linting --- src/Libplanet.Crypto/IConsensusCryptoBackend.cs | 4 ++-- src/Libplanet.Crypto/PrivateKey.cs | 3 +-- src/Libplanet.Crypto/Proof.cs | 4 +++- src/Libplanet.Crypto/PublicKey.cs | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Libplanet.Crypto/IConsensusCryptoBackend.cs b/src/Libplanet.Crypto/IConsensusCryptoBackend.cs index bb113f80d5b..d8b17f87448 100644 --- a/src/Libplanet.Crypto/IConsensusCryptoBackend.cs +++ b/src/Libplanet.Crypto/IConsensusCryptoBackend.cs @@ -10,8 +10,8 @@ namespace Libplanet.Crypto public interface IConsensusCryptoBackend { /// - /// Creates a piBytes(proof) from with the corresponding - /// . + /// Creates a piBytes(proof) from + /// with the corresponding . /// /// A message bytearray to generate piBytes. /// diff --git a/src/Libplanet.Crypto/PrivateKey.cs b/src/Libplanet.Crypto/PrivateKey.cs index 63a4af0a902..69537f750c3 100644 --- a/src/Libplanet.Crypto/PrivateKey.cs +++ b/src/Libplanet.Crypto/PrivateKey.cs @@ -262,12 +262,11 @@ public byte[] Sign(byte[] message) /// /// /// A message s to sign. - /// A that proves the authenticity of the + /// A that proves the authenticity of the /// . /// It can be verified using method. /// /// - /// public Proof Prove(IEnumerable message) => new Proof(CryptoConfig.ConsensusCryptoBackend.Prove(message.ToArray(), this)); diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index 369691096ca..70964f08620 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -147,7 +147,9 @@ public bool Verify(PublicKey publicKey, byte[] payload) /// public BigInteger Draw(int expectedSize, BigInteger power, BigInteger totalPower) { - double targetProb = (double)HashInt / (double)HashToInt(Enumerable.Repeat(byte.MaxValue, 64).ToImmutableArray()); + double targetProb + = (double)HashInt + / (double)HashToInt(Enumerable.Repeat(byte.MaxValue, 64).ToImmutableArray()); return BinomialQuantileFunction(targetProb, expectedSize / (double)totalPower, power); } diff --git a/src/Libplanet.Crypto/PublicKey.cs b/src/Libplanet.Crypto/PublicKey.cs index d68f2825495..1339a571196 100644 --- a/src/Libplanet.Crypto/PublicKey.cs +++ b/src/Libplanet.Crypto/PublicKey.cs @@ -227,7 +227,8 @@ public bool Verify(IReadOnlyList message, IReadOnlyList signature) /// method. /// A which tries to authenticity of /// . - /// I.e., a data that method returned. + /// I.e., a data that method returned. + /// /// true if the proves authenticity of /// the with the corresponding . /// Otherwise false. From 98861ee7e12bd09f38742a934e2c70dc608c6081 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 15:30:59 +0900 Subject: [PATCH 05/61] doc: Update changelog --- CHANGES.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5ee168ce85d..0b6cd5a6b14 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,14 +16,45 @@ To be released. ### Added APIs + - Added `IConsensusCryptoBackend` interface, which contains VRF + functionalities which is used on the consensus as a pseudo-random function. + [[#VRF]] + - Added `DefaultConsensusCryptoBackend` class as a default implementation of + `IConsensusCryptoBackend`. [[#VRF]] + - Added `CryptoConfig.ConsensusCryptoBackend` property as a VRF backend used + on the consensus. [[#VRF]] + - Added `PrivateKey.Prove()` method as a proof generation. [[#VRF]] + - Added `PublicKey.VerifyProof()` method as a proof verification. [[#VRF]] + - Added `Proof` struct as a wrapper structure of proof(pi-bytes) generated by + ECVRF. [[#VRF]] + - Added `ConsensusInformation` struct as a base payload to be proved + with private key. [[#VRF]] + - `BlockMetadata.CurrentProtocolVersion` has been changed from 5 to 6. + [[#VRF]] + - Added `IBlockMetadata.Proof` property. [[#VRF]] + - Added `PreProposal` class as a content of message that suggests + `PreEvaluationBlock` as a `Proposal` candidate during + `ConsensusStep.PrePropose`. [[#VRF]] + - Added `PreProposalMetadata` class as a metadata of `PreProposal`. [[#VRF]] + - Added `ConsensusStep.PrePropose`. [[#VRF]] + - Added `ConsensusPreProposalMsg` class as a `ConsensusMsg` broadcasted during + `ConsensusStep.PrePropose`. [[#VRF]] + - Added `PreProposalSet` class as a `PreProposal` selector. + ### Behavioral changes + - `ActionEvaluator.EvaluateActions()` use `Proof.Seed` as a initial + random seed instead of `PreEvaluationHash`, `signature` combined seed. + [[#VRF]] + ### Bug fixes ### Dependencies ### CLI tools +[#VRF]: https://github.com/planetarium/libplanet/pull/TBD + Version 5.2.0 ------------- From a02f0cbcd14501179c709a85dc7f12a199232c04 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 15:45:22 +0900 Subject: [PATCH 06/61] feat: Update LotMetadata.LastProof to be nullable --- src/Libplanet.Types/Consensus/ILotMetadata.cs | 2 +- src/Libplanet.Types/Consensus/Lot.cs | 4 ++-- src/Libplanet.Types/Consensus/LotMetadata.cs | 14 +++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Libplanet.Types/Consensus/ILotMetadata.cs b/src/Libplanet.Types/Consensus/ILotMetadata.cs index de58ab42f84..d1c78bb50c8 100644 --- a/src/Libplanet.Types/Consensus/ILotMetadata.cs +++ b/src/Libplanet.Types/Consensus/ILotMetadata.cs @@ -26,6 +26,6 @@ public interface ILotMetadata /// of the proposing. /// /// - Proof LastProof { get; } + Proof? LastProof { get; } } } diff --git a/src/Libplanet.Types/Consensus/Lot.cs b/src/Libplanet.Types/Consensus/Lot.cs index a0a8e439983..629dc264924 100644 --- a/src/Libplanet.Types/Consensus/Lot.cs +++ b/src/Libplanet.Types/Consensus/Lot.cs @@ -58,7 +58,7 @@ private Lot(Dictionary encoded) public int Round => _metadata.Round; /// - public Proof LastProof => _metadata.LastProof; + public Proof? LastProof => _metadata.LastProof; /// public PublicKey PublicKey { get; } @@ -105,7 +105,7 @@ public override string ToString() { "public_key", PublicKey.ToString() }, { "height", Height }, { "round", Round }, - { "lastProof", LastProof.ByteArray.Hex() }, + { "lastProof", LastProof?.ByteArray.Hex() ?? "Empty" }, }; return JsonSerializer.Serialize(dict); } diff --git a/src/Libplanet.Types/Consensus/LotMetadata.cs b/src/Libplanet.Types/Consensus/LotMetadata.cs index bc21cd89597..1b227740adb 100644 --- a/src/Libplanet.Types/Consensus/LotMetadata.cs +++ b/src/Libplanet.Types/Consensus/LotMetadata.cs @@ -25,7 +25,7 @@ public struct LotMetadata : ILotMetadata, IEquatable, IBencodable public LotMetadata( long height, int round, - Proof lastProof) + Proof? lastProof) { if (height < 0) { @@ -68,7 +68,7 @@ private LotMetadata(Dictionary bencoded) public int Round { get; } /// - public Proof LastProof { get; } + public Proof? LastProof { get; } /// [JsonIgnore] @@ -78,8 +78,12 @@ public IValue Bencoded { Dictionary bencoded = Dictionary.Empty .Add(HeightKey, Height) - .Add(RoundKey, Round) - .Add(LastProofKey, LastProof.ByteArray); + .Add(RoundKey, Round); + + if (LastProof is Proof lastProof) + { + bencoded = bencoded.Add(LastProofKey, lastProof.ByteArray); + } return bencoded; } @@ -93,7 +97,7 @@ public IValue Bencoded /// A with a . /// /// - public Lot Sign(PrivateKey prover) + public Lot Prove(PrivateKey prover) => new Lot(this, prover.PublicKey, prover.Prove(_codec.Encode(Bencoded))); /// From 717375bccb3829bcc470b017d42fd2c8ac913101 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 16:20:48 +0900 Subject: [PATCH 07/61] feat: Force to use compressed ECPoint form on Proof hashing, Add Proof length check --- src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs | 7 +------ src/Libplanet.Crypto/Proof.cs | 7 +++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs b/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs index fdd1f461eb7..a3cb390753c 100644 --- a/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs +++ b/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs @@ -135,15 +135,10 @@ public byte[] ProofToHash(byte[] piBytes) ? gamma : gamma.Multiply(_eCParams.H); - // On draft-irtf-cfrg-vrf-03, it's mentioned to use EC point encoding with compression, - // but as our logic targets 64-bytes system, adoped uncompressed form to get - // payload bytes larger than 64bytes. - // If we use compressed form, source domain would be smaller than target hash domain, - // so space of generated betaBytes would get sparse. byte[] payload = SuiteBytes .Concat(new byte[] { 3 }) - .Concat(gammaMul.GetEncoded(false)) + .Concat(gammaMul.GetEncoded(true)) .Concat(new byte[] { 0 }).ToArray(); HashDigest betaHash = HashDigest.DeriveFrom(payload); diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index 70964f08620..41b955b2531 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -20,6 +20,13 @@ public struct Proof : IBencodable, IEquatable, IComparable, ICompa public Proof(IReadOnlyList piBytes) { + if (piBytes.Count != 97) + { + throw new ArgumentException( + $"Proof byte length expected to be 97(gamma:33 + c:32 + s:32), " + + $"but found {piBytes.Count}"); + } + _piBytes = piBytes.ToImmutableArray(); _hash = null; } From 5e40ddb8147833bc4028831a50be99cda285c9ef Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 17:35:04 +0900 Subject: [PATCH 08/61] fix: Fix Proof.Equals() --- src/Libplanet.Crypto/Proof.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index 41b955b2531..ecedde3dd49 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -163,7 +163,7 @@ double targetProb /// public bool Equals(Proof other) - => ByteArray.Equals(other.ByteArray); + => ByteArray.SequenceEqual(other.ByteArray); /// public override bool Equals(object? obj) From 6f53c34eb29a184f81ecf204544065c8df263b4a Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 20:13:54 +0900 Subject: [PATCH 09/61] feat: Throw explicit InvalidProofException for invalid proof --- .../DefaultConsensusCryptoBackend.cs | 17 +++++++++-- src/Libplanet.Crypto/InvalidProofException.cs | 30 +++++++++++++++++++ src/Libplanet.Crypto/Proof.cs | 14 +++++++-- 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 src/Libplanet.Crypto/InvalidProofException.cs diff --git a/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs b/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs index a3cb390753c..62823081ed7 100644 --- a/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs +++ b/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs @@ -230,7 +230,10 @@ private BigInteger HashPoints(ECPoint[] points) int sBytesLen = (_eCParams.Curve.FieldSize + 7) >> 3; if (piBytes.Length != gammaBytesLen + cBytesLen + sBytesLen) { - throw new ArgumentException(); + throw new ArgumentException( + $"Length of piBytes are expected to be " + + $"{gammaBytesLen + cBytesLen + sBytesLen}, " + + $"but found {piBytes.Length}"); } byte[] gammaBytes = piBytes.Take(gammaBytesLen).ToArray(); @@ -242,11 +245,19 @@ private BigInteger HashPoints(ECPoint[] points) if (s.CompareTo(_eCParams.N) == 1) { - throw new ArgumentException(); + throw new ArgumentException("s cannot be bigger than EC parameter N"); } // dH : Gamma - ECPoint gamma = _eCParams.Curve.DecodePoint(gammaBytes); + ECPoint gamma; + try + { + gamma = _eCParams.Curve.DecodePoint(gammaBytes); + } + catch (FormatException) + { + throw new ArgumentException("Given piBytes does not contain valid gammaBytes"); + } return (gamma, c, s); } diff --git a/src/Libplanet.Crypto/InvalidProofException.cs b/src/Libplanet.Crypto/InvalidProofException.cs new file mode 100644 index 00000000000..870ce2ee2c8 --- /dev/null +++ b/src/Libplanet.Crypto/InvalidProofException.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.Serialization; + +namespace Libplanet.Crypto +{ + [Serializable] + public class InvalidProofException : Exception + { + public InvalidProofException() + { + } + + public InvalidProofException(string message) + : base(message) + { + } + + public InvalidProofException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected InvalidProofException( + SerializationInfo info, StreamingContext context + ) + : base(info, context) + { + } + } +} diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index ecedde3dd49..61fba1a8699 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -72,9 +72,17 @@ public ImmutableArray Hash } else { - _hash = CryptoConfig.ConsensusCryptoBackend - .ProofToHash(ToByteArray()).ToImmutableArray(); - return (ImmutableArray)_hash; + try + { + _hash = CryptoConfig.ConsensusCryptoBackend + .ProofToHash(ToByteArray()).ToImmutableArray(); + return (ImmutableArray)_hash; + } + catch (ArgumentException e) + { + throw new InvalidProofException( + $"Bytes of Proof is invalid", e); + } } } } From 520c043f6034a273eaa9200836bd316b92bebabc Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 20:14:39 +0900 Subject: [PATCH 10/61] test: Add Proof test --- test/Libplanet.Tests/Crypto/ProofTest.cs | 176 +++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 test/Libplanet.Tests/Crypto/ProofTest.cs diff --git a/test/Libplanet.Tests/Crypto/ProofTest.cs b/test/Libplanet.Tests/Crypto/ProofTest.cs new file mode 100644 index 00000000000..e12ee3ec1dd --- /dev/null +++ b/test/Libplanet.Tests/Crypto/ProofTest.cs @@ -0,0 +1,176 @@ +using System; +using System.Linq; +using System.Numerics; +using Bencodex.Types; +using Libplanet.Crypto; +using Xunit; + +namespace Libplanet.Tests.Crypto +{ + public class ProofTest + { + private readonly Random _rnd = new Random(); + + [Fact] + public void Constructor() + { + var acceptablePiBytes = new byte[97]; + + for (int i = 0; i < 100; i++) + { + _rnd.NextBytes(acceptablePiBytes); + new Proof(acceptablePiBytes); + + if (i != 97) + { + var unacceptablePiBytes = new byte[i]; + _rnd.NextBytes(unacceptablePiBytes); + Assert.Throws(() => new Proof(unacceptablePiBytes)); + } + } + } + + [Fact] + public void ConstructorDifferentEncoding() + { + var piBytes = new byte[97]; + _rnd.NextBytes(piBytes); + + Assert.Equal(new Proof(piBytes), new Proof(new Binary(piBytes))); + Assert.Equal(new Proof(piBytes), new Proof((IValue)new Binary(piBytes))); + } + + [Fact] + public void Bencoded() + { + var piBytes = new byte[97]; + _rnd.NextBytes(piBytes); + + var proof = new Proof(piBytes); + + Assert.Equal(proof, new Proof(proof.Bencoded)); + } + + [Fact] + public void ByteArray() + { + var piBytes = new byte[97]; + _rnd.NextBytes(piBytes); + + var proof = new Proof(piBytes); + + Assert.Equal(proof, new Proof(proof.ByteArray)); + Assert.Equal(proof, new Proof(proof.ToByteArray())); + } + + [Fact] + public void Verify() + { + var payload = new byte[100]; + _rnd.NextBytes(payload); + + var privateKey = new PrivateKey(); + var wrongPrivateKey = new PrivateKey(); + var proof = privateKey.Prove(payload); + Assert.True(proof.Verify(privateKey.PublicKey, payload)); + Assert.False(proof.Verify(wrongPrivateKey.PublicKey, payload)); + + _rnd.NextBytes(payload); + Assert.False(proof.Verify(privateKey.PublicKey, payload)); + } + + [Fact] + public void Hash() + { + var randomPiBytes = new byte[97]; + _rnd.NextBytes(randomPiBytes); + var invalidProof = new Proof(randomPiBytes); + Assert.Throws(() => invalidProof.Hash); + + var payload = new byte[100]; + _rnd.NextBytes(payload); + + var privateKey = new PrivateKey(); + var proof = privateKey.Prove(payload); + Assert.Equal(64, proof.Hash.Length); + Assert.True(proof.Hash.SequenceEqual(privateKey.Prove(payload).Hash)); + Assert.NotEqual(proof.Hash, new PrivateKey().Prove(payload).Hash); + _rnd.NextBytes(payload); + Assert.NotEqual(proof.Hash, new PrivateKey().Prove(payload).Hash); + } + + [Fact] + public void HashInt() + { + var maxInt = new BigInteger( + Enumerable.Repeat(byte.MaxValue, 64).Concat(new byte[] { 0 }).ToArray()); + + var randomPiBytes = new byte[97]; + _rnd.NextBytes(randomPiBytes); + var invalidProof = new Proof(randomPiBytes); + Assert.Throws(() => invalidProof.HashInt); + + var privateKey = new PrivateKey(); + var payload = new byte[100]; + _rnd.NextBytes(payload); + var proof = privateKey.Prove(payload); + + for (int i = 0; i < 100; i++) + { + Assert.True(proof.HashInt >= 0 && proof.HashInt <= maxInt); + Assert.Equal(proof, privateKey.Prove(payload)); + Assert.NotEqual(proof, new PrivateKey().Prove(payload)); + + _rnd.NextBytes(payload); + Assert.NotEqual(proof, privateKey.Prove(payload)); + + privateKey = new PrivateKey(); + var newProof = privateKey.Prove(payload); + Assert.NotEqual(proof.HashInt, new PrivateKey().Prove(payload).HashInt); + Assert.Equal(proof.HashInt > newProof.HashInt ? 1 : -1, proof.CompareTo(newProof)); + proof = newProof; + } + } + + [Fact] + public void Seed() + { + var randomPiBytes = new byte[97]; + _rnd.NextBytes(randomPiBytes); + var invalidProof = new Proof(randomPiBytes); + Assert.Throws(() => invalidProof.Seed); + + var payload = new byte[100]; + _rnd.NextBytes(payload); + + var privateKey = new PrivateKey(); + var proof = privateKey.Prove(payload); + Assert.Equal(proof.Seed, privateKey.Prove(payload).Seed); + Assert.NotEqual(proof.Seed, new PrivateKey().Prove(payload).Seed); + _rnd.NextBytes(payload); + Assert.NotEqual(proof.Seed, new PrivateKey().Prove(payload).Seed); + } + + [Fact] + public void Draw() + { + var randomPiBytes = new byte[97]; + _rnd.NextBytes(randomPiBytes); + var invalidProof = new Proof(randomPiBytes); + Assert.Throws(() => invalidProof.Draw(5, 10, 20)); + + var payload = new byte[100]; + _rnd.NextBytes(payload); + + var privateKey = new PrivateKey(); + var proof = privateKey.Prove(payload); + + for (int i = 0; i < 10; i++) + { + var drawn = proof.Draw(5, i, 20); + Assert.True(drawn <= i); + Assert.Equal(drawn, proof.Draw(5, i, 20)); + } + } + } +} From 3b72b6e80a5a8e8ccf6cebf0fe9b84bf2ef9dc1e Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 20:43:59 +0900 Subject: [PATCH 11/61] test: Add Key pair test --- test/Libplanet.Tests/Crypto/PrivateKeyTest.cs | 43 ++++++++++++++ test/Libplanet.Tests/Crypto/PublicKeyTest.cs | 56 +++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/test/Libplanet.Tests/Crypto/PrivateKeyTest.cs b/test/Libplanet.Tests/Crypto/PrivateKeyTest.cs index a3125b66904..e8879861f21 100644 --- a/test/Libplanet.Tests/Crypto/PrivateKeyTest.cs +++ b/test/Libplanet.Tests/Crypto/PrivateKeyTest.cs @@ -187,6 +187,49 @@ public void SignTest() Assert.False(wrongPubKey.Verify(payload, pk.Sign(imPayload).ToArray())); } + [Fact] + public void ProveTest() + { + var pk = new PrivateKey( + new byte[] + { + 0x52, 0x09, 0x38, 0xfa, 0xe0, 0x79, 0x78, 0x95, 0x61, 0x26, + 0x8c, 0x29, 0x33, 0xf6, 0x36, 0xd8, 0xb5, 0xa0, 0x01, 0x1e, + 0xa0, 0x41, 0x12, 0xdb, 0xab, 0xab, 0xf2, 0x95, 0xe5, 0xdd, + 0xef, 0x88, + } + ); + var pubKey = pk.PublicKey; + var wrongPubKey = new PrivateKey().PublicKey; + var payload = new byte[] + { + 0x64, 0x37, 0x3a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x6c, 0x65, 0x31, 0x30, 0x3a, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x36, 0x35, 0x3a, 0x04, 0xb5, + 0xa2, 0x4a, 0xa2, 0x11, 0x27, 0x20, 0x42, 0x3b, 0xad, 0x39, + 0xa0, 0x20, 0x51, 0x82, 0x37, 0x9d, 0x6f, 0x2b, 0x33, 0xe3, + 0x48, 0x7c, 0x9a, 0xb6, 0xcc, 0x8f, 0xc4, 0x96, 0xf8, 0xa5, + 0x48, 0x34, 0x40, 0xef, 0xbb, 0xef, 0x06, 0x57, 0xac, 0x2e, + 0xf6, 0xc6, 0xee, 0x05, 0xdb, 0x06, 0xa9, 0x45, 0x32, 0xfd, + 0xa7, 0xdd, 0xc4, 0x4a, 0x16, 0x95, 0xe5, 0xce, 0x1a, 0x3d, + 0x3c, 0x76, 0xdb, 0x39, 0x3a, 0x72, 0x65, 0x63, 0x69, 0x70, + 0x69, 0x65, 0x6e, 0x74, 0x32, 0x30, 0x3a, 0x8a, 0xe7, 0x2e, + 0xfa, 0xb0, 0x95, 0x94, 0x66, 0x51, 0x12, 0xe6, 0xd4, 0x9d, + 0xfd, 0x19, 0x41, 0x53, 0x8c, 0xf3, 0x74, 0x36, 0x3a, 0x73, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x32, 0x30, 0x3a, 0xb6, 0xc0, + 0x3d, 0xe5, 0x7d, 0xdf, 0x03, 0x69, 0xc7, 0x20, 0x7d, 0x2d, + 0x11, 0x3a, 0xdf, 0xf8, 0x20, 0x51, 0x99, 0xcf, 0x39, 0x3a, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x32, + 0x37, 0x3a, 0x32, 0x30, 0x31, 0x38, 0x2d, 0x30, 0x31, 0x2d, + 0x30, 0x32, 0x54, 0x30, 0x33, 0x3a, 0x30, 0x34, 0x3a, 0x30, + 0x35, 0x2e, 0x30, 0x30, 0x36, 0x30, 0x30, 0x30, 0x5a, 0x65, + }; + + Assert.True(pubKey.VerifyProof(payload, pk.Prove(payload))); + Assert.False(pubKey.VerifyProof(payload.Skip(1).ToArray(), pk.Prove(payload))); + Assert.False(wrongPubKey.VerifyProof(payload, pk.Prove(payload))); + } + [Fact] public void ExchangeTest() { diff --git a/test/Libplanet.Tests/Crypto/PublicKeyTest.cs b/test/Libplanet.Tests/Crypto/PublicKeyTest.cs index 9e62c6fdff1..7554b691aa2 100644 --- a/test/Libplanet.Tests/Crypto/PublicKeyTest.cs +++ b/test/Libplanet.Tests/Crypto/PublicKeyTest.cs @@ -110,6 +110,62 @@ public void Verify() Assert.False(pubKey.Verify(payload, default(ImmutableArray))); } + [Fact] + public void VerifyProof() + { + var x = new PrivateKey(); + var pubKey = new PublicKey( + new byte[] + { + 0x04, 0x93, 0x63, 0xa5, 0x0b, 0x13, 0xda, 0xd9, 0xa4, 0xd4, + 0xde, 0x97, 0x03, 0xf7, 0xcc, 0x32, 0x6e, 0x2b, 0xb0, 0x5e, + 0x00, 0xe6, 0x7a, 0x44, 0x03, 0x6f, 0x4d, 0x96, 0x5a, 0xb6, + 0xff, 0xcf, 0x88, 0x4d, 0x26, 0x07, 0x33, 0x2f, 0x91, 0x51, + 0xe3, 0xcf, 0xd8, 0x1a, 0x60, 0x5b, 0xd9, 0x3e, 0x6d, 0x69, + 0x8a, 0x4e, 0x70, 0xb9, 0xac, 0x1d, 0xb1, 0xd4, 0xbe, 0xc1, + 0x41, 0x08, 0x95, 0xa9, 0xc0, + } + ); + var payload = new byte[] + { + 0x64, 0x37, 0x3a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x6c, 0x65, 0x31, 0x30, 0x3a, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x36, 0x35, 0x3a, 0x04, 0xb5, + 0xa2, 0x4a, 0xa2, 0x11, 0x27, 0x20, 0x42, 0x3b, 0xad, 0x39, + 0xa0, 0x20, 0x51, 0x82, 0x37, 0x9d, 0x6f, 0x2b, 0x33, 0xe3, + 0x48, 0x7c, 0x9a, 0xb6, 0xcc, 0x8f, 0xc4, 0x96, 0xf8, 0xa5, + 0x48, 0x34, 0x40, 0xef, 0xbb, 0xef, 0x06, 0x57, 0xac, 0x2e, + 0xf6, 0xc6, 0xee, 0x05, 0xdb, 0x06, 0xa9, 0x45, 0x32, 0xfd, + 0xa7, 0xdd, 0xc4, 0x4a, 0x16, 0x95, 0xe5, 0xce, 0x1a, 0x3d, + 0x3c, 0x76, 0xdb, 0x39, 0x3a, 0x72, 0x65, 0x63, 0x69, 0x70, + 0x69, 0x65, 0x6e, 0x74, 0x32, 0x30, 0x3a, 0x8a, 0xe7, 0x2e, + 0xfa, 0xb0, 0x95, 0x94, 0x66, 0x51, 0x12, 0xe6, 0xd4, 0x9d, + 0xfd, 0x19, 0x41, 0x53, 0x8c, 0xf3, 0x74, 0x36, 0x3a, 0x73, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x32, 0x30, 0x3a, 0xb6, 0xc0, + 0x3d, 0xe5, 0x7d, 0xdf, 0x03, 0x69, 0xc7, 0x20, 0x7d, 0x2d, + 0x11, 0x3a, 0xdf, 0xf8, 0x20, 0x51, 0x99, 0xcf, 0x39, 0x3a, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x32, + 0x37, 0x3a, 0x32, 0x30, 0x31, 0x38, 0x2d, 0x30, 0x31, 0x2d, + 0x30, 0x32, 0x54, 0x30, 0x33, 0x3a, 0x30, 0x34, 0x3a, 0x30, + 0x35, 0x2e, 0x30, 0x30, 0x36, 0x30, 0x30, 0x30, 0x5a, 0x65, + }; + var proof = new Proof(new byte[] + { + 0x03, 0x47, 0xfc, 0xcb, 0x9f, 0x8b, 0x62, 0x8c, 0x00, 0x92, + 0x62, 0x7a, 0x7b, 0x91, 0x1a, 0x8e, 0x5b, 0xfb, 0xb4, 0x0b, + 0x5a, 0x25, 0xc1, 0x83, 0xf3, 0x4e, 0x91, 0x51, 0x3b, 0xaa, + 0xbd, 0x11, 0xfd, 0x9f, 0x72, 0xcd, 0x88, 0xac, 0x09, 0xab, + 0xe4, 0x97, 0xdb, 0x2b, 0x5e, 0x05, 0xb2, 0x52, 0x2c, 0x02, + 0xab, 0xd9, 0xb8, 0x5c, 0x62, 0x37, 0xcb, 0x48, 0x54, 0x08, + 0xd4, 0x6a, 0x13, 0x1e, 0xc1, 0xcd, 0xa7, 0xbc, 0xe3, 0x6c, + 0xce, 0x94, 0xaa, 0xd4, 0xca, 0x00, 0xcb, 0x3a, 0x3f, 0x24, + 0x9d, 0x4f, 0xaf, 0x76, 0x22, 0xa7, 0x28, 0x67, 0x2b, 0x08, + 0xa9, 0x8c, 0xa0, 0x63, 0xda, 0x27, 0xfa, + }); + Assert.True(pubKey.VerifyProof(payload, proof)); + Assert.False(pubKey.VerifyProof(payload, new Proof(new byte[97]))); + } + [Fact] public void VerifyShouldNotCrashForAnyInputs() { From 36ea1332c4a2c524c891f660e7f355a7a97e1173 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 21:57:10 +0900 Subject: [PATCH 12/61] feat: Move lazy calculation of Proof.Hash to constructor --- src/Libplanet.Crypto/Proof.cs | 42 +++++++++-------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index 61fba1a8699..193cd60642f 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -13,22 +13,25 @@ namespace Libplanet.Crypto /// Represents a proof that validators submits to be a proposer. /// Once decided, it can be a source of random seed. /// - public struct Proof : IBencodable, IEquatable, IComparable, IComparable + public readonly struct Proof : IBencodable, IEquatable, IComparable, IComparable { private readonly ImmutableArray _piBytes; - private ImmutableArray? _hash; + private readonly ImmutableArray _hash; public Proof(IReadOnlyList piBytes) { - if (piBytes.Count != 97) + try { - throw new ArgumentException( - $"Proof byte length expected to be 97(gamma:33 + c:32 + s:32), " + - $"but found {piBytes.Count}"); + _hash = CryptoConfig.ConsensusCryptoBackend + .ProofToHash(piBytes.ToArray()).ToImmutableArray(); + } + catch (ArgumentException e) + { + throw new InvalidProofException( + $"Bytes of Proof is invalid", e); } _piBytes = piBytes.ToImmutableArray(); - _hash = null; } public Proof(IValue bencoded) @@ -62,30 +65,7 @@ public Proof(Binary bencoded) /// Hash of the . /// It can be used as a random hash that can be verified. /// - public ImmutableArray Hash - { - get - { - if (_hash is { } hash) - { - return hash; - } - else - { - try - { - _hash = CryptoConfig.ConsensusCryptoBackend - .ProofToHash(ToByteArray()).ToImmutableArray(); - return (ImmutableArray)_hash; - } - catch (ArgumentException e) - { - throw new InvalidProofException( - $"Bytes of Proof is invalid", e); - } - } - } - } + public ImmutableArray Hash => _hash; /// /// Integer form of . From 7b7d40e8bfdc108ad992c81fe8367041346cc11c Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 21:58:35 +0900 Subject: [PATCH 13/61] feat: Remove Lot.Verify() --- src/Libplanet.Types/Consensus/Lot.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Libplanet.Types/Consensus/Lot.cs b/src/Libplanet.Types/Consensus/Lot.cs index 629dc264924..38d3e0fba0a 100644 --- a/src/Libplanet.Types/Consensus/Lot.cs +++ b/src/Libplanet.Types/Consensus/Lot.cs @@ -9,7 +9,7 @@ namespace Libplanet.Types.Consensus { - public struct Lot : ILot, IEquatable, IBencodable + public readonly struct Lot : ILot, IEquatable, IBencodable { private static readonly Binary PublicKeyKey = new Binary(new byte[] { 0x70 }); // 'p' private static readonly Binary ProofKey = new Binary(new byte[] { 0x50 }); // 'P' @@ -72,16 +72,6 @@ public IValue Bencoded .Add(PublicKeyKey, PublicKey.Format(true)) .Add(ProofKey, Proof.ByteArray); - /// - /// Verifies whether is proved by - /// that is corresponding to . - /// - /// true if the proves authenticity of - /// the . - /// Otherwise false. - public bool Verify() - => PublicKey.VerifyProof(_codec.Encode(_metadata.Bencoded), Proof); - /// public bool Equals(Lot other) => _metadata.Equals(other._metadata) From 8c559db8b5d13dcbd0bd53dadbc97d5832048079 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 22:00:16 +0900 Subject: [PATCH 14/61] fix: Fix LotMetadata null key Bencodex constructor --- src/Libplanet.Types/Consensus/LotMetadata.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Libplanet.Types/Consensus/LotMetadata.cs b/src/Libplanet.Types/Consensus/LotMetadata.cs index 1b227740adb..d88e97b6c96 100644 --- a/src/Libplanet.Types/Consensus/LotMetadata.cs +++ b/src/Libplanet.Types/Consensus/LotMetadata.cs @@ -9,7 +9,7 @@ namespace Libplanet.Types.Consensus /// /// Metadata of the . /// - public struct LotMetadata : ILotMetadata, IEquatable, IBencodable + public readonly struct LotMetadata : ILotMetadata, IEquatable, IBencodable { private static readonly Binary HeightKey = new Binary(new byte[] { 0x48 }); // 'H' @@ -57,7 +57,7 @@ private LotMetadata(Dictionary bencoded) : this( (Integer)bencoded[HeightKey], (Integer)bencoded[RoundKey], - new Proof(bencoded[LastProofKey])) + bencoded.TryGetValue(LastProofKey, out IValue p) ? (Proof?)new Proof(p) : null) { } From b170752ea454e87d8c0f09d2bbd362519c1d64c9 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 22:02:16 +0900 Subject: [PATCH 15/61] test: Add Lot tests, Fix Proof tests --- .../Consensus/LotMetadataTest.cs | 30 ++++++++ test/Libplanet.Tests/Consensus/LotTest.cs | 37 ++++++++++ test/Libplanet.Tests/Crypto/ProofTest.cs | 71 ++++++------------- test/Libplanet.Tests/Crypto/PublicKeyTest.cs | 3 +- 4 files changed, 91 insertions(+), 50 deletions(-) create mode 100644 test/Libplanet.Tests/Consensus/LotMetadataTest.cs create mode 100644 test/Libplanet.Tests/Consensus/LotTest.cs diff --git a/test/Libplanet.Tests/Consensus/LotMetadataTest.cs b/test/Libplanet.Tests/Consensus/LotMetadataTest.cs new file mode 100644 index 00000000000..8e8f9520f63 --- /dev/null +++ b/test/Libplanet.Tests/Consensus/LotMetadataTest.cs @@ -0,0 +1,30 @@ +using Bencodex; +using Libplanet.Crypto; +using Libplanet.Types.Consensus; +using Xunit; + +namespace Libplanet.Tests.Consensus +{ + public class LotMetadataTest + { + [Fact] + public void Bencode() + { + var lotMetadata = new LotMetadata(0L, 0, null); + Assert.Equal(lotMetadata, new LotMetadata(lotMetadata.Bencoded)); + } + + [Fact] + public void Prove() + { + var privateKey = new PrivateKey(); + var lastProof = new LotMetadata(0L, 0, null).Prove(privateKey).Proof; + var lotMetadata = new LotMetadata(1L, 0, lastProof); + var lot = lotMetadata.Prove(privateKey); + Assert.Equal(lotMetadata.Height, lot.Height); + Assert.Equal(lotMetadata.Round, lot.Round); + Assert.Equal(lotMetadata.LastProof, lot.LastProof); + Assert.Equal(privateKey.Prove(new Codec().Encode(lotMetadata.Bencoded)), lot.Proof); + } + } +} diff --git a/test/Libplanet.Tests/Consensus/LotTest.cs b/test/Libplanet.Tests/Consensus/LotTest.cs new file mode 100644 index 00000000000..6c577d34795 --- /dev/null +++ b/test/Libplanet.Tests/Consensus/LotTest.cs @@ -0,0 +1,37 @@ +using System; +using Bencodex; +using Libplanet.Crypto; +using Libplanet.Types.Consensus; +using Xunit; + +namespace Libplanet.Tests.Consensus +{ + public class LotTest + { + [Fact] + public void Constructor() + { + var privateKey = new PrivateKey(); + var unauthorizedPublicKey = new PrivateKey().PublicKey; + var lotMetadata = new LotMetadata(0L, 0, null); + + new Lot( + lotMetadata, + privateKey.PublicKey, + privateKey.Prove(new Codec().Encode(lotMetadata.Bencoded))); + + Assert.Throws( + () => new Lot( + lotMetadata, + unauthorizedPublicKey, + privateKey.Prove(new Codec().Encode(lotMetadata.Bencoded)))); + } + + [Fact] + public void Bencode() + { + var lot = new LotMetadata(0L, 0, null).Prove(new PrivateKey()); + Assert.Equal(lot, new Lot(lot.Bencoded)); + } + } +} diff --git a/test/Libplanet.Tests/Crypto/ProofTest.cs b/test/Libplanet.Tests/Crypto/ProofTest.cs index e12ee3ec1dd..d121e5979cc 100644 --- a/test/Libplanet.Tests/Crypto/ProofTest.cs +++ b/test/Libplanet.Tests/Crypto/ProofTest.cs @@ -10,55 +10,50 @@ namespace Libplanet.Tests.Crypto public class ProofTest { private readonly Random _rnd = new Random(); + private readonly byte[] _validProofBytes = new byte[] + { + 0x03, 0x47, 0xfc, 0xcb, 0x9f, 0x8b, 0x62, 0x8c, 0x00, 0x92, + 0x62, 0x7a, 0x7b, 0x91, 0x1a, 0x8e, 0x5b, 0xfb, 0xb4, 0x0b, + 0x5a, 0x25, 0xc1, 0x83, 0xf3, 0x4e, 0x91, 0x51, 0x3b, 0xaa, + 0xbd, 0x11, 0xfd, 0x9f, 0x72, 0xcd, 0x88, 0xac, 0x09, 0xab, + 0xe4, 0x97, 0xdb, 0x2b, 0x5e, 0x05, 0xb2, 0x52, 0x2c, 0x02, + 0xab, 0xd9, 0xb8, 0x5c, 0x62, 0x37, 0xcb, 0x48, 0x54, 0x08, + 0xd4, 0x6a, 0x13, 0x1e, 0xc1, 0xcd, 0xa7, 0xbc, 0xe3, 0x6c, + 0xce, 0x94, 0xaa, 0xd4, 0xca, 0x00, 0xcb, 0x3a, 0x3f, 0x24, + 0x9d, 0x4f, 0xaf, 0x76, 0x22, 0xa7, 0x28, 0x67, 0x2b, 0x08, + 0xa9, 0x8c, 0xa0, 0x63, 0xda, 0x27, 0xfa, + }; [Fact] public void Constructor() { - var acceptablePiBytes = new byte[97]; + new Proof(_validProofBytes); - for (int i = 0; i < 100; i++) - { - _rnd.NextBytes(acceptablePiBytes); - new Proof(acceptablePiBytes); - - if (i != 97) - { - var unacceptablePiBytes = new byte[i]; - _rnd.NextBytes(unacceptablePiBytes); - Assert.Throws(() => new Proof(unacceptablePiBytes)); - } - } + var randomPiBytes = new byte[97]; + _rnd.NextBytes(randomPiBytes); + Assert.Throws(() => new Proof(randomPiBytes)); } [Fact] public void ConstructorDifferentEncoding() { - var piBytes = new byte[97]; - _rnd.NextBytes(piBytes); - - Assert.Equal(new Proof(piBytes), new Proof(new Binary(piBytes))); - Assert.Equal(new Proof(piBytes), new Proof((IValue)new Binary(piBytes))); + Assert.Equal( + new Proof(_validProofBytes), new Proof(new Binary(_validProofBytes))); + Assert.Equal( + new Proof(_validProofBytes), new Proof((IValue)new Binary(_validProofBytes))); } [Fact] public void Bencoded() { - var piBytes = new byte[97]; - _rnd.NextBytes(piBytes); - - var proof = new Proof(piBytes); - + var proof = new Proof(_validProofBytes); Assert.Equal(proof, new Proof(proof.Bencoded)); } [Fact] public void ByteArray() { - var piBytes = new byte[97]; - _rnd.NextBytes(piBytes); - - var proof = new Proof(piBytes); - + var proof = new Proof(_validProofBytes); Assert.Equal(proof, new Proof(proof.ByteArray)); Assert.Equal(proof, new Proof(proof.ToByteArray())); } @@ -82,11 +77,6 @@ public void Verify() [Fact] public void Hash() { - var randomPiBytes = new byte[97]; - _rnd.NextBytes(randomPiBytes); - var invalidProof = new Proof(randomPiBytes); - Assert.Throws(() => invalidProof.Hash); - var payload = new byte[100]; _rnd.NextBytes(payload); @@ -105,11 +95,6 @@ public void HashInt() var maxInt = new BigInteger( Enumerable.Repeat(byte.MaxValue, 64).Concat(new byte[] { 0 }).ToArray()); - var randomPiBytes = new byte[97]; - _rnd.NextBytes(randomPiBytes); - var invalidProof = new Proof(randomPiBytes); - Assert.Throws(() => invalidProof.HashInt); - var privateKey = new PrivateKey(); var payload = new byte[100]; _rnd.NextBytes(payload); @@ -135,11 +120,6 @@ public void HashInt() [Fact] public void Seed() { - var randomPiBytes = new byte[97]; - _rnd.NextBytes(randomPiBytes); - var invalidProof = new Proof(randomPiBytes); - Assert.Throws(() => invalidProof.Seed); - var payload = new byte[100]; _rnd.NextBytes(payload); @@ -154,11 +134,6 @@ public void Seed() [Fact] public void Draw() { - var randomPiBytes = new byte[97]; - _rnd.NextBytes(randomPiBytes); - var invalidProof = new Proof(randomPiBytes); - Assert.Throws(() => invalidProof.Draw(5, 10, 20)); - var payload = new byte[100]; _rnd.NextBytes(payload); diff --git a/test/Libplanet.Tests/Crypto/PublicKeyTest.cs b/test/Libplanet.Tests/Crypto/PublicKeyTest.cs index 7554b691aa2..5bbb97c8a60 100644 --- a/test/Libplanet.Tests/Crypto/PublicKeyTest.cs +++ b/test/Libplanet.Tests/Crypto/PublicKeyTest.cs @@ -113,7 +113,6 @@ public void Verify() [Fact] public void VerifyProof() { - var x = new PrivateKey(); var pubKey = new PublicKey( new byte[] { @@ -163,7 +162,7 @@ public void VerifyProof() 0xa9, 0x8c, 0xa0, 0x63, 0xda, 0x27, 0xfa, }); Assert.True(pubKey.VerifyProof(payload, proof)); - Assert.False(pubKey.VerifyProof(payload, new Proof(new byte[97]))); + Assert.False(pubKey.VerifyProof(payload, new PrivateKey().Prove(payload))); } [Fact] From 5eaef4652ca7a29cb4134795551bdfcffbcd24f9 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 21 Mar 2024 15:59:25 +0900 Subject: [PATCH 16/61] feat: Add Proof on the Block --- src/Libplanet.Store/BlockDigest.cs | 3 +++ src/Libplanet.Types/Blocks/Block.cs | 3 +++ src/Libplanet.Types/Blocks/BlockContent.cs | 3 +++ src/Libplanet.Types/Blocks/BlockHeader.cs | 3 +++ src/Libplanet.Types/Blocks/BlockMarshaler.cs | 11 ++++++++++ src/Libplanet.Types/Blocks/BlockMetadata.cs | 22 ++++++++++++++++--- src/Libplanet.Types/Blocks/IBlockMetadata.cs | 9 ++++++++ .../Blocks/PreEvaluationBlock.cs | 3 +++ .../Blocks/PreEvaluationBlockHeader.cs | 3 +++ .../Blockchain/BlockChain.ProposeBlock.cs | 11 ++++++++-- 10 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/Libplanet.Store/BlockDigest.cs b/src/Libplanet.Store/BlockDigest.cs index e2871df7b67..872c3096578 100644 --- a/src/Libplanet.Store/BlockDigest.cs +++ b/src/Libplanet.Store/BlockDigest.cs @@ -98,6 +98,9 @@ public BlockDigest(Bencodex.Types.Dictionary dict) /// public BlockCommit? LastCommit => _metadata.LastCommit; + /// + public Proof? Proof => _metadata.Proof; + /// public HashDigest? EvidenceHash => _metadata.EvidenceHash; diff --git a/src/Libplanet.Types/Blocks/Block.cs b/src/Libplanet.Types/Blocks/Block.cs index f2d5b19d1f7..a477fb1b8d6 100644 --- a/src/Libplanet.Types/Blocks/Block.cs +++ b/src/Libplanet.Types/Blocks/Block.cs @@ -128,6 +128,9 @@ BlockHash Hash /// public BlockCommit? LastCommit => _preEvaluationBlock.LastCommit; + /// + public Proof? Proof => _preEvaluationBlock.Proof; + /// public HashDigest? EvidenceHash => _preEvaluationBlock.EvidenceHash; diff --git a/src/Libplanet.Types/Blocks/BlockContent.cs b/src/Libplanet.Types/Blocks/BlockContent.cs index 128d8dd4e8d..7149f3656af 100644 --- a/src/Libplanet.Types/Blocks/BlockContent.cs +++ b/src/Libplanet.Types/Blocks/BlockContent.cs @@ -157,6 +157,9 @@ public BlockContent( /// public BlockCommit? LastCommit => _blockMetadata.LastCommit; + /// + public Proof? Proof => _blockMetadata.Proof; + /// public HashDigest? EvidenceHash => _blockMetadata.EvidenceHash; diff --git a/src/Libplanet.Types/Blocks/BlockHeader.cs b/src/Libplanet.Types/Blocks/BlockHeader.cs index 6053d424baa..bb5133cf7f0 100644 --- a/src/Libplanet.Types/Blocks/BlockHeader.cs +++ b/src/Libplanet.Types/Blocks/BlockHeader.cs @@ -160,6 +160,9 @@ BlockHash Hash /// public BlockCommit? LastCommit => _preEvaluationBlockHeader.LastCommit; + /// + public Proof? Proof => _preEvaluationBlockHeader.Proof; + /// public HashDigest? EvidenceHash => _preEvaluationBlockHeader.EvidenceHash; diff --git a/src/Libplanet.Types/Blocks/BlockMarshaler.cs b/src/Libplanet.Types/Blocks/BlockMarshaler.cs index 1ca963c4cd0..08b52c0a639 100644 --- a/src/Libplanet.Types/Blocks/BlockMarshaler.cs +++ b/src/Libplanet.Types/Blocks/BlockMarshaler.cs @@ -41,6 +41,9 @@ public static class BlockMarshaler private static readonly Binary PreEvaluationHashKey = new Binary(0x63); // 'c' private static readonly Binary LastCommitKey = new Binary(0x43); // 'C' + private static readonly Binary ProofKey = + new Binary(new byte[] { 0x52 }); // 'R' + private static readonly Binary EvidenceHashKey = new Binary(new byte[] { 0x76 }); // 'v' @@ -76,6 +79,11 @@ public static Dictionary MarshalBlockMetadata(IBlockMetadata metadata) dict = dict.Add(LastCommitKey, commit.Bencoded); } + if (metadata.Proof is { } proof) + { + dict = dict.Add(ProofKey, proof.Bencoded); + } + if (metadata.EvidenceHash is { } evidenceHash) { dict = dict.Add(EvidenceHashKey, evidenceHash.ByteArray); @@ -204,6 +212,9 @@ public static BlockMetadata UnmarshalBlockMetadata(Dictionary marshaled) lastCommit: marshaled.ContainsKey(LastCommitKey) ? new BlockCommit(marshaled[LastCommitKey]) : (BlockCommit?)null, + proof: marshaled.ContainsKey(ProofKey) + ? new Proof(marshaled[ProofKey]) + : (Proof?)null), evidenceHash: marshaled.TryGetValue(EvidenceHashKey, out IValue ehv) ? new HashDigest(ehv) : (HashDigest?)null); diff --git a/src/Libplanet.Types/Blocks/BlockMetadata.cs b/src/Libplanet.Types/Blocks/BlockMetadata.cs index 23d239b0a99..b81adb55574 100644 --- a/src/Libplanet.Types/Blocks/BlockMetadata.cs +++ b/src/Libplanet.Types/Blocks/BlockMetadata.cs @@ -138,6 +138,7 @@ public BlockMetadata(IBlockMetadata metadata) previousHash: metadata.PreviousHash, txHash: metadata.TxHash, lastCommit: metadata.LastCommit, + proof: metadata.Proof, evidenceHash: metadata.EvidenceHash) { } @@ -153,9 +154,10 @@ public BlockMetadata(IBlockMetadata metadata) /// Goes to . /// Goes to . /// Goes to . + /// Goes to . /// Goes to . /// + /// PublicKey?, BlockHash?, HashDigest{SHA256}?, BlockCommit?, Proof?, HashDigest{SHA256}?)"/> public BlockMetadata( long index, DateTimeOffset timestamp, @@ -163,6 +165,7 @@ public BlockMetadata( BlockHash? previousHash, HashDigest? txHash, BlockCommit? lastCommit, + Proof? proof, HashDigest? evidenceHash) : this( protocolVersion: CurrentProtocolVersion, @@ -173,6 +176,7 @@ public BlockMetadata( previousHash: previousHash, txHash: txHash, lastCommit: lastCommit, + proof: proof, evidenceHash: evidenceHash) { } @@ -193,6 +197,7 @@ public BlockMetadata( /// Goes to . /// Goes to . /// Goes to . + /// Goes to . /// Goes to . /// Thrown when /// is less than zero or greater than @@ -222,6 +227,7 @@ public BlockMetadata( BlockHash? previousHash, HashDigest? txHash, BlockCommit? lastCommit, + Proof? proof, HashDigest? evidenceHash) { // Protocol version validity check. @@ -309,6 +315,7 @@ public BlockMetadata( TxHash = txHash; LastCommit = lastCommit; + Proof = proof; EvidenceHash = evidenceHash; } @@ -331,9 +338,13 @@ public BlockMetadata( public BlockHash? PreviousHash { get; } /// - public HashDigest? TxHash { get; private set; } + public HashDigest? TxHash { get; } + + /// + public BlockCommit? LastCommit { get; } - public BlockCommit? LastCommit { get; set; } + /// + public Proof? Proof { get; } public HashDigest? EvidenceHash { get; private set; } @@ -368,6 +379,11 @@ public Bencodex.Types.Dictionary MakeCandidateData() dict = dict.Add("last_commit", lastCommit.ToHash().ByteArray); } + if (Proof is { } proof) + { + dict = dict.Add("proof", proof.ByteArray); + } + if (EvidenceHash is { } evidenceHash) { dict = dict.Add("evidence_hash", evidenceHash.ByteArray); diff --git a/src/Libplanet.Types/Blocks/IBlockMetadata.cs b/src/Libplanet.Types/Blocks/IBlockMetadata.cs index 99e9ff60587..f8227e76494 100644 --- a/src/Libplanet.Types/Blocks/IBlockMetadata.cs +++ b/src/Libplanet.Types/Blocks/IBlockMetadata.cs @@ -91,6 +91,15 @@ public interface IBlockMetadata /// BlockCommit? LastCommit { get; } + /// + /// The from the proposer candidate. This can be verified with + /// proposer candidate's , and can be used as a source of + /// proposer candidate dependent random variable. With above property, + /// It is used for proposer sortition, and once proposer is decided, can be interpreted + /// as source of proposer dependent random variable. + /// + Proof? Proof { get; } + /// /// Committing s of vote infraction /// that has been made on previous blocks. diff --git a/src/Libplanet.Types/Blocks/PreEvaluationBlock.cs b/src/Libplanet.Types/Blocks/PreEvaluationBlock.cs index 1298955730e..77e3a9e5bba 100644 --- a/src/Libplanet.Types/Blocks/PreEvaluationBlock.cs +++ b/src/Libplanet.Types/Blocks/PreEvaluationBlock.cs @@ -83,6 +83,9 @@ internal PreEvaluationBlock( /// public BlockCommit? LastCommit => _header.LastCommit; + /// + public Proof? Proof => _header.Proof; + /// public HashDigest? EvidenceHash => _header.EvidenceHash; diff --git a/src/Libplanet.Types/Blocks/PreEvaluationBlockHeader.cs b/src/Libplanet.Types/Blocks/PreEvaluationBlockHeader.cs index bd12e6eb3c5..8c0995100d3 100644 --- a/src/Libplanet.Types/Blocks/PreEvaluationBlockHeader.cs +++ b/src/Libplanet.Types/Blocks/PreEvaluationBlockHeader.cs @@ -97,6 +97,9 @@ public PreEvaluationBlockHeader( /// public BlockCommit? LastCommit => Metadata.LastCommit; + /// + public Proof? Proof => Metadata.Proof; + /// public HashDigest? EvidenceHash => Metadata.EvidenceHash; diff --git a/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs b/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs index f2e98380a79..e85147b1f43 100644 --- a/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs +++ b/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs @@ -62,6 +62,7 @@ public static Block ProposeGenesisBlock( previousHash: null, txHash: BlockContent.DeriveTxHash(transactions), lastCommit: null, + proof: null, evidenceHash: null), transactions: transactions, evidence: Array.Empty()); @@ -84,6 +85,7 @@ public static Block ProposeGenesisBlock( /// /// The evidence of the previous /// . + /// The proved from proposer candidate. /// The pending s to be committed on the /// . /// An optional comparer for give certain transactions to @@ -94,6 +96,7 @@ public static Block ProposeGenesisBlock( public Block ProposeBlock( PrivateKey proposer, BlockCommit lastCommit = null, + Proof? proof = null, ImmutableArray? evidence = null, IComparer txPriority = null) { @@ -116,6 +119,7 @@ public Block ProposeBlock( proposer, transactions, lastCommit, + proof, evidence ?? ImmutableArray.Empty); _logger.Debug( "Proposed block #{Index} {Hash} with previous hash {PreviousHash}", @@ -132,8 +136,8 @@ public Block ProposeBlock( /// list of s. /// /// - /// Unlike , + /// Unlike , /// this may result in a that does not conform to the /// . /// @@ -143,12 +147,14 @@ public Block ProposeBlock( /// The list of s to include. /// The evidence of the previous /// . + /// The proved from proposer candidate. /// The s to be committed. /// A that is proposed. internal Block ProposeBlock( PrivateKey proposer, ImmutableList transactions, BlockCommit lastCommit, + Proof? proof, ImmutableArray evidence) { long index = Count; @@ -174,6 +180,7 @@ internal Block ProposeBlock( previousHash: prevHash, txHash: BlockContent.DeriveTxHash(orderedTransactions), lastCommit: lastCommit, + proof: proof, evidenceHash: BlockContent.DeriveEvidenceHash(orderedEvidence)), transactions: orderedTransactions, evidence: orderedEvidence); From ea4a8bb1dd40cab9f4d0797b623fe3e03349b6c8 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 22 Mar 2024 15:12:39 +0900 Subject: [PATCH 17/61] test: Fix test build --- src/Libplanet.Net/Consensus/Context.cs | 2 +- src/Libplanet.Types/Blocks/BlockMarshaler.cs | 2 +- .../GeneratedBlockChainFixture.cs | 1 + .../GraphTypes/BlockTypeTest.cs | 1 + .../Consensus/ContextNonProposerTest.cs | 3 + .../ContextProposerValidRoundTest.cs | 1 + .../SwarmTest.Broadcast.cs | 5 ++ test/Libplanet.Net.Tests/SwarmTest.Preload.cs | 90 +++++++++++++++---- .../Action/ActionEvaluatorTest.cs | 6 ++ .../Blockchain/BlockChainTest.Append.cs | 47 ++++++++-- .../Blockchain/BlockChainTest.Internals.cs | 3 + .../Blockchain/BlockChainTest.ProposeBlock.cs | 38 ++++++-- .../BlockChainTest.ValidateNextBlock.cs | 30 +++++++ .../Blockchain/BlockChainTest.cs | 21 +++++ .../Blocks/BlockContentTest.cs | 4 + .../Blocks/BlockMetadataExtensionsTest.cs | 1 + .../Blocks/BlockMetadataTest.cs | 9 ++ .../Blocks/PreEvaluationBlockTest.cs | 2 + .../Fixtures/BlockContentFixture.cs | 4 + test/Libplanet.Tests/TestUtils.cs | 5 ++ 20 files changed, 238 insertions(+), 37 deletions(-) diff --git a/src/Libplanet.Net/Consensus/Context.cs b/src/Libplanet.Net/Consensus/Context.cs index 69453b615fb..63e840b4cbd 100644 --- a/src/Libplanet.Net/Consensus/Context.cs +++ b/src/Libplanet.Net/Consensus/Context.cs @@ -420,7 +420,7 @@ private TimeSpan TimeoutPropose(long round) try { var evidence = _blockChain.GetPendingEvidence(); - Block block = _blockChain.ProposeBlock(_privateKey, _lastCommit, evidence); + Block block = _blockChain.ProposeBlock(_privateKey, _lastCommit, null, evidence); _blockChain.Store.PutBlock(block); return block; } diff --git a/src/Libplanet.Types/Blocks/BlockMarshaler.cs b/src/Libplanet.Types/Blocks/BlockMarshaler.cs index 08b52c0a639..c0bd8c3567a 100644 --- a/src/Libplanet.Types/Blocks/BlockMarshaler.cs +++ b/src/Libplanet.Types/Blocks/BlockMarshaler.cs @@ -214,7 +214,7 @@ public static BlockMetadata UnmarshalBlockMetadata(Dictionary marshaled) : (BlockCommit?)null, proof: marshaled.ContainsKey(ProofKey) ? new Proof(marshaled[ProofKey]) - : (Proof?)null), + : (Proof?)null, evidenceHash: marshaled.TryGetValue(EvidenceHashKey, out IValue ehv) ? new HashDigest(ehv) : (HashDigest?)null); diff --git a/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs b/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs index 79487a95b6d..a60e6bdfe3c 100644 --- a/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs +++ b/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs @@ -179,6 +179,7 @@ private void AddBlock(ImmutableArray transactions) Chain.Tip.Hash, BlockContent.DeriveTxHash(transactions), Chain.Store.GetChainBlockCommit(Chain.Store.GetCanonicalChainId()!.Value), + new LotMetadata(Chain.Tip.Index + 1, 0, Chain.Tip.Proof).Prove(proposer).Proof, evidenceHash: null), transactions, evidence: Array.Empty()).Propose(), diff --git a/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs b/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs index 1c47d06289f..5607a05ed7f 100644 --- a/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs +++ b/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs @@ -43,6 +43,7 @@ public async void Query() previousHash: lastBlockHash, txHash: null, lastCommit: lastBlockCommit, + proof: new LotMetadata(2, 0, null).Prove(privateKey).Proof, evidenceHash: null)).Propose(); var stateRootHash = new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)); diff --git a/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs index 9c4e0824ad2..c76f71152fa 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs @@ -181,6 +181,7 @@ public async void EnterPreCommitNilTwoThird() previousHash: blockChain.Tip.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), key); @@ -280,6 +281,7 @@ public async Task EnterPreVoteNilOnInvalidBlockHeader() previousHash: blockChain.Tip.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), TestUtils.PrivateKeys[1]); @@ -429,6 +431,7 @@ message is ConsensusPreCommitMsg commit && previousHash: blockChain.Genesis.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null); var preEval = new PreEvaluationBlock( preEvaluationBlockHeader: new PreEvaluationBlockHeader( diff --git a/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs index 57f30923824..8a4b0300383 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs @@ -182,6 +182,7 @@ public async void EnterValidRoundPreVoteNil() previousHash: blockChain.Tip.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), key); diff --git a/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs b/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs index 77cb117613b..aecdf77c646 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs @@ -22,6 +22,7 @@ using Libplanet.Tests.Blockchain.Evidence; using Libplanet.Tests.Store; using Libplanet.Types.Blocks; +using Libplanet.Types.Consensus; using Libplanet.Types.Evidence; using Libplanet.Types.Tx; using Serilog; @@ -755,12 +756,16 @@ public async Task BroadcastBlockWithSkip() GenesisProposer, new[] { transactions[0] }.ToImmutableList(), TestUtils.CreateBlockCommit(blockChain.Tip), + new LotMetadata(blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof) + .Prove(GenesisProposer).Proof, ImmutableArray.Empty); blockChain.Append(block1, TestUtils.CreateBlockCommit(block1), true); Block block2 = blockChain.ProposeBlock( GenesisProposer, new[] { transactions[1] }.ToImmutableList(), CreateBlockCommit(blockChain.Tip), + new LotMetadata(blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof) + .Prove(GenesisProposer).Proof, ImmutableArray.Empty); blockChain.Append(block2, TestUtils.CreateBlockCommit(block2), true); diff --git a/test/Libplanet.Net.Tests/SwarmTest.Preload.cs b/test/Libplanet.Net.Tests/SwarmTest.Preload.cs index 5d1a8396813..04b63256c10 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.Preload.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.Preload.cs @@ -46,7 +46,10 @@ public async Task InitialBlockDownload() foreach (int i in Enumerable.Range(0, 10)) { Block block = minerChain.ProposeBlock( - minerKey, CreateBlockCommit(minerChain.Tip)); + minerKey, + CreateBlockCommit(minerChain.Tip), + new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(minerKey).Proof); minerChain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -87,17 +90,26 @@ public async Task InitialBlockDownloadStates() minerChain.MakeTransaction(key, new[] { action }); var block = minerChain.ProposeBlock( - minerKey, CreateBlockCommit(minerChain.Tip)); + minerKey, + CreateBlockCommit(minerChain.Tip), + new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(minerKey).Proof); minerChain.Append(block, TestUtils.CreateBlockCommit(block)); minerChain.MakeTransaction(key, new[] { DumbAction.Create((address1, "bar")) }); block = minerChain.ProposeBlock( - minerKey, CreateBlockCommit(minerChain.Tip)); + minerKey, + CreateBlockCommit(minerChain.Tip), + new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(minerKey).Proof); minerChain.Append(block, TestUtils.CreateBlockCommit(block)); minerChain.MakeTransaction(key, new[] { DumbAction.Create((address1, "baz")) }); block = minerChain.ProposeBlock( - minerKey, CreateBlockCommit(minerChain.Tip)); + minerKey, + CreateBlockCommit(minerChain.Tip), + new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(minerKey).Proof); minerChain.Append(block, TestUtils.CreateBlockCommit(block)); try @@ -137,7 +149,9 @@ public async Task Preload() ProposeNext( previousBlock: i == 0 ? minerChain.Genesis : blocks[i - 1], miner: ChainPrivateKey.PublicKey, - lastCommit: CreateBlockCommit(minerChain.Tip)), + lastCommit: CreateBlockCommit(minerChain.Tip), + proof: new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(ChainPrivateKey).Proof), ChainPrivateKey); blocks.Add(block); if (i != 11) @@ -289,8 +303,12 @@ public async Task PreloadWithMaliciousPeer() // Setup initial state where all chains share the same blockchain state. for (int i = 1; i <= initialSharedTipHeight; i++) { + var proposerKey = new PrivateKey(); var block = chainA.ProposeBlock( - new PrivateKey(), TestUtils.CreateBlockCommit(chainA.Tip)); + proposerKey, + TestUtils.CreateBlockCommit(chainA.Tip), + new LotMetadata(chainA.Tip.Index + 1, 0, chainA.Tip.Proof) + .Prove(proposerKey).Proof); chainA.Append(block, TestUtils.CreateBlockCommit(block)); chainB.Append(block, TestUtils.CreateBlockCommit(block)); chainC.Append(block, TestUtils.CreateBlockCommit(block)); @@ -299,14 +317,22 @@ public async Task PreloadWithMaliciousPeer() // Setup malicious node to broadcast. for (int i = initialSharedTipHeight + 1; i < maliciousTipHeight; i++) { + var proposerKey = new PrivateKey(); var block = chainB.ProposeBlock( - new PrivateKey(), TestUtils.CreateBlockCommit(chainB.Tip)); + proposerKey, + TestUtils.CreateBlockCommit(chainB.Tip), + new LotMetadata(chainB.Tip.Index + 1, 0, chainB.Tip.Proof) + .Prove(proposerKey).Proof); chainB.Append(block, TestUtils.CreateBlockCommit(block)); chainC.Append(block, TestUtils.CreateBlockCommit(block)); } + var specialPK = new PrivateKey(); var specialBlock = chainB.ProposeBlock( - new PrivateKey(), TestUtils.CreateBlockCommit(chainB.Tip)); + specialPK, + TestUtils.CreateBlockCommit(chainB.Tip), + new LotMetadata(chainB.Tip.Index + 1, 0, chainB.Tip.Proof) + .Prove(specialPK).Proof); var invalidBlockCommit = new BlockCommit( maliciousTipHeight, 0, @@ -327,8 +353,12 @@ public async Task PreloadWithMaliciousPeer() // Setup honest node with higher tip for (int i = maliciousTipHeight + 1; i <= honestTipHeight; i++) { + var privateKey = new PrivateKey(); var block = chainC.ProposeBlock( - new PrivateKey(), TestUtils.CreateBlockCommit(chainC.Tip)); + privateKey, + TestUtils.CreateBlockCommit(chainC.Tip), + new LotMetadata(chainC.Tip.Index + 1, 0, chainC.Tip.Proof) + .Prove(privateKey).Proof); chainC.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -415,7 +445,10 @@ public async Task NoRenderInPreload() sender.BlockChain.MakeTransaction( privKey, new[] { DumbAction.Create((addr, item)) }); Block block = sender.BlockChain.ProposeBlock( - senderKey, CreateBlockCommit(sender.BlockChain.Tip)); + senderKey, + CreateBlockCommit(sender.BlockChain.Tip), + new LotMetadata(sender.BlockChain.Tip.Index + 1, 0, sender.BlockChain.Tip.Proof) + .Prove(senderKey).Proof); sender.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -456,9 +489,12 @@ public async Task PreloadWithFailedActions() foreach (var unused in Enumerable.Range(0, 10)) { - Block block = minerSwarm.BlockChain.ProposeBlock( - minerKey, CreateBlockCommit(minerSwarm.BlockChain.Tip)); - minerSwarm.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); + Block block = minerChain.ProposeBlock( + minerKey, + CreateBlockCommit(minerChain.Tip), + new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(minerKey).Proof); + minerChain.Append(block, TestUtils.CreateBlockCommit(block)); } try @@ -484,6 +520,8 @@ public async Task PreloadWithFailedActions() ChainPrivateKey, new[] { tx }.ToImmutableList(), CreateBlockCommit(minerChain.Tip), + new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(minerKey).Proof, ImmutableArray.Empty); minerSwarm.BlockChain.Append(block, CreateBlockCommit(block), true); @@ -535,7 +573,10 @@ public async Task PreloadFromNominer() foreach (int i in Enumerable.Range(0, 10)) { Block block = minerChain.ProposeBlock( - minerKey, CreateBlockCommit(minerChain.Tip)); + minerKey, + CreateBlockCommit(minerChain.Tip), + new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(minerKey).Proof); minerChain.Append(block, CreateBlockCommit(block)); } @@ -650,7 +691,10 @@ public async Task PreloadRetryWithNextPeers(int blockCount) for (int i = 0; i < blockCount; ++i) { var block = swarm0.BlockChain.ProposeBlock( - key0, CreateBlockCommit(swarm0.BlockChain.Tip)); + key0, + CreateBlockCommit(swarm0.BlockChain.Tip), + new LotMetadata(swarm0.BlockChain.Tip.Index + 1, 0, swarm0.BlockChain.Tip.Proof) + .Prove(key0).Proof); swarm0.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); swarm1.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -839,11 +883,13 @@ public async Task PreloadAfterReorg() BlockChain minerChain = minerSwarm.BlockChain; BlockChain receiverChain = receiverSwarm.BlockChain; - foreach (int i in Enumerable.Range(0, 25)) { Block block = minerChain.ProposeBlock( - minerKey, CreateBlockCommit(minerChain.Tip)); + minerKey, + CreateBlockCommit(minerChain.Tip), + new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(minerKey).Proof); minerChain.Append(block, CreateBlockCommit(block)); receiverChain.Append(block, CreateBlockCommit(block)); } @@ -852,7 +898,10 @@ public async Task PreloadAfterReorg() foreach (int i in Enumerable.Range(0, 20)) { Block block = receiverForked.ProposeBlock( - minerKey, CreateBlockCommit(receiverForked.Tip)); + minerKey, + CreateBlockCommit(receiverForked.Tip), + new LotMetadata(receiverForked.Tip.Index + 1, 0, receiverForked.Tip.Proof) + .Prove(minerKey).Proof); receiverForked.Append(block, CreateBlockCommit(block)); } @@ -861,7 +910,10 @@ public async Task PreloadAfterReorg() foreach (int i in Enumerable.Range(0, 1)) { Block block = minerChain.ProposeBlock( - minerKey, CreateBlockCommit(minerChain.Tip)); + minerKey, + CreateBlockCommit(minerChain.Tip), + new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) + .Prove(minerKey).Proof); minerChain.Append(block, CreateBlockCommit(block)); } diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs index 6a465c66a5e..b89e0d1e7e6 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -20,6 +20,7 @@ using Libplanet.Tests.Tx; using Libplanet.Types.Assets; using Libplanet.Types.Blocks; +using Libplanet.Types.Consensus; using Libplanet.Types.Evidence; using Libplanet.Types.Tx; using Serilog; @@ -105,6 +106,7 @@ public void Idempotent() previousHash: null, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: evs).Propose(); @@ -357,6 +359,7 @@ public void EvaluateWithCriticalException() previousHash: genesis.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: evs).Propose(); @@ -662,6 +665,7 @@ public void EvaluateTx() previousHash: default(BlockHash), txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: evs).Propose(); @@ -779,6 +783,7 @@ public void EvaluateTxResultThrowingException() previousHash: hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: CreateBlockCommit(hash, 122, 0), + proof: new LotMetadata(123L, 0, null).Prove(GenesisProposer).Proof, evidenceHash: null), transactions: txs, evidence: evs).Propose(); @@ -987,6 +992,7 @@ public void EvaluatePolicyEndBlockActions() GenesisProposer, txs.ToImmutableList(), CreateBlockCommit(chain.Tip), + new LotMetadata(1L, 0, null).Prove(GenesisProposer).Proof, ImmutableArray.Empty); IWorld previousState = _storeFx.StateStore.GetWorld(null); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs index 763255956d0..9bf1378c1ae 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs @@ -19,6 +19,7 @@ using Libplanet.Store.Trie; using Libplanet.Tests.Store; using Libplanet.Types.Blocks; +using Libplanet.Types.Consensus; using Libplanet.Types.Evidence; using Libplanet.Types.Tx; using Serilog; @@ -42,18 +43,21 @@ Func getTxExecution (Address[] addresses, Transaction[] txs) = MakeFixturesForAppendTests(keys: keys); var genesis = _blockChain.Genesis; - Assert.Equal(1, _blockChain.Count); Assert.Empty(_renderer.ActionRecords); Assert.Empty(_renderer.BlockRecords); var block1 = _blockChain.ProposeBlock( - keys[4], TestUtils.CreateBlockCommit(_blockChain.Tip)); + keys[4], + TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(keys[4]).Proof); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); Assert.NotNull(_blockChain.GetBlockCommit(block1.Hash)); Block block2 = _blockChain.ProposeBlock( keys[4], txs.ToImmutableList(), lastCommit: TestUtils.CreateBlockCommit(block1), + proof: new LotMetadata(block1.Index + 1, 0, block1.Proof).Prove(keys[4]).Proof, evidence: ImmutableArray.Empty); foreach (Transaction tx in txs) { @@ -75,10 +79,8 @@ Func getTxExecution } Assert.True(_blockChain.ContainsBlock(block2.Hash)); - RenderRecord.ActionSuccess[] renders = _renderer.ActionSuccessRecords - .Where(r => TestUtils.IsDumbAction(r.Action)) - .ToArray(); + .Where(r => TestUtils.IsDumbAction(r.Action)).ToArray(); DumbAction[] actions = renders.Select(r => TestUtils.ToDumbAction(r.Action)).ToArray(); Assert.Equal(4, renders.Length); Assert.True(renders.All(r => r.Render)); @@ -163,7 +165,6 @@ Func getTxExecution RenderRecord.ActionSuccess[] blockRenders = _renderer.ActionSuccessRecords .Where(r => TestUtils.IsMinerReward(r.Action)) .ToArray(); - Assert.Equal( (Integer)2, (Integer)_blockChain @@ -174,7 +175,6 @@ Func getTxExecution Assert.True(blockRenders.All(r => r.Render)); Assert.Equal(1, blockRenders[0].Context.BlockIndex); Assert.Equal(2, blockRenders[1].Context.BlockIndex); - Assert.Equal( (Integer)1, (Integer)_blockChain @@ -201,7 +201,6 @@ Func getTxExecution { Assert.Null(getTxExecution(genesis.Hash, tx.Id)); Assert.Null(getTxExecution(block1.Hash, tx.Id)); - TxExecution e = getTxExecution(block2.Hash, tx.Id); Assert.False(e.Fail); Assert.Equal(block2.Hash, e.BlockHash); @@ -265,6 +264,8 @@ Func getTxExecution keys[4], new[] { tx1Transfer, tx2Error, tx3Transfer }.ToImmutableList(), TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(keys[4]).Proof, ImmutableArray.Empty); _blockChain.Append(block3, TestUtils.CreateBlockCommit(block3)); var txExecution1 = getTxExecution(block3.Hash, tx1Transfer.Id); @@ -346,6 +347,8 @@ public void AppendModern() miner, new[] { tx1 }.ToImmutableList(), TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(miner).Proof, ImmutableArray.Empty); var commit1 = TestUtils.CreateBlockCommit(block1); _blockChain.Append(block1, commit1); @@ -358,6 +361,8 @@ public void AppendModern() miner, new[] { tx2 }.ToImmutableList(), commit1, + new LotMetadata(block1.Index + 1, 0, block1.Proof) + .Prove(miner).Proof, ImmutableArray.Empty); _blockChain.Append(block2, TestUtils.CreateBlockCommit(block2)); var world2 = _blockChain.GetNextWorldState(); @@ -396,6 +401,8 @@ public void AppendFailDueToInvalidBytesLength() miner, heavyTxs.ToImmutableList(), TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(miner).Proof, ImmutableArray.Empty); long maxBytes = _blockChain.Policy.GetMaxTransactionsBytes(block.Index); Assert.True(block.MarshalBlock().EncodingLength > maxBytes); @@ -426,6 +433,8 @@ public void AppendFailDueToInvalidTxCount() miner, manyTxs.ToImmutableList(), TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(miner).Proof, ImmutableArray.Empty); Assert.Equal(manyTxs.Count, block.Transactions.Count); @@ -502,6 +511,8 @@ TxPolicyViolationException IsSignerValid( miner, new[] { validTx }.ToImmutableList(), TestUtils.CreateBlockCommit(blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(miner).Proof, ImmutableArray.Empty); blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -509,6 +520,8 @@ TxPolicyViolationException IsSignerValid( miner, new[] { invalidTx }.ToImmutableList(), TestUtils.CreateBlockCommit(blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(miner).Proof, ImmutableArray.Empty); Assert.Throws(() => blockChain.Append( block2, TestUtils.CreateBlockCommit(block2))); @@ -526,7 +539,9 @@ public void UnstageAfterAppendComplete() // Mining with empty staged. Block block1 = _blockChain.ProposeBlock( privateKey, - TestUtils.CreateBlockCommit(_blockChain.Tip)); + TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(privateKey).Proof); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); Assert.Empty(_blockChain.GetStagedTransactionIds()); @@ -538,6 +553,8 @@ public void UnstageAfterAppendComplete() privateKey, ImmutableList.Empty.Add(txs[0]), TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(privateKey).Proof, ImmutableArray.Empty); _blockChain.Append(block2, TestUtils.CreateBlockCommit(block2)); Assert.Equal(1, _blockChain.GetStagedTransactionIds().Count); @@ -556,6 +573,8 @@ public void UnstageAfterAppendComplete() privateKey, ImmutableList.Empty.Add(txs[1]), TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(privateKey).Proof, ImmutableArray.Empty); _blockChain.Append(block3, TestUtils.CreateBlockCommit(block3)); Assert.Empty(_blockChain.GetStagedTransactionIds()); @@ -583,6 +602,8 @@ public void DoesNotUnstageOnAppendForForkedChain() privateKey, ImmutableList.Empty.Add(txs[0]), TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(privateKey).Proof, ImmutableArray.Empty); // Not actually unstaged, but lower nonce is filtered for workspace. @@ -602,6 +623,8 @@ public void DoesNotUnstageOnAppendForForkedChain() privateKey, ImmutableList.Empty.Add(txs[1]), TestUtils.CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(privateKey).Proof, ImmutableArray.Empty); // Actually gets unstaged. @@ -734,6 +757,7 @@ public void CannotAppendBlockWithInvalidActions() previousHash: _blockChain.Genesis.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null); var preEval = new PreEvaluationBlock( preEvaluationBlockHeader: new PreEvaluationBlockHeader( @@ -788,6 +812,7 @@ public void DoesNotMigrateStateWithoutAction() previousHash: null, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: evs).Propose(); @@ -807,6 +832,8 @@ public void DoesNotMigrateStateWithoutAction() fx.Proposer, ImmutableList.Empty, TestUtils.CreateBlockCommit(blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(fx.Proposer).Proof, ImmutableArray.Empty); blockChain.Append(emptyBlock, TestUtils.CreateBlockCommit(emptyBlock)); Assert.True(blockChain.GetNextWorldState(emptyBlock.Hash).Legacy); @@ -865,6 +892,7 @@ public void AppendSRHPostponeBPVBump() miner, new[] { tx }.ToImmutableList(), commitBeforeBump, + proof: null, evidence: ImmutableArray.Empty); Assert.Equal( BlockMetadata.CurrentProtocolVersion, @@ -880,6 +908,7 @@ public void AppendSRHPostponeBPVBump() miner, new[] { tx }.ToImmutableList(), commitAfterBump1, + proof: null, evidence: ImmutableArray.Empty); Assert.Equal( BlockMetadata.CurrentProtocolVersion, diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs index d27b747728b..87f1f31af95 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs @@ -10,6 +10,7 @@ using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Types.Blocks; +using Libplanet.Types.Consensus; using Libplanet.Types.Evidence; using Libplanet.Types.Tx; using Xunit; @@ -113,6 +114,8 @@ public void ExecuteActions() _fx.Proposer, txs.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); _blockChain.Append(block1, CreateBlockCommit(block1), render: true); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs index 94d50146efc..94b8ecaa702 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs @@ -55,6 +55,8 @@ public void ProposeBlock() Block anotherBlock = _blockChain.ProposeBlock( proposerB, CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0), + new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) + .Prove(proposerB).Proof, _blockChain.GetPendingEvidence()); _blockChain.Append(anotherBlock, CreateBlockCommit(anotherBlock)); Assert.True(_blockChain.ContainsBlock(anotherBlock.Hash)); @@ -72,9 +74,12 @@ public void ProposeBlock() .GetState(default) ); + var proposerC = new PrivateKey(); Block block3 = _blockChain.ProposeBlock( - new PrivateKey(), + proposerC, CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0), + new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) + .Prove(proposerC).Proof, _blockChain.GetPendingEvidence()); Assert.False(_blockChain.ContainsBlock(block3.Hash)); Assert.Equal(3, _blockChain.Count); @@ -111,9 +116,12 @@ public void ProposeBlock() _blockChain.StageTransaction(heavyTx); } + var proposerD = new PrivateKey(); Block block4 = _blockChain.ProposeBlock( - new PrivateKey(), + proposerD, CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0), + new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) + .Prove(proposerD).Proof, _blockChain.GetPendingEvidence()); Assert.False(_blockChain.ContainsBlock(block4.Hash)); _logger.Debug( @@ -201,7 +209,7 @@ public void CanProposeInvalidBlock() }.ToImmutableList(); var block = blockChain.ProposeBlock( - new PrivateKey(), txs, null, ImmutableArray.Empty); + new PrivateKey(), txs, null, null, ImmutableArray.Empty); Assert.Throws( () => blockChain.Append(block, CreateBlockCommit(block))); } @@ -490,12 +498,15 @@ public void ProposeBlockWithLowerNonces() ), } ); + var proposer = new PrivateKey(); Block block2 = _blockChain.ProposeBlock( - new PrivateKey(), + proposer, CreateBlockCommit( _blockChain.Tip.Hash, _blockChain.Tip.Index, 0), + new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) + .Prove(proposer).Proof, _blockChain.GetPendingEvidence()); _blockChain.Append(block2, CreateBlockCommit(block2)); @@ -537,6 +548,8 @@ public void ProposeBlockWithBlockAction() var block = blockChain.ProposeBlock( privateKey1, CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) + .Prove(privateKey1).Proof, _blockChain.GetPendingEvidence()); blockChain.Append(block, CreateBlockCommit(block)); @@ -558,6 +571,8 @@ public void ProposeBlockWithBlockAction() block = blockChain.ProposeBlock( privateKey1, CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) + .Prove(privateKey1).Proof, _blockChain.GetPendingEvidence()); blockChain.Append(block, CreateBlockCommit(block)); @@ -628,9 +643,12 @@ public void ProposeBlockWithLastCommit() VoteFlag.PreCommit).Sign(key)).ToImmutableArray(); var blockCommit = new BlockCommit( _blockChain.Tip.Index, 0, _blockChain.Tip.Hash, votes); + var proposer = new PrivateKey(); Block block = _blockChain.ProposeBlock( - new PrivateKey(), + proposer, blockCommit, + new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) + .Prove(proposer).Proof, _blockChain.GetPendingEvidence()); Assert.NotNull(block.LastCommit); @@ -665,9 +683,12 @@ public void IgnoreLowerNonceTxsAndPropose() StageTransactions(txsB); // Propose only txs having higher or equal with nonce than expected nonce. + var proposer = new PrivateKey(); Block b2 = _blockChain.ProposeBlock( - new PrivateKey(), + proposer, CreateBlockCommit(b1), + new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) + .Prove(proposer).Proof, _blockChain.GetPendingEvidence()); Assert.Single(b2.Transactions); Assert.Contains(txsB[3], b2.Transactions); @@ -799,9 +820,12 @@ public void MarkTransactionsToIgnoreWhileProposing() StageTransactions(txs); Assert.Equal(txs.Length, _blockChain.ListStagedTransactions().Count); + var proposer = new PrivateKey(); var block = _blockChain.ProposeBlock( - new PrivateKey(), + proposer, CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) + .Prove(proposer).Proof, _blockChain.GetPendingEvidence()); Assert.DoesNotContain(txWithInvalidNonce, block.Transactions); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs index e59c99e95ea..fcfb04bdd2e 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs @@ -31,6 +31,7 @@ public void ValidateNextBlock() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(validNextBlock, TestUtils.CreateBlockCommit(validNextBlock)); @@ -52,6 +53,7 @@ public void ValidateNextBlockProtocolVersion() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -67,6 +69,7 @@ public void ValidateNextBlockProtocolVersion() previousHash: block1.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer)); @@ -83,6 +86,7 @@ public void ValidateNextBlockProtocolVersion() previousHash: block1.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block3, TestUtils.CreateBlockCommit(block3)); @@ -104,6 +108,7 @@ public void ValidateNextBlockInvalidIndex() previousHash: prev.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws( @@ -121,6 +126,8 @@ public void ValidateNextBlockInvalidIndex() previousHash: prev.Hash, txHash: null, lastCommit: TestUtils.CreateBlockCommit(prev.Hash, prev.Index + 1, 0), + proof: new LotMetadata(prev.Index + 1, 0, prev.Proof) + .Prove(_fx.Proposer).Proof, evidenceHash: null)) .Propose(), _fx.Proposer); @@ -148,6 +155,8 @@ public void ValidateNextBlockInvalidPreviousHash() // ReSharper disable once PossibleInvalidOperationException lastCommit: TestUtils.CreateBlockCommit( _validNext.PreviousHash.Value, 1, 0), + proof: new LotMetadata(_validNext.Index + 1, 0, _validNext.Proof) + .Prove(_fx.Proposer).Proof, evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -170,6 +179,8 @@ public void ValidateNextBlockInvalidTimestamp() previousHash: _validNext.Hash, txHash: null, lastCommit: TestUtils.CreateBlockCommit(_validNext), + proof: new LotMetadata(_validNext.Index + 1, 0, _validNext.Proof) + .Prove(_fx.Proposer).Proof, evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -231,6 +242,7 @@ public void ValidateNextBlockInvalidStateRootHash() previousHash: genesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), TestUtils.GenesisProposer); @@ -276,6 +288,7 @@ public void ValidateNextBlockInvalidStateRootHashBeforePostpone() previousHash: genesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), TestUtils.GenesisProposer); @@ -341,6 +354,7 @@ public void ValidateNextBlockInvalidStateRootHashOnPostpone() previousHash: genesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(); Block block1 = chain.EvaluateAndSign( preBlock1, @@ -369,6 +383,7 @@ public void ValidateNextBlockLastCommitNullAtIndexOne() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(validNextBlock, TestUtils.CreateBlockCommit(validNextBlock)); @@ -387,6 +402,7 @@ public void ValidateNextBlockLastCommitUpperIndexOne() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -401,6 +417,8 @@ public void ValidateNextBlockLastCommitUpperIndexOne() previousHash: block1.Hash, txHash: null, lastCommit: blockCommit, + proof: new LotMetadata(2L, 0, block1.Proof) + .Prove(_fx.Proposer).Proof, evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block2, TestUtils.CreateBlockCommit(block2)); @@ -419,6 +437,7 @@ public void ValidateNextBlockLastCommitFailsUnexpectedValidator() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -447,6 +466,8 @@ public void ValidateNextBlockLastCommitFailsUnexpectedValidator() previousHash: block1.Hash, txHash: null, lastCommit: blockCommit, + proof: new LotMetadata(2L, 0, block1.Proof) + .Prove(_fx.Proposer).Proof, evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -465,6 +486,7 @@ public void ValidateNextBlockLastCommitFailsDropExpectedValidator() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -489,6 +511,8 @@ public void ValidateNextBlockLastCommitFailsDropExpectedValidator() previousHash: block1.Hash, txHash: null, lastCommit: blockCommit, + proof: new LotMetadata(2L, 0, block1.Proof) + .Prove(_fx.Proposer).Proof, evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -530,6 +554,7 @@ public void ValidateBlockCommitFailsDifferentBlockHash() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); @@ -554,6 +579,7 @@ public void ValidateBlockCommitFailsDifferentHeight() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); @@ -578,6 +604,7 @@ public void ValidateBlockCommitFailsDifferentValidatorSet() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); @@ -612,6 +639,7 @@ public void ValidateBlockCommitFailsNullBlockCommit() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); @@ -647,6 +675,7 @@ public void ValidateBlockCommitFailsInsufficientPower() previousHash: blockChain.Genesis.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); @@ -770,6 +799,7 @@ public void ValidateNextBlockAEVChangedOnChainRestart() previousHash: newChain.Tip.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), TestUtils.GenesisProposer); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.cs index acccd29c3be..1dd290cb202 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -84,6 +84,7 @@ public BlockChainTest(ITestOutputHelper output) previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, + proof: null, evidenceHash: null)).Propose(), _fx.Proposer); } @@ -709,6 +710,8 @@ public void DetectInvalidTxNonce() _fx.Proposer, txsA.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); _blockChain.Append(b1, TestUtils.CreateBlockCommit(b1)); @@ -716,6 +719,8 @@ public void DetectInvalidTxNonce() _fx.Proposer, txsA.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); Assert.Throws(() => _blockChain.Append(b2, CreateBlockCommit(b2))); @@ -731,6 +736,8 @@ public void DetectInvalidTxNonce() _fx.Proposer, txsB.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); _blockChain.Append(b2, CreateBlockCommit(b2)); } @@ -763,6 +770,8 @@ public void ForkTxNonce() _fx.Proposer, txsA.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); _blockChain.Append(b1, CreateBlockCommit(b1)); @@ -779,6 +788,8 @@ public void ForkTxNonce() _fx.Proposer, txsB.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); _blockChain.Append(b2, CreateBlockCommit(b2)); @@ -835,6 +846,8 @@ public void Swap(bool render) miner, txs1.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(miner).Proof, ImmutableArray.Empty); _blockChain.Append(block1, CreateBlockCommit(block1)); @@ -897,6 +910,8 @@ public void Swap(bool render) miner, txs.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); _blockChain.Append(b, CreateBlockCommit(b)); } @@ -954,6 +969,8 @@ public void Swap(bool render) miner, txs.ToImmutableList(), CreateBlockCommit(fork.Tip), + new LotMetadata(fork.Tip.Index + 1, 0, fork.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); fork.Append(b, CreateBlockCommit(b), render: true); } @@ -1238,6 +1255,8 @@ public void GetStateOnlyDrillsDownUntilRequestedAddressesAreFound() _fx.Proposer, txs.ToImmutableList(), CreateBlockCommit(chain.Tip), + new LotMetadata(chain.Tip.Index + 1, 0, chain.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); chain.Append(b, CreateBlockCommit(b)); } @@ -1530,6 +1549,8 @@ public void GetNextTxNonce() _fx.Proposer, txsA.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), + new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) + .Prove(_fx.Proposer).Proof, ImmutableArray.Empty); _blockChain.Append(b1, CreateBlockCommit(b1)); diff --git a/test/Libplanet.Tests/Blocks/BlockContentTest.cs b/test/Libplanet.Tests/Blocks/BlockContentTest.cs index f620ebb5f11..d90db048aa6 100644 --- a/test/Libplanet.Tests/Blocks/BlockContentTest.cs +++ b/test/Libplanet.Tests/Blocks/BlockContentTest.cs @@ -86,6 +86,7 @@ public void Transactions() previousHash: Block1Content.PreviousHash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: evs); @@ -124,6 +125,7 @@ public void TransactionsWithDuplicateNonce() previousHash: Block1Content.PreviousHash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: evs)); @@ -162,6 +164,7 @@ public void TransactionsWithMissingNonce() previousHash: Block1Content.PreviousHash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: evs)); @@ -203,6 +206,7 @@ public void TransactionsWithInconsistentGenesisHashes() previousHash: Block1Content.PreviousHash, txHash: BlockContent.DeriveTxHash(inconsistentTxs), lastCommit: null, + proof: null, evidenceHash: null), transactions: inconsistentTxs, evidence: evs)); diff --git a/test/Libplanet.Tests/Blocks/BlockMetadataExtensionsTest.cs b/test/Libplanet.Tests/Blocks/BlockMetadataExtensionsTest.cs index 4a8bd992a63..9e4e870d531 100644 --- a/test/Libplanet.Tests/Blocks/BlockMetadataExtensionsTest.cs +++ b/test/Libplanet.Tests/Blocks/BlockMetadataExtensionsTest.cs @@ -21,6 +21,7 @@ public void ValidateTimestamp() previousHash: null, txHash: null, lastCommit: null, + proof: null, evidenceHash: null); Assert.Throws(() => metadata.ValidateTimestamp(now)); diff --git a/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs b/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs index 1f1810421f6..e98290ccbf5 100644 --- a/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs +++ b/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs @@ -45,6 +45,7 @@ public void ProtocolVersion() previousHash: Block1Metadata.PreviousHash, txHash: Block1Metadata.TxHash, lastCommit: null, + proof: null, evidenceHash: null)); Assert.Throws( () => new BlockMetadata( @@ -56,6 +57,7 @@ public void ProtocolVersion() previousHash: Block1Metadata.PreviousHash, txHash: Block1Metadata.TxHash, lastCommit: null, + proof: null, evidenceHash: null)); } @@ -69,6 +71,7 @@ public void Index() previousHash: Block1Metadata.PreviousHash, txHash: Block1Metadata.TxHash, lastCommit: null, + proof: null, evidenceHash: null)); } @@ -86,6 +89,7 @@ public void Timestamp() previousHash: Block1Metadata.PreviousHash, txHash: Block1Metadata.TxHash, lastCommit: null, + proof: null, evidenceHash: null); Assert.Equal(TimeSpan.Zero, metadata.Timestamp.Offset); Assert.Equal( @@ -104,6 +108,7 @@ public void PreviousHash() previousHash: Block1Metadata.PreviousHash, txHash: GenesisMetadata.TxHash, lastCommit: null, + proof: null, evidenceHash: null)); Assert.Throws(() => new BlockMetadata( index: Block1Metadata.Index, @@ -112,6 +117,7 @@ public void PreviousHash() previousHash: null, txHash: Block1Metadata.TxHash, lastCommit: null, + proof: null, evidenceHash: null)); } @@ -241,6 +247,7 @@ public void ValidateLastCommit() previousHash: blockHash, txHash: null, lastCommit: invalidHeightLastCommit, + proof: new LotMetadata(2L, 0, null).Prove(validatorA).Proof, evidenceHash: null)); // BlockHash of the last commit is invalid. @@ -263,6 +270,7 @@ public void ValidateLastCommit() previousHash: GenesisHash, txHash: null, lastCommit: invalidBlockHashLastCommit, + proof: new LotMetadata(2L, 0, null).Prove(validatorA).Proof, evidenceHash: null)); var validLastCommit = new BlockCommit( @@ -292,6 +300,7 @@ public void ValidateLastCommit() previousHash: blockHash, txHash: null, lastCommit: validLastCommit, + proof: new LotMetadata(2L, 0, null).Prove(validatorA).Proof, evidenceHash: null); } diff --git a/test/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs b/test/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs index 84b2388742b..e191d4e796a 100644 --- a/test/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs +++ b/test/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs @@ -85,6 +85,7 @@ public void Evaluate() previousHash: genesis.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: evs); @@ -161,6 +162,7 @@ public void DetermineStateRootHash() previousHash: genesis.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: evs); diff --git a/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs b/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs index 41f24de8d05..8d6b4f47e0c 100644 --- a/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs +++ b/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs @@ -70,6 +70,7 @@ public BlockContentFixture() previousHash: null, txHash: BlockContent.DeriveTxHash(genTxs), lastCommit: null, + proof: null, evidenceHash: BlockContent.DeriveEvidenceHash(genEvidence)), transactions: genTxs, evidence: genEvidence); @@ -136,6 +137,7 @@ public BlockContentFixture() previousHash: GenesisHash, txHash: BlockContent.DeriveTxHash(block1Transactions), lastCommit: null, + proof: null, evidenceHash: BlockContent.DeriveEvidenceHash(block1Evidence)), transactions: block1Transactions, evidence: block1Evidence); @@ -153,6 +155,7 @@ public BlockContentFixture() previousHash: null, txHash: null, lastCommit: null, + proof: null, evidenceHash: null), transactions: new List(), evidence: new List()); // Tweaked GenesisContent @@ -167,6 +170,7 @@ public BlockContentFixture() previousHash: GenesisHash, txHash: BlockContent.DeriveTxHash(block1Transactions), lastCommit: null, + proof: null, evidenceHash: BlockContent.DeriveEvidenceHash(block1Evidence)), transactions: block1Transactions, evidence: block1Evidence); // Tweaked Block1Content diff --git a/test/Libplanet.Tests/TestUtils.cs b/test/Libplanet.Tests/TestUtils.cs index 2d582511f26..824130d4af7 100644 --- a/test/Libplanet.Tests/TestUtils.cs +++ b/test/Libplanet.Tests/TestUtils.cs @@ -448,6 +448,7 @@ public static PreEvaluationBlock ProposeGenesis( previousHash: null, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, + proof: null, evidenceHash: null), transactions: txs, evidence: Array.Empty()); @@ -488,6 +489,7 @@ public static PreEvaluationBlock ProposeNext( TimeSpan? blockInterval = null, int protocolVersion = Block.CurrentProtocolVersion, BlockCommit lastCommit = null, + Proof? proof = null, ImmutableArray? evidence = null) { var txs = transactions is null @@ -513,6 +515,7 @@ public static PreEvaluationBlock ProposeNext( previousHash: previousBlock.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: lastCommit, + proof: proof, evidenceHash: evidenceHash), transactions: txs, evidence: Array.Empty()); @@ -529,6 +532,7 @@ public static Block ProposeNextBlock( int protocolVersion = Block.CurrentProtocolVersion, HashDigest stateRootHash = default, BlockCommit lastCommit = null, + Proof? proof = null, ImmutableArray? evidence = null) { Skip.IfNot( @@ -543,6 +547,7 @@ public static Block ProposeNextBlock( blockInterval, protocolVersion, lastCommit, + proof, evidence); return preEval.Sign(miner, stateRootHash); } From f6abfb5575adec12902fa6220d787e9005c88429 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Sun, 24 Mar 2024 22:39:48 +0900 Subject: [PATCH 18/61] feat: Require proof on the block after VRF protocol version --- .../Blocks/InvalidBlockProofException.cs | 13 +++++++++++++ src/Libplanet/Blockchain/BlockChain.Validate.cs | 12 ++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 src/Libplanet.Types/Blocks/InvalidBlockProofException.cs diff --git a/src/Libplanet.Types/Blocks/InvalidBlockProofException.cs b/src/Libplanet.Types/Blocks/InvalidBlockProofException.cs new file mode 100644 index 00000000000..bb65bf74a91 --- /dev/null +++ b/src/Libplanet.Types/Blocks/InvalidBlockProofException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Libplanet.Types.Blocks +{ + [Serializable] + public class InvalidBlockProofException : InvalidBlockException + { + public InvalidBlockProofException(string message) + : base(message) + { + } + } +} diff --git a/src/Libplanet/Blockchain/BlockChain.Validate.cs b/src/Libplanet/Blockchain/BlockChain.Validate.cs index c471fb5661b..11322a9f21e 100644 --- a/src/Libplanet/Blockchain/BlockChain.Validate.cs +++ b/src/Libplanet/Blockchain/BlockChain.Validate.cs @@ -312,6 +312,18 @@ internal void ValidateBlock(Block block) { throw new InvalidBlockLastCommitException(ibce.Message); } + + if (block.Proof is { } && block.ProtocolVersion < 6) + { + throw new InvalidBlockProofException( + "Block of protocol version lower than 6 does not support proof."); + } + + if (block.Proof is null && block.ProtocolVersion >= 6) + { + throw new InvalidBlockProofException( + "Block of protocol version higher than 5 must contain proof."); + } } foreach (var ev in block.Evidence) From 61e3110fa4c242763a25c21ec398341aa9f3f1d6 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Sun, 24 Mar 2024 22:40:31 +0900 Subject: [PATCH 19/61] Replace random seed generator with VRF --- src/Libplanet.Action/ActionEvaluator.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Libplanet.Action/ActionEvaluator.cs b/src/Libplanet.Action/ActionEvaluator.cs index 8d6eda0bbb7..ee7d1380d22 100644 --- a/src/Libplanet.Action/ActionEvaluator.cs +++ b/src/Libplanet.Action/ActionEvaluator.cs @@ -59,7 +59,7 @@ private delegate (ITrie, int) StateCommitter( public IActionLoader ActionLoader => _actionLoader; /// - /// Creates a random seed. + /// Creates a legacy random seed. /// /// The pre-evaluation hash as bytes. /// @@ -72,7 +72,7 @@ private delegate (ITrie, int) StateCommitter( /// Thrown when /// is empty. [Pure] - public static int GenerateRandomSeed( + public static int GenerateLegacyRandomSeed( byte[] preEvaluationHashBytes, byte[] signature, int actionOffset) @@ -228,7 +228,9 @@ IActionContext CreateActionContext( byte[] preEvaluationHashBytes = block.PreEvaluationHash.ToByteArray(); byte[] signature = tx?.Signature ?? Array.Empty(); - int seed = GenerateRandomSeed(preEvaluationHashBytes, signature, 0); + int seed = block.Proof is { } proof + ? proof.Seed + : GenerateLegacyRandomSeed(preEvaluationHashBytes, signature, 0); IWorld state = previousState; foreach (IAction action in actions) From ccfa8a49d8d4372a486af70d7cf1df44b4983a59 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Sun, 24 Mar 2024 22:40:59 +0900 Subject: [PATCH 20/61] test: Update tests --- .../Indexing/BlockChainIndexTest.cs | 4 +- .../Queries/TransactionQueryTest.cs | 6 +- .../SwarmTest.Broadcast.cs | 9 +- test/Libplanet.Net.Tests/SwarmTest.Preload.cs | 61 +- test/Libplanet.Net.Tests/TestUtils.cs | 3 + .../Action/ActionEvaluatorTest.cs | 35 +- .../Blockchain/BlockChainTest.Append.cs | 56 +- .../Blockchain/BlockChainTest.Fork.cs | 716 +++++++++++++++ .../Blockchain/BlockChainTest.Internals.cs | 4 +- .../Blockchain/BlockChainTest.ProposeBlock.cs | 32 +- .../Blockchain/BlockChainTest.Stage.cs | 9 +- .../BlockChainTest.ValidateNextBlock.cs | 21 +- .../Blockchain/BlockChainTest.cs | 816 +++--------------- test/Libplanet.Tests/Fixtures/IntegerSet.cs | 4 +- test/Libplanet.Tests/Store/StoreTest.cs | 14 +- test/Libplanet.Tests/TestUtils.cs | 5 + 16 files changed, 968 insertions(+), 827 deletions(-) create mode 100644 test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs diff --git a/test/Libplanet.Explorer.Tests/Indexing/BlockChainIndexTest.cs b/test/Libplanet.Explorer.Tests/Indexing/BlockChainIndexTest.cs index cbc4ee73beb..25b8d890fda 100644 --- a/test/Libplanet.Explorer.Tests/Indexing/BlockChainIndexTest.cs +++ b/test/Libplanet.Explorer.Tests/Indexing/BlockChainIndexTest.cs @@ -43,7 +43,9 @@ await index.SynchronizeAsync( var forkedChain = ChainFx.Chain.Fork(ChainFx.Chain.Tip.PreviousHash!.Value); var divergentBlock = forkedChain.ProposeBlock( ChainFx.PrivateKeys[0], - forkedChain.GetBlockCommit(forkedChain.Tip.Hash)); + forkedChain.GetBlockCommit(forkedChain.Tip.Hash), + new LotMetadata(forkedChain.Tip.Index + 1, 0, forkedChain.Tip.Proof) + .Prove(ChainFx.PrivateKeys[0]).Proof); forkedChain.Append( divergentBlock, new BlockCommit( diff --git a/test/Libplanet.Explorer.Tests/Queries/TransactionQueryTest.cs b/test/Libplanet.Explorer.Tests/Queries/TransactionQueryTest.cs index a44d928054e..28f9e2fe46f 100644 --- a/test/Libplanet.Explorer.Tests/Queries/TransactionQueryTest.cs +++ b/test/Libplanet.Explorer.Tests/Queries/TransactionQueryTest.cs @@ -139,9 +139,11 @@ async Task AssertNextNonce(long expected, Address address) { // staging txs of key2 does not increase nonce of key1 Source.BlockChain.MakeTransaction(key2, ImmutableList.Empty.Add(new NullAction())); + var proposer = new PrivateKey(); block = Source.BlockChain.ProposeBlock( - new PrivateKey(), - Libplanet.Tests.TestUtils.CreateBlockCommit(block)); + proposer, + Libplanet.Tests.TestUtils.CreateBlockCommit(block), + Libplanet.Tests.TestUtils.CreateZeroRoundProof(block, proposer)); Source.BlockChain.Append(block, Libplanet.Tests.TestUtils.CreateBlockCommit(block)); await AssertNextNonce(1, key2.Address); await AssertNextNonce(2, key1.Address); diff --git a/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs b/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs index aecdf77c646..3114d0a6bb1 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs @@ -22,7 +22,6 @@ using Libplanet.Tests.Blockchain.Evidence; using Libplanet.Tests.Store; using Libplanet.Types.Blocks; -using Libplanet.Types.Consensus; using Libplanet.Types.Evidence; using Libplanet.Types.Tx; using Serilog; @@ -755,17 +754,15 @@ public async Task BroadcastBlockWithSkip() Block block1 = blockChain.ProposeBlock( GenesisProposer, new[] { transactions[0] }.ToImmutableList(), - TestUtils.CreateBlockCommit(blockChain.Tip), - new LotMetadata(blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof) - .Prove(GenesisProposer).Proof, + CreateBlockCommit(blockChain.Tip), + CreateZeroRoundProof(blockChain.Tip, GenesisProposer), ImmutableArray.Empty); blockChain.Append(block1, TestUtils.CreateBlockCommit(block1), true); Block block2 = blockChain.ProposeBlock( GenesisProposer, new[] { transactions[1] }.ToImmutableList(), CreateBlockCommit(blockChain.Tip), - new LotMetadata(blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof) - .Prove(GenesisProposer).Proof, + CreateZeroRoundProof(blockChain.Tip, GenesisProposer), ImmutableArray.Empty); blockChain.Append(block2, TestUtils.CreateBlockCommit(block2), true); diff --git a/test/Libplanet.Net.Tests/SwarmTest.Preload.cs b/test/Libplanet.Net.Tests/SwarmTest.Preload.cs index 04b63256c10..452ebfeec15 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.Preload.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.Preload.cs @@ -48,8 +48,7 @@ public async Task InitialBlockDownload() Block block = minerChain.ProposeBlock( minerKey, CreateBlockCommit(minerChain.Tip), - new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(minerKey).Proof); + CreateZeroRoundProof(minerChain.Tip, minerKey)); minerChain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -92,24 +91,21 @@ public async Task InitialBlockDownloadStates() var block = minerChain.ProposeBlock( minerKey, CreateBlockCommit(minerChain.Tip), - new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(minerKey).Proof); + CreateZeroRoundProof(minerChain.Tip, minerKey)); minerChain.Append(block, TestUtils.CreateBlockCommit(block)); minerChain.MakeTransaction(key, new[] { DumbAction.Create((address1, "bar")) }); block = minerChain.ProposeBlock( minerKey, CreateBlockCommit(minerChain.Tip), - new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(minerKey).Proof); + CreateZeroRoundProof(minerChain.Tip, minerKey)); minerChain.Append(block, TestUtils.CreateBlockCommit(block)); minerChain.MakeTransaction(key, new[] { DumbAction.Create((address1, "baz")) }); block = minerChain.ProposeBlock( minerKey, CreateBlockCommit(minerChain.Tip), - new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(minerKey).Proof); + CreateZeroRoundProof(minerChain.Tip, minerKey)); minerChain.Append(block, TestUtils.CreateBlockCommit(block)); try @@ -150,8 +146,7 @@ public async Task Preload() previousBlock: i == 0 ? minerChain.Genesis : blocks[i - 1], miner: ChainPrivateKey.PublicKey, lastCommit: CreateBlockCommit(minerChain.Tip), - proof: new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(ChainPrivateKey).Proof), + proof: CreateZeroRoundProof(minerChain.Tip, ChainPrivateKey)), ChainPrivateKey); blocks.Add(block); if (i != 11) @@ -306,9 +301,8 @@ public async Task PreloadWithMaliciousPeer() var proposerKey = new PrivateKey(); var block = chainA.ProposeBlock( proposerKey, - TestUtils.CreateBlockCommit(chainA.Tip), - new LotMetadata(chainA.Tip.Index + 1, 0, chainA.Tip.Proof) - .Prove(proposerKey).Proof); + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, proposerKey)); chainA.Append(block, TestUtils.CreateBlockCommit(block)); chainB.Append(block, TestUtils.CreateBlockCommit(block)); chainC.Append(block, TestUtils.CreateBlockCommit(block)); @@ -320,9 +314,8 @@ public async Task PreloadWithMaliciousPeer() var proposerKey = new PrivateKey(); var block = chainB.ProposeBlock( proposerKey, - TestUtils.CreateBlockCommit(chainB.Tip), - new LotMetadata(chainB.Tip.Index + 1, 0, chainB.Tip.Proof) - .Prove(proposerKey).Proof); + CreateBlockCommit(chainB.Tip), + CreateZeroRoundProof(chainB.Tip, proposerKey)); chainB.Append(block, TestUtils.CreateBlockCommit(block)); chainC.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -330,9 +323,8 @@ public async Task PreloadWithMaliciousPeer() var specialPK = new PrivateKey(); var specialBlock = chainB.ProposeBlock( specialPK, - TestUtils.CreateBlockCommit(chainB.Tip), - new LotMetadata(chainB.Tip.Index + 1, 0, chainB.Tip.Proof) - .Prove(specialPK).Proof); + CreateBlockCommit(chainB.Tip), + CreateZeroRoundProof(chainB.Tip, specialPK)); var invalidBlockCommit = new BlockCommit( maliciousTipHeight, 0, @@ -356,9 +348,8 @@ public async Task PreloadWithMaliciousPeer() var privateKey = new PrivateKey(); var block = chainC.ProposeBlock( privateKey, - TestUtils.CreateBlockCommit(chainC.Tip), - new LotMetadata(chainC.Tip.Index + 1, 0, chainC.Tip.Proof) - .Prove(privateKey).Proof); + CreateBlockCommit(chainC.Tip), + CreateZeroRoundProof(chainC.Tip, privateKey)); chainC.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -447,8 +438,7 @@ public async Task NoRenderInPreload() Block block = sender.BlockChain.ProposeBlock( senderKey, CreateBlockCommit(sender.BlockChain.Tip), - new LotMetadata(sender.BlockChain.Tip.Index + 1, 0, sender.BlockChain.Tip.Proof) - .Prove(senderKey).Proof); + CreateZeroRoundProof(sender.BlockChain.Tip, senderKey)); sender.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -492,8 +482,7 @@ public async Task PreloadWithFailedActions() Block block = minerChain.ProposeBlock( minerKey, CreateBlockCommit(minerChain.Tip), - new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(minerKey).Proof); + CreateZeroRoundProof(minerChain.Tip, minerKey)); minerChain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -520,10 +509,9 @@ public async Task PreloadWithFailedActions() ChainPrivateKey, new[] { tx }.ToImmutableList(), CreateBlockCommit(minerChain.Tip), - new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(minerKey).Proof, + CreateZeroRoundProof(minerChain.Tip, ChainPrivateKey), ImmutableArray.Empty); - minerSwarm.BlockChain.Append(block, CreateBlockCommit(block), true); + minerChain.Append(block, CreateBlockCommit(block), true); await receiverSwarm.PreloadAsync(); @@ -575,8 +563,7 @@ public async Task PreloadFromNominer() Block block = minerChain.ProposeBlock( minerKey, CreateBlockCommit(minerChain.Tip), - new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(minerKey).Proof); + CreateZeroRoundProof(minerChain.Tip, minerKey)); minerChain.Append(block, CreateBlockCommit(block)); } @@ -693,8 +680,7 @@ public async Task PreloadRetryWithNextPeers(int blockCount) var block = swarm0.BlockChain.ProposeBlock( key0, CreateBlockCommit(swarm0.BlockChain.Tip), - new LotMetadata(swarm0.BlockChain.Tip.Index + 1, 0, swarm0.BlockChain.Tip.Proof) - .Prove(key0).Proof); + CreateZeroRoundProof(swarm0.BlockChain.Tip, key0)); swarm0.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); swarm1.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -888,8 +874,7 @@ public async Task PreloadAfterReorg() Block block = minerChain.ProposeBlock( minerKey, CreateBlockCommit(minerChain.Tip), - new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(minerKey).Proof); + CreateZeroRoundProof(minerChain.Tip, minerKey)); minerChain.Append(block, CreateBlockCommit(block)); receiverChain.Append(block, CreateBlockCommit(block)); } @@ -900,8 +885,7 @@ public async Task PreloadAfterReorg() Block block = receiverForked.ProposeBlock( minerKey, CreateBlockCommit(receiverForked.Tip), - new LotMetadata(receiverForked.Tip.Index + 1, 0, receiverForked.Tip.Proof) - .Prove(minerKey).Proof); + CreateZeroRoundProof(receiverForked.Tip, minerKey)); receiverForked.Append(block, CreateBlockCommit(block)); } @@ -912,8 +896,7 @@ public async Task PreloadAfterReorg() Block block = minerChain.ProposeBlock( minerKey, CreateBlockCommit(minerChain.Tip), - new LotMetadata(minerChain.Tip.Index + 1, 0, minerChain.Tip.Proof) - .Prove(minerKey).Proof); + CreateZeroRoundProof(minerChain.Tip, minerKey)); minerChain.Append(block, CreateBlockCommit(block)); } diff --git a/test/Libplanet.Net.Tests/TestUtils.cs b/test/Libplanet.Net.Tests/TestUtils.cs index 3659ed28284..2404a4f279d 100644 --- a/test/Libplanet.Net.Tests/TestUtils.cs +++ b/test/Libplanet.Net.Tests/TestUtils.cs @@ -134,6 +134,9 @@ public static BlockCommit CreateBlockCommit(Block block) => public static BlockCommit CreateBlockCommit(BlockHash blockHash, long height, int round) => Libplanet.Tests.TestUtils.CreateBlockCommit(blockHash, height, round); + public static Proof CreateZeroRoundProof(Block tip, PrivateKey proposerKey) => + Libplanet.Tests.TestUtils.CreateZeroRoundProof(tip, proposerKey); + public static void HandleFourPeersPreCommitMessages( ConsensusContext consensusContext, PrivateKey nodePrivateKey, diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs index b89e0d1e7e6..59b1cf969f4 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -166,7 +166,9 @@ public void Evaluate() chain.StageTransaction(tx); var miner = new PrivateKey(); - Block block = chain.ProposeBlock(miner); + Block block = chain.ProposeBlock( + miner, + proof: CreateZeroRoundProof(chain.Tip, miner)); chain.Append(block, CreateBlockCommit(block)); var evaluations = chain.ActionEvaluator.Evaluate( @@ -307,7 +309,10 @@ public void EvaluateWithException() actions: new[] { action }.ToPlainValues()); chain.StageTransaction(tx); - Block block = chain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block = chain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(chain.Tip, proposer)); chain.Append(block, CreateBlockCommit(block)); var evaluations = chain.ActionEvaluator.Evaluate( chain.Tip, chain.Store.GetStateRootHash(chain.Tip.PreviousHash)); @@ -992,7 +997,7 @@ public void EvaluatePolicyEndBlockActions() GenesisProposer, txs.ToImmutableList(), CreateBlockCommit(chain.Tip), - new LotMetadata(1L, 0, null).Prove(GenesisProposer).Proof, + CreateZeroRoundProof(chain.Tip, GenesisProposer), ImmutableArray.Empty); IWorld previousState = _storeFx.StateStore.GetWorld(null); @@ -1285,7 +1290,9 @@ public void EvaluateActionAndCollectFee() chain.StageTransaction(tx); var miner = new PrivateKey(); - Block block = chain.ProposeBlock(miner); + Block block = chain.ProposeBlock( + miner, + proof: CreateZeroRoundProof(chain.Tip, miner)); var evaluations = chain.ActionEvaluator.Evaluate( block, chain.GetNextStateRootHash((BlockHash)block.PreviousHash)); @@ -1354,7 +1361,9 @@ public void EvaluateThrowingExceedGasLimit() chain.StageTransaction(tx); var miner = new PrivateKey(); - Block block = chain.ProposeBlock(miner); + Block block = chain.ProposeBlock( + miner, + proof: CreateZeroRoundProof(chain.Tip, miner)); var evaluations = chain.ActionEvaluator.Evaluate( block, @@ -1426,7 +1435,9 @@ public void EvaluateThrowingInsufficientBalanceForGasFee() chain.StageTransaction(tx); var miner = new PrivateKey(); - Block block = chain.ProposeBlock(miner); + Block block = chain.ProposeBlock( + miner, + proof: CreateZeroRoundProof(chain.Tip, miner)); var evaluations = chain.ActionEvaluator.Evaluate( block, chain.Store.GetStateRootHash(block.PreviousHash)); @@ -1441,7 +1452,7 @@ public void EvaluateThrowingInsufficientBalanceForGasFee() } [Fact] - public void GenerateRandomSeed() + public void GenerateLegacyRandomSeed() { byte[] preEvaluationHashBytes = { @@ -1458,14 +1469,16 @@ public void GenerateRandomSeed() 0x7d, 0x37, 0x67, 0xe1, 0xe9, }; - int seed = ActionEvaluator.GenerateRandomSeed(preEvaluationHashBytes, signature, 0); + int seed = ActionEvaluator.GenerateLegacyRandomSeed( + preEvaluationHashBytes, signature, 0); Assert.Equal(353767086, seed); - seed = ActionEvaluator.GenerateRandomSeed(preEvaluationHashBytes, signature, 1); + seed = ActionEvaluator.GenerateLegacyRandomSeed( + preEvaluationHashBytes, signature, 1); Assert.Equal(353767087, seed); } [Fact] - public void CheckRandomSeedInAction() + public void CheckLegacyRandomSeedInAction() { IntegerSet fx = new IntegerSet(new[] { 5, 10 }); @@ -1490,7 +1503,7 @@ public void CheckRandomSeedInAction() byte[] preEvaluationHashBytes = blockA.PreEvaluationHash.ToByteArray(); int[] randomSeeds = Enumerable .Range(0, txA.Actions.Count) - .Select(offset => ActionEvaluator.GenerateRandomSeed( + .Select(offset => ActionEvaluator.GenerateLegacyRandomSeed( preEvaluationHashBytes, txA.Signature, offset)) diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs index 9bf1378c1ae..744f36ed6ef 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs @@ -19,7 +19,6 @@ using Libplanet.Store.Trie; using Libplanet.Tests.Store; using Libplanet.Types.Blocks; -using Libplanet.Types.Consensus; using Libplanet.Types.Evidence; using Libplanet.Types.Tx; using Serilog; @@ -49,16 +48,15 @@ Func getTxExecution var block1 = _blockChain.ProposeBlock( keys[4], TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(keys[4]).Proof); + TestUtils.CreateZeroRoundProof(_blockChain.Tip, keys[4])); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); Assert.NotNull(_blockChain.GetBlockCommit(block1.Hash)); Block block2 = _blockChain.ProposeBlock( keys[4], txs.ToImmutableList(), lastCommit: TestUtils.CreateBlockCommit(block1), - proof: new LotMetadata(block1.Index + 1, 0, block1.Proof).Prove(keys[4]).Proof, - evidence: ImmutableArray.Empty); + proof: TestUtils.CreateZeroRoundProof(block1, keys[4]), + ImmutableArray.Empty); foreach (Transaction tx in txs) { Assert.Null(getTxExecution(genesis.Hash, tx.Id)); @@ -264,8 +262,7 @@ Func getTxExecution keys[4], new[] { tx1Transfer, tx2Error, tx3Transfer }.ToImmutableList(), TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(keys[4]).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, keys[4]), ImmutableArray.Empty); _blockChain.Append(block3, TestUtils.CreateBlockCommit(block3)); var txExecution1 = getTxExecution(block3.Hash, tx1Transfer.Id); @@ -347,8 +344,7 @@ public void AppendModern() miner, new[] { tx1 }.ToImmutableList(), TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(miner).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner), ImmutableArray.Empty); var commit1 = TestUtils.CreateBlockCommit(block1); _blockChain.Append(block1, commit1); @@ -361,8 +357,7 @@ public void AppendModern() miner, new[] { tx2 }.ToImmutableList(), commit1, - new LotMetadata(block1.Index + 1, 0, block1.Proof) - .Prove(miner).Proof, + TestUtils.CreateZeroRoundProof(block1, miner), ImmutableArray.Empty); _blockChain.Append(block2, TestUtils.CreateBlockCommit(block2)); var world2 = _blockChain.GetNextWorldState(); @@ -401,8 +396,7 @@ public void AppendFailDueToInvalidBytesLength() miner, heavyTxs.ToImmutableList(), TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(miner).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner), ImmutableArray.Empty); long maxBytes = _blockChain.Policy.GetMaxTransactionsBytes(block.Index); Assert.True(block.MarshalBlock().EncodingLength > maxBytes); @@ -433,8 +427,7 @@ public void AppendFailDueToInvalidTxCount() miner, manyTxs.ToImmutableList(), TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(miner).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner), ImmutableArray.Empty); Assert.Equal(manyTxs.Count, block.Transactions.Count); @@ -461,7 +454,10 @@ public void AppendWhenActionEvaluationFailed() blockChain.MakeTransaction(privateKey, new[] { action }); renderer.ResetRecords(); - Block block = blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block = blockChain.ProposeBlock( + proposer, + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); Assert.Equal(2, blockChain.Count); @@ -511,8 +507,7 @@ TxPolicyViolationException IsSignerValid( miner, new[] { validTx }.ToImmutableList(), TestUtils.CreateBlockCommit(blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(miner).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner), ImmutableArray.Empty); blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -520,8 +515,7 @@ TxPolicyViolationException IsSignerValid( miner, new[] { invalidTx }.ToImmutableList(), TestUtils.CreateBlockCommit(blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(miner).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner), ImmutableArray.Empty); Assert.Throws(() => blockChain.Append( block2, TestUtils.CreateBlockCommit(block2))); @@ -540,8 +534,7 @@ public void UnstageAfterAppendComplete() Block block1 = _blockChain.ProposeBlock( privateKey, TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(privateKey).Proof); + TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey)); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); Assert.Empty(_blockChain.GetStagedTransactionIds()); @@ -553,8 +546,7 @@ public void UnstageAfterAppendComplete() privateKey, ImmutableList.Empty.Add(txs[0]), TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(privateKey).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey), ImmutableArray.Empty); _blockChain.Append(block2, TestUtils.CreateBlockCommit(block2)); Assert.Equal(1, _blockChain.GetStagedTransactionIds().Count); @@ -573,8 +565,7 @@ public void UnstageAfterAppendComplete() privateKey, ImmutableList.Empty.Add(txs[1]), TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(privateKey).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey), ImmutableArray.Empty); _blockChain.Append(block3, TestUtils.CreateBlockCommit(block3)); Assert.Empty(_blockChain.GetStagedTransactionIds()); @@ -602,8 +593,7 @@ public void DoesNotUnstageOnAppendForForkedChain() privateKey, ImmutableList.Empty.Add(txs[0]), TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(privateKey).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey), ImmutableArray.Empty); // Not actually unstaged, but lower nonce is filtered for workspace. @@ -623,8 +613,7 @@ public void DoesNotUnstageOnAppendForForkedChain() privateKey, ImmutableList.Empty.Add(txs[1]), TestUtils.CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(privateKey).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey), ImmutableArray.Empty); // Actually gets unstaged. @@ -675,7 +664,9 @@ void AssertTxIdSetEqual( txA1 = Transaction.Create(1, signerA, genesis, emptyActions); _blockChain.StageTransaction(txA0); _blockChain.StageTransaction(txA1); - Block block = _blockChain.ProposeBlock(signerA); + Block block = _blockChain.ProposeBlock( + signerA, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, signerA)); Transaction txA2 = Transaction.Create(2, signerA, genesis, emptyActions), @@ -832,8 +823,7 @@ public void DoesNotMigrateStateWithoutAction() fx.Proposer, ImmutableList.Empty, TestUtils.CreateBlockCommit(blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(fx.Proposer).Proof, + TestUtils.CreateZeroRoundProof(_blockChain.Tip, fx.Proposer), ImmutableArray.Empty); blockChain.Append(emptyBlock, TestUtils.CreateBlockCommit(emptyBlock)); Assert.True(blockChain.GetNextWorldState(emptyBlock.Hash).Legacy); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs new file mode 100644 index 00000000000..a5ae27ecc65 --- /dev/null +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs @@ -0,0 +1,716 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.Loader; +using Libplanet.Action.State; +using Libplanet.Action.Tests.Common; +using Libplanet.Blockchain; +using Libplanet.Blockchain.Policies; +using Libplanet.Blockchain.Renderers.Debug; +using Libplanet.Crypto; +using Libplanet.Store; +using Libplanet.Store.Trie; +using Libplanet.Tests.Store; +using Libplanet.Types.Blocks; +using Libplanet.Types.Evidence; +using Libplanet.Types.Tx; +using Xunit; +using static Libplanet.Tests.TestUtils; + +namespace Libplanet.Tests.Blockchain +{ + public partial class BlockChainTest + { + [SkippableFact] + public void ForkTxNonce() + { + // An active account, so that its some recent transactions became "stale" due to a fork. + var privateKey = new PrivateKey(); + Address address = privateKey.Address; + + // An inactive account, so that it has no recent transactions but only an old + // transaction, so that its all transactions are stale-proof (stale-resistant). + var lessActivePrivateKey = new PrivateKey(); + Address lessActiveAddress = lessActivePrivateKey.Address; + + var actions = new[] { DumbAction.Create((address, "foo")) }; + + var genesis = _blockChain.Genesis; + + Transaction[] txsA = + { + _fx.MakeTransaction(actions, privateKey: privateKey), + _fx.MakeTransaction(privateKey: lessActivePrivateKey), + }; + + Assert.Equal(0, _blockChain.GetNextTxNonce(address)); + + Block b1 = _blockChain.ProposeBlock( + _fx.Proposer, + txsA.ToImmutableList(), + CreateBlockCommit(_blockChain.Tip), + CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), + ImmutableArray.Empty); + _blockChain.Append(b1, CreateBlockCommit(b1)); + + Assert.Equal(1, _blockChain.GetNextTxNonce(address)); + + Transaction[] txsB = + { + _fx.MakeTransaction( + actions, + nonce: 1, + privateKey: privateKey), + }; + Block b2 = _blockChain.ProposeBlock( + _fx.Proposer, + txsB.ToImmutableList(), + CreateBlockCommit(_blockChain.Tip), + CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), + ImmutableArray.Empty); + _blockChain.Append(b2, CreateBlockCommit(b2)); + + Assert.Equal(2, _blockChain.GetNextTxNonce(address)); + + BlockChain forked = _blockChain.Fork(b1.Hash); + Assert.Equal(1, forked.GetNextTxNonce(address)); + Assert.Equal(1, forked.GetNextTxNonce(lessActiveAddress)); + } + + [SkippableFact] + public void FindNextHashesAfterFork() + { + var key = new PrivateKey(); + + Block block = _blockChain.ProposeBlock( + key, + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(block, CreateBlockCommit(block)); + Block block2 = _blockChain.ProposeBlock( + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(block2, CreateBlockCommit(block2)); + Block block3 = _blockChain.ProposeBlock( + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(block3, CreateBlockCommit(block3)); + + BlockChain forked = _blockChain.Fork(_blockChain.Genesis.Hash); + Block forkedBlock = forked.ProposeBlock( + key, + proof: CreateZeroRoundProof(forked.Tip, key)); + forked.Append(forkedBlock, CreateBlockCommit(forkedBlock)); + + BlockLocator locator = _blockChain.GetBlockLocator(); + forked.FindNextHashes(locator) + .Deconstruct(out long? offset, out IReadOnlyList hashes); + + Assert.Equal(forked[0].Index, offset); + Assert.Equal(new[] { forked[0].Hash, forked[1].Hash }, hashes); + } + + [SkippableFact] + public void Fork() + { + var key = new PrivateKey(); + + Block block1 = _blockChain.ProposeBlock( + key, + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(block1, CreateBlockCommit(block1)); + Block block2 = _blockChain.ProposeBlock( + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(block2, CreateBlockCommit(block2)); + Block block3 = _blockChain.ProposeBlock( + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(block3, CreateBlockCommit(block3)); + + BlockChain forked = _blockChain.Fork(block2.Hash); + + Assert.Equal( + new[] { block1, block2, block3 }, + new[] { _blockChain[1], _blockChain[2], _blockChain[3] } + ); + Assert.Equal(4, _blockChain.Count); + + Assert.Equal( + new[] { block1, block2 }, + new[] { forked[1], forked[2] } + ); + Assert.Equal(3, forked.Count); + } + + [SkippableFact] + public void ForkAndSwapCanonicity() + { + // Fork is not canonical. + var key = new PrivateKey(); + var workspace = _blockChain.Fork(_blockChain.Genesis.Hash); + var b = workspace.ProposeBlock( + key, + lastCommit: CreateBlockCommit(workspace.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + workspace.Append(b, CreateBlockCommit(b)); + Assert.True(_blockChain.IsCanonical); + Assert.False(workspace.IsCanonical); + + // Both are canonical after swap. + _blockChain.Swap(workspace, false); + Assert.True(_blockChain.IsCanonical); + Assert.True(workspace.IsCanonical); + } + + [SkippableFact] + public void ForkWithBlockNotExistInChain() + { + var key = new PrivateKey(); + var genesis = _blockChain.Genesis; + + for (var i = 0; i < 2; i++) + { + Block block = _blockChain.ProposeBlock( + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(block, CreateBlockCommit(block)); + } + + Block newBlock = _blockChain.EvaluateAndSign( + ProposeNext( + genesis, + miner: key.PublicKey, + proof: CreateZeroRoundProof(_blockChain.Tip, key)), key); + + Assert.Throws(() => + _blockChain.Fork(newBlock.Hash)); + + _blockChain.Store.PutBlock(newBlock); + Assert.Throws(() => + _blockChain.Fork(newBlock.Hash)); + } + + [SkippableFact] + public void ForkChainWithIncompleteBlockStates() + { + var fx = new MemoryStoreFixture(_policy.BlockAction); + (_, _, BlockChain chain) = + MakeIncompleteBlockStates(fx.Store, fx.StateStore); + BlockChain forked = chain.Fork(chain[5].Hash); + Assert.Equal(chain[5], forked.Tip); + Assert.Equal(6, forked.Count); + } + + [SkippableFact] + public void StateAfterForkingAndAddingExistingBlock() + { + var miner = new PrivateKey(); + var signer = new PrivateKey(); + var address = signer.Address; + var actions1 = new[] { DumbAction.Create((address, "foo")) }; + var actions2 = new[] { DumbAction.Create((address, "bar")) }; + + _blockChain.MakeTransaction(signer, actions1); + var b1 = _blockChain.ProposeBlock( + miner, + proof: CreateZeroRoundProof(_blockChain.Tip, miner)); + _blockChain.Append(b1, CreateBlockCommit(b1)); + + _blockChain.MakeTransaction(signer, actions2); + var b2 = _blockChain.ProposeBlock( + miner, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, miner)); + _blockChain.Append(b2, CreateBlockCommit(b2)); + var state = _blockChain + .GetWorldState() + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetState(address); + + Assert.Equal((Text)"foo,bar", state); + + var forked = _blockChain.Fork(b1.Hash); + state = forked + .GetWorldState() + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetState(address); + Assert.Equal((Text)"foo", state); + + forked.Append(b2, CreateBlockCommit(b2)); + state = forked + .GetWorldState() + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetState(address); + Assert.Equal((Text)"foo,bar", state); + } + + [SkippableFact] + public void ForkShouldSkipExecuteAndRenderGenesis() + { + var miner = new PrivateKey(); + var action = DumbAction.Create((_fx.Address1, "genesis")); + + using (IStore store = new MemoryStore()) + using (var stateStore = new TrieStateStore(new MemoryKeyValueStore())) + { + var actionEvaluator = new ActionEvaluator( + _ => _policy.BlockAction, + stateStore, + new SingleActionLoader(typeof(DumbAction))); + var privateKey = new PrivateKey(); + var genesis = ProposeGenesisBlock( + ProposeGenesis( + GenesisProposer.PublicKey, + transactions: new[] + { + new Transaction( + new UnsignedTx( + new TxInvoice( + genesisHash: null, + updatedAddresses: ImmutableHashSet.Create(_fx.Address1), + timestamp: DateTimeOffset.UtcNow, + actions: new TxActionList(new[] { action }.ToPlainValues()), + maxGasPrice: null, + gasLimit: null), + new TxSigningMetadata(privateKey.PublicKey, 0)), + privateKey), + }), + privateKey: GenesisProposer); + + store.PutBlock(genesis); + var renderer = new RecordingActionRenderer(); + var blockChain = BlockChain.Create( + _policy, + new VolatileStagePolicy(), + store, + stateStore, + genesis, + new ActionEvaluator( + _ => _policy.BlockAction, + stateStore: stateStore, + actionTypeLoader: new SingleActionLoader(typeof(DumbAction))), + renderers: new[] { renderer } + ); + + // Creation does not render anything + Assert.Equal(0, renderer.ActionRecords.Count); + Assert.Equal(0, renderer.BlockRecords.Count); + + Block block1 = blockChain.ProposeBlock( + miner, + proof: CreateZeroRoundProof(_blockChain.Tip, miner)); + blockChain.Append(block1, CreateBlockCommit(block1)); + Block block2 = blockChain.ProposeBlock( + miner, + lastCommit: CreateBlockCommit(blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, miner)); + blockChain.Append(block2, CreateBlockCommit(block2)); + + int blockRecordsBeforeFork = renderer.BlockRecords.Count; + + blockChain.Fork(blockChain.Tip.Hash); + + Assert.Equal(0, renderer.ActionRecords.Count(r => IsDumbAction(r.Action))); + Assert.Equal(blockRecordsBeforeFork, renderer.BlockRecords.Count); + } + } + + [SkippableFact] + public void GetBlockCommitAfterFork() + { + var proposer = new PrivateKey(); + Block block1 = _blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(_blockChain.Tip, proposer)); + _blockChain.Append(block1, CreateBlockCommit(block1)); + proposer = new PrivateKey(); + Block block2 = _blockChain.ProposeBlock( + proposer, + lastCommit: CreateBlockCommit(block1), + proof: CreateZeroRoundProof(block1, proposer)); + _blockChain.Append(block2, CreateBlockCommit(block2)); + proposer = new PrivateKey(); + Block block3 = _blockChain.ProposeBlock( + proposer, + lastCommit: CreateBlockCommit(block2), + proof: CreateZeroRoundProof(block2, proposer)); + _blockChain.Append(block3, CreateBlockCommit(block3)); + + var forked = _blockChain.Fork(block2.Hash); + Assert.NotNull(forked.GetBlockCommit(forked.Tip.Index)); + Assert.Equal( + forked.GetBlockCommit(forked.Tip.Index), + block3.LastCommit); + } + + [SkippableFact] + public void GetStateReturnsValidStateAfterFork() + { + var privateKey = new PrivateKey(); + var store = new MemoryStore(); + var stateStore = + new TrieStateStore(new MemoryKeyValueStore()); + var actionLoader = new SingleActionLoader(typeof(DumbAction)); + var chain = MakeBlockChain( + new NullBlockPolicy(), + store, + stateStore, + actionLoader, + new[] { DumbAction.Create((_fx.Address1, "item0.0")) }); + Assert.Equal( + "item0.0", + (Text)chain + .GetWorldState() + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetState(_fx.Address1)); + + chain.MakeTransaction( + privateKey, + new[] { DumbAction.Create((_fx.Address1, "item1.0")), } + ); + var proposer = new PrivateKey(); + Block block = chain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(chain.Tip, proposer)); + + chain.Append(block, CreateBlockCommit(block)); + Assert.Equal( + new IValue[] { (Text)"item0.0,item1.0" }, + chain + .GetWorldState() + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetStates(new[] { _fx.Address1 }) + ); + Assert.Equal( + "item0.0,item1.0", + (Text)chain + .GetWorldState() + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetState(_fx.Address1)); + + var forked = chain.Fork(chain.Tip.Hash); + Assert.Equal(2, forked.Count); + Assert.Equal( + new IValue[] { (Text)"item0.0,item1.0" }, + forked + .GetWorldState() + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetStates(new[] { _fx.Address1 }) + ); + Assert.Equal( + "item0.0,item1.0", + (Text)forked + .GetWorldState() + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetState(_fx.Address1)); + } + + [SkippableTheory] + [InlineData(true)] + [InlineData(false)] + public void Swap(bool render) + { + Assert.Throws(() => _blockChain.Swap(null, render)()); + + (var addresses, Transaction[] txs1) = + MakeFixturesForAppendTests(); + var genesis = _blockChain.Genesis; + var miner = new PrivateKey(); + var minerAddress = miner.Address; + + Block block1 = _blockChain.ProposeBlock( + miner, + txs1.ToImmutableList(), + CreateBlockCommit(_blockChain.Tip), + CreateZeroRoundProof(_blockChain.Tip, miner), + ImmutableArray.Empty); + _blockChain.Append(block1, CreateBlockCommit(block1)); + + PrivateKey privateKey = new PrivateKey(new byte[] + { + 0xa8, 0x21, 0xc7, 0xc2, 0x08, 0xa9, 0x1e, 0x53, 0xbb, 0xb2, + 0x71, 0x15, 0xf4, 0x23, 0x5d, 0x82, 0x33, 0x44, 0xd1, 0x16, + 0x82, 0x04, 0x13, 0xb6, 0x30, 0xe7, 0x96, 0x4f, 0x22, 0xe0, + 0xec, 0xe0, + }); + + BlockHash tipHash = _blockChain.Tip.Hash; + BlockChain fork = _blockChain.Fork(tipHash); + + Transaction[][] txsA = + { + new[] // block #2 + { + _fx.MakeTransaction( + new[] + { + DumbAction.Create((addresses[0], "2-0")), + }, + timestamp: DateTimeOffset.MinValue, + nonce: 2, + privateKey: privateKey), + _fx.MakeTransaction( + new[] + { + DumbAction.Create((addresses[1], "2-1")), + }, + timestamp: DateTimeOffset.MinValue.AddSeconds(3), + nonce: 3, + privateKey: privateKey), + }, + new[] // block #3 + { + _fx.MakeTransaction( + new[] + { + DumbAction.Create((addresses[2], "3-0")), + }, + timestamp: DateTimeOffset.MinValue, + nonce: 4, + privateKey: privateKey), + _fx.MakeTransaction( + new[] + { + DumbAction.Create((addresses[3], "3-1")), + }, + timestamp: DateTimeOffset.MinValue.AddSeconds(4), + nonce: 5, + privateKey: privateKey), + }, + }; + + foreach (Transaction[] txs in txsA) + { + Block b = _blockChain.ProposeBlock( + miner, + txs.ToImmutableList(), + CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, miner), + ImmutableArray.Empty); + _blockChain.Append(b, CreateBlockCommit(b)); + } + + Transaction[][] txsB = + { + new[] + { + // block #2' + _fx.MakeTransaction( + new[] + { + DumbAction.Create((addresses[0], "2'-0")), + }, + timestamp: DateTimeOffset.MinValue, + nonce: 2, + privateKey: privateKey), + _fx.MakeTransaction( + new[] + { + DumbAction.Create((addresses[1], "2'-1-0")), + DumbAction.Create((addresses[2], "2'-1-1")), + }, + timestamp: DateTimeOffset.MinValue.AddSeconds(2), + nonce: 3, + privateKey: privateKey), + }, + new[] + { + _fx.MakeTransaction( + new[] + { + DumbAction.Create((addresses[0], "3'-0")), + }, + timestamp: DateTimeOffset.MinValue, + nonce: 4, + privateKey: privateKey), + }, + new[] + { + _fx.MakeTransaction( + new[] + { + DumbAction.Create((addresses[0], "4'-0")), + }, + timestamp: DateTimeOffset.MinValue, + nonce: 5, + privateKey: privateKey), + }, + }; + + foreach (Transaction[] txs in txsB) + { + Block b = fork.ProposeBlock( + miner, + txs.ToImmutableList(), + CreateBlockCommit(fork.Tip), + CreateZeroRoundProof(fork.Tip, miner), + ImmutableArray.Empty); + fork.Append(b, CreateBlockCommit(b), render: true); + } + + Guid previousChainId = _blockChain.Id; + _renderer.ResetRecords(); + _blockChain.Swap(fork, render)(); // #3 -> #2 -> #1 -> #2' -> #3' -> #4' + + Assert.Empty(_blockChain.Store.IterateIndexes(previousChainId)); + Assert.Empty(_blockChain.Store.ListTxNonces(previousChainId)); + + RenderRecord.BlockBase[] blockLevelRenders = _renderer.Records + .OfType() + .ToArray(); + + RenderRecord.ActionBase[] actionRenders = _renderer.ActionRecords + .Where(r => IsDumbAction(r.Action)) + .ToArray(); + DumbAction[] actions = actionRenders.Select(r => ToDumbAction(r.Action)).ToArray(); + + int actionsCountA = txsA.Sum(a => a.Sum(tx => tx.Actions.Count)); + int actionsCountB = txsB.Sum(b => b.Sum(tx => tx.Actions.Count)); + + int totalBlockCount = (int)_blockChain.Tip.Index + 1; + + if (render) + { + Assert.Equal(2, blockLevelRenders.Length); + Assert.IsType(blockLevelRenders[0]); + Assert.True(blockLevelRenders[0].Begin); + Assert.IsType(blockLevelRenders[1]); + Assert.True(blockLevelRenders[1].End); + + Assert.True(blockLevelRenders[0].Index < actionRenders[0].Index); + Assert.True(blockLevelRenders[0].Index < actionRenders.First(r => r.Render).Index); + Assert.True(actionRenders.Last().Index < blockLevelRenders[1].Index); + + Assert.Equal(actionsCountB, actionRenders.Length); + Assert.Equal(0, actionRenders.Count(r => r.Unrender)); + Assert.True(actionRenders.All(r => r.Render)); + + Assert.Equal("2'-0", actions[0].Append?.Item); + Assert.Equal("2'-1-0", actions[1].Append?.Item); + Assert.Equal("2'-1-1", actions[2].Append?.Item); + Assert.Equal("3'-0", actions[3].Append?.Item); + Assert.Equal("4'-0", actions[4].Append?.Item); + + RenderRecord.ActionBase[] blockActionRenders = _renderer.ActionRecords + .Where(r => IsMinerReward(r.Action)) + .ToArray(); + + // except genesis block. + Assert.Equal( + (Integer)(totalBlockCount - 1), + (Integer)_blockChain + .GetWorldState() + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetState(minerAddress) + ); + Assert.Equal(3, blockActionRenders.Length); // #1 -> #2' -> #3' -> #4' + Assert.True(blockActionRenders.All(r => r.Render)); + } + else + { + Assert.Empty(actionRenders); + } + } + + [SkippableTheory] + [InlineData(true)] + [InlineData(false)] + public void CannotSwapForSameHeightTip(bool render) + { + BlockChain fork = _blockChain.Fork(_blockChain.Tip.Hash); + IReadOnlyList prevRecords = _renderer.Records; + Assert.Throws(() => _blockChain.Swap(fork, render: render)()); + + // Render methods should be invoked if and only if the tip changes + Assert.Equal(prevRecords, _renderer.Records); + } + + [SkippableFact] + public void FindBranchPoint() + { + var key = new PrivateKey(); + Block b1 = _blockChain.ProposeBlock( + key, + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(b1, CreateBlockCommit(b1)); + Block b2 = _blockChain.ProposeBlock( + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(b2, CreateBlockCommit(b2)); + Block b3 = _blockChain.ProposeBlock( + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(b3, CreateBlockCommit(b3)); + Block b4 = _blockChain.ProposeBlock( + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append(b4, CreateBlockCommit(b4)); + + Assert.Equal(b1.PreviousHash, _blockChain.Genesis.Hash); + + var emptyLocator = new BlockLocator(new[] { _blockChain.Genesis.Hash }); + var invalidLocator = new BlockLocator( + new[] { new BlockHash(TestUtils.GetRandomBytes(BlockHash.Size)) }); + var locator = new BlockLocator( + new[] { b4.Hash, b3.Hash, b1.Hash, _blockChain.Genesis.Hash }); + + using (var emptyFx = new MemoryStoreFixture(_policy.BlockAction)) + using (var forkFx = new MemoryStoreFixture(_policy.BlockAction)) + { + var emptyChain = BlockChain.Create( + _blockChain.Policy, + new VolatileStagePolicy(), + emptyFx.Store, + emptyFx.StateStore, + emptyFx.GenesisBlock, + new ActionEvaluator( + _ => _blockChain.Policy.BlockAction, + stateStore: emptyFx.StateStore, + actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); + var fork = BlockChain.Create( + _blockChain.Policy, + new VolatileStagePolicy(), + forkFx.Store, + forkFx.StateStore, + forkFx.GenesisBlock, + new ActionEvaluator( + _ => _blockChain.Policy.BlockAction, + stateStore: forkFx.StateStore, + actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); + fork.Append(b1, CreateBlockCommit(b1)); + fork.Append(b2, CreateBlockCommit(b2)); + Block b5 = fork.ProposeBlock( + key, + lastCommit: CreateBlockCommit(fork.Tip), + proof: CreateZeroRoundProof(fork.Tip, key)); + fork.Append(b5, CreateBlockCommit(b5)); + + // Testing emptyChain + Assert.Equal(_blockChain.Genesis.Hash, emptyChain.FindBranchpoint(emptyLocator)); + Assert.Equal(_blockChain.Genesis.Hash, emptyChain.FindBranchpoint(locator)); + Assert.Null(emptyChain.FindBranchpoint(invalidLocator)); + + // Testing _blockChain + Assert.Equal(_blockChain.Genesis.Hash, _blockChain.FindBranchpoint(emptyLocator)); + Assert.Equal(b4.Hash, _blockChain.FindBranchpoint(locator)); + Assert.Null(_blockChain.FindBranchpoint(invalidLocator)); + + // Testing fork + Assert.Equal(_blockChain.Genesis.Hash, fork.FindBranchpoint(emptyLocator)); + Assert.Equal(b1.Hash, fork.FindBranchpoint(locator)); + Assert.Null(_blockChain.FindBranchpoint(invalidLocator)); + } + } + } +} diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs index 87f1f31af95..9c7ffbcb7da 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs @@ -10,7 +10,6 @@ using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Types.Blocks; -using Libplanet.Types.Consensus; using Libplanet.Types.Evidence; using Libplanet.Types.Tx; using Xunit; @@ -114,8 +113,7 @@ public void ExecuteActions() _fx.Proposer, txs.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(_fx.Proposer).Proof, + CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), ImmutableArray.Empty); _blockChain.Append(block1, CreateBlockCommit(block1), render: true); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs index 94b8ecaa702..c7a176ec062 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs @@ -37,7 +37,9 @@ public void ProposeBlock() .GetState(default)); var proposerA = new PrivateKey(); - Block block = _blockChain.ProposeBlock(proposerA); + Block block = _blockChain.ProposeBlock( + proposerA, + proof: CreateZeroRoundProof(_blockChain.Tip, proposerA)); _blockChain.Append(block, CreateBlockCommit(block)); Assert.True(_blockChain.ContainsBlock(block.Hash)); Assert.Equal(2, _blockChain.Count); @@ -55,8 +57,7 @@ public void ProposeBlock() Block anotherBlock = _blockChain.ProposeBlock( proposerB, CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0), - new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) - .Prove(proposerB).Proof, + CreateZeroRoundProof(_blockChain.Tip, proposerB), _blockChain.GetPendingEvidence()); _blockChain.Append(anotherBlock, CreateBlockCommit(anotherBlock)); Assert.True(_blockChain.ContainsBlock(anotherBlock.Hash)); @@ -74,12 +75,11 @@ public void ProposeBlock() .GetState(default) ); - var proposerC = new PrivateKey(); + var proposer = new PrivateKey(); Block block3 = _blockChain.ProposeBlock( - proposerC, + proposer, CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0), - new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) - .Prove(proposerC).Proof, + CreateZeroRoundProof(_blockChain.Tip, proposer), _blockChain.GetPendingEvidence()); Assert.False(_blockChain.ContainsBlock(block3.Hash)); Assert.Equal(3, _blockChain.Count); @@ -116,12 +116,11 @@ public void ProposeBlock() _blockChain.StageTransaction(heavyTx); } - var proposerD = new PrivateKey(); + proposer = new PrivateKey(); Block block4 = _blockChain.ProposeBlock( - proposerD, + proposer, CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0), - new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) - .Prove(proposerD).Proof, + CreateZeroRoundProof(_blockChain.Tip, proposer), _blockChain.GetPendingEvidence()); Assert.False(_blockChain.ContainsBlock(block4.Hash)); _logger.Debug( @@ -501,12 +500,8 @@ public void ProposeBlockWithLowerNonces() var proposer = new PrivateKey(); Block block2 = _blockChain.ProposeBlock( proposer, - CreateBlockCommit( - _blockChain.Tip.Hash, - _blockChain.Tip.Index, - 0), - new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) - .Prove(proposer).Proof, + CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0), + CreateZeroRoundProof(_blockChain.Tip, proposer), _blockChain.GetPendingEvidence()); _blockChain.Append(block2, CreateBlockCommit(block2)); @@ -571,8 +566,7 @@ public void ProposeBlockWithBlockAction() block = blockChain.ProposeBlock( privateKey1, CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) - .Prove(privateKey1).Proof, + CreateZeroRoundProof(_blockChain.Tip, privateKey1), _blockChain.GetPendingEvidence()); blockChain.Append(block, CreateBlockCommit(block)); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs index 2900005a001..330b7c580ee 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs @@ -102,8 +102,13 @@ public void TransactionsWithDuplicatedNonce() txIds.OrderBy(id => id), _blockChain.GetStagedTransactionIds().OrderBy(id => id) ); - block = _blockChain.ProposeBlock(key, TestUtils.CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(block, TestUtils.CreateBlockCommit(block)); + block = _blockChain.ProposeBlock( + key, + TestUtils.CreateBlockCommit(_blockChain.Tip), + TestUtils.CreateZeroRoundProof(_blockChain.Tip, key)); + _blockChain.Append( + block, + TestUtils.CreateBlockCommit(block)); // tx_0_1 and tx_1_x should be still staged, just filtered Assert.Empty(_blockChain.GetStagedTransactionIds()); Assert.Empty(_blockChain.StagePolicy.Iterate(_blockChain, filtered: true)); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs index fcfb04bdd2e..d5f4d669d6a 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs @@ -126,10 +126,8 @@ public void ValidateNextBlockInvalidIndex() previousHash: prev.Hash, txHash: null, lastCommit: TestUtils.CreateBlockCommit(prev.Hash, prev.Index + 1, 0), - proof: new LotMetadata(prev.Index + 1, 0, prev.Proof) - .Prove(_fx.Proposer).Proof, - evidenceHash: null)) - .Propose(), + proof: TestUtils.CreateZeroRoundProof(prev, _fx.Proposer), + evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws( () => _blockChain.Append( @@ -155,8 +153,7 @@ public void ValidateNextBlockInvalidPreviousHash() // ReSharper disable once PossibleInvalidOperationException lastCommit: TestUtils.CreateBlockCommit( _validNext.PreviousHash.Value, 1, 0), - proof: new LotMetadata(_validNext.Index + 1, 0, _validNext.Proof) - .Prove(_fx.Proposer).Proof, + proof: TestUtils.CreateZeroRoundProof(_validNext, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -179,8 +176,7 @@ public void ValidateNextBlockInvalidTimestamp() previousHash: _validNext.Hash, txHash: null, lastCommit: TestUtils.CreateBlockCommit(_validNext), - proof: new LotMetadata(_validNext.Index + 1, 0, _validNext.Proof) - .Prove(_fx.Proposer).Proof, + proof: TestUtils.CreateZeroRoundProof(_validNext, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -417,8 +413,7 @@ public void ValidateNextBlockLastCommitUpperIndexOne() previousHash: block1.Hash, txHash: null, lastCommit: blockCommit, - proof: new LotMetadata(2L, 0, block1.Proof) - .Prove(_fx.Proposer).Proof, + proof: TestUtils.CreateZeroRoundProof(block1, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block2, TestUtils.CreateBlockCommit(block2)); @@ -466,8 +461,7 @@ public void ValidateNextBlockLastCommitFailsUnexpectedValidator() previousHash: block1.Hash, txHash: null, lastCommit: blockCommit, - proof: new LotMetadata(2L, 0, block1.Proof) - .Prove(_fx.Proposer).Proof, + proof: TestUtils.CreateZeroRoundProof(block1, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -511,8 +505,7 @@ public void ValidateNextBlockLastCommitFailsDropExpectedValidator() previousHash: block1.Hash, txHash: null, lastCommit: blockCommit, - proof: new LotMetadata(2L, 0, block1.Proof) - .Prove(_fx.Proposer).Proof, + proof: TestUtils.CreateZeroRoundProof(block1, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.cs index 1dd290cb202..eea715e7945 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -112,8 +112,10 @@ public void CanFindBlockByIndex() { var genesis = _blockChain.Genesis; Assert.Equal(genesis, _blockChain[0]); - - Block block = _blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block = _blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(_blockChain.Tip, proposer)); _blockChain.Append(block, TestUtils.CreateBlockCommit(block)); Assert.Equal(block, _blockChain[1]); } @@ -123,13 +125,21 @@ public void CanonicalId() { var chain1 = _blockChain; var key = new PrivateKey(); - Block block1 = chain1.ProposeBlock(key); + Block block1 = chain1.ProposeBlock( + key, + proof: CreateZeroRoundProof(chain1.Tip, key)); chain1.Append(block1, CreateBlockCommit(block1)); - Block block2 = chain1.ProposeBlock(key, CreateBlockCommit(chain1.Tip)); + Block block2 = chain1.ProposeBlock( + key, + CreateBlockCommit(chain1.Tip), + CreateZeroRoundProof(chain1.Tip, key)); chain1.Append(block2, CreateBlockCommit(block2)); Assert.Equal(chain1.Id, _fx.Store.GetCanonicalChainId()); var chain2 = chain1.Fork(chain1.Tip.Hash); - Block block3 = chain2.ProposeBlock(key, CreateBlockCommit(chain1.Tip)); + Block block3 = chain2.ProposeBlock( + key, + CreateBlockCommit(chain1.Tip), + CreateZeroRoundProof(chain1.Tip, key)); chain2.Append(block3, CreateBlockCommit(block3)); Assert.Equal(chain1.Id, _fx.Store.GetCanonicalChainId()); @@ -172,7 +182,9 @@ public void BlockHashes() Assert.Equal(new[] { genesis.Hash, b1.Hash }, _blockChain.BlockHashes); Block b2 = _blockChain.ProposeBlock( - key, CreateBlockCommit(_blockChain.Tip)); + key, + CreateBlockCommit(_blockChain.Tip), + CreateZeroRoundProof(_blockChain.Tip, key)); _blockChain.Append(b2, CreateBlockCommit(b2)); Assert.Equal( new[] { genesis.Hash, b1.Hash, b2.Hash }, @@ -180,7 +192,9 @@ public void BlockHashes() ); Block b3 = _blockChain.ProposeBlock( - key, CreateBlockCommit(_blockChain.Tip)); + key, + CreateBlockCommit(_blockChain.Tip), + CreateZeroRoundProof(_blockChain.Tip, key)); _blockChain.Append(b3, CreateBlockCommit(b3)); Assert.Equal( new[] { genesis.Hash, b1.Hash, b2.Hash, b3.Hash }, @@ -255,7 +269,10 @@ public void ProcessActions() ); chain.StageTransaction(tx1); - Block block1 = chain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block1 = chain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(chain.Tip, proposer)); chain.Append(block1, CreateBlockCommit(block1)); IValue state = chain .GetNextWorldState() @@ -286,8 +303,11 @@ public void ProcessActions() ); chain.StageTransaction(tx2); + proposer = new PrivateKey(); Block block2 = chain.ProposeBlock( - new PrivateKey(), CreateBlockCommit(chain.Tip)); + proposer, + CreateBlockCommit(chain.Tip), + CreateZeroRoundProof(chain.Tip, proposer)); chain.Append(block2, CreateBlockCommit(block2)); state = chain @@ -311,8 +331,12 @@ public void ProcessActions() }, }.ToPlainValues() ); + + proposer = new PrivateKey(); Block block3 = chain.ProposeBlock( - new PrivateKey(), CreateBlockCommit(chain.Tip)); + proposer, + CreateBlockCommit(chain.Tip), + CreateZeroRoundProof(chain.Tip, proposer)); chain.StageTransaction(tx3); chain.Append(block3, CreateBlockCommit(block3)); state = chain @@ -354,7 +378,10 @@ public void ActionRenderersHaveDistinctContexts() var action = DumbAction.Create((default, string.Empty)); var actions = new[] { action }; blockChain.MakeTransaction(privateKey, actions); - Block block = blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block = blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(blockChain.Tip, proposer)); generatedRandomValueLogs.Clear(); Assert.Empty(generatedRandomValueLogs); @@ -381,7 +408,10 @@ public void RenderActionsAfterBlockIsRendered() blockChain.MakeTransaction(privateKey, actions); recordingRenderer.ResetRecords(); Block prevBlock = blockChain.Tip; - Block block = blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block = blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(prevBlock, proposer)); blockChain.Append(block, CreateBlockCommit(block)); Assert.Equal(2, blockChain.Count); @@ -427,7 +457,10 @@ public void RenderActionsAfterAppendComplete() var action = DumbAction.Create((default, string.Empty)); var actions = new[] { action }; blockChain.MakeTransaction(privateKey, actions); - Block block = blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block = blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(blockChain.Tip, proposer)); ThrowException.SomeException e = Assert.Throws( () => blockChain.Append(block, CreateBlockCommit(block))); @@ -448,13 +481,19 @@ public void FindNextHashes() Assert.Single(hashes); Assert.Equal(_blockChain.Genesis.Hash, hashes.First()); var block0 = _blockChain.Genesis; - var block1 = _blockChain.ProposeBlock(key); + var block1 = _blockChain.ProposeBlock( + key, + proof: CreateZeroRoundProof(_blockChain.Tip, key)); _blockChain.Append(block1, CreateBlockCommit(block1)); var block2 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); _blockChain.Append(block2, CreateBlockCommit(block2)); var block3 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); _blockChain.Append(block3, CreateBlockCommit(block3)); _blockChain.FindNextHashes(new BlockLocator(new[] { block0.Hash })) @@ -478,221 +517,6 @@ public void FindNextHashes() Assert.Equal(new[] { block0.Hash, block1.Hash }, hashes); } - [SkippableFact] - public void FindNextHashesAfterFork() - { - var key = new PrivateKey(); - - Block block = _blockChain.ProposeBlock(key); - _blockChain.Append(block, CreateBlockCommit(block)); - Block block2 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(block2, CreateBlockCommit(block2)); - Block block3 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(block3, CreateBlockCommit(block3)); - - BlockChain forked = _blockChain.Fork(_blockChain.Genesis.Hash); - Block forkedBlock = forked.ProposeBlock(key); - forked.Append(forkedBlock, CreateBlockCommit(forkedBlock)); - - BlockLocator locator = _blockChain.GetBlockLocator(); - forked.FindNextHashes(locator) - .Deconstruct(out long? offset, out IReadOnlyList hashes); - - Assert.Equal(forked[0].Index, offset); - Assert.Equal(new[] { forked[0].Hash, forked[1].Hash }, hashes); - } - - [SkippableFact] - public void Fork() - { - var key = new PrivateKey(); - - Block block1 = _blockChain.ProposeBlock(key); - _blockChain.Append(block1, CreateBlockCommit(block1)); - Block block2 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(block2, CreateBlockCommit(block2)); - Block block3 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(block3, CreateBlockCommit(block3)); - - BlockChain forked = _blockChain.Fork(block2.Hash); - - Assert.Equal( - new[] { block1, block2, block3 }, - new[] { _blockChain[1], _blockChain[2], _blockChain[3] } - ); - Assert.Equal(4, _blockChain.Count); - - Assert.Equal( - new[] { block1, block2 }, - new[] { forked[1], forked[2] } - ); - Assert.Equal(3, forked.Count); - } - - [SkippableFact] - public void ForkAndSwapCanonicity() - { - // Fork is not canonical. - var workspace = _blockChain.Fork(_blockChain.Genesis.Hash); - var b = workspace.ProposeBlock( - new PrivateKey(), - lastCommit: CreateBlockCommit(workspace.Tip)); - workspace.Append(b, CreateBlockCommit(b)); - Assert.True(_blockChain.IsCanonical); - Assert.False(workspace.IsCanonical); - - // Both are canonical after swap. - _blockChain.Swap(workspace, false); - Assert.True(_blockChain.IsCanonical); - Assert.True(workspace.IsCanonical); - } - - [SkippableFact] - public void ForkWithBlockNotExistInChain() - { - var key = new PrivateKey(); - var genesis = _blockChain.Genesis; - - for (var i = 0; i < 2; i++) - { - Block block = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(block, CreateBlockCommit(block)); - } - - Block newBlock = _blockChain.EvaluateAndSign( - ProposeNext(genesis, miner: key.PublicKey), key); - - Assert.Throws(() => - _blockChain.Fork(newBlock.Hash)); - - _blockChain.Store.PutBlock(newBlock); - Assert.Throws(() => - _blockChain.Fork(newBlock.Hash)); - } - - [SkippableFact] - public void ForkChainWithIncompleteBlockStates() - { - var fx = new MemoryStoreFixture( - _policy.PolicyActionsRegistry); - (_, _, BlockChain chain) = - MakeIncompleteBlockStates(fx.Store, fx.StateStore); - BlockChain forked = chain.Fork(chain[5].Hash); - Assert.Equal(chain[5], forked.Tip); - Assert.Equal(6, forked.Count); - } - - [SkippableFact] - public void StateAfterForkingAndAddingExistingBlock() - { - var miner = new PrivateKey(); - var signer = new PrivateKey(); - var address = signer.Address; - var actions1 = new[] { DumbAction.Create((address, "foo")) }; - var actions2 = new[] { DumbAction.Create((address, "bar")) }; - - _blockChain.MakeTransaction(signer, actions1); - var b1 = _blockChain.ProposeBlock(miner); - _blockChain.Append(b1, CreateBlockCommit(b1)); - - _blockChain.MakeTransaction(signer, actions2); - var b2 = _blockChain.ProposeBlock( - miner, lastCommit: CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(b2, CreateBlockCommit(b2)); - var state = _blockChain - .GetNextWorldState() - .GetAccountState(ReservedAddresses.LegacyAccount) - .GetState(address); - - Assert.Equal((Text)"foo,bar", state); - - var forked = _blockChain.Fork(b1.Hash); - state = forked - .GetNextWorldState() - .GetAccountState(ReservedAddresses.LegacyAccount) - .GetState(address); - Assert.Equal((Text)"foo", state); - - forked.Append(b2, CreateBlockCommit(b2)); - state = forked - .GetNextWorldState() - .GetAccountState(ReservedAddresses.LegacyAccount) - .GetState(address); - Assert.Equal((Text)"foo,bar", state); - } - - [SkippableFact] - public void ForkShouldSkipExecuteAndRenderGenesis() - { - var miner = new PrivateKey(); - var action = DumbAction.Create((_fx.Address1, "genesis")); - - using (IStore store = new MemoryStore()) - using (var stateStore = new TrieStateStore(new MemoryKeyValueStore())) - { - var actionEvaluator = new ActionEvaluator( - _policy.PolicyActionsRegistry, - stateStore, - new SingleActionLoader(typeof(DumbAction))); - var privateKey = new PrivateKey(); - var genesis = ProposeGenesisBlock( - ProposeGenesis( - GenesisProposer.PublicKey, - transactions: new[] - { - new Transaction( - new UnsignedTx( - new TxInvoice( - genesisHash: null, - updatedAddresses: ImmutableHashSet.Create(_fx.Address1), - timestamp: DateTimeOffset.UtcNow, - actions: new TxActionList(new[] { action }.ToPlainValues()), - maxGasPrice: null, - gasLimit: null), - new TxSigningMetadata(privateKey.PublicKey, 0)), - privateKey), - }), - privateKey: GenesisProposer); - - store.PutBlock(genesis); - var renderer = new RecordingActionRenderer(); - var blockChain = BlockChain.Create( - _policy, - new VolatileStagePolicy(), - store, - stateStore, - genesis, - new ActionEvaluator( - _policy.PolicyActionsRegistry, - stateStore: stateStore, - actionTypeLoader: new SingleActionLoader(typeof(DumbAction))), - renderers: new[] { renderer } - ); - - // Creation does not render anything - Assert.Equal(0, renderer.ActionRecords.Count); - Assert.Equal(0, renderer.BlockRecords.Count); - - Block block1 = blockChain.ProposeBlock(miner); - blockChain.Append(block1, CreateBlockCommit(block1)); - Block block2 = blockChain.ProposeBlock( - miner, lastCommit: CreateBlockCommit(blockChain.Tip)); - blockChain.Append(block2, CreateBlockCommit(block2)); - - int blockRecordsBeforeFork = renderer.BlockRecords.Count; - - blockChain.Fork(blockChain.Tip.Hash); - - Assert.Equal(0, renderer.ActionRecords.Count(r => IsDumbAction(r.Action))); - Assert.Equal(blockRecordsBeforeFork, renderer.BlockRecords.Count); - } - } - [SkippableFact] public void DetectInvalidTxNonce() { @@ -710,8 +534,7 @@ public void DetectInvalidTxNonce() _fx.Proposer, txsA.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(_fx.Proposer).Proof, + proof: CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), ImmutableArray.Empty); _blockChain.Append(b1, TestUtils.CreateBlockCommit(b1)); @@ -719,8 +542,7 @@ public void DetectInvalidTxNonce() _fx.Proposer, txsA.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(_fx.Proposer).Proof, + proof: CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), ImmutableArray.Empty); Assert.Throws(() => _blockChain.Append(b2, CreateBlockCommit(b2))); @@ -736,70 +558,11 @@ public void DetectInvalidTxNonce() _fx.Proposer, txsB.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(_fx.Proposer).Proof, + proof: CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), ImmutableArray.Empty); _blockChain.Append(b2, CreateBlockCommit(b2)); } - [SkippableFact] - public void ForkTxNonce() - { - // An active account, so that its some recent transactions became "stale" due to a fork. - var privateKey = new PrivateKey(); - Address address = privateKey.Address; - - // An inactive account, so that it has no recent transactions but only an old - // transaction, so that its all transactions are stale-proof (stale-resistant). - var lessActivePrivateKey = new PrivateKey(); - Address lessActiveAddress = lessActivePrivateKey.Address; - - var actions = new[] { DumbAction.Create((address, "foo")) }; - - var genesis = _blockChain.Genesis; - - Transaction[] txsA = - { - _fx.MakeTransaction(actions, privateKey: privateKey), - _fx.MakeTransaction(privateKey: lessActivePrivateKey), - }; - - Assert.Equal(0, _blockChain.GetNextTxNonce(address)); - - Block b1 = _blockChain.ProposeBlock( - _fx.Proposer, - txsA.ToImmutableList(), - CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(_fx.Proposer).Proof, - ImmutableArray.Empty); - _blockChain.Append(b1, CreateBlockCommit(b1)); - - Assert.Equal(1, _blockChain.GetNextTxNonce(address)); - - Transaction[] txsB = - { - _fx.MakeTransaction( - actions, - nonce: 1, - privateKey: privateKey), - }; - Block b2 = _blockChain.ProposeBlock( - _fx.Proposer, - txsB.ToImmutableList(), - CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(_fx.Proposer).Proof, - ImmutableArray.Empty); - _blockChain.Append(b2, CreateBlockCommit(b2)); - - Assert.Equal(2, _blockChain.GetNextTxNonce(address)); - - BlockChain forked = _blockChain.Fork(b1.Hash); - Assert.Equal(1, forked.GetNextTxNonce(address)); - Assert.Equal(1, forked.GetNextTxNonce(lessActiveAddress)); - } - [SkippableFact] public void GetBlockLocator() { @@ -809,7 +572,8 @@ public void GetBlockLocator() { var block = _blockChain.ProposeBlock( key, - lastCommit: CreateBlockCommit(_blockChain.Tip)); + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); _blockChain.Append(block, CreateBlockCommit(block)); blocks.Add(block); } @@ -829,216 +593,6 @@ public void GetBlockLocator() Assert.Equal(expected, actual); } - [SkippableTheory] - [InlineData(true)] - [InlineData(false)] - public void Swap(bool render) - { - Assert.Throws(() => _blockChain.Swap(null, render)()); - - (var addresses, Transaction[] txs1) = - MakeFixturesForAppendTests(); - var genesis = _blockChain.Genesis; - var miner = new PrivateKey(); - var minerAddress = miner.Address; - - Block block1 = _blockChain.ProposeBlock( - miner, - txs1.ToImmutableList(), - CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(miner).Proof, - ImmutableArray.Empty); - _blockChain.Append(block1, CreateBlockCommit(block1)); - - PrivateKey privateKey = new PrivateKey(new byte[] - { - 0xa8, 0x21, 0xc7, 0xc2, 0x08, 0xa9, 0x1e, 0x53, 0xbb, 0xb2, - 0x71, 0x15, 0xf4, 0x23, 0x5d, 0x82, 0x33, 0x44, 0xd1, 0x16, - 0x82, 0x04, 0x13, 0xb6, 0x30, 0xe7, 0x96, 0x4f, 0x22, 0xe0, - 0xec, 0xe0, - }); - - BlockHash tipHash = _blockChain.Tip.Hash; - BlockChain fork = _blockChain.Fork(tipHash); - - Transaction[][] txsA = - { - new[] // block #2 - { - _fx.MakeTransaction( - new[] - { - DumbAction.Create((addresses[0], "2-0")), - }, - timestamp: DateTimeOffset.MinValue, - nonce: 2, - privateKey: privateKey), - _fx.MakeTransaction( - new[] - { - DumbAction.Create((addresses[1], "2-1")), - }, - timestamp: DateTimeOffset.MinValue.AddSeconds(3), - nonce: 3, - privateKey: privateKey), - }, - new[] // block #3 - { - _fx.MakeTransaction( - new[] - { - DumbAction.Create((addresses[2], "3-0")), - }, - timestamp: DateTimeOffset.MinValue, - nonce: 4, - privateKey: privateKey), - _fx.MakeTransaction( - new[] - { - DumbAction.Create((addresses[3], "3-1")), - }, - timestamp: DateTimeOffset.MinValue.AddSeconds(4), - nonce: 5, - privateKey: privateKey), - }, - }; - - foreach (Transaction[] txs in txsA) - { - Block b = _blockChain.ProposeBlock( - miner, - txs.ToImmutableList(), - CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(_fx.Proposer).Proof, - ImmutableArray.Empty); - _blockChain.Append(b, CreateBlockCommit(b)); - } - - Transaction[][] txsB = - { - new[] - { - // block #2' - _fx.MakeTransaction( - new[] - { - DumbAction.Create((addresses[0], "2'-0")), - }, - timestamp: DateTimeOffset.MinValue, - nonce: 2, - privateKey: privateKey), - _fx.MakeTransaction( - new[] - { - DumbAction.Create((addresses[1], "2'-1-0")), - DumbAction.Create((addresses[2], "2'-1-1")), - }, - timestamp: DateTimeOffset.MinValue.AddSeconds(2), - nonce: 3, - privateKey: privateKey), - }, - new[] - { - _fx.MakeTransaction( - new[] - { - DumbAction.Create((addresses[0], "3'-0")), - }, - timestamp: DateTimeOffset.MinValue, - nonce: 4, - privateKey: privateKey), - }, - new[] - { - _fx.MakeTransaction( - new[] - { - DumbAction.Create((addresses[0], "4'-0")), - }, - timestamp: DateTimeOffset.MinValue, - nonce: 5, - privateKey: privateKey), - }, - }; - - foreach (Transaction[] txs in txsB) - { - Block b = fork.ProposeBlock( - miner, - txs.ToImmutableList(), - CreateBlockCommit(fork.Tip), - new LotMetadata(fork.Tip.Index + 1, 0, fork.Tip.Proof) - .Prove(_fx.Proposer).Proof, - ImmutableArray.Empty); - fork.Append(b, CreateBlockCommit(b), render: true); - } - - Guid previousChainId = _blockChain.Id; - _renderer.ResetRecords(); - _blockChain.Swap(fork, render)(); // #3 -> #2 -> #1 -> #2' -> #3' -> #4' - - Assert.Empty(_blockChain.Store.IterateIndexes(previousChainId)); - Assert.Empty(_blockChain.Store.ListTxNonces(previousChainId)); - - RenderRecord.BlockBase[] blockLevelRenders = _renderer.Records - .OfType() - .ToArray(); - - RenderRecord.ActionBase[] actionRenders = _renderer.ActionRecords - .Where(r => IsDumbAction(r.Action)) - .ToArray(); - DumbAction[] actions = actionRenders.Select(r => ToDumbAction(r.Action)).ToArray(); - - int actionsCountA = txsA.Sum(a => a.Sum(tx => tx.Actions.Count)); - int actionsCountB = txsB.Sum(b => b.Sum(tx => tx.Actions.Count)); - - int totalBlockCount = (int)_blockChain.Tip.Index + 1; - - if (render) - { - Assert.Equal(2, blockLevelRenders.Length); - Assert.IsType(blockLevelRenders[0]); - Assert.True(blockLevelRenders[0].Begin); - Assert.IsType(blockLevelRenders[1]); - Assert.True(blockLevelRenders[1].End); - - Assert.True(blockLevelRenders[0].Index < actionRenders[0].Index); - Assert.True(blockLevelRenders[0].Index < actionRenders.First(r => r.Render).Index); - Assert.True(actionRenders.Last().Index < blockLevelRenders[1].Index); - - Assert.Equal(actionsCountB, actionRenders.Length); - Assert.Equal(0, actionRenders.Count(r => r.Unrender)); - Assert.True(actionRenders.All(r => r.Render)); - - Assert.Equal("2'-0", actions[0].Append?.Item); - Assert.Equal("2'-1-0", actions[1].Append?.Item); - Assert.Equal("2'-1-1", actions[2].Append?.Item); - Assert.Equal("3'-0", actions[3].Append?.Item); - Assert.Equal("4'-0", actions[4].Append?.Item); - - RenderRecord.ActionBase[] blockActionRenders = _renderer.ActionRecords - .Where(r => IsMinerReward(r.Action)) - .ToArray(); - - // except genesis block. - Assert.Equal( - (Integer)(totalBlockCount - 1), - (Integer)_blockChain - .GetNextWorldState() - .GetAccountState(ReservedAddresses.LegacyAccount) - .GetState(minerAddress) - ); - Assert.Equal(3, blockActionRenders.Length); // #1 -> #2' -> #3' -> #4' - Assert.True(blockActionRenders.All(r => r.Render)); - } - else - { - Assert.Empty(actionRenders); - } - } - [SkippableFact] public void GetBlockCommit() { @@ -1048,16 +602,21 @@ public void GetBlockCommit() Assert.Null(_blockChain.GetBlockCommit(_blockChain.Genesis.Hash)); // BlockCommit is put to store when block is appended. - Block block1 = _blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block1 = _blockChain.ProposeBlock( + new PrivateKey(), + proof: CreateZeroRoundProof(_blockChain.Tip, proposer)); BlockCommit blockCommit1 = CreateBlockCommit(block1); _blockChain.Append(block1, blockCommit1); Assert.Equal(blockCommit1, _blockChain.GetBlockCommit(block1.Index)); Assert.Equal(blockCommit1, _blockChain.GetBlockCommit(block1.Hash)); // BlockCommit is retrieved from lastCommit. + proposer = new PrivateKey(); Block block2 = _blockChain.ProposeBlock( - new PrivateKey(), - lastCommit: CreateBlockCommit(_blockChain.Tip)); + proposer, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, proposer)); BlockCommit blockCommit2 = CreateBlockCommit(block2); _blockChain.Append(block2, blockCommit2); @@ -1067,27 +626,6 @@ public void GetBlockCommit() Assert.Equal(block2.LastCommit, _blockChain.GetBlockCommit(block1.Hash)); } - [SkippableFact] - public void GetBlockCommitAfterFork() - { - Block block1 = _blockChain.ProposeBlock(new PrivateKey()); - _blockChain.Append(block1, CreateBlockCommit(block1)); - Block block2 = _blockChain.ProposeBlock( - new PrivateKey(), - lastCommit: CreateBlockCommit(block1)); - _blockChain.Append(block2, CreateBlockCommit(block2)); - Block block3 = _blockChain.ProposeBlock( - new PrivateKey(), - lastCommit: CreateBlockCommit(block2)); - _blockChain.Append(block3, CreateBlockCommit(block3)); - - var forked = _blockChain.Fork(block2.Hash); - Assert.NotNull(forked.GetBlockCommit(forked.Tip.Index)); - Assert.Equal( - forked.GetBlockCommit(forked.Tip.Index), - block3.LastCommit); - } - [SkippableFact] public void CleanupBlockCommitStore() { @@ -1108,19 +646,6 @@ public void CleanupBlockCommitStore() Assert.Equal(blockCommit3, _blockChain.Store.GetBlockCommit(blockCommit3.BlockHash)); } - [SkippableTheory] - [InlineData(true)] - [InlineData(false)] - public void CannotSwapForSameHeightTip(bool render) - { - BlockChain fork = _blockChain.Fork(_blockChain.Tip.Hash); - IReadOnlyList prevRecords = _renderer.Records; - Assert.Throws(() => _blockChain.Swap(fork, render: render)()); - - // Render methods should be invoked if and only if the tip changes - Assert.Equal(prevRecords, _renderer.Records); - } - [SkippableTheory] [InlineData(true)] [InlineData(false)] @@ -1148,11 +673,15 @@ public void ReorgIsUnableToHeterogenousChain(bool render) for (int i = 0; i < 5; i++) { Block block1 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); + key, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, key)); _blockChain.Append(block1, CreateBlockCommit(block1)); Block block2 = chain2.ProposeBlock( - key, lastCommit: CreateBlockCommit(chain2.Tip)); + key, + lastCommit: CreateBlockCommit(chain2.Tip), + proof: CreateZeroRoundProof(chain2.Tip, key)); chain2.Append(block2, CreateBlockCommit(block2)); } @@ -1255,8 +784,7 @@ public void GetStateOnlyDrillsDownUntilRequestedAddressesAreFound() _fx.Proposer, txs.ToImmutableList(), CreateBlockCommit(chain.Tip), - new LotMetadata(chain.Tip.Index + 1, 0, chain.Tip.Proof) - .Prove(_fx.Proposer).Proof, + CreateZeroRoundProof(chain.Tip, _fx.Proposer), ImmutableArray.Empty); chain.Append(b, CreateBlockCommit(b)); } @@ -1302,7 +830,10 @@ public void GetStateReturnsEarlyForNonexistentAccount() Block b = chain.Genesis; for (int i = 0; i < 20; ++i) { - b = chain.ProposeBlock(_fx.Proposer, CreateBlockCommit(chain.Tip)); + b = chain.ProposeBlock( + _fx.Proposer, + CreateBlockCommit(chain.Tip), + CreateZeroRoundProof(chain.Tip, _fx.Proposer)); chain.Append(b, CreateBlockCommit(b)); } @@ -1322,65 +853,6 @@ public void GetStateReturnsEarlyForNonexistentAccount() ); } - [SkippableFact] - public void GetStateReturnsValidStateAfterFork() - { - var privateKey = new PrivateKey(); - var store = new MemoryStore(); - var stateStore = - new TrieStateStore(new MemoryKeyValueStore()); - var actionLoader = new SingleActionLoader(typeof(DumbAction)); - var chain = MakeBlockChain( - new NullBlockPolicy(), - store, - stateStore, - actionLoader, - new[] { DumbAction.Create((_fx.Address1, "item0.0")) }); - Assert.Equal( - "item0.0", - (Text)chain - .GetNextWorldState() - .GetAccountState(ReservedAddresses.LegacyAccount) - .GetState(_fx.Address1)); - - chain.MakeTransaction( - privateKey, - new[] { DumbAction.Create((_fx.Address1, "item1.0")), } - ); - Block block = chain.ProposeBlock(new PrivateKey()); - - chain.Append(block, CreateBlockCommit(block)); - Assert.Equal( - new IValue[] { (Text)"item0.0,item1.0" }, - chain - .GetNextWorldState() - .GetAccountState(ReservedAddresses.LegacyAccount) - .GetStates(new[] { _fx.Address1 }) - ); - Assert.Equal( - "item0.0,item1.0", - (Text)chain - .GetNextWorldState() - .GetAccountState(ReservedAddresses.LegacyAccount) - .GetState(_fx.Address1)); - - var forked = chain.Fork(chain.Tip.Hash); - Assert.Equal(2, forked.Count); - Assert.Equal( - new IValue[] { (Text)"item0.0,item1.0" }, - forked - .GetNextWorldState() - .GetAccountState(ReservedAddresses.LegacyAccount) - .GetStates(new[] { _fx.Address1 }) - ); - Assert.Equal( - "item0.0,item1.0", - (Text)forked - .GetNextWorldState() - .GetAccountState(ReservedAddresses.LegacyAccount) - .GetState(_fx.Address1)); - } - [SkippableFact] public void GetStateReturnsLatestStatesWhenMultipleAddresses() { @@ -1421,7 +893,9 @@ public void GetStateReturnsLatestStatesWhenMultipleAddresses() } Block block1 = chain.ProposeBlock( - privateKeys[0], lastCommit: CreateBlockCommit(chain.Tip)); + privateKeys[0], + lastCommit: CreateBlockCommit(chain.Tip), + proof: CreateZeroRoundProof(chain.Tip, privateKeys[0])); chain.Append(block1, CreateBlockCommit(block1)); @@ -1443,7 +917,9 @@ public void GetStateReturnsLatestStatesWhenMultipleAddresses() chain.MakeTransaction(privateKeys[0], new[] { DumbAction.Create((addresses[0], "2")) }); Block block2 = chain.ProposeBlock( - privateKeys[0], lastCommit: CreateBlockCommit(chain.Tip)); + privateKeys[0], + lastCommit: CreateBlockCommit(chain.Tip), + proof: CreateZeroRoundProof(chain.Tip, privateKeys[0])); chain.Append(block2, CreateBlockCommit(block2)); Assert.Equal( (Text)"1,2", @@ -1460,76 +936,6 @@ public void GetStateReturnsLatestStatesWhenMultipleAddresses() ); } - [SkippableFact] - public void FindBranchPoint() - { - var key = new PrivateKey(); - Block b1 = _blockChain.ProposeBlock(key); - _blockChain.Append(b1, CreateBlockCommit(b1)); - Block b2 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(b2, CreateBlockCommit(b2)); - Block b3 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(b3, CreateBlockCommit(b3)); - Block b4 = _blockChain.ProposeBlock( - key, lastCommit: CreateBlockCommit(_blockChain.Tip)); - _blockChain.Append(b4, CreateBlockCommit(b4)); - - Assert.Equal(b1.PreviousHash, _blockChain.Genesis.Hash); - - var emptyLocator = new BlockLocator(new[] { _blockChain.Genesis.Hash }); - var invalidLocator = new BlockLocator( - new[] { new BlockHash(TestUtils.GetRandomBytes(BlockHash.Size)) }); - var locator = new BlockLocator( - new[] { b4.Hash, b3.Hash, b1.Hash, _blockChain.Genesis.Hash }); - - using (var emptyFx = new MemoryStoreFixture(_policy.PolicyActionsRegistry)) - using (var forkFx = new MemoryStoreFixture(_policy.PolicyActionsRegistry)) - { - var emptyChain = BlockChain.Create( - _blockChain.Policy, - new VolatileStagePolicy(), - emptyFx.Store, - emptyFx.StateStore, - emptyFx.GenesisBlock, - new ActionEvaluator( - _blockChain.Policy.PolicyActionsRegistry, - stateStore: emptyFx.StateStore, - actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); - var fork = BlockChain.Create( - _blockChain.Policy, - new VolatileStagePolicy(), - forkFx.Store, - forkFx.StateStore, - forkFx.GenesisBlock, - new ActionEvaluator( - _blockChain.Policy.PolicyActionsRegistry, - stateStore: forkFx.StateStore, - actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); - fork.Append(b1, CreateBlockCommit(b1)); - fork.Append(b2, CreateBlockCommit(b2)); - Block b5 = fork.ProposeBlock( - key, lastCommit: CreateBlockCommit(fork.Tip)); - fork.Append(b5, CreateBlockCommit(b5)); - - // Testing emptyChain - Assert.Equal(_blockChain.Genesis.Hash, emptyChain.FindBranchpoint(emptyLocator)); - Assert.Equal(_blockChain.Genesis.Hash, emptyChain.FindBranchpoint(locator)); - Assert.Null(emptyChain.FindBranchpoint(invalidLocator)); - - // Testing _blockChain - Assert.Equal(_blockChain.Genesis.Hash, _blockChain.FindBranchpoint(emptyLocator)); - Assert.Equal(b4.Hash, _blockChain.FindBranchpoint(locator)); - Assert.Null(_blockChain.FindBranchpoint(invalidLocator)); - - // Testing fork - Assert.Equal(_blockChain.Genesis.Hash, fork.FindBranchpoint(emptyLocator)); - Assert.Equal(b1.Hash, fork.FindBranchpoint(locator)); - Assert.Null(_blockChain.FindBranchpoint(invalidLocator)); - } - } - [SkippableFact] public void GetNextTxNonce() { @@ -1549,8 +955,7 @@ public void GetNextTxNonce() _fx.Proposer, txsA.ToImmutableList(), CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index + 1, 0, _blockChain.Tip.Proof) - .Prove(_fx.Proposer).Proof, + CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), ImmutableArray.Empty); _blockChain.Append(b1, CreateBlockCommit(b1)); @@ -1618,7 +1023,9 @@ public void GetNextTxNonceWithStaleTx() }; StageTransactions(txs); - Block block = _blockChain.ProposeBlock(privateKey); + Block block = _blockChain.ProposeBlock( + privateKey, + proof: CreateZeroRoundProof(_blockChain.Tip, privateKey)); _blockChain.Append(block, CreateBlockCommit(block)); Transaction[] staleTxs = @@ -1655,7 +1062,8 @@ IReadOnlyList txs txs, blockInterval: TimeSpan.FromSeconds(10), miner: _fx.Proposer.PublicKey, - lastCommit: CreateBlockCommit(block)), + lastCommit: CreateBlockCommit(block), + proof: CreateZeroRoundProof(block, _fx.Proposer)), _fx.Proposer); Transaction[] txsA = @@ -1790,13 +1198,19 @@ public void BlockActionWithMultipleAddress() var rewardRecordAddress = MinerReward.RewardRecordAddress; Block block1 = _blockChain.ProposeBlock( - miner1, lastCommit: CreateBlockCommit(_blockChain.Tip)); + miner1, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, miner1)); _blockChain.Append(block1, CreateBlockCommit(block1)); Block block2 = _blockChain.ProposeBlock( - miner1, lastCommit: CreateBlockCommit(_blockChain.Tip)); + miner1, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, miner1)); _blockChain.Append(block2, CreateBlockCommit(block2)); Block block3 = _blockChain.ProposeBlock( - miner2, lastCommit: CreateBlockCommit(_blockChain.Tip)); + miner2, + lastCommit: CreateBlockCommit(_blockChain.Tip), + proof: CreateZeroRoundProof(_blockChain.Tip, miner2)); _blockChain.Append(block3, CreateBlockCommit(block3)); IValue miner1state = _blockChain @@ -1918,7 +1332,8 @@ void BuildIndex(Guid id, Block block) new[] { tx }, blockInterval: TimeSpan.FromSeconds(10), miner: GenesisProposer.PublicKey, - lastCommit: CreateBlockCommit(b)), + lastCommit: CreateBlockCommit(b), + proof: CreateZeroRoundProof(b, GenesisProposer)), GenesisProposer); var evals = actionEvaluator.EvaluateBlock(b, previousState); @@ -2029,7 +1444,10 @@ private void TipChanged() _renderer.ResetRecords(); Assert.Empty(_renderer.BlockRecords); - Block block = _blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block = _blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(_blockChain.Tip, proposer)); _blockChain.Append(block, CreateBlockCommit(block)); IReadOnlyList records = _renderer.BlockRecords; Assert.Equal(2, records.Count); @@ -2184,7 +1602,9 @@ private void FilterLowerNonceTxAfterStaging() nonce: nonce, privateKey: privateKey, timestamp: DateTimeOffset.Now)) .ToArray(); StageTransactions(txsA); - Block b1 = _blockChain.ProposeBlock(privateKey); + Block b1 = _blockChain.ProposeBlock( + privateKey, + proof: CreateZeroRoundProof(_blockChain.Tip, privateKey)); _blockChain.Append(b1, CreateBlockCommit(b1)); Assert.Equal( txsA, @@ -2256,7 +1676,8 @@ private void CheckIfTxPolicyExceptionHasInnerException() previousBlock: chain.Genesis, miner: GenesisProposer, txs: new[] { blockTx }, - stateRootHash: (HashDigest)nextStateRootHash); + stateRootHash: (HashDigest)nextStateRootHash, + proof: CreateZeroRoundProof(chain.Genesis, GenesisProposer)); var e = Assert.Throws( () => chain.Append(block, CreateBlockCommit(block))); @@ -2322,7 +1743,10 @@ private void ValidateNextBlockCommitOnValidatorSetChange() new Validator(newValidatorPrivateKey.PublicKey, BigInteger.One)), } ); - var newBlock = blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + var newBlock = blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(blockChain.Tip, proposer)); var newBlockCommit = new BlockCommit( newBlock.Index, 0, newBlock.Hash, ValidatorPrivateKeys.Select( pk => new VoteMetadata( @@ -2344,8 +1768,11 @@ private void ValidateNextBlockCommitOnValidatorSetChange() new SetValidator(new Validator(new PrivateKey().PublicKey, BigInteger.One)), } ); + proposer = new PrivateKey(); var nextBlock = blockChain.ProposeBlock( - new PrivateKey(), lastCommit: newBlockCommit); + proposer, + lastCommit: newBlockCommit, + proof: CreateZeroRoundProof(_blockChain.Tip, proposer)); var nextBlockCommit = new BlockCommit( nextBlock.Index, 0, @@ -2371,8 +1798,11 @@ private void ValidateNextBlockCommitOnValidatorSetChange() new SetValidator(new Validator(new PrivateKey().PublicKey, BigInteger.One)), } ); + proposer = new PrivateKey(); var invalidCommitBlock = blockChain.ProposeBlock( - new PrivateKey(), lastCommit: nextBlockCommit); + proposer, + lastCommit: nextBlockCommit, + proof: CreateZeroRoundProof(blockChain.Tip, proposer)); Assert.Throws( () => blockChain.Append( diff --git a/test/Libplanet.Tests/Fixtures/IntegerSet.cs b/test/Libplanet.Tests/Fixtures/IntegerSet.cs index 679eb971e35..fd0e0e61e2b 100644 --- a/test/Libplanet.Tests/Fixtures/IntegerSet.cs +++ b/test/Libplanet.Tests/Fixtures/IntegerSet.cs @@ -173,7 +173,9 @@ public TxWithContext Sign(int signerIndex, params Arithmetic[] actions) => Sign(PrivateKeys[signerIndex], actions); public Block Propose() => Chain.ProposeBlock( - Miner, TestUtils.CreateBlockCommit(Chain.Tip)); + Miner, + TestUtils.CreateBlockCommit(Chain.Tip), + TestUtils.CreateZeroRoundProof(Chain.Tip, Miner)); public void Append(Block block) => Chain.Append(block, TestUtils.CreateBlockCommit(block)); diff --git a/test/Libplanet.Tests/Store/StoreTest.cs b/test/Libplanet.Tests/Store/StoreTest.cs index 5a25950f8b6..bffa328a8a6 100644 --- a/test/Libplanet.Tests/Store/StoreTest.cs +++ b/test/Libplanet.Tests/Store/StoreTest.cs @@ -1026,11 +1026,19 @@ public void Copy() // FIXME: Need to add more complex blocks/transactions. var key = new PrivateKey(); - var block = blocks.ProposeBlock(key); + var block = blocks.ProposeBlock( + key, + proof: TestUtils.CreateZeroRoundProof(blocks.Tip, key)); blocks.Append(block, CreateBlockCommit(block)); - block = blocks.ProposeBlock(key, CreateBlockCommit(blocks.Tip)); + block = blocks.ProposeBlock( + key, + CreateBlockCommit(blocks.Tip), + TestUtils.CreateZeroRoundProof(blocks.Tip, key)); blocks.Append(block, CreateBlockCommit(block)); - block = blocks.ProposeBlock(key, CreateBlockCommit(blocks.Tip)); + block = blocks.ProposeBlock( + key, + CreateBlockCommit(blocks.Tip), + TestUtils.CreateZeroRoundProof(blocks.Tip, key)); blocks.Append(block, CreateBlockCommit(block)); s1.Copy(to: Fx.Store); diff --git a/test/Libplanet.Tests/TestUtils.cs b/test/Libplanet.Tests/TestUtils.cs index 824130d4af7..c0251356d0e 100644 --- a/test/Libplanet.Tests/TestUtils.cs +++ b/test/Libplanet.Tests/TestUtils.cs @@ -411,6 +411,11 @@ public static BlockCommit CreateBlockCommit( height, round, blockHash, votes); } + public static Proof CreateZeroRoundProof( + Block tip, + PrivateKey proposerKey) + => new LotMetadata(tip.Index + 1, 0, tip.Proof).Prove(proposerKey).Proof; + public static PreEvaluationBlock ProposeGenesis( PublicKey proposer = null, IReadOnlyList transactions = null, From 3566ea66ad9837785848bd8bb638f4a849cb0691 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Sun, 24 Mar 2024 22:50:25 +0900 Subject: [PATCH 21/61] doc: Update chagelog --- CHANGES.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0b6cd5a6b14..d143aed26cb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,19 +27,13 @@ To be released. - Added `PublicKey.VerifyProof()` method as a proof verification. [[#VRF]] - Added `Proof` struct as a wrapper structure of proof(pi-bytes) generated by ECVRF. [[#VRF]] - - Added `ConsensusInformation` struct as a base payload to be proved - with private key. [[#VRF]] + - Added `LotMetadata` struct as a base payload to be proved with private key. + [[#VRF]] + - Added `Lot` struct as a proved contents to be submitted as a lot of proposer + sortition. [[#VRF]] - `BlockMetadata.CurrentProtocolVersion` has been changed from 5 to 6. [[#VRF]] - Added `IBlockMetadata.Proof` property. [[#VRF]] - - Added `PreProposal` class as a content of message that suggests - `PreEvaluationBlock` as a `Proposal` candidate during - `ConsensusStep.PrePropose`. [[#VRF]] - - Added `PreProposalMetadata` class as a metadata of `PreProposal`. [[#VRF]] - - Added `ConsensusStep.PrePropose`. [[#VRF]] - - Added `ConsensusPreProposalMsg` class as a `ConsensusMsg` broadcasted during - `ConsensusStep.PrePropose`. [[#VRF]] - - Added `PreProposalSet` class as a `PreProposal` selector. ### Behavioral changes From 448d6f7e931f59efab7fa809ab57918f59aa650a Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 25 Mar 2024 18:41:15 +0900 Subject: [PATCH 22/61] fix: Fix bug where BigInteger byte array conversion used right padding --- .../DefaultConsensusCryptoBackend.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs b/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs index 62823081ed7..ead5730f375 100644 --- a/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs +++ b/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs @@ -20,11 +20,13 @@ namespace Libplanet.Crypto public class DefaultConsensusCryptoBackend : IConsensusCryptoBackend { private ECDomainParameters _eCParams; + private int _eCFieldBytesSize; public DefaultConsensusCryptoBackend() { X9ECParameters ps = ECNamedCurveTable.GetByName("secp256k1"); _eCParams = new ECDomainParameters(ps.Curve, ps.G, ps.N, ps.H); + _eCFieldBytesSize = (_eCParams.Curve.FieldSize + 7) >> 3; SuiteBytes = new byte[] { 0 }.ToImmutableArray(); } @@ -77,10 +79,14 @@ public byte[] Prove(byte[] alphaBytes, PrivateKey privateKey) byte[] gammaBytes = dPointH.GetEncoded(true); byte[] cBytes = c.ToByteArrayUnsigned(); byte[] sBytes = s.ToByteArrayUnsigned(); - Array.Resize(ref cBytes, (_eCParams.Curve.FieldSize + 7) >> 3); - Array.Resize(ref sBytes, (_eCParams.Curve.FieldSize + 7) >> 3); - byte[] piBytes = gammaBytes.Concat(cBytes).Concat(sBytes).ToArray(); + byte[] leftPadCBytes = new byte[_eCFieldBytesSize]; + byte[] leftPadSBytes = new byte[_eCFieldBytesSize]; + + Array.Copy(cBytes, 0, leftPadCBytes, _eCFieldBytesSize - cBytes.Length, cBytes.Length); + Array.Copy(sBytes, 0, leftPadSBytes, _eCFieldBytesSize - sBytes.Length, sBytes.Length); + + byte[] piBytes = gammaBytes.Concat(leftPadCBytes).Concat(leftPadSBytes).ToArray(); return piBytes; } @@ -225,9 +231,9 @@ private BigInteger HashPoints(ECPoint[] points) /// private (ECPoint, BigInteger, BigInteger) DecodeProof(byte[] piBytes) { - int gammaBytesLen = ((_eCParams.Curve.FieldSize + 7) >> 3) + 1; - int cBytesLen = (_eCParams.Curve.FieldSize + 7) >> 3; - int sBytesLen = (_eCParams.Curve.FieldSize + 7) >> 3; + int gammaBytesLen = _eCFieldBytesSize + 1; + int cBytesLen = _eCFieldBytesSize; + int sBytesLen = _eCFieldBytesSize; if (piBytes.Length != gammaBytesLen + cBytesLen + sBytesLen) { throw new ArgumentException( From d7fa11c94859f47f46b66c4d60c6b3819399eed1 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 25 Mar 2024 20:33:20 +0900 Subject: [PATCH 23/61] test: Update Libplanet.Net.Tests --- .../ConsensusContextNonProposerTest.cs | 19 +++- .../Consensus/ConsensusContextTest.cs | 30 +++-- .../Consensus/ContextProposerTest.cs | 11 +- .../Consensus/ContextTest.cs | 22 +++- .../SwarmTest.Broadcast.cs | 77 +++++++++---- .../Libplanet.Net.Tests/SwarmTest.Fixtures.cs | 4 +- test/Libplanet.Net.Tests/SwarmTest.Preload.cs | 36 ++++-- test/Libplanet.Net.Tests/SwarmTest.cs | 103 +++++++++++++----- 8 files changed, 223 insertions(+), 79 deletions(-) diff --git a/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs index d681debcfbe..2d27e562971 100644 --- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs @@ -58,7 +58,9 @@ public async void NewHeightWithLastCommit() }; consensusContext.Start(); - var block1 = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + var block1 = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); consensusContext.HandleMessage( TestUtils.CreateConsensusPropose(block1, TestUtils.PrivateKeys[1])); var expectedVotes = new Vote[4]; @@ -165,7 +167,9 @@ public async void HandleMessageFromHigherHeight() } }; - Block block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + Block block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); blockChain.Store.PutBlockCommit(TestUtils.CreateBlockCommit(blockChain[1])); @@ -231,7 +235,8 @@ in TestUtils.PrivateKeys.Zip( (Dictionary)codec.Decode(proposal.Proposal.MarshaledBlock)); var blockHeightThree = blockChain.ProposeBlock( TestUtils.PrivateKeys[3], - TestUtils.CreateBlockCommit(blockHeightTwo)); + TestUtils.CreateBlockCommit(blockHeightTwo), + TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[3])); // Message from higher height consensusContext.HandleMessage( @@ -269,7 +274,9 @@ public async void UseLastCommitCacheIfHeightContextIsEmpty() }; consensusContext.Start(); - Block block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + Block block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); var createdLastCommit = TestUtils.CreateBlockCommit(block); blockChain.Append(block, createdLastCommit); @@ -311,7 +318,9 @@ public async void NewHeightDelay() }; consensusContext.Start(); - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + var block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); consensusContext.HandleMessage( TestUtils.CreateConsensusPropose(block, TestUtils.PrivateKeys[1])); diff --git a/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs index f292c41c5d4..3080fbf8cc1 100644 --- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs @@ -83,10 +83,15 @@ public async void NewHeightIncreasing() } }; - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + var block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); var blockCommit = TestUtils.CreateBlockCommit(block); blockChain.Append(block, blockCommit); - block = blockChain.ProposeBlock(TestUtils.PrivateKeys[2], blockCommit); + block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[2], + blockCommit, + TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[2])); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); Assert.Equal(2, blockChain.Tip.Index); @@ -178,7 +183,9 @@ public async void NewHeightWhenTipChanged() consensusContext.Start(); Assert.Equal(1, consensusContext.Height); - Block block = blockChain.ProposeBlock(new PrivateKey()); + Block block = blockChain.ProposeBlock( + new PrivateKey(), + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); Assert.Equal(1, consensusContext.Height); await Task.Delay(newHeightDelay + TimeSpan.FromSeconds(1)); @@ -197,7 +204,10 @@ public void IgnoreMessagesFromLowerHeight() Assert.True(consensusContext.Height == 1); Assert.False(consensusContext.HandleMessage( TestUtils.CreateConsensusPropose( - blockChain.ProposeBlock(TestUtils.PrivateKeys[0]), + blockChain.ProposeBlock( + TestUtils.PrivateKeys[0], + proof: TestUtils.CreateZeroRoundProof( + blockChain.Tip, TestUtils.PrivateKeys[0])), TestUtils.PrivateKeys[0], 0))); } @@ -285,7 +295,9 @@ public async Task GetVoteSetBits() TestUtils.ActionLoader, TestUtils.PrivateKeys[0]); consensusContext.Start(); - var block = blockChain.ProposeBlock(proposer); + var block = blockChain.ProposeBlock( + proposer, + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); var proposal = new ProposalMetadata( 1, 0, @@ -371,7 +383,9 @@ public async Task HandleVoteSetBits() }; consensusContext.Start(); - var block = blockChain.ProposeBlock(proposer); + var block = blockChain.ProposeBlock( + proposer, + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); var proposal = new ProposalMetadata( 1, 0, @@ -443,7 +457,9 @@ public async Task HandleProposalClaim() } }; consensusContext.Start(); - var block = blockChain.ProposeBlock(proposer); + var block = blockChain.ProposeBlock( + proposer, + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); var proposal = new ProposalMetadata( 1, 0, diff --git a/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs index fd2cae3923e..43f51d10764 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs @@ -345,10 +345,17 @@ public async void VoteNilOnSelfProposedInvalidBlock() var preVoteSent = new AsyncAutoResetEvent(); var blockChain = TestUtils.CreateDummyBlockChain(); - var block1 = blockChain.ProposeBlock(new PrivateKey()); + var proposer1 = new PrivateKey(); + var block1 = blockChain.ProposeBlock( + proposer1, + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer1)); var block1Commit = TestUtils.CreateBlockCommit(block1); blockChain.Append(block1, block1Commit); - var block2 = blockChain.ProposeBlock(new PrivateKey(), block1Commit); + var proposer2 = new PrivateKey(); + var block2 = blockChain.ProposeBlock( + proposer2, + block1Commit, + TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer2)); var block2Commit = TestUtils.CreateBlockCommit(block2); blockChain.Append(block2, block2Commit); diff --git a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs index f21cc5615fd..8dba0dc2ee6 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs @@ -159,7 +159,9 @@ public async Task CanAcceptMessagesAfterCommitFailure() // Add block #1 so we can start with a last commit for height 2. var blockChain = TestUtils.CreateDummyBlockChain(); - Block heightOneBlock = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + Block heightOneBlock = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); var lastCommit = TestUtils.CreateBlockCommit(heightOneBlock); blockChain.Append(heightOneBlock, lastCommit); @@ -266,7 +268,9 @@ public async Task ThrowOnInvalidProposerMessage() exceptionThrown = e; exceptionOccurred.Set(); }; - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + var block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); context.Start(); context.ProduceMessage( @@ -287,7 +291,9 @@ public async Task ThrowOnDifferentHeightMessage() exceptionThrown = e; exceptionOccurred.Set(); }; - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[2]); + var block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[2], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[2])); context.Start(); context.ProduceMessage( @@ -376,7 +382,9 @@ public async Task CanPreCommitOnEndCommit() genesisHash: blockChain.Genesis.Hash, actions: new[] { action }.ToPlainValues()); blockChain.StageTransaction(tx); - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + var block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); context.Start(); context.ProduceMessage( @@ -506,10 +514,12 @@ public async Task CanReplaceProposal() validatorSet: validatorSet); var blockA = blockChain.ProposeBlock( proposer, - lastCommit: blockChain.GetBlockCommit(blockChain.Tip.Hash)); + lastCommit: blockChain.GetBlockCommit(blockChain.Tip.Hash), + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); var blockB = blockChain.ProposeBlock( proposer, - lastCommit: blockChain.GetBlockCommit(blockChain.Tip.Hash)); + lastCommit: blockChain.GetBlockCommit(blockChain.Tip.Hash), + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); context.StateChanged += (sender, state) => { if (state.Step != prevStep) diff --git a/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs b/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs index 3114d0a6bb1..7d90aecb5e8 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs @@ -58,9 +58,12 @@ public async Task BroadcastBlock() foreach (int i in Enumerable.Range(0, numBlocks)) { + var proposer = new PrivateKey(); var block = chainA.ProposeBlock( - new PrivateKey(), TestUtils.CreateBlockCommit(chainA.Tip)); - chainA.Append(block, TestUtils.CreateBlockCommit(block)); + proposer, + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, proposer)); + chainA.Append(block, CreateBlockCommit(block)); } Assert.Equal(numBlocks, chainA.Tip.Index); @@ -101,8 +104,10 @@ public async Task BroadcastBlockToReconnectedPeer() foreach (int i in Enumerable.Range(0, 10)) { Block block = minerChain.ProposeBlock( - miner, CreateBlockCommit(minerChain.Tip)); - minerChain.Append(block, TestUtils.CreateBlockCommit(block)); + miner, + CreateBlockCommit(minerChain.Tip), + CreateZeroRoundProof(minerChain.Tip, miner)); + minerChain.Append(block, CreateBlockCommit(block)); } Swarm seed = await CreateSwarm( @@ -201,8 +206,10 @@ public async Task BroadcastIgnoreFromDifferentGenesisHash() await StartAsync(seedSwarm); await receiverSwarm.AddPeersAsync(new[] { seedSwarm.AsPeer }, null); - Block block = seedChain.ProposeBlock(seedMiner); - seedChain.Append(block, TestUtils.CreateBlockCommit(block)); + Block block = seedChain.ProposeBlock( + seedMiner, + proof: CreateZeroRoundProof(seedChain.Tip, seedMiner)); + seedChain.Append(block, CreateBlockCommit(block)); seedSwarm.BroadcastBlock(block); Assert.NotEqual(seedChain.Tip, receiverChain.Tip); } @@ -239,8 +246,10 @@ CancellationToken cancellationToken try { var block = chain.ProposeBlock( - miner, CreateBlockCommit(chain.Tip)); - chain.Append(block, TestUtils.CreateBlockCommit(block)); + miner, + CreateBlockCommit(chain.Tip), + CreateZeroRoundProof(chain.Tip, miner)); + chain.Append(block, CreateBlockCommit(block)); Log.Debug( "Block mined. [Node: {0}, Block: {1}]", @@ -311,8 +320,10 @@ public async Task BroadcastTx() ); chainA.StageTransaction(tx); - Block block = chainA.ProposeBlock(minerA); - chainA.Append(block, TestUtils.CreateBlockCommit(block)); + Block block = chainA.ProposeBlock( + minerA, + proof: CreateZeroRoundProof(chainA.Tip, minerA)); + chainA.Append(block, CreateBlockCommit(block)); try { @@ -374,8 +385,10 @@ public async Task BroadcastTxWhileMining() for (var i = 0; i < 10; i++) { Block block = chainC.ProposeBlock( - minerC, CreateBlockCommit(chainC.Tip)); - chainC.Append(block, TestUtils.CreateBlockCommit(block)); + minerC, + CreateBlockCommit(chainC.Tip), + CreateZeroRoundProof(chainC.Tip, minerC)); + chainC.Append(block, CreateBlockCommit(block)); } }); @@ -585,7 +598,9 @@ public async Task DoNotRebroadcastTxsWithLowerNonce() await StartAsync(swarmA); await StartAsync(swarmB); - Block block = chainB.ProposeBlock(keyB); + Block block = chainB.ProposeBlock( + keyB, + proof: CreateZeroRoundProof(chainB.Tip, keyB)); chainB.Append(block, TestUtils.CreateBlockCommit(block)); var tx3 = chainA.MakeTransaction( @@ -650,14 +665,18 @@ public async Task CanBroadcastBlock() foreach (int i in Enumerable.Range(0, 10)) { Block block = chainA.ProposeBlock( - keyA, CreateBlockCommit(chainA.Tip)); + keyA, + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, keyA)); chainA.Append(block, TestUtils.CreateBlockCommit(block)); } foreach (int i in Enumerable.Range(0, 3)) { Block block = chainB.ProposeBlock( - keyB, CreateBlockCommit(chainB.Tip)); + keyB, + CreateBlockCommit(chainB.Tip), + CreateZeroRoundProof(chainB.Tip, keyB)); chainB.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -810,7 +829,9 @@ public async Task BroadcastBlockWithoutGenesis() await BootstrapAsync(swarmB, swarmA.AsPeer); var block = chainA.ProposeBlock( - keyA, CreateBlockCommit(chainA.Tip)); + keyA, + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, keyA)); chainA.Append(block, TestUtils.CreateBlockCommit(block)); swarmA.BroadcastBlock(chainA[-1]); @@ -819,7 +840,9 @@ public async Task BroadcastBlockWithoutGenesis() Assert.Equal(chainB.BlockHashes, chainA.BlockHashes); block = chainA.ProposeBlock( - keyB, CreateBlockCommit(chainA.Tip)); + keyB, + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, keyB)); chainA.Append(block, TestUtils.CreateBlockCommit(block)); swarmA.BroadcastBlock(chainA[-1]); @@ -848,7 +871,9 @@ public async Task IgnoreExistingBlocks() BlockChain chainB = swarmB.BlockChain; var block = chainA.ProposeBlock( - keyA, CreateBlockCommit(chainA.Tip)); + keyA, + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, keyA)); BlockCommit blockCommit = TestUtils.CreateBlockCommit(block); chainA.Append(block, blockCommit); chainB.Append(block, blockCommit); @@ -856,7 +881,9 @@ public async Task IgnoreExistingBlocks() foreach (int i in Enumerable.Range(0, 3)) { block = chainA.ProposeBlock( - keyA, CreateBlockCommit(chainA.Tip)); + keyA, + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, keyA)); chainA.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -907,7 +934,9 @@ public async Task PullBlocks() foreach (int i in Enumerable.Range(0, 10)) { Block block = chainA.ProposeBlock( - keyA, CreateBlockCommit(chainA.Tip)); + keyA, + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, keyA)); chainA.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -916,14 +945,18 @@ public async Task PullBlocks() foreach (int i in Enumerable.Range(0, 5)) { Block block = chainB.ProposeBlock( - keyB, CreateBlockCommit(chainB.Tip)); + keyB, + CreateBlockCommit(chainB.Tip), + CreateZeroRoundProof(chainB.Tip, keyB)); chainB.Append(block, TestUtils.CreateBlockCommit(block)); } foreach (int i in Enumerable.Range(0, 3)) { Block block = chainC.ProposeBlock( - keyB, CreateBlockCommit(chainC.Tip)); + keyB, + CreateBlockCommit(chainC.Tip), + CreateZeroRoundProof(chainC.Tip, keyB)); chainC.Append(block, TestUtils.CreateBlockCommit(block)); } diff --git a/test/Libplanet.Net.Tests/SwarmTest.Fixtures.cs b/test/Libplanet.Net.Tests/SwarmTest.Fixtures.cs index 91c60f4ff6f..a89304e3573 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.Fixtures.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.Fixtures.cs @@ -59,7 +59,9 @@ private static (Address, Block[]) } Block block = chain.ProposeBlock( - miner, CreateBlockCommit(chain.Tip)); + miner, + CreateBlockCommit(chain.Tip), + CreateZeroRoundProof(chain.Tip, miner)); Log.Logger.Information(" #{0,2} {1}", block.Index, block.Hash); chain.Append(block, CreateBlockCommit(block)); } diff --git a/test/Libplanet.Net.Tests/SwarmTest.Preload.cs b/test/Libplanet.Net.Tests/SwarmTest.Preload.cs index 452ebfeec15..0e50e0ee062 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.Preload.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.Preload.cs @@ -940,7 +940,9 @@ public async Task GetDemandBlockHashesDuringReorg() while (forked.Count <= minerChain.Count + 1) { Block block = forked.ProposeBlock( - minerKey, CreateBlockCommit(forked.Tip)); + minerKey, + CreateBlockCommit(forked.Tip), + CreateZeroRoundProof(forked.Tip, minerKey)); forked.Append(block, CreateBlockCommit(block)); } @@ -1023,14 +1025,20 @@ public async Task PreloadFromTheHighestTipIndexChain() BlockChain receiverChain = receiverSwarm.BlockChain; Block block1 = minerChain1.ProposeBlock( - minerKey1, CreateBlockCommit(minerChain1.Tip)); + minerKey1, + CreateBlockCommit(minerChain1.Tip), + CreateZeroRoundProof(minerChain1.Tip, minerKey1)); minerChain1.Append(block1, CreateBlockCommit(block1)); Block block2 = minerChain1.ProposeBlock( - minerKey1, CreateBlockCommit(minerChain1.Tip)); + minerKey1, + CreateBlockCommit(minerChain1.Tip), + CreateZeroRoundProof(minerChain1.Tip, minerKey1)); minerChain1.Append(block2, CreateBlockCommit(block2)); Block block = minerChain2.ProposeBlock( - ChainPrivateKey, CreateBlockCommit(minerChain2.Tip)); + ChainPrivateKey, + CreateBlockCommit(minerChain2.Tip), + CreateZeroRoundProof(minerChain2.Tip, ChainPrivateKey)); minerChain2.Append(block, CreateBlockCommit(block)); Assert.True(minerChain1.Count > minerChain2.Count); @@ -1094,14 +1102,18 @@ public async Task PreloadIgnorePeerWithDifferentGenesisBlock() for (int i = 0; i < 10; i++) { Block block = validSeedChain.ProposeBlock( - key1, CreateBlockCommit(validSeedChain.Tip)); + key1, + CreateBlockCommit(validSeedChain.Tip), + CreateZeroRoundProof(validSeedChain.Tip, key1)); validSeedChain.Append(block, CreateBlockCommit(block)); } for (int i = 0; i < 20; i++) { Block block = invalidSeedChain.ProposeBlock( - key1, CreateBlockCommit(invalidSeedChain.Tip)); + key1, + CreateBlockCommit(invalidSeedChain.Tip), + CreateZeroRoundProof(invalidSeedChain.Tip, key1)); invalidSeedChain.Append(block, CreateBlockCommit(block)); } @@ -1148,7 +1160,9 @@ public async Task ActionExecutionWithBranchpoint() for (int i = 0; i < 10; i++) { var block = seedChain.ProposeBlock( - seedKey, CreateBlockCommit(seedChain.Tip)); + seedKey, + CreateBlockCommit(seedChain.Tip), + CreateZeroRoundProof(seedChain.Tip, seedKey)); seedChain.Append(block, TestUtils.CreateBlockCommit(block)); receiverChain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -1157,7 +1171,9 @@ public async Task ActionExecutionWithBranchpoint() for (int i = 0; i < 10; i++) { Block block = forked.ProposeBlock( - seedKey, CreateBlockCommit(forked.Tip)); + seedKey, + CreateBlockCommit(forked.Tip), + CreateZeroRoundProof(forked.Tip, seedKey)); forked.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -1218,7 +1234,9 @@ public async Task UpdateTxExecution() DumbAction.Create((default, $"Item{i}")), }); Block block = seedChain.ProposeBlock( - seedKey, CreateBlockCommit(seedChain.Tip)); + seedKey, + CreateBlockCommit(seedChain.Tip), + CreateZeroRoundProof(seedChain.Tip, seedKey)); seedChain.Append(block, TestUtils.CreateBlockCommit(block)); transactions.Add(transaction); } diff --git a/test/Libplanet.Net.Tests/SwarmTest.cs b/test/Libplanet.Net.Tests/SwarmTest.cs index 184fe796c81..ff59bf4d0cb 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.cs @@ -514,9 +514,14 @@ public async Task GetBlocks() BlockChain chainA = swarmA.BlockChain; - Block block1 = chainA.ProposeBlock(keyA); + Block block1 = chainA.ProposeBlock( + keyA, + proof: CreateZeroRoundProof(chainA.Tip, keyA)); chainA.Append(block1, TestUtils.CreateBlockCommit(block1)); - Block block2 = chainA.ProposeBlock(keyA, CreateBlockCommit(block1)); + Block block2 = chainA.ProposeBlock( + keyA, + CreateBlockCommit(block1), + CreateZeroRoundProof(block1, keyA)); chainA.Append(block2, TestUtils.CreateBlockCommit(block2)); try @@ -584,10 +589,14 @@ public async Task GetMultipleBlocksAtOnce() BlockChain chainB = swarmB.BlockChain; Block block1 = chainA.ProposeBlock( - keyA, CreateBlockCommit(chainA.Tip)); + keyA, + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, keyA)); chainA.Append(block1, TestUtils.CreateBlockCommit(block1)); Block block2 = chainA.ProposeBlock( - keyA, CreateBlockCommit(chainA.Tip)); + keyA, + CreateBlockCommit(chainA.Tip), + CreateZeroRoundProof(chainA.Tip, keyA)); chainA.Append(block2, TestUtils.CreateBlockCommit(block2)); try @@ -649,7 +658,9 @@ public async Task GetTx() Array.Empty().ToPlainValues() ); chainB.StageTransaction(tx); - Block block = chainB.ProposeBlock(keyB); + Block block = chainB.ProposeBlock( + keyB, + proof: CreateZeroRoundProof(chainB.Tip, keyB)); chainB.Append(block, TestUtils.CreateBlockCommit(block)); try @@ -834,7 +845,9 @@ async Task MineAndBroadcast(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - var block = seed.BlockChain.ProposeBlock(seedKey); + var block = seed.BlockChain.ProposeBlock( + seedKey, + proof: CreateZeroRoundProof(seed.BlockChain.Tip, seedKey)); seed.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); seed.BroadcastBlock(block); await Task.Delay(1000, cancellationToken); @@ -911,17 +924,23 @@ public async Task RenderInFork() miner1.BlockChain.MakeTransaction(privKey, new[] { DumbAction.Create((addr, item)) }); Block block1 = miner1.BlockChain.ProposeBlock( - key1, CreateBlockCommit(miner1.BlockChain.Tip)); + key1, + CreateBlockCommit(miner1.BlockChain.Tip), + CreateZeroRoundProof(miner1.BlockChain.Tip, key1)); miner1.BlockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); miner2.BlockChain.MakeTransaction(privKey, new[] { DumbAction.Create((addr, item)) }); Block block2 = miner2.BlockChain.ProposeBlock( - key2, CreateBlockCommit(miner2.BlockChain.Tip)); + key2, + CreateBlockCommit(miner2.BlockChain.Tip), + CreateZeroRoundProof(miner2.BlockChain.Tip, key2)); miner2.BlockChain.Append(block2, TestUtils.CreateBlockCommit(block2)); miner2.BlockChain.MakeTransaction(privKey, new[] { DumbAction.Create((addr, item)) }); var latest = miner2.BlockChain.ProposeBlock( - key2, CreateBlockCommit(miner2.BlockChain.Tip)); + key2, + CreateBlockCommit(miner2.BlockChain.Tip), + CreateZeroRoundProof(miner2.BlockChain.Tip, key2)); miner2.BlockChain.Append(latest, TestUtils.CreateBlockCommit(latest)); renderer.RenderEventHandler += (_, a) => @@ -985,7 +1004,8 @@ await CreateSwarm( CreateBlockCommit( miner1.BlockChain.Tip.Hash, miner1.BlockChain.Tip.Index, - 0)); + 0), + CreateZeroRoundProof(miner1.BlockChain.Tip, key1)); miner1.BlockChain.Append(b, TestUtils.CreateBlockCommit(b)); miner2.BlockChain.Append(b, TestUtils.CreateBlockCommit(b)); } @@ -999,11 +1019,17 @@ await CreateSwarm( await BootstrapAsync(receiver, miner1.AsPeer); var t = receiver.PreloadAsync(); - Block block1 = miner1.BlockChain.ProposeBlock(key1); + Block block1 = miner1.BlockChain.ProposeBlock( + key1, + proof: CreateZeroRoundProof(miner1.BlockChain.Tip, key1)); miner1.BlockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); - Block block2 = miner2.BlockChain.ProposeBlock(key1); + Block block2 = miner2.BlockChain.ProposeBlock( + key1, + proof: CreateZeroRoundProof(miner2.BlockChain.Tip, key1)); miner2.BlockChain.Append(block2, TestUtils.CreateBlockCommit(block2)); - Block latest = miner2.BlockChain.ProposeBlock(key2); + Block latest = miner2.BlockChain.ProposeBlock( + key2, + proof: CreateZeroRoundProof(miner2.BlockChain.Tip, key2)); miner2.BlockChain.Append(latest, TestUtils.CreateBlockCommit(latest)); miner2.BroadcastBlock(latest); await t; @@ -1055,13 +1081,19 @@ public async void RestageTransactionsOnceLocallyMinedAfterReorg(bool restage) Log.Debug("Make minerB's chain longer than minerA's chain"); Block blockA = minerA.BlockChain.ProposeBlock( - keyA, CreateBlockCommit(minerA.BlockChain.Tip)); + keyA, + CreateBlockCommit(minerA.BlockChain.Tip), + CreateZeroRoundProof(minerA.BlockChain.Tip, keyA)); minerA.BlockChain.Append(blockA, TestUtils.CreateBlockCommit(blockA)); Block blockB = minerB.BlockChain.ProposeBlock( - keyB, CreateBlockCommit(minerB.BlockChain.Tip)); + keyB, + CreateBlockCommit(minerB.BlockChain.Tip), + CreateZeroRoundProof(minerB.BlockChain.Tip, keyB)); minerB.BlockChain.Append(blockB, TestUtils.CreateBlockCommit(blockB)); Block blockC = minerB.BlockChain.ProposeBlock( - keyB, CreateBlockCommit(minerB.BlockChain.Tip)); + keyB, + CreateBlockCommit(minerB.BlockChain.Tip), + CreateZeroRoundProof(minerB.BlockChain.Tip, keyB)); minerB.BlockChain.Append(blockC, TestUtils.CreateBlockCommit(blockC)); Assert.Equal( @@ -1107,7 +1139,9 @@ public async void RestageTransactionsOnceLocallyMinedAfterReorg(bool restage) minerA.BlockChain.GetStagedTransactionIds().Contains(txA.Id)); Block block = minerA.BlockChain.ProposeBlock( - keyA, CreateBlockCommit(minerA.BlockChain.Tip)); + keyA, + CreateBlockCommit(minerA.BlockChain.Tip), + CreateZeroRoundProof(minerA.BlockChain.Tip, keyA)); minerA.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); minerA.BroadcastBlock(minerA.BlockChain.Tip); await minerB.BlockAppended.WaitAsync(); @@ -1289,26 +1323,31 @@ public async Task CreateNewChainWhenBranchPointNotExist() Block aBlock1 = ProposeNextBlock( genesis, keyA, - stateRootHash: nextSrh); + stateRootHash: nextSrh, + proof: CreateZeroRoundProof(genesis, keyA)); Block aBlock2 = ProposeNextBlock( aBlock1, keyA, stateRootHash: nextSrh, - lastCommit: CreateBlockCommit(aBlock1)); + lastCommit: CreateBlockCommit(aBlock1), + proof: CreateZeroRoundProof(aBlock1, keyA)); Block aBlock3 = ProposeNextBlock( aBlock2, keyA, stateRootHash: nextSrh, - lastCommit: CreateBlockCommit(aBlock2)); + lastCommit: CreateBlockCommit(aBlock2), + proof: CreateZeroRoundProof(aBlock2, keyA)); Block bBlock1 = ProposeNextBlock( genesis, keyB, - stateRootHash: nextSrh); + stateRootHash: nextSrh, + proof: CreateZeroRoundProof(genesis, keyB)); Block bBlock2 = ProposeNextBlock( bBlock1, keyB, stateRootHash: nextSrh, - lastCommit: CreateBlockCommit(bBlock1)); + lastCommit: CreateBlockCommit(bBlock1), + proof: CreateZeroRoundProof(bBlock1, keyB)); policyA.BlockedMiners.Add(keyB.Address); policyB.BlockedMiners.Add(keyA.Address); @@ -1437,7 +1476,9 @@ public async Task DoNotReceiveBlockFromNodeHavingDifferenceGenesisBlock() await swarmB.AddPeersAsync(new[] { swarmA.AsPeer }, null); await swarmC.AddPeersAsync(new[] { swarmA.AsPeer }, null); - var block = swarmA.BlockChain.ProposeBlock(privateKeyA); + var block = swarmA.BlockChain.ProposeBlock( + privateKeyA, + proof: CreateZeroRoundProof(swarmA.BlockChain.Tip, privateKeyA)); swarmA.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); Task.WaitAll(new[] @@ -1627,7 +1668,9 @@ public async Task DoNotFillWhenGetAllBlockAtFirstTimeFromSender() for (int i = 0; i < 6; i++) { Block block = chain.ProposeBlock( - ChainPrivateKey, TestUtils.CreateBlockCommit(chain.Tip)); + ChainPrivateKey, + CreateBlockCommit(chain.Tip), + CreateZeroRoundProof(chain.Tip, ChainPrivateKey)); chain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -1667,7 +1710,9 @@ public async Task FillWhenGetAChunkOfBlocksFromSender() for (int i = 0; i < 6; i++) { Block block = chain.ProposeBlock( - ChainPrivateKey, TestUtils.CreateBlockCommit(chain.Tip)); + ChainPrivateKey, + CreateBlockCommit(chain.Tip), + CreateZeroRoundProof(chain.Tip, ChainPrivateKey)); chain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -1708,7 +1753,9 @@ public async Task FillWhenGetAllBlocksFromSender() for (int i = 0; i < 6; i++) { Block block = chain.ProposeBlock( - ChainPrivateKey, CreateBlockCommit(chain.Tip)); + ChainPrivateKey, + CreateBlockCommit(chain.Tip), + CreateZeroRoundProof(chain.Tip, ChainPrivateKey)); chain.Append(block, TestUtils.CreateBlockCommit(block)); } @@ -1782,7 +1829,9 @@ public async Task GetPeerChainStateAsync() peerChainState.First() ); - Block block = swarm2.BlockChain.ProposeBlock(key2); + Block block = swarm2.BlockChain.ProposeBlock( + key2, + proof: CreateZeroRoundProof(swarm2.BlockChain.Tip, key2)); swarm2.BlockChain.Append(block, TestUtils.CreateBlockCommit(block)); peerChainState = await swarm1.GetPeerChainStateAsync( TimeSpan.FromSeconds(1), default); From b1487e796d7b4cc891f907c24cf000b8c0623f75 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 25 Mar 2024 22:49:40 +0900 Subject: [PATCH 24/61] test: Update ActionEvaluatorTest.Idempotent() --- .../Action/ActionEvaluatorTest.cs | 76 ++++++++++++++++--- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs index 59b1cf969f4..c324c28e4a3 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -96,7 +96,7 @@ public void Idempotent() }; var evs = Array.Empty(); var stateStore = new TrieStateStore(new MemoryKeyValueStore()); - var noStateRootBlock = new BlockContent( + var noStateRootBlockWithoutProof = new BlockContent( new BlockMetadata( protocolVersion: Block.CurrentProtocolVersion, index: 0, @@ -114,32 +114,88 @@ public void Idempotent() new PolicyActionsRegistry(), stateStore, new SingleActionLoader(typeof(ContextRecordingAction))); - Block stateRootBlock = noStateRootBlock.Sign( + Block stateRootBlockWithoutProof = noStateRootBlockWithoutProof.Sign( GenesisProposer, MerkleTrie.EmptyRootHash); - var generatedRandomNumbers = new List(); + var generatedRandomNumbersWithoutProof = new List(); - AssertPreEvaluationBlocksEqual(stateRootBlock, noStateRootBlock); + AssertPreEvaluationBlocksEqual( + stateRootBlockWithoutProof, noStateRootBlockWithoutProof); for (int i = 0; i < repeatCount; ++i) { - var actionEvaluations = actionEvaluator.Evaluate(noStateRootBlock, null); - generatedRandomNumbers.Add( + var actionEvaluations = actionEvaluator.Evaluate( + noStateRootBlockWithoutProof, null); + generatedRandomNumbersWithoutProof.Add( (Integer)new WorldBaseState( stateStore.GetStateRoot(actionEvaluations[0].OutputState), stateStore) .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(ContextRecordingAction.RandomRecordAddress)); - actionEvaluations = actionEvaluator.Evaluate(stateRootBlock, null); - generatedRandomNumbers.Add( + actionEvaluations = actionEvaluator.Evaluate(stateRootBlockWithoutProof, null); + generatedRandomNumbersWithoutProof.Add( (Integer)new WorldBaseState( stateStore.GetStateRoot(actionEvaluations[0].OutputState), stateStore) .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(ContextRecordingAction.RandomRecordAddress)); } - for (int i = 1; i < generatedRandomNumbers.Count; ++i) + for (int i = 1; i < generatedRandomNumbersWithoutProof.Count; ++i) { - Assert.Equal(generatedRandomNumbers[0], generatedRandomNumbers[i]); + Assert.Equal( + generatedRandomNumbersWithoutProof[0], + generatedRandomNumbersWithoutProof[i]); + } + + var noStateRootBlockWithProof = new BlockContent( + new BlockMetadata( + protocolVersion: Block.CurrentProtocolVersion, + index: 0, + timestamp: timestamp, + miner: GenesisProposer.Address, + publicKey: GenesisProposer.PublicKey, + previousHash: null, + txHash: BlockContent.DeriveTxHash(txs), + lastCommit: null, + proof: new LotMetadata(0, 0, null).Prove(GenesisProposer).Proof), + transactions: txs).Propose(); + + // Since there is no static method determine state root hash of common block, + // used method for genesis block instead. + var stateRootBlockWithProof = noStateRootBlockWithProof.Sign( + GenesisProposer, + MerkleTrie.EmptyRootHash); + var generatedRandomNumbersWithProof = new List(); + + AssertPreEvaluationBlocksEqual(stateRootBlockWithProof, noStateRootBlockWithProof); + + for (int i = 0; i < repeatCount; ++i) + { + var actionEvaluations = actionEvaluator.Evaluate(noStateRootBlockWithProof, null); + generatedRandomNumbersWithProof.Add( + (Integer)new WorldBaseState( + stateStore.GetStateRoot(actionEvaluations[0].OutputState), stateStore) + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetState(txAddress)); + actionEvaluations = actionEvaluator.Evaluate(stateRootBlockWithProof, null); + generatedRandomNumbersWithProof.Add( + (Integer)new WorldBaseState( + stateStore.GetStateRoot(actionEvaluations[0].OutputState), stateStore) + .GetAccountState(ReservedAddresses.LegacyAccount) + .GetState(txAddress)); + } + + for (int i = 1; i < generatedRandomNumbersWithProof.Count; ++i) + { + Assert.Equal( + generatedRandomNumbersWithProof[0], + generatedRandomNumbersWithProof[i]); + } + + for (int i = 0; i < repeatCount * 2; i++) + { + Assert.NotEqual( + generatedRandomNumbersWithoutProof[i], + generatedRandomNumbersWithProof[i]); } } From 9645f5406b4c0edc02a360c44a5a87dab2b26282 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 29 Mar 2024 13:01:13 +0900 Subject: [PATCH 25/61] test: Update and fix tests --- .../Action/ActionEvaluatorTest.cs | 33 +++++++++++++++---- test/Libplanet.Tests/Fixtures/IntegerSet.cs | 4 +++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs index c324c28e4a3..4d3ca1f9ed5 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -1507,6 +1507,29 @@ public void EvaluateThrowingInsufficientBalanceForGasFee() evaluations.Single().Exception?.InnerException?.GetType()); } + [Fact] + public void GenerateRandomSeed() + { + byte[] proofBytes = + { + 0x03, 0x47, 0xfc, 0xcb, 0x9f, 0x8b, 0x62, 0x8c, 0x00, 0x92, + 0x62, 0x7a, 0x7b, 0x91, 0x1a, 0x8e, 0x5b, 0xfb, 0xb4, 0x0b, + 0x5a, 0x25, 0xc1, 0x83, 0xf3, 0x4e, 0x91, 0x51, 0x3b, 0xaa, + 0xbd, 0x11, 0xfd, 0x9f, 0x72, 0xcd, 0x88, 0xac, 0x09, 0xab, + 0xe4, 0x97, 0xdb, 0x2b, 0x5e, 0x05, 0xb2, 0x52, 0x2c, 0x02, + 0xab, 0xd9, 0xb8, 0x5c, 0x62, 0x37, 0xcb, 0x48, 0x54, 0x08, + 0xd4, 0x6a, 0x13, 0x1e, 0xc1, 0xcd, 0xa7, 0xbc, 0xe3, 0x6c, + 0xce, 0x94, 0xaa, 0xd4, 0xca, 0x00, 0xcb, 0x3a, 0x3f, 0x24, + 0x9d, 0x4f, 0xaf, 0x76, 0x22, 0xa7, 0x28, 0x67, 0x2b, 0x08, + 0xa9, 0x8c, 0xa0, 0x63, 0xda, 0x27, 0xfa, + }; + + Proof proof = new Proof(proofBytes); + + int seed = proof.Seed; + Assert.Equal(-713621093, seed); + } + [Fact] public void GenerateLegacyRandomSeed() { @@ -1534,7 +1557,7 @@ public void GenerateLegacyRandomSeed() } [Fact] - public void CheckLegacyRandomSeedInAction() + public void CheckRandomSeedInAction() { IntegerSet fx = new IntegerSet(new[] { 5, 10 }); @@ -1556,13 +1579,11 @@ public void CheckLegacyRandomSeedInAction() stateStore: fx.StateStore, isPolicyAction: false).ToArray(); - byte[] preEvaluationHashBytes = blockA.PreEvaluationHash.ToByteArray(); + Assert.NotNull(blockA.Proof); + Proof proof = (Proof)blockA.Proof; int[] randomSeeds = Enumerable .Range(0, txA.Actions.Count) - .Select(offset => ActionEvaluator.GenerateLegacyRandomSeed( - preEvaluationHashBytes, - txA.Signature, - offset)) + .Select(offset => proof.Seed + offset) .ToArray(); for (int i = 0; i < evalsA.Length; i++) diff --git a/test/Libplanet.Tests/Fixtures/IntegerSet.cs b/test/Libplanet.Tests/Fixtures/IntegerSet.cs index fd0e0e61e2b..482de8d1ac7 100644 --- a/test/Libplanet.Tests/Fixtures/IntegerSet.cs +++ b/test/Libplanet.Tests/Fixtures/IntegerSet.cs @@ -177,6 +177,10 @@ public Block Propose() => Chain.ProposeBlock( TestUtils.CreateBlockCommit(Chain.Tip), TestUtils.CreateZeroRoundProof(Chain.Tip, Miner)); + public Block ProposeEmptyProof() => Chain.ProposeBlock( + Miner, + TestUtils.CreateBlockCommit(Chain.Tip)); + public void Append(Block block) => Chain.Append(block, TestUtils.CreateBlockCommit(block)); From 8843f73831cb4b47b7cdfb2c7e50059db3a23bc9 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 4 Apr 2024 12:28:17 +0900 Subject: [PATCH 26/61] feat: Add PreEvaluationBlockMarshaler --- src/Libplanet.Types/Blocks/BlockMarshaler.cs | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Libplanet.Types/Blocks/BlockMarshaler.cs b/src/Libplanet.Types/Blocks/BlockMarshaler.cs index c0bd8c3567a..3a95ac11c9e 100644 --- a/src/Libplanet.Types/Blocks/BlockMarshaler.cs +++ b/src/Libplanet.Types/Blocks/BlockMarshaler.cs @@ -21,6 +21,7 @@ public static class BlockMarshaler // Block fields: private static readonly Binary HeaderKey = new Binary(new byte[] { 0x48 }); // 'H' + private static readonly Binary PreEvalHeaderKey = new Binary(new byte[] { 0x45 }); // 'E' private static readonly Binary TransactionsKey = new Binary(new byte[] { 0x54 }); // 'T' private static readonly Binary EvidenceKey = new Binary(new byte[] { 0x56 }); // 'V' @@ -174,6 +175,27 @@ public static Dictionary MarshalBlock(this Block block) => MarshalTransactions(block.Transactions), MarshalEvidence(block.Evidence)); + public static Dictionary MarshalPreEvaluationBlock( + Dictionary marshaledPreEvaluationBlockHeader, + List marshaledTransactions + ) + { + Dictionary dict = Dictionary.Empty + .Add(PreEvalHeaderKey, marshaledPreEvaluationBlockHeader); + if (marshaledTransactions.Any()) + { + dict = dict.Add(TransactionsKey, marshaledTransactions); + } + + return dict; + } + + public static Dictionary MarshalPreEvaluationBlock( + this PreEvaluationBlock preEvaluationBlock) => + MarshalPreEvaluationBlock( + MarshalPreEvaluationBlockHeader(preEvaluationBlock.Header), + MarshalTransactions(preEvaluationBlock.Transactions)); + public static long UnmarshalBlockMetadataIndex(Dictionary marshaledMetadata) => (Integer)marshaledMetadata[IndexKey]; @@ -291,5 +313,13 @@ public static Block UnmarshalBlock(Dictionary marshaled) IReadOnlyList evidence = UnmarshalBlockEvidence(marshaled); return new Block(header, txs, evidence); } + + public static PreEvaluationBlock UnmarshalPreEvaluationBlock(Dictionary marshaled) + { + PreEvaluationBlockHeader header + = UnmarshalPreEvaluationBlockHeader((Dictionary)marshaled[PreEvalHeaderKey]); + IReadOnlyList txs = UnmarshalBlockTransactions(marshaled); + return new PreEvaluationBlock(header, txs); + } } } From 595723efbfb8569a4cc4a71fdb68f061e5057522 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 8 Apr 2024 10:50:04 +0900 Subject: [PATCH 27/61] chore: Update PublicKey.VerifyProof to receive IReadOnlyList --- src/Libplanet.Crypto/PublicKey.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Libplanet.Crypto/PublicKey.cs b/src/Libplanet.Crypto/PublicKey.cs index 1339a571196..90115c27bc3 100644 --- a/src/Libplanet.Crypto/PublicKey.cs +++ b/src/Libplanet.Crypto/PublicKey.cs @@ -232,8 +232,9 @@ public bool Verify(IReadOnlyList message, IReadOnlyList signature) /// true if the proves authenticity of /// the with the corresponding . /// Otherwise false. - public bool VerifyProof(byte[] message, Proof proof) - => CryptoConfig.ConsensusCryptoBackend.VerifyProof(message, proof.ToByteArray(), this); + public bool VerifyProof(IReadOnlyList message, Proof proof) + => CryptoConfig.ConsensusCryptoBackend.VerifyProof( + message.ToArray(), proof.ToByteArray(), this); /// /// Gets the public key's hexadecimal representation in compressed form. From 76c895618cb2f8232f4513b56b562d69d9926bcf Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 28 Jun 2024 12:30:15 +0900 Subject: [PATCH 28/61] feat: Add ConsensusInformation --- .../Consensus/ConsensusInformation.cs | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/Libplanet/Consensus/ConsensusInformation.cs diff --git a/src/Libplanet/Consensus/ConsensusInformation.cs b/src/Libplanet/Consensus/ConsensusInformation.cs new file mode 100644 index 00000000000..eb253ee4c73 --- /dev/null +++ b/src/Libplanet/Consensus/ConsensusInformation.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Immutable; +using Bencodex; +using Bencodex.Types; +using Libplanet.Crypto; + +namespace Libplanet.Consensus +{ + /// + /// Consensus round information used as payload of the + /// in the + /// . + /// + public readonly struct ConsensusInformation : IEquatable + { + private static readonly Binary HeightKey = + new Binary(new byte[] { 0x48 }); // 'H' + + private static readonly Binary RoundKey = + new Binary(new byte[] { 0x52 }); // 'R' + + private static readonly Binary LastProofKey = + new Binary(new byte[] { 0x4c }); // 'L' + + private static readonly Codec _codec = new Codec(); + + /// + /// Instantiates . + /// + /// Height of the consensus where + /// participate in the draw of the + /// . + /// Round of the consensus where + /// participate in the draw of the . + /// that has been decided on the previous round. + /// if current is 0, it indicates + /// from the + /// of the preproposing. + /// + /// Thrown when or + /// is negative. + public ConsensusInformation( + long height, + int round, + Proof? lastProof) + { + if (height < 0) + { + throw new ArgumentException( + $"Given {nameof(height)} cannot be negative: {height}"); + } + else if (round < 0) + { + throw new ArgumentException( + $"Given {nameof(round)} cannot be negative: {round}"); + } + + Height = height; + Round = round; + LastProof = lastProof; + Encoded = Encode(); + } + + /// + /// Height of the consensus where + /// participate in the draw of the + /// . + /// + public long Height { get; } + + /// + /// Round of the consensus where + /// participate in the draw of the . + /// + public int Round { get; } + + /// + /// that has been decided on the previous round. + /// + public Proof? LastProof { get; } + + /// + /// Byte array encoded form of , + /// used as a payload of during prepropose consensus step. + /// + public ImmutableArray Encoded { get; } + + /// + /// Generate a with given consensus information and + /// . + /// + /// + /// Height of the consensus where + /// participate in the draw of the + /// . + /// + /// Round of the consensus where + /// participate in the draw of the . + /// + /// that has been decided on the previous round. + /// + /// to prove given information. + /// that has been proved by . + /// + public static Proof Prove(long height, int round, Proof? lastProof, PrivateKey prover) + => new ConsensusInformation(height, round, lastProof).Prove(prover); + + /// + /// Verify the with given consensus information and + /// . + /// + /// + /// Height of the consensus where + /// participate in the draw of the + /// . + /// + /// Round of the consensus where + /// participate in the draw of the . + /// + /// that has been decided on the previous round. + /// + /// to verify. + /// + /// which corresponds to prover of + /// . + /// + /// if verified properly , otherwise . + /// + public static bool Verify( + long height, int round, Proof? lastProof, Proof proof, PublicKey verifier) + => new ConsensusInformation(height, round, lastProof).Verify(proof, verifier); + + /// + /// Generate a with and + /// . + /// + /// + /// to prove with. + /// + /// + /// that has been proved by . + public Proof Prove(PrivateKey prover) + => prover.Prove(Encoded); + + /// + /// Verify the with and + /// . + /// + /// + /// to verify with and + /// . + /// + /// to verify with. + /// + /// + /// if verified properly , otherwise . + /// + public bool Verify(Proof proof, PublicKey verifier) + => verifier.VerifyProof(Encoded, proof); + + /// + public bool Equals(ConsensusInformation other) + => Height == other.Height + && Round == other.Round + && LastProof.Equals(other.LastProof); + + /// + public override bool Equals(object? obj) => + obj is ConsensusInformation other && Equals(other); + + /// + public override int GetHashCode() + { + return HashCode.Combine( + Height, + Round, + LastProof); + } + + private ImmutableArray Encode() + { + Dictionary bencoded = Dictionary.Empty + .Add(HeightKey, Height) + .Add(RoundKey, Round); + + if (LastProof is Proof lastProof) + { + bencoded = bencoded.Add(LastProofKey, lastProof.ByteArray); + } + + return _codec.Encode(bencoded).ToImmutableArray(); + } + } +} From 4e367fffc95c7acb49d5c8304ec23bb52cd7b2e9 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 8 Apr 2024 10:52:20 +0900 Subject: [PATCH 29/61] feat: Add BlockMarshaler.UnmarshalProof() --- src/Libplanet.Types/Blocks/BlockMarshaler.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Libplanet.Types/Blocks/BlockMarshaler.cs b/src/Libplanet.Types/Blocks/BlockMarshaler.cs index 3a95ac11c9e..40294302f7a 100644 --- a/src/Libplanet.Types/Blocks/BlockMarshaler.cs +++ b/src/Libplanet.Types/Blocks/BlockMarshaler.cs @@ -234,9 +234,7 @@ public static BlockMetadata UnmarshalBlockMetadata(Dictionary marshaled) lastCommit: marshaled.ContainsKey(LastCommitKey) ? new BlockCommit(marshaled[LastCommitKey]) : (BlockCommit?)null, - proof: marshaled.ContainsKey(ProofKey) - ? new Proof(marshaled[ProofKey]) - : (Proof?)null, + proof: UnmarshalProof(marshaled), evidenceHash: marshaled.TryGetValue(EvidenceHashKey, out IValue ehv) ? new HashDigest(ehv) : (HashDigest?)null); @@ -321,5 +319,11 @@ PreEvaluationBlockHeader header IReadOnlyList txs = UnmarshalBlockTransactions(marshaled); return new PreEvaluationBlock(header, txs); } + + public static Proof? UnmarshalProof(Dictionary marshaled) => + marshaled.ContainsKey(ProofKey) + ? new Proof(marshaled[ProofKey]) + : (Proof?)null; + } } From 4b9b6e9809e6774a44a4ca2b0c698732a654b320 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 8 Apr 2024 10:52:50 +0900 Subject: [PATCH 30/61] feat: Add PreProposal, PreProposalMetadata --- src/Libplanet/Consensus/PreProposal.cs | 154 ++++++++++++ .../Consensus/PreProposalMetadata.cs | 219 ++++++++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 src/Libplanet/Consensus/PreProposal.cs create mode 100644 src/Libplanet/Consensus/PreProposalMetadata.cs diff --git a/src/Libplanet/Consensus/PreProposal.cs b/src/Libplanet/Consensus/PreProposal.cs new file mode 100644 index 00000000000..9fda50594b4 --- /dev/null +++ b/src/Libplanet/Consensus/PreProposal.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Immutable; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Security.Cryptography; +using System.Text.Json.Serialization; +using Bencodex; +using Bencodex.Types; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Types.Consensus; + +namespace Libplanet.Consensus +{ + /// + /// Represents a from a validator for consensus. + /// It contains an essential information to pre-propose + /// a pre-evaluation block for a consensus in a height, a round, a last proof, + /// and its signature to verify. The signature is verified in + /// constructor, so the instance of should be valid. + /// + public class PreProposal : IEquatable + { + private static readonly Binary SignatureKey = new Binary(new byte[] { 0x53 }); // 'S' + private static readonly Codec _codec = new Codec(); + + private readonly PreProposalMetadata _preProposalMetadata; + + /// + /// Instantiates a with given + /// and its . + /// + /// A to pre-propose. + /// + /// A signature signed with . + /// + /// Thrown if given is + /// empty. + /// Thrown if given is + /// invalid and cannot be verified with . + public PreProposal(PreProposalMetadata preProposalMetadata, ImmutableArray signature) + { + _preProposalMetadata = preProposalMetadata; + Signature = signature; + + if (signature.IsDefaultOrEmpty) + { + throw new ArgumentNullException( + nameof(signature), + "Signature cannot be null or empty."); + } + else if (!Verify()) + { + throw new ArgumentException("Signature is invalid.", nameof(signature)); + } + } + + public PreProposal(byte[] marshaled) + : this((Dictionary)_codec.Decode(marshaled)) + { + } + +#pragma warning disable SA1118 // The parameter spans multiple lines + public PreProposal(Dictionary bencoded) + : this( + new PreProposalMetadata(bencoded), + bencoded.ContainsKey(SignatureKey) + ? ((Binary)bencoded[SignatureKey]).ByteArray + : ImmutableArray.Empty) + { + } +#pragma warning restore SA1118 + + /// + public long Height => _preProposalMetadata.Height; + + /// + public int Round => _preProposalMetadata.Round; + + /// + public Proof? LastProof => _preProposalMetadata.LastProof; + + /// + public HashDigest PreEvaluationHash => _preProposalMetadata.PreEvaluationHash; + + /// + public DateTimeOffset Timestamp => _preProposalMetadata.Timestamp; + + /// + public PublicKey ValidatorPublicKey => _preProposalMetadata.ValidatorPublicKey; + + /// + public byte[] MarshaledPreEvalBlock => _preProposalMetadata.MarshaledPreEvalBlock; + + /// + /// A signature that signed with . + /// + public ImmutableArray Signature { get; } + + /// + /// A Bencodex-encoded value of . + /// + [JsonIgnore] + public IValue Bencoded => + !Signature.IsEmpty + ? ((Dictionary)_preProposalMetadata.Bencoded).Add(SignatureKey, Signature) + : _preProposalMetadata.Bencoded; + + /// + /// encoded data. + /// + public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); + + public byte[] ToByteArray() => _codec.Encode(Bencoded); + + /// + /// Verifies whether the is properly signed by + /// . + /// + /// if the is not empty + /// and is a valid signature signed by . + [Pure] + public bool Verify() => + !Signature.IsDefaultOrEmpty && + ValidatorPublicKey.Verify( + _preProposalMetadata.ByteArray.ToImmutableArray(), + Signature); + + /// + [Pure] + public bool Equals(PreProposal? other) + { + return other is PreProposal proposal && + _preProposalMetadata.Equals(proposal._preProposalMetadata) && + Signature.SequenceEqual(proposal.Signature); + } + + /// + [Pure] + public override bool Equals(object? obj) + { + return obj is Proposal other && Equals(other); + } + + /// + [Pure] + public override int GetHashCode() + { + return HashCode.Combine( + _preProposalMetadata.GetHashCode(), + ByteUtil.CalculateHashCode(Signature.ToArray())); + } + } +} diff --git a/src/Libplanet/Consensus/PreProposalMetadata.cs b/src/Libplanet/Consensus/PreProposalMetadata.cs new file mode 100644 index 00000000000..43247ed348d --- /dev/null +++ b/src/Libplanet/Consensus/PreProposalMetadata.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Immutable; +using System.Globalization; +using System.Security.Cryptography; +using System.Text.Json.Serialization; +using Bencodex; +using Bencodex.Types; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Types.Blocks; + +namespace Libplanet.Consensus +{ + /// + /// A class for constructing . This class contains proposal information + /// in consensus of a height and a round. Use to create a + /// . + /// + public class PreProposalMetadata : IEquatable, IBencodable + { + private const string TimestampFormat = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; + private static readonly Binary HeightKey = + new Binary(new byte[] { 0x48 }); // 'H' + + private static readonly Binary RoundKey = + new Binary(new byte[] { 0x52 }); // 'R' + + private static readonly Binary LastProofKey = + new Binary(new byte[] { 0x4c }); // 'L + + private static readonly Binary TimestampKey = + new Binary(new byte[] { 0x74 }); // 't' + + private static readonly Binary ValidatorPublicKeyKey = + new Binary(new byte[] { 0x50 }); // 'P' + + private static readonly Binary PreEvalBlockKey = + new Binary(new byte[] { 0x42 }); // 'B' + + private static readonly Codec _codec = new Codec(); + + /// + /// Instantiates with given parameters. + /// + /// a height of given pre-proposal values. + /// a round of given pre-proposal values. + /// a of previous round. + /// The time at which the pre-proposal took place. + /// a of pre-proposing validator. + /// + /// a marshaled bencodex-encoded + /// array of . + /// This can be thrown in following reasons: + /// + /// + /// Given is less than 0. + /// + /// + /// Given is less than 0. + /// + /// + /// + public PreProposalMetadata( + long height, + int round, + Proof? lastProof, + DateTimeOffset timestamp, + PublicKey validatorPublicKey, + byte[] marshaledPreEvalBlock) + { + if (height < 0) + { + throw new ArgumentOutOfRangeException( + nameof(height), + "Height must be greater than or equal to 0."); + } + else if (round < 0) + { + throw new ArgumentOutOfRangeException( + nameof(round), + "Round must be greater than or equal to 0."); + } + + Height = height; + Round = round; + LastProof = lastProof; + Timestamp = timestamp; + ValidatorPublicKey = validatorPublicKey; + MarshaledPreEvalBlock = marshaledPreEvalBlock; + PreEvaluationHash = BlockMarshaler.UnmarshalPreEvaluationHash( + (Dictionary)_codec.Decode(marshaledPreEvalBlock)); + } + +#pragma warning disable SA1118 // The parameter spans multiple lines + public PreProposalMetadata(Dictionary encoded) + : this( + height: (Integer)encoded[HeightKey], + round: (Integer)encoded[RoundKey], + lastProof: encoded.TryGetValue(LastProofKey, out IValue proof) + ? (Proof?)new Proof(proof) + : null, + timestamp: DateTimeOffset.ParseExact( + (Text)encoded[TimestampKey], + TimestampFormat, + CultureInfo.InvariantCulture), + validatorPublicKey: new PublicKey( + ((Binary)encoded[ValidatorPublicKeyKey]).ByteArray), + marshaledPreEvalBlock: ((Binary)encoded[PreEvalBlockKey]).ToByteArray()) + { + } +#pragma warning restore SA1118 + + /// + /// A height of given pre-proposal values. + /// + public long Height { get; } + + /// + /// A round of given pre-proposal values. + /// + public int Round { get; } + + /// + /// A of previous round. + /// + public Proof? LastProof { get; } + + /// + /// The pre-evaluation hash of . + /// This is automatically derived from . + /// + public HashDigest PreEvaluationHash { get; } + + /// + /// The time at which the pre-proposal took place. + /// + public DateTimeOffset Timestamp { get; } + + /// + /// A of pre-proposing validator. + /// + public PublicKey ValidatorPublicKey { get; } + + /// + /// A marshaled bencodex-encoded array of pre-evaluation block. + /// + public byte[] MarshaledPreEvalBlock { get; } + + /// + /// A Bencodex-encoded value of . + /// + [JsonIgnore] + public IValue Bencoded + { + get + { + Dictionary encoded = Dictionary.Empty + .Add(HeightKey, Height) + .Add(RoundKey, Round) + .Add( + TimestampKey, + Timestamp.ToString(TimestampFormat, CultureInfo.InvariantCulture)) + .Add(ValidatorPublicKeyKey, ValidatorPublicKey.Format(compress: true)) + .Add(PreEvalBlockKey, MarshaledPreEvalBlock); + + if (LastProof is Proof proof) + { + encoded = encoded.Add(LastProofKey, proof.Bencoded); + } + + return encoded; + } + } + + public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); + + public byte[] ToByteArray() => _codec.Encode(Bencoded); + + /// + /// Signs given with given . + /// + /// A to sign. + /// Returns a signed . + public PreProposal Sign(PrivateKey signer) => + new PreProposal(this, signer.Sign(ByteArray).ToImmutableArray()); + + /// + public bool Equals(PreProposalMetadata? other) + { + return other is PreProposalMetadata metadata && + Height == metadata.Height && + Round == metadata.Round && + LastProof.Equals(metadata.LastProof) && + PreEvaluationHash.Equals(metadata.PreEvaluationHash) && + Timestamp + .ToString(TimestampFormat, CultureInfo.InvariantCulture).Equals( + metadata.Timestamp.ToString( + TimestampFormat, + CultureInfo.InvariantCulture)) && + ValidatorPublicKey.Equals(metadata.ValidatorPublicKey); + } + + /// + public override bool Equals(object? obj) => + obj is PreProposalMetadata other && Equals(other); + + /// + public override int GetHashCode() + { + return HashCode.Combine( + Height, + Round, + LastProof is Proof proof ? ByteUtil.CalculateHashCode(proof.ToByteArray()) : 0, + ByteUtil.CalculateHashCode(PreEvaluationHash.ToByteArray()), + Timestamp.ToString(TimestampFormat, CultureInfo.InvariantCulture), + ValidatorPublicKey); + } + } +} From c23fbb76817d7a4f81876290dfe1fbe084506b86 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 8 Apr 2024 10:53:18 +0900 Subject: [PATCH 31/61] feat: Add ConsensusPreProposalMsg --- .../Messages/ConsensusPreProposalMsg.cs | 69 +++++++++++++++++++ src/Libplanet.Net/Messages/MessageContent.cs | 5 ++ 2 files changed, 74 insertions(+) create mode 100644 src/Libplanet.Net/Messages/ConsensusPreProposalMsg.cs diff --git a/src/Libplanet.Net/Messages/ConsensusPreProposalMsg.cs b/src/Libplanet.Net/Messages/ConsensusPreProposalMsg.cs new file mode 100644 index 00000000000..35b7b56a633 --- /dev/null +++ b/src/Libplanet.Net/Messages/ConsensusPreProposalMsg.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using Libplanet.Consensus; +using Libplanet.Net.Consensus; +using Libplanet.Types.Blocks; + +namespace Libplanet.Net.Messages +{ + /// + /// A message class for . + /// + public class ConsensusPreProposalMsg : ConsensusMsg + { + /// + /// Initializes a new instance of the class. + /// + /// A of given height and round. + public ConsensusPreProposalMsg( + PreProposal preProposal) + : base( + preProposal.ValidatorPublicKey, + preProposal.Height, + preProposal.Round) + { + PreProposal = preProposal; + } + + /// + /// Initializes a new instance of the class + /// with marshalled message. + /// + /// A marshalled message. + public ConsensusPreProposalMsg(byte[][] dataframes) + : this(preProposal: new PreProposal(dataframes[0])) + { + } + + /// + /// A of the message. + /// + public PreProposal PreProposal { get; } + + /// + public override IEnumerable DataFrames => + new List { PreProposal.ToByteArray() }; + + /// + public override MessageType Type => MessageType.ConsensusPreProposal; + + /// + public override bool Equals(ConsensusMsg? other) + { + return other is ConsensusPreProposalMsg message && + message.PreProposal.Equals(PreProposal); + } + + /// + public override bool Equals(object? obj) + { + return obj is ConsensusMsg other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Type, PreProposal); + } + } +} diff --git a/src/Libplanet.Net/Messages/MessageContent.cs b/src/Libplanet.Net/Messages/MessageContent.cs index dce4c52d744..3f5fc10db4a 100644 --- a/src/Libplanet.Net/Messages/MessageContent.cs +++ b/src/Libplanet.Net/Messages/MessageContent.cs @@ -136,6 +136,11 @@ public enum MessageType : byte /// ConsensusProposalClaimMsg = 0x55, + /// + /// Consensus pre-proposal message. + /// + ConsensusPreProposal = 0x56, + /// /// Inventory to transfer evidence. /// From a6c212c375852a2d535bb266a83165dd16044cac Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 8 Apr 2024 10:53:50 +0900 Subject: [PATCH 32/61] feat: Add PreProposalSet --- src/Libplanet.Net/Consensus/Context.cs | 1 + src/Libplanet.Net/Consensus/PreProposalSet.cs | 89 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/Libplanet.Net/Consensus/PreProposalSet.cs diff --git a/src/Libplanet.Net/Consensus/Context.cs b/src/Libplanet.Net/Consensus/Context.cs index 63e840b4cbd..96b6059390d 100644 --- a/src/Libplanet.Net/Consensus/Context.cs +++ b/src/Libplanet.Net/Consensus/Context.cs @@ -85,6 +85,7 @@ public partial class Context : IDisposable private readonly ValidatorSet _validatorSet; private readonly Channel _messageRequests; private readonly Channel _mutationRequests; + private readonly PreProposalSet _preProposalSet; private readonly HeightVoteSet _heightVoteSet; private readonly PrivateKey _privateKey; private readonly HashSet _preVoteTimeoutFlags; diff --git a/src/Libplanet.Net/Consensus/PreProposalSet.cs b/src/Libplanet.Net/Consensus/PreProposalSet.cs new file mode 100644 index 00000000000..bb46027c05a --- /dev/null +++ b/src/Libplanet.Net/Consensus/PreProposalSet.cs @@ -0,0 +1,89 @@ +using System.Collections.Concurrent; +using System.Numerics; +using Bencodex; +using Bencodex.Types; +using Libplanet.Consensus; +using Libplanet.Crypto; +using Libplanet.Types.Blocks; +using Libplanet.Types.Consensus; + +namespace Libplanet.Net.Consensus +{ + internal class PreProposalSet + { + private static readonly Codec _codec = new Codec(); + + private ConsensusInformation _consensusInformation; + private ValidatorSet _validatorSet; + private ConcurrentDictionary _preProsals; + private int _drawSize; + private (PreEvaluationBlock, BigInteger)? _dominantPreEvalBlock; + + public PreProposalSet( + long height, int round, Proof? lastProof, ValidatorSet validatorSet, int drawSize) + : this(new ConsensusInformation(height, round, lastProof), validatorSet, drawSize) + { + } + + public PreProposalSet( + ConsensusInformation consensusInformation, ValidatorSet validatorSet, int drawSize) + { + _consensusInformation = consensusInformation; + _validatorSet = validatorSet; + _preProsals = new ConcurrentDictionary(); + _drawSize = drawSize; + _dominantPreEvalBlock = null; + } + + public long Height => _consensusInformation.Height; + + public int Round => _consensusInformation.Round; + + public Proof? LastProof => _consensusInformation.LastProof; + + public (PreEvaluationBlock, BigInteger)? DominantPreEvalBlock + => _dominantPreEvalBlock; + + public bool TryAdd(PreProposal preProposal) + { + PublicKey preProposer = preProposal.ValidatorPublicKey; + + if (!preProposal.Height.Equals(Height) + || !preProposal.Round.Equals(Round) + || !preProposal.LastProof.Equals(LastProof)) + { + return false; + } + + if (_preProsals.ContainsKey(preProposer)) + { + return false; + } + + if (_codec.Decode(preProposal.MarshaledPreEvalBlock) is Dictionary bencodedPreEvalBlock + && BlockMarshaler.UnmarshalProof(bencodedPreEvalBlock) is Proof proof + && _consensusInformation.Verify(proof, preProposer)) + { + BigInteger drawn = proof.Draw( + _drawSize, + _validatorSet.GetValidator(preProposer).Power, + _validatorSet.TotalPower); + + if (!(_dominantPreEvalBlock is { } dominantPreEvalBlock + && drawn < dominantPreEvalBlock.Item2)) + { + PreEvaluationBlock preEvalBlock + = BlockMarshaler.UnmarshalPreEvaluationBlock(bencodedPreEvalBlock); + + // Won't check PreEvaluationBlock integrity. + // Only proof integrity check helds on pre-propose step. + _dominantPreEvalBlock = (preEvalBlock, drawn); + + return _preProsals.TryAdd(preProposer, preProposal); + } + } + + return false; + } + } +} From b1d8e14416c53ba652c4af3c126b8b3a56da3c84 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 8 Apr 2024 11:05:26 +0900 Subject: [PATCH 33/61] doc: Update chagelog --- CHANGES.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d143aed26cb..0b6cd5a6b14 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,13 +27,19 @@ To be released. - Added `PublicKey.VerifyProof()` method as a proof verification. [[#VRF]] - Added `Proof` struct as a wrapper structure of proof(pi-bytes) generated by ECVRF. [[#VRF]] - - Added `LotMetadata` struct as a base payload to be proved with private key. - [[#VRF]] - - Added `Lot` struct as a proved contents to be submitted as a lot of proposer - sortition. [[#VRF]] + - Added `ConsensusInformation` struct as a base payload to be proved + with private key. [[#VRF]] - `BlockMetadata.CurrentProtocolVersion` has been changed from 5 to 6. [[#VRF]] - Added `IBlockMetadata.Proof` property. [[#VRF]] + - Added `PreProposal` class as a content of message that suggests + `PreEvaluationBlock` as a `Proposal` candidate during + `ConsensusStep.PrePropose`. [[#VRF]] + - Added `PreProposalMetadata` class as a metadata of `PreProposal`. [[#VRF]] + - Added `ConsensusStep.PrePropose`. [[#VRF]] + - Added `ConsensusPreProposalMsg` class as a `ConsensusMsg` broadcasted during + `ConsensusStep.PrePropose`. [[#VRF]] + - Added `PreProposalSet` class as a `PreProposal` selector. ### Behavioral changes From c5900839e3edad26c789d91656e880d222290c63 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 28 Jun 2024 23:59:48 +0900 Subject: [PATCH 34/61] feat: Update consensus --- src/Libplanet.Crypto/Proof.cs | 5 + src/Libplanet.Net/Consensus/ConsensusStep.cs | 5 + src/Libplanet.Net/Consensus/Context.Async.cs | 44 +++- src/Libplanet.Net/Consensus/Context.Event.cs | 8 + src/Libplanet.Net/Consensus/Context.Mutate.cs | 168 +++++++++++--- src/Libplanet.Net/Consensus/Context.cs | 27 ++- .../Consensus/ContextTimeoutOption.cs | 47 +++- src/Libplanet.Net/Consensus/HeightVoteSet.cs | 5 - .../Consensus/InvalidDominantLotException.cs | 73 ++++++ .../Consensus/InvalidLotException.cs | 72 ++++++ .../Consensus/InvalidProposalException.cs | 6 +- src/Libplanet.Net/Consensus/LotSet.cs | 177 ++++++++++++++ src/Libplanet.Net/Consensus/PreProposalSet.cs | 89 ------- .../Messages/ConsensusDominantLotMsg.cs | 65 ++++++ ...usPreProposalMsg.cs => ConsensusLotMsg.cs} | 37 ++- src/Libplanet.Net/Messages/MessageContent.cs | 15 +- .../Messages/NetMQMessageCodec.cs | 4 + src/Libplanet.Types/Blocks/BlockMarshaler.cs | 8 - src/Libplanet.Types/Consensus/ILot.cs | 21 -- src/Libplanet.Types/Consensus/ILotMetadata.cs | 31 --- src/Libplanet.Types/Consensus/Lot.cs | 103 -------- src/Libplanet.Types/Consensus/LotMetadata.cs | 122 ---------- src/Libplanet.Types/Consensus/ValidatorSet.cs | 20 -- .../Consensus/ConsensusInformation.cs | 81 +++++-- src/Libplanet/Consensus/DominantLot.cs | 132 +++++++++++ .../Consensus/DominantLotMetadata.cs | 163 +++++++++++++ src/Libplanet/Consensus/Lot.cs | 129 +++++++++++ src/Libplanet/Consensus/PreProposal.cs | 154 ------------ .../Consensus/PreProposalMetadata.cs | 219 ------------------ .../GeneratedBlockChainFixture.cs | 3 +- .../GraphTypes/BlockTypeTest.cs | 3 +- .../Indexing/BlockChainIndexTest.cs | 5 +- .../Consensus/ConsensusContextTest.cs | 4 +- .../Consensus/ContextProposerTest.cs | 2 +- test/Libplanet.Net.Tests/SwarmTest.Preload.cs | 2 +- .../Action/ActionEvaluatorTest.cs | 10 +- .../Blocks/BlockMetadataTest.cs | 7 +- .../Consensus/LotMetadataTest.cs | 30 --- test/Libplanet.Tests/Consensus/LotTest.cs | 37 --- test/Libplanet.Tests/TestUtils.cs | 3 +- 40 files changed, 1194 insertions(+), 942 deletions(-) create mode 100644 src/Libplanet.Net/Consensus/InvalidDominantLotException.cs create mode 100644 src/Libplanet.Net/Consensus/InvalidLotException.cs create mode 100644 src/Libplanet.Net/Consensus/LotSet.cs delete mode 100644 src/Libplanet.Net/Consensus/PreProposalSet.cs create mode 100644 src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs rename src/Libplanet.Net/Messages/{ConsensusPreProposalMsg.cs => ConsensusLotMsg.cs} (56%) delete mode 100644 src/Libplanet.Types/Consensus/ILot.cs delete mode 100644 src/Libplanet.Types/Consensus/ILotMetadata.cs delete mode 100644 src/Libplanet.Types/Consensus/Lot.cs delete mode 100644 src/Libplanet.Types/Consensus/LotMetadata.cs create mode 100644 src/Libplanet/Consensus/DominantLot.cs create mode 100644 src/Libplanet/Consensus/DominantLotMetadata.cs create mode 100644 src/Libplanet/Consensus/Lot.cs delete mode 100644 src/Libplanet/Consensus/PreProposal.cs delete mode 100644 src/Libplanet/Consensus/PreProposalMetadata.cs delete mode 100644 test/Libplanet.Tests/Consensus/LotMetadataTest.cs delete mode 100644 test/Libplanet.Tests/Consensus/LotTest.cs diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index 193cd60642f..273b72bdbe5 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Numerics; using Bencodex; +using Bencodex.Misc; using Bencodex.Types; using Libplanet.Common; @@ -174,6 +175,10 @@ public int CompareTo(object? obj) public override int GetHashCode() => ByteUtil.CalculateHashCode(ToByteArray()); + /// + public override string ToString() + => ByteArray.Hex(); + private static BigInteger HashToInt(ImmutableArray hash) => new BigInteger( BitConverter.IsLittleEndian diff --git a/src/Libplanet.Net/Consensus/ConsensusStep.cs b/src/Libplanet.Net/Consensus/ConsensusStep.cs index ca00ec0de1f..116dd720eb7 100644 --- a/src/Libplanet.Net/Consensus/ConsensusStep.cs +++ b/src/Libplanet.Net/Consensus/ConsensusStep.cs @@ -7,6 +7,11 @@ public enum ConsensusStep /// Default, + /// + /// Sortition step. + /// + Sortition, + /// /// Proposing Step. /// diff --git a/src/Libplanet.Net/Consensus/Context.Async.cs b/src/Libplanet.Net/Consensus/Context.Async.cs index 790339c3355..68036bdfb45 100644 --- a/src/Libplanet.Net/Consensus/Context.Async.cs +++ b/src/Libplanet.Net/Consensus/Context.Async.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Libplanet.Consensus; using Libplanet.Net.Messages; using Libplanet.Types.Blocks; using Libplanet.Types.Evidence; @@ -139,6 +140,7 @@ private async Task ConsumeMutation(CancellationToken cancellationToken) System.Action mutation = await _mutationRequests.Reader.ReadAsync(cancellationToken); var prevState = new ContextState( _heightVoteSet.Count, + _lotSet.DominantLots.Count, Height, Round, Step, @@ -146,6 +148,7 @@ private async Task ConsumeMutation(CancellationToken cancellationToken) mutation(); var nextState = new ContextState( _heightVoteSet.Count, + _lotSet.DominantLots.Count, Height, Round, Step, @@ -168,6 +171,7 @@ private async Task ConsumeMutation(CancellationToken cancellationToken) StateChanged?.Invoke(this, nextState); prevState = new ContextState( _heightVoteSet.Count, + _lotSet.DominantLots.Count, Height, Round, Step, @@ -175,6 +179,7 @@ private async Task ConsumeMutation(CancellationToken cancellationToken) ProcessGenericUponRules(); nextState = new ContextState( _heightVoteSet.Count, + _lotSet.DominantLots.Count, Height, Round, Step, @@ -189,6 +194,33 @@ private void AppendBlock(Block block) _ = Task.Run(() => _blockChain.Append(block, GetBlockCommit())); } + private async Task VoteLotAfterGathering() + { + TimeSpan delay = DelayLotGather(); + await Task.Delay(delay, _cancellationTokenSource.Token); + if (_lotSet.DominantLot is { } lot) + { + DominantLot dominantLot = new DominantLotMetadata( + Height, + Round, + lot, + DateTimeOffset.UtcNow, + _privateKey.PublicKey).Sign(_privateKey); + PublishMessage(new ConsensusDominantLotMsg(dominantLot)); + } + } + + private async Task OnTimeoutSortition(int round) + { + TimeSpan timeout = TimeoutSortition(round); + await Task.Delay(timeout, _cancellationTokenSource.Token); + _logger.Information( + "TimeoutSortition has occurred in {Timeout}. {Info}", + timeout, + ToString()); + ProduceMutation(() => ProcessTimeoutSortition(round)); + } + /// /// Schedules to be queued after /// amount of time. @@ -241,12 +273,14 @@ internal struct ContextState : IEquatable { public ContextState( int voteCount, + int dominantLotCount, long height, int round, ConsensusStep step, BlockHash? proposal) { VoteCount = voteCount; + DominantLotCount = dominantLotCount; Height = height; Round = round; Step = step; @@ -255,6 +289,8 @@ public ContextState( public int VoteCount { get; } + public int DominantLotCount { get; } + public long Height { get; } public int Round { get; } @@ -266,6 +302,7 @@ public ContextState( public bool Equals(ContextState other) { return VoteCount == other.VoteCount && + DominantLotCount == other.DominantLotCount && Round == other.Round && Step == other.Step && Proposal.Equals(other.Proposal); @@ -278,7 +315,12 @@ public override bool Equals(object? obj) public override int GetHashCode() { - return HashCode.Combine(VoteCount, Round, (int)Step, Proposal.GetHashCode()); + return HashCode.Combine( + VoteCount, + DominantLotCount, + Round, + (int)Step, + Proposal.GetHashCode()); } } } diff --git a/src/Libplanet.Net/Consensus/Context.Event.cs b/src/Libplanet.Net/Consensus/Context.Event.cs index 0ef7176515f..306911af190 100644 --- a/src/Libplanet.Net/Consensus/Context.Event.cs +++ b/src/Libplanet.Net/Consensus/Context.Event.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using Libplanet.Consensus; +using Libplanet.Crypto; using Libplanet.Net.Messages; using Libplanet.Types.Consensus; @@ -62,5 +65,10 @@ public partial class Context /// internal event EventHandler<(int Round, VoteFlag Flag, IEnumerable Votes)>? VoteSetModified; + + internal event EventHandler<( + ImmutableDictionary Lots, + ImmutableDictionary DominantLots)>? + LotSetModified; } } diff --git a/src/Libplanet.Net/Consensus/Context.Mutate.cs b/src/Libplanet.Net/Consensus/Context.Mutate.cs index 8b71cfee34b..085705265ca 100644 --- a/src/Libplanet.Net/Consensus/Context.Mutate.cs +++ b/src/Libplanet.Net/Consensus/Context.Mutate.cs @@ -1,5 +1,6 @@ using System; using Libplanet.Consensus; +using Libplanet.Crypto; using Libplanet.Net.Messages; using Libplanet.Types.Blocks; using Libplanet.Types.Consensus; @@ -27,43 +28,19 @@ private void StartRound(int round) Round = round; RoundStarted?.Invoke(this, Round); + + // Last proof is not a parameter of StartRound() + // for its update by peers developed in future. + // It's crucial, to prevent network partition. + _lotSet.SetRound(round, _lastProof); _heightVoteSet.SetRound(round); Proposal = null; - Step = ConsensusStep.Propose; - if (_validatorSet.GetProposer(Height, Round).PublicKey == _privateKey.PublicKey) - { - _logger.Information( - "Starting round {NewRound} and is a proposer.", - round, - ToString()); - if ((_validValue ?? GetValue()) is Block proposalValue) - { - Proposal proposal = new ProposalMetadata( - Height, - Round, - DateTimeOffset.UtcNow, - _privateKey.PublicKey, - _codec.Encode(proposalValue.MarshalBlock()), - _validRound).Sign(_privateKey); + Step = ConsensusStep.Sortition; - PublishMessage(new ConsensusProposalMsg(proposal)); - } - else - { - _logger.Information( - "Failed to propose a block for round {Round}.", - round); - _ = OnTimeoutPropose(Round); - } - } - else - { - _logger.Information( - "Starting round {NewRound} and is not a proposer.", - round); - _ = OnTimeoutPropose(Round); - } + PublishMessage(new ConsensusLotMsg(_lotSet.GenerateLot(_privateKey))); + _ = OnTimeoutSortition(Round); + _ = VoteLotAfterGathering(); } /// @@ -98,9 +75,46 @@ private bool AddMessage(ConsensusMsg message) message); } + if (message is ConsensusLotMsg lot) + { + _lotSet.AddLot(lot.Lot); + LotSetModified?.Invoke(this, (_lotSet.Lots, _lotSet.DominantLots)); + _logger.Debug( + "{FName}: Message: {Message} => Height: {Height}, Round: {Round}, " + + "Public key: {PublicKey}, " + + "Lot: {Lot}. (context: {Context})", + nameof(AddMessage), + lot, + lot.Height, + lot.Round, + lot.ValidatorPublicKey, + lot.Lot, + ToString()); + return true; + } + + if (message is ConsensusDominantLotMsg dominantLot) + { + _lotSet.AddDominantLot(dominantLot.DominantLot); + LotSetModified?.Invoke(this, (_lotSet.Lots, _lotSet.DominantLots)); + _logger.Debug( + "{FName}: Message: {Message} => Height: {Height}, Round: {Round}, " + + "Public key: {PublicKey}, " + + "Dominant lot: {Lot}. (context: {Context})", + nameof(AddMessage), + dominantLot, + dominantLot.Height, + dominantLot.Round, + dominantLot.ValidatorPublicKey, + dominantLot.DominantLot.Lot, + ToString()); + return true; + } + if (message is ConsensusProposalMsg proposal) { AddProposal(proposal.Proposal); + return true; } if (message is ConsensusVoteMsg voteMsg) @@ -142,6 +156,28 @@ private bool AddMessage(ConsensusMsg message) return false; } + catch (InvalidLotException ile) + { + var icme = new InvalidConsensusMessageException( + ile.Message, + message); + var msg = $"Failed to add invalid message {message} to the " + + $"{nameof(LotSet)}"; + _logger.Error(icme, msg); + ExceptionOccurred?.Invoke(this, icme); + return false; + } + catch (InvalidDominantLotException idle) + { + var icme = new InvalidConsensusMessageException( + idle.Message, + message); + var msg = $"Failed to add invalid message {message} to the " + + $"{nameof(LotSet)}"; + _logger.Error(icme, msg); + ExceptionOccurred?.Invoke(this, icme); + return false; + } catch (InvalidProposalException ipe) { var icme = new InvalidConsensusMessageException( @@ -176,8 +212,7 @@ private bool AddMessage(ConsensusMsg message) private void AddProposal(Proposal proposal) { - if (!_validatorSet.GetProposer(Height, Round) - .PublicKey.Equals(proposal.ValidatorPublicKey)) + if (!Proposer!.Equals(proposal.ValidatorPublicKey)) { throw new InvalidProposalException( $"Given proposal's proposer {proposal.ValidatorPublicKey} is not the " + @@ -236,7 +271,47 @@ private void ProcessGenericUponRules() return; } + if (Proposer is { } proposer && + Step == ConsensusStep.Sortition) + { + Step = ConsensusStep.Propose; + if (proposer == _privateKey.PublicKey) + { + _logger.Information( + "Entering propose step for round {NewRound} and is a proposer.", + Round, + ToString()); + if ((_validValue ?? GetValue()) is Block proposalValue) + { + Proposal proposal = new ProposalMetadata( + Height, + Round, + DateTimeOffset.UtcNow, + _privateKey.PublicKey, + _codec.Encode(proposalValue.MarshalBlock()), + _validRound).Sign(_privateKey); + + PublishMessage(new ConsensusProposalMsg(proposal)); + } + else + { + _logger.Information( + "Failed to propose a block for round {Round}.", + Round); + _ = OnTimeoutPropose(Round); + } + } + else + { + _logger.Information( + "Entering propose step for round {NewRound} and is not a proposer.", + Round); + _ = OnTimeoutPropose(Round); + } + } + (Block Block, int ValidRound)? propose = GetProposal(); + if (propose is { } p1 && p1.ValidRound == -1 && Step == ConsensusStep.Propose) @@ -456,11 +531,29 @@ private void ProcessHeightOrRoundUponRules(ConsensusMsg message) round, Round, ToString()); + + // TODO: This have to be modified with + // Update by peers + if (_lotSet.Maj23 is { } lotMaj23) + { + _lastProof = lotMaj23.Proof; + } + StartRound(round); return; } } + private void ProcessTimeoutSortition(int round) + { + if (round == Round && Step == ConsensusStep.Sortition) + { + Step = ConsensusStep.Propose; + TimeoutProcessed?.Invoke(this, (round, ConsensusStep.Sortition)); + ProcessTimeoutPropose(round); + } + } + /// /// A timeout mutation to run if no is received in /// and is still in step. @@ -510,6 +603,11 @@ private void ProcessTimeoutPreCommit(int round) if (round == Round) { + if (_lotSet.Maj23 is { } lotMaj23) + { + _lastProof = lotMaj23.Proof; + } + StartRound(Round + 1); TimeoutProcessed?.Invoke(this, (round, ConsensusStep.PreCommit)); } diff --git a/src/Libplanet.Net/Consensus/Context.cs b/src/Libplanet.Net/Consensus/Context.cs index 96b6059390d..a8471039d83 100644 --- a/src/Libplanet.Net/Consensus/Context.cs +++ b/src/Libplanet.Net/Consensus/Context.cs @@ -85,7 +85,7 @@ public partial class Context : IDisposable private readonly ValidatorSet _validatorSet; private readonly Channel _messageRequests; private readonly Channel _mutationRequests; - private readonly PreProposalSet _preProposalSet; + private readonly LotSet _lotSet; private readonly HeightVoteSet _heightVoteSet; private readonly PrivateKey _privateKey; private readonly HashSet _preVoteTimeoutFlags; @@ -106,6 +106,7 @@ private readonly EvidenceExceptionCollector _evidenceCollector private Block? _decision; private int _committedRound; private BlockCommit? _lastCommit; + private Proof? _lastProof; /// /// Initializes a new instance of the class. @@ -173,6 +174,7 @@ private Context( Round = round; Step = consensusStep; _lastCommit = lastCommit; + _lastProof = blockChain[height - 1].Proof; _lockedValue = null; _lockedRound = -1; _validValue = null; @@ -183,6 +185,7 @@ private Context( _codec = new Codec(); _messageRequests = Channel.CreateUnbounded(); _mutationRequests = Channel.CreateUnbounded(); + _lotSet = new LotSet(height, round, _lastProof, validators, 20); _heightVoteSet = new HeightVoteSet(height, validators); _preVoteTimeoutFlags = new HashSet(); _hasTwoThirdsPreVoteFlags = new HashSet(); @@ -217,6 +220,9 @@ private Context( public Proposal? Proposal { get; private set; } + public PublicKey? Proposer + => _lotSet.Maj23?.PublicKey; + /// public void Dispose() { @@ -411,6 +417,18 @@ private TimeSpan TimeoutPropose(long round) round * _contextTimeoutOption.ProposeMultiplier); } + private TimeSpan TimeoutSortition(long round) + { + return TimeSpan.FromSeconds( + _contextTimeoutOption.SortitionSecondBase + + round * _contextTimeoutOption.SortitionMultiplier); + } + + private TimeSpan DelayLotGather() + { + return TimeSpan.FromSeconds(_contextTimeoutOption.LotGatherSecond); + } + /// /// Creates a new to propose. /// @@ -420,8 +438,11 @@ private TimeSpan TimeoutPropose(long round) { try { - var evidence = _blockChain.GetPendingEvidence(); - Block block = _blockChain.ProposeBlock(_privateKey, _lastCommit, null, evidence); + Block block = _blockChain.ProposeBlock( + _privateKey, + _lastCommit, + _lotSet.GenerateProof(_privateKey), + _blockChain.GetPendingEvidence()); _blockChain.Store.PutBlock(block); return block; } diff --git a/src/Libplanet.Net/Consensus/ContextTimeoutOption.cs b/src/Libplanet.Net/Consensus/ContextTimeoutOption.cs index 78a5515ffd2..c742aea9f28 100644 --- a/src/Libplanet.Net/Consensus/ContextTimeoutOption.cs +++ b/src/Libplanet.Net/Consensus/ContextTimeoutOption.cs @@ -9,13 +9,41 @@ namespace Libplanet.Net.Consensus public class ContextTimeoutOption { public ContextTimeoutOption( - int proposeSecondBase = 8, + int lotGatherSecond = 1, + int sortitionSecondBase = 4, + int proposeSecondBase = 4, int preVoteSecondBase = 4, int preCommitSecondBase = 4, - int proposeMultiplier = 4, + int sortitionMultiplier = 2, + int proposeMultiplier = 2, int preVoteMultiplier = 2, int preCommitMultiplier = 2) { + if (lotGatherSecond <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(lotGatherSecond), + "LotGatherSecond must be greater than 0."); + } + + LotGatherSecond = lotGatherSecond; + + if (sortitionSecondBase <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(sortitionSecondBase), + "SortitionSecondBase must be greater than 0."); + } + + SortitionSecondBase = sortitionSecondBase; + + if (SortitionSecondBase < LotGatherSecond) + { + throw new ArgumentOutOfRangeException( + nameof(sortitionSecondBase), + "SortitionSecondBase must be greater than LotGatherSecond."); + } + if (proposeSecondBase <= 0) { throw new ArgumentOutOfRangeException( @@ -43,6 +71,15 @@ public ContextTimeoutOption( PreCommitSecondBase = preCommitSecondBase; + if (sortitionMultiplier <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(sortitionMultiplier), + "SortitionMultiplier must be greater than 0."); + } + + SortitionMultiplier = sortitionMultiplier; + if (proposeMultiplier <= 0) { throw new ArgumentOutOfRangeException( @@ -71,12 +108,18 @@ public ContextTimeoutOption( PreCommitMultiplier = preCommitMultiplier; } + public int LotGatherSecond { get; } + + public int SortitionSecondBase { get; } + public int ProposeSecondBase { get; } public int PreVoteSecondBase { get; } public int PreCommitSecondBase { get; } + public int SortitionMultiplier { get; } + public int ProposeMultiplier { get; } public int PreVoteMultiplier { get; } diff --git a/src/Libplanet.Net/Consensus/HeightVoteSet.cs b/src/Libplanet.Net/Consensus/HeightVoteSet.cs index a6eef27c7b5..e32434cb9ad 100644 --- a/src/Libplanet.Net/Consensus/HeightVoteSet.cs +++ b/src/Libplanet.Net/Consensus/HeightVoteSet.cs @@ -120,11 +120,6 @@ public void AddVote(Vote vote) PublicKey validatorKey = vote.ValidatorPublicKey; - if (validatorKey is null) - { - throw new InvalidVoteException("ValidatorKey of the vote cannot be null", vote); - } - if (!_validatorSet.ContainsPublicKey(validatorKey)) { throw new InvalidVoteException( diff --git a/src/Libplanet.Net/Consensus/InvalidDominantLotException.cs b/src/Libplanet.Net/Consensus/InvalidDominantLotException.cs new file mode 100644 index 00000000000..412b562a324 --- /dev/null +++ b/src/Libplanet.Net/Consensus/InvalidDominantLotException.cs @@ -0,0 +1,73 @@ +using System; +using System.Runtime.Serialization; +using Libplanet.Consensus; + +namespace Libplanet.Net.Consensus +{ + /// + /// An exception thrown when a received is invalid. In particular, + /// this is thrown pre-emptively before a is processed, i.e. + /// does not change the state of a in a meaningful way. + /// + [Serializable] + public class InvalidDominantLotException : Exception + { + /// + /// Initializes a new instance of class. + /// + /// The error message that explains the reason for the exception. + /// + /// The that caused this exception. + /// + /// The exception that is the cause of the current exception. + /// + public InvalidDominantLotException( + string message, + DominantLot dominantLot, + Exception innerException) + : base(message, innerException) + { + DominantLot = dominantLot; + } + + /// + /// Initializes a new instance of class. + /// + /// The error message that explains the reason for the exception. + /// + /// The that caused this exception. + /// + public InvalidDominantLotException(string message, DominantLot dominantLot) + : base(message) + { + DominantLot = dominantLot; + } + + /// + /// Initializes a new instance of the + /// class with serialized data. + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected InvalidDominantLotException(SerializationInfo info, StreamingContext context) + { + DominantLot = + info.GetValue(nameof(DominantLot), typeof(DominantLot)) as DominantLot ?? + throw new SerializationException( + $"{nameof(DominantLot)} is expected to be a non-null {nameof(DominantLot)}."); + } + + public DominantLot DominantLot { get; } + + public override void GetObjectData( + SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue(nameof(DominantLot), DominantLot); + } + } +} diff --git a/src/Libplanet.Net/Consensus/InvalidLotException.cs b/src/Libplanet.Net/Consensus/InvalidLotException.cs new file mode 100644 index 00000000000..dfc96d76f89 --- /dev/null +++ b/src/Libplanet.Net/Consensus/InvalidLotException.cs @@ -0,0 +1,72 @@ +using System; +using System.Runtime.Serialization; +using Libplanet.Consensus; + +namespace Libplanet.Net.Consensus +{ + /// + /// An exception thrown when a received is invalid. In particular, + /// this is thrown pre-emptively before a is processed, i.e. + /// does not change the state of a in a meaningful way. + /// + [Serializable] + public class InvalidLotException : Exception + { + /// + /// Initializes a new instance of class. + /// + /// The error message that explains the reason for the exception. + /// + /// The that caused this exception. + /// + /// The exception that is the cause of the current exception. + /// + public InvalidLotException( + string message, + Lot lot, + Exception innerException) + : base(message, innerException) + { + Lot = lot; + } + + /// + /// Initializes a new instance of class. + /// + /// The error message that explains the reason for the exception. + /// + /// The that caused this exception. + /// + public InvalidLotException(string message, Lot lot) + : base(message) + { + Lot = lot; + } + + /// + /// Initializes a new instance of the + /// class with serialized data. + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected InvalidLotException(SerializationInfo info, StreamingContext context) + { + Lot = info.GetValue(nameof(Lot), typeof(Lot)) as Lot? ?? + throw new SerializationException( + $"{nameof(Lot)} is expected to be a non-null {nameof(Lot)}."); + } + + public Lot Lot { get; } + + public override void GetObjectData( + SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue(nameof(Lot), Lot); + } + } +} diff --git a/src/Libplanet.Net/Consensus/InvalidProposalException.cs b/src/Libplanet.Net/Consensus/InvalidProposalException.cs index 16b8b300f12..7158a22b8c3 100644 --- a/src/Libplanet.Net/Consensus/InvalidProposalException.cs +++ b/src/Libplanet.Net/Consensus/InvalidProposalException.cs @@ -13,7 +13,7 @@ namespace Libplanet.Net.Consensus public class InvalidProposalException : Exception { /// - /// Initializes a new instance of class. + /// Initializes a new instance of class. /// /// The error message that explains the reason for the exception. /// @@ -31,7 +31,7 @@ public InvalidProposalException( } /// - /// Initializes a new instance of class. + /// Initializes a new instance of class. /// /// The error message that explains the reason for the exception. /// @@ -44,7 +44,7 @@ public InvalidProposalException(string message, Proposal proposal) } /// - /// Initializes a new instance of the + /// Initializes a new instance of the /// class with serialized data. /// /// The diff --git a/src/Libplanet.Net/Consensus/LotSet.cs b/src/Libplanet.Net/Consensus/LotSet.cs new file mode 100644 index 00000000000..dcd55062ead --- /dev/null +++ b/src/Libplanet.Net/Consensus/LotSet.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Numerics; +using Bencodex; +using Libplanet.Consensus; +using Libplanet.Crypto; +using Libplanet.Types.Consensus; +using Serilog; + +namespace Libplanet.Net.Consensus +{ + public class LotSet + { + private static readonly Codec _codec = new Codec(); + private readonly ILogger _logger; + private ConsensusInformation _consensusInformation; + private ValidatorSet _validatorSet; + private ConcurrentDictionary _lots; + private int _drawSize; + private (Lot, BigInteger)? _dominantLot; + private ConcurrentDictionary _dominantLots; + private ConcurrentDictionary _lotsPower; + + public LotSet( + long height, int round, Proof? lastProof, ValidatorSet validatorSet, int drawSize) + : this( + new ConsensusInformation(height, Math.Max(round, 0), lastProof), + validatorSet, + drawSize) + { + } + + public LotSet( + ConsensusInformation consensusInformation, ValidatorSet validatorSet, int drawSize) + { + _logger = Log + .ForContext("Tag", "Consensus") + .ForContext("SubTag", "Context") + .ForContext() + .ForContext("Source", nameof(LotSet)); + _consensusInformation = consensusInformation; + _validatorSet = validatorSet; + _lots = new ConcurrentDictionary(); + _drawSize = drawSize; + _dominantLot = null; + _dominantLots = new ConcurrentDictionary(); + _lotsPower = new ConcurrentDictionary(); + Maj23 = null; + } + + public long Height => _consensusInformation.Height; + + public int Round => _consensusInformation.Round; + + public Proof? LastProof => _consensusInformation.LastProof; + + public ConsensusInformation Consensusinformation => _consensusInformation; + + public Lot? Maj23 { get; private set; } + + public Lot? DominantLot + => _dominantLot?.Item1; + + public ImmutableDictionary Lots + => _lots.ToImmutableDictionary(); + + public ImmutableDictionary DominantLots + => _dominantLots.ToImmutableDictionary(); + + public void SetRound(int round, Proof? lastProof) + { + _consensusInformation = new ConsensusInformation(Height, Math.Max(round, 0), lastProof); + _lots.Clear(); + _dominantLots.Clear(); + } + + public Proof GenerateProof(PrivateKey privateKey) + => _consensusInformation.Prove(privateKey); + + public Lot GenerateLot(PrivateKey privateKey) + => _consensusInformation.ToLot(privateKey); + + public void AddLot(Lot lot) + { + if (_lots.ContainsKey(lot.PublicKey)) + { + throw new InvalidLotException( + $"Found duplicated {nameof(lot)} of public key {lot.PublicKey}", + lot); + } + + ValidateLot(lot); + _lots.TryAdd(lot.PublicKey, lot); + CompeteLot(lot); + } + + public void AddDominantLot(DominantLot dominantLot) + { + if (_dominantLots.ContainsKey(dominantLot.ValidatorPublicKey)) + { + throw new InvalidDominantLotException( + $"Found duplicated {nameof(dominantLot)} " + + $"of public key {dominantLot.ValidatorPublicKey}", + dominantLot); + } + + if (!_validatorSet.ContainsPublicKey(dominantLot.ValidatorPublicKey)) + { + throw new InvalidDominantLotException( + $"Public key of {nameof(dominantLot)} {dominantLot.ValidatorPublicKey} " + + $"is not in the validator set.", + dominantLot); + } + + ValidateLot(dominantLot.Lot); + _dominantLots.TryAdd(dominantLot.ValidatorPublicKey, dominantLot); + UpdateMaj23(dominantLot); + } + + public void UpdateMaj23(DominantLot dominantLot) + { + BigInteger validatorPower = _validatorSet.GetValidatorsPower( + new List() { dominantLot.ValidatorPublicKey }); + _lotsPower.AddOrUpdate( + dominantLot.Lot, validatorPower, (lot, power) => power + validatorPower); + + if (!_lotsPower.TryGetValue(dominantLot.Lot, out BigInteger lotPower)) + { + throw new NullReferenceException( + "Lot is expected to exist in the dictionary, but it does not."); + } + + if (lotPower > _validatorSet.TwoThirdsPower) + { + Maj23 = dominantLot.Lot; + } + } + + private void ValidateLot(Lot lot) + { + if (!lot.ConsensusInformation.Equals(_consensusInformation)) + { + throw new InvalidLotException( + $"{nameof(lot)} has different consensus information with " + + $"{nameof(LotSet)}. " + + $"Expected : height {Height}, round {Round}, last proof {LastProof}" + + $"Actual : height {lot.ConsensusInformation.Height}, " + + $"round {lot.ConsensusInformation.Round}, " + + $"last proof {lot.ConsensusInformation.LastProof}", + lot); + } + + if (!_validatorSet.ContainsPublicKey(lot.PublicKey)) + { + throw new InvalidLotException( + $"Public key of {nameof(lot)} {lot.PublicKey} is not in the validator set.", + lot); + } + } + + private void CompeteLot(Lot lot) + { + BigInteger drawn = lot.Proof.Draw( + _drawSize, + _validatorSet.GetValidator(lot.PublicKey).Power, + _validatorSet.TotalPower); + + if (!(_dominantLot is { } dominantLot + && drawn < dominantLot.Item2)) + { + _dominantLot = (lot, drawn); + } + } + } +} diff --git a/src/Libplanet.Net/Consensus/PreProposalSet.cs b/src/Libplanet.Net/Consensus/PreProposalSet.cs deleted file mode 100644 index bb46027c05a..00000000000 --- a/src/Libplanet.Net/Consensus/PreProposalSet.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Collections.Concurrent; -using System.Numerics; -using Bencodex; -using Bencodex.Types; -using Libplanet.Consensus; -using Libplanet.Crypto; -using Libplanet.Types.Blocks; -using Libplanet.Types.Consensus; - -namespace Libplanet.Net.Consensus -{ - internal class PreProposalSet - { - private static readonly Codec _codec = new Codec(); - - private ConsensusInformation _consensusInformation; - private ValidatorSet _validatorSet; - private ConcurrentDictionary _preProsals; - private int _drawSize; - private (PreEvaluationBlock, BigInteger)? _dominantPreEvalBlock; - - public PreProposalSet( - long height, int round, Proof? lastProof, ValidatorSet validatorSet, int drawSize) - : this(new ConsensusInformation(height, round, lastProof), validatorSet, drawSize) - { - } - - public PreProposalSet( - ConsensusInformation consensusInformation, ValidatorSet validatorSet, int drawSize) - { - _consensusInformation = consensusInformation; - _validatorSet = validatorSet; - _preProsals = new ConcurrentDictionary(); - _drawSize = drawSize; - _dominantPreEvalBlock = null; - } - - public long Height => _consensusInformation.Height; - - public int Round => _consensusInformation.Round; - - public Proof? LastProof => _consensusInformation.LastProof; - - public (PreEvaluationBlock, BigInteger)? DominantPreEvalBlock - => _dominantPreEvalBlock; - - public bool TryAdd(PreProposal preProposal) - { - PublicKey preProposer = preProposal.ValidatorPublicKey; - - if (!preProposal.Height.Equals(Height) - || !preProposal.Round.Equals(Round) - || !preProposal.LastProof.Equals(LastProof)) - { - return false; - } - - if (_preProsals.ContainsKey(preProposer)) - { - return false; - } - - if (_codec.Decode(preProposal.MarshaledPreEvalBlock) is Dictionary bencodedPreEvalBlock - && BlockMarshaler.UnmarshalProof(bencodedPreEvalBlock) is Proof proof - && _consensusInformation.Verify(proof, preProposer)) - { - BigInteger drawn = proof.Draw( - _drawSize, - _validatorSet.GetValidator(preProposer).Power, - _validatorSet.TotalPower); - - if (!(_dominantPreEvalBlock is { } dominantPreEvalBlock - && drawn < dominantPreEvalBlock.Item2)) - { - PreEvaluationBlock preEvalBlock - = BlockMarshaler.UnmarshalPreEvaluationBlock(bencodedPreEvalBlock); - - // Won't check PreEvaluationBlock integrity. - // Only proof integrity check helds on pre-propose step. - _dominantPreEvalBlock = (preEvalBlock, drawn); - - return _preProsals.TryAdd(preProposer, preProposal); - } - } - - return false; - } - } -} diff --git a/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs b/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs new file mode 100644 index 00000000000..068163424dc --- /dev/null +++ b/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using Libplanet.Consensus; +using Libplanet.Net.Consensus; + +namespace Libplanet.Net.Messages +{ + /// + /// A message class for . + /// + public class ConsensusDominantLotMsg : ConsensusMsg + { + /// + /// Initializes a new instance of the class. + /// + /// A + /// that represents dominant of given height and round. + public ConsensusDominantLotMsg(DominantLot dominantLot) + : base(dominantLot.ValidatorPublicKey, dominantLot.Height, dominantLot.Round) + { + DominantLot = dominantLot; + } + + /// + /// Initializes a new instance of the class + /// with marshalled message. + /// + /// A marshalled message. + public ConsensusDominantLotMsg(byte[][] dataframes) + : this(dominantLot: new DominantLot(dataframes[0])) + { + } + + /// + /// A of the message. + /// + public DominantLot DominantLot { get; } + + /// + public override IEnumerable DataFrames => + new List { DominantLot.ToByteArray() }; + + /// + public override MessageType Type => MessageType.ConsensusDominantLot; + + /// + public override bool Equals(ConsensusMsg? other) + { + return other is ConsensusDominantLotMsg message + && message.DominantLot.Equals(DominantLot); + } + + /// + public override bool Equals(object? obj) + { + return obj is ConsensusMsg other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Type, DominantLot); + } + } +} diff --git a/src/Libplanet.Net/Messages/ConsensusPreProposalMsg.cs b/src/Libplanet.Net/Messages/ConsensusLotMsg.cs similarity index 56% rename from src/Libplanet.Net/Messages/ConsensusPreProposalMsg.cs rename to src/Libplanet.Net/Messages/ConsensusLotMsg.cs index 35b7b56a633..e558c2b7063 100644 --- a/src/Libplanet.Net/Messages/ConsensusPreProposalMsg.cs +++ b/src/Libplanet.Net/Messages/ConsensusLotMsg.cs @@ -2,56 +2,51 @@ using System.Collections.Generic; using Libplanet.Consensus; using Libplanet.Net.Consensus; -using Libplanet.Types.Blocks; namespace Libplanet.Net.Messages { /// /// A message class for . /// - public class ConsensusPreProposalMsg : ConsensusMsg + public class ConsensusLotMsg : ConsensusMsg { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// A of given height and round. - public ConsensusPreProposalMsg( - PreProposal preProposal) - : base( - preProposal.ValidatorPublicKey, - preProposal.Height, - preProposal.Round) + /// A + /// of given height and round. + public ConsensusLotMsg(Lot lot) + : base(lot.PublicKey, lot.ConsensusInformation.Height, lot.ConsensusInformation.Round) { - PreProposal = preProposal; + Lot = lot; } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// with marshalled message. /// /// A marshalled message. - public ConsensusPreProposalMsg(byte[][] dataframes) - : this(preProposal: new PreProposal(dataframes[0])) + public ConsensusLotMsg(byte[][] dataframes) + : this(lot: new Lot(dataframes[0])) { } /// - /// A of the message. + /// A of the message. /// - public PreProposal PreProposal { get; } + public Lot Lot { get; } /// public override IEnumerable DataFrames => - new List { PreProposal.ToByteArray() }; + new List { Lot.ToByteArray() }; /// - public override MessageType Type => MessageType.ConsensusPreProposal; + public override MessageType Type => MessageType.ConsensusLot; /// public override bool Equals(ConsensusMsg? other) { - return other is ConsensusPreProposalMsg message && - message.PreProposal.Equals(PreProposal); + return other is ConsensusLotMsg message && message.Lot.Equals(Lot); } /// @@ -63,7 +58,7 @@ public override bool Equals(object? obj) /// public override int GetHashCode() { - return HashCode.Combine(Type, PreProposal); + return HashCode.Combine(Type, Lot); } } } diff --git a/src/Libplanet.Net/Messages/MessageContent.cs b/src/Libplanet.Net/Messages/MessageContent.cs index 3f5fc10db4a..10745d741c2 100644 --- a/src/Libplanet.Net/Messages/MessageContent.cs +++ b/src/Libplanet.Net/Messages/MessageContent.cs @@ -137,24 +137,29 @@ public enum MessageType : byte ConsensusProposalClaimMsg = 0x55, /// - /// Consensus pre-proposal message. + /// Consensus message that sends a lot to be drawn. /// - ConsensusPreProposal = 0x56, + ConsensusLot = 0x56, + + /// + /// Consensus message that sends a dominant lot to vote for proposer. + /// + ConsensusDominantLot = 0x57, /// /// Inventory to transfer evidence. /// - EvidenceIds = 0x56, + EvidenceIds = 0x58, /// /// Request to query evidence. /// - GetEvidence = 0x57, + GetEvidence = 0x59, /// /// Message containing serialized evidence. /// - Evidence = 0x58, + Evidence = 0x60, } /// diff --git a/src/Libplanet.Net/Messages/NetMQMessageCodec.cs b/src/Libplanet.Net/Messages/NetMQMessageCodec.cs index 5fa79fc817f..9b7d0fab05d 100644 --- a/src/Libplanet.Net/Messages/NetMQMessageCodec.cs +++ b/src/Libplanet.Net/Messages/NetMQMessageCodec.cs @@ -229,6 +229,10 @@ internal static MessageContent CreateMessage( return new ConsensusVoteSetBitsMsg(dataframes); case MessageContent.MessageType.ConsensusProposalClaimMsg: return new ConsensusProposalClaimMsg(dataframes); + case MessageContent.MessageType.ConsensusLot: + return new ConsensusLotMsg(dataframes); + case MessageContent.MessageType.ConsensusDominantLot: + return new ConsensusDominantLotMsg(dataframes); default: throw new InvalidCastException($"Given type {type} is not a valid message."); } diff --git a/src/Libplanet.Types/Blocks/BlockMarshaler.cs b/src/Libplanet.Types/Blocks/BlockMarshaler.cs index 40294302f7a..fed099bcff7 100644 --- a/src/Libplanet.Types/Blocks/BlockMarshaler.cs +++ b/src/Libplanet.Types/Blocks/BlockMarshaler.cs @@ -312,14 +312,6 @@ public static Block UnmarshalBlock(Dictionary marshaled) return new Block(header, txs, evidence); } - public static PreEvaluationBlock UnmarshalPreEvaluationBlock(Dictionary marshaled) - { - PreEvaluationBlockHeader header - = UnmarshalPreEvaluationBlockHeader((Dictionary)marshaled[PreEvalHeaderKey]); - IReadOnlyList txs = UnmarshalBlockTransactions(marshaled); - return new PreEvaluationBlock(header, txs); - } - public static Proof? UnmarshalProof(Dictionary marshaled) => marshaled.ContainsKey(ProofKey) ? new Proof(marshaled[ProofKey]) diff --git a/src/Libplanet.Types/Consensus/ILot.cs b/src/Libplanet.Types/Consensus/ILot.cs deleted file mode 100644 index 801324ceafc..00000000000 --- a/src/Libplanet.Types/Consensus/ILot.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Libplanet.Crypto; - -namespace Libplanet.Types.Consensus -{ - /// - /// An for the . - /// - public interface ILot : ILotMetadata - { - /// - /// that proved an . - /// can be verified by it. - /// - public PublicKey PublicKey { get; } - - /// - /// that has been proved by . - /// - public Proof Proof { get; } - } -} diff --git a/src/Libplanet.Types/Consensus/ILotMetadata.cs b/src/Libplanet.Types/Consensus/ILotMetadata.cs deleted file mode 100644 index d1c78bb50c8..00000000000 --- a/src/Libplanet.Types/Consensus/ILotMetadata.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Libplanet.Crypto; - -namespace Libplanet.Types.Consensus -{ - /// - /// An for the . - /// - public interface ILotMetadata - { - /// - /// Height of the consensus where - /// participate in the draw of the proposer. - /// - long Height { get; } - - /// - /// Round of the consensus where - /// participate in the draw of the proposer. - /// - int Round { get; } - - /// - /// that has been decided on the previous . - /// if current is 0, it indicates - /// from the - /// of the proposing. - /// - /// - Proof? LastProof { get; } - } -} diff --git a/src/Libplanet.Types/Consensus/Lot.cs b/src/Libplanet.Types/Consensus/Lot.cs deleted file mode 100644 index 38d3e0fba0a..00000000000 --- a/src/Libplanet.Types/Consensus/Lot.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json; -using System.Text.Json.Serialization; -using Bencodex; -using Bencodex.Misc; -using Bencodex.Types; -using Libplanet.Crypto; - -namespace Libplanet.Types.Consensus -{ - public readonly struct Lot : ILot, IEquatable, IBencodable - { - private static readonly Binary PublicKeyKey = new Binary(new byte[] { 0x70 }); // 'p' - private static readonly Binary ProofKey = new Binary(new byte[] { 0x50 }); // 'P' - private static readonly Codec _codec = new Codec(); - private readonly LotMetadata _metadata; - - public Lot( - LotMetadata metadata, - PublicKey publicKey, - Proof proof) - { - if (!publicKey.VerifyProof(_codec.Encode(metadata.Bencoded), proof)) - { - throw new ArgumentException( - $"Given {nameof(proof)} is invalid.", - nameof(proof)); - } - - _metadata = metadata; - PublicKey = publicKey; - Proof = proof; - } - - public Lot(IValue bencoded) - : this(bencoded is Dictionary dict - ? dict - : throw new ArgumentException( - $"Given {nameof(bencoded)} must be of type " + - $"{typeof(Dictionary)}: {bencoded.GetType()}", - nameof(bencoded))) - { - } - - private Lot(Dictionary encoded) - : this( - new LotMetadata(encoded), - new PublicKey(((Binary)encoded[PublicKeyKey]).ByteArray), - new Proof(encoded[ProofKey])) - { - } - - /// - public long Height => _metadata.Height; - - /// - public int Round => _metadata.Round; - - /// - public Proof? LastProof => _metadata.LastProof; - - /// - public PublicKey PublicKey { get; } - - /// - public Proof Proof { get; } - - [JsonIgnore] - public IValue Bencoded - => ((Dictionary)_metadata.Bencoded) - .Add(PublicKeyKey, PublicKey.Format(true)) - .Add(ProofKey, Proof.ByteArray); - - /// - public bool Equals(Lot other) - => _metadata.Equals(other._metadata) - && Proof.Equals(other.Proof); - - /// - public override bool Equals(object? obj) - => obj is Lot otherLot && Equals(otherLot); - - /// - public override int GetHashCode() - => HashCode.Combine( - _metadata.GetHashCode(), - Proof.GetHashCode()); - - /// - public override string ToString() - { - var dict = new Dictionary - { - { "public_key", PublicKey.ToString() }, - { "height", Height }, - { "round", Round }, - { "lastProof", LastProof?.ByteArray.Hex() ?? "Empty" }, - }; - return JsonSerializer.Serialize(dict); - } - } -} diff --git a/src/Libplanet.Types/Consensus/LotMetadata.cs b/src/Libplanet.Types/Consensus/LotMetadata.cs deleted file mode 100644 index d88e97b6c96..00000000000 --- a/src/Libplanet.Types/Consensus/LotMetadata.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Text.Json.Serialization; -using Bencodex; -using Bencodex.Types; -using Libplanet.Crypto; - -namespace Libplanet.Types.Consensus -{ - /// - /// Metadata of the . - /// - public readonly struct LotMetadata : ILotMetadata, IEquatable, IBencodable - { - private static readonly Binary HeightKey = - new Binary(new byte[] { 0x48 }); // 'H' - - private static readonly Binary RoundKey = - new Binary(new byte[] { 0x52 }); // 'R' - - private static readonly Binary LastProofKey = - new Binary(new byte[] { 0x4c }); // 'L' - - private static readonly Codec _codec = new Codec(); - - public LotMetadata( - long height, - int round, - Proof? lastProof) - { - if (height < 0) - { - throw new ArgumentException( - $"Given {nameof(height)} cannot be negative: {height}"); - } - else if (round < 0) - { - throw new ArgumentException( - $"Given {nameof(round)} cannot be negative: {round}"); - } - - Height = height; - Round = round; - LastProof = lastProof; - } - - public LotMetadata(IValue bencoded) - : this(bencoded is Dictionary dict - ? dict - : throw new ArgumentException( - $"Given {nameof(bencoded)} must be of type " + - $"{typeof(Dictionary)}: {bencoded.GetType()}", - nameof(bencoded))) - { - } - - private LotMetadata(Dictionary bencoded) - : this( - (Integer)bencoded[HeightKey], - (Integer)bencoded[RoundKey], - bencoded.TryGetValue(LastProofKey, out IValue p) ? (Proof?)new Proof(p) : null) - { - } - - /// - public long Height { get; } - - /// - public int Round { get; } - - /// - public Proof? LastProof { get; } - - /// - [JsonIgnore] - public IValue Bencoded - { - get - { - Dictionary bencoded = Dictionary.Empty - .Add(HeightKey, Height) - .Add(RoundKey, Round); - - if (LastProof is Proof lastProof) - { - bencoded = bencoded.Add(LastProofKey, lastProof.ByteArray); - } - - return bencoded; - } - } - - /// - /// Proves a to create a - /// using given . - /// - /// The to prove the data with. - /// A with a . - /// - /// - public Lot Prove(PrivateKey prover) - => new Lot(this, prover.PublicKey, prover.Prove(_codec.Encode(Bencoded))); - - /// - public bool Equals(LotMetadata other) - => Height == other.Height - && Round == other.Round - && LastProof.Equals(other.LastProof); - - /// - public override bool Equals(object? obj) => - obj is LotMetadata other && Equals(other); - - /// - public override int GetHashCode() - { - return HashCode.Combine( - Height, - Round, - LastProof); - } - } -} diff --git a/src/Libplanet.Types/Consensus/ValidatorSet.cs b/src/Libplanet.Types/Consensus/ValidatorSet.cs index 849571ad7a2..53817e8cf17 100644 --- a/src/Libplanet.Types/Consensus/ValidatorSet.cs +++ b/src/Libplanet.Types/Consensus/ValidatorSet.cs @@ -236,26 +236,6 @@ public override int GetHashCode() return hashCode; } - /// - /// Gets the proposer for a given context. - /// - /// The height of the context under consideration. - /// The round of the context under consideration. - /// A deterministically chosen from - /// , , and . - /// - /// Thrown when - /// is empty. - public Validator GetProposer(long height, int round) - { - // FIXME: Empty Validators should not be allowed. Preventing during construction - // would require heavier refactoring of BlockPolicy. - return Validators.IsEmpty - ? throw new InvalidOperationException( - "Cannot select a proposer from an empty list of validators.") - : Validators[(int)((height + round) % Validators.Count)]; - } - /// /// Checks whether is ordered /// by of each , diff --git a/src/Libplanet/Consensus/ConsensusInformation.cs b/src/Libplanet/Consensus/ConsensusInformation.cs index eb253ee4c73..941b2296aca 100644 --- a/src/Libplanet/Consensus/ConsensusInformation.cs +++ b/src/Libplanet/Consensus/ConsensusInformation.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using Bencodex; using Bencodex.Types; using Libplanet.Crypto; @@ -8,8 +10,8 @@ namespace Libplanet.Consensus { /// /// Consensus round information used as payload of the - /// in the - /// . + /// in the + /// . /// public readonly struct ConsensusInformation : IEquatable { @@ -29,13 +31,12 @@ namespace Libplanet.Consensus /// /// Height of the consensus where /// participate in the draw of the - /// . + /// . /// Round of the consensus where - /// participate in the draw of the . + /// participate in the draw of the . /// that has been decided on the previous round. - /// if current is 0, it indicates - /// from the - /// of the preproposing. + /// if deciding is 0, it indicates + /// from the previous . /// /// Thrown when or /// is negative. @@ -61,16 +62,43 @@ public ConsensusInformation( Encoded = Encode(); } + public ConsensusInformation(IReadOnlyList encoded) + : this(_codec.Decode(encoded.ToArray())) + { + } + + public ConsensusInformation(IValue bencoded) + : this(bencoded is Dictionary dict + ? dict + : throw new ArgumentException( + $"Given {nameof(bencoded)} must be of type " + + $"{typeof(Dictionary)}: {bencoded.GetType()}", + nameof(bencoded))) + { + } + +#pragma warning disable SA1118 // Parameter should not span multiple lines + private ConsensusInformation(Dictionary bencoded) + : this( + (Integer)bencoded[HeightKey], + (Integer)bencoded[RoundKey], + bencoded.ContainsKey(LastProofKey) + ? (Proof?)new Proof(bencoded[LastProofKey]) + : null) + { + } +#pragma warning restore SA1118 // Parameter should not span multiple lines + /// /// Height of the consensus where /// participate in the draw of the - /// . + /// . /// public long Height { get; } /// /// Round of the consensus where - /// participate in the draw of the . + /// participate in the draw of the . /// public int Round { get; } @@ -92,10 +120,10 @@ public ConsensusInformation( /// /// Height of the consensus where /// participate in the draw of the - /// . + /// . /// /// Round of the consensus where - /// participate in the draw of the . + /// participate in the draw of the . /// /// that has been decided on the previous round. /// @@ -112,10 +140,10 @@ public static Proof Prove(long height, int round, Proof? lastProof, PrivateKey p /// /// Height of the consensus where /// participate in the draw of the - /// . + /// . /// /// Round of the consensus where - /// participate in the draw of the . + /// participate in the draw of the . /// /// that has been decided on the previous round. /// @@ -130,6 +158,20 @@ public static bool Verify( long height, int round, Proof? lastProof, Proof proof, PublicKey verifier) => new ConsensusInformation(height, round, lastProof).Verify(proof, verifier); + /// + /// Generate a with and + /// . + /// + /// + /// to prove with. + /// + /// + /// that contains generated by + /// , of + /// and . + public Lot ToLot(PrivateKey prover) + => new Lot(Prove(prover), prover.PublicKey, this); + /// /// Generate a with and /// . @@ -170,14 +212,19 @@ public override bool Equals(object? obj) => /// public override int GetHashCode() - { - return HashCode.Combine( + => HashCode.Combine( Height, Round, LastProof); - } + + public override string ToString() + => $"{nameof(ConsensusInformation)} " + + $": Height {Height}, Round {Round}, LastProof {LastProof}"; private ImmutableArray Encode() + => _codec.Encode(Bencode()).ToImmutableArray(); + + private IValue Bencode() { Dictionary bencoded = Dictionary.Empty .Add(HeightKey, Height) @@ -188,7 +235,7 @@ private ImmutableArray Encode() bencoded = bencoded.Add(LastProofKey, lastProof.ByteArray); } - return _codec.Encode(bencoded).ToImmutableArray(); + return bencoded; } } } diff --git a/src/Libplanet/Consensus/DominantLot.cs b/src/Libplanet/Consensus/DominantLot.cs new file mode 100644 index 00000000000..d04d2ded149 --- /dev/null +++ b/src/Libplanet/Consensus/DominantLot.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Immutable; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text.Json.Serialization; +using Bencodex; +using Bencodex.Types; +using Libplanet.Common; +using Libplanet.Crypto; + +namespace Libplanet.Consensus +{ + /// + /// Represents a drawn for consensus. + /// This can be interpreted as a vote for proposer. + /// + public class DominantLot : IEquatable + { + private static readonly Binary SignatureKey = new Binary(new byte[] { 0x53 }); // 'S' + private static readonly Codec _codec = new Codec(); + + private readonly DominantLotMetadata _dominantLotMetadata; + + /// + /// Instantiates a with given + /// and its . + /// + /// A to vote. + /// A signature signed with . + /// + /// Thrown if given is + /// empty. + /// Thrown if given is + /// invalid and cannot be verified with . + public DominantLot(DominantLotMetadata dominantLotMetadata, ImmutableArray signature) + { + _dominantLotMetadata = dominantLotMetadata; + Signature = signature; + + if (signature.IsDefaultOrEmpty) + { + throw new ArgumentNullException( + nameof(signature), + "Signature cannot be null or empty."); + } + + if (!ValidatorPublicKey.Verify( + _dominantLotMetadata.ByteArray.ToImmutableArray(), + Signature)) + { + throw new ArgumentException("Signature is invalid.", nameof(signature)); + } + } + + public DominantLot(byte[] marshaled) + : this((Dictionary)_codec.Decode(marshaled)) + { + } + +#pragma warning disable SA1118 // The parameter spans multiple lines + public DominantLot(Dictionary encoded) + : this( + new DominantLotMetadata(encoded), + encoded.ContainsKey(SignatureKey) + ? ((Binary)encoded[SignatureKey]).ByteArray + : ImmutableArray.Empty) + { + } +#pragma warning restore SA1118 + + /// + public long Height => _dominantLotMetadata.Height; + + /// + public int Round => _dominantLotMetadata.Round; + + /// + public Lot Lot => _dominantLotMetadata.Lot; + + /// + public DateTimeOffset Timestamp => _dominantLotMetadata.Timestamp; + + /// + public PublicKey ValidatorPublicKey => _dominantLotMetadata.ValidatorPublicKey; + + /// + /// A signature that signed with . + /// + public ImmutableArray Signature { get; } + + /// + /// A Bencodex-encoded value of . + /// + [JsonIgnore] + public Dictionary Encoded => + !Signature.IsEmpty + ? _dominantLotMetadata.Encoded.Add(SignatureKey, Signature) + : _dominantLotMetadata.Encoded; + + /// + /// encoded data. + /// + public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); + + public byte[] ToByteArray() => _codec.Encode(Encoded); + + /// + [Pure] + public bool Equals(DominantLot? other) + { + return other is { } drawn && + _dominantLotMetadata.Equals(drawn._dominantLotMetadata) && + Signature.SequenceEqual(drawn.Signature); + } + + /// + [Pure] + public override bool Equals(object? obj) + { + return obj is Maj23 other && Equals(other); + } + + /// + [Pure] + public override int GetHashCode() + { + return HashCode.Combine( + _dominantLotMetadata.GetHashCode(), + ByteUtil.CalculateHashCode(Signature.ToArray())); + } + } +} diff --git a/src/Libplanet/Consensus/DominantLotMetadata.cs b/src/Libplanet/Consensus/DominantLotMetadata.cs new file mode 100644 index 00000000000..9741f26d57f --- /dev/null +++ b/src/Libplanet/Consensus/DominantLotMetadata.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Immutable; +using System.Globalization; +using System.Text.Json.Serialization; +using Bencodex; +using Bencodex.Types; +using Libplanet.Crypto; + +namespace Libplanet.Consensus +{ + public class DominantLotMetadata : IEquatable + { + private const string TimestampFormat = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; + + private static readonly Binary HeightKey = + new Binary(new byte[] { 0x48 }); // 'H' + + private static readonly Binary RoundKey = + new Binary(new byte[] { 0x52 }); // 'R' + + private static readonly Binary LotKey = + new Binary(new byte[] { 0x4C }); // 'L' + + private static readonly Binary TimestampKey = + new Binary(new byte[] { 0x74 }); // 't' + + private static readonly Binary ValidatorPublicKeyKey = + new Binary(new byte[] { 0x50 }); // 'P' + + private static readonly Codec _codec = new Codec(); + + public DominantLotMetadata( + long height, + int round, + Lot lot, + DateTimeOffset timestamp, + PublicKey validatorPublicKey) + { + if (height < 0) + { + throw new ArgumentOutOfRangeException( + nameof(height), + "Height must be greater than or equal to 0."); + } + else if (round < 0) + { + throw new ArgumentOutOfRangeException( + nameof(round), + "Round must be greater than or equal to 0."); + } + + Height = height; + Round = round; + Lot = lot; + Timestamp = timestamp; + ValidatorPublicKey = validatorPublicKey; + } + +#pragma warning disable SA1118 // The parameter spans multiple lines + public DominantLotMetadata(Dictionary encoded) + : this( + height: (Integer)encoded[HeightKey], + round: (Integer)encoded[RoundKey], + lot: new Lot(encoded[LotKey]), + timestamp: DateTimeOffset.ParseExact( + (Text)encoded[TimestampKey], + TimestampFormat, + CultureInfo.InvariantCulture), + validatorPublicKey: new PublicKey( + ((Binary)encoded[ValidatorPublicKeyKey]).ByteArray)) + { + } +#pragma warning restore SA1118 + + /// + /// A height of consensus. + /// + public long Height { get; } + + /// + /// A round of consensus. + /// + public int Round { get; } + + /// + /// The of vote claim. + /// + public Lot Lot { get; } + + /// + /// The time at which the claim took place. + /// + public DateTimeOffset Timestamp { get; } + + /// + /// A of claimant. + /// + public PublicKey ValidatorPublicKey { get; } + + /// + /// A Bencodex-encoded value of . + /// + [JsonIgnore] + public Dictionary Encoded + { + get + { + Dictionary encoded = Bencodex.Types.Dictionary.Empty + .Add(HeightKey, Height) + .Add(RoundKey, Round) + .Add(LotKey, Lot.Bencoded) + .Add( + TimestampKey, + Timestamp.ToString(TimestampFormat, CultureInfo.InvariantCulture)) + .Add(ValidatorPublicKeyKey, ValidatorPublicKey.Format(compress: true)); + + return encoded; + } + } + + public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); + + public byte[] ToByteArray() => _codec.Encode(Encoded); + + /// + /// Signs given with given . + /// + /// A to sign. + /// Returns a signed . + public DominantLot Sign(PrivateKey signer) => + new DominantLot(this, signer.Sign(ByteArray).ToImmutableArray()); + + /// + public bool Equals(DominantLotMetadata? other) + { + return other is { } metadata && + Height == metadata.Height && + Round == metadata.Round && + Lot.Equals(metadata.Lot) && + Timestamp + .ToString(TimestampFormat, CultureInfo.InvariantCulture).Equals( + metadata.Timestamp.ToString( + TimestampFormat, + CultureInfo.InvariantCulture)) && + ValidatorPublicKey.Equals(metadata.ValidatorPublicKey); + } + + /// + public override bool Equals(object? obj) => + obj is DominantLotMetadata other && Equals(other); + + /// + public override int GetHashCode() + { + return HashCode.Combine( + Height, + Round, + Lot, + Timestamp.ToString(TimestampFormat, CultureInfo.InvariantCulture), + ValidatorPublicKey); + } + } +} diff --git a/src/Libplanet/Consensus/Lot.cs b/src/Libplanet/Consensus/Lot.cs new file mode 100644 index 00000000000..ec0f0631dba --- /dev/null +++ b/src/Libplanet/Consensus/Lot.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Bencodex; +using Bencodex.Misc; +using Bencodex.Types; +using Libplanet.Crypto; + +namespace Libplanet.Consensus +{ + public readonly struct Lot : IEquatable, IBencodable + { + private static readonly Binary ProofKey + = new Binary(new byte[] { 0x50 }); // 'P' + + private static readonly Binary PublicKeyKey + = new Binary(new byte[] { 0x70 }); // 'p' + + private static readonly Binary ConsensusInformationKey + = new Binary(new byte[] { 0x43 }); // 'C' + + private static readonly Codec _codec = new Codec(); + + public Lot( + Proof proof, + PublicKey publicKey, + ConsensusInformation consensusInformation) + { + if (!consensusInformation.Verify(proof, publicKey)) + { + throw new ArgumentException( + $"Given {nameof(proof)} is invalid.", + nameof(proof)); + } + + Proof = proof; + PublicKey = publicKey; + ConsensusInformation = consensusInformation; + } + + public Lot(IReadOnlyList encoded) + : this(_codec.Decode(encoded.ToArray())) + { + } + + public Lot(IValue bencoded) + : this(bencoded is Dictionary dict + ? dict + : throw new ArgumentException( + $"Given {nameof(bencoded)} must be of type " + + $"{typeof(Dictionary)}: {bencoded.GetType()}", + nameof(bencoded))) + { + } + + private Lot(Dictionary bencoded) + : this( + new Proof(bencoded[ProofKey]), + new PublicKey(((Binary)bencoded[PublicKeyKey]).ByteArray), + new ConsensusInformation(((Binary)bencoded[ConsensusInformationKey]).ByteArray)) + { + } + + /// + /// can be verified with and + /// . + /// + public Proof Proof { get; } + + /// + /// that proved an . + /// can be verified by it. + /// + public PublicKey PublicKey { get; } + + /// + /// that has been proved by . + /// + public ConsensusInformation ConsensusInformation { get; } + + [JsonIgnore] + public IValue Bencoded + => Dictionary.Empty + .Add(ProofKey, Proof.ByteArray) + .Add(PublicKeyKey, PublicKey.Format(true)) + .Add(ConsensusInformationKey, ConsensusInformation.Encoded); + + /// + /// encoded data. + /// + public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); + + public byte[] ToByteArray() => _codec.Encode(Bencoded); + + /// + public bool Equals(Lot other) + => Proof.Equals(other.Proof) + && PublicKey.Equals(other.PublicKey) + && ConsensusInformation.Equals(other.ConsensusInformation); + + /// + public override bool Equals(object? obj) + => obj is Lot otherLot && Equals(otherLot); + + /// + public override int GetHashCode() + => HashCode.Combine( + Proof.GetHashCode(), + PublicKey.GetHashCode(), + ConsensusInformation.GetHashCode()); + + /// + public override string ToString() + { + var dict = new Dictionary + { + { "proof", Proof.ByteArray.Hex() }, + { "height", ConsensusInformation.Height }, + { "round", ConsensusInformation.Round }, + { "lastProof", ConsensusInformation.LastProof?.ByteArray.Hex() ?? "Empty" }, + { "public_key", PublicKey.ToString() }, + }; + return JsonSerializer.Serialize(dict); + } + } +} diff --git a/src/Libplanet/Consensus/PreProposal.cs b/src/Libplanet/Consensus/PreProposal.cs deleted file mode 100644 index 9fda50594b4..00000000000 --- a/src/Libplanet/Consensus/PreProposal.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Security.Cryptography; -using System.Text.Json.Serialization; -using Bencodex; -using Bencodex.Types; -using Libplanet.Common; -using Libplanet.Crypto; -using Libplanet.Types.Consensus; - -namespace Libplanet.Consensus -{ - /// - /// Represents a from a validator for consensus. - /// It contains an essential information to pre-propose - /// a pre-evaluation block for a consensus in a height, a round, a last proof, - /// and its signature to verify. The signature is verified in - /// constructor, so the instance of should be valid. - /// - public class PreProposal : IEquatable - { - private static readonly Binary SignatureKey = new Binary(new byte[] { 0x53 }); // 'S' - private static readonly Codec _codec = new Codec(); - - private readonly PreProposalMetadata _preProposalMetadata; - - /// - /// Instantiates a with given - /// and its . - /// - /// A to pre-propose. - /// - /// A signature signed with . - /// - /// Thrown if given is - /// empty. - /// Thrown if given is - /// invalid and cannot be verified with . - public PreProposal(PreProposalMetadata preProposalMetadata, ImmutableArray signature) - { - _preProposalMetadata = preProposalMetadata; - Signature = signature; - - if (signature.IsDefaultOrEmpty) - { - throw new ArgumentNullException( - nameof(signature), - "Signature cannot be null or empty."); - } - else if (!Verify()) - { - throw new ArgumentException("Signature is invalid.", nameof(signature)); - } - } - - public PreProposal(byte[] marshaled) - : this((Dictionary)_codec.Decode(marshaled)) - { - } - -#pragma warning disable SA1118 // The parameter spans multiple lines - public PreProposal(Dictionary bencoded) - : this( - new PreProposalMetadata(bencoded), - bencoded.ContainsKey(SignatureKey) - ? ((Binary)bencoded[SignatureKey]).ByteArray - : ImmutableArray.Empty) - { - } -#pragma warning restore SA1118 - - /// - public long Height => _preProposalMetadata.Height; - - /// - public int Round => _preProposalMetadata.Round; - - /// - public Proof? LastProof => _preProposalMetadata.LastProof; - - /// - public HashDigest PreEvaluationHash => _preProposalMetadata.PreEvaluationHash; - - /// - public DateTimeOffset Timestamp => _preProposalMetadata.Timestamp; - - /// - public PublicKey ValidatorPublicKey => _preProposalMetadata.ValidatorPublicKey; - - /// - public byte[] MarshaledPreEvalBlock => _preProposalMetadata.MarshaledPreEvalBlock; - - /// - /// A signature that signed with . - /// - public ImmutableArray Signature { get; } - - /// - /// A Bencodex-encoded value of . - /// - [JsonIgnore] - public IValue Bencoded => - !Signature.IsEmpty - ? ((Dictionary)_preProposalMetadata.Bencoded).Add(SignatureKey, Signature) - : _preProposalMetadata.Bencoded; - - /// - /// encoded data. - /// - public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); - - public byte[] ToByteArray() => _codec.Encode(Bencoded); - - /// - /// Verifies whether the is properly signed by - /// . - /// - /// if the is not empty - /// and is a valid signature signed by . - [Pure] - public bool Verify() => - !Signature.IsDefaultOrEmpty && - ValidatorPublicKey.Verify( - _preProposalMetadata.ByteArray.ToImmutableArray(), - Signature); - - /// - [Pure] - public bool Equals(PreProposal? other) - { - return other is PreProposal proposal && - _preProposalMetadata.Equals(proposal._preProposalMetadata) && - Signature.SequenceEqual(proposal.Signature); - } - - /// - [Pure] - public override bool Equals(object? obj) - { - return obj is Proposal other && Equals(other); - } - - /// - [Pure] - public override int GetHashCode() - { - return HashCode.Combine( - _preProposalMetadata.GetHashCode(), - ByteUtil.CalculateHashCode(Signature.ToArray())); - } - } -} diff --git a/src/Libplanet/Consensus/PreProposalMetadata.cs b/src/Libplanet/Consensus/PreProposalMetadata.cs deleted file mode 100644 index 43247ed348d..00000000000 --- a/src/Libplanet/Consensus/PreProposalMetadata.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Globalization; -using System.Security.Cryptography; -using System.Text.Json.Serialization; -using Bencodex; -using Bencodex.Types; -using Libplanet.Common; -using Libplanet.Crypto; -using Libplanet.Types.Blocks; - -namespace Libplanet.Consensus -{ - /// - /// A class for constructing . This class contains proposal information - /// in consensus of a height and a round. Use to create a - /// . - /// - public class PreProposalMetadata : IEquatable, IBencodable - { - private const string TimestampFormat = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; - private static readonly Binary HeightKey = - new Binary(new byte[] { 0x48 }); // 'H' - - private static readonly Binary RoundKey = - new Binary(new byte[] { 0x52 }); // 'R' - - private static readonly Binary LastProofKey = - new Binary(new byte[] { 0x4c }); // 'L - - private static readonly Binary TimestampKey = - new Binary(new byte[] { 0x74 }); // 't' - - private static readonly Binary ValidatorPublicKeyKey = - new Binary(new byte[] { 0x50 }); // 'P' - - private static readonly Binary PreEvalBlockKey = - new Binary(new byte[] { 0x42 }); // 'B' - - private static readonly Codec _codec = new Codec(); - - /// - /// Instantiates with given parameters. - /// - /// a height of given pre-proposal values. - /// a round of given pre-proposal values. - /// a of previous round. - /// The time at which the pre-proposal took place. - /// a of pre-proposing validator. - /// - /// a marshaled bencodex-encoded - /// array of . - /// This can be thrown in following reasons: - /// - /// - /// Given is less than 0. - /// - /// - /// Given is less than 0. - /// - /// - /// - public PreProposalMetadata( - long height, - int round, - Proof? lastProof, - DateTimeOffset timestamp, - PublicKey validatorPublicKey, - byte[] marshaledPreEvalBlock) - { - if (height < 0) - { - throw new ArgumentOutOfRangeException( - nameof(height), - "Height must be greater than or equal to 0."); - } - else if (round < 0) - { - throw new ArgumentOutOfRangeException( - nameof(round), - "Round must be greater than or equal to 0."); - } - - Height = height; - Round = round; - LastProof = lastProof; - Timestamp = timestamp; - ValidatorPublicKey = validatorPublicKey; - MarshaledPreEvalBlock = marshaledPreEvalBlock; - PreEvaluationHash = BlockMarshaler.UnmarshalPreEvaluationHash( - (Dictionary)_codec.Decode(marshaledPreEvalBlock)); - } - -#pragma warning disable SA1118 // The parameter spans multiple lines - public PreProposalMetadata(Dictionary encoded) - : this( - height: (Integer)encoded[HeightKey], - round: (Integer)encoded[RoundKey], - lastProof: encoded.TryGetValue(LastProofKey, out IValue proof) - ? (Proof?)new Proof(proof) - : null, - timestamp: DateTimeOffset.ParseExact( - (Text)encoded[TimestampKey], - TimestampFormat, - CultureInfo.InvariantCulture), - validatorPublicKey: new PublicKey( - ((Binary)encoded[ValidatorPublicKeyKey]).ByteArray), - marshaledPreEvalBlock: ((Binary)encoded[PreEvalBlockKey]).ToByteArray()) - { - } -#pragma warning restore SA1118 - - /// - /// A height of given pre-proposal values. - /// - public long Height { get; } - - /// - /// A round of given pre-proposal values. - /// - public int Round { get; } - - /// - /// A of previous round. - /// - public Proof? LastProof { get; } - - /// - /// The pre-evaluation hash of . - /// This is automatically derived from . - /// - public HashDigest PreEvaluationHash { get; } - - /// - /// The time at which the pre-proposal took place. - /// - public DateTimeOffset Timestamp { get; } - - /// - /// A of pre-proposing validator. - /// - public PublicKey ValidatorPublicKey { get; } - - /// - /// A marshaled bencodex-encoded array of pre-evaluation block. - /// - public byte[] MarshaledPreEvalBlock { get; } - - /// - /// A Bencodex-encoded value of . - /// - [JsonIgnore] - public IValue Bencoded - { - get - { - Dictionary encoded = Dictionary.Empty - .Add(HeightKey, Height) - .Add(RoundKey, Round) - .Add( - TimestampKey, - Timestamp.ToString(TimestampFormat, CultureInfo.InvariantCulture)) - .Add(ValidatorPublicKeyKey, ValidatorPublicKey.Format(compress: true)) - .Add(PreEvalBlockKey, MarshaledPreEvalBlock); - - if (LastProof is Proof proof) - { - encoded = encoded.Add(LastProofKey, proof.Bencoded); - } - - return encoded; - } - } - - public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); - - public byte[] ToByteArray() => _codec.Encode(Bencoded); - - /// - /// Signs given with given . - /// - /// A to sign. - /// Returns a signed . - public PreProposal Sign(PrivateKey signer) => - new PreProposal(this, signer.Sign(ByteArray).ToImmutableArray()); - - /// - public bool Equals(PreProposalMetadata? other) - { - return other is PreProposalMetadata metadata && - Height == metadata.Height && - Round == metadata.Round && - LastProof.Equals(metadata.LastProof) && - PreEvaluationHash.Equals(metadata.PreEvaluationHash) && - Timestamp - .ToString(TimestampFormat, CultureInfo.InvariantCulture).Equals( - metadata.Timestamp.ToString( - TimestampFormat, - CultureInfo.InvariantCulture)) && - ValidatorPublicKey.Equals(metadata.ValidatorPublicKey); - } - - /// - public override bool Equals(object? obj) => - obj is PreProposalMetadata other && Equals(other); - - /// - public override int GetHashCode() - { - return HashCode.Combine( - Height, - Round, - LastProof is Proof proof ? ByteUtil.CalculateHashCode(proof.ToByteArray()) : 0, - ByteUtil.CalculateHashCode(PreEvaluationHash.ToByteArray()), - Timestamp.ToString(TimestampFormat, CultureInfo.InvariantCulture), - ValidatorPublicKey); - } - } -} diff --git a/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs b/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs index a60e6bdfe3c..6a520033be8 100644 --- a/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs +++ b/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs @@ -16,6 +16,7 @@ using Libplanet.Types.Tx; using Libplanet.Store; using Libplanet.Store.Trie; +using Libplanet.Consensus; namespace Libplanet.Explorer.Tests; @@ -179,7 +180,7 @@ private void AddBlock(ImmutableArray transactions) Chain.Tip.Hash, BlockContent.DeriveTxHash(transactions), Chain.Store.GetChainBlockCommit(Chain.Store.GetCanonicalChainId()!.Value), - new LotMetadata(Chain.Tip.Index + 1, 0, Chain.Tip.Proof).Prove(proposer).Proof, + new ConsensusInformation(Chain.Tip.Index + 1, 0, Chain.Tip.Proof).Prove(proposer), evidenceHash: null), transactions, evidence: Array.Empty()).Propose(), diff --git a/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs b/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs index 5607a05ed7f..af470f580c4 100644 --- a/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs +++ b/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs @@ -15,6 +15,7 @@ using Libplanet.Store; using Xunit; using static Libplanet.Explorer.Tests.GraphQLTestUtils; +using Libplanet.Consensus; namespace Libplanet.Explorer.Tests.GraphTypes { @@ -43,7 +44,7 @@ public async void Query() previousHash: lastBlockHash, txHash: null, lastCommit: lastBlockCommit, - proof: new LotMetadata(2, 0, null).Prove(privateKey).Proof, + proof: new ConsensusInformation(2, 0, null).Prove(privateKey), evidenceHash: null)).Propose(); var stateRootHash = new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)); diff --git a/test/Libplanet.Explorer.Tests/Indexing/BlockChainIndexTest.cs b/test/Libplanet.Explorer.Tests/Indexing/BlockChainIndexTest.cs index 25b8d890fda..f1c9503d9d2 100644 --- a/test/Libplanet.Explorer.Tests/Indexing/BlockChainIndexTest.cs +++ b/test/Libplanet.Explorer.Tests/Indexing/BlockChainIndexTest.cs @@ -11,6 +11,7 @@ using Libplanet.Types.Tx; using Libplanet.Explorer.Indexing; using Xunit; +using Libplanet.Consensus; namespace Libplanet.Explorer.Tests.Indexing; @@ -44,8 +45,8 @@ await index.SynchronizeAsync( var divergentBlock = forkedChain.ProposeBlock( ChainFx.PrivateKeys[0], forkedChain.GetBlockCommit(forkedChain.Tip.Hash), - new LotMetadata(forkedChain.Tip.Index + 1, 0, forkedChain.Tip.Proof) - .Prove(ChainFx.PrivateKeys[0]).Proof); + new ConsensusInformation(forkedChain.Tip.Index + 1, 0, forkedChain.Tip.Proof) + .Prove(ChainFx.PrivateKeys[0])); forkedChain.Append( divergentBlock, new BlockCommit( diff --git a/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs index 3080fbf8cc1..a95d9d497c2 100644 --- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs @@ -181,10 +181,10 @@ public async void NewHeightWhenTipChanged() TestUtils.ActionLoader, TestUtils.PrivateKeys[1]); consensusContext.Start(); - + var proposer = new PrivateKey(); Assert.Equal(1, consensusContext.Height); Block block = blockChain.ProposeBlock( - new PrivateKey(), + proposer, proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); Assert.Equal(1, consensusContext.Height); diff --git a/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs index 43f51d10764..3fd55d41faa 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs @@ -381,7 +381,7 @@ public async void VoteNilOnSelfProposedInvalidBlock() Assert.Equal( TestUtils.PrivateKeys[2].PublicKey, - TestUtils.ValidatorSet.GetProposer(2, 0).PublicKey); + context.Proposer); context.Start(); await proposalSent.WaitAsync(); diff --git a/test/Libplanet.Net.Tests/SwarmTest.Preload.cs b/test/Libplanet.Net.Tests/SwarmTest.Preload.cs index 0e50e0ee062..1fdb8919f9a 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.Preload.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.Preload.cs @@ -1171,7 +1171,7 @@ public async Task ActionExecutionWithBranchpoint() for (int i = 0; i < 10; i++) { Block block = forked.ProposeBlock( - seedKey, + seedKey, CreateBlockCommit(forked.Tip), CreateZeroRoundProof(forked.Tip, seedKey)); forked.Append(block, TestUtils.CreateBlockCommit(block)); diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs index 4d3ca1f9ed5..b0f74a1d5d2 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -10,6 +10,7 @@ using Libplanet.Action.State; using Libplanet.Action.Tests.Common; using Libplanet.Blockchain.Policies; +using Libplanet.Consensus; using Libplanet.Crypto; using Libplanet.Mocks; using Libplanet.Store; @@ -20,7 +21,6 @@ using Libplanet.Tests.Tx; using Libplanet.Types.Assets; using Libplanet.Types.Blocks; -using Libplanet.Types.Consensus; using Libplanet.Types.Evidence; using Libplanet.Types.Tx; using Serilog; @@ -156,8 +156,10 @@ public void Idempotent() previousHash: null, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, - proof: new LotMetadata(0, 0, null).Prove(GenesisProposer).Proof), - transactions: txs).Propose(); + proof: new ConsensusInformation(0, 0, null).Prove(GenesisProposer), + evidenceHash: null), + transactions: txs, + evidence: evs).Propose(); // Since there is no static method determine state root hash of common block, // used method for genesis block instead. @@ -844,7 +846,7 @@ public void EvaluateTxResultThrowingException() previousHash: hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: CreateBlockCommit(hash, 122, 0), - proof: new LotMetadata(123L, 0, null).Prove(GenesisProposer).Proof, + proof: new ConsensusInformation(123L, 0, null).Prove(GenesisProposer), evidenceHash: null), transactions: txs, evidence: evs).Propose(); diff --git a/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs b/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs index e98290ccbf5..cfaa873be56 100644 --- a/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs +++ b/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs @@ -3,6 +3,7 @@ using System.Numerics; using System.Security.Cryptography; using Libplanet.Common; +using Libplanet.Consensus; using Libplanet.Crypto; using Libplanet.Tests.Fixtures; using Libplanet.Types.Blocks; @@ -247,7 +248,7 @@ public void ValidateLastCommit() previousHash: blockHash, txHash: null, lastCommit: invalidHeightLastCommit, - proof: new LotMetadata(2L, 0, null).Prove(validatorA).Proof, + proof: new ConsensusInformation(2L, 0, null).Prove(validatorA), evidenceHash: null)); // BlockHash of the last commit is invalid. @@ -270,7 +271,7 @@ public void ValidateLastCommit() previousHash: GenesisHash, txHash: null, lastCommit: invalidBlockHashLastCommit, - proof: new LotMetadata(2L, 0, null).Prove(validatorA).Proof, + proof: new ConsensusInformation(2L, 0, null).Prove(validatorA), evidenceHash: null)); var validLastCommit = new BlockCommit( @@ -300,7 +301,7 @@ public void ValidateLastCommit() previousHash: blockHash, txHash: null, lastCommit: validLastCommit, - proof: new LotMetadata(2L, 0, null).Prove(validatorA).Proof, + proof: new ConsensusInformation(2L, 0, null).Prove(validatorA), evidenceHash: null); } diff --git a/test/Libplanet.Tests/Consensus/LotMetadataTest.cs b/test/Libplanet.Tests/Consensus/LotMetadataTest.cs deleted file mode 100644 index 8e8f9520f63..00000000000 --- a/test/Libplanet.Tests/Consensus/LotMetadataTest.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Bencodex; -using Libplanet.Crypto; -using Libplanet.Types.Consensus; -using Xunit; - -namespace Libplanet.Tests.Consensus -{ - public class LotMetadataTest - { - [Fact] - public void Bencode() - { - var lotMetadata = new LotMetadata(0L, 0, null); - Assert.Equal(lotMetadata, new LotMetadata(lotMetadata.Bencoded)); - } - - [Fact] - public void Prove() - { - var privateKey = new PrivateKey(); - var lastProof = new LotMetadata(0L, 0, null).Prove(privateKey).Proof; - var lotMetadata = new LotMetadata(1L, 0, lastProof); - var lot = lotMetadata.Prove(privateKey); - Assert.Equal(lotMetadata.Height, lot.Height); - Assert.Equal(lotMetadata.Round, lot.Round); - Assert.Equal(lotMetadata.LastProof, lot.LastProof); - Assert.Equal(privateKey.Prove(new Codec().Encode(lotMetadata.Bencoded)), lot.Proof); - } - } -} diff --git a/test/Libplanet.Tests/Consensus/LotTest.cs b/test/Libplanet.Tests/Consensus/LotTest.cs deleted file mode 100644 index 6c577d34795..00000000000 --- a/test/Libplanet.Tests/Consensus/LotTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Bencodex; -using Libplanet.Crypto; -using Libplanet.Types.Consensus; -using Xunit; - -namespace Libplanet.Tests.Consensus -{ - public class LotTest - { - [Fact] - public void Constructor() - { - var privateKey = new PrivateKey(); - var unauthorizedPublicKey = new PrivateKey().PublicKey; - var lotMetadata = new LotMetadata(0L, 0, null); - - new Lot( - lotMetadata, - privateKey.PublicKey, - privateKey.Prove(new Codec().Encode(lotMetadata.Bencoded))); - - Assert.Throws( - () => new Lot( - lotMetadata, - unauthorizedPublicKey, - privateKey.Prove(new Codec().Encode(lotMetadata.Bencoded)))); - } - - [Fact] - public void Bencode() - { - var lot = new LotMetadata(0L, 0, null).Prove(new PrivateKey()); - Assert.Equal(lot, new Lot(lot.Bencoded)); - } - } -} diff --git a/test/Libplanet.Tests/TestUtils.cs b/test/Libplanet.Tests/TestUtils.cs index c0251356d0e..8da1c899c80 100644 --- a/test/Libplanet.Tests/TestUtils.cs +++ b/test/Libplanet.Tests/TestUtils.cs @@ -25,6 +25,7 @@ using Libplanet.Blockchain.Renderers; using Libplanet.Blockchain.Renderers.Debug; using Libplanet.Common; +using Libplanet.Consensus; using Libplanet.Crypto; using Libplanet.Store; using Libplanet.Store.Trie; @@ -414,7 +415,7 @@ public static BlockCommit CreateBlockCommit( public static Proof CreateZeroRoundProof( Block tip, PrivateKey proposerKey) - => new LotMetadata(tip.Index + 1, 0, tip.Proof).Prove(proposerKey).Proof; + => new ConsensusInformation(tip.Index + 1, 0, tip.Proof).Prove(proposerKey); public static PreEvaluationBlock ProposeGenesis( PublicKey proposer = null, From bbc25b674c74e799e8e5ed9328db1c1fda39033d Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 4 Jul 2024 12:51:20 +0900 Subject: [PATCH 35/61] fix: Update VRF BPV --- src/Libplanet.Types/Blocks/BlockMetadata.cs | 11 ++++++++++- src/Libplanet/Blockchain/BlockChain.Validate.cs | 8 ++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Libplanet.Types/Blocks/BlockMetadata.cs b/src/Libplanet.Types/Blocks/BlockMetadata.cs index b81adb55574..bd232f7eb3c 100644 --- a/src/Libplanet.Types/Blocks/BlockMetadata.cs +++ b/src/Libplanet.Types/Blocks/BlockMetadata.cs @@ -23,7 +23,7 @@ public class BlockMetadata : IBlockMetadata /// /// The latest protocol version. /// - public const int CurrentProtocolVersion = 9; + public const int CurrentProtocolVersion = 10; /// /// @@ -100,6 +100,15 @@ public class BlockMetadata : IBlockMetadata /// public const int EvidenceProtocolVersion = 9; + /// + /// The starting protocol version where has been added + /// for verifiable random function. This is used for proposer selection, + /// and app random seed. Prior to this version, + /// proposer of block was selected with round robin, and + /// was used for app random seed. + /// + public const int VRFProtocolVersion = 10; + private const string TimestampFormat = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; private static readonly Codec Codec = new Codec(); diff --git a/src/Libplanet/Blockchain/BlockChain.Validate.cs b/src/Libplanet/Blockchain/BlockChain.Validate.cs index 11322a9f21e..310fb14cc88 100644 --- a/src/Libplanet/Blockchain/BlockChain.Validate.cs +++ b/src/Libplanet/Blockchain/BlockChain.Validate.cs @@ -313,16 +313,16 @@ internal void ValidateBlock(Block block) throw new InvalidBlockLastCommitException(ibce.Message); } - if (block.Proof is { } && block.ProtocolVersion < 6) + if (block.Proof is { } && block.ProtocolVersion < BlockMetadata.VRFProtocolVersion) { throw new InvalidBlockProofException( - "Block of protocol version lower than 6 does not support proof."); + "Block of protocol version lower than 9 does not support proof."); } - if (block.Proof is null && block.ProtocolVersion >= 6) + if (block.Proof is null && block.ProtocolVersion >= BlockMetadata.VRFProtocolVersion) { throw new InvalidBlockProofException( - "Block of protocol version higher than 5 must contain proof."); + "Block of protocol version higher than 9 must contain proof."); } } From 5ee566c8b8eb73f5a59f82074a406b1de5408410 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 4 Jul 2024 13:46:49 +0900 Subject: [PATCH 36/61] feat: Prevent proposer from proof manipulation --- src/Libplanet.Net/Consensus/Context.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Libplanet.Net/Consensus/Context.cs b/src/Libplanet.Net/Consensus/Context.cs index a8471039d83..a2b4256068a 100644 --- a/src/Libplanet.Net/Consensus/Context.cs +++ b/src/Libplanet.Net/Consensus/Context.cs @@ -492,6 +492,15 @@ private bool IsValid(Block block) try { + if (!block.Proof.Equals(_lotSet.Maj23?.Proof)) + { + throw new InvalidBlockProofException( + $"Proof if given block is different from " + + $"majority proof of consensus. " + + $"Expected: {_lotSet.Maj23?.Proof}" + + $"Actual: {block.Proof}"); + } + _blockChain.ValidateBlock(block); _blockChain.ValidateBlockNonces( block.Transactions From 9a77b1c5731ab7ab57e1983e4fe9c6f8cb2001f4 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 4 Jul 2024 18:38:11 +0900 Subject: [PATCH 37/61] fix: Linting --- src/Libplanet/Blockchain/BlockChain.Validate.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Libplanet/Blockchain/BlockChain.Validate.cs b/src/Libplanet/Blockchain/BlockChain.Validate.cs index 310fb14cc88..52b10d5641a 100644 --- a/src/Libplanet/Blockchain/BlockChain.Validate.cs +++ b/src/Libplanet/Blockchain/BlockChain.Validate.cs @@ -313,13 +313,15 @@ internal void ValidateBlock(Block block) throw new InvalidBlockLastCommitException(ibce.Message); } - if (block.Proof is { } && block.ProtocolVersion < BlockMetadata.VRFProtocolVersion) + if (block.Proof is { } + && block.ProtocolVersion < BlockMetadata.VRFProtocolVersion) { throw new InvalidBlockProofException( "Block of protocol version lower than 9 does not support proof."); } - if (block.Proof is null && block.ProtocolVersion >= BlockMetadata.VRFProtocolVersion) + if (block.Proof is null + && block.ProtocolVersion >= BlockMetadata.VRFProtocolVersion) { throw new InvalidBlockProofException( "Block of protocol version higher than 9 must contain proof."); From b0314b3057c3dbb5b08cd32222a4a83cd9761818 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 4 Jul 2024 19:18:06 +0900 Subject: [PATCH 38/61] fix: Build fix --- src/Libplanet.Net/Messages/ConsensusLotMsg.cs | 2 +- .../Consensus/ConsensusInformation.cs | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Libplanet.Net/Messages/ConsensusLotMsg.cs b/src/Libplanet.Net/Messages/ConsensusLotMsg.cs index e558c2b7063..6ca3b11122a 100644 --- a/src/Libplanet.Net/Messages/ConsensusLotMsg.cs +++ b/src/Libplanet.Net/Messages/ConsensusLotMsg.cs @@ -6,7 +6,7 @@ namespace Libplanet.Net.Messages { /// - /// A message class for . + /// A message class for . /// public class ConsensusLotMsg : ConsensusMsg { diff --git a/src/Libplanet/Consensus/ConsensusInformation.cs b/src/Libplanet/Consensus/ConsensusInformation.cs index 941b2296aca..183323c530d 100644 --- a/src/Libplanet/Consensus/ConsensusInformation.cs +++ b/src/Libplanet/Consensus/ConsensusInformation.cs @@ -59,7 +59,7 @@ public ConsensusInformation( Height = height; Round = round; LastProof = lastProof; - Encoded = Encode(); + Encoded = Encode(height, round, lastProof); } public ConsensusInformation(IReadOnlyList encoded) @@ -67,7 +67,7 @@ public ConsensusInformation(IReadOnlyList encoded) { } - public ConsensusInformation(IValue bencoded) + private ConsensusInformation(IValue bencoded) : this(bencoded is Dictionary dict ? dict : throw new ArgumentException( @@ -221,21 +221,21 @@ public override string ToString() => $"{nameof(ConsensusInformation)} " + $": Height {Height}, Round {Round}, LastProof {LastProof}"; - private ImmutableArray Encode() - => _codec.Encode(Bencode()).ToImmutableArray(); - - private IValue Bencode() + private static IValue Bencode(long height, int round, Proof? lastProof) { Dictionary bencoded = Dictionary.Empty - .Add(HeightKey, Height) - .Add(RoundKey, Round); + .Add(HeightKey, height) + .Add(RoundKey, round); - if (LastProof is Proof lastProof) + if (lastProof is Proof lastProofNonNull) { - bencoded = bencoded.Add(LastProofKey, lastProof.ByteArray); + bencoded = bencoded.Add(LastProofKey, lastProofNonNull.ByteArray); } return bencoded; } + + private static ImmutableArray Encode(long height, int round, Proof? lastProof) + => new Codec().Encode(Bencode(height, round, lastProof)).ToImmutableArray(); } } From 15057af1da8930eb1f94f0978b74a1d427e975c8 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 5 Jul 2024 12:33:39 +0900 Subject: [PATCH 39/61] feat: Add Proof on GenesisBlock --- src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs b/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs index e85147b1f43..0c582c44a77 100644 --- a/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs +++ b/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs @@ -7,6 +7,7 @@ using Bencodex.Types; using Libplanet.Blockchain.Policies; using Libplanet.Common; +using Libplanet.Consensus; using Libplanet.Crypto; using Libplanet.Store.Trie; using Libplanet.Types.Blocks; @@ -62,7 +63,7 @@ public static Block ProposeGenesisBlock( previousHash: null, txHash: BlockContent.DeriveTxHash(transactions), lastCommit: null, - proof: null, + proof: new ConsensusInformation(0L, 0, null).Prove(privateKey), evidenceHash: null), transactions: transactions, evidence: Array.Empty()); From 9ce8cce891f7381cf73b8aed6948e95415e9e833 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 5 Jul 2024 12:34:00 +0900 Subject: [PATCH 40/61] test: Fix Libplanet tests --- .../Action/ActionEvaluatorTest.Migration.cs | 12 ++++++--- .../Action/ActionEvaluatorTest.cs | 5 ++-- .../Blockchain/BlockChainTest.Append.cs | 25 ++++++++----------- .../Blockchain/BlockChainTest.Fork.cs | 18 ++++++------- .../Blockchain/BlockChainTest.cs | 4 ++- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.Migration.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.Migration.cs index 19edd2969bd..b9af6c3168b 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.Migration.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.Migration.cs @@ -311,7 +311,8 @@ public void MigrateThroughBlockWorldState() Assert.Equal(0, chain.GetWorldState().Version); // A block that doesn't touch any state does not migrate its state. - var block2 = chain.ProposeBlock(miner, blockCommit); + var block2 = chain.ProposeBlock( + miner, blockCommit, CreateZeroRoundProof(chain.Tip, miner)); blockCommit = CreateBlockCommit(block2); chain.Append(block2, blockCommit); Assert.Equal(0, chain.GetWorldState().Version); @@ -329,7 +330,8 @@ public void MigrateThroughBlockWorldState() actions: new[] { action }.ToPlainValues()); chain.StageTransaction(tx); - var block3 = chain.ProposeBlock(miner, blockCommit); + var block3 = chain.ProposeBlock( + miner, blockCommit, CreateZeroRoundProof(chain.Tip, miner)); chain.Append(block3, CreateBlockCommit(block3)); Assert.Equal(BlockMetadata.CurrentProtocolVersion, chain.GetNextWorldState().Version); var accountStateRoot = stateStore.GetStateRoot(chain.GetNextStateRootHash(block3.Hash)) @@ -365,7 +367,8 @@ public void MigrateThroughBlockCurrencyAccount() Assert.Equal(0, chain.GetWorldState().Version); // A block that doesn't touch any state does not migrate its state. - var block2 = chain.ProposeBlock(miner, blockCommit); + var block2 = chain.ProposeBlock( + miner, blockCommit, CreateZeroRoundProof(chain.Tip, miner)); blockCommit = CreateBlockCommit(block2); chain.Append(block2, blockCommit); Assert.Equal(0, chain.GetWorldState().Version); @@ -382,7 +385,8 @@ public void MigrateThroughBlockCurrencyAccount() actions: new[] { action }.ToPlainValues()); chain.StageTransaction(tx); - var block3 = chain.ProposeBlock(miner, blockCommit); + var block3 = chain.ProposeBlock( + miner, blockCommit, CreateZeroRoundProof(chain.Tip, miner)); chain.Append(block3, CreateBlockCommit(block3)); Assert.Equal(BlockMetadata.CurrentProtocolVersion, chain.GetNextWorldState().Version); diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs index b0f74a1d5d2..1dc18de0992 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -177,13 +177,13 @@ public void Idempotent() (Integer)new WorldBaseState( stateStore.GetStateRoot(actionEvaluations[0].OutputState), stateStore) .GetAccountState(ReservedAddresses.LegacyAccount) - .GetState(txAddress)); + .GetState(ContextRecordingAction.RandomRecordAddress)); actionEvaluations = actionEvaluator.Evaluate(stateRootBlockWithProof, null); generatedRandomNumbersWithProof.Add( (Integer)new WorldBaseState( stateStore.GetStateRoot(actionEvaluations[0].OutputState), stateStore) .GetAccountState(ReservedAddresses.LegacyAccount) - .GetState(txAddress)); + .GetState(ContextRecordingAction.RandomRecordAddress)); } for (int i = 1; i < generatedRandomNumbersWithProof.Count; ++i) @@ -537,6 +537,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu var evals = actionEvaluator.EvaluateBlock( block1, previousState).ToImmutableArray(); + // Once the BlockMetadata.CurrentProtocolVersion gets bumped, expectations may also // have to be updated, since the order may change due to different PreEvaluationHash. (int TxIdx, int ActionIdx, string[] UpdatedStates, Address Signer)[] expectations = diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs index 744f36ed6ef..d4e87df8c30 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Linq; using System.Security.Cryptography; +using System.Security.Policy; using Bencodex.Types; using Libplanet.Action; using Libplanet.Action.Loader; @@ -879,14 +880,12 @@ public void AppendSRHPostponeBPVBump() action = DumbAction.Create((new Address(TestUtils.GetRandomBytes(20)), "bar")); tx = Transaction.Create(1, miner, genesis.Hash, new[] { action }.ToPlainValues()); var blockAfterBump1 = blockChain.ProposeBlock( - miner, - new[] { tx }.ToImmutableList(), - commitBeforeBump, - proof: null, + proposer: miner, + transactions: new[] { tx }.ToImmutableList(), + lastCommit: commitBeforeBump, + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, miner), evidence: ImmutableArray.Empty); - Assert.Equal( - BlockMetadata.CurrentProtocolVersion, - blockAfterBump1.ProtocolVersion); + Assert.True(blockAfterBump1.ProtocolVersion >= BlockMetadata.SlothProtocolVersion); var commitAfterBump1 = TestUtils.CreateBlockCommit(blockAfterBump1); blockChain.Append(blockAfterBump1, commitAfterBump1); Assert.Equal(blockBeforeBump.StateRootHash, blockAfterBump1.StateRootHash); @@ -895,14 +894,12 @@ public void AppendSRHPostponeBPVBump() action = DumbAction.Create((new Address(TestUtils.GetRandomBytes(20)), "baz")); tx = Transaction.Create(2, miner, genesis.Hash, new[] { action }.ToPlainValues()); var blockAfterBump2 = blockChain.ProposeBlock( - miner, - new[] { tx }.ToImmutableList(), - commitAfterBump1, - proof: null, + proposer: miner, + transactions: new[] { tx }.ToImmutableList(), + lastCommit: commitAfterBump1, + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, miner), evidence: ImmutableArray.Empty); - Assert.Equal( - BlockMetadata.CurrentProtocolVersion, - blockAfterBump2.ProtocolVersion); + Assert.True(blockAfterBump1.ProtocolVersion >= BlockMetadata.SlothProtocolVersion); var commitAfterBump2 = TestUtils.CreateBlockCommit(blockAfterBump2); blockChain.Append(blockAfterBump2, commitAfterBump2); Assert.Equal( diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs index a5ae27ecc65..790846011db 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs @@ -231,7 +231,7 @@ public void StateAfterForkingAndAddingExistingBlock() proof: CreateZeroRoundProof(_blockChain.Tip, miner)); _blockChain.Append(b2, CreateBlockCommit(b2)); var state = _blockChain - .GetWorldState() + .GetNextWorldState() .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(address); @@ -239,14 +239,14 @@ public void StateAfterForkingAndAddingExistingBlock() var forked = _blockChain.Fork(b1.Hash); state = forked - .GetWorldState() + .GetNextWorldState() .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(address); Assert.Equal((Text)"foo", state); forked.Append(b2, CreateBlockCommit(b2)); state = forked - .GetWorldState() + .GetNextWorldState() .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(address); Assert.Equal((Text)"foo,bar", state); @@ -368,7 +368,7 @@ public void GetStateReturnsValidStateAfterFork() Assert.Equal( "item0.0", (Text)chain - .GetWorldState() + .GetNextWorldState() .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(_fx.Address1)); @@ -385,14 +385,14 @@ public void GetStateReturnsValidStateAfterFork() Assert.Equal( new IValue[] { (Text)"item0.0,item1.0" }, chain - .GetWorldState() + .GetNextWorldState() .GetAccountState(ReservedAddresses.LegacyAccount) .GetStates(new[] { _fx.Address1 }) ); Assert.Equal( "item0.0,item1.0", (Text)chain - .GetWorldState() + .GetNextWorldState() .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(_fx.Address1)); @@ -401,14 +401,14 @@ public void GetStateReturnsValidStateAfterFork() Assert.Equal( new IValue[] { (Text)"item0.0,item1.0" }, forked - .GetWorldState() + .GetNextWorldState() .GetAccountState(ReservedAddresses.LegacyAccount) .GetStates(new[] { _fx.Address1 }) ); Assert.Equal( "item0.0,item1.0", (Text)forked - .GetWorldState() + .GetNextWorldState() .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(_fx.Address1)); } @@ -607,7 +607,7 @@ public void Swap(bool render) Assert.Equal( (Integer)(totalBlockCount - 1), (Integer)_blockChain - .GetWorldState() + .GetNextWorldState() .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(minerAddress) ); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.cs index eea715e7945..d66e0058bd3 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -686,7 +686,9 @@ public void ReorgIsUnableToHeterogenousChain(bool render) } Block extraBlock2 = chain2.ProposeBlock( - key, lastCommit: CreateBlockCommit(chain2.Tip)); + key, + lastCommit: CreateBlockCommit(chain2.Tip), + proof: CreateZeroRoundProof(chain2.Tip, key)); chain2.Append(extraBlock2, CreateBlockCommit(extraBlock2)); Log.Logger.CompareBothChains( From b1bd3ba9ef244585e27da96e4384ce19f3335109 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Sat, 6 Jul 2024 23:07:28 +0900 Subject: [PATCH 41/61] chore: Clean lot implementations --- src/Libplanet/Consensus/ConsensusInformation.cs | 13 +++++++++++-- src/Libplanet/Consensus/DominantLot.cs | 13 +++++++------ src/Libplanet/Consensus/DominantLotMetadata.cs | 4 ++-- src/Libplanet/Consensus/Lot.cs | 7 ++----- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/Libplanet/Consensus/ConsensusInformation.cs b/src/Libplanet/Consensus/ConsensusInformation.cs index 183323c530d..af70c1d5eaf 100644 --- a/src/Libplanet/Consensus/ConsensusInformation.cs +++ b/src/Libplanet/Consensus/ConsensusInformation.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Text.Json; using Bencodex; using Bencodex.Types; using Libplanet.Crypto; @@ -217,9 +218,17 @@ public override int GetHashCode() Round, LastProof); + /// public override string ToString() - => $"{nameof(ConsensusInformation)} " + - $": Height {Height}, Round {Round}, LastProof {LastProof}"; + { + var dict = new Dictionary + { + { "height", Height }, + { "round", Round }, + { "lastProof", LastProof.ToString() ?? "Empty" }, + }; + return JsonSerializer.Serialize(dict); + } private static IValue Bencode(long height, int round, Proof? lastProof) { diff --git a/src/Libplanet/Consensus/DominantLot.cs b/src/Libplanet/Consensus/DominantLot.cs index d04d2ded149..43bfcc799bf 100644 --- a/src/Libplanet/Consensus/DominantLot.cs +++ b/src/Libplanet/Consensus/DominantLot.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.Contracts; using System.Linq; @@ -52,8 +53,8 @@ public DominantLot(DominantLotMetadata dominantLotMetadata, ImmutableArray } } - public DominantLot(byte[] marshaled) - : this((Dictionary)_codec.Decode(marshaled)) + public DominantLot(IReadOnlyList marshaled) + : this((Dictionary)_codec.Decode(marshaled.ToArray())) { } @@ -92,17 +93,17 @@ public DominantLot(Dictionary encoded) /// A Bencodex-encoded value of . /// [JsonIgnore] - public Dictionary Encoded => + public Dictionary Bencoded => !Signature.IsEmpty - ? _dominantLotMetadata.Encoded.Add(SignatureKey, Signature) - : _dominantLotMetadata.Encoded; + ? _dominantLotMetadata.Bencoded.Add(SignatureKey, Signature) + : _dominantLotMetadata.Bencoded; /// /// encoded data. /// public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); - public byte[] ToByteArray() => _codec.Encode(Encoded); + public byte[] ToByteArray() => _codec.Encode(Bencoded); /// [Pure] diff --git a/src/Libplanet/Consensus/DominantLotMetadata.cs b/src/Libplanet/Consensus/DominantLotMetadata.cs index 9741f26d57f..d48e0639214 100644 --- a/src/Libplanet/Consensus/DominantLotMetadata.cs +++ b/src/Libplanet/Consensus/DominantLotMetadata.cs @@ -101,7 +101,7 @@ public DominantLotMetadata(Dictionary encoded) /// A Bencodex-encoded value of . /// [JsonIgnore] - public Dictionary Encoded + public Dictionary Bencoded { get { @@ -120,7 +120,7 @@ public Dictionary Encoded public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); - public byte[] ToByteArray() => _codec.Encode(Encoded); + public byte[] ToByteArray() => _codec.Encode(Bencoded); /// /// Signs given with given . diff --git a/src/Libplanet/Consensus/Lot.cs b/src/Libplanet/Consensus/Lot.cs index ec0f0631dba..459a7bc28c6 100644 --- a/src/Libplanet/Consensus/Lot.cs +++ b/src/Libplanet/Consensus/Lot.cs @@ -5,7 +5,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using Bencodex; -using Bencodex.Misc; using Bencodex.Types; using Libplanet.Crypto; @@ -117,11 +116,9 @@ public override string ToString() { var dict = new Dictionary { - { "proof", Proof.ByteArray.Hex() }, - { "height", ConsensusInformation.Height }, - { "round", ConsensusInformation.Round }, - { "lastProof", ConsensusInformation.LastProof?.ByteArray.Hex() ?? "Empty" }, + { "proof", Proof }, { "public_key", PublicKey.ToString() }, + { "consensus_information", ConsensusInformation }, }; return JsonSerializer.Serialize(dict); } From fd808a0474e19b37367067561662c5793059dfee Mon Sep 17 00:00:00 2001 From: ilgyu Date: Sat, 6 Jul 2024 23:07:49 +0900 Subject: [PATCH 42/61] test: Add lot tests --- .../Consensus/ConsensusInformationTest.cs | 98 +++++++++++++ .../Consensus/DominantLotMetadataTest.cs | 121 ++++++++++++++++ .../Consensus/DominantLotTest.cs | 136 ++++++++++++++++++ test/Libplanet.Tests/Consensus/LotTest.cs | 75 ++++++++++ 4 files changed, 430 insertions(+) create mode 100644 test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs create mode 100644 test/Libplanet.Tests/Consensus/DominantLotMetadataTest.cs create mode 100644 test/Libplanet.Tests/Consensus/DominantLotTest.cs create mode 100644 test/Libplanet.Tests/Consensus/LotTest.cs diff --git a/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs b/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs new file mode 100644 index 00000000000..3045e5071f4 --- /dev/null +++ b/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs @@ -0,0 +1,98 @@ +using System; +using Libplanet.Consensus; +using Libplanet.Crypto; +using Xunit; + +namespace Libplanet.Tests.Consensus +{ + public class ConsensusInformationTest + { + private readonly ConsensusInformation _genesisConsensusInformation; + private readonly PrivateKey _genesisProver; + private readonly Proof _genesisProof; + + private readonly ConsensusInformation _consensusInformation; + + public ConsensusInformationTest() + { + _genesisProver = new PrivateKey(); + _genesisConsensusInformation = new ConsensusInformation(0, 0, null); + _genesisProof = _genesisConsensusInformation.Prove(_genesisProver); + _consensusInformation = new ConsensusInformation(1, 2, _genesisProof); + } + + [Fact] + public void Constructor() + { + Assert.Equal(0, _genesisConsensusInformation.Height); + Assert.Equal(0, _genesisConsensusInformation.Round); + Assert.Null(_genesisConsensusInformation.LastProof); + + Assert.Throws(() => new ConsensusInformation(-1, 0, null)); + Assert.Throws(() => new ConsensusInformation(0, -1, null)); + + var ci = new ConsensusInformation(1, 2, _genesisProof); + Assert.Equal(1, ci.Height); + Assert.Equal(2, ci.Round); + Assert.Equal(_genesisProof, ci.LastProof); + Assert.Equal(ci, new ConsensusInformation(ci.Encoded)); + } + + [Fact] + public void ProveAndVerify() + { + var prover = new PrivateKey(); + var nonProver = new PrivateKey(); + var proof = _consensusInformation.Prove(prover); + Assert.True(_consensusInformation.Verify(proof, prover.PublicKey)); + Assert.False(_consensusInformation.Verify(proof, nonProver.PublicKey)); + } + + [Fact] + public void ToLot() + { + var prover = new PrivateKey(); + var lot = _consensusInformation.ToLot(prover); + Assert.Equal(_consensusInformation, lot.ConsensusInformation); + Assert.Equal(prover.PublicKey, lot.PublicKey); + Assert.Equal(_consensusInformation.Prove(prover), lot.Proof); + Assert.True(_consensusInformation.Verify(lot.Proof, prover.PublicKey)); + } + + [Fact] + public void Equal() + { + var differentProof = _genesisConsensusInformation.Prove(new PrivateKey()); + Assert.Equal( + _consensusInformation, + new ConsensusInformation(1, 2, _genesisProof)); + Assert.NotEqual( + _consensusInformation, + new ConsensusInformation(2, 2, _genesisProof)); + Assert.NotEqual( + _consensusInformation, + new ConsensusInformation(1, 3, _genesisProof)); + Assert.NotEqual( + _consensusInformation, + new ConsensusInformation(1, 2, differentProof)); + } + + [Fact] + public void HashCode() + { + var differentProof = _genesisConsensusInformation.Prove(new PrivateKey()); + Assert.Equal( + _consensusInformation.GetHashCode(), + new ConsensusInformation(1, 2, _genesisProof).GetHashCode()); + Assert.NotEqual( + _consensusInformation.GetHashCode(), + new ConsensusInformation(2, 2, _genesisProof).GetHashCode()); + Assert.NotEqual( + _consensusInformation.GetHashCode(), + new ConsensusInformation(1, 3, _genesisProof).GetHashCode()); + Assert.NotEqual( + _consensusInformation.GetHashCode(), + new ConsensusInformation(1, 2, differentProof).GetHashCode()); + } + } +} diff --git a/test/Libplanet.Tests/Consensus/DominantLotMetadataTest.cs b/test/Libplanet.Tests/Consensus/DominantLotMetadataTest.cs new file mode 100644 index 00000000000..44f2b5d45f7 --- /dev/null +++ b/test/Libplanet.Tests/Consensus/DominantLotMetadataTest.cs @@ -0,0 +1,121 @@ +using System; +using Libplanet.Consensus; +using Libplanet.Crypto; +using Xunit; + +namespace Libplanet.Tests.Consensus +{ + public class DominantLotMetadataTest + { + private readonly Lot _lot; + private readonly PrivateKey _prover; + private readonly PrivateKey _signer; + private readonly DominantLotMetadata _dominantLotMetadata; + + public DominantLotMetadataTest() + { + _prover = new PrivateKey(); + _signer = new PrivateKey(); + _lot = new ConsensusInformation(0, 0, null).ToLot(_prover); + _dominantLotMetadata = new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey); + } + + [Fact] + public void Constructor() + { + Assert.Throws( + () => new DominantLotMetadata( + -1, 0, _lot, DateTimeOffset.UtcNow, _signer.PublicKey)); + Assert.Throws( + () => new DominantLotMetadata( + 0, -1, _lot, DateTimeOffset.UtcNow, _signer.PublicKey)); + + var dominantLotMetadata = new DominantLotMetadata( + 1, 2, _lot, DateTimeOffset.UtcNow, _signer.PublicKey); + Assert.Equal(1, dominantLotMetadata.Height); + Assert.Equal(2, dominantLotMetadata.Round); + Assert.Equal(_lot, dominantLotMetadata.Lot); + Assert.Equal(_signer.PublicKey, dominantLotMetadata.ValidatorPublicKey); + Assert.Equal( + dominantLotMetadata, new DominantLotMetadata(dominantLotMetadata.Bencoded)); + + // DominantLotMetadata can be generated by non-prover. + Assert.NotEqual(_prover.PublicKey, dominantLotMetadata.ValidatorPublicKey); + } + + [Fact] + public void Sign() + { + var stranger = new PrivateKey(); + _dominantLotMetadata.Sign(_signer); + Assert.Throws(() => _dominantLotMetadata.Sign(stranger)); + } + + [Fact] + public void Equal() + { + Assert.Equal( + _dominantLotMetadata, + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey)); + Assert.NotEqual( + _dominantLotMetadata, + new DominantLotMetadata( + 1, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey)); + Assert.NotEqual( + _dominantLotMetadata, + new DominantLotMetadata( + 0, 1, _lot, DateTimeOffset.MinValue, _signer.PublicKey)); + Assert.NotEqual( + _dominantLotMetadata, + new DominantLotMetadata( + 0, + 0, + new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()), + DateTimeOffset.MinValue, + _signer.PublicKey)); + Assert.NotEqual( + _dominantLotMetadata, + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MaxValue, _signer.PublicKey)); + Assert.NotEqual( + _dominantLotMetadata, + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MinValue, new PrivateKey().PublicKey)); + } + + [Fact] + public void HashCode() + { + Assert.Equal( + _dominantLotMetadata.GetHashCode(), + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey).GetHashCode()); + Assert.NotEqual( + _dominantLotMetadata.GetHashCode(), + new DominantLotMetadata( + 1, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey).GetHashCode()); + Assert.NotEqual( + _dominantLotMetadata.GetHashCode(), + new DominantLotMetadata( + 0, 1, _lot, DateTimeOffset.MinValue, _signer.PublicKey).GetHashCode()); + Assert.NotEqual( + _dominantLotMetadata.GetHashCode(), + new DominantLotMetadata( + 0, + 0, + new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()), + DateTimeOffset.MinValue, + _signer.PublicKey).GetHashCode()); + Assert.NotEqual( + _dominantLotMetadata.GetHashCode(), + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MaxValue, _signer.PublicKey).GetHashCode()); + Assert.NotEqual( + _dominantLotMetadata.GetHashCode(), + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MinValue, new PrivateKey().PublicKey).GetHashCode()); + } + } +} diff --git a/test/Libplanet.Tests/Consensus/DominantLotTest.cs b/test/Libplanet.Tests/Consensus/DominantLotTest.cs new file mode 100644 index 00000000000..e84240e2c4d --- /dev/null +++ b/test/Libplanet.Tests/Consensus/DominantLotTest.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Immutable; +using Libplanet.Consensus; +using Libplanet.Crypto; +using Xunit; + +namespace Libplanet.Tests.Consensus +{ + public class DominantLotTest + { + private readonly Lot _lot; + private readonly PrivateKey _prover; + private readonly PrivateKey _signer; + private readonly DominantLotMetadata _dominantLotMetadata; + private readonly DominantLot _dominantLot; + + public DominantLotTest() + { + _prover = new PrivateKey(); + _signer = new PrivateKey(); + _lot = new ConsensusInformation(0, 0, null).ToLot(_prover); + _dominantLotMetadata = new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey); + _dominantLot = _dominantLotMetadata.Sign(_signer); + } + + [Fact] + public void Constructor() + { + Assert.Throws( + () => new DominantLot( + _dominantLotMetadata, + ImmutableArray.Empty)); + Assert.Throws( + () => new DominantLot( + _dominantLotMetadata, + ImmutableArray.Create(0x00, 0x00))); + Assert.Throws( + () => new DominantLot( + _dominantLotMetadata, + new PrivateKey().Sign(_dominantLotMetadata.ByteArray))); + var signature = _signer.Sign(_dominantLotMetadata.ByteArray); + var dominantLot = new DominantLot(_dominantLotMetadata, signature); + Assert.Equal(_dominantLot, dominantLot); + Assert.Equal(_dominantLotMetadata.Height, dominantLot.Height); + Assert.Equal(_dominantLotMetadata.Round, dominantLot.Round); + Assert.Equal(_dominantLotMetadata.Lot, dominantLot.Lot); + Assert.Equal(_dominantLotMetadata.Timestamp, dominantLot.Timestamp); + Assert.Equal(_signer.PublicKey, dominantLot.ValidatorPublicKey); + Assert.Equal(signature, dominantLot.Signature); + Assert.Equal(_dominantLot, new DominantLot(_dominantLot.Bencoded)); + Assert.Equal(_dominantLot, new DominantLot(_dominantLot.ByteArray)); + + // DominantLotMetadata can be generated by non-prover. + Assert.NotEqual(_prover.PublicKey, _dominantLot.ValidatorPublicKey); + } + + [Fact] + public void Equal() + { + Assert.Equal( + _dominantLot, + _dominantLotMetadata + .Sign(_signer)); + Assert.NotEqual( + _dominantLot, + new DominantLotMetadata( + 1, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey) + .Sign(_signer)); + Assert.NotEqual( + _dominantLot, + new DominantLotMetadata( + 0, 1, _lot, DateTimeOffset.MinValue, _signer.PublicKey) + .Sign(_signer)); + Assert.NotEqual( + _dominantLot, + new DominantLotMetadata( + 0, + 0, + new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()), + DateTimeOffset.MinValue, + _signer.PublicKey) + .Sign(_signer)); + Assert.NotEqual( + _dominantLot, + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MaxValue, _signer.PublicKey) + .Sign(_signer)); + var stranger = new PrivateKey(); + Assert.NotEqual( + _dominantLot, + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MinValue, stranger.PublicKey) + .Sign(stranger)); + } + + [Fact] + public void HashCode() + { + Assert.Equal( + _dominantLot.GetHashCode(), + _dominantLotMetadata + .Sign(_signer).GetHashCode()); + Assert.NotEqual( + _dominantLot.GetHashCode(), + new DominantLotMetadata( + 1, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey) + .Sign(_signer).GetHashCode()); + Assert.NotEqual( + _dominantLot.GetHashCode(), + new DominantLotMetadata( + 0, 1, _lot, DateTimeOffset.MinValue, _signer.PublicKey) + .Sign(_signer).GetHashCode()); + Assert.NotEqual( + _dominantLot.GetHashCode(), + new DominantLotMetadata( + 0, + 0, + new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()), + DateTimeOffset.MinValue, + _signer.PublicKey) + .Sign(_signer).GetHashCode()); + Assert.NotEqual( + _dominantLot.GetHashCode(), + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MaxValue, _signer.PublicKey) + .Sign(_signer).GetHashCode()); + var stranger = new PrivateKey(); + Assert.NotEqual( + _dominantLot.GetHashCode(), + new DominantLotMetadata( + 0, 0, _lot, DateTimeOffset.MinValue, stranger.PublicKey) + .Sign(stranger).GetHashCode()); + } + } +} diff --git a/test/Libplanet.Tests/Consensus/LotTest.cs b/test/Libplanet.Tests/Consensus/LotTest.cs new file mode 100644 index 00000000000..80a246f9c17 --- /dev/null +++ b/test/Libplanet.Tests/Consensus/LotTest.cs @@ -0,0 +1,75 @@ +using System; +using Libplanet.Consensus; +using Libplanet.Crypto; +using Xunit; + +namespace Libplanet.Tests.Consensus +{ + public class LotTest + { + private readonly ConsensusInformation _genesisConsensusInformation; + private readonly PrivateKey _genesisProver; + private readonly Proof _genesisProof; + + private readonly ConsensusInformation _consensusInformation; + private readonly PrivateKey _prover; + private readonly Proof _proof; + private readonly Lot _lot; + + public LotTest() + { + _genesisProver = new PrivateKey(); + _genesisConsensusInformation = new ConsensusInformation(0, 0, null); + _genesisProof = _genesisConsensusInformation.Prove(_genesisProver); + _consensusInformation = new ConsensusInformation(1, 2, _genesisProof); + _prover = new PrivateKey(); + _proof = _consensusInformation.Prove(_prover); + _lot = new Lot(_proof, _prover.PublicKey, _consensusInformation); + } + + [Fact] + public void Constructor() + { + Assert.Throws( + () => new Lot( + _proof, + new PrivateKey().PublicKey, + _consensusInformation)); + Assert.Throws( + () => new Lot( + _proof, + _prover.PublicKey, + new ConsensusInformation(0, 1, null))); + + var lot = new Lot(_proof, _prover.PublicKey, _consensusInformation); + + Assert.Equal(_proof, lot.Proof); + Assert.Equal(_prover.PublicKey, lot.PublicKey); + Assert.Equal(_consensusInformation, lot.ConsensusInformation); + Assert.Equal(lot, new Lot(lot.Bencoded)); + Assert.Equal(lot, new Lot(lot.ByteArray)); + } + + [Fact] + public void Equal() + { + Assert.Equal(_lot, new Lot(_proof, _prover.PublicKey, _consensusInformation)); + Assert.NotEqual(_lot, _consensusInformation.ToLot(new PrivateKey())); + Assert.NotEqual(_lot, new ConsensusInformation(0, 1, null).ToLot(_prover)); + } + + [Fact] + public void HashCode() + { + Assert.Equal( + _lot.GetHashCode(), + new Lot(_proof, _prover.PublicKey, _consensusInformation).GetHashCode()); + Assert.NotEqual( + _lot.GetHashCode(), + _consensusInformation.ToLot(new PrivateKey()).GetHashCode()); + Assert.NotEqual( + _lot.GetHashCode(), + new ConsensusInformation(0, 1, null).ToLot(_prover).GetHashCode()); + } + } +} From 291b135fd92971a52a5c45547006125eb336ed46 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 11 Jul 2024 13:44:43 +0900 Subject: [PATCH 43/61] feat: Add comparer operator on Proof --- src/Libplanet.Crypto/Proof.cs | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index 273b72bdbe5..b02ec540f14 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.Contracts; using System.Linq; using System.Numerics; using Bencodex; @@ -91,6 +92,84 @@ public int Seed } } + /// + /// Tests if two values are equal. + /// + /// A value. + /// Another value. + /// if two values are equal. + /// Otherwise . + [Pure] + public static bool operator ==(Proof obj, Proof other) => + obj.Equals(other); + + /// + /// Tests if two values are unequal. + /// + /// A value. + /// Another value. + /// if two values are equal. + /// Otherwise . + [Pure] + public static bool operator !=(Proof obj, Proof other) => + !(obj == other); + + /// + /// Tests if the left operand () is less than the right operand + /// (). + /// + /// The left operand to compare. + /// The right operand to compare. + /// + /// if the left operand () is less than the right + /// operand (). Otherwise (even if two operands are equal) + /// . + [Pure] + public static bool operator <(Proof obj, Proof other) => + obj.CompareTo(other) < 0; + + /// + /// Tests if the left operand () is less than or equal to the right + /// operand (). + /// + /// The left operand to compare. + /// The right operand to compare. + /// + /// if the left operand () is less than or equal + /// to the right operand (). Otherwise . + /// + [Pure] + public static bool operator <=(Proof obj, Proof other) => + obj.CompareTo(other) <= 0; + + /// + /// Tests if the left operand () is greater than the right operand + /// (). + /// + /// The left operand to compare. + /// The right operand to compare. + /// + /// if the left operand () is greater than + /// the right operand (). Otherwise (even if two operands are + /// equal) . + [Pure] + public static bool operator >(Proof obj, Proof other) => + other < obj; + + /// + /// Tests if the left operand () is greater than or equal to the right + /// operand (). + /// + /// The left operand to compare. + /// The right operand to compare. + /// + /// if the left operand () is greater than or + /// equal to the right operand (). + /// Otherwise . + [Pure] + public static bool operator >=(Proof obj, Proof other) => + other <= obj; + /// /// Gets a mutable byte array that represent this . /// From 1eda971b028b9345e8608b5f30aeabc985eb26cd Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 11 Jul 2024 13:46:09 +0900 Subject: [PATCH 44/61] fix: Fix sortition consensus --- src/Libplanet.Net/Consensus/Context.Mutate.cs | 1 - src/Libplanet.Net/Consensus/Context.cs | 4 +++- src/Libplanet.Net/Consensus/LotSet.cs | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Libplanet.Net/Consensus/Context.Mutate.cs b/src/Libplanet.Net/Consensus/Context.Mutate.cs index 085705265ca..e932e11dfb5 100644 --- a/src/Libplanet.Net/Consensus/Context.Mutate.cs +++ b/src/Libplanet.Net/Consensus/Context.Mutate.cs @@ -1,6 +1,5 @@ using System; using Libplanet.Consensus; -using Libplanet.Crypto; using Libplanet.Net.Messages; using Libplanet.Types.Blocks; using Libplanet.Types.Consensus; diff --git a/src/Libplanet.Net/Consensus/Context.cs b/src/Libplanet.Net/Consensus/Context.cs index a2b4256068a..dec6b9b40a4 100644 --- a/src/Libplanet.Net/Consensus/Context.cs +++ b/src/Libplanet.Net/Consensus/Context.cs @@ -141,6 +141,7 @@ public Context( validators, ConsensusStep.Default, -1, + 20, 128, contextTimeoutOptions) { @@ -154,6 +155,7 @@ private Context( ValidatorSet validators, ConsensusStep consensusStep, int round = -1, + int drawSize = 20, int cacheSize = 128, ContextTimeoutOption? contextTimeoutOptions = null) { @@ -185,7 +187,7 @@ private Context( _codec = new Codec(); _messageRequests = Channel.CreateUnbounded(); _mutationRequests = Channel.CreateUnbounded(); - _lotSet = new LotSet(height, round, _lastProof, validators, 20); + _lotSet = new LotSet(height, round, _lastProof, validators, drawSize); _heightVoteSet = new HeightVoteSet(height, validators); _preVoteTimeoutFlags = new HashSet(); _hasTwoThirdsPreVoteFlags = new HashSet(); diff --git a/src/Libplanet.Net/Consensus/LotSet.cs b/src/Libplanet.Net/Consensus/LotSet.cs index dcd55062ead..f1b5ba616c9 100644 --- a/src/Libplanet.Net/Consensus/LotSet.cs +++ b/src/Libplanet.Net/Consensus/LotSet.cs @@ -73,7 +73,10 @@ public void SetRound(int round, Proof? lastProof) { _consensusInformation = new ConsensusInformation(Height, Math.Max(round, 0), lastProof); _lots.Clear(); + _dominantLot = null; _dominantLots.Clear(); + _lotsPower.Clear(); + Maj23 = null; } public Proof GenerateProof(PrivateKey privateKey) @@ -168,7 +171,8 @@ private void CompeteLot(Lot lot) _validatorSet.TotalPower); if (!(_dominantLot is { } dominantLot - && drawn < dominantLot.Item2)) + && (drawn < dominantLot.Item2 + || (drawn == dominantLot.Item2 && lot.Proof <= dominantLot.Item1.Proof)))) { _dominantLot = (lot, drawn); } From 858610825b8114dc49cc663e9d732b421373bdee Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 11 Jul 2024 13:46:30 +0900 Subject: [PATCH 45/61] test: Update Libplanet.Net.Tests --- .../ConsensusContextNonProposerTest.cs | 86 +++++++++-- .../Consensus/ConsensusContextProposerTest.cs | 3 + .../Consensus/ConsensusContextTest.cs | 73 ++++++++++ .../Consensus/ContextNonProposerTest.cs | 135 +++++++++++++++++- .../Consensus/ContextProposerTest.cs | 109 ++++++-------- .../ContextProposerValidRoundTest.cs | 107 +++++++++++++- .../Consensus/ContextTest.cs | 91 ++++++++++-- test/Libplanet.Net.Tests/SwarmTest.cs | 51 +++---- test/Libplanet.Net.Tests/TestUtils.cs | 62 ++++++++ test/Libplanet.Tests/TestUtils.cs | 9 +- 10 files changed, 597 insertions(+), 129 deletions(-) diff --git a/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs index 2d27e562971..e7231e5e9de 100644 --- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs @@ -58,6 +58,15 @@ public async void NewHeightWithLastCommit() }; consensusContext.Start(); + var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 0, 1, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + } + var block1 = blockChain.ProposeBlock( TestUtils.PrivateKeys[1], proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); @@ -95,6 +104,15 @@ public async void NewHeightWithLastCommit() consensusContext.HandleMessage(new ConsensusPreCommitMsg(expectedVotes[i])); } + lotSet = new LotSet(2L, 0, block1.Proof, TestUtils.ValidatorSet, 20); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); + foreach (int i in new int[] { 0, 1, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 2L, 0, lot))); + } + await heightTwoProposalSent.WaitAsync(); Assert.NotNull(proposal); @@ -114,9 +132,11 @@ public async void HandleMessageFromHigherHeight() { var codec = new Codec(); ConsensusProposalMsg? proposal = null; + var heightTwoStepChangedToSortition = new AsyncAutoResetEvent(); var heightTwoStepChangedToPreVote = new AsyncAutoResetEvent(); var heightTwoStepChangedToPreCommit = new AsyncAutoResetEvent(); var heightTwoStepChangedToEndCommit = new AsyncAutoResetEvent(); + var heightThreeStepChangedToSortition = new AsyncAutoResetEvent(); var heightThreeStepChangedToPropose = new AsyncAutoResetEvent(); var heightThreeStepChangedToPreVote = new AsyncAutoResetEvent(); var proposalSent = new AsyncAutoResetEvent(); @@ -160,7 +180,8 @@ public async void HandleMessageFromHigherHeight() }; consensusContext.MessagePublished += (_, eventArgs) => { - if (eventArgs.Message is ConsensusProposalMsg proposalMsg) + if (eventArgs.Message is ConsensusProposalMsg proposalMsg + && proposalMsg.Height == 2L) { proposal = proposalMsg; proposalSent.Set(); @@ -173,6 +194,16 @@ public async void HandleMessageFromHigherHeight() blockChain.Append(block, TestUtils.CreateBlockCommit(block)); blockChain.Store.PutBlockCommit(TestUtils.CreateBlockCommit(blockChain[1])); + + var lotSet = new LotSet(2L, 0, block.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); + foreach (int i in new int[] { 0, 1, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 2L, 0, lot))); + } + await proposalSent.WaitAsync(); Assert.Equal(2, consensusContext.Height); @@ -238,6 +269,15 @@ in TestUtils.PrivateKeys.Zip( TestUtils.CreateBlockCommit(blockHeightTwo), TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[3])); + lotSet = new LotSet(3L, 0, blockHeightTwo.Proof, TestUtils.ValidatorSet, 20); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 3L, 0, lot))); + } + // Message from higher height consensusContext.HandleMessage( TestUtils.CreateConsensusPropose(blockHeightThree, TestUtils.PrivateKeys[3], 3)); @@ -274,12 +314,30 @@ public async void UseLastCommitCacheIfHeightContextIsEmpty() }; consensusContext.Start(); + var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 0, 1, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + } + Block block = blockChain.ProposeBlock( TestUtils.PrivateKeys[1], proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); var createdLastCommit = TestUtils.CreateBlockCommit(block); blockChain.Append(block, createdLastCommit); + lotSet = new LotSet(2L, 0, block.Proof, TestUtils.ValidatorSet, 20); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); + foreach (int i in new int[] { 0, 1, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 2L, 0, lot))); + } + // Context for height #2 where node #2 is the proposer is automatically started // by watching blockchain's Tip. await heightTwoProposalSent.WaitAsync(); @@ -296,7 +354,7 @@ public async void NewHeightDelay() // The maximum error margin. (macos-netcore-test) var timeError = 500; var heightOneEndCommit = new AsyncAutoResetEvent(); - var heightTwoProposalSent = new AsyncAutoResetEvent(); + var heightTwoSortitionStarted = new AsyncAutoResetEvent(); var (blockChain, consensusContext) = TestUtils.CreateDummyConsensusContext( newHeightDelay, TestUtils.Policy, @@ -308,16 +366,22 @@ public async void NewHeightDelay() { heightOneEndCommit.Set(); } - }; - consensusContext.MessagePublished += (_, eventArgs) => - { - if (eventArgs.Height == 2 && eventArgs.Message is ConsensusProposalMsg) + else if (eventArgs.Height == 2 && eventArgs.Step == ConsensusStep.Sortition) { - heightTwoProposalSent.Set(); + heightTwoSortitionStarted.Set(); } }; consensusContext.Start(); + var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 0, 1, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + } + var block = blockChain.ProposeBlock( TestUtils.PrivateKeys[1], proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); @@ -330,15 +394,15 @@ public async void NewHeightDelay() await heightOneEndCommit.WaitAsync(); var endCommitTime = DateTimeOffset.UtcNow; - await heightTwoProposalSent.WaitAsync(); - var proposeTime = DateTimeOffset.UtcNow; - var difference = proposeTime - endCommitTime; + await heightTwoSortitionStarted.WaitAsync(); + var sortitionTime = DateTimeOffset.UtcNow; + var difference = sortitionTime - endCommitTime; _logger.Debug("Difference: {Difference}", difference); // Check new height delay; slight margin of error is allowed as delay task // is run asynchronously from context events. Assert.True( - ((proposeTime - endCommitTime) - newHeightDelay).Duration() < + ((sortitionTime - endCommitTime) - newHeightDelay).Duration() < TimeSpan.FromMilliseconds(timeError)); } } diff --git a/test/Libplanet.Net.Tests/Consensus/ConsensusContextProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ConsensusContextProposerTest.cs index a9036906c3f..fa0dbac8bed 100644 --- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextProposerTest.cs @@ -49,6 +49,9 @@ public async void IncreaseRoundWhenTimeout() Assert.Equal(1, consensusContext.Height); Assert.Equal(0, consensusContext.Round); + // Triggers sortition timeout. + await timeoutProcessed.WaitAsync(); + // Triggers timeout +2/3 with NIL and Block consensusContext.HandleMessage( new ConsensusPreVoteMsg( diff --git a/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs index a95d9d497c2..02b6bfc430f 100644 --- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs @@ -83,11 +83,30 @@ public async void NewHeightIncreasing() } }; + var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + } + var block = blockChain.ProposeBlock( TestUtils.PrivateKeys[1], proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); var blockCommit = TestUtils.CreateBlockCommit(block); blockChain.Append(block, blockCommit); + + lotSet = new LotSet(2L, 0, block.Proof, TestUtils.ValidatorSet, 20); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 2L, 0, lot))); + } + block = blockChain.ProposeBlock( TestUtils.PrivateKeys[2], blockCommit, @@ -95,6 +114,15 @@ public async void NewHeightIncreasing() blockChain.Append(block, TestUtils.CreateBlockCommit(block)); Assert.Equal(2, blockChain.Tip.Index); + lotSet = new LotSet(3L, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 3L, 0, lot))); + } + // Wait for context of height 3 to start. await heightThreeStepChangedToPropose.WaitAsync(); Assert.Equal(3, consensusContext.Height); @@ -139,6 +167,15 @@ public async void NewHeightIncreasing() await onTipChangedToThree.WaitAsync(); Assert.Equal(3, blockChain.Tip.Index); + lotSet = new LotSet(4L, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 4L, 0, lot))); + } + // Next height starts normally. await heightFourStepChangedToPropose.WaitAsync(); Assert.Equal(4, consensusContext.Height); @@ -242,6 +279,15 @@ public async void VoteSetGetOnlyProposeCommitHash() }; consensusContext.Start(); + var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 0, 2, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + } + await heightOneProposalSent.WaitAsync(); BlockHash proposedblockHash = Assert.IsType(proposal?.BlockHash); @@ -295,6 +341,15 @@ public async Task GetVoteSetBits() TestUtils.ActionLoader, TestUtils.PrivateKeys[0]); consensusContext.Start(); + var lotSet = new LotSet(1L, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(proposer); + foreach (int i in new int[] { 1, 2, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + } + var block = blockChain.ProposeBlock( proposer, proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); @@ -383,6 +438,15 @@ public async Task HandleVoteSetBits() }; consensusContext.Start(); + var lotSet = new LotSet(1L, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + } + var block = blockChain.ProposeBlock( proposer, proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); @@ -457,6 +521,15 @@ public async Task HandleProposalClaim() } }; consensusContext.Start(); + var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + } + var block = blockChain.ProposeBlock( proposer, proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer)); diff --git a/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs index c76f71152fa..cfdc25737ff 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs @@ -50,7 +50,6 @@ public async Task EnterPreVoteBlockOneThird() var (blockChain, context) = TestUtils.CreateDummyContext( privateKey: TestUtils.PrivateKeys[0]); - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); var stateChangedToRoundOnePreVote = new AsyncAutoResetEvent(); context.StateChanged += (_, eventArgs) => { @@ -61,6 +60,19 @@ public async Task EnterPreVoteBlockOneThird() }; context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + + var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1], proof: lot.Proof); + context.ProduceMessage( new ConsensusPreVoteMsg( TestUtils.CreateVote( @@ -96,8 +108,6 @@ public async Task EnterPreCommitBlockTwoThird() var (blockChain, context) = TestUtils.CreateDummyContext( privateKey: TestUtils.PrivateKeys[0]); - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); - context.StateChanged += (_, eventArgs) => { if (eventArgs.Step == ConsensusStep.PreCommit) @@ -115,6 +125,20 @@ public async Task EnterPreCommitBlockTwoThird() }; context.Start(); + + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + + var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1], proof: lot.Proof); + context.ProduceMessage( TestUtils.CreateConsensusPropose(block, TestUtils.PrivateKeys[1])); @@ -202,6 +226,18 @@ public async void EnterPreCommitNilTwoThird() }; context.Start(); + + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + context.ProduceMessage( TestUtils.CreateConsensusPropose(invalidBlock, TestUtils.PrivateKeys[1])); context.ProduceMessage( @@ -286,6 +322,18 @@ public async Task EnterPreVoteNilOnInvalidBlockHeader() TestUtils.PrivateKeys[1]); context.Start(); + + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + context.ProduceMessage( TestUtils.CreateConsensusPropose( invalidBlock, TestUtils.PrivateKeys[1])); @@ -357,6 +405,18 @@ public async Task EnterPreVoteNilOnInvalidBlockContent() TestUtils.PrivateKeys[1]); context.Start(); + + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + context.ProduceMessage( TestUtils.CreateConsensusPropose( invalidBlock, @@ -443,6 +503,18 @@ message is ConsensusPreCommitMsg commit && HashDigest.DeriveFrom(TestUtils.GetRandomBytes(1024))); context.Start(); + + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + context.ProduceMessage( TestUtils.CreateConsensusPropose( invalidBlock, @@ -578,6 +650,28 @@ public async Task UponRulesCheckAfterTimeout() } }; + // Push DominantLots + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + + lotSet.SetRound(1, lot.Proof); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 1, lot))); + } + // Push round 0 and round 1 proposes. context.ProduceMessage( TestUtils.CreateConsensusPropose( @@ -647,11 +741,26 @@ public async Task TimeoutPreVote() privateKey: TestUtils.PrivateKeys[0], contextTimeoutOptions: new ContextTimeoutOption(preVoteSecondBase: 1)); - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); var timeoutProcessed = new AsyncAutoResetEvent(); context.TimeoutProcessed += (_, __) => timeoutProcessed.Set(); context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + + var block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: lot.Proof); + context.ProduceMessage( TestUtils.CreateConsensusPropose( block, TestUtils.PrivateKeys[1], round: 0)); @@ -698,11 +807,25 @@ public async Task TimeoutPreCommit() privateKey: TestUtils.PrivateKeys[0], contextTimeoutOptions: new ContextTimeoutOption(preCommitSecondBase: 1)); - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); var timeoutProcessed = new AsyncAutoResetEvent(); context.TimeoutProcessed += (_, __) => timeoutProcessed.Set(); context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + + var block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: lot.Proof); + context.ProduceMessage( TestUtils.CreateConsensusPropose( block, TestUtils.PrivateKeys[1], round: 0)); @@ -737,7 +860,7 @@ public async Task TimeoutPreCommit() // Wait for timeout. await timeoutProcessed.WaitAsync(); - Assert.Equal(ConsensusStep.Propose, context.Step); + Assert.Equal(ConsensusStep.Sortition, context.Step); Assert.Equal(1, context.Height); Assert.Equal(1, context.Round); } diff --git a/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs index 3fd55d41faa..ed6e6b4da69 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; -using Libplanet.Crypto; using Libplanet.Net.Consensus; using Libplanet.Net.Messages; using Libplanet.Types.Blocks; @@ -98,7 +97,8 @@ public async void EnterPreCommitBlock() ConsensusPreCommitMsg? preCommit = null; var preCommitSent = new AsyncAutoResetEvent(); - var (_, context) = TestUtils.CreateDummyContext(); + var (blockChain, context) = TestUtils.CreateDummyContext( + privateKey: TestUtils.PrivateKeys[0]); context.StateChanged += (_, eventArgs) => { if (eventArgs.Step == ConsensusStep.PreCommit) @@ -122,6 +122,17 @@ public async void EnterPreCommitBlock() context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[0]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + // Wait for propose to process. await proposalSent.WaitAsync(); BlockHash proposedblockHash = Assert.IsType(proposal?.BlockHash); @@ -204,7 +215,7 @@ public async void EnterNewRoundNil() await roundChangedToOne.WaitAsync(); Assert.Equal(1, context.Height); Assert.Equal(1, context.Round); - Assert.Equal(ConsensusStep.Propose, context.Step); + Assert.Equal(ConsensusStep.Sortition, context.Step); } [Fact(Timeout = Timeout)] @@ -215,7 +226,8 @@ public async Task EndCommitBlock() ConsensusProposalMsg? proposal = null; var proposalSent = new AsyncAutoResetEvent(); - var (_, context) = TestUtils.CreateDummyContext(); + var (blockChain, context) = TestUtils.CreateDummyContext( + privateKey: TestUtils.PrivateKeys[0]); context.StateChanged += (_, eventArgs) => { if (eventArgs.Step == ConsensusStep.PreCommit) @@ -239,6 +251,17 @@ public async Task EndCommitBlock() context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[0]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + // Wait for propose to process. await proposalSent.WaitAsync(); Assert.NotNull(proposal?.BlockHash); @@ -268,7 +291,7 @@ public async void EnterPreVoteNil() var stepChangedToPreVote = new AsyncAutoResetEvent(); var nilPreVoteSent = new AsyncAutoResetEvent(); var (_, context) = TestUtils.CreateDummyContext( - height: 5, + height: 1, validatorSet: Libplanet.Tests.TestUtils.ValidatorSet); // Peer1 should be a proposer context.StateChanged += (_, eventArgs) => @@ -289,7 +312,7 @@ public async void EnterPreVoteNil() context.Start(); await Task.WhenAll(nilPreVoteSent.WaitAsync(), stepChangedToPreVote.WaitAsync()); Assert.Equal(ConsensusStep.PreVote, context.Step); - Assert.Equal(5, context.Height); + Assert.Equal(1, context.Height); } [Fact(Timeout = Timeout)] @@ -301,7 +324,8 @@ public async void EnterPreVoteBlock() ConsensusPreVoteMsg? preVote = null; var preVoteSent = new AsyncAutoResetEvent(); - var (_, context) = TestUtils.CreateDummyContext(); + var (blockChain, context) = TestUtils.CreateDummyContext( + privateKey: TestUtils.PrivateKeys[0]); context.StateChanged += (_, eventArgs) => { @@ -325,6 +349,18 @@ public async void EnterPreVoteBlock() }; context.Start(); + + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[0]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + await proposalSent.WaitAsync(); Assert.NotNull(proposal?.BlockHash); @@ -334,64 +370,5 @@ public async void EnterPreVoteBlock() Assert.Equal(0, context.Round); Assert.Equal(ConsensusStep.PreVote, context.Step); } - - [Fact(Timeout = Timeout)] - public async void VoteNilOnSelfProposedInvalidBlock() - { - var privateKey = new PrivateKey(); - ConsensusProposalMsg? proposal = null; - var proposalSent = new AsyncAutoResetEvent(); - ConsensusPreVoteMsg? preVote = null; - var preVoteSent = new AsyncAutoResetEvent(); - - var blockChain = TestUtils.CreateDummyBlockChain(); - var proposer1 = new PrivateKey(); - var block1 = blockChain.ProposeBlock( - proposer1, - proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer1)); - var block1Commit = TestUtils.CreateBlockCommit(block1); - blockChain.Append(block1, block1Commit); - var proposer2 = new PrivateKey(); - var block2 = blockChain.ProposeBlock( - proposer2, - block1Commit, - TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer2)); - var block2Commit = TestUtils.CreateBlockCommit(block2); - blockChain.Append(block2, block2Commit); - - var context = TestUtils.CreateDummyContext( - blockChain, - privateKey: TestUtils.PrivateKeys[2], - height: 2, - lastCommit: block2Commit, - validatorSet: TestUtils.ValidatorSet); - context.MessageToPublish += (_, message) => - { - if (message is ConsensusProposalMsg proposalMsg) - { - proposal = proposalMsg; - proposalSent.Set(); - } - else if (message is ConsensusPreVoteMsg preVoteMsg) - { - preVote = preVoteMsg; - preVoteSent.Set(); - } - }; - - Assert.Equal( - TestUtils.PrivateKeys[2].PublicKey, - context.Proposer); - - context.Start(); - await proposalSent.WaitAsync(); - Bencodex.Codec codec = new Bencodex.Codec(); - var proposedBlock = BlockMarshaler.UnmarshalBlock( - (Bencodex.Types.Dictionary)codec.Decode(proposal?.Proposal.MarshaledBlock!)); - Assert.Equal(context.Height + 1, proposedBlock.Index); - await preVoteSent.WaitAsync(); - Assert.Equal(default(BlockHash), preVote?.BlockHash); - Assert.Equal(default(BlockHash), preVote?.PreVote.BlockHash); - } } } diff --git a/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs index 8a4b0300383..00c9043cb72 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Libplanet.Crypto; using Libplanet.Net.Consensus; @@ -37,13 +38,18 @@ public async Task EnterValidRoundPreVoteBlock() ConsensusProposalMsg? proposal = null; var proposalSent = new AsyncAutoResetEvent(); var roundTwoVoteSent = new AsyncAutoResetEvent(); + var stateChangedToRoundTwoSortition = new AsyncAutoResetEvent(); var stateChangedToRoundTwoPropose = new AsyncAutoResetEvent(); bool timeoutProcessed = false; - var (_, context) = TestUtils.CreateDummyContext(); + var (_, context) = TestUtils.CreateDummyContext(privateKey: TestUtils.PrivateKeys[0]); context.StateChanged += (_, eventArgs) => { - if (eventArgs.Round == 2 && eventArgs.Step == ConsensusStep.Propose) + if (eventArgs.Round == 2 && eventArgs.Step == ConsensusStep.Sortition) + { + stateChangedToRoundTwoSortition.Set(); + } + else if (eventArgs.Round == 2 && eventArgs.Step == ConsensusStep.Propose) { stateChangedToRoundTwoPropose.Set(); } @@ -66,6 +72,31 @@ prevote.BlockHash is { } hash && }; context.Start(); + var lotSet = new LotSet(1, 0, null, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[0]); + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[1], + 1, + 0, + lot))); + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[2], + 1, + 0, + lot))); + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[3], + 1, + 0, + lot))); + + await Task.Delay(1000); await proposalSent.WaitAsync(); Assert.NotNull(proposal); Block proposedBlock = BlockMarshaler.UnmarshalBlock( @@ -88,6 +119,31 @@ prevote.BlockHash is { } hash && round: 2, hash: proposedBlock.Hash, flag: VoteFlag.PreVote))); + + await stateChangedToRoundTwoSortition.WaitAsync(); + lotSet.SetRound(2, lot.Proof); + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[1], + 1, + 2, + lotSet.GenerateLot(TestUtils.PrivateKeys[3])))); + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[2], + 1, + 2, + lotSet.GenerateLot(TestUtils.PrivateKeys[3])))); + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[3], + 1, + 2, + lotSet.GenerateLot(TestUtils.PrivateKeys[3])))); + await stateChangedToRoundTwoPropose.WaitAsync(); Assert.Equal(2, context.Round); @@ -128,16 +184,23 @@ public async void EnterValidRoundPreVoteNil() { ConsensusProposalMsg? proposal = null; var proposalSent = new AsyncAutoResetEvent(); + var stateChangedToRoundTwoSortition = new AsyncAutoResetEvent(); var stateChangedToRoundTwoPropose = new AsyncAutoResetEvent(); var stateChangedToRoundTwoPreVote = new AsyncAutoResetEvent(); var stateChangedToRoundTwoPreCommit = new AsyncAutoResetEvent(); + var stateChangedToRoundThreeSortition = new AsyncAutoResetEvent(); var stateChangedToRoundThreePropose = new AsyncAutoResetEvent(); var roundThreeNilPreVoteSent = new AsyncAutoResetEvent(); bool timeoutProcessed = false; - var (blockChain, context) = TestUtils.CreateDummyContext(); + var (blockChain, context) = TestUtils.CreateDummyContext( + privateKey: TestUtils.PrivateKeys[0]); context.StateChanged += (_, eventArgs) => { - if (eventArgs.Round == 2 && eventArgs.Step == ConsensusStep.Propose) + if (eventArgs.Round == 2 && eventArgs.Step == ConsensusStep.Sortition) + { + stateChangedToRoundTwoSortition.Set(); + } + else if (eventArgs.Round == 2 && eventArgs.Step == ConsensusStep.Propose) { stateChangedToRoundTwoPropose.Set(); } @@ -149,6 +212,10 @@ public async void EnterValidRoundPreVoteNil() { stateChangedToRoundTwoPreCommit.Set(); } + else if (eventArgs.Round == 3 && eventArgs.Step == ConsensusStep.Sortition) + { + stateChangedToRoundThreeSortition.Set(); + } else if (eventArgs.Round == 3 && eventArgs.Step == ConsensusStep.Propose) { stateChangedToRoundThreePropose.Set(); @@ -187,6 +254,16 @@ public async void EnterValidRoundPreVoteNil() key); context.Start(); + var lotSet = new LotSet(1, 0, null, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[0]); + foreach (int i in Enumerable.Range(1, 3)) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], 1, 0, lot))); + } + await proposalSent.WaitAsync(); Assert.NotNull(proposal); Block proposedBlock = BlockMarshaler.UnmarshalBlock( @@ -209,6 +286,17 @@ public async void EnterValidRoundPreVoteNil() round: 2, hash: proposedBlock.Hash, flag: VoteFlag.PreVote))); + await stateChangedToRoundTwoSortition.WaitAsync(); + lotSet.SetRound(2, lot.Proof); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in Enumerable.Range(1, 3)) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], 1, 2, lot))); + } + await stateChangedToRoundTwoPropose.WaitAsync(); Assert.Equal(2, context.Round); Assert.False(timeoutProcessed); // Assert no transition is due to timeout. @@ -248,6 +336,17 @@ public async void EnterValidRoundPreVoteNil() round: 3, hash: differentBlock.Hash, flag: VoteFlag.PreVote))); + await stateChangedToRoundThreeSortition.WaitAsync(); + lotSet.SetRound(3, lot.Proof); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in Enumerable.Range(1, 3)) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], 1, 3, lot))); + } + await stateChangedToRoundThreePropose.WaitAsync(); Assert.Equal(3, context.Round); diff --git a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs index 8dba0dc2ee6..9bdb6568ddc 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Numerics; using System.Text.Json; @@ -54,7 +53,7 @@ public async Task StartAsProposer() { var proposalSent = new AsyncAutoResetEvent(); var stepChangedToPreVote = new AsyncAutoResetEvent(); - var (_, context) = TestUtils.CreateDummyContext(); + var (blockChain, context) = TestUtils.CreateDummyContext(); context.StateChanged += (_, eventArgs) => { if (eventArgs.Step == ConsensusStep.PreVote) @@ -71,6 +70,17 @@ public async Task StartAsProposer() }; context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 0, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + await Task.WhenAll(proposalSent.WaitAsync(), stepChangedToPreVote.WaitAsync()); Assert.Equal(ConsensusStep.PreVote, context.Step); @@ -117,6 +127,17 @@ public async Task StartAsProposerWithLastCommit() }; context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); + foreach (int i in new int[] { 0, 1, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + await Task.WhenAll(stepChangedToPreVote.WaitAsync(), proposalSent.WaitAsync()); Assert.Equal(ConsensusStep.PreVote, context.Step); @@ -134,7 +155,7 @@ public async Task CannotStartTwice() var (_, context) = TestUtils.CreateDummyContext(); context.StateChanged += (_, eventArgs) => { - if (eventArgs.Step == ConsensusStep.Propose) + if (eventArgs.Step == ConsensusStep.Sortition) { stepChanged.Set(); } @@ -200,6 +221,16 @@ public async Task CanAcceptMessagesAfterCommitFailure() }; context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); + foreach (int i in new int[] { 0, 1, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } await Task.WhenAll(stepChangedToPreVote.WaitAsync(), proposalSent.WaitAsync()); @@ -268,6 +299,17 @@ public async Task ThrowOnInvalidProposerMessage() exceptionThrown = e; exceptionOccurred.Set(); }; + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 0, 1, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + var block = blockChain.ProposeBlock( TestUtils.PrivateKeys[1], proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); @@ -387,6 +429,17 @@ public async Task CanPreCommitOnEndCommit() proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 0, 1, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + context.ProduceMessage( TestUtils.CreateConsensusPropose(block, TestUtils.PrivateKeys[1])); @@ -535,6 +588,18 @@ public async Task CanReplaceProposal() } }; context.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, validatorSet, 20); + var lot = lotSet.GenerateLot(proposer); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + privateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + + await stepChanged.WaitAsync(); await stepChanged.WaitAsync(); Assert.Equal(ConsensusStep.Propose, context.Step); @@ -676,9 +741,22 @@ public async Task CanCreateContextWithLastingEvaluation() genesisHash: blockChain.Genesis.Hash, actions: new[] { action }.ToPlainValues()); blockChain.StageTransaction(tx); - var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + var block = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); consensusContext.Start(); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); + foreach (int i in new int[] { 0, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + } + context.ProduceMessage( TestUtils.CreateConsensusPropose(block, TestUtils.PrivateKeys[1])); @@ -711,17 +789,12 @@ public async Task CanCreateContextWithLastingEvaluation() } Assert.Equal(1, consensusContext.Height); - var watch = Stopwatch.StartNew(); await onTipChanged.WaitAsync(); - Assert.True(watch.ElapsedMilliseconds < (actionDelay * 0.5)); - watch.Restart(); - await enteredHeightTwo.WaitAsync(); Assert.Equal( 4, context.GetBlockCommit()!.Votes.Count( vote => vote.Flag.Equals(VoteFlag.PreCommit))); - Assert.True(watch.ElapsedMilliseconds > (actionDelay * 0.5)); Assert.Equal(2, consensusContext.Height); } diff --git a/test/Libplanet.Net.Tests/SwarmTest.cs b/test/Libplanet.Net.Tests/SwarmTest.cs index ff59bf4d0cb..8192bebcce7 100644 --- a/test/Libplanet.Net.Tests/SwarmTest.cs +++ b/test/Libplanet.Net.Tests/SwarmTest.cs @@ -430,12 +430,11 @@ public async Task BootstrapContext() try { - // swarms[1] is the round 0 proposer for height 1. - // swarms[2] is the round 1 proposer for height 2. _ = swarms[0].StartAsync(); - _ = swarms[3].StartAsync(); + _ = swarms[1].StartAsync(); - swarms[0].ConsensusReactor.ConsensusContext.StateChanged += (_, eventArgs) => + swarms[0].ConsensusReactor.ConsensusContext.StateChanged + += (_, eventArgs) => { if (eventArgs.VoteCount == 2) { @@ -443,13 +442,13 @@ public async Task BootstrapContext() } }; - // Make sure both swarms time out and swarm[0] collects two PreVotes. + // Make sure both swarms time out and non-proposer swarm[0] collects two PreVotes. await collectedTwoMessages[0].WaitAsync(); - // Dispose swarm[3] to simulate shutdown during bootstrap. - swarms[3].Dispose(); + // Dispose non-proposer swarm[1] to simulate shutdown during bootstrap. + swarms[1].Dispose(); - // Bring swarm[2] online. + // Bring non-proposer swarm[2] online. _ = swarms[2].StartAsync(); swarms[0].ConsensusReactor.ConsensusContext.StateChanged += (_, eventArgs) => { @@ -466,38 +465,30 @@ public async Task BootstrapContext() } }; - // Since we already have swarm[3]'s PreVote, when swarm[2] times out, - // swarm[2] adds additional PreVote, making it possible to reach PreCommit. + // Since we already have non-proposer swarm[1]'s PreVote, + // when non-proposer swarm[2] times out, + // non-proposer swarm[0] adds additional PreVote, + // making it possible to reach PreCommit. // Current network's context state should be: // Proposal: null - // PreVote: swarm[0], swarm[2], swarm[3], - // PreCommit: swarm[0], swarm[2] - await Task.WhenAll( - stepChangedToPreCommits[0].WaitAsync(), stepChangedToPreCommits[2].WaitAsync()); + // PreVote: non-proposer swarm[0],[1],[2], + // PreCommit: non-proposer swarm[0],[2] + await Task.Delay(1500); - // After swarm[1] comes online, eventually it'll catch up to vote PreCommit, - // at which point the round will move to 1 where swarm[2] is the proposer. - _ = swarms[1].StartAsync(); - swarms[2].ConsensusReactor.ConsensusContext.MessagePublished += (_, eventArgs) => - { - if (eventArgs.Message is ConsensusProposalMsg proposalMsg && - proposalMsg.Round == 1 && - proposalMsg.ValidatorPublicKey.Equals(TestUtils.PrivateKeys[2].PublicKey)) - { - roundOneProposed.Set(); - } - }; + await Task.WhenAll( + stepChangedToPreCommits[0].WaitAsync(), + stepChangedToPreCommits[2].WaitAsync()); - await roundOneProposed.WaitAsync(); + _ = swarms[3].StartAsync(); - await AssertThatEventually(() => swarms[0].BlockChain.Tip.Index == 1, int.MaxValue); - Assert.Equal(1, swarms[0].BlockChain.GetBlockCommit(1).Round); + await AssertThatEventually( + () => swarms[0].BlockChain.Tip.Index == 1, int.MaxValue); } finally { CleaningSwarm(swarms[0]); - CleaningSwarm(swarms[1]); CleaningSwarm(swarms[2]); + CleaningSwarm(swarms[3]); } } diff --git a/test/Libplanet.Net.Tests/TestUtils.cs b/test/Libplanet.Net.Tests/TestUtils.cs index 2404a4f279d..ad538f3d825 100644 --- a/test/Libplanet.Net.Tests/TestUtils.cs +++ b/test/Libplanet.Net.Tests/TestUtils.cs @@ -63,6 +63,68 @@ public static class TestUtils private static readonly Random Random = new Random(); + public static List GetProposers(IEnumerable<(long Height, int Round)> sequence) + { + List proposers = new List(); + (long Height, int Round, Proof? Proof) prev = (-1, 0, null); + foreach (var consensus in sequence) + { + if ((prev.Height < 0 || prev.Round < 0 || prev.Proof is null) && + (consensus.Height != 0 || consensus.Round != 0)) + { + throw new ArgumentException("First item of sequence have to be (0L, 0)."); + } + + if (prev.Height + prev.Round + 1 != consensus.Height + consensus.Round) + { + throw new ArgumentException( + "Height + Round of sequence have to be gradually increased by 1."); + } + + var result = GetProposer(consensus.Height, consensus.Round, prev.Proof); + prev = (consensus.Height, consensus.Round, result.Proof); + proposers.Add(result.Proposer); + } + + return proposers; + } + + public static (PrivateKey Proposer, Proof Proof) GetProposer( + long height, + int round, + Proof? lastProof) + => GetProposer(height, round, lastProof, PrivateKeys, ValidatorSet); + + public static (PrivateKey Proposer, Proof Proof) GetProposer( + long height, + int round, + Proof? lastProof, + IReadOnlyList privateKeys, + ValidatorSet validatorSet) + { + var lotSet = new LotSet(height, round, lastProof, validatorSet, 20); + foreach (var privateKey in privateKeys) + { + lotSet.AddLot(lotSet.Consensusinformation.ToLot(privateKey)); + } + + return (privateKeys.First( + pk => pk.PublicKey.Equals(lotSet.DominantLot!.Value.PublicKey)), + lotSet.DominantLot!.Value.Proof); + } + + public static DominantLot CreateDominantLot( + PrivateKey privateKey, + long height, + int round, + Lot lot) => + new DominantLotMetadata( + height, + round, + lot, + DateTimeOffset.Now, + privateKey.PublicKey).Sign(privateKey); + public static Vote CreateVote( PrivateKey privateKey, BigInteger power, diff --git a/test/Libplanet.Tests/TestUtils.cs b/test/Libplanet.Tests/TestUtils.cs index 8da1c899c80..175fd6b17ae 100644 --- a/test/Libplanet.Tests/TestUtils.cs +++ b/test/Libplanet.Tests/TestUtils.cs @@ -8,6 +8,7 @@ using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; +using System.Security.Policy; using System.Text; using System.Text.Json; using System.Text.Json.JsonDiffPatch.Xunit; @@ -422,7 +423,8 @@ public static PreEvaluationBlock ProposeGenesis( IReadOnlyList transactions = null, ValidatorSet validatorSet = null, DateTimeOffset? timestamp = null, - int protocolVersion = Block.CurrentProtocolVersion + int protocolVersion = Block.CurrentProtocolVersion, + Proof? proof = null ) { var txs = transactions?.ToList() ?? new List(); @@ -454,7 +456,7 @@ public static PreEvaluationBlock ProposeGenesis( previousHash: null, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, - proof: null, + proof: proof, evidenceHash: null), transactions: txs, evidence: Array.Empty()); @@ -652,7 +654,8 @@ public static (BlockChain BlockChain, ActionEvaluator ActionEvaluator) txs, validatorSet, timestamp, - protocolVersion); + protocolVersion, + new ConsensusInformation(0, 0, null).Prove(GenesisProposer)); var evaluatedSrh = actionEvaluator.Evaluate(preEval, null).Last().OutputState; genesisBlock = protocolVersion < BlockMetadata.SignatureProtocolVersion ? new Block( From 35549a3f4620556c4e6807b8070d6a35151a75dd Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 11 Jul 2024 23:02:15 +0900 Subject: [PATCH 46/61] fix: Fix and document for consensus --- src/Libplanet.Crypto/Proof.cs | 18 ++++ src/Libplanet.Net/Consensus/Context.Async.cs | 11 ++- src/Libplanet.Net/Consensus/Context.Event.cs | 3 + src/Libplanet.Net/Consensus/Context.Mutate.cs | 13 ++- src/Libplanet.Net/Consensus/Context.cs | 16 ++++ .../Consensus/InvalidVoteException.cs | 6 +- src/Libplanet.Net/Consensus/LotSet.cs | 86 ++++++++++++++++++- .../Messages/ConsensusDominantLotMsg.cs | 5 +- .../Consensus/ConsensusInformation.cs | 5 ++ .../Consensus/DominantLotMetadata.cs | 63 ++++++-------- src/Libplanet/Consensus/Lot.cs | 17 +++- 11 files changed, 196 insertions(+), 47 deletions(-) diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index b02ec540f14..3d82853ff1d 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -20,6 +20,12 @@ namespace Libplanet.Crypto private readonly ImmutableArray _piBytes; private readonly ImmutableArray _hash; + /// + /// Instantiates a new with given . + /// + /// Byte array represents proof. + /// Thrown when given + /// is invalid. public Proof(IReadOnlyList piBytes) { try @@ -36,6 +42,13 @@ public Proof(IReadOnlyList piBytes) _piBytes = piBytes.ToImmutableArray(); } + /// + /// Creates a new instance of with given + /// proof value. + /// + /// Bencodex-encoded proof value. + /// Thrown when format of given + /// is not . public Proof(IValue bencoded) : this(bencoded is Binary piBytes ? piBytes @@ -46,6 +59,11 @@ public Proof(IValue bencoded) { } + /// + /// Creates a new instance of with given + /// proof value. + /// + /// Bencodex-encoded proof value. public Proof(Binary bencoded) : this(bencoded.ByteArray) { diff --git a/src/Libplanet.Net/Consensus/Context.Async.cs b/src/Libplanet.Net/Consensus/Context.Async.cs index 68036bdfb45..569569131f0 100644 --- a/src/Libplanet.Net/Consensus/Context.Async.cs +++ b/src/Libplanet.Net/Consensus/Context.Async.cs @@ -194,6 +194,10 @@ private void AppendBlock(Block block) _ = Task.Run(() => _blockChain.Append(block, GetBlockCommit())); } + /// + /// Schedule to vote for dominant lot after + /// amount of time. + /// private async Task VoteLotAfterGathering() { TimeSpan delay = DelayLotGather(); @@ -201,8 +205,6 @@ private async Task VoteLotAfterGathering() if (_lotSet.DominantLot is { } lot) { DominantLot dominantLot = new DominantLotMetadata( - Height, - Round, lot, DateTimeOffset.UtcNow, _privateKey.PublicKey).Sign(_privateKey); @@ -210,6 +212,11 @@ private async Task VoteLotAfterGathering() } } + /// + /// Schedule to be queued after + /// amount of time. + /// + /// A round that the timeout task is scheduled for. private async Task OnTimeoutSortition(int round) { TimeSpan timeout = TimeoutSortition(round); diff --git a/src/Libplanet.Net/Consensus/Context.Event.cs b/src/Libplanet.Net/Consensus/Context.Event.cs index 306911af190..00fa27bd29e 100644 --- a/src/Libplanet.Net/Consensus/Context.Event.cs +++ b/src/Libplanet.Net/Consensus/Context.Event.cs @@ -66,6 +66,9 @@ public partial class Context internal event EventHandler<(int Round, VoteFlag Flag, IEnumerable Votes)>? VoteSetModified; + /// + /// An event that is invoked when the is modified. + /// internal event EventHandler<( ImmutableDictionary Lots, ImmutableDictionary DominantLots)>? diff --git a/src/Libplanet.Net/Consensus/Context.Mutate.cs b/src/Libplanet.Net/Consensus/Context.Mutate.cs index e932e11dfb5..67cb62635ce 100644 --- a/src/Libplanet.Net/Consensus/Context.Mutate.cs +++ b/src/Libplanet.Net/Consensus/Context.Mutate.cs @@ -28,6 +28,7 @@ private void StartRound(int round) Round = round; RoundStarted?.Invoke(this, Round); + // TODO: Update last proof by peers. // Last proof is not a parameter of StartRound() // for its update by peers developed in future. // It's crucial, to prevent network partition. @@ -51,9 +52,13 @@ private void StartRound(int round) /// If an invalid is given, this method throws /// an and handles it internally /// while invoking event. - /// An can be thrown when + /// An can be thrown when /// the internal does not accept it, i.e. /// returns . + /// An or + /// can be trown when the internal does not accept it, i.e. + /// or + /// returns . /// /// private bool AddMessage(ConsensusMsg message) @@ -543,6 +548,12 @@ private void ProcessHeightOrRoundUponRules(ConsensusMsg message) } } + /// + /// A timeout mutation to run if +2/3 s were + /// not gathered in and is still in + /// step. + /// + /// A round that the timeout task is scheduled for. private void ProcessTimeoutSortition(int round) { if (round == Round && Step == ConsensusStep.Sortition) diff --git a/src/Libplanet.Net/Consensus/Context.cs b/src/Libplanet.Net/Consensus/Context.cs index dec6b9b40a4..fc12c7e66b7 100644 --- a/src/Libplanet.Net/Consensus/Context.cs +++ b/src/Libplanet.Net/Consensus/Context.cs @@ -220,8 +220,14 @@ private Context( /// public ConsensusStep Step { get; private set; } + /// + /// A proposal block for this round. If the block is proposed, it will be stored here. + /// public Proposal? Proposal { get; private set; } + /// + /// The proposer of this round. Determined by the . + /// public PublicKey? Proposer => _lotSet.Maj23?.PublicKey; @@ -419,6 +425,12 @@ private TimeSpan TimeoutPropose(long round) round * _contextTimeoutOption.ProposeMultiplier); } + /// + /// Gets the timeout of with the given + /// round. + /// + /// A round to get the timeout. + /// A duration in . private TimeSpan TimeoutSortition(long round) { return TimeSpan.FromSeconds( @@ -426,6 +438,10 @@ private TimeSpan TimeoutSortition(long round) round * _contextTimeoutOption.SortitionMultiplier); } + /// + /// Gets the delay to gather s to determine . + /// + /// A duration in . private TimeSpan DelayLotGather() { return TimeSpan.FromSeconds(_contextTimeoutOption.LotGatherSecond); diff --git a/src/Libplanet.Net/Consensus/InvalidVoteException.cs b/src/Libplanet.Net/Consensus/InvalidVoteException.cs index 5d3c9201e8b..a5caf91e513 100644 --- a/src/Libplanet.Net/Consensus/InvalidVoteException.cs +++ b/src/Libplanet.Net/Consensus/InvalidVoteException.cs @@ -13,7 +13,7 @@ namespace Libplanet.Net.Consensus public class InvalidVoteException : Exception { /// - /// Initializes a new instance of class. + /// Initializes a new instance of class. /// /// The error message that explains the reason for the exception. /// @@ -31,7 +31,7 @@ public InvalidVoteException( } /// - /// Initializes a new instance of class. + /// Initializes a new instance of class. /// /// The error message that explains the reason for the exception. /// @@ -44,7 +44,7 @@ public InvalidVoteException(string message, Vote vote) } /// - /// Initializes a new instance of the + /// Initializes a new instance of the /// class with serialized data. /// /// The diff --git a/src/Libplanet.Net/Consensus/LotSet.cs b/src/Libplanet.Net/Consensus/LotSet.cs index f1b5ba616c9..c69802dba8d 100644 --- a/src/Libplanet.Net/Consensus/LotSet.cs +++ b/src/Libplanet.Net/Consensus/LotSet.cs @@ -23,6 +23,18 @@ public class LotSet private ConcurrentDictionary _dominantLots; private ConcurrentDictionary _lotsPower; + /// + /// Instantiates a new . + /// + /// The height of the consensus. + /// The round of the consensus. + /// The last + /// that has been decided on last round. + /// if last round is zero, it represents of the last block. + /// The of consensus. + /// The size of draw for selecting dominant lot. + /// if this value is too small, probability of selection can be close to even, + /// and influence of validator power can be reduced. public LotSet( long height, int round, Proof? lastProof, ValidatorSet validatorSet, int drawSize) : this( @@ -32,6 +44,14 @@ public LotSet( { } + /// + /// Instantiates a new . + /// + /// The information of the consensus. + /// The of consensus. + /// The size of draw for selecting dominant lot. + /// if this value is too small, probability of selection can be close to even, + /// and influence of validator power can be reduced. public LotSet( ConsensusInformation consensusInformation, ValidatorSet validatorSet, int drawSize) { @@ -50,25 +70,56 @@ public LotSet( Maj23 = null; } + /// + /// The height of the consensus. + /// public long Height => _consensusInformation.Height; + /// + /// The round of the consensus. + /// public int Round => _consensusInformation.Round; + /// + /// The last that has been decided on last round. + /// if last round is zero, it represents of the last block. + /// public Proof? LastProof => _consensusInformation.LastProof; - public ConsensusInformation Consensusinformation => _consensusInformation; + /// + /// The information of the consensus. + /// + public ConsensusInformation ConsensusInformation => _consensusInformation; + /// + /// The that has gain the majority +2/3 of the power of the validator set + /// by . + /// public Lot? Maj23 { get; private set; } + /// + /// The that is dominant among the gathered. + /// public Lot? DominantLot => _dominantLot?.Item1; + /// + /// The s that have been gathered. + /// public ImmutableDictionary Lots => _lots.ToImmutableDictionary(); + /// + /// The s that have been gathered. + /// public ImmutableDictionary DominantLots => _dominantLots.ToImmutableDictionary(); + /// + /// Sets the round of the . + /// + /// The round of the consensus. + /// The last proof that has been decided on the last round. public void SetRound(int round, Proof? lastProof) { _consensusInformation = new ConsensusInformation(Height, Math.Max(round, 0), lastProof); @@ -79,12 +130,31 @@ public void SetRound(int round, Proof? lastProof) Maj23 = null; } + /// + /// Generate a for the . + /// + /// The prover of the . + /// A that has been proved by , + /// with the . public Proof GenerateProof(PrivateKey privateKey) => _consensusInformation.Prove(privateKey); + /// + /// Generate a for the . + /// + /// The prover of the . + /// A that has been proved by , + /// with the . public Lot GenerateLot(PrivateKey privateKey) => _consensusInformation.ToLot(privateKey); + /// + /// Add a to the . + /// + /// A to be added. + /// Thrown when + /// is duplicated, or does not match with , + /// or prover does not belongs to the validator set. public void AddLot(Lot lot) { if (_lots.ContainsKey(lot.PublicKey)) @@ -99,6 +169,18 @@ public void AddLot(Lot lot) CompeteLot(lot); } + /// + /// Add a to the . + /// + /// A + /// to be added. + /// Thrown when + /// is duplicated, or does not belongs to the + /// validator set. + /// Thrown when of + /// does not match with + /// , or prover does not belongs to the validator set. + /// public void AddDominantLot(DominantLot dominantLot) { if (_dominantLots.ContainsKey(dominantLot.ValidatorPublicKey)) @@ -122,7 +204,7 @@ public void AddDominantLot(DominantLot dominantLot) UpdateMaj23(dominantLot); } - public void UpdateMaj23(DominantLot dominantLot) + private void UpdateMaj23(DominantLot dominantLot) { BigInteger validatorPower = _validatorSet.GetValidatorsPower( new List() { dominantLot.ValidatorPublicKey }); diff --git a/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs b/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs index 068163424dc..3ed5b360cb5 100644 --- a/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs +++ b/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs @@ -16,7 +16,10 @@ public class ConsensusDominantLotMsg : ConsensusMsg /// A /// that represents dominant of given height and round. public ConsensusDominantLotMsg(DominantLot dominantLot) - : base(dominantLot.ValidatorPublicKey, dominantLot.Height, dominantLot.Round) + : base( + dominantLot.ValidatorPublicKey, + dominantLot.Lot.ConsensusInformation.Height, + dominantLot.Lot.ConsensusInformation.Round) { DominantLot = dominantLot; } diff --git a/src/Libplanet/Consensus/ConsensusInformation.cs b/src/Libplanet/Consensus/ConsensusInformation.cs index af70c1d5eaf..b4cce9b136c 100644 --- a/src/Libplanet/Consensus/ConsensusInformation.cs +++ b/src/Libplanet/Consensus/ConsensusInformation.cs @@ -63,6 +63,11 @@ public ConsensusInformation( Encoded = Encode(height, round, lastProof); } + /// + /// Instantiates with byte array encoded form + /// . + /// + /// Byte array encoded . public ConsensusInformation(IReadOnlyList encoded) : this(_codec.Decode(encoded.ToArray())) { diff --git a/src/Libplanet/Consensus/DominantLotMetadata.cs b/src/Libplanet/Consensus/DominantLotMetadata.cs index d48e0639214..b33fdcb8e1c 100644 --- a/src/Libplanet/Consensus/DominantLotMetadata.cs +++ b/src/Libplanet/Consensus/DominantLotMetadata.cs @@ -12,12 +12,6 @@ public class DominantLotMetadata : IEquatable { private const string TimestampFormat = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; - private static readonly Binary HeightKey = - new Binary(new byte[] { 0x48 }); // 'H' - - private static readonly Binary RoundKey = - new Binary(new byte[] { 0x52 }); // 'R' - private static readonly Binary LotKey = new Binary(new byte[] { 0x4C }); // 'L' @@ -29,28 +23,18 @@ public class DominantLotMetadata : IEquatable private static readonly Codec _codec = new Codec(); + /// + /// Creates a new instance of . + /// + /// The lot that is dominant. + /// The time at which the dominant lot selected. + /// + /// The of the validator selected dominant lot. public DominantLotMetadata( - long height, - int round, Lot lot, DateTimeOffset timestamp, PublicKey validatorPublicKey) { - if (height < 0) - { - throw new ArgumentOutOfRangeException( - nameof(height), - "Height must be greater than or equal to 0."); - } - else if (round < 0) - { - throw new ArgumentOutOfRangeException( - nameof(round), - "Round must be greater than or equal to 0."); - } - - Height = height; - Round = round; Lot = lot; Timestamp = timestamp; ValidatorPublicKey = validatorPublicKey; @@ -59,8 +43,6 @@ public DominantLotMetadata( #pragma warning disable SA1118 // The parameter spans multiple lines public DominantLotMetadata(Dictionary encoded) : this( - height: (Integer)encoded[HeightKey], - round: (Integer)encoded[RoundKey], lot: new Lot(encoded[LotKey]), timestamp: DateTimeOffset.ParseExact( (Text)encoded[TimestampKey], @@ -73,29 +55,29 @@ public DominantLotMetadata(Dictionary encoded) #pragma warning restore SA1118 /// - /// A height of consensus. + /// The that is dominant. /// - public long Height { get; } + public Lot Lot { get; } /// - /// A round of consensus. + /// The time at which the dominant lot selected. /// - public int Round { get; } + public DateTimeOffset Timestamp { get; } /// - /// The of vote claim. + /// The of the validator selected dominant lot. /// - public Lot Lot { get; } + public PublicKey ValidatorPublicKey { get; } /// - /// The time at which the claim took place. + /// The height of the dominant lot. /// - public DateTimeOffset Timestamp { get; } + public long Height => Lot.Height; /// - /// A of claimant. + /// The round of the dominant lot. /// - public PublicKey ValidatorPublicKey { get; } + public int Round => Lot.Round; /// /// A Bencodex-encoded value of . @@ -106,8 +88,6 @@ public Dictionary Bencoded get { Dictionary encoded = Bencodex.Types.Dictionary.Empty - .Add(HeightKey, Height) - .Add(RoundKey, Round) .Add(LotKey, Lot.Bencoded) .Add( TimestampKey, @@ -118,8 +98,17 @@ public Dictionary Bencoded } } + /// + /// An immutable byte array encoded value of . + /// public ImmutableArray ByteArray => ToByteArray().ToImmutableArray(); + /// + /// Retrieve a byte array encoded value of . + /// + /// + /// A byte array encoded value of . + /// public byte[] ToByteArray() => _codec.Encode(Bencoded); /// diff --git a/src/Libplanet/Consensus/Lot.cs b/src/Libplanet/Consensus/Lot.cs index 459a7bc28c6..d636b63954c 100644 --- a/src/Libplanet/Consensus/Lot.cs +++ b/src/Libplanet/Consensus/Lot.cs @@ -76,10 +76,25 @@ private Lot(Dictionary bencoded) public PublicKey PublicKey { get; } /// - /// that has been proved by . + /// that has been proven by . /// public ConsensusInformation ConsensusInformation { get; } + /// + /// The height of the proven . + /// + public long Height => ConsensusInformation.Height; + + /// + /// The round of the proven . + /// + public int Round => ConsensusInformation.Round; + + /// + /// The last proof of the proven . + /// + public Proof? LastProof => ConsensusInformation.LastProof; + [JsonIgnore] public IValue Bencoded => Dictionary.Empty From db829f85321bf0e1c7c8fc6b0440137f57d942e5 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 11 Jul 2024 23:02:42 +0900 Subject: [PATCH 47/61] test: Fix tests --- .../ConsensusContextNonProposerTest.cs | 32 +++++--- .../Consensus/ConsensusContextTest.cs | 40 ++++++---- .../Consensus/ContextNonProposerTest.cs | 20 ++--- .../Consensus/ContextProposerTest.cs | 6 +- .../ContextProposerValidRoundTest.cs | 80 ++++++------------- .../Consensus/ContextTest.cs | 14 ++-- test/Libplanet.Net.Tests/TestUtils.cs | 6 +- .../Consensus/DominantLotMetadataTest.cs | 47 +++-------- .../Consensus/DominantLotTest.cs | 34 ++------ 9 files changed, 105 insertions(+), 174 deletions(-) diff --git a/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs index e7231e5e9de..0b076bfddb2 100644 --- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs @@ -58,13 +58,14 @@ public async void NewHeightWithLastCommit() }; consensusContext.Start(); - var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); foreach (int i in new int[] { 0, 1, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } var block1 = blockChain.ProposeBlock( @@ -110,7 +111,7 @@ public async void NewHeightWithLastCommit() { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 2L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } await heightTwoProposalSent.WaitAsync(); @@ -195,13 +196,14 @@ public async void HandleMessageFromHigherHeight() blockChain.Store.PutBlockCommit(TestUtils.CreateBlockCommit(blockChain[1])); - var lotSet = new LotSet(2L, 0, block.Proof, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); foreach (int i in new int[] { 0, 1, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 2L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } await proposalSent.WaitAsync(); @@ -269,13 +271,14 @@ in TestUtils.PrivateKeys.Zip( TestUtils.CreateBlockCommit(blockHeightTwo), TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[3])); - lotSet = new LotSet(3L, 0, blockHeightTwo.Proof, TestUtils.ValidatorSet, 20); + lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); foreach (int i in new int[] { 0, 1, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 3L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } // Message from higher height @@ -314,13 +317,14 @@ public async void UseLastCommitCacheIfHeightContextIsEmpty() }; consensusContext.Start(); - var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); foreach (int i in new int[] { 0, 1, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } Block block = blockChain.ProposeBlock( @@ -329,13 +333,14 @@ public async void UseLastCommitCacheIfHeightContextIsEmpty() var createdLastCommit = TestUtils.CreateBlockCommit(block); blockChain.Append(block, createdLastCommit); - lotSet = new LotSet(2L, 0, block.Proof, TestUtils.ValidatorSet, 20); + lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); foreach (int i in new int[] { 0, 1, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 2L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } // Context for height #2 where node #2 is the proposer is automatically started @@ -373,13 +378,14 @@ public async void NewHeightDelay() }; consensusContext.Start(); - var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); foreach (int i in new int[] { 0, 1, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock( diff --git a/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs index 02b6bfc430f..afea7d212b9 100644 --- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs @@ -83,13 +83,14 @@ public async void NewHeightIncreasing() } }; - var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); foreach (int i in new int[] { 0, 1, 2 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock( @@ -98,13 +99,14 @@ public async void NewHeightIncreasing() var blockCommit = TestUtils.CreateBlockCommit(block); blockChain.Append(block, blockCommit); - lotSet = new LotSet(2L, 0, block.Proof, TestUtils.ValidatorSet, 20); + lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); lot = lotSet.GenerateLot(TestUtils.PrivateKeys[2]); foreach (int i in new int[] { 0, 1, 2 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 2L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } block = blockChain.ProposeBlock( @@ -114,13 +116,14 @@ public async void NewHeightIncreasing() blockChain.Append(block, TestUtils.CreateBlockCommit(block)); Assert.Equal(2, blockChain.Tip.Index); - lotSet = new LotSet(3L, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); foreach (int i in new int[] { 0, 1, 2 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 3L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } // Wait for context of height 3 to start. @@ -167,13 +170,14 @@ public async void NewHeightIncreasing() await onTipChangedToThree.WaitAsync(); Assert.Equal(3, blockChain.Tip.Index); - lotSet = new LotSet(4L, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); foreach (int i in new int[] { 0, 1, 2 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 4L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } // Next height starts normally. @@ -279,13 +283,14 @@ public async void VoteSetGetOnlyProposeCommitHash() }; consensusContext.Start(); - var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); foreach (int i in new int[] { 0, 2, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } await heightOneProposalSent.WaitAsync(); @@ -341,13 +346,14 @@ public async Task GetVoteSetBits() TestUtils.ActionLoader, TestUtils.PrivateKeys[0]); consensusContext.Start(); - var lotSet = new LotSet(1L, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(proposer); foreach (int i in new int[] { 1, 2, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock( @@ -438,13 +444,14 @@ public async Task HandleVoteSetBits() }; consensusContext.Start(); - var lotSet = new LotSet(1L, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); foreach (int i in new int[] { 1, 2, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock( @@ -521,13 +528,14 @@ public async Task HandleProposalClaim() } }; consensusContext.Start(); - var lotSet = new LotSet(1L, 0, null, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[1]); foreach (int i in new int[] { 1, 2, 3 }) { consensusContext.HandleMessage( new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], 1L, 0, lot))); + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock( diff --git a/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs index cfdc25737ff..c01bdc5e22b 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs @@ -68,7 +68,7 @@ public async Task EnterPreVoteBlockOneThird() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1], proof: lot.Proof); @@ -134,7 +134,7 @@ public async Task EnterPreCommitBlockTwoThird() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1], proof: lot.Proof); @@ -235,7 +235,7 @@ public async void EnterPreCommitNilTwoThird() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } context.ProduceMessage( @@ -331,7 +331,7 @@ public async Task EnterPreVoteNilOnInvalidBlockHeader() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } context.ProduceMessage( @@ -414,7 +414,7 @@ public async Task EnterPreVoteNilOnInvalidBlockContent() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } context.ProduceMessage( @@ -512,7 +512,7 @@ message is ConsensusPreCommitMsg commit && context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } context.ProduceMessage( @@ -659,7 +659,7 @@ public async Task UponRulesCheckAfterTimeout() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } lotSet.SetRound(1, lot.Proof); @@ -669,7 +669,7 @@ public async Task UponRulesCheckAfterTimeout() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 1, lot))); + TestUtils.PrivateKeys[i], lot))); } // Push round 0 and round 1 proposes. @@ -754,7 +754,7 @@ public async Task TimeoutPreVote() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock( @@ -819,7 +819,7 @@ public async Task TimeoutPreCommit() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock( diff --git a/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs index ed6e6b4da69..a113e74c8aa 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextProposerTest.cs @@ -130,7 +130,7 @@ public async void EnterPreCommitBlock() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } // Wait for propose to process. @@ -259,7 +259,7 @@ public async Task EndCommitBlock() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } // Wait for propose to process. @@ -358,7 +358,7 @@ public async void EnterPreVoteBlock() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } await proposalSent.WaitAsync(); diff --git a/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs index 00c9043cb72..1cb7539d48c 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextProposerValidRoundTest.cs @@ -42,7 +42,8 @@ public async Task EnterValidRoundPreVoteBlock() var stateChangedToRoundTwoPropose = new AsyncAutoResetEvent(); bool timeoutProcessed = false; - var (_, context) = TestUtils.CreateDummyContext(privateKey: TestUtils.PrivateKeys[0]); + var (blockChain, context) = TestUtils.CreateDummyContext( + privateKey: TestUtils.PrivateKeys[0]); context.StateChanged += (_, eventArgs) => { if (eventArgs.Round == 2 && eventArgs.Step == ConsensusStep.Sortition) @@ -72,29 +73,15 @@ prevote.BlockHash is { } hash && }; context.Start(); - var lotSet = new LotSet(1, 0, null, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[0]); - context.ProduceMessage( - new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[1], - 1, - 0, - lot))); - context.ProduceMessage( - new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[2], - 1, - 0, - lot))); - context.ProduceMessage( - new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[3], - 1, - 0, - lot))); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); + } await Task.Delay(1000); await proposalSent.WaitAsync(); @@ -122,27 +109,13 @@ prevote.BlockHash is { } hash && await stateChangedToRoundTwoSortition.WaitAsync(); lotSet.SetRound(2, lot.Proof); - context.ProduceMessage( - new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[1], - 1, - 2, - lotSet.GenerateLot(TestUtils.PrivateKeys[3])))); - context.ProduceMessage( - new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[2], - 1, - 2, - lotSet.GenerateLot(TestUtils.PrivateKeys[3])))); - context.ProduceMessage( - new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[3], - 1, - 2, - lotSet.GenerateLot(TestUtils.PrivateKeys[3])))); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); + } await stateChangedToRoundTwoPropose.WaitAsync(); Assert.Equal(2, context.Round); @@ -254,14 +227,14 @@ public async void EnterValidRoundPreVoteNil() key); context.Start(); - var lotSet = new LotSet(1, 0, null, TestUtils.ValidatorSet, 20); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[0]); - foreach (int i in Enumerable.Range(1, 3)) + foreach (int i in new int[] { 1, 2, 3 }) { context.ProduceMessage( - new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], 1, 0, lot))); + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } await proposalSent.WaitAsync(); @@ -289,12 +262,11 @@ public async void EnterValidRoundPreVoteNil() await stateChangedToRoundTwoSortition.WaitAsync(); lotSet.SetRound(2, lot.Proof); lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); - foreach (int i in Enumerable.Range(1, 3)) + foreach (int i in new int[] { 1, 2, 3 }) { context.ProduceMessage( - new ConsensusDominantLotMsg( - TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], 1, 2, lot))); + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot))); } await stateChangedToRoundTwoPropose.WaitAsync(); @@ -344,7 +316,7 @@ public async void EnterValidRoundPreVoteNil() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], 1, 3, lot))); + TestUtils.PrivateKeys[i], lot))); } await stateChangedToRoundThreePropose.WaitAsync(); diff --git a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs index 9bdb6568ddc..001d2f217b8 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs @@ -78,7 +78,7 @@ public async Task StartAsProposer() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } await Task.WhenAll(proposalSent.WaitAsync(), stepChangedToPreVote.WaitAsync()); @@ -135,7 +135,7 @@ public async Task StartAsProposerWithLastCommit() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } await Task.WhenAll(stepChangedToPreVote.WaitAsync(), proposalSent.WaitAsync()); @@ -229,7 +229,7 @@ public async Task CanAcceptMessagesAfterCommitFailure() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } await Task.WhenAll(stepChangedToPreVote.WaitAsync(), proposalSent.WaitAsync()); @@ -307,7 +307,7 @@ public async Task ThrowOnInvalidProposerMessage() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } var block = blockChain.ProposeBlock( @@ -437,7 +437,7 @@ public async Task CanPreCommitOnEndCommit() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } context.ProduceMessage( @@ -596,7 +596,7 @@ public async Task CanReplaceProposal() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - privateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + privateKeys[i], lot))); } await stepChanged.WaitAsync(); @@ -754,7 +754,7 @@ public async Task CanCreateContextWithLastingEvaluation() context.ProduceMessage( new ConsensusDominantLotMsg( TestUtils.CreateDominantLot( - TestUtils.PrivateKeys[i], blockChain.Tip.Index + 1, 0, lot))); + TestUtils.PrivateKeys[i], lot))); } context.ProduceMessage( diff --git a/test/Libplanet.Net.Tests/TestUtils.cs b/test/Libplanet.Net.Tests/TestUtils.cs index ad538f3d825..64fa434023e 100644 --- a/test/Libplanet.Net.Tests/TestUtils.cs +++ b/test/Libplanet.Net.Tests/TestUtils.cs @@ -105,7 +105,7 @@ public static (PrivateKey Proposer, Proof Proof) GetProposer( var lotSet = new LotSet(height, round, lastProof, validatorSet, 20); foreach (var privateKey in privateKeys) { - lotSet.AddLot(lotSet.Consensusinformation.ToLot(privateKey)); + lotSet.AddLot(lotSet.ConsensusInformation.ToLot(privateKey)); } return (privateKeys.First( @@ -115,12 +115,8 @@ public static (PrivateKey Proposer, Proof Proof) GetProposer( public static DominantLot CreateDominantLot( PrivateKey privateKey, - long height, - int round, Lot lot) => new DominantLotMetadata( - height, - round, lot, DateTimeOffset.Now, privateKey.PublicKey).Sign(privateKey); diff --git a/test/Libplanet.Tests/Consensus/DominantLotMetadataTest.cs b/test/Libplanet.Tests/Consensus/DominantLotMetadataTest.cs index 44f2b5d45f7..910e0ddffaa 100644 --- a/test/Libplanet.Tests/Consensus/DominantLotMetadataTest.cs +++ b/test/Libplanet.Tests/Consensus/DominantLotMetadataTest.cs @@ -18,23 +18,16 @@ public DominantLotMetadataTest() _signer = new PrivateKey(); _lot = new ConsensusInformation(0, 0, null).ToLot(_prover); _dominantLotMetadata = new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey); + _lot, DateTimeOffset.MinValue, _signer.PublicKey); } [Fact] public void Constructor() { - Assert.Throws( - () => new DominantLotMetadata( - -1, 0, _lot, DateTimeOffset.UtcNow, _signer.PublicKey)); - Assert.Throws( - () => new DominantLotMetadata( - 0, -1, _lot, DateTimeOffset.UtcNow, _signer.PublicKey)); - var dominantLotMetadata = new DominantLotMetadata( - 1, 2, _lot, DateTimeOffset.UtcNow, _signer.PublicKey); - Assert.Equal(1, dominantLotMetadata.Height); - Assert.Equal(2, dominantLotMetadata.Round); + _lot, DateTimeOffset.UtcNow, _signer.PublicKey); + Assert.Equal(0, dominantLotMetadata.Height); + Assert.Equal(0, dominantLotMetadata.Round); Assert.Equal(_lot, dominantLotMetadata.Lot); Assert.Equal(_signer.PublicKey, dominantLotMetadata.ValidatorPublicKey); Assert.Equal( @@ -58,31 +51,21 @@ public void Equal() Assert.Equal( _dominantLotMetadata, new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey)); - Assert.NotEqual( - _dominantLotMetadata, - new DominantLotMetadata( - 1, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey)); - Assert.NotEqual( - _dominantLotMetadata, - new DominantLotMetadata( - 0, 1, _lot, DateTimeOffset.MinValue, _signer.PublicKey)); + _lot, DateTimeOffset.MinValue, _signer.PublicKey)); Assert.NotEqual( _dominantLotMetadata, new DominantLotMetadata( - 0, - 0, new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()), DateTimeOffset.MinValue, _signer.PublicKey)); Assert.NotEqual( _dominantLotMetadata, new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MaxValue, _signer.PublicKey)); + _lot, DateTimeOffset.MaxValue, _signer.PublicKey)); Assert.NotEqual( _dominantLotMetadata, new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MinValue, new PrivateKey().PublicKey)); + _lot, DateTimeOffset.MinValue, new PrivateKey().PublicKey)); } [Fact] @@ -91,31 +74,21 @@ public void HashCode() Assert.Equal( _dominantLotMetadata.GetHashCode(), new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey).GetHashCode()); - Assert.NotEqual( - _dominantLotMetadata.GetHashCode(), - new DominantLotMetadata( - 1, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey).GetHashCode()); - Assert.NotEqual( - _dominantLotMetadata.GetHashCode(), - new DominantLotMetadata( - 0, 1, _lot, DateTimeOffset.MinValue, _signer.PublicKey).GetHashCode()); + _lot, DateTimeOffset.MinValue, _signer.PublicKey).GetHashCode()); Assert.NotEqual( _dominantLotMetadata.GetHashCode(), new DominantLotMetadata( - 0, - 0, new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()), DateTimeOffset.MinValue, _signer.PublicKey).GetHashCode()); Assert.NotEqual( _dominantLotMetadata.GetHashCode(), new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MaxValue, _signer.PublicKey).GetHashCode()); + _lot, DateTimeOffset.MaxValue, _signer.PublicKey).GetHashCode()); Assert.NotEqual( _dominantLotMetadata.GetHashCode(), new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MinValue, new PrivateKey().PublicKey).GetHashCode()); + _lot, DateTimeOffset.MinValue, new PrivateKey().PublicKey).GetHashCode()); } } } diff --git a/test/Libplanet.Tests/Consensus/DominantLotTest.cs b/test/Libplanet.Tests/Consensus/DominantLotTest.cs index e84240e2c4d..a88e7295a32 100644 --- a/test/Libplanet.Tests/Consensus/DominantLotTest.cs +++ b/test/Libplanet.Tests/Consensus/DominantLotTest.cs @@ -20,7 +20,7 @@ public DominantLotTest() _signer = new PrivateKey(); _lot = new ConsensusInformation(0, 0, null).ToLot(_prover); _dominantLotMetadata = new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey); + _lot, DateTimeOffset.MinValue, _signer.PublicKey); _dominantLot = _dominantLotMetadata.Sign(_signer); } @@ -65,18 +65,6 @@ public void Equal() Assert.NotEqual( _dominantLot, new DominantLotMetadata( - 1, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey) - .Sign(_signer)); - Assert.NotEqual( - _dominantLot, - new DominantLotMetadata( - 0, 1, _lot, DateTimeOffset.MinValue, _signer.PublicKey) - .Sign(_signer)); - Assert.NotEqual( - _dominantLot, - new DominantLotMetadata( - 0, - 0, new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()), DateTimeOffset.MinValue, _signer.PublicKey) @@ -84,13 +72,13 @@ public void Equal() Assert.NotEqual( _dominantLot, new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MaxValue, _signer.PublicKey) + _lot, DateTimeOffset.MaxValue, _signer.PublicKey) .Sign(_signer)); var stranger = new PrivateKey(); Assert.NotEqual( _dominantLot, new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MinValue, stranger.PublicKey) + _lot, DateTimeOffset.MinValue, stranger.PublicKey) .Sign(stranger)); } @@ -104,18 +92,6 @@ public void HashCode() Assert.NotEqual( _dominantLot.GetHashCode(), new DominantLotMetadata( - 1, 0, _lot, DateTimeOffset.MinValue, _signer.PublicKey) - .Sign(_signer).GetHashCode()); - Assert.NotEqual( - _dominantLot.GetHashCode(), - new DominantLotMetadata( - 0, 1, _lot, DateTimeOffset.MinValue, _signer.PublicKey) - .Sign(_signer).GetHashCode()); - Assert.NotEqual( - _dominantLot.GetHashCode(), - new DominantLotMetadata( - 0, - 0, new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()), DateTimeOffset.MinValue, _signer.PublicKey) @@ -123,13 +99,13 @@ public void HashCode() Assert.NotEqual( _dominantLot.GetHashCode(), new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MaxValue, _signer.PublicKey) + _lot, DateTimeOffset.MaxValue, _signer.PublicKey) .Sign(_signer).GetHashCode()); var stranger = new PrivateKey(); Assert.NotEqual( _dominantLot.GetHashCode(), new DominantLotMetadata( - 0, 0, _lot, DateTimeOffset.MinValue, stranger.PublicKey) + _lot, DateTimeOffset.MinValue, stranger.PublicKey) .Sign(stranger).GetHashCode()); } } From 2cb42b907264553a248d909be60709cb3ced324b Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 11 Jul 2024 23:35:31 +0900 Subject: [PATCH 48/61] document: Update changelog --- CHANGES.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0b6cd5a6b14..81cb6a99929 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ To be released. ### Deprecated APIs + - `ValidatorSet.GetProposer()` has been removed. [[#VRF]] + ### Backward-incompatible API changes ### Backward-incompatible network protocol changes @@ -32,20 +34,26 @@ To be released. - `BlockMetadata.CurrentProtocolVersion` has been changed from 5 to 6. [[#VRF]] - Added `IBlockMetadata.Proof` property. [[#VRF]] - - Added `PreProposal` class as a content of message that suggests - `PreEvaluationBlock` as a `Proposal` candidate during - `ConsensusStep.PrePropose`. [[#VRF]] - - Added `PreProposalMetadata` class as a metadata of `PreProposal`. [[#VRF]] - - Added `ConsensusStep.PrePropose`. [[#VRF]] - - Added `ConsensusPreProposalMsg` class as a `ConsensusMsg` broadcasted during - `ConsensusStep.PrePropose`. [[#VRF]] - - Added `PreProposalSet` class as a `PreProposal` selector. + - Added `Lot` class as a content of message that submits a `Proof` + to be a proposer candidate during `ConsensusStep.Sortition`. [[#VRF]] + - Added `DominantLot` class as a content of message that submits a vote for + dominant `Lot` during `ConsensusStep.Sortition`. [[#VRF]]` + - Added `ConsensusStep.Sortition`. [[#VRF]] + - Added `LotGatherSecond`, `SortitionSecondBase`, `SortitionMultiplier` + to `ContestTimeoutOption`. [[#VRF]] + - Added `ConsensusLotMsg` class as a `ConsensusMsg` broadcasted during + `ConsensusStep.Sortition`. [[#VRF]] + - Added `ConsensusDominantLotMsg` class as a `ConsensusMsg` broadcasted during + `ConsensusStep.Sortition`. after lot gathering delay. [[#VRF]] + - Added `LotSet` class as a `Lot` and `DominantLot` selector. [[#VRF]] ### Behavioral changes - `ActionEvaluator.EvaluateActions()` use `Proof.Seed` as a initial random seed instead of `PreEvaluationHash`, `signature` combined seed. [[#VRF]] + - Proposer selection is now based on the VRF result. [[#VRF]] + - Consensus now starts with `ConsensusStep.Sortition`. [[#VRF]]` ### Bug fixes From df216514befd8ab7553355b88c163eb368ec3847 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 11 Jul 2024 23:37:33 +0900 Subject: [PATCH 49/61] chore: Fix typo --- src/Libplanet.Net/Consensus/Context.Mutate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libplanet.Net/Consensus/Context.Mutate.cs b/src/Libplanet.Net/Consensus/Context.Mutate.cs index 67cb62635ce..d4e867e941e 100644 --- a/src/Libplanet.Net/Consensus/Context.Mutate.cs +++ b/src/Libplanet.Net/Consensus/Context.Mutate.cs @@ -56,7 +56,7 @@ private void StartRound(int round) /// the internal does not accept it, i.e. /// returns . /// An or - /// can be trown when the internal does not accept it, i.e. + /// can be thrown when the internal does not accept it, i.e. /// or /// returns . /// From 2e61f3336ef9bcadbea73614c708d3c437c6adbc Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 12 Jul 2024 10:56:22 +0900 Subject: [PATCH 50/61] chore: Minor fixes --- src/Libplanet.Crypto/Proof.cs | 4 +--- src/Libplanet.Net/Consensus/LotSet.cs | 7 +++++++ src/Libplanet/Consensus/Lot.cs | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index 3d82853ff1d..b29f62c1f86 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -257,9 +257,7 @@ public override bool Equals(object? obj) /// public int CompareTo(Proof other) - => other is Proof otherProof - ? (HashInt - otherProof.HashInt).Sign - : throw new ArgumentException($"Argument {nameof(other)} is null"); + => (HashInt - other.HashInt).Sign; /// public int CompareTo(object? obj) diff --git a/src/Libplanet.Net/Consensus/LotSet.cs b/src/Libplanet.Net/Consensus/LotSet.cs index c69802dba8d..dbc295ec042 100644 --- a/src/Libplanet.Net/Consensus/LotSet.cs +++ b/src/Libplanet.Net/Consensus/LotSet.cs @@ -122,6 +122,13 @@ public ImmutableDictionary DominantLots /// The last proof that has been decided on the last round. public void SetRound(int round, Proof? lastProof) { + if (round <= Round) + { + throw new ArgumentException( + $"Parameter round must be greater than the current round {Round}.", + nameof(round)); + } + _consensusInformation = new ConsensusInformation(Height, Math.Max(round, 0), lastProof); _lots.Clear(); _dominantLot = null; diff --git a/src/Libplanet/Consensus/Lot.cs b/src/Libplanet/Consensus/Lot.cs index d636b63954c..81b2cba95f4 100644 --- a/src/Libplanet/Consensus/Lot.cs +++ b/src/Libplanet/Consensus/Lot.cs @@ -10,7 +10,7 @@ namespace Libplanet.Consensus { - public readonly struct Lot : IEquatable, IBencodable + public readonly struct Lot : IBencodable, IEquatable { private static readonly Binary ProofKey = new Binary(new byte[] { 0x50 }); // 'P' From 2a2a6838bc624dde21702446a0fa166c64d299ec Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 12 Jul 2024 10:56:36 +0900 Subject: [PATCH 51/61] test: Add LotSetTest --- .../Consensus/LotSetTest.cs | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 test/Libplanet.Net.Tests/Consensus/LotSetTest.cs diff --git a/test/Libplanet.Net.Tests/Consensus/LotSetTest.cs b/test/Libplanet.Net.Tests/Consensus/LotSetTest.cs new file mode 100644 index 00000000000..e09911d408e --- /dev/null +++ b/test/Libplanet.Net.Tests/Consensus/LotSetTest.cs @@ -0,0 +1,196 @@ +using System; +using System.Linq; +using Libplanet.Consensus; +using Libplanet.Crypto; +using Libplanet.Net.Consensus; +using Xunit; + +namespace Libplanet.Net.Tests.Consensus +{ + public class LotSetTest + { + private const int DrawSize = 20; + private readonly Proof _lastProof; + private readonly ConsensusInformation _consensusInformation; + private readonly LotSet _lotSet; + + public LotSetTest() + { + _lastProof = new ConsensusInformation(1L, 1, null).Prove(new PrivateKey()); + _consensusInformation = new ConsensusInformation(1L, 2, _lastProof); + _lotSet = new LotSet(_consensusInformation, TestUtils.ValidatorSet, DrawSize); + } + + [Fact] + public void Constructor() + { + var lotSet = new LotSet( + 1L, + 2, + _lastProof, + TestUtils.ValidatorSet, + DrawSize); + + Assert.Equal(1L, lotSet.Height); + Assert.Equal(2, lotSet.Round); + Assert.Equal(_lastProof, lotSet.LastProof); + Assert.Equal(_consensusInformation, lotSet.ConsensusInformation); + Assert.Null(lotSet.Maj23); + Assert.Null(lotSet.DominantLot); + Assert.Empty(lotSet.Lots); + Assert.Empty(lotSet.DominantLots); + } + + [Fact] + public void SetRound() + { + _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[0])); + _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[1])); + _lotSet.AddDominantLot( + new DominantLotMetadata( + _lotSet.Lots[TestUtils.PrivateKeys[0].PublicKey], + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[1].PublicKey).Sign(TestUtils.PrivateKeys[1])); + _lotSet.AddDominantLot( + new DominantLotMetadata( + _lotSet.Lots[TestUtils.PrivateKeys[0].PublicKey], + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[2].PublicKey).Sign(TestUtils.PrivateKeys[2])); + _lotSet.AddDominantLot( + new DominantLotMetadata( + _lotSet.Lots[TestUtils.PrivateKeys[0].PublicKey], + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[3].PublicKey).Sign(TestUtils.PrivateKeys[3])); + + Assert.NotNull(_lotSet.Maj23); + Assert.NotNull(_lotSet.DominantLot); + Assert.Equal(2, _lotSet.Lots.Count); + Assert.Equal(3, _lotSet.DominantLots.Count); + + Assert.Throws(() => _lotSet.SetRound(1, null)); + _lotSet.SetRound(3, null); + Assert.Null(_lotSet.Maj23); + Assert.Null(_lotSet.DominantLot); + Assert.Empty(_lotSet.Lots); + Assert.Empty(_lotSet.DominantLots); + } + + [Fact] + public void GenerateProof() + { + var prover = new PrivateKey(); + var proof = _lotSet.GenerateProof(prover); + Assert.Equal(_consensusInformation.Prove(prover), proof); + Assert.True(_consensusInformation.Verify(proof, prover.PublicKey)); + } + + [Fact] + public void GenerateLot() + { + var prover = new PrivateKey(); + var lot = _lotSet.GenerateLot(prover); + Assert.Equal(_consensusInformation.ToLot(prover), lot); + Assert.Equal(1L, lot.Height); + Assert.Equal(2, lot.Round); + Assert.Equal(_lastProof, lot.LastProof); + Assert.Equal(_consensusInformation, lot.ConsensusInformation); + Assert.Equal(prover.PublicKey, lot.PublicKey); + } + + [Fact] + public void AddLot() + { + Assert.Throws( + () => _lotSet.AddLot(_consensusInformation.ToLot(new PrivateKey()))); + Assert.Throws( + () => _lotSet.AddLot( + new ConsensusInformation(0L, 0, null) + .ToLot(TestUtils.PrivateKeys[0]))); + _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[0])); + Assert.Throws( + () => _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[0]))); + Assert.Single(_lotSet.Lots); + _lotSet.DominantLot.Equals(_lotSet.Lots[TestUtils.PrivateKeys[0].PublicKey]); + _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[1])); + _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[2])); + _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[3])); + Assert.Equal(4, _lotSet.Lots.Count); + Lot dominantLot = (Lot)_lotSet.DominantLot!; + var nonDominantLots + = _lotSet.Lots.Remove(dominantLot.PublicKey).Select(e => e.Value); + Assert.All(nonDominantLots, lot => Assert.True(FormerLotWins(dominantLot, lot))); + } + + [Fact] + public void AddDominantLot() + { + var nonValidator = new PrivateKey(); + Assert.Throws( + () => _lotSet.AddDominantLot( + new DominantLotMetadata( + _consensusInformation.ToLot(TestUtils.PrivateKeys[0]), + DateTimeOffset.MinValue, + nonValidator.PublicKey).Sign(nonValidator))); + Assert.Throws( + () => _lotSet.AddDominantLot( + new DominantLotMetadata( + _consensusInformation.ToLot(new PrivateKey()), + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[0].PublicKey).Sign(TestUtils.PrivateKeys[0]))); + Assert.Throws( + () => _lotSet.AddDominantLot( + new DominantLotMetadata( + new ConsensusInformation(0L, 0, null) + .ToLot(TestUtils.PrivateKeys[0]), + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[0].PublicKey).Sign(TestUtils.PrivateKeys[0]))); + var lotByValidator0 = _consensusInformation.ToLot(TestUtils.PrivateKeys[0]); + _lotSet.AddDominantLot(new DominantLotMetadata( + lotByValidator0, + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[0].PublicKey).Sign(TestUtils.PrivateKeys[0])); + Assert.Throws( + () => _lotSet.AddDominantLot( + new DominantLotMetadata( + lotByValidator0, + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[0].PublicKey).Sign(TestUtils.PrivateKeys[0]))); + Assert.Throws( + () => _lotSet.AddDominantLot( + new DominantLotMetadata( + _consensusInformation.ToLot(TestUtils.PrivateKeys[1]), + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[0].PublicKey).Sign(TestUtils.PrivateKeys[0]))); + Assert.Single(_lotSet.DominantLots); + Assert.Null(_lotSet.Maj23); + _lotSet.AddDominantLot(new DominantLotMetadata( + lotByValidator0, + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[1].PublicKey).Sign(TestUtils.PrivateKeys[1])); + _lotSet.AddDominantLot(new DominantLotMetadata( + _consensusInformation.ToLot(TestUtils.PrivateKeys[1]), + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[2].PublicKey).Sign(TestUtils.PrivateKeys[2])); + Assert.Equal(3, _lotSet.DominantLots.Count); + _lotSet.AddDominantLot(new DominantLotMetadata( + lotByValidator0, + DateTimeOffset.MinValue, + TestUtils.PrivateKeys[3].PublicKey).Sign(TestUtils.PrivateKeys[3])); + Assert.Equal(lotByValidator0, _lotSet.Maj23); + } + + private static bool FormerLotWins(Lot former, Lot latter) + { + var formerDraw = former.Proof.Draw( + DrawSize, + TestUtils.ValidatorSet.GetValidator(former.PublicKey).Power, + TestUtils.ValidatorSet.TotalPower); + var latterDraw = latter.Proof.Draw( + DrawSize, + TestUtils.ValidatorSet.GetValidator(former.PublicKey).Power, + TestUtils.ValidatorSet.TotalPower); + return formerDraw > latterDraw + || (formerDraw == latterDraw && former.Proof > latter.Proof); + } + } +} From e67ac0f4c2785a13bc40cb7863eb6d89995b0928 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 12 Jul 2024 20:18:34 +0900 Subject: [PATCH 52/61] feat: Allow -1 round to ConsensusInformation --- src/Libplanet.Net/Consensus/LotSet.cs | 4 +- .../Consensus/ConsensusInformation.cs | 71 ++++++------------- 2 files changed, 24 insertions(+), 51 deletions(-) diff --git a/src/Libplanet.Net/Consensus/LotSet.cs b/src/Libplanet.Net/Consensus/LotSet.cs index dbc295ec042..2ba7d70927e 100644 --- a/src/Libplanet.Net/Consensus/LotSet.cs +++ b/src/Libplanet.Net/Consensus/LotSet.cs @@ -38,7 +38,7 @@ public class LotSet public LotSet( long height, int round, Proof? lastProof, ValidatorSet validatorSet, int drawSize) : this( - new ConsensusInformation(height, Math.Max(round, 0), lastProof), + new ConsensusInformation(height, round, lastProof), validatorSet, drawSize) { @@ -129,7 +129,7 @@ public void SetRound(int round, Proof? lastProof) nameof(round)); } - _consensusInformation = new ConsensusInformation(Height, Math.Max(round, 0), lastProof); + _consensusInformation = new ConsensusInformation(Height, round, lastProof); _lots.Clear(); _dominantLot = null; _dominantLots.Clear(); diff --git a/src/Libplanet/Consensus/ConsensusInformation.cs b/src/Libplanet/Consensus/ConsensusInformation.cs index b4cce9b136c..1ea5fca8e42 100644 --- a/src/Libplanet/Consensus/ConsensusInformation.cs +++ b/src/Libplanet/Consensus/ConsensusInformation.cs @@ -51,10 +51,10 @@ public ConsensusInformation( throw new ArgumentException( $"Given {nameof(height)} cannot be negative: {height}"); } - else if (round < 0) + else if (round < -1) { throw new ArgumentException( - $"Given {nameof(round)} cannot be negative: {round}"); + $"Given {nameof(round)} cannot be less than -1: {round}"); } Height = height; @@ -119,51 +119,6 @@ private ConsensusInformation(Dictionary bencoded) /// public ImmutableArray Encoded { get; } - /// - /// Generate a with given consensus information and - /// . - /// - /// - /// Height of the consensus where - /// participate in the draw of the - /// . - /// - /// Round of the consensus where - /// participate in the draw of the . - /// - /// that has been decided on the previous round. - /// - /// to prove given information. - /// that has been proved by . - /// - public static Proof Prove(long height, int round, Proof? lastProof, PrivateKey prover) - => new ConsensusInformation(height, round, lastProof).Prove(prover); - - /// - /// Verify the with given consensus information and - /// . - /// - /// - /// Height of the consensus where - /// participate in the draw of the - /// . - /// - /// Round of the consensus where - /// participate in the draw of the . - /// - /// that has been decided on the previous round. - /// - /// to verify. - /// - /// which corresponds to prover of - /// . - /// - /// if verified properly , otherwise . - /// - public static bool Verify( - long height, int round, Proof? lastProof, Proof proof, PublicKey verifier) - => new ConsensusInformation(height, round, lastProof).Verify(proof, verifier); - /// /// Generate a with and /// . @@ -188,7 +143,16 @@ public Lot ToLot(PrivateKey prover) /// /// that has been proved by . public Proof Prove(PrivateKey prover) - => prover.Prove(Encoded); + { + if (Round < 0) + { + throw new InvalidOperationException( + $"Cannot prove {nameof(ConsensusInformation)} " + + $"with negative {nameof(Round)}: {Round}"); + } + + return prover.Prove(Encoded); + } /// /// Verify the with and @@ -204,7 +168,16 @@ public Proof Prove(PrivateKey prover) /// if verified properly , otherwise . /// public bool Verify(Proof proof, PublicKey verifier) - => verifier.VerifyProof(Encoded, proof); + { + if (Round < 0) + { + throw new InvalidOperationException( + $"Cannot verify {nameof(ConsensusInformation)} " + + $"with negative {nameof(Round)}: {Round}"); + } + + return verifier.VerifyProof(Encoded, proof); + } /// public bool Equals(ConsensusInformation other) From ef525a2ebb0aeb8207189b92aec236c5f04c5f10 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 12 Jul 2024 20:19:02 +0900 Subject: [PATCH 53/61] test: Add sortition tests --- .../Consensus/ContextTest.cs | 170 +++++++++++++++++- .../Consensus/ConsensusInformationTest.cs | 16 +- 2 files changed, 184 insertions(+), 2 deletions(-) diff --git a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs index 001d2f217b8..c7c98fa1392 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs @@ -537,7 +537,7 @@ public async Task CanPreCommitOnEndCommit() /// /// [Fact(Timeout = Timeout)] - public async Task CanReplaceProposal() + public async void CanReplaceProposal() { var codec = new Codec(); var privateKeys = Enumerable.Range(0, 4).Select(_ => new PrivateKey()).ToArray(); @@ -798,6 +798,174 @@ public async Task CanCreateContextWithLastingEvaluation() Assert.Equal(2, consensusContext.Height); } + [Fact] + public async void BroadcastLot() + { + var lotSent = new AsyncAutoResetEvent(); + Lot? lotPublished = null; + var (blockChain, context) = TestUtils.CreateDummyContext( + height: 1, + privateKey: TestUtils.PrivateKeys[0]); + + context.MessageToPublish += (_, message) => + { + if (message is ConsensusLotMsg consensusLotMsg) + { + lotPublished = consensusLotMsg.Lot; + lotSent.Set(); + } + }; + + context.Start(); + + await lotSent.WaitAsync(); + Assert.NotNull(lotPublished); + + Lot lot = lotPublished.Value; + Assert.Equal(1L, lot.Height); + Assert.Equal(0, lot.Round); + Assert.Equal(blockChain.Tip.Proof, lot.LastProof); + Assert.Equal(TestUtils.PrivateKeys[0].PublicKey, lot.PublicKey); + } + + [Fact] + public async void BroadcastDominantLot() + { + var dominantLotSent = new AsyncAutoResetEvent(); + DominantLot? dominantLotPublished = null; + var (blockChain, context) = TestUtils.CreateDummyContext( + height: 1, + privateKey: TestUtils.PrivateKeys[0]); + + context.MessageToPublish += (_, message) => + { + if (message is ConsensusDominantLotMsg consensusDominantLotMsg) + { + dominantLotPublished = consensusDominantLotMsg.DominantLot; + dominantLotSent.Set(); + } + }; + + context.Start(); + + var lots = Enumerable.Range(0, 4).Select( + i => new ConsensusInformation(1L, 0, blockChain.Tip.Proof) + .ToLot(TestUtils.PrivateKeys[i])); + + var dominantLotAmongFour = lots.OrderBy( + lot => lot.Proof.Draw( + 20, + TestUtils.ValidatorSet.GetValidator(lot.PublicKey).Power, + TestUtils.ValidatorSet.TotalPower)) + .ThenBy(lot => lot.Proof).LastOrDefault(); + + foreach (var lot in lots.Skip(1)) + { + context.ProduceMessage(new ConsensusLotMsg(lot)); + } + + await dominantLotSent.WaitAsync(); + Assert.NotNull(dominantLotPublished); + + DominantLot dominantLot = dominantLotPublished; + Assert.Equal(1L, dominantLot.Height); + Assert.Equal(0, dominantLot.Round); + Assert.Equal(TestUtils.PrivateKeys[0].PublicKey, dominantLot.ValidatorPublicKey); + Assert.Equal(dominantLotAmongFour, dominantLot.Lot); + } + + [Fact] + public async void SetProposerWithMajorityDominantLot() + { + var enteredPropose = new AsyncAutoResetEvent(); + var (blockChain, context) = TestUtils.CreateDummyContext( + height: 1, + privateKey: TestUtils.PrivateKeys[0]); + context.StateChanged += (_, eventArgs) => + { + if (eventArgs.Step == ConsensusStep.Propose) + { + enteredPropose.Set(); + } + }; + + context.Start(); + + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[0]); + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], lot))); + } + + await enteredPropose.WaitAsync(); + Assert.Equal(TestUtils.PrivateKeys[0].PublicKey, context.Proposer); + } + + [Fact] + public async void IncreaseRoundAfterTimeoutSortition() + { + var timeoutSortition = new AsyncAutoResetEvent(); + var enteredRoundTwo = new AsyncAutoResetEvent(); + var (blockChain, context) = TestUtils.CreateDummyContext( + height: 1, + privateKey: TestUtils.PrivateKeys[0]); + + context.TimeoutProcessed += (_, eventArgs) => + { + if (eventArgs.Round == 0 && eventArgs.Step == ConsensusStep.Sortition) + { + timeoutSortition.Set(); + } + }; + + context.StateChanged += (_, eventArgs) => + { + if (eventArgs.Round == 1) + { + enteredRoundTwo.Set(); + } + }; + + context.Start(); + await timeoutSortition.WaitAsync(); + + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusPreVoteMsg( + new VoteMetadata( + 1, + 0, + default, + DateTimeOffset.UtcNow, + TestUtils.PrivateKeys[i].PublicKey, + BigInteger.One, + VoteFlag.PreVote).Sign(TestUtils.PrivateKeys[i]))); + } + + foreach (int i in new int[] { 1, 2, 3 }) + { + context.ProduceMessage( + new ConsensusPreCommitMsg( + new VoteMetadata( + 1, + 0, + default, + DateTimeOffset.UtcNow, + TestUtils.PrivateKeys[i].PublicKey, + BigInteger.One, + VoteFlag.PreCommit).Sign(TestUtils.PrivateKeys[i]))); + } + + await enteredRoundTwo.WaitAsync(); + Assert.Equal(1, context.Round); + } + public struct ContextJson { #pragma warning disable SA1300 diff --git a/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs b/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs index 3045e5071f4..ecd384daf8a 100644 --- a/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs +++ b/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs @@ -29,7 +29,7 @@ public void Constructor() Assert.Null(_genesisConsensusInformation.LastProof); Assert.Throws(() => new ConsensusInformation(-1, 0, null)); - Assert.Throws(() => new ConsensusInformation(0, -1, null)); + Assert.Throws(() => new ConsensusInformation(0, -2, null)); var ci = new ConsensusInformation(1, 2, _genesisProof); Assert.Equal(1, ci.Height); @@ -38,6 +38,20 @@ public void Constructor() Assert.Equal(ci, new ConsensusInformation(ci.Encoded)); } + [Fact] + public void CannotProveOrVerifyWithNegativeRound() + { + var prover = new PrivateKey(); + var negativeRoundConsensusInformation = new ConsensusInformation(0, -1, _genesisProof); + Assert.Throws( + () => negativeRoundConsensusInformation.Prove(prover)); + + var negativeRoundProof = prover.Prove(negativeRoundConsensusInformation.Encoded); + Assert.Throws( + () => negativeRoundConsensusInformation.Verify( + negativeRoundProof, prover.PublicKey)); + } + [Fact] public void ProveAndVerify() { From a9fa924e8ee34311116d5c3c60f8cf114c72bd30 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 16 Jul 2024 22:44:03 +0900 Subject: [PATCH 54/61] Build fix after rebasing --- src/Libplanet.Net/Consensus/Context.cs | 2 +- .../Blockchain/BlockChainTest.Append.cs | 1 - .../Blockchain/BlockChainTest.ProposeBlock.cs | 12 ++++-------- test/Libplanet.Tests/TestUtils.cs | 1 - 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Libplanet.Net/Consensus/Context.cs b/src/Libplanet.Net/Consensus/Context.cs index fc12c7e66b7..dab3dc54ac2 100644 --- a/src/Libplanet.Net/Consensus/Context.cs +++ b/src/Libplanet.Net/Consensus/Context.cs @@ -457,7 +457,7 @@ private TimeSpan DelayLotGather() try { Block block = _blockChain.ProposeBlock( - _privateKey, + _privateKey, _lastCommit, _lotSet.GenerateProof(_privateKey), _blockChain.GetPendingEvidence()); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs index d4e87df8c30..5926df07474 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs @@ -3,7 +3,6 @@ using System.Collections.Immutable; using System.Linq; using System.Security.Cryptography; -using System.Security.Policy; using Bencodex.Types; using Libplanet.Action; using Libplanet.Action.Loader; diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs index c7a176ec062..e5fa367034c 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs @@ -543,8 +543,7 @@ public void ProposeBlockWithBlockAction() var block = blockChain.ProposeBlock( privateKey1, CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) - .Prove(privateKey1).Proof, + CreateZeroRoundProof(_blockChain.Tip, privateKey1), _blockChain.GetPendingEvidence()); blockChain.Append(block, CreateBlockCommit(block)); @@ -641,8 +640,7 @@ public void ProposeBlockWithLastCommit() Block block = _blockChain.ProposeBlock( proposer, blockCommit, - new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) - .Prove(proposer).Proof, + CreateZeroRoundProof(_blockChain.Tip, proposer), _blockChain.GetPendingEvidence()); Assert.NotNull(block.LastCommit); @@ -681,8 +679,7 @@ public void IgnoreLowerNonceTxsAndPropose() Block b2 = _blockChain.ProposeBlock( proposer, CreateBlockCommit(b1), - new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) - .Prove(proposer).Proof, + CreateZeroRoundProof(_blockChain.Tip, proposer), _blockChain.GetPendingEvidence()); Assert.Single(b2.Transactions); Assert.Contains(txsB[3], b2.Transactions); @@ -818,8 +815,7 @@ public void MarkTransactionsToIgnoreWhileProposing() var block = _blockChain.ProposeBlock( proposer, CreateBlockCommit(_blockChain.Tip), - new LotMetadata(_blockChain.Tip.Index, 0, _blockChain.Tip.Proof) - .Prove(proposer).Proof, + CreateZeroRoundProof(_blockChain.Tip, proposer), _blockChain.GetPendingEvidence()); Assert.DoesNotContain(txWithInvalidNonce, block.Transactions); diff --git a/test/Libplanet.Tests/TestUtils.cs b/test/Libplanet.Tests/TestUtils.cs index 175fd6b17ae..921112399aa 100644 --- a/test/Libplanet.Tests/TestUtils.cs +++ b/test/Libplanet.Tests/TestUtils.cs @@ -8,7 +8,6 @@ using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; -using System.Security.Policy; using System.Text; using System.Text.Json; using System.Text.Json.JsonDiffPatch.Xunit; From 9f86ce378372c783c57fe428696b934f16b9ad04 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 17 Jul 2024 15:04:04 +0900 Subject: [PATCH 55/61] test: Fix tests from rebasing --- .../Action/ActionEvaluatorTest.cs | 84 ++++++++++++++----- .../Blockchain/BlockChainTest.Evidence.cs | 1 + .../Blocks/BlockMetadataTest.cs | 2 +- .../Blocks/PreEvaluationBlockHeaderTest.cs | 20 ++--- 4 files changed, 73 insertions(+), 34 deletions(-) diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs index 1dc18de0992..60bdd252e4d 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -230,7 +230,7 @@ public void Evaluate() chain.Append(block, CreateBlockCommit(block)); var evaluations = chain.ActionEvaluator.Evaluate( - chain.Tip, chain.Store.GetStateRootHash(chain.Tip.PreviousHash)); + chain.Tip, chain.Store.GetStateRootHash(chain.Tip.Hash)); Assert.False(evaluations[0].InputContext.IsPolicyAction); Assert.Single(evaluations); @@ -373,7 +373,7 @@ public void EvaluateWithException() proof: CreateZeroRoundProof(chain.Tip, proposer)); chain.Append(block, CreateBlockCommit(block)); var evaluations = chain.ActionEvaluator.Evaluate( - chain.Tip, chain.Store.GetStateRootHash(chain.Tip.PreviousHash)); + chain.Tip, chain.Store.GetStateRootHash(chain.Tip.Hash)); Assert.False(evaluations[0].InputContext.IsPolicyAction); Assert.Single(evaluations); @@ -414,19 +414,20 @@ public void EvaluateWithCriticalException() actions: new[] { action }.ToPlainValues()); var txs = new Transaction[] { tx }; var evs = Array.Empty(); + var proposer = new PrivateKey(); PreEvaluationBlock block = new BlockContent( new BlockMetadata( index: 1L, timestamp: DateTimeOffset.UtcNow, - publicKey: new PrivateKey().PublicKey, + publicKey: proposer.PublicKey, previousHash: genesis.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, - proof: null, + proof: CreateZeroRoundProof(genesis, proposer), evidenceHash: null), transactions: txs, evidence: evs).Propose(); - IWorld previousState = stateStore.GetWorld(genesis.StateRootHash); + IWorld previousState = new World(chain.GetNextWorldState()); Assert.Throws( () => actionEvaluator.EvaluateTx( @@ -435,7 +436,7 @@ public void EvaluateWithCriticalException() previousState: previousState).ToList()); Assert.Throws( () => chain.ActionEvaluator.Evaluate( - block, chain.Store.GetStateRootHash(block.PreviousHash)).ToList()); + block, previousState.Trie.Hash).ToList()); } [Fact] @@ -529,22 +530,45 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu _logger.Debug("{0}[{1}] = {2}", nameof(block1Txs), i, tx.Id); } + var block1Srh = actionEvaluator.Evaluate( + genesis, genesis.StateRootHash).Last().OutputState; + Block block1 = ProposeNextBlock( genesis, GenesisProposer, - block1Txs); - IWorld previousState = stateStore.GetWorld(genesis.StateRootHash); + block1Txs, + stateRootHash: block1Srh, + proof: CreateZeroRoundProof(genesis, GenesisProposer)); + IWorld previousState = stateStore.GetWorld(block1.StateRootHash); var evals = actionEvaluator.EvaluateBlock( block1, previousState).ToImmutableArray(); + // Logging for fixing test expectations. + foreach (var eval in evals) + { + int txIdx = block1Txs.Select( + (e, idx) => new { e, idx }).First( + x => x.e.Id.Equals(eval.InputContext.TxId)).idx; + int actionIdx = block1Txs[txIdx].Actions.Select( + (e, idx) => new { e, idx }).First( + x => x.e.Equals(eval.Action.PlainValue)).idx; + string updatedStates = "new[] { " + string.Join(", ", addresses.Select( + eval.OutputState.GetAccount(ReservedAddresses.LegacyAccount).GetState) + .Select(x => x is Text t ? '"' + t.Value + '"' : "null")) + " }"; + string signerIdx = "_txFx.Address" + (addresses.Select( + (e, idx) => new { e, idx }).First( + x => x.e.Equals(eval.InputContext.Signer)).idx + 1); + _logger.Information($"({txIdx}, {actionIdx}, {updatedStates}, {signerIdx})"); + } + // Once the BlockMetadata.CurrentProtocolVersion gets bumped, expectations may also // have to be updated, since the order may change due to different PreEvaluationHash. (int TxIdx, int ActionIdx, string[] UpdatedStates, Address Signer)[] expectations = { - (0, 0, new[] { "A", null, null, null, null }, _txFx.Address1), // Adds "C" - (0, 1, new[] { "A", "B", null, null, null }, _txFx.Address1), // Adds "A" - (1, 0, new[] { "A", "B", "C", null, null }, _txFx.Address2), // Adds "B" + (1, 0, new[] { null, null, "C", null, null }, _txFx.Address2), // Adds "C" + (0, 0, new[] { "A", null, "C", null, null }, _txFx.Address1), // Adds "A" + (0, 1, new[] { "A", "B", "C", null, null }, _txFx.Address1), // Adds "B" }; Assert.Equal(expectations.Length, evals.Length); foreach (var (expect, eval) in expectations.Zip(evals, (x, y) => (x, y))) @@ -563,10 +587,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu Assert.Equal(block1.Index, eval.InputContext.BlockIndex); } - previousState = stateStore.GetWorld(genesis.StateRootHash); - ActionEvaluation[] evals1 = - actionEvaluator.EvaluateBlock(block1, previousState).ToArray(); - var output1 = new WorldBaseState(evals1.Last().OutputState.Trie, stateStore); + var output1 = new WorldBaseState(evals.Last().OutputState.Trie, stateStore); Assert.Equal( (Text)"A", output1.GetAccountState(ReservedAddresses.LegacyAccount).GetState(addresses[0])); @@ -651,21 +672,40 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu block1, GenesisProposer, block2Txs, + stateRootHash: evals.Last().OutputState.Trie.Hash, lastCommit: CreateBlockCommit(block1, true)); // Forcefully reset to null delta - previousState = evals1.Last().OutputState; + previousState = evals.Last().OutputState; evals = actionEvaluator.EvaluateBlock( block2, previousState).ToImmutableArray(); + // Logging for fixing test expectations. + foreach (var eval in evals) + { + int txIdx = block2Txs.Select( + (e, idx) => new { e, idx }).First( + x => x.e.Id.Equals(eval.InputContext.TxId)).idx; + int actionIdx = block2Txs[txIdx].Actions.Select( + (e, idx) => new { e, idx }).First( + x => x.e.Equals(eval.Action.PlainValue)).idx; + string updatedStates = "new[] { " + string.Join(", ", addresses.Select( + eval.OutputState.GetAccount(ReservedAddresses.LegacyAccount).GetState) + .Select(x => x is Text t ? '"' + t.Value + '"' : "null")) + " }"; + string signerIdx = "_txFx.Address" + (addresses.Select( + (e, idx) => new { e, idx }).First( + x => x.e.Equals(eval.InputContext.Signer)).idx + 1); + _logger.Information($"({txIdx}, {actionIdx}, {updatedStates}, {signerIdx})"); + } + // Once the BlockMetadata.CurrentProtocolVersion gets bumped, expectations may also // have to be updated, since the order may change due to different PreEvaluationHash. expectations = new (int TxIdx, int ActionIdx, string[] UpdatedStates, Address Signer)[] { - (2, 0, new[] { "A", "B", "C", null, "F" }, _txFx.Address3), - (1, 0, new[] { "A", "B", "C", "E", "F" }, _txFx.Address2), - (0, 0, new[] { "A,D", "B", "C", "E", "F" }, _txFx.Address1), + (0, 0, new[] { "A,D", "B", "C", null, null }, _txFx.Address1), + (1, 0, new[] { "A,D", "B", "C", "E", null }, _txFx.Address2), + (2, 0, new[] { "A,D", "B", "C", "E", "F" }, _txFx.Address3), }; Assert.Equal(expectations.Length, evals.Length); foreach (var (expect, eval) in expectations.Zip(evals, (x, y) => (x, y))) @@ -685,9 +725,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu Assert.Null(eval.Exception); } - previousState = evals1.Last().OutputState; - var evals2 = actionEvaluator.EvaluateBlock(block2, previousState).ToArray(); - var output2 = new WorldBaseState(evals2.Last().OutputState.Trie, stateStore); + var output2 = new WorldBaseState(evals.Last().OutputState.Trie, stateStore); Assert.Equal( (Text)"A,D", output2.GetAccountState(ReservedAddresses.LegacyAccount).GetState(addresses[0])); @@ -729,7 +767,7 @@ public void EvaluateTx() previousHash: default(BlockHash), txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, - proof: null, + proof: new ConsensusInformation(1L, 0, null).Prove(GenesisProposer), evidenceHash: null), transactions: txs, evidence: evs).Propose(); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Evidence.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Evidence.cs index 3cf4749d40f..47255a39999 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Evidence.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Evidence.cs @@ -581,6 +581,7 @@ private static Block NextBlock( var block = blockChain.ProposeBlock( proposer: proposer, lastCommit: TestUtils.CreateBlockCommit(tip, true), + proof: TestUtils.CreateZeroRoundProof(tip, proposer), evidence: evidence); blockChain.Append(block, TestUtils.CreateBlockCommit(block, true)); return block; diff --git a/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs b/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs index cfaa873be56..c7f137285f8 100644 --- a/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs +++ b/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs @@ -214,7 +214,7 @@ public void DerivePreEvaluationHash() HashDigest hash = GenesisMetadata.DerivePreEvaluationHash(); AssertBytesEqual( - FromHex("9ff328716814fed03de454dfc0a9d9aeb0077ad0c393513bf29895a45ded13aa"), + FromHex("4b9d55d06e4db1d693d6843f67b5e818fa819a8f29cde18582631b8c8f6f11cb"), hash.ByteArray); } diff --git a/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs b/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs index e7cea982f7d..048e2ff276c 100644 --- a/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs +++ b/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs @@ -181,8 +181,8 @@ public void VerifySignature() // Same as block1.MakeSignature(_contents.Block1Key, arbitraryHash) ImmutableArray validSig = ByteUtil.ParseHexToImmutable( - "3045022100f975e902971092f16dbbb1fe6b7c956de648a8cd62346dbadc07e5fca4ce3" + - "07a02200987a349f0763efd0448659ed66c6bd0ad0971dd57fbb89c672aed592fbd70d6"); + "304402203ed4c76c5196b0fe280ea0235ba05211cfd4a96d2660e2c6883d11ddb49c47e" + + "90220095958c7c019db2bb71e9f5fc857ff6c94c2cfdf8484f08050694451ddfe569c"); AssertBytesEqual( validSig, block1.MakeSignature(_contents.Block1Key, arbitraryHash)); @@ -215,22 +215,22 @@ public void DeriveBlockHash() _contents.GenesisMetadata, _contents.GenesisMetadata.DerivePreEvaluationHash()); AssertBytesEqual( - fromHex("d790aa3b2d985d58e6fe6547336ca9d2bfdb749a27cd58a17dbfd0c6880da8e3"), + fromHex("6f8f1d4ca79463e85f56b2927ac4414bd8b0ac86942a2af44fa71ac10c61c3d5"), genesis.DeriveBlockHash(default, null) ); AssertBytesEqual( - fromHex("47b5227dfdd99af4faf9ae9e82ef3b615063179d275081eae4c122685bbf7dcb"), + fromHex("59a9d5a9f3de4859f557437f3667f91cda146b6e980bda8cb4f0c1b9eff3bd43"), genesis.DeriveBlockHash( default, genesis.MakeSignature(_contents.GenesisKey, default) ) ); AssertBytesEqual( - fromHex("2c45bb52e4c7d79caa28da9b63ec0f492262836c975bfa5bb27f62e96f2aad99"), + fromHex("0b5ea8cf0a678075b9438cdf7d0a01bf61ec62c05d9320c597a255f63196f437"), genesis.DeriveBlockHash(arbitraryHash, null) ); AssertBytesEqual( - fromHex("e985fcdce3f73dee90a4eaec9399283f856bb6f9326e4300bbe1d6126ff7ad55"), + fromHex("8f8e5bdcbff809c89fcd295adba98c194babacb6ed69639b13cdc1b990b6c8e0"), genesis.DeriveBlockHash( arbitraryHash, genesis.MakeSignature(_contents.GenesisKey, arbitraryHash)) @@ -240,19 +240,19 @@ public void DeriveBlockHash() _contents.Block1Metadata, _contents.Block1Metadata.DerivePreEvaluationHash()); AssertBytesEqual( - fromHex("ade696a646c9e4321cc90160807cba3d15d7cd28556d2dfb4103e8730a46038c"), + fromHex("b15d91c7df2b6e568110dc0e0547e79218645fef159d90ca87ea6d347c814be7"), block1.DeriveBlockHash(default, null) ); AssertBytesEqual( - fromHex("b3157a151d2168653e21ffc850f9d1a96bca6310275cccbeb9bd705f67c2e1c9"), + fromHex("de112dcbe27aa0f040ee9dc93c8681bb58b514e52f769f72d60cbaaf203962e8"), block1.DeriveBlockHash(default, block1.MakeSignature(_contents.Block1Key, default)) ); AssertBytesEqual( - fromHex("3fd4ee37ed2fc5dae5a9533984f06b3975e176bdaa70689a3c14acd8b4ea384d"), + fromHex("1c3481fade04f4f82734f497fcdafb85745134506eacac288acb25f5d989be06"), block1.DeriveBlockHash(arbitraryHash, null) ); AssertBytesEqual( - fromHex("83ceb4d1e5bbc385daaebfd044a5e4ba65bf1d8b63ef0aabe4d68fc5642b4516"), + fromHex("37cc2ae1e59e01192d14dc6d09b9d52e4fbc8cdf11db2fa181f000fd2c00b6c1"), block1.DeriveBlockHash( arbitraryHash, block1.MakeSignature(_contents.Block1Key, arbitraryHash) ) From 0a78dce5f95957d2165369384056334760490842 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 17 Jul 2024 18:18:24 +0900 Subject: [PATCH 56/61] refactor: Refactor structs into classes --- src/Libplanet.Crypto/Proof.cs | 20 ++++++++++------- src/Libplanet.Net/Consensus/Context.cs | 3 ++- .../Consensus/InvalidLotException.cs | 2 +- .../Blockchain/BlockChain.ProposeBlock.cs | 4 ++-- .../Blockchain/BlockChain.Validate.cs | 22 +++++++++---------- .../Consensus/ConsensusInformation.cs | 15 +++++++------ src/Libplanet/Consensus/DominantLot.cs | 8 +++---- src/Libplanet/Consensus/Lot.cs | 11 +++++----- 8 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/Libplanet.Crypto/Proof.cs b/src/Libplanet.Crypto/Proof.cs index b29f62c1f86..58b43175ef5 100644 --- a/src/Libplanet.Crypto/Proof.cs +++ b/src/Libplanet.Crypto/Proof.cs @@ -15,7 +15,7 @@ namespace Libplanet.Crypto /// Represents a proof that validators submits to be a proposer. /// Once decided, it can be a source of random seed. /// - public readonly struct Proof : IBencodable, IEquatable, IComparable, IComparable + public class Proof : IBencodable, IEquatable, IComparable, IComparable { private readonly ImmutableArray _piBytes; private readonly ImmutableArray _hash; @@ -248,23 +248,27 @@ double targetProb } /// - public bool Equals(Proof other) - => ByteArray.SequenceEqual(other.ByteArray); + public bool Equals(Proof? other) + => other is Proof proof + && ByteArray.SequenceEqual(proof.ByteArray); /// public override bool Equals(object? obj) => obj is Proof otherProof && Equals(otherProof); /// - public int CompareTo(Proof other) - => (HashInt - other.HashInt).Sign; + public int CompareTo(Proof? other) + => other is Proof proof + ? (HashInt - proof.HashInt).Sign + : throw new ArgumentException( + $"Argument {nameof(other)} cannot be null.", nameof(other)); /// public int CompareTo(object? obj) => obj is Proof otherProof - ? CompareTo(otherProof) - : throw new ArgumentException( - $"Argument {nameof(obj)} is not an ${nameof(Proof)}.", nameof(obj)); + ? CompareTo(otherProof) + : throw new ArgumentException( + $"Argument {nameof(obj)} is not an ${nameof(Proof)}.", nameof(obj)); /// public override int GetHashCode() diff --git a/src/Libplanet.Net/Consensus/Context.cs b/src/Libplanet.Net/Consensus/Context.cs index dab3dc54ac2..04a30a3f701 100644 --- a/src/Libplanet.Net/Consensus/Context.cs +++ b/src/Libplanet.Net/Consensus/Context.cs @@ -510,7 +510,8 @@ private bool IsValid(Block block) try { - if (!block.Proof.Equals(_lotSet.Maj23?.Proof)) + if (!(block.Proof is Proof proof + && proof.Equals(_lotSet.Maj23?.Proof))) { throw new InvalidBlockProofException( $"Proof if given block is different from " + diff --git a/src/Libplanet.Net/Consensus/InvalidLotException.cs b/src/Libplanet.Net/Consensus/InvalidLotException.cs index dfc96d76f89..febe749e4c9 100644 --- a/src/Libplanet.Net/Consensus/InvalidLotException.cs +++ b/src/Libplanet.Net/Consensus/InvalidLotException.cs @@ -55,7 +55,7 @@ public InvalidLotException(string message, Lot lot) /// protected InvalidLotException(SerializationInfo info, StreamingContext context) { - Lot = info.GetValue(nameof(Lot), typeof(Lot)) as Lot? ?? + Lot = info.GetValue(nameof(Lot), typeof(Lot)) as Lot ?? throw new SerializationException( $"{nameof(Lot)} is expected to be a non-null {nameof(Lot)}."); } diff --git a/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs b/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs index 0c582c44a77..779ba6db9c9 100644 --- a/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs +++ b/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs @@ -97,7 +97,7 @@ public static Block ProposeGenesisBlock( public Block ProposeBlock( PrivateKey proposer, BlockCommit lastCommit = null, - Proof? proof = null, + Proof proof = null, ImmutableArray? evidence = null, IComparer txPriority = null) { @@ -155,7 +155,7 @@ internal Block ProposeBlock( PrivateKey proposer, ImmutableList transactions, BlockCommit lastCommit, - Proof? proof, + Proof proof, ImmutableArray evidence) { long index = Count; diff --git a/src/Libplanet/Blockchain/BlockChain.Validate.cs b/src/Libplanet/Blockchain/BlockChain.Validate.cs index 52b10d5641a..6ee875f6ba6 100644 --- a/src/Libplanet/Blockchain/BlockChain.Validate.cs +++ b/src/Libplanet/Blockchain/BlockChain.Validate.cs @@ -312,20 +312,20 @@ internal void ValidateBlock(Block block) { throw new InvalidBlockLastCommitException(ibce.Message); } + } - if (block.Proof is { } + if (block.Proof is { } && block.ProtocolVersion < BlockMetadata.VRFProtocolVersion) - { - throw new InvalidBlockProofException( - "Block of protocol version lower than 9 does not support proof."); - } + { + throw new InvalidBlockProofException( + "Block of protocol version lower than 9 does not support proof."); + } - if (block.Proof is null - && block.ProtocolVersion >= BlockMetadata.VRFProtocolVersion) - { - throw new InvalidBlockProofException( - "Block of protocol version higher than 9 must contain proof."); - } + if (block.Proof is null + && block.ProtocolVersion >= BlockMetadata.VRFProtocolVersion) + { + throw new InvalidBlockProofException( + "Block of protocol version higher than 9 must contain proof."); } foreach (var ev in block.Evidence) diff --git a/src/Libplanet/Consensus/ConsensusInformation.cs b/src/Libplanet/Consensus/ConsensusInformation.cs index 1ea5fca8e42..b9373424137 100644 --- a/src/Libplanet/Consensus/ConsensusInformation.cs +++ b/src/Libplanet/Consensus/ConsensusInformation.cs @@ -14,7 +14,7 @@ namespace Libplanet.Consensus /// in the /// . /// - public readonly struct ConsensusInformation : IEquatable + public class ConsensusInformation : IEquatable { private static readonly Binary HeightKey = new Binary(new byte[] { 0x48 }); // 'H' @@ -89,7 +89,7 @@ private ConsensusInformation(Dictionary bencoded) (Integer)bencoded[HeightKey], (Integer)bencoded[RoundKey], bencoded.ContainsKey(LastProofKey) - ? (Proof?)new Proof(bencoded[LastProofKey]) + ? new Proof(bencoded[LastProofKey]) : null) { } @@ -180,10 +180,11 @@ public bool Verify(Proof proof, PublicKey verifier) } /// - public bool Equals(ConsensusInformation other) - => Height == other.Height - && Round == other.Round - && LastProof.Equals(other.LastProof); + public bool Equals(ConsensusInformation? other) + => other is ConsensusInformation ci + && Height == ci.Height + && Round == ci.Round + && (LastProof?.Equals(ci.LastProof) ?? ci.LastProof is null); /// public override bool Equals(object? obj) => @@ -203,7 +204,7 @@ public override string ToString() { { "height", Height }, { "round", Round }, - { "lastProof", LastProof.ToString() ?? "Empty" }, + { "lastProof", LastProof?.ToString() ?? "Empty" }, }; return JsonSerializer.Serialize(dict); } diff --git a/src/Libplanet/Consensus/DominantLot.cs b/src/Libplanet/Consensus/DominantLot.cs index 43bfcc799bf..26e3298554e 100644 --- a/src/Libplanet/Consensus/DominantLot.cs +++ b/src/Libplanet/Consensus/DominantLot.cs @@ -109,16 +109,16 @@ public DominantLot(Dictionary encoded) [Pure] public bool Equals(DominantLot? other) { - return other is { } drawn && - _dominantLotMetadata.Equals(drawn._dominantLotMetadata) && - Signature.SequenceEqual(drawn.Signature); + return other is DominantLot dominantLot && + _dominantLotMetadata.Equals(dominantLot._dominantLotMetadata) && + Signature.SequenceEqual(dominantLot.Signature); } /// [Pure] public override bool Equals(object? obj) { - return obj is Maj23 other && Equals(other); + return obj is DominantLot other && Equals(other); } /// diff --git a/src/Libplanet/Consensus/Lot.cs b/src/Libplanet/Consensus/Lot.cs index 81b2cba95f4..16bcb41b067 100644 --- a/src/Libplanet/Consensus/Lot.cs +++ b/src/Libplanet/Consensus/Lot.cs @@ -10,7 +10,7 @@ namespace Libplanet.Consensus { - public readonly struct Lot : IBencodable, IEquatable + public class Lot : IBencodable, IEquatable { private static readonly Binary ProofKey = new Binary(new byte[] { 0x50 }); // 'P' @@ -110,10 +110,11 @@ public IValue Bencoded public byte[] ToByteArray() => _codec.Encode(Bencoded); /// - public bool Equals(Lot other) - => Proof.Equals(other.Proof) - && PublicKey.Equals(other.PublicKey) - && ConsensusInformation.Equals(other.ConsensusInformation); + public bool Equals(Lot? other) + => other is Lot lot + && Proof.Equals(lot.Proof) + && PublicKey.Equals(lot.PublicKey) + && ConsensusInformation.Equals(lot.ConsensusInformation); /// public override bool Equals(object? obj) From 8ea4ed752d38b13a3486fb55fd8037ec1a207fbe Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 17 Jul 2024 18:18:33 +0900 Subject: [PATCH 57/61] test: Fix tests --- .../Consensus/ContextTest.cs | 9 +- .../Consensus/DuplicateVoteEvidenceTest.cs | 135 ++++++++++++++---- .../Consensus/LotSetTest.cs | 4 +- .../Messages/MessageTest.cs | 2 +- test/Libplanet.Net.Tests/TestUtils.cs | 4 +- .../Action/ActionEvaluatorTest.cs | 2 +- test/Libplanet.Tests/TestUtils.cs | 6 +- 7 files changed, 122 insertions(+), 40 deletions(-) diff --git a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs index c7c98fa1392..39dde4faf0f 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs @@ -821,11 +821,10 @@ public async void BroadcastLot() await lotSent.WaitAsync(); Assert.NotNull(lotPublished); - Lot lot = lotPublished.Value; - Assert.Equal(1L, lot.Height); - Assert.Equal(0, lot.Round); - Assert.Equal(blockChain.Tip.Proof, lot.LastProof); - Assert.Equal(TestUtils.PrivateKeys[0].PublicKey, lot.PublicKey); + Assert.Equal(1L, lotPublished.Height); + Assert.Equal(0, lotPublished.Round); + Assert.Equal(blockChain.Tip.Proof, lotPublished.LastProof); + Assert.Equal(TestUtils.PrivateKeys[0].PublicKey, lotPublished.PublicKey); } [Fact] diff --git a/test/Libplanet.Net.Tests/Consensus/DuplicateVoteEvidenceTest.cs b/test/Libplanet.Net.Tests/Consensus/DuplicateVoteEvidenceTest.cs index 2f7ff981c27..28ba9a6038c 100644 --- a/test/Libplanet.Net.Tests/Consensus/DuplicateVoteEvidenceTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/DuplicateVoteEvidenceTest.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Bencodex; using Bencodex.Types; +using Libplanet.Consensus; using Libplanet.Net.Consensus; using Libplanet.Net.Messages; using Libplanet.Types.Blocks; @@ -49,17 +50,30 @@ public async Task Evidence_WithDuplicateVotes_Test() consensusContext: consensusContext, height: 3, cancellationToken: new CancellationTokenSource(Timeout).Token); - var consensusProposalMsgAt7Task = WaitUntilPublishedAsync( + var consensusProposalMsgAt5Task = WaitUntilPublishedAsync( consensusContext: consensusContext, - height: 7, + height: 5, cancellationToken: new CancellationTokenSource(Timeout).Token); - var block = blockChain.ProposeBlock(privateKeys[1]); + var proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[1]); + var block = blockChain.ProposeBlock(privateKeys[1], proof: proof); var blockCommit = TestUtils.CreateBlockCommit(block); consensusContext.Start(); blockChain.Append(block, blockCommit); - block = blockChain.ProposeBlock(privateKeys[2], blockCommit); + proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[2]); + block = blockChain.ProposeBlock(privateKeys[2], blockCommit, proof); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], lot))); + } + await consensusProposalMsgAt3Task; var consensusProposalMsgAt3 = consensusProposalMsgAt3Task.Result; var blockHash = consensusProposalMsgAt3.BlockHash; @@ -103,23 +117,27 @@ await WaitUntilHeightAsync( Assert.Equal(0, consensusContext.Round); blockCommit = blockChain.GetBlockCommit(blockChain.Tip.Hash); - block = blockChain.ProposeBlock(privateKeys[0], blockCommit); - blockCommit = TestUtils.CreateBlockCommit(block); - blockChain.Append(block, blockCommit); - - block = blockChain.ProposeBlock(privateKeys[1], blockCommit); - blockCommit = TestUtils.CreateBlockCommit(block); - blockChain.Append(block, blockCommit); - - block = blockChain.ProposeBlock(privateKeys[2], blockCommit); + proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[0]); + block = blockChain.ProposeBlock(privateKeys[0], blockCommit, proof); blockCommit = TestUtils.CreateBlockCommit(block); blockChain.Append(block, blockCommit); - await consensusProposalMsgAt7Task; - var consensusProposalMsgAt7 = consensusProposalMsgAt7Task.Result; + lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], lot))); + } + + await consensusProposalMsgAt5Task; + var consensusProposalMsgAt5 = consensusProposalMsgAt5Task.Result; Assert.NotNull(consensusProposalMsgAt3?.BlockHash); var actualBlock = BlockMarshaler.UnmarshalBlock( - (Dictionary)_codec.Decode(consensusProposalMsgAt7.Proposal.MarshaledBlock)); + (Dictionary)_codec.Decode(consensusProposalMsgAt5.Proposal.MarshaledBlock)); Assert.Single(actualBlock.Evidence); } @@ -137,13 +155,26 @@ public async Task IgnoreDifferentHeightVote() consensusContext: consensusContext, height: 3, cancellationToken: new CancellationTokenSource(Timeout).Token); - var block = blockChain.ProposeBlock(privateKeys[1]); + var proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[1]); + var block = blockChain.ProposeBlock(privateKeys[1], proof: proof); var blockCommit = TestUtils.CreateBlockCommit(block); consensusContext.Start(); blockChain.Append(block, blockCommit); - block = blockChain.ProposeBlock(privateKeys[2], blockCommit); + proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[2]); + block = blockChain.ProposeBlock(privateKeys[2], blockCommit, proof); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], lot))); + } + await consensusProposalMsgAt3Task; var consensusProposalMsgAt3 = consensusProposalMsgAt3Task.Result; var blockHash = consensusProposalMsgAt3.BlockHash; @@ -199,13 +230,26 @@ public async Task IgnoreDifferentRoundVote() consensusContext: consensusContext, height: 3, cancellationToken: new CancellationTokenSource(Timeout).Token); - var block = blockChain.ProposeBlock(privateKeys[1]); + var proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[1]); + var block = blockChain.ProposeBlock(privateKeys[1], proof: proof); var blockCommit = TestUtils.CreateBlockCommit(block); consensusContext.Start(); blockChain.Append(block, blockCommit); - block = blockChain.ProposeBlock(privateKeys[2], blockCommit); + proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[2]); + block = blockChain.ProposeBlock(privateKeys[2], blockCommit, proof); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], lot))); + } + await consensusProposalMsgAt3Task; var consensusProposalMsgAt3 = consensusProposalMsgAt3Task.Result; var blockHash = consensusProposalMsgAt3.BlockHash; @@ -261,13 +305,26 @@ public async Task IgnoreDifferentFlagVote() consensusContext: consensusContext, height: 3, cancellationToken: new CancellationTokenSource(Timeout).Token); - var block = blockChain.ProposeBlock(privateKeys[1]); + var proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[1]); + var block = blockChain.ProposeBlock(privateKeys[1], proof: proof); var blockCommit = TestUtils.CreateBlockCommit(block); consensusContext.Start(); blockChain.Append(block, blockCommit); - block = blockChain.ProposeBlock(privateKeys[2], blockCommit); + proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[2]); + block = blockChain.ProposeBlock(privateKeys[2], blockCommit, proof); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], lot))); + } + await consensusProposalMsgAt3Task; var consensusProposalMsgAt3 = consensusProposalMsgAt3Task.Result; var blockHash = consensusProposalMsgAt3.BlockHash; @@ -323,13 +380,26 @@ public async Task IgnoreSameBlockHashVote() consensusContext: consensusContext, height: 3, cancellationToken: new CancellationTokenSource(Timeout).Token); - var block = blockChain.ProposeBlock(privateKeys[1]); + var proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[1]); + var block = blockChain.ProposeBlock(privateKeys[1], proof: proof); var blockCommit = TestUtils.CreateBlockCommit(block); consensusContext.Start(); blockChain.Append(block, blockCommit); - block = blockChain.ProposeBlock(privateKeys[2], blockCommit); + proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[2]); + block = blockChain.ProposeBlock(privateKeys[2], blockCommit, proof); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], lot))); + } + await consensusProposalMsgAt3Task; var consensusProposalMsgAt3 = consensusProposalMsgAt3Task.Result; var blockHash = consensusProposalMsgAt3.BlockHash; @@ -385,13 +455,26 @@ public async Task IgnoreNillVote() consensusContext: consensusContext, height: 3, cancellationToken: new CancellationTokenSource(Timeout).Token); - var block = blockChain.ProposeBlock(privateKeys[1]); + var proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[1]); + var block = blockChain.ProposeBlock(privateKeys[1], proof: proof); var blockCommit = TestUtils.CreateBlockCommit(block); consensusContext.Start(); blockChain.Append(block, blockCommit); - block = blockChain.ProposeBlock(privateKeys[2], blockCommit); + proof = TestUtils.CreateZeroRoundProof(blockChain.Tip, privateKeys[2]); + block = blockChain.ProposeBlock(privateKeys[2], blockCommit, proof); blockChain.Append(block, TestUtils.CreateBlockCommit(block)); + var lotSet = new LotSet( + blockChain.Tip.Index + 1, 0, blockChain.Tip.Proof, TestUtils.ValidatorSet, 20); + var lot = lotSet.GenerateLot(TestUtils.PrivateKeys[3]); + foreach (int i in new int[] { 0, 1, 2 }) + { + consensusContext.HandleMessage( + new ConsensusDominantLotMsg( + TestUtils.CreateDominantLot( + TestUtils.PrivateKeys[i], lot))); + } + await consensusProposalMsgAt3Task; var consensusProposalMsgAt3 = consensusProposalMsgAt3Task.Result; var blockHash = consensusProposalMsgAt3.BlockHash; diff --git a/test/Libplanet.Net.Tests/Consensus/LotSetTest.cs b/test/Libplanet.Net.Tests/Consensus/LotSetTest.cs index e09911d408e..172474c12da 100644 --- a/test/Libplanet.Net.Tests/Consensus/LotSetTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/LotSetTest.cs @@ -110,12 +110,12 @@ public void AddLot() Assert.Throws( () => _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[0]))); Assert.Single(_lotSet.Lots); - _lotSet.DominantLot.Equals(_lotSet.Lots[TestUtils.PrivateKeys[0].PublicKey]); + Assert.Equal(_lotSet.DominantLot, _lotSet.Lots[TestUtils.PrivateKeys[0].PublicKey]); _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[1])); _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[2])); _lotSet.AddLot(_consensusInformation.ToLot(TestUtils.PrivateKeys[3])); Assert.Equal(4, _lotSet.Lots.Count); - Lot dominantLot = (Lot)_lotSet.DominantLot!; + Lot dominantLot = _lotSet.DominantLot!; var nonDominantLots = _lotSet.Lots.Remove(dominantLot.PublicKey).Select(e => e.Value); Assert.All(nonDominantLots, lot => Assert.True(FormerLotWins(dominantLot, lot))); diff --git a/test/Libplanet.Net.Tests/Messages/MessageTest.cs b/test/Libplanet.Net.Tests/Messages/MessageTest.cs index b98a1b5f4c3..a0b85a58632 100644 --- a/test/Libplanet.Net.Tests/Messages/MessageTest.cs +++ b/test/Libplanet.Net.Tests/Messages/MessageTest.cs @@ -115,7 +115,7 @@ public void GetId() var message = new BlockHeaderMsg(genesis.Hash, genesis.Header); Assert.Equal( new MessageId(ByteUtil.ParseHex( - "1aa2c8fad502f8890b2e8cf6f9afe57e6f718f454f14f8304165e921b28905bf")), + "f83e5f07b464bb41d182ec373a1f1bf087defaaaedf466b302c040601c5254e9")), message.Id); } diff --git a/test/Libplanet.Net.Tests/TestUtils.cs b/test/Libplanet.Net.Tests/TestUtils.cs index 64fa434023e..94e9bb7ddc6 100644 --- a/test/Libplanet.Net.Tests/TestUtils.cs +++ b/test/Libplanet.Net.Tests/TestUtils.cs @@ -109,8 +109,8 @@ public static (PrivateKey Proposer, Proof Proof) GetProposer( } return (privateKeys.First( - pk => pk.PublicKey.Equals(lotSet.DominantLot!.Value.PublicKey)), - lotSet.DominantLot!.Value.Proof); + pk => pk.PublicKey.Equals(lotSet.DominantLot!.PublicKey)), + lotSet.DominantLot!.Proof); } public static DominantLot CreateDominantLot( diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs index 60bdd252e4d..92c97b63737 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -1621,7 +1621,7 @@ public void CheckRandomSeedInAction() isPolicyAction: false).ToArray(); Assert.NotNull(blockA.Proof); - Proof proof = (Proof)blockA.Proof; + Proof proof = blockA.Proof; int[] randomSeeds = Enumerable .Range(0, txA.Actions.Count) .Select(offset => proof.Seed + offset) diff --git a/test/Libplanet.Tests/TestUtils.cs b/test/Libplanet.Tests/TestUtils.cs index 921112399aa..4ad7ab80f4d 100644 --- a/test/Libplanet.Tests/TestUtils.cs +++ b/test/Libplanet.Tests/TestUtils.cs @@ -423,7 +423,7 @@ public static PreEvaluationBlock ProposeGenesis( ValidatorSet validatorSet = null, DateTimeOffset? timestamp = null, int protocolVersion = Block.CurrentProtocolVersion, - Proof? proof = null + Proof proof = null ) { var txs = transactions?.ToList() ?? new List(); @@ -496,7 +496,7 @@ public static PreEvaluationBlock ProposeNext( TimeSpan? blockInterval = null, int protocolVersion = Block.CurrentProtocolVersion, BlockCommit lastCommit = null, - Proof? proof = null, + Proof proof = null, ImmutableArray? evidence = null) { var txs = transactions is null @@ -539,7 +539,7 @@ public static Block ProposeNextBlock( int protocolVersion = Block.CurrentProtocolVersion, HashDigest stateRootHash = default, BlockCommit lastCommit = null, - Proof? proof = null, + Proof proof = null, ImmutableArray? evidence = null) { Skip.IfNot( From 9ff3b755ffa161de0f259ac93c886fbc0f6e9bc9 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 17 Jul 2024 23:34:09 +0900 Subject: [PATCH 58/61] test: Fix tests about proof on genesis --- .../Queries/TransactionQueryTest.cs | 6 ++- .../Consensus/ContextTest.cs | 4 +- .../Consensus/HeightVoteSetTest.cs | 4 +- .../Blockchain/BlockChainTest.Append.cs | 2 +- .../Blockchain/BlockChainTest.ProposeBlock.cs | 33 ++++++++++---- .../Blockchain/BlockChainTest.Stage.cs | 4 +- .../BlockChainTest.ValidateNextBlock.cs | 43 ++++++++++--------- .../Blockchain/BlockChainTest.cs | 6 ++- .../Blocks/BlockMetadataTest.cs | 15 ++++++- .../Blocks/PreEvaluationBlockHeaderTest.cs | 33 +++++++++----- .../Blocks/PreEvaluationBlockTest.cs | 4 +- .../Fixtures/BlockContentFixture.cs | 4 +- test/Libplanet.Tests/Store/StoreFixture.cs | 14 ++++-- test/Libplanet.Tests/TestUtils.cs | 4 +- 14 files changed, 118 insertions(+), 58 deletions(-) diff --git a/test/Libplanet.Explorer.Tests/Queries/TransactionQueryTest.cs b/test/Libplanet.Explorer.Tests/Queries/TransactionQueryTest.cs index 28f9e2fe46f..47c1282314d 100644 --- a/test/Libplanet.Explorer.Tests/Queries/TransactionQueryTest.cs +++ b/test/Libplanet.Explorer.Tests/Queries/TransactionQueryTest.cs @@ -130,7 +130,10 @@ async Task AssertNextNonce(long expected, Address address) { await AssertNextNonce(1, key1.Address); Source.BlockChain.MakeTransaction(key1, ImmutableList.Empty.Add(new NullAction())); await AssertNextNonce(2, key1.Address); - var block = Source.BlockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + var block = Source.BlockChain.ProposeBlock( + proposer, + proof: Libplanet.Tests.TestUtils.CreateZeroRoundProof(Source.BlockChain.Tip, proposer)); Source.BlockChain.Append(block, Libplanet.Tests.TestUtils.CreateBlockCommit(block)); await AssertNextNonce(2, key1.Address); @@ -139,7 +142,6 @@ async Task AssertNextNonce(long expected, Address address) { // staging txs of key2 does not increase nonce of key1 Source.BlockChain.MakeTransaction(key2, ImmutableList.Empty.Add(new NullAction())); - var proposer = new PrivateKey(); block = Source.BlockChain.ProposeBlock( proposer, Libplanet.Tests.TestUtils.CreateBlockCommit(block), diff --git a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs index 39dde4faf0f..3f58d2499c4 100644 --- a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs @@ -99,7 +99,9 @@ public async Task StartAsProposerWithLastCommit() // whether the lastCommit of height 1 is used for propose. Note that Peer2 // is the proposer for height 2. var blockChain = TestUtils.CreateDummyBlockChain(); - Block heightOneBlock = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + Block heightOneBlock = blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1])); var lastCommit = TestUtils.CreateBlockCommit(heightOneBlock); blockChain.Append(heightOneBlock, lastCommit); diff --git a/test/Libplanet.Net.Tests/Consensus/HeightVoteSetTest.cs b/test/Libplanet.Net.Tests/Consensus/HeightVoteSetTest.cs index a17cebe7170..09c4eca7272 100644 --- a/test/Libplanet.Net.Tests/Consensus/HeightVoteSetTest.cs +++ b/test/Libplanet.Net.Tests/Consensus/HeightVoteSetTest.cs @@ -24,7 +24,9 @@ public class HeightVoteSetTest public HeightVoteSetTest() { _blockChain = TestUtils.CreateDummyBlockChain(); - var block = _blockChain.ProposeBlock(TestUtils.PrivateKeys[1]); + var block = _blockChain.ProposeBlock( + TestUtils.PrivateKeys[1], + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, TestUtils.PrivateKeys[1])); _lastCommit = TestUtils.CreateBlockCommit(block); _heightVoteSet = new HeightVoteSet(2, TestUtils.ValidatorSet); _blockChain.Append(block, TestUtils.CreateBlockCommit(block)); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs index 5926df07474..ad53644dd6a 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs @@ -748,7 +748,7 @@ public void CannotAppendBlockWithInvalidActions() previousHash: _blockChain.Genesis.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null); var preEval = new PreEvaluationBlock( preEvaluationBlockHeader: new PreEvaluationBlockHeader( diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs index e5fa367034c..94fc1d920d2 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs @@ -207,8 +207,13 @@ public void CanProposeInvalidBlock() }.ToPlainValues()), }.ToImmutableList(); + var proposer = new PrivateKey(); var block = blockChain.ProposeBlock( - new PrivateKey(), txs, null, null, ImmutableArray.Empty); + proposer, + txs, + null, + CreateZeroRoundProof(_blockChain.Tip, proposer), + ImmutableArray.Empty); Assert.Throws( () => blockChain.Append(block, CreateBlockCommit(block))); } @@ -325,7 +330,9 @@ public void ProposeBlockWithPendingTxs() Assert.Null(_blockChain.GetTxExecution(_blockChain.Genesis.Hash, tx.Id)); } - Block block = _blockChain.ProposeBlock(keyA); + Block block = _blockChain.ProposeBlock( + keyA, + proof: CreateZeroRoundProof(_blockChain.Tip, keyA)); _blockChain.Append(block, CreateBlockCommit(block)); Assert.True(_blockChain.ContainsBlock(block.Hash)); @@ -425,7 +432,9 @@ TxPolicyViolationException IsSignerValid( var invalidTx = blockChain.MakeTransaction(invalidKey, new DumbAction[] { }); var proposer = new PrivateKey(); - var block = blockChain.ProposeBlock(proposer); + var block = blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(blockChain.Tip, proposer)); blockChain.Append(block, CreateBlockCommit(block)); var txs = block.Transactions.ToHashSet(); @@ -482,7 +491,10 @@ public void ProposeBlockWithLowerNonces() ), } ); - Block block1 = _blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block block1 = _blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(_blockChain.Tip, proposer)); _blockChain.Append(block1, CreateBlockCommit(block1)); // Trying to propose with lower nonce (0) than expected. @@ -497,7 +509,7 @@ public void ProposeBlockWithLowerNonces() ), } ); - var proposer = new PrivateKey(); + proposer = new PrivateKey(); Block block2 = _blockChain.ProposeBlock( proposer, CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0), @@ -657,7 +669,10 @@ public void IgnoreLowerNonceTxsAndPropose() nonce: nonce, privateKey: privateKey, timestamp: DateTimeOffset.Now)) .ToArray(); StageTransactions(txsA); - Block b1 = _blockChain.ProposeBlock(new PrivateKey()); + var proposer = new PrivateKey(); + Block b1 = _blockChain.ProposeBlock( + proposer, + proof: CreateZeroRoundProof(_blockChain.Tip, proposer)); _blockChain.Append(b1, CreateBlockCommit(b1)); Assert.Equal( txsA, @@ -675,7 +690,7 @@ public void IgnoreLowerNonceTxsAndPropose() StageTransactions(txsB); // Propose only txs having higher or equal with nonce than expected nonce. - var proposer = new PrivateKey(); + proposer = new PrivateKey(); Block b2 = _blockChain.ProposeBlock( proposer, CreateBlockCommit(b1), @@ -696,7 +711,9 @@ public void IgnoreDuplicatedNonceTxs() timestamp: DateTimeOffset.Now)) .ToArray(); StageTransactions(txs); - Block b = _blockChain.ProposeBlock(privateKey); + Block b = _blockChain.ProposeBlock( + privateKey, + proof: CreateZeroRoundProof(_blockChain.Tip, privateKey)); _blockChain.Append(b, CreateBlockCommit(b)); Assert.Single(b.Transactions); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs index 330b7c580ee..3434166461d 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs @@ -81,7 +81,9 @@ public void TransactionsWithDuplicatedNonce() // stage tx_0_0 -> mine tx_0_0 -> stage tx_0_1 Assert.True(_blockChain.StageTransaction(tx_0_0)); - var block = _blockChain.ProposeBlock(key); + var block = _blockChain.ProposeBlock( + key, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, key)); _blockChain.Append(block, TestUtils.CreateBlockCommit(block)); Assert.Empty(_blockChain.GetStagedTransactionIds()); Assert.Empty(_blockChain.StagePolicy.Iterate(_blockChain, filtered: true)); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs index d5f4d669d6a..475802d49b5 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs @@ -31,7 +31,7 @@ public void ValidateNextBlock() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(validNextBlock, TestUtils.CreateBlockCommit(validNextBlock)); @@ -53,7 +53,7 @@ public void ValidateNextBlockProtocolVersion() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -69,7 +69,7 @@ public void ValidateNextBlockProtocolVersion() previousHash: block1.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer)); @@ -86,7 +86,7 @@ public void ValidateNextBlockProtocolVersion() previousHash: block1.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block3, TestUtils.CreateBlockCommit(block3)); @@ -108,7 +108,7 @@ public void ValidateNextBlockInvalidIndex() previousHash: prev.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws( @@ -126,7 +126,7 @@ public void ValidateNextBlockInvalidIndex() previousHash: prev.Hash, txHash: null, lastCommit: TestUtils.CreateBlockCommit(prev.Hash, prev.Index + 1, 0), - proof: TestUtils.CreateZeroRoundProof(prev, _fx.Proposer), + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws( @@ -153,7 +153,7 @@ public void ValidateNextBlockInvalidPreviousHash() // ReSharper disable once PossibleInvalidOperationException lastCommit: TestUtils.CreateBlockCommit( _validNext.PreviousHash.Value, 1, 0), - proof: TestUtils.CreateZeroRoundProof(_validNext, _fx.Proposer), + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -176,7 +176,7 @@ public void ValidateNextBlockInvalidTimestamp() previousHash: _validNext.Hash, txHash: null, lastCommit: TestUtils.CreateBlockCommit(_validNext), - proof: TestUtils.CreateZeroRoundProof(_validNext, _fx.Proposer), + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -238,7 +238,7 @@ public void ValidateNextBlockInvalidStateRootHash() previousHash: genesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), TestUtils.GenesisProposer); @@ -379,7 +379,7 @@ public void ValidateNextBlockLastCommitNullAtIndexOne() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(validNextBlock, TestUtils.CreateBlockCommit(validNextBlock)); @@ -398,7 +398,7 @@ public void ValidateNextBlockLastCommitUpperIndexOne() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -432,7 +432,7 @@ public void ValidateNextBlockLastCommitFailsUnexpectedValidator() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -461,7 +461,7 @@ public void ValidateNextBlockLastCommitFailsUnexpectedValidator() previousHash: block1.Hash, txHash: null, lastCommit: blockCommit, - proof: TestUtils.CreateZeroRoundProof(block1, _fx.Proposer), + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -480,7 +480,7 @@ public void ValidateNextBlockLastCommitFailsDropExpectedValidator() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); _blockChain.Append(block1, TestUtils.CreateBlockCommit(block1)); @@ -505,7 +505,7 @@ public void ValidateNextBlockLastCommitFailsDropExpectedValidator() previousHash: block1.Hash, txHash: null, lastCommit: blockCommit, - proof: TestUtils.CreateZeroRoundProof(block1, _fx.Proposer), + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); Assert.Throws(() => @@ -547,7 +547,7 @@ public void ValidateBlockCommitFailsDifferentBlockHash() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); @@ -572,7 +572,7 @@ public void ValidateBlockCommitFailsDifferentHeight() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); @@ -597,7 +597,7 @@ public void ValidateBlockCommitFailsDifferentValidatorSet() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); @@ -632,7 +632,7 @@ public void ValidateBlockCommitFailsNullBlockCommit() previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); @@ -668,7 +668,7 @@ public void ValidateBlockCommitFailsInsufficientPower() previousHash: blockChain.Genesis.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); @@ -792,7 +792,8 @@ public void ValidateNextBlockAEVChangedOnChainRestart() previousHash: newChain.Tip.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof( + newChain.Tip, TestUtils.GenesisProposer), evidenceHash: null)).Propose(), TestUtils.GenesisProposer); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.cs index d66e0058bd3..a8a128c276f 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -84,7 +84,7 @@ public BlockChainTest(ITestOutputHelper output) previousHash: _fx.GenesisBlock.Hash, txHash: null, lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer), evidenceHash: null)).Propose(), _fx.Proposer); } @@ -177,7 +177,9 @@ public void BlockHashes() Assert.Single(_blockChain.BlockHashes); - Block b1 = _blockChain.ProposeBlock(key); + Block b1 = _blockChain.ProposeBlock( + key, + proof: CreateZeroRoundProof(_blockChain.Tip, key)); _blockChain.Append(b1, CreateBlockCommit(b1)); Assert.Equal(new[] { genesis.Hash, b1.Hash }, _blockChain.BlockHashes); diff --git a/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs b/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs index c7f137285f8..0a19c85874b 100644 --- a/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs +++ b/test/Libplanet.Tests/Blocks/BlockMetadataTest.cs @@ -138,6 +138,12 @@ public void MakeCandidateData() "transaction_fingerprint", ParseHex("3d8e87977b1142863435b9385657e69557df8951a0698e9719f7d06c5fb8db1f") ) + .Add( + "proof", + ParseHex("0224313f215eb0c9d511f43a4aa55c5df1cd5dffbc29197f1dc7b936aa5a" + + "30813d88ad81348df92917b8fd2e88b513d3c4381e554c41f91f201fe07a7c6967718" + + "03b0740d363ab3e15a76823b092d2f245f288291bb01063fecd2298011449668b") + ) .Add( "evidence_hash", ParseHex("bd1b6bc740c7d74fe39f8c78dd6860b7b5bf9c58336a703a583a5a59651a4af3") @@ -161,6 +167,13 @@ public void MakeCandidateData() ParseHex("654698d34b6d9a55b0c93e4ffb2639278324868c91965bc5f96cb3071d6903a0") ) .Add("protocol_version", BlockMetadata.CurrentProtocolVersion) + .Add( + "proof", + ParseHex("02b5de417ca459f8ab048625c55daf95fb47f7a9a41de80dcbbb0a454247" + + "e315ee845dda470812bf8b16741a8aacf7702c57d74e35c9e751089ee478d35a4fdcc" + + "7e150b5880ff57799bb7cd64fff33f99b663e98156f698d1590a472c520ffa9c8" + ) + ) .Add( "evidence_hash", ParseHex("e7198889cc4a82a8b7be4b7f428b6201400ef222709f756e540b32bc1e8d5d86") @@ -214,7 +227,7 @@ public void DerivePreEvaluationHash() HashDigest hash = GenesisMetadata.DerivePreEvaluationHash(); AssertBytesEqual( - FromHex("4b9d55d06e4db1d693d6843f67b5e818fa819a8f29cde18582631b8c8f6f11cb"), + FromHex("b96b3a3bcb0afe52fb4b90a9bc00678dd18b4c4adf3a38099ebc1fd7b8fca4c8"), hash.ByteArray); } diff --git a/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs b/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs index 048e2ff276c..333a6e98f60 100644 --- a/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs +++ b/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs @@ -71,6 +71,12 @@ public void MakeCandidateData() "transaction_fingerprint", ParseHex("3d8e87977b1142863435b9385657e69557df8951a0698e9719f7d06c5fb8db1f") ) + .Add( + "proof", + ParseHex("0224313f215eb0c9d511f43a4aa55c5df1cd5dffbc29197f1dc7b936aa5a" + + "30813d88ad81348df92917b8fd2e88b513d3c4381e554c41f91f201fe07a7c6967718" + + "03b0740d363ab3e15a76823b092d2f245f288291bb01063fecd2298011449668b") + ) .Add( "evidence_hash", ParseHex("bd1b6bc740c7d74fe39f8c78dd6860b7b5bf9c58336a703a583a5a59651a4af3") @@ -105,6 +111,13 @@ public void MakeCandidateData() "654698d34b6d9a55b0c93e4ffb2639278324868c91965bc5f96cb3071d6903a0" ) ) + .Add( + "proof", + ParseHex("02b5de417ca459f8ab048625c55daf95fb47f7a9a41de80dcbbb0a454247" + + "e315ee845dda470812bf8b16741a8aacf7702c57d74e35c9e751089ee478d35a4fdcc" + + "7e150b5880ff57799bb7cd64fff33f99b663e98156f698d1590a472c520ffa9c8" + ) + ) .Add( "evidence_hash", ParseHex( @@ -181,8 +194,8 @@ public void VerifySignature() // Same as block1.MakeSignature(_contents.Block1Key, arbitraryHash) ImmutableArray validSig = ByteUtil.ParseHexToImmutable( - "304402203ed4c76c5196b0fe280ea0235ba05211cfd4a96d2660e2c6883d11ddb49c47e" + - "90220095958c7c019db2bb71e9f5fc857ff6c94c2cfdf8484f08050694451ddfe569c"); + "3045022100bc8efa3cff74cea1da682c815d35c701d181bf13129734336d260fe14ed75" + + "2940220305f9c1f6ecfb7880acccb56b9886ace22c7b4cac1ad78bc2077ab53d5efd160"); AssertBytesEqual( validSig, block1.MakeSignature(_contents.Block1Key, arbitraryHash)); @@ -215,22 +228,22 @@ public void DeriveBlockHash() _contents.GenesisMetadata, _contents.GenesisMetadata.DerivePreEvaluationHash()); AssertBytesEqual( - fromHex("6f8f1d4ca79463e85f56b2927ac4414bd8b0ac86942a2af44fa71ac10c61c3d5"), + fromHex("74b76fccf910cf6dbdf9e0ee63d096122763d025152691bbd3093f0cc0b61d7a"), genesis.DeriveBlockHash(default, null) ); AssertBytesEqual( - fromHex("59a9d5a9f3de4859f557437f3667f91cda146b6e980bda8cb4f0c1b9eff3bd43"), + fromHex("af99a7832c3e1fdd880b7c69e3db9a101f57c0ac2370e26b2f30e8bbb7f1d0d0"), genesis.DeriveBlockHash( default, genesis.MakeSignature(_contents.GenesisKey, default) ) ); AssertBytesEqual( - fromHex("0b5ea8cf0a678075b9438cdf7d0a01bf61ec62c05d9320c597a255f63196f437"), + fromHex("061f08d4f8e52e00647191c64d21c74d43b26256ca2b5ce14b80fc430d390c6a"), genesis.DeriveBlockHash(arbitraryHash, null) ); AssertBytesEqual( - fromHex("8f8e5bdcbff809c89fcd295adba98c194babacb6ed69639b13cdc1b990b6c8e0"), + fromHex("b2fa69b10237a2f3ae1fe51050418b445a40ffc9e9ea1b7f474cfee134e84ce8"), genesis.DeriveBlockHash( arbitraryHash, genesis.MakeSignature(_contents.GenesisKey, arbitraryHash)) @@ -240,19 +253,19 @@ public void DeriveBlockHash() _contents.Block1Metadata, _contents.Block1Metadata.DerivePreEvaluationHash()); AssertBytesEqual( - fromHex("b15d91c7df2b6e568110dc0e0547e79218645fef159d90ca87ea6d347c814be7"), + fromHex("007908f9ca1335ec065c8302a6627e97da6fa627dc049f44b970d02b20898f02"), block1.DeriveBlockHash(default, null) ); AssertBytesEqual( - fromHex("de112dcbe27aa0f040ee9dc93c8681bb58b514e52f769f72d60cbaaf203962e8"), + fromHex("61bdf03b8ff2666d813352259c6d454eed8268c1386ea749b3a557e03e33919a"), block1.DeriveBlockHash(default, block1.MakeSignature(_contents.Block1Key, default)) ); AssertBytesEqual( - fromHex("1c3481fade04f4f82734f497fcdafb85745134506eacac288acb25f5d989be06"), + fromHex("7dc612d7b0579135ed8db8b9a4861386be97c05fa343a3e4233f535c0a2ffb26"), block1.DeriveBlockHash(arbitraryHash, null) ); AssertBytesEqual( - fromHex("37cc2ae1e59e01192d14dc6d09b9d52e4fbc8cdf11db2fa181f000fd2c00b6c1"), + fromHex("f7de4a0462dde48da9b57de8a642f71376ae706f2a58ca28ec2dce2d50641e27"), block1.DeriveBlockHash( arbitraryHash, block1.MakeSignature(_contents.Block1Key, arbitraryHash) ) diff --git a/test/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs b/test/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs index e191d4e796a..345f0f54a5f 100644 --- a/test/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs +++ b/test/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs @@ -85,7 +85,7 @@ public void Evaluate() previousHash: genesis.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, - proof: null, + proof: _contents.Block1Content.Proof, evidenceHash: null), transactions: txs, evidence: evs); @@ -162,7 +162,7 @@ public void DetermineStateRootHash() previousHash: genesis.Hash, txHash: BlockContent.DeriveTxHash(txs), lastCommit: null, - proof: null, + proof: _contents.Block1Content.Proof, evidenceHash: null), transactions: txs, evidence: evs); diff --git a/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs b/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs index 8d6b4f47e0c..3786fd97ef2 100644 --- a/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs +++ b/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs @@ -70,7 +70,7 @@ public BlockContentFixture() previousHash: null, txHash: BlockContent.DeriveTxHash(genTxs), lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(null, GenesisKey), evidenceHash: BlockContent.DeriveEvidenceHash(genEvidence)), transactions: genTxs, evidence: genEvidence); @@ -137,7 +137,7 @@ public BlockContentFixture() previousHash: GenesisHash, txHash: BlockContent.DeriveTxHash(block1Transactions), lastCommit: null, - proof: null, + proof: TestUtils.CreateZeroRoundProof(GenesisMetadata, Block1Key), evidenceHash: BlockContent.DeriveEvidenceHash(block1Evidence)), transactions: block1Transactions, evidence: block1Evidence); diff --git a/test/Libplanet.Tests/Store/StoreFixture.cs b/test/Libplanet.Tests/Store/StoreFixture.cs index 0c480826495..fca24c5cfcb 100644 --- a/test/Libplanet.Tests/Store/StoreFixture.cs +++ b/test/Libplanet.Tests/Store/StoreFixture.cs @@ -114,22 +114,28 @@ protected StoreFixture(PolicyActionsRegistry policyActionsRegistry = null) GenesisBlock, miner: Proposer, stateRootHash: genesisNextSrh, - lastCommit: null); + lastCommit: null, + proof: TestUtils.CreateZeroRoundProof(GenesisBlock, Proposer)); stateRootHashes[Block1.Hash] = Block1.StateRootHash; Block2 = TestUtils.ProposeNextBlock( Block1, miner: Proposer, stateRootHash: genesisNextSrh, - lastCommit: TestUtils.CreateBlockCommit(Block1)); + lastCommit: TestUtils.CreateBlockCommit(Block1), + proof: TestUtils.CreateZeroRoundProof(Block1, Proposer)); stateRootHashes[Block2.Hash] = Block2.StateRootHash; Block3 = TestUtils.ProposeNextBlock( Block2, miner: Proposer, stateRootHash: genesisNextSrh, - lastCommit: TestUtils.CreateBlockCommit(Block2)); + lastCommit: TestUtils.CreateBlockCommit(Block2), + proof: TestUtils.CreateZeroRoundProof(Block2, Proposer)); stateRootHashes[Block3.Hash] = Block3.StateRootHash; Block3Alt = TestUtils.ProposeNextBlock( - Block2, miner: Proposer, stateRootHash: genesisNextSrh); + Block2, + miner: Proposer, + stateRootHash: genesisNextSrh, + proof: TestUtils.CreateZeroRoundProof(Block2, Proposer)); stateRootHashes[Block3Alt.Hash] = Block3Alt.StateRootHash; Block4 = TestUtils.ProposeNextBlock( Block3, miner: Proposer, stateRootHash: genesisNextSrh); diff --git a/test/Libplanet.Tests/TestUtils.cs b/test/Libplanet.Tests/TestUtils.cs index 4ad7ab80f4d..03b9b10436c 100644 --- a/test/Libplanet.Tests/TestUtils.cs +++ b/test/Libplanet.Tests/TestUtils.cs @@ -413,9 +413,9 @@ public static BlockCommit CreateBlockCommit( } public static Proof CreateZeroRoundProof( - Block tip, + IBlockMetadata tip, PrivateKey proposerKey) - => new ConsensusInformation(tip.Index + 1, 0, tip.Proof).Prove(proposerKey); + => new ConsensusInformation((tip?.Index ?? -1) + 1, 0, tip?.Proof).Prove(proposerKey); public static PreEvaluationBlock ProposeGenesis( PublicKey proposer = null, From 7eb55a2e0ae1c0ad1a9ce8ceaec7557b13d43d99 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 23 Jul 2024 22:54:12 +0900 Subject: [PATCH 59/61] test: Test fix from rebasing --- test/Libplanet.Tests/Action/ActionEvaluatorTest.cs | 4 ++++ .../Blockchain/BlockChainTest.Fork.cs | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs index 92c97b63737..f732b508fd3 100644 --- a/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/test/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -309,6 +309,7 @@ public void EvaluateWithPolicyActions() proposer: GenesisProposer, transactions: txs.ToImmutableList(), lastCommit: CreateBlockCommit(chain.Tip), + proof: CreateZeroRoundProof(chain.Tip, GenesisProposer), evidence: ImmutableArray.Empty); var evaluations = actionEvaluator.Evaluate( block, chain.Store.GetStateRootHash(chain.Tip.Hash)).ToArray(); @@ -1043,6 +1044,7 @@ public void EvaluatePolicyBeginBlockActions() proposer: GenesisProposer, transactions: txs.ToImmutableList(), lastCommit: CreateBlockCommit(chain.Tip), + proof: CreateZeroRoundProof(chain.Tip, GenesisProposer), evidence: ImmutableArray.Empty); IWorld previousState = _storeFx.StateStore.GetWorld(null); @@ -1147,6 +1149,7 @@ public void EvaluatePolicyBeginTxActions() proposer: GenesisProposer, transactions: txs.ToImmutableList(), lastCommit: CreateBlockCommit(chain.Tip), + proof: CreateZeroRoundProof(chain.Tip, GenesisProposer), evidence: ImmutableArray.Empty); IWorld previousState = _storeFx.StateStore.GetWorld(null); @@ -1202,6 +1205,7 @@ public void EvaluatePolicyEndTxActions() proposer: GenesisProposer, transactions: txs.ToImmutableList(), lastCommit: CreateBlockCommit(chain.Tip), + proof: CreateZeroRoundProof(chain.Tip, GenesisProposer), evidence: ImmutableArray.Empty); IWorld previousState = _storeFx.StateStore.GetWorld(null); diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs index 790846011db..ec1a7b7e81e 100644 --- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs +++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs @@ -201,7 +201,7 @@ public void ForkWithBlockNotExistInChain() [SkippableFact] public void ForkChainWithIncompleteBlockStates() { - var fx = new MemoryStoreFixture(_policy.BlockAction); + var fx = new MemoryStoreFixture(_policy.PolicyActionsRegistry); (_, _, BlockChain chain) = MakeIncompleteBlockStates(fx.Store, fx.StateStore); BlockChain forked = chain.Fork(chain[5].Hash); @@ -262,7 +262,7 @@ public void ForkShouldSkipExecuteAndRenderGenesis() using (var stateStore = new TrieStateStore(new MemoryKeyValueStore())) { var actionEvaluator = new ActionEvaluator( - _ => _policy.BlockAction, + _policy.PolicyActionsRegistry, stateStore, new SingleActionLoader(typeof(DumbAction))); var privateKey = new PrivateKey(); @@ -294,7 +294,7 @@ public void ForkShouldSkipExecuteAndRenderGenesis() stateStore, genesis, new ActionEvaluator( - _ => _policy.BlockAction, + _policy.PolicyActionsRegistry, stateStore: stateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction))), renderers: new[] { renderer } @@ -665,8 +665,8 @@ public void FindBranchPoint() var locator = new BlockLocator( new[] { b4.Hash, b3.Hash, b1.Hash, _blockChain.Genesis.Hash }); - using (var emptyFx = new MemoryStoreFixture(_policy.BlockAction)) - using (var forkFx = new MemoryStoreFixture(_policy.BlockAction)) + using (var emptyFx = new MemoryStoreFixture(_policy.PolicyActionsRegistry)) + using (var forkFx = new MemoryStoreFixture(_policy.PolicyActionsRegistry)) { var emptyChain = BlockChain.Create( _blockChain.Policy, @@ -675,7 +675,7 @@ public void FindBranchPoint() emptyFx.StateStore, emptyFx.GenesisBlock, new ActionEvaluator( - _ => _blockChain.Policy.BlockAction, + _blockChain.Policy.PolicyActionsRegistry, stateStore: emptyFx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); var fork = BlockChain.Create( @@ -685,7 +685,7 @@ public void FindBranchPoint() forkFx.StateStore, forkFx.GenesisBlock, new ActionEvaluator( - _ => _blockChain.Policy.BlockAction, + _blockChain.Policy.PolicyActionsRegistry, stateStore: forkFx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); fork.Append(b1, CreateBlockCommit(b1)); From 5f5ba47917646301556fb1b277ef4b6c30ea82f7 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 23 Jul 2024 23:12:55 +0900 Subject: [PATCH 60/61] document: Update changelog --- CHANGES.md | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 81cb6a99929..9dab7398ac3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,10 +8,13 @@ To be released. ### Deprecated APIs - - `ValidatorSet.GetProposer()` has been removed. [[#VRF]] + - `ValidatorSet.GetProposer()` has been removed. [[#3895]] ### Backward-incompatible API changes + - `BlockMetadata.CurrentProtocolVersion` has been changed from 9 to 10. + [[#3895]] + ### Backward-incompatible network protocol changes ### Backward-incompatible storage format changes @@ -20,40 +23,38 @@ To be released. - Added `IConsensusCryptoBackend` interface, which contains VRF functionalities which is used on the consensus as a pseudo-random function. - [[#VRF]] + [[#3895]] - Added `DefaultConsensusCryptoBackend` class as a default implementation of - `IConsensusCryptoBackend`. [[#VRF]] + `IConsensusCryptoBackend`. [[#3895]] - Added `CryptoConfig.ConsensusCryptoBackend` property as a VRF backend used - on the consensus. [[#VRF]] - - Added `PrivateKey.Prove()` method as a proof generation. [[#VRF]] - - Added `PublicKey.VerifyProof()` method as a proof verification. [[#VRF]] + on the consensus. [[#3895]] + - Added `PrivateKey.Prove()` method as a proof generation. [[#3895]] + - Added `PublicKey.VerifyProof()` method as a proof verification. [[#3895]] - Added `Proof` struct as a wrapper structure of proof(pi-bytes) generated by - ECVRF. [[#VRF]] + ECVRF. [[#3895]] - Added `ConsensusInformation` struct as a base payload to be proved - with private key. [[#VRF]] - - `BlockMetadata.CurrentProtocolVersion` has been changed from 5 to 6. - [[#VRF]] - - Added `IBlockMetadata.Proof` property. [[#VRF]] + with private key. [[#3895]] + - Added `IBlockMetadata.Proof` property. [[#3895]] - Added `Lot` class as a content of message that submits a `Proof` - to be a proposer candidate during `ConsensusStep.Sortition`. [[#VRF]] + to be a proposer candidate during `ConsensusStep.Sortition`. [[#3895]] - Added `DominantLot` class as a content of message that submits a vote for - dominant `Lot` during `ConsensusStep.Sortition`. [[#VRF]]` - - Added `ConsensusStep.Sortition`. [[#VRF]] + dominant `Lot` during `ConsensusStep.Sortition`. [[#3895]]` + - Added `ConsensusStep.Sortition`. [[#3895]] - Added `LotGatherSecond`, `SortitionSecondBase`, `SortitionMultiplier` - to `ContestTimeoutOption`. [[#VRF]] + to `ContestTimeoutOption`. [[#3895]] - Added `ConsensusLotMsg` class as a `ConsensusMsg` broadcasted during - `ConsensusStep.Sortition`. [[#VRF]] + `ConsensusStep.Sortition`. [[#3895]] - Added `ConsensusDominantLotMsg` class as a `ConsensusMsg` broadcasted during - `ConsensusStep.Sortition`. after lot gathering delay. [[#VRF]] - - Added `LotSet` class as a `Lot` and `DominantLot` selector. [[#VRF]] + `ConsensusStep.Sortition`. after lot gathering delay. [[#3895]] + - Added `LotSet` class as a `Lot` and `DominantLot` selector. [[#3895]] ### Behavioral changes - `ActionEvaluator.EvaluateActions()` use `Proof.Seed` as a initial random seed instead of `PreEvaluationHash`, `signature` combined seed. - [[#VRF]] - - Proposer selection is now based on the VRF result. [[#VRF]] - - Consensus now starts with `ConsensusStep.Sortition`. [[#VRF]]` + [[#3895]] + - Proposer selection is now based on the VRF result. [[#3895]] + - Consensus now starts with `ConsensusStep.Sortition`. [[#3895]]` ### Bug fixes @@ -61,7 +62,7 @@ To be released. ### CLI tools -[#VRF]: https://github.com/planetarium/libplanet/pull/TBD +[#3895]: https://github.com/planetarium/libplanet/pull/3895 Version 5.2.0 From 93b311e3e5871b860dea8237a9b8baee6aca7bf8 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 23 Jul 2024 23:26:30 +0900 Subject: [PATCH 61/61] Lint code --- src/Libplanet.Types/Blocks/BlockMarshaler.cs | 1 - src/Libplanet.Types/Blocks/BlockMetadata.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Libplanet.Types/Blocks/BlockMarshaler.cs b/src/Libplanet.Types/Blocks/BlockMarshaler.cs index fed099bcff7..6e8efb0628c 100644 --- a/src/Libplanet.Types/Blocks/BlockMarshaler.cs +++ b/src/Libplanet.Types/Blocks/BlockMarshaler.cs @@ -316,6 +316,5 @@ public static Block UnmarshalBlock(Dictionary marshaled) marshaled.ContainsKey(ProofKey) ? new Proof(marshaled[ProofKey]) : (Proof?)null; - } } diff --git a/src/Libplanet.Types/Blocks/BlockMetadata.cs b/src/Libplanet.Types/Blocks/BlockMetadata.cs index bd232f7eb3c..b9c5a0edccf 100644 --- a/src/Libplanet.Types/Blocks/BlockMetadata.cs +++ b/src/Libplanet.Types/Blocks/BlockMetadata.cs @@ -165,8 +165,8 @@ public BlockMetadata(IBlockMetadata metadata) /// Goes to . /// Goes to . /// Goes to . - /// + /// public BlockMetadata( long index, DateTimeOffset timestamp,