-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
308 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
using System; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Security.Cryptography; | ||
using Bencodex; | ||
using Libplanet.Action; | ||
using Libplanet.Blocks; | ||
using Libplanet.Crypto; | ||
using Libplanet.Store; | ||
using Libplanet.Store.Trie; | ||
using Libplanet.Tx; | ||
using Xunit; | ||
|
||
namespace Libplanet.Node.Tests | ||
{ | ||
public class UntypedBlockTest | ||
{ | ||
private static readonly HashAlgorithmType Sha256 = HashAlgorithmType.Of<SHA256>(); | ||
private static readonly Codec Codec = new Codec(); | ||
private readonly PrivateKey _signerKey; | ||
private readonly Transaction<NullAction>[] _txs; | ||
private readonly PrivateKey _minerKey; | ||
private readonly BlockContent<NullAction> _content; | ||
private readonly PreEvaluationBlock<NullAction> _preEval; | ||
private readonly Block<NullAction> _block; | ||
|
||
public UntypedBlockTest() | ||
{ | ||
_signerKey = new PrivateKey(new byte[] | ||
{ | ||
0xfc, 0xf3, 0x0b, 0x33, 0x3d, 0x04, 0xcc, 0xfe, 0xb5, 0x62, 0xf0, | ||
0x00, 0xa3, 0x2d, 0xf4, 0x88, 0xe7, 0x15, 0x49, 0x49, 0xd3, 0x1d, | ||
0xdc, 0xac, 0x3c, 0xf9, 0x27, 0x8a, 0xcb, 0x57, 0x86, 0xc7, | ||
}); | ||
var txA = Transaction<NullAction>.Create( | ||
nonce: 0L, | ||
privateKey: _signerKey, | ||
genesisHash: null, | ||
actions: Enumerable.Empty<NullAction>(), | ||
timestamp: new DateTimeOffset(2022, 5, 24, 0, 0, 0, TimeSpan.Zero) | ||
); | ||
var txB = Transaction<NullAction>.Create( | ||
nonce: 1L, | ||
privateKey: _signerKey, | ||
genesisHash: null, | ||
actions: new[] | ||
{ | ||
new NullAction(), | ||
new NullAction(), | ||
}, | ||
timestamp: new DateTimeOffset(2022, 5, 24, 0, 0, 1, TimeSpan.Zero) | ||
); | ||
_txs = new[] { txA, txB }; | ||
_minerKey = new PrivateKey(new byte[] | ||
{ | ||
0x9b, 0xf4, 0x66, 0x4b, 0xa0, 0x9a, 0x89, 0xfa, 0xeb, 0x68, 0x4b, | ||
0x94, 0xe6, 0x9f, 0xfd, 0xe0, 0x1d, 0x26, 0xae, 0x14, 0xb5, 0x56, | ||
0x20, 0x4d, 0x3f, 0x6a, 0xb5, 0x8f, 0x61, 0xf7, 0x84, 0x18, | ||
}); | ||
_content = new BlockContent<NullAction> | ||
{ | ||
Index = 0L, | ||
Timestamp = new DateTimeOffset(2022, 5, 24, 1, 2, 3, 456, TimeSpan.Zero), | ||
PublicKey = _minerKey.PublicKey, | ||
Difficulty = 0L, | ||
PreviousHash = null, | ||
Transactions = _txs, | ||
}; | ||
var nonce = default(Nonce); | ||
byte[] blockBytes = Codec.Encode(_content.MakeCandidateData(nonce)); | ||
ImmutableArray<byte> preEvalHash = Sha256.Digest(blockBytes).ToImmutableArray(); | ||
var proof = (nonce, preEvalHash); | ||
_preEval = new PreEvaluationBlock<NullAction>(_content, Sha256, proof); | ||
_block = _preEval.Evaluate( | ||
_minerKey, | ||
null, | ||
new TrieStateStore(new MemoryKeyValueStore()) | ||
); | ||
} | ||
|
||
[Fact] | ||
public void Deserialize() | ||
{ | ||
Bencodex.Types.Dictionary dict = _block.MarshalBlock(); | ||
var untyped = new UntypedBlock(_ => Sha256, dict); | ||
Assert.Equal(_block.ProtocolVersion, untyped.ProtocolVersion); | ||
Assert.Equal(_block.HashAlgorithm, untyped.HashAlgorithm); | ||
Assert.Equal(_block.Index, untyped.Index); | ||
Assert.Equal(_block.Timestamp, untyped.Timestamp); | ||
Assert.Equal(_block.Nonce, untyped.Nonce); | ||
Assert.Equal(_block.Miner, untyped.Miner); | ||
Assert.Equal(_block.PublicKey, untyped.PublicKey); | ||
Assert.Equal(_block.Difficulty, untyped.Difficulty); | ||
Assert.Equal(_block.TotalDifficulty, untyped.TotalDifficulty); | ||
Assert.Equal(_block.PreviousHash, untyped.PreviousHash); | ||
Assert.Equal(_block.TxHash, untyped.TxHash); | ||
Assert.Equal(_block.Signature, untyped.Signature); | ||
Assert.Equal(_block.PreEvaluationHash, untyped.PreEvaluationHash); | ||
Assert.Equal(_block.StateRootHash, untyped.StateRootHash); | ||
Assert.Equal(_block.Hash, untyped.Hash); | ||
Assert.Equal(_block.Transactions.Count, untyped.UntypedTransactions.Count); | ||
Assert.All( | ||
_block.Transactions.Zip(untyped.UntypedTransactions, (t, u) => (t, u)), | ||
pair => pair.Item1.Id.Equals(pair.Item2.Id) | ||
); | ||
} | ||
|
||
[Fact] | ||
public void ToBencodex() | ||
{ | ||
var untypedTxs = _txs.Select(tx => | ||
new UntypedTransaction( | ||
tx, | ||
tx.Actions.Select(a => a.PlainValue), | ||
tx.Signature.ToImmutableArray())); | ||
var untyped = new UntypedBlock(_block, untypedTxs); | ||
Assert.Equal(_block.MarshalBlock(), untyped.ToBencodex()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Numerics; | ||
using System.Security.Cryptography; | ||
using Bencodex; | ||
using Bencodex.Types; | ||
using Libplanet.Blockchain.Policies; | ||
using Libplanet.Blocks; | ||
using Libplanet.Crypto; | ||
using Libplanet.Tx; | ||
|
||
namespace Libplanet.Node | ||
{ | ||
/// <summary> | ||
/// Untyped equivalent of <see cref="Block{T}"/>. It's guaranteed that all proofs are valid. | ||
/// </summary> | ||
public sealed class UntypedBlock : IBlockHeader | ||
{ | ||
private static readonly Codec Codec = new Codec(); | ||
private BlockHeader _header; | ||
|
||
/// <summary> | ||
/// Creates an <see cref="UntypedBlock"/> instance. | ||
/// </summary> | ||
/// <param name="header">A block header without transactions.</param> | ||
/// <param name="untypedTransactions">A list of transactions. Ordering does not matter. | ||
/// </param> | ||
/// <exception cref="InvalidTxNonceException">Thrown when the same tx nonce is used by | ||
/// a signer twice or more, or a tx nonce is used without its previous nonce by a signer. | ||
/// Note that this validates only a block's intrinsic integrity between its transactions, | ||
/// but does not guarantee integrity between blocks. Such validation needs to be conducted | ||
/// by <see cref="Blockchain.BlockChain{T}"/>.</exception> | ||
/// <exception cref="InvalidTxGenesisHashException">Thrown when transactions to set have | ||
/// inconsistent genesis hashes.</exception> | ||
/// <exception cref="InvalidBlockTxHashException">Thrown when the given | ||
/// <paramref name="header"/>'s <see cref="IBlockMetadata.TxHash"/> is invalid.</exception> | ||
public UntypedBlock( | ||
IBlockHeader header, | ||
IEnumerable<UntypedTransaction> untypedTransactions) | ||
{ | ||
_header = header is BlockHeader h | ||
? h | ||
: new BlockHeader( | ||
new PreEvaluationBlockHeader(header), | ||
header.StateRootHash, | ||
header.Signature, | ||
header.Hash); | ||
UntypedTransactions = untypedTransactions.OrderBy(tx => tx.Id).ToImmutableList(); | ||
UntypedTransactions.ValidateTxNonces(_header.Index); | ||
var expectedTxHash = DeriveTxHash(UntypedTransactions); | ||
if (!Nullable.Equals(expectedTxHash, _header.TxHash)) | ||
{ | ||
throw new InvalidBlockTxHashException( | ||
$"{nameof(TxHash)} is invalid.", | ||
_header.TxHash, | ||
expectedTxHash | ||
); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Decodes a Bencodex <paramref name="dictionary"/> into an <see cref="UntypedBlock"/> | ||
/// instance. | ||
/// </summary> | ||
/// <param name="hashAlgorithmGetter">A function to determine the hash algorithm used | ||
/// for the block to decode. See also <see cref="IBlockPolicy{T}.GetHashAlgorithm(long)"/> | ||
/// method.</param> | ||
/// <param name="dictionary">A Bencodex dictionary made using <see cref="ToBencodex()"/> | ||
/// method or <see cref="BlockMarshaler.MarshalBlock{T}(Block{T})"/> method.</param> | ||
/// <seealso cref="ToBencodex()"/> | ||
/// <seealso cref="BlockMarshaler.MarshalBlock{T}(Block{T})"/> | ||
public UntypedBlock( | ||
HashAlgorithmGetter hashAlgorithmGetter, | ||
Bencodex.Types.Dictionary dictionary | ||
) | ||
: this( | ||
BlockMarshaler.UnmarshalBlockHeader( | ||
hashAlgorithmGetter, | ||
dictionary.GetValue<Dictionary>(BlockMarshaler.HeaderKey)), | ||
dictionary.GetValue<List>(BlockMarshaler.TransactionsKey) | ||
.Select(b => new UntypedTransaction((Dictionary)Codec.Decode((Binary)b))) | ||
) | ||
{ | ||
} | ||
|
||
/// <inheritdoc cref="IBlockMetadata.ProtocolVersion"/> | ||
public int ProtocolVersion => _header.ProtocolVersion; | ||
|
||
/// <inheritdoc cref="IPreEvaluationBlockHeader.HashAlgorithm"/> | ||
public HashAlgorithmType HashAlgorithm => _header.HashAlgorithm; | ||
|
||
/// <inheritdoc cref="IBlockMetadata.Index"/> | ||
public long Index => _header.Index; | ||
|
||
/// <inheritdoc cref="IBlockMetadata.Timestamp"/> | ||
public DateTimeOffset Timestamp => _header.Timestamp; | ||
|
||
/// <inheritdoc cref="IPreEvaluationBlockHeader.Nonce"/> | ||
public Nonce Nonce => _header.Nonce; | ||
|
||
/// <inheritdoc cref="IBlockMetadata.Miner"/> | ||
public Address Miner => _header.Miner; | ||
|
||
/// <inheritdoc cref="IBlockMetadata.PublicKey"/> | ||
public PublicKey? PublicKey => _header.PublicKey; | ||
|
||
/// <inheritdoc cref="IBlockMetadata.Difficulty"/> | ||
public long Difficulty => _header.Difficulty; | ||
|
||
/// <inheritdoc cref="IBlockMetadata.TotalDifficulty"/> | ||
public BigInteger TotalDifficulty => _header.TotalDifficulty; | ||
|
||
/// <inheritdoc cref="IBlockMetadata.PreviousHash"/> | ||
public BlockHash? PreviousHash => _header.PreviousHash; | ||
|
||
/// <inheritdoc cref="IBlockMetadata.TxHash"/> | ||
public HashDigest<SHA256>? TxHash => _header.TxHash; | ||
|
||
/// <inheritdoc cref="IBlockHeader.Signature"/> | ||
public ImmutableArray<byte>? Signature => _header.Signature; | ||
|
||
/// <inheritdoc cref="IPreEvaluationBlockHeader.PreEvaluationHash"/> | ||
public ImmutableArray<byte> PreEvaluationHash => _header.PreEvaluationHash; | ||
|
||
/// <inheritdoc cref="IBlockHeader.StateRootHash"/> | ||
public HashDigest<SHA256> StateRootHash => _header.StateRootHash; | ||
|
||
/// <inheritdoc cref="IBlockExcerpt.Hash"/> | ||
public BlockHash Hash => _header.Hash; | ||
|
||
/// <summary> | ||
/// The list of untyped transactions belonging to the block. | ||
/// </summary> | ||
/// <remarks>This is always ordered by <see cref="UntypedTransaction.Id"/>.</remarks> | ||
public IReadOnlyList<UntypedTransaction> UntypedTransactions { get; } | ||
|
||
/// <summary> | ||
/// Encodes this block into a Bencodex dictionary. | ||
/// </summary> | ||
/// <returns>A Bencodex dictionary which encodes this block. This is equivalent to | ||
/// <see cref="BlockMarshaler.MarshalBlock{T}(Block{T})"/> method's return value. | ||
/// This can be decoded back to <see cref="UntypedBlock"/> using | ||
/// <see cref="UntypedBlock(HashAlgorithmGetter, Dictionary)"/> constructor or | ||
/// <see cref="BlockMarshaler.UnmarshalBlock{T}(HashAlgorithmGetter, Dictionary)"/> | ||
/// method.</returns> | ||
/// <seealso cref="UntypedBlock(HashAlgorithmGetter, Dictionary)"/> | ||
/// <seealso cref="BlockMarshaler.UnmarshalBlock{T}(HashAlgorithmGetter, Dictionary)"/> | ||
public Bencodex.Types.Dictionary ToBencodex() | ||
{ | ||
Bencodex.Types.Dictionary headerDict = _header.MarshalBlockHeader(); | ||
var txs = new List( | ||
UntypedTransactions | ||
.Select(tx => new Binary(Codec.Encode(tx.ToBencodex()))) | ||
.Cast<IValue>()); | ||
return BlockMarshaler.MarshalBlock(headerDict, txs); | ||
} | ||
|
||
/// <summary> | ||
/// Derives <see cref="TxHash"/> from the given <paramref name="transactions"/>. | ||
/// </summary> | ||
/// <param name="transactions">The transactions to derive <see cref="TxHash"/> from. | ||
/// This must be ordered by <see cref="TxId"/>.</param> | ||
/// <returns>The derived <see cref="TxHash"/>.</returns> | ||
/// <exception cref="ArgumentException">Thrown when the <paramref name="transactions"/> are | ||
/// not ordered by their <see cref="TxId"/>s.</exception> | ||
// FIXME: It does the same thing with BlockContent<T>.DeriveTxHash() method. | ||
private static HashDigest<SHA256>? DeriveTxHash( | ||
IReadOnlyList<UntypedTransaction> transactions) | ||
{ | ||
if (!transactions.Any()) | ||
{ | ||
return null; | ||
} | ||
|
||
var list = new Bencodex.Types.List( | ||
transactions.Select(tx => tx.ToBencodex())); | ||
byte[] payload = Codec.Encode(list); | ||
return HashDigest<SHA256>.DeriveFrom(payload); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters