Skip to content

Commit

Permalink
Initial compatibility patch attempt with configurable hash algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
greymistcube committed Jul 20, 2021
1 parent c36fdfc commit 09f54a9
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 71 deletions.
137 changes: 72 additions & 65 deletions Libplanet/Blocks/Block.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,17 @@ public Block(
// FIXME: This constructor needs to be separated into several overloads.
// Let (bool h, bool p, bool s) represent non-null arguments provided for
// hashAlgorithm, preEvaluationHash, and stateRootHash respectively.
// Currently accepted use cases are:
// Despite its summary, currently accepted only use cases are:
// - (true, false, false): Called when preEvaluationHash needs to be calculated.
// - (false, true, true): Called when stateRootHash needs to be attached.
// All other combinations are rejected.
if (!(hashAlgorithm is { } ha ^ preEvaluationHash is { } peh))
if (!(hashAlgorithm is { } ^ preEvaluationHash is { }))
{
throw new ArgumentException(
$"Exactly one of {nameof(hashAlgorithm)} " +
$"and {nameof(preEvaluationHash)} should be provided as non-null.");
}

if (preEvaluationHash is { } ^ stateRootHash is { } srh)
else if (preEvaluationHash is { } ^ stateRootHash is { })
{
throw new ArgumentException(
$"Either both {nameof(preEvaluationHash)} and {nameof(stateRootHash)} " +
Expand All @@ -121,19 +120,60 @@ public Block(
Transactions = transactions.OrderBy(tx => tx.Id).ToArray();
TxHash = CalculateTxHashes(Transactions);

if (preEvaluationHash is { })
#pragma warning disable SA1118
if (hashAlgorithm is { } ha)
{
_preEvaluationHash = peh;
StateRootHash = srh;
_hash = BlockHash.DeriveFrom(Header.SerializeForHash());
// FIXME: This only works due to sanity constraint on usage.
_header = new BlockHeader(
protocolVersion: ProtocolVersion,
index: Index,
timestamp: Timestamp.ToString(
BlockHeader.TimestampFormat,
CultureInfo.InvariantCulture),
nonce: Nonce.ToByteArray().ToImmutableArray(),
miner: Miner.ToByteArray().ToImmutableArray(),
difficulty: Difficulty,
totalDifficulty: TotalDifficulty,
previousHash: PreviousHash?.ToByteArray().ToImmutableArray()
?? ImmutableArray<byte>.Empty,
txHash: TxHash?.ToByteArray().ToImmutableArray()
?? ImmutableArray<byte>.Empty,
hashAlgorithm: ha);
_preEvaluationHash = Header.PreEvaluationHash;
StateRootHash = stateRootHash;
_hash = new BlockHash(Header.Hash);
}
else
{
// FIXME: This only works due to sanity constraint on usage.
ImmutableArray<byte> peh = preEvaluationHash
?? throw new NullReferenceException(
$"Parameter {nameof(preEvaluationHash)} cannot be null.");
HashDigest<SHA256> srh = stateRootHash
?? throw new NullReferenceException(
$"Parameter {nameof(stateRootHash)} cannot be null.");

_header = new BlockHeader(
protocolVersion: ProtocolVersion,
index: Index,
timestamp: Timestamp.ToString(
BlockHeader.TimestampFormat,
CultureInfo.InvariantCulture),
nonce: Nonce.ToByteArray().ToImmutableArray(),
miner: Miner.ToByteArray().ToImmutableArray(),
difficulty: Difficulty,
totalDifficulty: TotalDifficulty,
previousHash: PreviousHash?.ToByteArray().ToImmutableArray()
?? ImmutableArray<byte>.Empty,
txHash: TxHash?.ToByteArray().ToImmutableArray()
?? ImmutableArray<byte>.Empty,
preEvaluationHash: peh,
stateRootHash: srh.ToByteArray().ToImmutableArray());

_preEvaluationHash = Header.PreEvaluationHash;
StateRootHash = stateRootHash;
_hash = BlockHash.DeriveFrom(Header.SerializeForHash());
_hash = new BlockHash(Header.Hash);
}
#pragma warning restore SA1118

// As the order of transactions should be unpredictable until a block is mined,
// the sorter key should be derived from both a block hash and a txid.
Expand Down Expand Up @@ -181,12 +221,20 @@ public Block(Bencodex.Types.Dictionary dict)
{
}

// FIXME: Should this necessarily be a public constructor?
#pragma warning disable SA1514, SA1515
// FIXME: This probably should not be public.
// See also <https://github.com/planetarium/libplanet/issues/1146>.
/// <summary>
/// Creates a <see cref="Block{T}"/> instance by attaching <paramref name="stateRootHash"/>
/// to given <paramref name="block"/>.
/// </summary>
/// <param name="block">A <see cref="Block{T}"/> instance without
/// <see cref="Block{T}.StateRootHash"/> to use.</param>
/// <param name="stateRootHash">A <see cref="HashDigest{SHA256}"/> to attach.</param>
#pragma warning restore SA1514, SA1515
public Block(
Block<T> block,
HashDigest<SHA256>? stateRootHash
)
HashDigest<SHA256> stateRootHash)
: this(
block.Index,
block.Difficulty,
Expand All @@ -199,13 +247,14 @@ public Block(
hashAlgorithm: null,
preEvaluationHash: block.PreEvaluationHash,
stateRootHash: stateRootHash,
protocolVersion: block.ProtocolVersion
)
protocolVersion: block.ProtocolVersion)
{
}

private Block(RawBlock rawBlock)
{
_header = rawBlock.Header;

ProtocolVersion = rawBlock.Header.ProtocolVersion;
Index = rawBlock.Header.Index;
Difficulty = rawBlock.Header.Difficulty;
Expand All @@ -231,14 +280,16 @@ private Block(RawBlock rawBlock)

_preEvaluationHash = rawBlock.Header.PreEvaluationHash.Any()
? rawBlock.Header.PreEvaluationHash
: throw new ArgumentException(nameof(rawBlock.Header));
: throw new ArgumentException(
$"PreEvaluationHash of {nameof(rawBlock.Header)} cannot be empty.");

// FIXME: we should convert `StateRootHash`'s type to `HashDisgest<SHA256>` after
// removing `IBlockStateStore`.
// See also <https://github.com/planetarium/libplanet/pull/1116#discussion_r535836480>.
StateRootHash = rawBlock.Header.StateRootHash.Any()
? new HashDigest<SHA256>(rawBlock.Header.StateRootHash)
: (HashDigest<SHA256>?)null;
: throw new ArgumentException(
$"StateRootHash of {nameof(rawBlock.Header)} cannot be empty.");
_hash = new BlockHash(rawBlock.Header.Hash);
}

Expand All @@ -259,7 +310,7 @@ public BlockHash Hash
get
{
return _hash
?? throw new InvalidOperationException("Hash is not set.");
?? throw new InvalidOperationException("Hash has not been set.");
}
}

Expand All @@ -271,7 +322,7 @@ public BlockHash Hash
/// <seealso cref="Nonce"/>
/// <seealso cref="BlockHeader.Validate"/>
public ImmutableArray<byte> PreEvaluationHash => _preEvaluationHash
?? throw new InvalidOperationException("PreEvaluationHash is not set.");
?? throw new InvalidOperationException("PreEvaluationHash is has not been set.");

/// <summary>
/// The <see cref="ITrie.Hash"/> of the states on the block.
Expand Down Expand Up @@ -326,52 +377,8 @@ public int BytesLength
/// The <see cref="BlockHeader"/> of the block.
/// </summary>
[IgnoreDuringEquals]
public BlockHeader Header
{
// FIXME: Even though old implicit logic in the constructor is made slightly more
// explicit, this is still hard to understand and problematic. Should be
// refactored further.
// See also <https://github.com/planetarium/libplanet/issues/1164>.
get
{
#pragma warning disable SA1118
// FIXME: When hash is not assigned, should throw an exception.
return _header
?? (_preEvaluationHash is null
? (BlockHeader)(_header = new BlockHeader(
protocolVersion: ProtocolVersion,
index: Index,
timestamp: Timestamp.ToString(
BlockHeader.TimestampFormat,
CultureInfo.InvariantCulture),
nonce: Nonce.ToByteArray().ToImmutableArray(),
miner: Miner.ToByteArray().ToImmutableArray(),
difficulty: Difficulty,
totalDifficulty: TotalDifficulty,
previousHash: PreviousHash?.ToByteArray().ToImmutableArray()
?? ImmutableArray<byte>.Empty,
txHash: TxHash?.ToByteArray().ToImmutableArray()
?? ImmutableArray<byte>.Empty))
: (BlockHeader)(_header = new BlockHeader(
protocolVersion: ProtocolVersion,
index: Index,
timestamp: Timestamp.ToString(
BlockHeader.TimestampFormat,
CultureInfo.InvariantCulture),
nonce: Nonce.ToByteArray().ToImmutableArray(),
miner: Miner.ToByteArray().ToImmutableArray(),
difficulty: Difficulty,
totalDifficulty: TotalDifficulty,
previousHash: PreviousHash?.ToByteArray().ToImmutableArray()
?? ImmutableArray<byte>.Empty,
txHash: TxHash?.ToByteArray().ToImmutableArray()
?? ImmutableArray<byte>.Empty,
preEvaluationHash: PreEvaluationHash,
stateRootHash: StateRootHash?.ToByteArray().ToImmutableArray()
?? ImmutableArray<byte>.Empty)));
#pragma warning restore SA1118
}
}
public BlockHeader Header => _header
?? throw new InvalidOperationException("Header has not been set.");

public static bool operator ==(Block<T> left, Block<T> right) =>
Operator.Weave(left, right);
Expand Down
74 changes: 68 additions & 6 deletions Libplanet/Blocks/BlockHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ namespace Libplanet.Blocks
/// Used for checking <paramref name="nonce"/>. See also <see cref="Validate"/>.</param>
/// <param name="stateRootHash">The <see cref="ITrie.Hash"/> of the states on the block.
/// </param>
/// <remarks>
/// This is only exposed for testing. Should not be used as an entry point to create
/// a <see cref="BlockHeader"/> instance under normal circumstances.
/// </remarks>
public BlockHeader(
int protocolVersion,
long index,
Expand Down Expand Up @@ -104,6 +108,12 @@ public BlockHeader(
StateRootHash = stateRootHash;
}

/// <summary>
/// Creates a <see cref="BlockHeader"/> instance from its serialization.
/// </summary>
/// <param name="dict">The <see cref="Bencodex.Types.Dictionary"/>
/// representation of <see cref="BlockHeader"/> instance.
/// </param>
public BlockHeader(Bencodex.Types.Dictionary dict)
{
ProtocolVersion = dict.ContainsKey(ProtocolVersionKey)
Expand Down Expand Up @@ -137,6 +147,29 @@ public BlockHeader(Bencodex.Types.Dictionary dict)
: ImmutableArray<byte>.Empty;
}

/// <summary>
/// Creates a <see cref="BlockHeader"/> instance for a <see cref="Block{T}"/> instance with
/// missing <see cref="Block{T}.StateRootHash"/>.
/// </summary>
/// <param name="protocolVersion">The protocol version. Goes to
/// <see cref="ProtocolVersion"/>.</param>
/// <param name="index">The height of the block. Goes to <see cref="Index"/>.
/// </param>
/// <param name="timestamp">The time the block is created.
/// Goes to <see cref="Timestamp"/>.</param>
/// <param name="nonce">The nonce which satisfies given <paramref name="difficulty"/>.
/// Goes to <see cref="Nonce"/>.</param>
/// <param name="miner">The miner of the block. Goes to <see cref="Miner"/>.</param>
/// <param name="difficulty">The mining difficulty that <paramref name="nonce"/>
/// has to satisfy. Goes to <see cref="Difficulty"/>.</param>
/// <param name="totalDifficulty">The total mining difficulty since the genesis
/// including the block's difficulty. See also <see cref="Difficulty"/>.</param>
/// <param name="previousHash">The previous block's <see cref="Hash"/>. If it's a genesis
/// block (i.e., its <see cref="Block{T}.Index"/> is 0) this should be <c>null</c>.
/// Goes to <see cref="PreviousHash"/>.</param>
/// <param name="txHash">The result of hashing the transactions the block has.
/// Goes to <see cref="TxHash"/>.</param>
/// <param name="hashAlgorithm">The proof-of-work hash algorithm.</param>
internal BlockHeader(
int protocolVersion,
long index,
Expand All @@ -146,7 +179,8 @@ internal BlockHeader(
long difficulty,
BigInteger totalDifficulty,
ImmutableArray<byte> previousHash,
ImmutableArray<byte> txHash)
ImmutableArray<byte> txHash,
HashAlgorithmType hashAlgorithm)
{
ProtocolVersion = protocolVersion;
Index = index;
Expand All @@ -157,11 +191,39 @@ internal BlockHeader(
TotalDifficulty = totalDifficulty;
PreviousHash = previousHash;
TxHash = txHash;
PreEvaluationHash = BlockHash.DeriveFrom(SerializeForPreEvaluationHash()).ByteArray;

PreEvaluationHash =
hashAlgorithm.Digest(SerializeForPreEvaluationHash()).ToImmutableArray();
StateRootHash = ImmutableArray<byte>.Empty;
Hash = PreEvaluationHash;
Hash = hashAlgorithm.Digest(SerializeForHash()).ToImmutableArray();
}

/// <summary>
/// Creates a <see cref="BlockHeader"/> instance for a <see cref="Block{T}"/>.
/// </summary>
/// <param name="protocolVersion">The protocol version. Goes to
/// <see cref="ProtocolVersion"/>.</param>
/// <param name="index">The height of the block. Goes to <see cref="Index"/>.
/// </param>
/// <param name="timestamp">The time the block is created.
/// Goes to <see cref="Timestamp"/>.</param>
/// <param name="nonce">The nonce which satisfies given <paramref name="difficulty"/>.
/// Goes to <see cref="Nonce"/>.</param>
/// <param name="miner">The miner of the block. Goes to <see cref="Miner"/>.</param>
/// <param name="difficulty">The mining difficulty that <paramref name="nonce"/>
/// has to satisfy. Goes to <see cref="Difficulty"/>.</param>
/// <param name="totalDifficulty">The total mining difficulty since the genesis
/// including the block's difficulty. See also <see cref="Difficulty"/>.</param>
/// <param name="previousHash">The previous block's <see cref="Hash"/>. If it's a genesis
/// block (i.e., its <see cref="Block{T}.Index"/> is 0) this should be <c>null</c>.
/// Goes to <see cref="PreviousHash"/>.</param>
/// <param name="txHash">The result of hashing the transactions the block has.
/// Goes to <see cref="TxHash"/>.</param>
/// <param name="preEvaluationHash">The hash derived from the block <em>excluding</em>
/// <paramref name="stateRootHash"/> (i.e., without action evaluation).
/// Used for checking <paramref name="nonce"/>. See also <see cref="Validate"/>.</param>
/// <param name="stateRootHash">The <see cref="ITrie.Hash"/> of the states on the block.
/// </param>
internal BlockHeader(
int protocolVersion,
long index,
Expand All @@ -175,9 +237,9 @@ internal BlockHeader(
ImmutableArray<byte> preEvaluationHash,
ImmutableArray<byte> stateRootHash)
{
// FIXME: Basic sanity check to prevent improper usage should be present.
// For the same reason as Block<T>() constructor comment, should be added in
// on furter refactoring.
// FIXME: Basic sanity check, such as whether stateRootHash is empty or not,
// to prevent improper usage should be present. For the same reason as
// a comment in Block<T>(), should be added in on furter refactoring.
ProtocolVersion = protocolVersion;
Index = index;
Timestamp = timestamp;
Expand Down

0 comments on commit 09f54a9

Please sign in to comment.