diff --git a/CHANGES.md b/CHANGES.md
index 5ee168ce85d..9dab7398ac3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -8,22 +8,62 @@ To be released.
### Deprecated APIs
+ - `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
### Added APIs
+ - Added `IConsensusCryptoBackend` interface, which contains VRF
+ functionalities which is used on the consensus as a pseudo-random function.
+ [[#3895]]
+ - Added `DefaultConsensusCryptoBackend` class as a default implementation of
+ `IConsensusCryptoBackend`. [[#3895]]
+ - Added `CryptoConfig.ConsensusCryptoBackend` property as a VRF backend used
+ 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. [[#3895]]
+ - Added `ConsensusInformation` struct as a base payload to be proved
+ 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`. [[#3895]]
+ - Added `DominantLot` class as a content of message that submits a vote for
+ dominant `Lot` during `ConsensusStep.Sortition`. [[#3895]]`
+ - Added `ConsensusStep.Sortition`. [[#3895]]
+ - Added `LotGatherSecond`, `SortitionSecondBase`, `SortitionMultiplier`
+ to `ContestTimeoutOption`. [[#3895]]
+ - Added `ConsensusLotMsg` class as a `ConsensusMsg` broadcasted during
+ `ConsensusStep.Sortition`. [[#3895]]
+ - Added `ConsensusDominantLotMsg` class as a `ConsensusMsg` broadcasted during
+ `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.
+ [[#3895]]
+ - Proposer selection is now based on the VRF result. [[#3895]]
+ - Consensus now starts with `ConsensusStep.Sortition`. [[#3895]]`
+
### Bug fixes
### Dependencies
### CLI tools
+[#3895]: https://github.com/planetarium/libplanet/pull/3895
+
Version 5.2.0
-------------
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)
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..ead5730f375
--- /dev/null
+++ b/src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs
@@ -0,0 +1,271 @@
+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;
+ 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();
+ }
+
+ ///
+ /// 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();
+
+ 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;
+ }
+
+ ///
+ 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);
+
+ byte[] payload
+ = SuiteBytes
+ .Concat(new byte[] { 3 })
+ .Concat(gammaMul.GetEncoded(true))
+ .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 = _eCFieldBytesSize + 1;
+ int cBytesLen = _eCFieldBytesSize;
+ int sBytesLen = _eCFieldBytesSize;
+ if (piBytes.Length != gammaBytesLen + cBytesLen + sBytesLen)
+ {
+ throw new ArgumentException(
+ $"Length of piBytes are expected to be " +
+ $"{gammaBytesLen + cBytesLen + sBytesLen}, " +
+ $"but found {piBytes.Length}");
+ }
+
+ 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("s cannot be bigger than EC parameter N");
+ }
+
+ // dH : Gamma
+ 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/IConsensusCryptoBackend.cs b/src/Libplanet.Crypto/IConsensusCryptoBackend.cs
new file mode 100644
index 00000000000..d8b17f87448
--- /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);
+ }
+}
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/PrivateKey.cs b/src/Libplanet.Crypto/PrivateKey.cs
index 94644818f45..69537f750c3 100644
--- a/src/Libplanet.Crypto/PrivateKey.cs
+++ b/src/Libplanet.Crypto/PrivateKey.cs
@@ -230,6 +230,46 @@ 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..58b43175ef5
--- /dev/null
+++ b/src/Libplanet.Crypto/Proof.cs
@@ -0,0 +1,329 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Numerics;
+using Bencodex;
+using Bencodex.Misc;
+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 class Proof : IBencodable, IEquatable, IComparable, IComparable
+ {
+ 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
+ {
+ _hash = CryptoConfig.ConsensusCryptoBackend
+ .ProofToHash(piBytes.ToArray()).ToImmutableArray();
+ }
+ catch (ArgumentException e)
+ {
+ throw new InvalidProofException(
+ $"Bytes of Proof is invalid", e);
+ }
+
+ _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
+ : throw new ArgumentException(
+ $"Given {nameof(bencoded)} must be of type " +
+ $"{typeof(Binary)}: {bencoded.GetType()}",
+ nameof(bencoded)))
+ {
+ }
+
+ ///
+ /// Creates a new instance of with given
+ /// proof value.
+ ///
+ /// Bencodex-encoded proof value.
+ 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 => _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);
+ }
+ }
+
+ ///
+ /// 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 .
+ ///
+ /// 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)
+ => 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)
+ => 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));
+
+ ///
+ public override int GetHashCode()
+ => ByteUtil.CalculateHashCode(ToByteArray());
+
+ ///
+ public override string ToString()
+ => ByteArray.Hex();
+
+ 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..90115c27bc3 100644
--- a/src/Libplanet.Crypto/PublicKey.cs
+++ b/src/Libplanet.Crypto/PublicKey.cs
@@ -216,6 +216,26 @@ 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(IReadOnlyList message, Proof proof)
+ => CryptoConfig.ConsensusCryptoBackend.VerifyProof(
+ message.ToArray(), proof.ToByteArray(), this);
+
///
/// Gets the public key's hexadecimal representation in compressed form.
///
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..569569131f0 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,40 @@ 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();
+ await Task.Delay(delay, _cancellationTokenSource.Token);
+ if (_lotSet.DominantLot is { } lot)
+ {
+ DominantLot dominantLot = new DominantLotMetadata(
+ lot,
+ DateTimeOffset.UtcNow,
+ _privateKey.PublicKey).Sign(_privateKey);
+ PublishMessage(new ConsensusDominantLotMsg(dominantLot));
+ }
+ }
+
+ ///
+ /// 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);
+ 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 +280,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 +296,8 @@ public ContextState(
public int VoteCount { get; }
+ public int DominantLotCount { get; }
+
public long Height { get; }
public int Round { get; }
@@ -266,6 +309,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 +322,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..00fa27bd29e 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,13 @@ 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)>?
+ LotSetModified;
}
}
diff --git a/src/Libplanet.Net/Consensus/Context.Mutate.cs b/src/Libplanet.Net/Consensus/Context.Mutate.cs
index 8b71cfee34b..d4e867e941e 100644
--- a/src/Libplanet.Net/Consensus/Context.Mutate.cs
+++ b/src/Libplanet.Net/Consensus/Context.Mutate.cs
@@ -27,43 +27,20 @@ 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.
+ _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();
}
///
@@ -75,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 thrown when the internal does not accept it, i.e.
+ /// or
+ /// returns .
///
///
private bool AddMessage(ConsensusMsg message)
@@ -98,9 +79,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 +160,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 +216,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 +275,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 +535,35 @@ 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;
}
}
+ ///
+ /// 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)
+ {
+ 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 +613,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 69453b615fb..04a30a3f701 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 LotSet _lotSet;
private readonly HeightVoteSet _heightVoteSet;
private readonly PrivateKey _privateKey;
private readonly HashSet _preVoteTimeoutFlags;
@@ -105,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.
@@ -139,6 +141,7 @@ public Context(
validators,
ConsensusStep.Default,
-1,
+ 20,
128,
contextTimeoutOptions)
{
@@ -152,6 +155,7 @@ private Context(
ValidatorSet validators,
ConsensusStep consensusStep,
int round = -1,
+ int drawSize = 20,
int cacheSize = 128,
ContextTimeoutOption? contextTimeoutOptions = null)
{
@@ -172,6 +176,7 @@ private Context(
Round = round;
Step = consensusStep;
_lastCommit = lastCommit;
+ _lastProof = blockChain[height - 1].Proof;
_lockedValue = null;
_lockedRound = -1;
_validValue = null;
@@ -182,6 +187,7 @@ private Context(
_codec = new Codec();
_messageRequests = Channel.CreateUnbounded();
_mutationRequests = Channel.CreateUnbounded();
+ _lotSet = new LotSet(height, round, _lastProof, validators, drawSize);
_heightVoteSet = new HeightVoteSet(height, validators);
_preVoteTimeoutFlags = new HashSet();
_hasTwoThirdsPreVoteFlags = new HashSet();
@@ -214,8 +220,17 @@ 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;
+
///
public void Dispose()
{
@@ -410,6 +425,28 @@ 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(
+ _contextTimeoutOption.SortitionSecondBase +
+ round * _contextTimeoutOption.SortitionMultiplier);
+ }
+
+ ///
+ /// Gets the delay to gather s to determine .
+ ///
+ /// A duration in .
+ private TimeSpan DelayLotGather()
+ {
+ return TimeSpan.FromSeconds(_contextTimeoutOption.LotGatherSecond);
+ }
+
///
/// Creates a new to propose.
///
@@ -419,8 +456,11 @@ private TimeSpan TimeoutPropose(long round)
{
try
{
- var evidence = _blockChain.GetPendingEvidence();
- Block block = _blockChain.ProposeBlock(_privateKey, _lastCommit, evidence);
+ Block block = _blockChain.ProposeBlock(
+ _privateKey,
+ _lastCommit,
+ _lotSet.GenerateProof(_privateKey),
+ _blockChain.GetPendingEvidence());
_blockChain.Store.PutBlock(block);
return block;
}
@@ -470,6 +510,16 @@ private bool IsValid(Block block)
try
{
+ if (!(block.Proof is Proof proof
+ && 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
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..febe749e4c9
--- /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/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
new file mode 100644
index 00000000000..2ba7d70927e
--- /dev/null
+++ b/src/Libplanet.Net/Consensus/LotSet.cs
@@ -0,0 +1,270 @@
+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;
+
+ ///
+ /// 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(
+ new ConsensusInformation(height, round, lastProof),
+ validatorSet,
+ drawSize)
+ {
+ }
+
+ ///
+ /// 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)
+ {
+ _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;
+ }
+
+ ///
+ /// 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;
+
+ ///
+ /// 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)
+ {
+ if (round <= Round)
+ {
+ throw new ArgumentException(
+ $"Parameter round must be greater than the current round {Round}.",
+ nameof(round));
+ }
+
+ _consensusInformation = new ConsensusInformation(Height, round, lastProof);
+ _lots.Clear();
+ _dominantLot = null;
+ _dominantLots.Clear();
+ _lotsPower.Clear();
+ 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))
+ {
+ throw new InvalidLotException(
+ $"Found duplicated {nameof(lot)} of public key {lot.PublicKey}",
+ lot);
+ }
+
+ ValidateLot(lot);
+ _lots.TryAdd(lot.PublicKey, 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))
+ {
+ 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);
+ }
+
+ private 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
+ || (drawn == dominantLot.Item2 && lot.Proof <= dominantLot.Item1.Proof))))
+ {
+ _dominantLot = (lot, drawn);
+ }
+ }
+ }
+}
diff --git a/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs b/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs
new file mode 100644
index 00000000000..3ed5b360cb5
--- /dev/null
+++ b/src/Libplanet.Net/Messages/ConsensusDominantLotMsg.cs
@@ -0,0 +1,68 @@
+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.Lot.ConsensusInformation.Height,
+ dominantLot.Lot.ConsensusInformation.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/ConsensusLotMsg.cs b/src/Libplanet.Net/Messages/ConsensusLotMsg.cs
new file mode 100644
index 00000000000..6ca3b11122a
--- /dev/null
+++ b/src/Libplanet.Net/Messages/ConsensusLotMsg.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using Libplanet.Consensus;
+using Libplanet.Net.Consensus;
+
+namespace Libplanet.Net.Messages
+{
+ ///
+ /// A message class for .
+ ///
+ public class ConsensusLotMsg : ConsensusMsg
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A
+ /// of given height and round.
+ public ConsensusLotMsg(Lot lot)
+ : base(lot.PublicKey, lot.ConsensusInformation.Height, lot.ConsensusInformation.Round)
+ {
+ Lot = lot;
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with marshalled message.
+ ///
+ /// A marshalled message.
+ public ConsensusLotMsg(byte[][] dataframes)
+ : this(lot: new Lot(dataframes[0]))
+ {
+ }
+
+ ///
+ /// A of the message.
+ ///
+ public Lot Lot { get; }
+
+ ///
+ public override IEnumerable DataFrames =>
+ new List { Lot.ToByteArray() };
+
+ ///
+ public override MessageType Type => MessageType.ConsensusLot;
+
+ ///
+ public override bool Equals(ConsensusMsg? other)
+ {
+ return other is ConsensusLotMsg message && message.Lot.Equals(Lot);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is ConsensusMsg other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Type, Lot);
+ }
+ }
+}
diff --git a/src/Libplanet.Net/Messages/MessageContent.cs b/src/Libplanet.Net/Messages/MessageContent.cs
index dce4c52d744..10745d741c2 100644
--- a/src/Libplanet.Net/Messages/MessageContent.cs
+++ b/src/Libplanet.Net/Messages/MessageContent.cs
@@ -136,20 +136,30 @@ public enum MessageType : byte
///
ConsensusProposalClaimMsg = 0x55,
+ ///
+ /// Consensus message that sends a lot to be drawn.
+ ///
+ 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.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..6e8efb0628c 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'
@@ -41,6 +42,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 +80,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);
@@ -166,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];
@@ -204,6 +234,7 @@ public static BlockMetadata UnmarshalBlockMetadata(Dictionary marshaled)
lastCommit: marshaled.ContainsKey(LastCommitKey)
? new BlockCommit(marshaled[LastCommitKey])
: (BlockCommit?)null,
+ proof: UnmarshalProof(marshaled),
evidenceHash: marshaled.TryGetValue(EvidenceHashKey, out IValue ehv)
? new HashDigest(ehv)
: (HashDigest?)null);
@@ -280,5 +311,10 @@ public static Block UnmarshalBlock(Dictionary marshaled)
IReadOnlyList evidence = UnmarshalBlockEvidence(marshaled);
return new Block(header, txs, evidence);
}
+
+ public static Proof? UnmarshalProof(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 23d239b0a99..b9c5a0edccf 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();
@@ -138,6 +147,7 @@ public BlockMetadata(IBlockMetadata metadata)
previousHash: metadata.PreviousHash,
txHash: metadata.TxHash,
lastCommit: metadata.LastCommit,
+ proof: metadata.Proof,
evidenceHash: metadata.EvidenceHash)
{
}
@@ -153,9 +163,10 @@ public BlockMetadata(IBlockMetadata metadata)
/// Goes to .
/// Goes to .
/// Goes to .
+ /// Goes to .
/// Goes to .
- ///
+ ///
public BlockMetadata(
long index,
DateTimeOffset timestamp,
@@ -163,6 +174,7 @@ public BlockMetadata(
BlockHash? previousHash,
HashDigest? txHash,
BlockCommit? lastCommit,
+ Proof? proof,
HashDigest? evidenceHash)
: this(
protocolVersion: CurrentProtocolVersion,
@@ -173,6 +185,7 @@ public BlockMetadata(
previousHash: previousHash,
txHash: txHash,
lastCommit: lastCommit,
+ proof: proof,
evidenceHash: evidenceHash)
{
}
@@ -193,6 +206,7 @@ public BlockMetadata(
/// Goes to .
/// Goes to .
/// Goes to .
+ /// Goes to .
/// Goes to .
/// Thrown when
/// is less than zero or greater than
@@ -222,6 +236,7 @@ public BlockMetadata(
BlockHash? previousHash,
HashDigest? txHash,
BlockCommit? lastCommit,
+ Proof? proof,
HashDigest? evidenceHash)
{
// Protocol version validity check.
@@ -309,6 +324,7 @@ public BlockMetadata(
TxHash = txHash;
LastCommit = lastCommit;
+ Proof = proof;
EvidenceHash = evidenceHash;
}
@@ -331,9 +347,13 @@ public BlockMetadata(
public BlockHash? PreviousHash { get; }
///
- public HashDigest? TxHash { get; private set; }
+ public HashDigest? TxHash { get; }
- public BlockCommit? LastCommit { get; set; }
+ ///
+ public BlockCommit? LastCommit { get; }
+
+ ///
+ public Proof? Proof { get; }
public HashDigest? EvidenceHash { get; private set; }
@@ -368,6 +388,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/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.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.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/Blockchain/BlockChain.ProposeBlock.cs b/src/Libplanet/Blockchain/BlockChain.ProposeBlock.cs
index f2e98380a79..779ba6db9c9 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,6 +63,7 @@ public static Block ProposeGenesisBlock(
previousHash: null,
txHash: BlockContent.DeriveTxHash(transactions),
lastCommit: null,
+ proof: new ConsensusInformation(0L, 0, null).Prove(privateKey),
evidenceHash: null),
transactions: transactions,
evidence: Array.Empty());
@@ -84,6 +86,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 +97,7 @@ public static Block ProposeGenesisBlock(
public Block ProposeBlock(
PrivateKey proposer,
BlockCommit lastCommit = null,
+ Proof proof = null,
ImmutableArray? evidence = null,
IComparer txPriority = null)
{
@@ -116,6 +120,7 @@ public Block ProposeBlock(
proposer,
transactions,
lastCommit,
+ proof,
evidence ?? ImmutableArray.Empty);
_logger.Debug(
"Proposed block #{Index} {Hash} with previous hash {PreviousHash}",
@@ -132,8 +137,8 @@ public Block ProposeBlock(
/// list of s.
///
///
- /// Unlike ,
+ /// Unlike ,
/// this may result in a that does not conform to the
/// .
///
@@ -143,12 +148,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 +181,7 @@ internal Block ProposeBlock(
previousHash: prevHash,
txHash: BlockContent.DeriveTxHash(orderedTransactions),
lastCommit: lastCommit,
+ proof: proof,
evidenceHash: BlockContent.DeriveEvidenceHash(orderedEvidence)),
transactions: orderedTransactions,
evidence: orderedEvidence);
diff --git a/src/Libplanet/Blockchain/BlockChain.Validate.cs b/src/Libplanet/Blockchain/BlockChain.Validate.cs
index c471fb5661b..6ee875f6ba6 100644
--- a/src/Libplanet/Blockchain/BlockChain.Validate.cs
+++ b/src/Libplanet/Blockchain/BlockChain.Validate.cs
@@ -314,6 +314,20 @@ internal void ValidateBlock(Block block)
}
}
+ 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)
+ {
+ throw new InvalidBlockProofException(
+ "Block of protocol version higher than 9 must contain proof.");
+ }
+
foreach (var ev in block.Evidence)
{
var stateRootHash = GetNextStateRootHash(ev.Height);
diff --git a/src/Libplanet/Consensus/ConsensusInformation.cs b/src/Libplanet/Consensus/ConsensusInformation.cs
new file mode 100644
index 00000000000..b9373424137
--- /dev/null
+++ b/src/Libplanet/Consensus/ConsensusInformation.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text.Json;
+using Bencodex;
+using Bencodex.Types;
+using Libplanet.Crypto;
+
+namespace Libplanet.Consensus
+{
+ ///
+ /// Consensus round information used as payload of the
+ /// in the
+ /// .
+ ///
+ public class 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 deciding is 0, it indicates
+ /// from the previous .
+ ///
+ /// 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 < -1)
+ {
+ throw new ArgumentException(
+ $"Given {nameof(round)} cannot be less than -1: {round}");
+ }
+
+ Height = height;
+ Round = round;
+ LastProof = lastProof;
+ Encoded = Encode(height, round, lastProof);
+ }
+
+ ///
+ /// Instantiates with byte array encoded form
+ /// .
+ ///
+ /// Byte array encoded .
+ public ConsensusInformation(IReadOnlyList encoded)
+ : this(_codec.Decode(encoded.ToArray()))
+ {
+ }
+
+ private 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)
+ ? 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 .
+ ///
+ 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 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
+ /// .
+ ///
+ ///
+ /// to prove with.
+ ///
+ ///
+ /// that has been proved by .
+ public Proof Prove(PrivateKey prover)
+ {
+ if (Round < 0)
+ {
+ throw new InvalidOperationException(
+ $"Cannot prove {nameof(ConsensusInformation)} " +
+ $"with negative {nameof(Round)}: {Round}");
+ }
+
+ return 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)
+ {
+ 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)
+ => other is ConsensusInformation ci
+ && Height == ci.Height
+ && Round == ci.Round
+ && (LastProof?.Equals(ci.LastProof) ?? ci.LastProof is null);
+
+ ///
+ public override bool Equals(object? obj) =>
+ obj is ConsensusInformation other && Equals(other);
+
+ ///
+ public override int GetHashCode()
+ => HashCode.Combine(
+ Height,
+ Round,
+ LastProof);
+
+ ///
+ public override string ToString()
+ {
+ 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)
+ {
+ Dictionary bencoded = Dictionary.Empty
+ .Add(HeightKey, height)
+ .Add(RoundKey, round);
+
+ if (lastProof is Proof lastProofNonNull)
+ {
+ 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();
+ }
+}
diff --git a/src/Libplanet/Consensus/DominantLot.cs b/src/Libplanet/Consensus/DominantLot.cs
new file mode 100644
index 00000000000..26e3298554e
--- /dev/null
+++ b/src/Libplanet/Consensus/DominantLot.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+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(IReadOnlyList marshaled)
+ : this((Dictionary)_codec.Decode(marshaled.ToArray()))
+ {
+ }
+
+#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 Bencoded =>
+ !Signature.IsEmpty
+ ? _dominantLotMetadata.Bencoded.Add(SignatureKey, Signature)
+ : _dominantLotMetadata.Bencoded;
+
+ ///
+ /// encoded data.
+ ///
+ public ImmutableArray ByteArray => ToByteArray().ToImmutableArray();
+
+ public byte[] ToByteArray() => _codec.Encode(Bencoded);
+
+ ///
+ [Pure]
+ public bool Equals(DominantLot? other)
+ {
+ return other is DominantLot dominantLot &&
+ _dominantLotMetadata.Equals(dominantLot._dominantLotMetadata) &&
+ Signature.SequenceEqual(dominantLot.Signature);
+ }
+
+ ///
+ [Pure]
+ public override bool Equals(object? obj)
+ {
+ return obj is DominantLot 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..b33fdcb8e1c
--- /dev/null
+++ b/src/Libplanet/Consensus/DominantLotMetadata.cs
@@ -0,0 +1,152 @@
+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 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();
+
+ ///
+ /// 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(
+ Lot lot,
+ DateTimeOffset timestamp,
+ PublicKey validatorPublicKey)
+ {
+ Lot = lot;
+ Timestamp = timestamp;
+ ValidatorPublicKey = validatorPublicKey;
+ }
+
+#pragma warning disable SA1118 // The parameter spans multiple lines
+ public DominantLotMetadata(Dictionary encoded)
+ : this(
+ 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
+
+ ///
+ /// The that is dominant.
+ ///
+ public Lot Lot { get; }
+
+ ///
+ /// The time at which the dominant lot selected.
+ ///
+ public DateTimeOffset Timestamp { get; }
+
+ ///
+ /// The of the validator selected dominant lot.
+ ///
+ public PublicKey ValidatorPublicKey { get; }
+
+ ///
+ /// The height of the dominant lot.
+ ///
+ public long Height => Lot.Height;
+
+ ///
+ /// The round of the dominant lot.
+ ///
+ public int Round => Lot.Round;
+
+ ///
+ /// A Bencodex-encoded value of .
+ ///
+ [JsonIgnore]
+ public Dictionary Bencoded
+ {
+ get
+ {
+ Dictionary encoded = Bencodex.Types.Dictionary.Empty
+ .Add(LotKey, Lot.Bencoded)
+ .Add(
+ TimestampKey,
+ Timestamp.ToString(TimestampFormat, CultureInfo.InvariantCulture))
+ .Add(ValidatorPublicKeyKey, ValidatorPublicKey.Format(compress: true));
+
+ return encoded;
+ }
+ }
+
+ ///
+ /// 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);
+
+ ///
+ /// 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..16bcb41b067
--- /dev/null
+++ b/src/Libplanet/Consensus/Lot.cs
@@ -0,0 +1,142 @@
+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.Types;
+using Libplanet.Crypto;
+
+namespace Libplanet.Consensus
+{
+ public class Lot : IBencodable, IEquatable
+ {
+ 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 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
+ .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)
+ => other is Lot lot
+ && Proof.Equals(lot.Proof)
+ && PublicKey.Equals(lot.PublicKey)
+ && ConsensusInformation.Equals(lot.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 },
+ { "public_key", PublicKey.ToString() },
+ { "consensus_information", ConsensusInformation },
+ };
+ return JsonSerializer.Serialize(dict);
+ }
+ }
+}
diff --git a/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs b/test/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs
index 79487a95b6d..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,6 +180,7 @@ private void AddBlock(ImmutableArray transactions)
Chain.Tip.Hash,
BlockContent.DeriveTxHash(transactions),
Chain.Store.GetChainBlockCommit(Chain.Store.GetCanonicalChainId()!.Value),
+ 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 1c47d06289f..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,6 +44,7 @@ public async void Query()
previousHash: lastBlockHash,
txHash: null,
lastCommit: lastBlockCommit,
+ 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 cbc4ee73beb..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;
@@ -43,7 +44,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 ConsensusInformation(forkedChain.Tip.Index + 1, 0, forkedChain.Tip.Proof)
+ .Prove(ChainFx.PrivateKeys[0]));
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..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);
@@ -140,8 +143,9 @@ 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()));
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/Consensus/ConsensusContextNonProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs
index d681debcfbe..0b076bfddb2 100644
--- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs
+++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextNonProposerTest.cs
@@ -58,7 +58,19 @@ public async void NewHeightWithLastCommit()
};
consensusContext.Start();
- var block1 = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]);
+ 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], lot)));
+ }
+
+ 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];
@@ -93,6 +105,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], lot)));
+ }
+
await heightTwoProposalSent.WaitAsync();
Assert.NotNull(proposal);
@@ -112,9 +133,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();
@@ -158,17 +181,31 @@ 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();
}
};
- 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]));
+
+ 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], lot)));
+ }
+
await proposalSent.WaitAsync();
Assert.Equal(2, consensusContext.Height);
@@ -231,7 +268,18 @@ 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]));
+
+ 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], lot)));
+ }
// Message from higher height
consensusContext.HandleMessage(
@@ -269,10 +317,32 @@ public async void UseLastCommitCacheIfHeightContextIsEmpty()
};
consensusContext.Start();
- Block block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]);
+ 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], 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(
+ 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], lot)));
+ }
+
// Context for height #2 where node #2 is the proposer is automatically started
// by watching blockchain's Tip.
await heightTwoProposalSent.WaitAsync();
@@ -289,7 +359,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,
@@ -301,17 +371,26 @@ 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 block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]);
+ 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], lot)));
+ }
+
+ var block = blockChain.ProposeBlock(
+ TestUtils.PrivateKeys[1],
+ proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1]));
consensusContext.HandleMessage(
TestUtils.CreateConsensusPropose(block, TestUtils.PrivateKeys[1]));
@@ -321,15 +400,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 f292c41c5d4..afea7d212b9 100644
--- a/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs
+++ b/test/Libplanet.Net.Tests/Consensus/ConsensusContextTest.cs
@@ -83,13 +83,49 @@ public async void NewHeightIncreasing()
}
};
- var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]);
+ 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], 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);
- block = blockChain.ProposeBlock(TestUtils.PrivateKeys[2], blockCommit);
+
+ 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], lot)));
+ }
+
+ 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);
+ 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)));
+ }
+
// Wait for context of height 3 to start.
await heightThreeStepChangedToPropose.WaitAsync();
Assert.Equal(3, consensusContext.Height);
@@ -134,6 +170,16 @@ public async void NewHeightIncreasing()
await onTipChangedToThree.WaitAsync();
Assert.Equal(3, blockChain.Tip.Index);
+ 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)));
+ }
+
// Next height starts normally.
await heightFourStepChangedToPropose.WaitAsync();
Assert.Equal(4, consensusContext.Height);
@@ -176,9 +222,11 @@ 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());
+ Block block = blockChain.ProposeBlock(
+ proposer,
+ 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 +245,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)));
}
@@ -232,6 +283,16 @@ public async void VoteSetGetOnlyProposeCommitHash()
};
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 })
+ {
+ consensusContext.HandleMessage(
+ new ConsensusDominantLotMsg(
+ TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot)));
+ }
+
await heightOneProposalSent.WaitAsync();
BlockHash proposedblockHash = Assert.IsType(proposal?.BlockHash);
@@ -285,7 +346,19 @@ public async Task GetVoteSetBits()
TestUtils.ActionLoader,
TestUtils.PrivateKeys[0]);
consensusContext.Start();
- var block = blockChain.ProposeBlock(proposer);
+ 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], lot)));
+ }
+
+ var block = blockChain.ProposeBlock(
+ proposer,
+ proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer));
var proposal = new ProposalMetadata(
1,
0,
@@ -371,7 +444,19 @@ public async Task HandleVoteSetBits()
};
consensusContext.Start();
- var block = blockChain.ProposeBlock(proposer);
+ 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], lot)));
+ }
+
+ var block = blockChain.ProposeBlock(
+ proposer,
+ proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, proposer));
var proposal = new ProposalMetadata(
1,
0,
@@ -443,7 +528,19 @@ public async Task HandleProposalClaim()
}
};
consensusContext.Start();
- var block = blockChain.ProposeBlock(proposer);
+ 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], lot)));
+ }
+
+ 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/ContextNonProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs
index 9c4e0824ad2..c01bdc5e22b 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], 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], lot)));
+ }
+
+ var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1], proof: lot.Proof);
+
context.ProduceMessage(
TestUtils.CreateConsensusPropose(block, TestUtils.PrivateKeys[1]));
@@ -181,6 +205,7 @@ public async void EnterPreCommitNilTwoThird()
previousHash: blockChain.Tip.Hash,
txHash: null,
lastCommit: null,
+ proof: null,
evidenceHash: null)).Propose(),
key);
@@ -201,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], lot)));
+ }
+
context.ProduceMessage(
TestUtils.CreateConsensusPropose(invalidBlock, TestUtils.PrivateKeys[1]));
context.ProduceMessage(
@@ -280,10 +317,23 @@ public async Task EnterPreVoteNilOnInvalidBlockHeader()
previousHash: blockChain.Tip.Hash,
txHash: null,
lastCommit: null,
+ proof: null,
evidenceHash: null)).Propose(),
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], lot)));
+ }
+
context.ProduceMessage(
TestUtils.CreateConsensusPropose(
invalidBlock, TestUtils.PrivateKeys[1]));
@@ -355,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], lot)));
+ }
+
context.ProduceMessage(
TestUtils.CreateConsensusPropose(
invalidBlock,
@@ -429,6 +491,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(
@@ -440,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], lot)));
+ }
+
context.ProduceMessage(
TestUtils.CreateConsensusPropose(
invalidBlock,
@@ -575,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], 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], lot)));
+ }
+
// Push round 0 and round 1 proposes.
context.ProduceMessage(
TestUtils.CreateConsensusPropose(
@@ -644,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], lot)));
+ }
+
+ var block = blockChain.ProposeBlock(
+ TestUtils.PrivateKeys[1],
+ proof: lot.Proof);
+
context.ProduceMessage(
TestUtils.CreateConsensusPropose(
block, TestUtils.PrivateKeys[1], round: 0));
@@ -695,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], lot)));
+ }
+
+ var block = blockChain.ProposeBlock(
+ TestUtils.PrivateKeys[1],
+ proof: lot.Proof);
+
context.ProduceMessage(
TestUtils.CreateConsensusPropose(
block, TestUtils.PrivateKeys[1], round: 0));
@@ -734,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 fd2cae3923e..a113e74c8aa 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], 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], 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], lot)));
+ }
+
await proposalSent.WaitAsync();
Assert.NotNull(proposal?.BlockHash);
@@ -334,57 +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 block1 = blockChain.ProposeBlock(new PrivateKey());
- var block1Commit = TestUtils.CreateBlockCommit(block1);
- blockChain.Append(block1, block1Commit);
- var block2 = blockChain.ProposeBlock(new PrivateKey(), block1Commit);
- 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,
- TestUtils.ValidatorSet.GetProposer(2, 0).PublicKey);
-
- 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 57f30923824..1cb7539d48c 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,19 @@ 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 (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();
}
@@ -66,6 +73,17 @@ prevote.BlockHash is { } hash &&
};
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 Task.Delay(1000);
await proposalSent.WaitAsync();
Assert.NotNull(proposal);
Block proposedBlock = BlockMarshaler.UnmarshalBlock(
@@ -88,6 +106,17 @@ prevote.BlockHash is { } hash &&
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 new int[] { 1, 2, 3 })
+ {
+ context.ProduceMessage(
+ new ConsensusDominantLotMsg(
+ TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot)));
+ }
+
await stateChangedToRoundTwoPropose.WaitAsync();
Assert.Equal(2, context.Round);
@@ -128,16 +157,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 +185,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();
@@ -182,10 +222,21 @@ public async void EnterValidRoundPreVoteNil()
previousHash: blockChain.Tip.Hash,
txHash: null,
lastCommit: null,
+ proof: null,
evidenceHash: null)).Propose(),
key);
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 proposalSent.WaitAsync();
Assert.NotNull(proposal);
Block proposedBlock = BlockMarshaler.UnmarshalBlock(
@@ -208,6 +259,16 @@ 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 new int[] { 1, 2, 3 })
+ {
+ context.ProduceMessage(
+ new ConsensusDominantLotMsg(
+ TestUtils.CreateDominantLot(TestUtils.PrivateKeys[i], lot)));
+ }
+
await stateChangedToRoundTwoPropose.WaitAsync();
Assert.Equal(2, context.Round);
Assert.False(timeoutProcessed); // Assert no transition is due to timeout.
@@ -247,6 +308,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], 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 f21cc5615fd..3f58d2499c4 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], lot)));
+ }
+
await Task.WhenAll(proposalSent.WaitAsync(), stepChangedToPreVote.WaitAsync());
Assert.Equal(ConsensusStep.PreVote, context.Step);
@@ -89,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);
@@ -117,6 +129,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], lot)));
+ }
+
await Task.WhenAll(stepChangedToPreVote.WaitAsync(), proposalSent.WaitAsync());
Assert.Equal(ConsensusStep.PreVote, context.Step);
@@ -134,7 +157,7 @@ public async Task CannotStartTwice()
var (_, context) = TestUtils.CreateDummyContext();
context.StateChanged += (_, eventArgs) =>
{
- if (eventArgs.Step == ConsensusStep.Propose)
+ if (eventArgs.Step == ConsensusStep.Sortition)
{
stepChanged.Set();
}
@@ -159,7 +182,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);
@@ -198,6 +223,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], lot)));
+ }
await Task.WhenAll(stepChangedToPreVote.WaitAsync(), proposalSent.WaitAsync());
@@ -266,7 +301,20 @@ public async Task ThrowOnInvalidProposerMessage()
exceptionThrown = e;
exceptionOccurred.Set();
};
- var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]);
+ 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], lot)));
+ }
+
+ var block = blockChain.ProposeBlock(
+ TestUtils.PrivateKeys[1],
+ proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, TestUtils.PrivateKeys[1]));
context.Start();
context.ProduceMessage(
@@ -287,7 +335,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,9 +426,22 @@ 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();
+ 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], lot)));
+ }
+
context.ProduceMessage(
TestUtils.CreateConsensusPropose(block, TestUtils.PrivateKeys[1]));
@@ -476,7 +539,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();
@@ -506,10 +569,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)
@@ -525,6 +590,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], lot)));
+ }
+
+ await stepChanged.WaitAsync();
await stepChanged.WaitAsync();
Assert.Equal(ConsensusStep.Propose, context.Step);
@@ -666,9 +743,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], lot)));
+ }
+
context.ProduceMessage(
TestUtils.CreateConsensusPropose(block, TestUtils.PrivateKeys[1]));
@@ -701,20 +791,182 @@ 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);
}
+ [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);
+
+ 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]
+ 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.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/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.Net.Tests/Consensus/LotSetTest.cs b/test/Libplanet.Net.Tests/Consensus/LotSetTest.cs
new file mode 100644
index 00000000000..172474c12da
--- /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);
+ 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 = _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);
+ }
+ }
+}
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/SwarmTest.Broadcast.cs b/test/Libplanet.Net.Tests/SwarmTest.Broadcast.cs
index 77cb117613b..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));
}
@@ -754,13 +773,15 @@ public async Task BroadcastBlockWithSkip()
Block block1 = blockChain.ProposeBlock(
GenesisProposer,
new[] { transactions[0] }.ToImmutableList(),
- TestUtils.CreateBlockCommit(blockChain.Tip),
+ 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),
+ CreateZeroRoundProof(blockChain.Tip, GenesisProposer),
ImmutableArray.Empty);
blockChain.Append(block2, TestUtils.CreateBlockCommit(block2), true);
@@ -808,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]);
@@ -817,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]);
@@ -846,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);
@@ -854,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));
}
@@ -905,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));
}
@@ -914,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 5d1a8396813..1fdb8919f9a 100644
--- a/test/Libplanet.Net.Tests/SwarmTest.Preload.cs
+++ b/test/Libplanet.Net.Tests/SwarmTest.Preload.cs
@@ -46,7 +46,9 @@ public async Task InitialBlockDownload()
foreach (int i in Enumerable.Range(0, 10))
{
Block block = minerChain.ProposeBlock(
- minerKey, CreateBlockCommit(minerChain.Tip));
+ minerKey,
+ CreateBlockCommit(minerChain.Tip),
+ CreateZeroRoundProof(minerChain.Tip, minerKey));
minerChain.Append(block, TestUtils.CreateBlockCommit(block));
}
@@ -87,17 +89,23 @@ public async Task InitialBlockDownloadStates()
minerChain.MakeTransaction(key, new[] { action });
var block = minerChain.ProposeBlock(
- minerKey, CreateBlockCommit(minerChain.Tip));
+ minerKey,
+ CreateBlockCommit(minerChain.Tip),
+ 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));
+ minerKey,
+ CreateBlockCommit(minerChain.Tip),
+ 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));
+ minerKey,
+ CreateBlockCommit(minerChain.Tip),
+ CreateZeroRoundProof(minerChain.Tip, minerKey));
minerChain.Append(block, TestUtils.CreateBlockCommit(block));
try
@@ -137,7 +145,8 @@ 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: CreateZeroRoundProof(minerChain.Tip, ChainPrivateKey)),
ChainPrivateKey);
blocks.Add(block);
if (i != 11)
@@ -289,8 +298,11 @@ 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,
+ 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));
@@ -299,14 +311,20 @@ 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,
+ CreateBlockCommit(chainB.Tip),
+ CreateZeroRoundProof(chainB.Tip, proposerKey));
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,
+ CreateBlockCommit(chainB.Tip),
+ CreateZeroRoundProof(chainB.Tip, specialPK));
var invalidBlockCommit = new BlockCommit(
maliciousTipHeight,
0,
@@ -327,8 +345,11 @@ 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,
+ CreateBlockCommit(chainC.Tip),
+ CreateZeroRoundProof(chainC.Tip, privateKey));
chainC.Append(block, TestUtils.CreateBlockCommit(block));
}
@@ -415,7 +436,9 @@ 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),
+ CreateZeroRoundProof(sender.BlockChain.Tip, senderKey));
sender.BlockChain.Append(block, TestUtils.CreateBlockCommit(block));
}
@@ -456,9 +479,11 @@ 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),
+ CreateZeroRoundProof(minerChain.Tip, minerKey));
+ minerChain.Append(block, TestUtils.CreateBlockCommit(block));
}
try
@@ -484,8 +509,9 @@ public async Task PreloadWithFailedActions()
ChainPrivateKey,
new[] { tx }.ToImmutableList(),
CreateBlockCommit(minerChain.Tip),
+ CreateZeroRoundProof(minerChain.Tip, ChainPrivateKey),
ImmutableArray.Empty);
- minerSwarm.BlockChain.Append(block, CreateBlockCommit(block), true);
+ minerChain.Append(block, CreateBlockCommit(block), true);
await receiverSwarm.PreloadAsync();
@@ -535,7 +561,9 @@ public async Task PreloadFromNominer()
foreach (int i in Enumerable.Range(0, 10))
{
Block block = minerChain.ProposeBlock(
- minerKey, CreateBlockCommit(minerChain.Tip));
+ minerKey,
+ CreateBlockCommit(minerChain.Tip),
+ CreateZeroRoundProof(minerChain.Tip, minerKey));
minerChain.Append(block, CreateBlockCommit(block));
}
@@ -650,7 +678,9 @@ 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),
+ CreateZeroRoundProof(swarm0.BlockChain.Tip, key0));
swarm0.BlockChain.Append(block, TestUtils.CreateBlockCommit(block));
swarm1.BlockChain.Append(block, TestUtils.CreateBlockCommit(block));
}
@@ -839,11 +869,12 @@ 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),
+ CreateZeroRoundProof(minerChain.Tip, minerKey));
minerChain.Append(block, CreateBlockCommit(block));
receiverChain.Append(block, CreateBlockCommit(block));
}
@@ -852,7 +883,9 @@ public async Task PreloadAfterReorg()
foreach (int i in Enumerable.Range(0, 20))
{
Block block = receiverForked.ProposeBlock(
- minerKey, CreateBlockCommit(receiverForked.Tip));
+ minerKey,
+ CreateBlockCommit(receiverForked.Tip),
+ CreateZeroRoundProof(receiverForked.Tip, minerKey));
receiverForked.Append(block, CreateBlockCommit(block));
}
@@ -861,7 +894,9 @@ public async Task PreloadAfterReorg()
foreach (int i in Enumerable.Range(0, 1))
{
Block block = minerChain.ProposeBlock(
- minerKey, CreateBlockCommit(minerChain.Tip));
+ minerKey,
+ CreateBlockCommit(minerChain.Tip),
+ CreateZeroRoundProof(minerChain.Tip, minerKey));
minerChain.Append(block, CreateBlockCommit(block));
}
@@ -905,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));
}
@@ -988,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);
@@ -1059,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));
}
@@ -1113,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));
}
@@ -1122,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));
}
@@ -1183,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..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]);
}
}
@@ -514,9 +505,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 +580,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 +649,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 +836,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 +915,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 +995,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 +1010,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 +1072,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 +1130,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 +1314,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 +1467,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 +1659,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 +1701,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 +1744,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 +1820,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);
diff --git a/test/Libplanet.Net.Tests/TestUtils.cs b/test/Libplanet.Net.Tests/TestUtils.cs
index 3659ed28284..94e9bb7ddc6 100644
--- a/test/Libplanet.Net.Tests/TestUtils.cs
+++ b/test/Libplanet.Net.Tests/TestUtils.cs
@@ -63,6 +63,64 @@ 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!.PublicKey)),
+ lotSet.DominantLot!.Proof);
+ }
+
+ public static DominantLot CreateDominantLot(
+ PrivateKey privateKey,
+ Lot lot) =>
+ new DominantLotMetadata(
+ lot,
+ DateTimeOffset.Now,
+ privateKey.PublicKey).Sign(privateKey);
+
public static Vote CreateVote(
PrivateKey privateKey,
BigInteger power,
@@ -134,6 +192,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.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 6a465c66a5e..f732b508fd3 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;
@@ -95,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,
@@ -105,6 +106,7 @@ public void Idempotent()
previousHash: null,
txHash: BlockContent.DeriveTxHash(txs),
lastCommit: null,
+ proof: null,
evidenceHash: null),
transactions: txs,
evidence: evs).Propose();
@@ -112,32 +114,90 @@ 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 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.
+ 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(ContextRecordingAction.RandomRecordAddress));
+ actionEvaluations = actionEvaluator.Evaluate(stateRootBlockWithProof, null);
+ generatedRandomNumbersWithProof.Add(
+ (Integer)new WorldBaseState(
+ stateStore.GetStateRoot(actionEvaluations[0].OutputState), stateStore)
+ .GetAccountState(ReservedAddresses.LegacyAccount)
+ .GetState(ContextRecordingAction.RandomRecordAddress));
+ }
+
+ 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]);
}
}
@@ -164,11 +224,13 @@ 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(
- 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);
@@ -247,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();
@@ -305,10 +368,13 @@ 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));
+ chain.Tip, chain.Store.GetStateRootHash(chain.Tip.Hash));
Assert.False(evaluations[0].InputContext.IsPolicyAction);
Assert.Single(evaluations);
@@ -349,18 +415,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: 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(
@@ -369,7 +437,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]
@@ -463,21 +531,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)))
@@ -496,10 +588,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]));
@@ -584,21 +673,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)))
@@ -618,9 +726,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]));
@@ -662,6 +768,7 @@ public void EvaluateTx()
previousHash: default(BlockHash),
txHash: BlockContent.DeriveTxHash(txs),
lastCommit: null,
+ proof: new ConsensusInformation(1L, 0, null).Prove(GenesisProposer),
evidenceHash: null),
transactions: txs,
evidence: evs).Propose();
@@ -779,6 +886,7 @@ public void EvaluateTxResultThrowingException()
previousHash: hash,
txHash: BlockContent.DeriveTxHash(txs),
lastCommit: CreateBlockCommit(hash, 122, 0),
+ proof: new ConsensusInformation(123L, 0, null).Prove(GenesisProposer),
evidenceHash: null),
transactions: txs,
evidence: evs).Propose();
@@ -936,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);
@@ -987,6 +1096,7 @@ public void EvaluatePolicyEndBlockActions()
GenesisProposer,
txs.ToImmutableList(),
CreateBlockCommit(chain.Tip),
+ CreateZeroRoundProof(chain.Tip, GenesisProposer),
ImmutableArray.Empty);
IWorld previousState = _storeFx.StateStore.GetWorld(null);
@@ -1039,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);
@@ -1094,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);
@@ -1279,7 +1391,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));
@@ -1348,7 +1462,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,
@@ -1420,7 +1536,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));
@@ -1436,6 +1554,29 @@ public void EvaluateThrowingInsufficientBalanceForGasFee()
[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()
{
byte[] preEvaluationHashBytes =
{
@@ -1452,9 +1593,11 @@ 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);
}
@@ -1481,13 +1624,11 @@ public void CheckRandomSeedInAction()
stateStore: fx.StateStore,
isPolicyAction: false).ToArray();
- byte[] preEvaluationHashBytes = blockA.PreEvaluationHash.ToByteArray();
+ Assert.NotNull(blockA.Proof);
+ Proof proof = blockA.Proof;
int[] randomSeeds = Enumerable
.Range(0, txA.Actions.Count)
- .Select(offset => ActionEvaluator.GenerateRandomSeed(
- preEvaluationHashBytes,
- txA.Signature,
- offset))
+ .Select(offset => proof.Seed + offset)
.ToArray();
for (int i = 0; i < evalsA.Length; i++)
diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs
index 763255956d0..ad53644dd6a 100644
--- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs
+++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs
@@ -42,19 +42,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),
+ 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),
- evidence: ImmutableArray.Empty);
+ proof: TestUtils.CreateZeroRoundProof(block1, keys[4]),
+ ImmutableArray.Empty);
foreach (Transaction tx in txs)
{
Assert.Null(getTxExecution(genesis.Hash, tx.Id));
@@ -75,10 +77,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 +163,6 @@ Func getTxExecution
RenderRecord.ActionSuccess[] blockRenders = _renderer.ActionSuccessRecords
.Where(r => TestUtils.IsMinerReward(r.Action))
.ToArray();
-
Assert.Equal(
(Integer)2,
(Integer)_blockChain
@@ -174,7 +173,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 +199,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 +262,7 @@ Func getTxExecution
keys[4],
new[] { tx1Transfer, tx2Error, tx3Transfer }.ToImmutableList(),
TestUtils.CreateBlockCommit(_blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, keys[4]),
ImmutableArray.Empty);
_blockChain.Append(block3, TestUtils.CreateBlockCommit(block3));
var txExecution1 = getTxExecution(block3.Hash, tx1Transfer.Id);
@@ -346,6 +344,7 @@ public void AppendModern()
miner,
new[] { tx1 }.ToImmutableList(),
TestUtils.CreateBlockCommit(_blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner),
ImmutableArray.Empty);
var commit1 = TestUtils.CreateBlockCommit(block1);
_blockChain.Append(block1, commit1);
@@ -358,6 +357,7 @@ public void AppendModern()
miner,
new[] { tx2 }.ToImmutableList(),
commit1,
+ TestUtils.CreateZeroRoundProof(block1, miner),
ImmutableArray.Empty);
_blockChain.Append(block2, TestUtils.CreateBlockCommit(block2));
var world2 = _blockChain.GetNextWorldState();
@@ -396,6 +396,7 @@ public void AppendFailDueToInvalidBytesLength()
miner,
heavyTxs.ToImmutableList(),
TestUtils.CreateBlockCommit(_blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner),
ImmutableArray.Empty);
long maxBytes = _blockChain.Policy.GetMaxTransactionsBytes(block.Index);
Assert.True(block.MarshalBlock().EncodingLength > maxBytes);
@@ -426,6 +427,7 @@ public void AppendFailDueToInvalidTxCount()
miner,
manyTxs.ToImmutableList(),
TestUtils.CreateBlockCommit(_blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner),
ImmutableArray.Empty);
Assert.Equal(manyTxs.Count, block.Transactions.Count);
@@ -452,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);
@@ -502,6 +507,7 @@ TxPolicyViolationException IsSignerValid(
miner,
new[] { validTx }.ToImmutableList(),
TestUtils.CreateBlockCommit(blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner),
ImmutableArray.Empty);
blockChain.Append(block1, TestUtils.CreateBlockCommit(block1));
@@ -509,6 +515,7 @@ TxPolicyViolationException IsSignerValid(
miner,
new[] { invalidTx }.ToImmutableList(),
TestUtils.CreateBlockCommit(blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, miner),
ImmutableArray.Empty);
Assert.Throws(() => blockChain.Append(
block2, TestUtils.CreateBlockCommit(block2)));
@@ -526,7 +533,8 @@ public void UnstageAfterAppendComplete()
// Mining with empty staged.
Block block1 = _blockChain.ProposeBlock(
privateKey,
- TestUtils.CreateBlockCommit(_blockChain.Tip));
+ TestUtils.CreateBlockCommit(_blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey));
_blockChain.Append(block1, TestUtils.CreateBlockCommit(block1));
Assert.Empty(_blockChain.GetStagedTransactionIds());
@@ -538,6 +546,7 @@ public void UnstageAfterAppendComplete()
privateKey,
ImmutableList.Empty.Add(txs[0]),
TestUtils.CreateBlockCommit(_blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey),
ImmutableArray.Empty);
_blockChain.Append(block2, TestUtils.CreateBlockCommit(block2));
Assert.Equal(1, _blockChain.GetStagedTransactionIds().Count);
@@ -556,6 +565,7 @@ public void UnstageAfterAppendComplete()
privateKey,
ImmutableList.Empty.Add(txs[1]),
TestUtils.CreateBlockCommit(_blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey),
ImmutableArray.Empty);
_blockChain.Append(block3, TestUtils.CreateBlockCommit(block3));
Assert.Empty(_blockChain.GetStagedTransactionIds());
@@ -583,6 +593,7 @@ public void DoesNotUnstageOnAppendForForkedChain()
privateKey,
ImmutableList.Empty.Add(txs[0]),
TestUtils.CreateBlockCommit(_blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey),
ImmutableArray.Empty);
// Not actually unstaged, but lower nonce is filtered for workspace.
@@ -602,6 +613,7 @@ public void DoesNotUnstageOnAppendForForkedChain()
privateKey,
ImmutableList.Empty.Add(txs[1]),
TestUtils.CreateBlockCommit(_blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, privateKey),
ImmutableArray.Empty);
// Actually gets unstaged.
@@ -652,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),
@@ -734,6 +748,7 @@ public void CannotAppendBlockWithInvalidActions()
previousHash: _blockChain.Genesis.Hash,
txHash: BlockContent.DeriveTxHash(txs),
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null);
var preEval = new PreEvaluationBlock(
preEvaluationBlockHeader: new PreEvaluationBlockHeader(
@@ -788,6 +803,7 @@ public void DoesNotMigrateStateWithoutAction()
previousHash: null,
txHash: BlockContent.DeriveTxHash(txs),
lastCommit: null,
+ proof: null,
evidenceHash: null),
transactions: txs,
evidence: evs).Propose();
@@ -807,6 +823,7 @@ public void DoesNotMigrateStateWithoutAction()
fx.Proposer,
ImmutableList.Empty,
TestUtils.CreateBlockCommit(blockChain.Tip),
+ TestUtils.CreateZeroRoundProof(_blockChain.Tip, fx.Proposer),
ImmutableArray.Empty);
blockChain.Append(emptyBlock, TestUtils.CreateBlockCommit(emptyBlock));
Assert.True(blockChain.GetNextWorldState(emptyBlock.Hash).Legacy);
@@ -862,13 +879,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,
+ 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);
@@ -877,13 +893,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,
+ 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.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/Blockchain/BlockChainTest.Fork.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Fork.cs
new file mode 100644
index 00000000000..ec1a7b7e81e
--- /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.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,
+ 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
+ .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,
+ 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
+ .GetNextWorldState()
+ .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
+ .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));
+ }
+
+ [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
+ .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);
+ }
+ }
+
+ [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.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),
+ 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 d27b747728b..9c7ffbcb7da 100644
--- a/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs
+++ b/test/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs
@@ -113,6 +113,7 @@ public void ExecuteActions()
_fx.Proposer,
txs.ToImmutableList(),
CreateBlockCommit(_blockChain.Tip),
+ 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 94d50146efc..94fc1d920d2 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,6 +57,7 @@ public void ProposeBlock()
Block anotherBlock = _blockChain.ProposeBlock(
proposerB,
CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0),
+ CreateZeroRoundProof(_blockChain.Tip, proposerB),
_blockChain.GetPendingEvidence());
_blockChain.Append(anotherBlock, CreateBlockCommit(anotherBlock));
Assert.True(_blockChain.ContainsBlock(anotherBlock.Hash));
@@ -72,9 +75,11 @@ public void ProposeBlock()
.GetState(default)
);
+ var proposer = new PrivateKey();
Block block3 = _blockChain.ProposeBlock(
- new PrivateKey(),
+ proposer,
CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0),
+ CreateZeroRoundProof(_blockChain.Tip, proposer),
_blockChain.GetPendingEvidence());
Assert.False(_blockChain.ContainsBlock(block3.Hash));
Assert.Equal(3, _blockChain.Count);
@@ -111,9 +116,11 @@ public void ProposeBlock()
_blockChain.StageTransaction(heavyTx);
}
+ proposer = new PrivateKey();
Block block4 = _blockChain.ProposeBlock(
- new PrivateKey(),
+ proposer,
CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0),
+ CreateZeroRoundProof(_blockChain.Tip, proposer),
_blockChain.GetPendingEvidence());
Assert.False(_blockChain.ContainsBlock(block4.Hash));
_logger.Debug(
@@ -200,8 +207,13 @@ public void CanProposeInvalidBlock()
}.ToPlainValues()),
}.ToImmutableList();
+ var proposer = new PrivateKey();
var block = blockChain.ProposeBlock(
- new PrivateKey(), txs, null, ImmutableArray.Empty);
+ proposer,
+ txs,
+ null,
+ CreateZeroRoundProof(_blockChain.Tip, proposer),
+ ImmutableArray.Empty);
Assert.Throws(
() => blockChain.Append(block, CreateBlockCommit(block)));
}
@@ -318,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));
@@ -418,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();
@@ -475,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.
@@ -490,12 +509,11 @@ public void ProposeBlockWithLowerNonces()
),
}
);
+ proposer = new PrivateKey();
Block block2 = _blockChain.ProposeBlock(
- new PrivateKey(),
- CreateBlockCommit(
- _blockChain.Tip.Hash,
- _blockChain.Tip.Index,
- 0),
+ proposer,
+ CreateBlockCommit(_blockChain.Tip.Hash, _blockChain.Tip.Index, 0),
+ CreateZeroRoundProof(_blockChain.Tip, proposer),
_blockChain.GetPendingEvidence());
_blockChain.Append(block2, CreateBlockCommit(block2));
@@ -537,6 +555,7 @@ public void ProposeBlockWithBlockAction()
var block = blockChain.ProposeBlock(
privateKey1,
CreateBlockCommit(_blockChain.Tip),
+ CreateZeroRoundProof(_blockChain.Tip, privateKey1),
_blockChain.GetPendingEvidence());
blockChain.Append(block, CreateBlockCommit(block));
@@ -558,6 +577,7 @@ public void ProposeBlockWithBlockAction()
block = blockChain.ProposeBlock(
privateKey1,
CreateBlockCommit(_blockChain.Tip),
+ CreateZeroRoundProof(_blockChain.Tip, privateKey1),
_blockChain.GetPendingEvidence());
blockChain.Append(block, CreateBlockCommit(block));
@@ -628,9 +648,11 @@ 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,
+ CreateZeroRoundProof(_blockChain.Tip, proposer),
_blockChain.GetPendingEvidence());
Assert.NotNull(block.LastCommit);
@@ -647,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,
@@ -665,9 +690,11 @@ public void IgnoreLowerNonceTxsAndPropose()
StageTransactions(txsB);
// Propose only txs having higher or equal with nonce than expected nonce.
+ proposer = new PrivateKey();
Block b2 = _blockChain.ProposeBlock(
- new PrivateKey(),
+ proposer,
CreateBlockCommit(b1),
+ CreateZeroRoundProof(_blockChain.Tip, proposer),
_blockChain.GetPendingEvidence());
Assert.Single(b2.Transactions);
Assert.Contains(txsB[3], b2.Transactions);
@@ -684,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);
@@ -799,9 +828,11 @@ 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),
+ CreateZeroRoundProof(_blockChain.Tip, proposer),
_blockChain.GetPendingEvidence());
Assert.DoesNotContain(txWithInvalidNonce, block.Transactions);
diff --git a/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs b/test/Libplanet.Tests/Blockchain/BlockChainTest.Stage.cs
index 2900005a001..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));
@@ -102,8 +104,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 e59c99e95ea..475802d49b5 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: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
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: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
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: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer));
@@ -83,6 +86,7 @@ public void ValidateNextBlockProtocolVersion()
previousHash: block1.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
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: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
Assert.Throws(
@@ -121,8 +126,8 @@ public void ValidateNextBlockInvalidIndex()
previousHash: prev.Hash,
txHash: null,
lastCommit: TestUtils.CreateBlockCommit(prev.Hash, prev.Index + 1, 0),
- evidenceHash: null))
- .Propose(),
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
+ evidenceHash: null)).Propose(),
_fx.Proposer);
Assert.Throws(
() => _blockChain.Append(
@@ -148,6 +153,7 @@ public void ValidateNextBlockInvalidPreviousHash()
// ReSharper disable once PossibleInvalidOperationException
lastCommit: TestUtils.CreateBlockCommit(
_validNext.PreviousHash.Value, 1, 0),
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
Assert.Throws(() =>
@@ -170,6 +176,7 @@ public void ValidateNextBlockInvalidTimestamp()
previousHash: _validNext.Hash,
txHash: null,
lastCommit: TestUtils.CreateBlockCommit(_validNext),
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
Assert.Throws(() =>
@@ -231,6 +238,7 @@ public void ValidateNextBlockInvalidStateRootHash()
previousHash: genesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
TestUtils.GenesisProposer);
@@ -276,6 +284,7 @@ public void ValidateNextBlockInvalidStateRootHashBeforePostpone()
previousHash: genesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: null,
evidenceHash: null)).Propose(),
TestUtils.GenesisProposer);
@@ -341,6 +350,7 @@ public void ValidateNextBlockInvalidStateRootHashOnPostpone()
previousHash: genesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: null,
evidenceHash: null)).Propose();
Block block1 = chain.EvaluateAndSign(
preBlock1,
@@ -369,6 +379,7 @@ public void ValidateNextBlockLastCommitNullAtIndexOne()
previousHash: _fx.GenesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
_blockChain.Append(validNextBlock, TestUtils.CreateBlockCommit(validNextBlock));
@@ -387,6 +398,7 @@ public void ValidateNextBlockLastCommitUpperIndexOne()
previousHash: _fx.GenesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
_blockChain.Append(block1, TestUtils.CreateBlockCommit(block1));
@@ -401,6 +413,7 @@ public void ValidateNextBlockLastCommitUpperIndexOne()
previousHash: block1.Hash,
txHash: null,
lastCommit: blockCommit,
+ proof: TestUtils.CreateZeroRoundProof(block1, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
_blockChain.Append(block2, TestUtils.CreateBlockCommit(block2));
@@ -419,6 +432,7 @@ public void ValidateNextBlockLastCommitFailsUnexpectedValidator()
previousHash: _fx.GenesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
_blockChain.Append(block1, TestUtils.CreateBlockCommit(block1));
@@ -447,6 +461,7 @@ public void ValidateNextBlockLastCommitFailsUnexpectedValidator()
previousHash: block1.Hash,
txHash: null,
lastCommit: blockCommit,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
Assert.Throws(() =>
@@ -465,6 +480,7 @@ public void ValidateNextBlockLastCommitFailsDropExpectedValidator()
previousHash: _fx.GenesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
_blockChain.Append(block1, TestUtils.CreateBlockCommit(block1));
@@ -489,6 +505,7 @@ public void ValidateNextBlockLastCommitFailsDropExpectedValidator()
previousHash: block1.Hash,
txHash: null,
lastCommit: blockCommit,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
Assert.Throws(() =>
@@ -530,6 +547,7 @@ public void ValidateBlockCommitFailsDifferentBlockHash()
previousHash: _fx.GenesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
@@ -554,6 +572,7 @@ public void ValidateBlockCommitFailsDifferentHeight()
previousHash: _fx.GenesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
@@ -578,6 +597,7 @@ public void ValidateBlockCommitFailsDifferentValidatorSet()
previousHash: _fx.GenesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
@@ -612,6 +632,7 @@ public void ValidateBlockCommitFailsNullBlockCommit()
previousHash: _fx.GenesisBlock.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
@@ -647,6 +668,7 @@ public void ValidateBlockCommitFailsInsufficientPower()
previousHash: blockChain.Genesis.Hash,
txHash: null,
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
@@ -770,6 +792,8 @@ public void ValidateNextBlockAEVChangedOnChainRestart()
previousHash: newChain.Tip.Hash,
txHash: null,
lastCommit: 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 acccd29c3be..a8a128c276f 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: TestUtils.CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
evidenceHash: null)).Propose(),
_fx.Proposer);
}
@@ -111,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]);
}
@@ -122,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());
@@ -166,12 +177,16 @@ 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);
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 },
@@ -179,7 +194,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 },
@@ -254,7 +271,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()
@@ -285,8 +305,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
@@ -310,8 +333,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
@@ -353,7 +380,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);
@@ -380,7 +410,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);
@@ -426,7 +459,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)));
@@ -447,13 +483,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 }))
@@ -477,221 +519,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()
{
@@ -709,6 +536,7 @@ public void DetectInvalidTxNonce()
_fx.Proposer,
txsA.ToImmutableList(),
CreateBlockCommit(_blockChain.Tip),
+ proof: CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
ImmutableArray.Empty);
_blockChain.Append(b1, TestUtils.CreateBlockCommit(b1));
@@ -716,6 +544,7 @@ public void DetectInvalidTxNonce()
_fx.Proposer,
txsA.ToImmutableList(),
CreateBlockCommit(_blockChain.Tip),
+ proof: CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
ImmutableArray.Empty);
Assert.Throws(() =>
_blockChain.Append(b2, CreateBlockCommit(b2)));
@@ -731,64 +560,11 @@ public void DetectInvalidTxNonce()
_fx.Proposer,
txsB.ToImmutableList(),
CreateBlockCommit(_blockChain.Tip),
+ 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),
- 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),
- 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()
{
@@ -798,7 +574,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);
}
@@ -818,210 +595,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),
- 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),
- 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),
- 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()
{
@@ -1031,16 +604,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);
@@ -1050,27 +628,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()
{
@@ -1091,19 +648,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)]
@@ -1131,16 +675,22 @@ 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));
}
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(
@@ -1238,6 +788,7 @@ public void GetStateOnlyDrillsDownUntilRequestedAddressesAreFound()
_fx.Proposer,
txs.ToImmutableList(),
CreateBlockCommit(chain.Tip),
+ CreateZeroRoundProof(chain.Tip, _fx.Proposer),
ImmutableArray.Empty);
chain.Append(b, CreateBlockCommit(b));
}
@@ -1283,7 +834,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));
}
@@ -1303,65 +857,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()
{
@@ -1402,7 +897,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));
@@ -1424,7 +921,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",
@@ -1441,76 +940,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()
{
@@ -1530,6 +959,7 @@ public void GetNextTxNonce()
_fx.Proposer,
txsA.ToImmutableList(),
CreateBlockCommit(_blockChain.Tip),
+ CreateZeroRoundProof(_blockChain.Tip, _fx.Proposer),
ImmutableArray.Empty);
_blockChain.Append(b1, CreateBlockCommit(b1));
@@ -1597,7 +1027,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 =
@@ -1634,7 +1066,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 =
@@ -1769,13 +1202,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
@@ -1897,7 +1336,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);
@@ -2008,7 +1448,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);
@@ -2163,7 +1606,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,
@@ -2235,7 +1680,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)));
@@ -2301,7 +1747,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(
@@ -2323,8 +1772,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,
@@ -2350,8 +1802,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/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..0a19c85874b 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;
@@ -45,6 +46,7 @@ public void ProtocolVersion()
previousHash: Block1Metadata.PreviousHash,
txHash: Block1Metadata.TxHash,
lastCommit: null,
+ proof: null,
evidenceHash: null));
Assert.Throws(
() => new BlockMetadata(
@@ -56,6 +58,7 @@ public void ProtocolVersion()
previousHash: Block1Metadata.PreviousHash,
txHash: Block1Metadata.TxHash,
lastCommit: null,
+ proof: null,
evidenceHash: null));
}
@@ -69,6 +72,7 @@ public void Index()
previousHash: Block1Metadata.PreviousHash,
txHash: Block1Metadata.TxHash,
lastCommit: null,
+ proof: null,
evidenceHash: null));
}
@@ -86,6 +90,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 +109,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 +118,7 @@ public void PreviousHash()
previousHash: null,
txHash: Block1Metadata.TxHash,
lastCommit: null,
+ proof: null,
evidenceHash: null));
}
@@ -131,6 +138,12 @@ public void MakeCandidateData()
"transaction_fingerprint",
ParseHex("3d8e87977b1142863435b9385657e69557df8951a0698e9719f7d06c5fb8db1f")
)
+ .Add(
+ "proof",
+ ParseHex("0224313f215eb0c9d511f43a4aa55c5df1cd5dffbc29197f1dc7b936aa5a" +
+ "30813d88ad81348df92917b8fd2e88b513d3c4381e554c41f91f201fe07a7c6967718" +
+ "03b0740d363ab3e15a76823b092d2f245f288291bb01063fecd2298011449668b")
+ )
.Add(
"evidence_hash",
ParseHex("bd1b6bc740c7d74fe39f8c78dd6860b7b5bf9c58336a703a583a5a59651a4af3")
@@ -154,6 +167,13 @@ public void MakeCandidateData()
ParseHex("654698d34b6d9a55b0c93e4ffb2639278324868c91965bc5f96cb3071d6903a0")
)
.Add("protocol_version", BlockMetadata.CurrentProtocolVersion)
+ .Add(
+ "proof",
+ ParseHex("02b5de417ca459f8ab048625c55daf95fb47f7a9a41de80dcbbb0a454247" +
+ "e315ee845dda470812bf8b16741a8aacf7702c57d74e35c9e751089ee478d35a4fdcc" +
+ "7e150b5880ff57799bb7cd64fff33f99b663e98156f698d1590a472c520ffa9c8"
+ )
+ )
.Add(
"evidence_hash",
ParseHex("e7198889cc4a82a8b7be4b7f428b6201400ef222709f756e540b32bc1e8d5d86")
@@ -207,7 +227,7 @@ public void DerivePreEvaluationHash()
HashDigest hash = GenesisMetadata.DerivePreEvaluationHash();
AssertBytesEqual(
- FromHex("9ff328716814fed03de454dfc0a9d9aeb0077ad0c393513bf29895a45ded13aa"),
+ FromHex("b96b3a3bcb0afe52fb4b90a9bc00678dd18b4c4adf3a38099ebc1fd7b8fca4c8"),
hash.ByteArray);
}
@@ -241,6 +261,7 @@ public void ValidateLastCommit()
previousHash: blockHash,
txHash: null,
lastCommit: invalidHeightLastCommit,
+ proof: new ConsensusInformation(2L, 0, null).Prove(validatorA),
evidenceHash: null));
// BlockHash of the last commit is invalid.
@@ -263,6 +284,7 @@ public void ValidateLastCommit()
previousHash: GenesisHash,
txHash: null,
lastCommit: invalidBlockHashLastCommit,
+ proof: new ConsensusInformation(2L, 0, null).Prove(validatorA),
evidenceHash: null));
var validLastCommit = new BlockCommit(
@@ -292,6 +314,7 @@ public void ValidateLastCommit()
previousHash: blockHash,
txHash: null,
lastCommit: validLastCommit,
+ proof: new ConsensusInformation(2L, 0, null).Prove(validatorA),
evidenceHash: null);
}
diff --git a/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs b/test/Libplanet.Tests/Blocks/PreEvaluationBlockHeaderTest.cs
index e7cea982f7d..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(
- "3045022100f975e902971092f16dbbb1fe6b7c956de648a8cd62346dbadc07e5fca4ce3" +
- "07a02200987a349f0763efd0448659ed66c6bd0ad0971dd57fbb89c672aed592fbd70d6");
+ "3045022100bc8efa3cff74cea1da682c815d35c701d181bf13129734336d260fe14ed75" +
+ "2940220305f9c1f6ecfb7880acccb56b9886ace22c7b4cac1ad78bc2077ab53d5efd160");
AssertBytesEqual(
validSig,
block1.MakeSignature(_contents.Block1Key, arbitraryHash));
@@ -215,22 +228,22 @@ public void DeriveBlockHash()
_contents.GenesisMetadata,
_contents.GenesisMetadata.DerivePreEvaluationHash());
AssertBytesEqual(
- fromHex("d790aa3b2d985d58e6fe6547336ca9d2bfdb749a27cd58a17dbfd0c6880da8e3"),
+ fromHex("74b76fccf910cf6dbdf9e0ee63d096122763d025152691bbd3093f0cc0b61d7a"),
genesis.DeriveBlockHash(default, null)
);
AssertBytesEqual(
- fromHex("47b5227dfdd99af4faf9ae9e82ef3b615063179d275081eae4c122685bbf7dcb"),
+ fromHex("af99a7832c3e1fdd880b7c69e3db9a101f57c0ac2370e26b2f30e8bbb7f1d0d0"),
genesis.DeriveBlockHash(
default,
genesis.MakeSignature(_contents.GenesisKey, default)
)
);
AssertBytesEqual(
- fromHex("2c45bb52e4c7d79caa28da9b63ec0f492262836c975bfa5bb27f62e96f2aad99"),
+ fromHex("061f08d4f8e52e00647191c64d21c74d43b26256ca2b5ce14b80fc430d390c6a"),
genesis.DeriveBlockHash(arbitraryHash, null)
);
AssertBytesEqual(
- fromHex("e985fcdce3f73dee90a4eaec9399283f856bb6f9326e4300bbe1d6126ff7ad55"),
+ fromHex("b2fa69b10237a2f3ae1fe51050418b445a40ffc9e9ea1b7f474cfee134e84ce8"),
genesis.DeriveBlockHash(
arbitraryHash,
genesis.MakeSignature(_contents.GenesisKey, arbitraryHash))
@@ -240,19 +253,19 @@ public void DeriveBlockHash()
_contents.Block1Metadata,
_contents.Block1Metadata.DerivePreEvaluationHash());
AssertBytesEqual(
- fromHex("ade696a646c9e4321cc90160807cba3d15d7cd28556d2dfb4103e8730a46038c"),
+ fromHex("007908f9ca1335ec065c8302a6627e97da6fa627dc049f44b970d02b20898f02"),
block1.DeriveBlockHash(default, null)
);
AssertBytesEqual(
- fromHex("b3157a151d2168653e21ffc850f9d1a96bca6310275cccbeb9bd705f67c2e1c9"),
+ fromHex("61bdf03b8ff2666d813352259c6d454eed8268c1386ea749b3a557e03e33919a"),
block1.DeriveBlockHash(default, block1.MakeSignature(_contents.Block1Key, default))
);
AssertBytesEqual(
- fromHex("3fd4ee37ed2fc5dae5a9533984f06b3975e176bdaa70689a3c14acd8b4ea384d"),
+ fromHex("7dc612d7b0579135ed8db8b9a4861386be97c05fa343a3e4233f535c0a2ffb26"),
block1.DeriveBlockHash(arbitraryHash, null)
);
AssertBytesEqual(
- fromHex("83ceb4d1e5bbc385daaebfd044a5e4ba65bf1d8b63ef0aabe4d68fc5642b4516"),
+ 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 84b2388742b..345f0f54a5f 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: _contents.Block1Content.Proof,
evidenceHash: null),
transactions: txs,
evidence: evs);
@@ -161,6 +162,7 @@ public void DetermineStateRootHash()
previousHash: genesis.Hash,
txHash: BlockContent.DeriveTxHash(txs),
lastCommit: null,
+ proof: _contents.Block1Content.Proof,
evidenceHash: null),
transactions: txs,
evidence: evs);
diff --git a/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs b/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs
new file mode 100644
index 00000000000..ecd384daf8a
--- /dev/null
+++ b/test/Libplanet.Tests/Consensus/ConsensusInformationTest.cs
@@ -0,0 +1,112 @@
+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, -2, 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 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()
+ {
+ 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..910e0ddffaa
--- /dev/null
+++ b/test/Libplanet.Tests/Consensus/DominantLotMetadataTest.cs
@@ -0,0 +1,94 @@
+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(
+ _lot, DateTimeOffset.MinValue, _signer.PublicKey);
+ }
+
+ [Fact]
+ public void Constructor()
+ {
+ var dominantLotMetadata = new DominantLotMetadata(
+ _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(
+ 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(
+ _lot, DateTimeOffset.MinValue, _signer.PublicKey));
+ Assert.NotEqual(
+ _dominantLotMetadata,
+ new DominantLotMetadata(
+ new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()),
+ DateTimeOffset.MinValue,
+ _signer.PublicKey));
+ Assert.NotEqual(
+ _dominantLotMetadata,
+ new DominantLotMetadata(
+ _lot, DateTimeOffset.MaxValue, _signer.PublicKey));
+ Assert.NotEqual(
+ _dominantLotMetadata,
+ new DominantLotMetadata(
+ _lot, DateTimeOffset.MinValue, new PrivateKey().PublicKey));
+ }
+
+ [Fact]
+ public void HashCode()
+ {
+ Assert.Equal(
+ _dominantLotMetadata.GetHashCode(),
+ new DominantLotMetadata(
+ _lot, DateTimeOffset.MinValue, _signer.PublicKey).GetHashCode());
+ Assert.NotEqual(
+ _dominantLotMetadata.GetHashCode(),
+ new DominantLotMetadata(
+ new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()),
+ DateTimeOffset.MinValue,
+ _signer.PublicKey).GetHashCode());
+ Assert.NotEqual(
+ _dominantLotMetadata.GetHashCode(),
+ new DominantLotMetadata(
+ _lot, DateTimeOffset.MaxValue, _signer.PublicKey).GetHashCode());
+ Assert.NotEqual(
+ _dominantLotMetadata.GetHashCode(),
+ new DominantLotMetadata(
+ _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..a88e7295a32
--- /dev/null
+++ b/test/Libplanet.Tests/Consensus/DominantLotTest.cs
@@ -0,0 +1,112 @@
+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(
+ _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(
+ new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()),
+ DateTimeOffset.MinValue,
+ _signer.PublicKey)
+ .Sign(_signer));
+ Assert.NotEqual(
+ _dominantLot,
+ new DominantLotMetadata(
+ _lot, DateTimeOffset.MaxValue, _signer.PublicKey)
+ .Sign(_signer));
+ var stranger = new PrivateKey();
+ Assert.NotEqual(
+ _dominantLot,
+ new DominantLotMetadata(
+ _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(
+ new ConsensusInformation(0, 0, null).ToLot(new PrivateKey()),
+ DateTimeOffset.MinValue,
+ _signer.PublicKey)
+ .Sign(_signer).GetHashCode());
+ Assert.NotEqual(
+ _dominantLot.GetHashCode(),
+ new DominantLotMetadata(
+ _lot, DateTimeOffset.MaxValue, _signer.PublicKey)
+ .Sign(_signer).GetHashCode());
+ var stranger = new PrivateKey();
+ Assert.NotEqual(
+ _dominantLot.GetHashCode(),
+ new DominantLotMetadata(
+ _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());
+ }
+ }
+}
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/ProofTest.cs b/test/Libplanet.Tests/Crypto/ProofTest.cs
new file mode 100644
index 00000000000..d121e5979cc
--- /dev/null
+++ b/test/Libplanet.Tests/Crypto/ProofTest.cs
@@ -0,0 +1,151 @@
+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();
+ 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()
+ {
+ new Proof(_validProofBytes);
+
+ var randomPiBytes = new byte[97];
+ _rnd.NextBytes(randomPiBytes);
+ Assert.Throws(() => new Proof(randomPiBytes));
+ }
+
+ [Fact]
+ public void ConstructorDifferentEncoding()
+ {
+ 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 proof = new Proof(_validProofBytes);
+ Assert.Equal(proof, new Proof(proof.Bencoded));
+ }
+
+ [Fact]
+ public void ByteArray()
+ {
+ var proof = new Proof(_validProofBytes);
+ 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 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 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 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 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));
+ }
+ }
+ }
+}
diff --git a/test/Libplanet.Tests/Crypto/PublicKeyTest.cs b/test/Libplanet.Tests/Crypto/PublicKeyTest.cs
index 9e62c6fdff1..5bbb97c8a60 100644
--- a/test/Libplanet.Tests/Crypto/PublicKeyTest.cs
+++ b/test/Libplanet.Tests/Crypto/PublicKeyTest.cs
@@ -110,6 +110,61 @@ public void Verify()
Assert.False(pubKey.Verify(payload, default(ImmutableArray)));
}
+ [Fact]
+ public void VerifyProof()
+ {
+ 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 PrivateKey().Prove(payload)));
+ }
+
[Fact]
public void VerifyShouldNotCrashForAnyInputs()
{
diff --git a/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs b/test/Libplanet.Tests/Fixtures/BlockContentFixture.cs
index 41f24de8d05..3786fd97ef2 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: TestUtils.CreateZeroRoundProof(null, GenesisKey),
evidenceHash: BlockContent.DeriveEvidenceHash(genEvidence)),
transactions: genTxs,
evidence: genEvidence);
@@ -136,6 +137,7 @@ public BlockContentFixture()
previousHash: GenesisHash,
txHash: BlockContent.DeriveTxHash(block1Transactions),
lastCommit: null,
+ proof: TestUtils.CreateZeroRoundProof(GenesisMetadata, Block1Key),
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/Fixtures/IntegerSet.cs b/test/Libplanet.Tests/Fixtures/IntegerSet.cs
index 679eb971e35..482de8d1ac7 100644
--- a/test/Libplanet.Tests/Fixtures/IntegerSet.cs
+++ b/test/Libplanet.Tests/Fixtures/IntegerSet.cs
@@ -173,7 +173,13 @@ 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 Block ProposeEmptyProof() => Chain.ProposeBlock(
+ Miner,
+ TestUtils.CreateBlockCommit(Chain.Tip));
public void Append(Block block) =>
Chain.Append(block, TestUtils.CreateBlockCommit(block));
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/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 2d582511f26..03b9b10436c 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;
@@ -411,12 +412,18 @@ public static BlockCommit CreateBlockCommit(
height, round, blockHash, votes);
}
+ public static Proof CreateZeroRoundProof(
+ IBlockMetadata tip,
+ PrivateKey proposerKey)
+ => new ConsensusInformation((tip?.Index ?? -1) + 1, 0, tip?.Proof).Prove(proposerKey);
+
public static PreEvaluationBlock ProposeGenesis(
PublicKey proposer = null,
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();
@@ -448,6 +455,7 @@ public static PreEvaluationBlock ProposeGenesis(
previousHash: null,
txHash: BlockContent.DeriveTxHash(txs),
lastCommit: null,
+ proof: proof,
evidenceHash: null),
transactions: txs,
evidence: Array.Empty());
@@ -488,6 +496,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 +522,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 +539,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 +554,7 @@ public static Block ProposeNextBlock(
blockInterval,
protocolVersion,
lastCommit,
+ proof,
evidence);
return preEval.Sign(miner, stateRootHash);
}
@@ -641,7 +653,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(