Skip to content

Commit

Permalink
fix: Clean up BlockChain API, update Explorer query
Browse files Browse the repository at this point in the history
  • Loading branch information
OnedgeLee authored and limebell committed Oct 27, 2023
1 parent 31b526d commit 6fe88e3
Show file tree
Hide file tree
Showing 7 changed files with 467 additions and 342 deletions.
200 changes: 81 additions & 119 deletions Libplanet.Action/State/IBlockChainStates.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using Bencodex.Types;
using Libplanet.Common;
Expand All @@ -19,17 +18,13 @@ namespace Libplanet.Action.State
public interface IBlockChainStates
{
/// <summary>
/// Gets a state associated to specified <paramref name="address"/>.
/// Returns the <see cref="IWorldState"/> in the <see cref="BlockChain"/>
/// at <paramref name="offset"/>.
/// </summary>
/// <param name="address">The <see cref="Address"/> of the state to query.</param>
/// <param name="accountAddress">The <see cref="Address"/> of the account to fetch
/// the state from.</param>
/// <param name="offset">The <see cref="BlockHash"/> of the <see cref="Block"/> to fetch
/// the state from.</param>
/// <returns>The state associated to specified <paramref name="address"/>.
/// An absent state is represented as <see langword="null"/>. The returned value
/// must be the same as the single element when retrieved via
/// <see cref="GetStates"/>.
/// <param name="offset">The <see cref="BlockHash"/> of the <see cref="Block"/> to create
/// for which to create an <see cref="IWorldState"/>.</param>
/// <returns>
/// The <see cref="IWorldState"/> at <paramref name="offset"/>.
/// </returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="offset"/> is not
/// <see langword="null"/> and one of the following is true.
Expand All @@ -43,25 +38,74 @@ public interface IBlockChainStates
/// </description></item>
/// </list>
/// </exception>
/// <remarks>
/// For performance reasons, it is generally recommended to use <see cref="GetStates"/>
/// with a batch of <see cref="Address"/>es instead of iterating over this method.
/// </remarks>
IValue? GetState(Address address, Address accountAddress, BlockHash? offset);
/// <seealso cref="IWorldState"/>
IWorldState GetWorldState(BlockHash? offset);

/// <summary>
/// Returns the <see cref="IWorldState"/> in the <see cref="BlockChain"/>'s state storage
/// with <paramref name="stateRootHash"/>.
/// </summary>
/// <param name="stateRootHash">The state root hash for which to create
/// an <see cref="IWorldState"/>.</param>
/// <returns>
/// The <see cref="IWorldState"/> with <paramref name="stateRootHash"/>.
/// </returns>
/// <exception cref="ArgumentException">Thrown when no <see cref="ITrie"/> with
/// <paramref name="hash"/> as its state root hash is found.
/// </exception>
/// <seealso cref="IWorldState"/>
IWorldState GetWorldState(HashDigest<SHA256>? stateRootHash);

/// <summary>
/// Returns the <see cref="IAccountState"/> in the <see cref="BlockChain"/>
/// at <paramref name="offset"/>.
/// </summary>
/// <param name="address">The <see cref="Address"/> of <see cref="IAccountState"/>
/// to be returned.</param>
/// <param name="offset">The <see cref="BlockHash"/> of the <see cref="Block"/> to create
/// for which to create an <see cref="IAccountState"/>.</param>
/// <returns>
/// The <see cref="IAccountState"/> at <paramref name="offset"/>.
/// </returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="offset"/> is not
/// <see langword="null"/> and one of the following is true.
/// <list type="bullet">
/// <item><description>
/// Corresponding <see cref="Block"/> is not found in the <see cref="IStore"/>.
/// </description></item>
/// <item><description>
/// Corresponding <see cref="Block"/> is found but its state root is not found
/// in the <see cref="IStateStore"/>.
/// </description></item>
/// </list>
/// </exception>
/// <seealso cref="IAccountState"/>
IAccountState GetAccountState(Address address, BlockHash? offset);

/// <summary>
/// Returns the <see cref="IAccountState"/> in the <see cref="BlockChain"/>
/// of root hash <paramref name="stateRootHash"/>.
/// </summary>
/// <param name="stateRootHash">The <see cref="HashDigest{SHA256}"/> of the root hash
/// for which to create an <see cref="IAccountState"/>.</param>
/// <returns>
/// The <see cref="IAccountState"/> of state root hash <paramref name="stateRootHash"/>.
/// </returns>
/// <seealso cref="IAccountState"/>
IAccountState GetAccountState(HashDigest<SHA256>? stateRootHash);

/// <summary>
/// Gets multiple states associated to specified <paramref name="addresses"/>.
/// Gets a state associated to specified <paramref name="address"/>.
/// </summary>
/// <param name="addresses">The <see cref="Address"/>es of the states to query.</param>
/// <param name="address">The <see cref="Address"/> of the state to query.</param>
/// <param name="accountAddress">The <see cref="Address"/> of the account to fetch
/// the states from.</param>
/// the state from.</param>
/// <param name="offset">The <see cref="BlockHash"/> of the <see cref="Block"/> to fetch
/// the states from.</param>
/// <returns>The states associated to specified <paramref name="addresses"/>.
/// Associated values are ordered in the same way to the corresponding
/// <paramref name="addresses"/>. Absent states are represented as <see langword="null"/>.
/// Hence, the returned <see cref="IReadOnlyList{T}"/> is guaranteed to be of the same
/// length as <paramref name="addresses"/> with possible <see langword="null"/> values.
/// the state from.</param>
/// <returns>The state associated to specified <paramref name="address"/>.
/// An absent state is represented as <see langword="null"/>. The returned value
/// must be the same as the single element when retrieved via
/// <see cref="GetStates"/>.
/// </returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="offset"/> is not
/// <see langword="null"/> and one of the following is true.
Expand All @@ -75,27 +119,22 @@ public interface IBlockChainStates
/// </description></item>
/// </list>
/// </exception>
IReadOnlyList<IValue?> GetStates(
IReadOnlyList<Address> addresses,
Address accountAddress,
BlockHash? offset);

/// <remarks>
/// For performance reasons, it is generally recommended to use <see cref="GetStates"/>
/// with a batch of <see cref="Address"/>es instead of iterating over this method.
/// </remarks>
IValue? GetState(Address address, Address accountAddress, BlockHash? offset);

/// <summary>
/// Gets multiple states associated to specified <paramref name="addresses"/>.
/// Gets a state associated to specified <paramref name="address"/>.
/// </summary>
/// <param name="addresses">The <see cref="Address"/>es of the states to query.</param>
/// <param name="stateRootHash">The <see cref="HashDigest{SHA256}"/> of the state root hash
/// of the <see cref="ITrie"/> to fetch from.</param>
/// <returns>The states associated to specified <paramref name="addresses"/>.
/// Associated values are ordered in the same way to the corresponding
/// <paramref name="addresses"/>. Absent states are represented as <see langword="null"/>.
/// Hence, the returned <see cref="IReadOnlyList{T}"/> is guaranteed to be of the same
/// length as <paramref name="addresses"/> with possible <see langword="null"/> values.
/// <param name="address">The <see cref="Address"/> of the state to query.</param>
/// <param name="stateRootHash">The <see cref="HashDigest{SHA256}"/> of the root hash
/// for which to create an <see cref="IAccountState"/>.</param>
/// <returns>The state associated to specified <paramref name="address"/>.
/// An absent state is represented as <see langword="null"/>.
/// </returns>
IReadOnlyList<IValue?> GetStates(
IReadOnlyList<Address> addresses,
HashDigest<SHA256>? stateRootHash);
IValue? GetState(Address address, HashDigest<SHA256>? stateRootHash);

/// <summary>
/// Gets <paramref name="address"/>'s balance for given <paramref name="currency"/> in the
Expand Down Expand Up @@ -150,82 +189,5 @@ FungibleAssetValue GetTotalSupply(
/// <paramref name="offset"/> cannot be created.</exception>
/// <seealso cref="GetAccountState"/>
ValidatorSet GetValidatorSet(BlockHash? offset);

/// <summary>
/// Returns the <see cref="IWorldState"/> in the <see cref="BlockChain"/>
/// at <paramref name="offset"/>.
/// </summary>
/// <param name="offset">The <see cref="BlockHash"/> of the <see cref="Block"/> to create
/// for which to create an <see cref="IWorldState"/>.</param>
/// <returns>
/// The <see cref="IWorldState"/> at <paramref name="offset"/>.
/// </returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="offset"/> is not
/// <see langword="null"/> and one of the following is true.
/// <list type="bullet">
/// <item><description>
/// Corresponding <see cref="Block"/> is not found in the <see cref="IStore"/>.
/// </description></item>
/// <item><description>
/// Corresponding <see cref="Block"/> is found but its state root is not found
/// in the <see cref="IStateStore"/>.
/// </description></item>
/// </list>
/// </exception>
/// <seealso cref="IWorldState"/>
IWorldState GetWorldState(BlockHash? offset);

/// <summary>
/// Returns the <see cref="IWorldState"/> in the <see cref="BlockChain"/>'s state storage
/// with <paramref name="hash"/>.
/// </summary>
/// <param name="hash">The state root hash for which to create
/// an <see cref="IWorldState"/>.</param>
/// <returns>
/// The <see cref="IWorldState"/> with <paramref name="hash"/> as its state root hash.
/// </returns>
/// <exception cref="ArgumentException">Thrown when no <see cref="ITrie"/> with
/// <paramref name="hash"/> as its state root hash is found.
/// </exception>
/// <seealso cref="IAccountState"/>
IWorldState GetWorldState(HashDigest<SHA256>? hash);

/// <summary>
/// Returns the <see cref="IAccountState"/> in the <see cref="BlockChain"/>
/// at <paramref name="offset"/>.
/// </summary>
/// <param name="address">The <see cref="Address"/> of <see cref="IAccountState"/>
/// to be returned.</param>
/// <param name="offset">The <see cref="BlockHash"/> of the <see cref="Block"/> to create
/// for which to create an <see cref="IAccountState"/>.</param>
/// <returns>
/// The <see cref="IAccountState"/> at <paramref name="offset"/>.
/// </returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="offset"/> is not
/// <see langword="null"/> and one of the following is true.
/// <list type="bullet">
/// <item><description>
/// Corresponding <see cref="Block"/> is not found in the <see cref="IStore"/>.
/// </description></item>
/// <item><description>
/// Corresponding <see cref="Block"/> is found but its state root is not found
/// in the <see cref="IStateStore"/>.
/// </description></item>
/// </list>
/// </exception>
/// <seealso cref="IAccountState"/>
IAccountState GetAccountState(Address address, BlockHash? offset);

/// <summary>
/// Returns the <see cref="IAccountState"/> in the <see cref="BlockChain"/>
/// of root hash <paramref name="stateRootHash"/>.
/// </summary>
/// <param name="stateRootHash">The <see cref="HashDigest{SHA256}"/> of the root hash
/// for which to create an <see cref="IAccountState"/>.</param>
/// <returns>
/// The <see cref="IAccountState"/> of state root hash <paramref name="stateRootHash"/>.
/// </returns>
/// <seealso cref="IAccountState"/>
IAccountState GetAccountState(HashDigest<SHA256>? stateRootHash);
}
}
2 changes: 1 addition & 1 deletion Libplanet.Action/State/KeyConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Libplanet.Action.State
public static class KeyConverters
{
// "___"
internal static readonly KeyBytes ValidatorSetKey =
public static readonly KeyBytes ValidatorSetKey =
new KeyBytes(new byte[] { _underScore, _underScore, _underScore });

private const byte _underScore = 95; // '_'
Expand Down
46 changes: 21 additions & 25 deletions Libplanet.Explorer.Tests/Queries/StateQueryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ public async Task AccountStates()
}

[Fact]
public async Task States()
public async Task State()
{
(IBlockChainStates, IBlockPolicy) source = (
new MockChainStates(), new BlockPolicy()
);
ExecutionResult result = await ExecuteQueryAsync<StateQuery>(@"
{
states(
addresses: [""0x5003712B63baAB98094aD678EA2B24BcE445D076"", ""0x0000000000000000000000000000000000000000""],
state(
address: ""0x5003712B63baAB98094aD678EA2B24BcE445D076"",
accountAddress: ""0x40837BFebC1b192600023a431400557EA5FDE51a""
offsetBlockHash:
""01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b""
Expand All @@ -100,9 +100,9 @@ public async Task States()
ExecutionNode resultData = Assert.IsAssignableFrom<ExecutionNode>(result.Data);
IDictionary<string, object> resultDict =
Assert.IsAssignableFrom<IDictionary<string, object>>(resultData!.ToValue());
object[] states =
Assert.IsAssignableFrom<object[]>(resultDict["states"]);
Assert.Equal(new[] { new byte[] { 110, }, null }, states);
object state =
Assert.IsAssignableFrom<object>(resultDict["state"]);
Assert.Equal(new byte[] { 110, }, state);
}

[Fact]
Expand Down Expand Up @@ -252,16 +252,17 @@ public async Task ThrowExecutionErrorIfViolateMutualExclusive()
}

[Fact]
public async Task StatesBySrh()
public async Task StateBySrh()
{
var currency = Currency.Uncapped("ABC", 2, minters: null);
(IBlockChainStates, IBlockPolicy) source = (
new MockChainStates(), new BlockPolicy()
);
ExecutionResult result = await ExecuteQueryAsync<StateQuery>(@"
{
states(
addresses: [""0x5003712B63baAB98094aD678EA2B24BcE445D076"", ""0x0000000000000000000000000000000000000000""],
state(
address: ""0x5003712B63baAB98094aD678EA2B24BcE445D076"",
accountAddress: ""0x1000000000000000000000000000000000000000"",
offsetStateRootHash:
""c33b27773104f75ac9df5b0533854108bd498fab31e5236b6f1e1f6404d5ef64""
)
Expand All @@ -271,9 +272,9 @@ public async Task StatesBySrh()
ExecutionNode resultData = Assert.IsAssignableFrom<ExecutionNode>(result.Data);
IDictionary<string, object> resultDict =
Assert.IsAssignableFrom<IDictionary<string, object>>(resultData!.ToValue());
object[] states =
Assert.IsAssignableFrom<object[]>(resultDict["states"]);
Assert.Equal(new[] { new byte[] { 110, }, null }, states);
object state =
Assert.IsAssignableFrom<object>(resultDict["state"]);
Assert.Equal(new byte[] { 110, }, state);
}

[Fact]
Expand Down Expand Up @@ -407,10 +408,8 @@ private class MockChainStates : IBlockChainStates
{
public IReadOnlyList<IValue> GetStates(
IReadOnlyList<Address> addresses,
HashDigest<SHA256>? stateRootHash)
{
throw new System.NotImplementedException();
}
HashDigest<SHA256>? stateRootHash) =>
addresses.Select(addr => GetAccountState(stateRootHash).GetState(addr)).ToList().AsReadOnly();

public FungibleAssetValue GetBalance(
Address address, Currency currency, BlockHash? offset) =>
Expand All @@ -423,15 +422,12 @@ public ValidatorSet GetValidatorSet(BlockHash? offset) =>
new MockAccount().GetValidatorSet();


public IValue GetState(Address address, Address accountAddress, BlockHash? offset)
public IValue? GetState(Address address, Address accountAddress, BlockHash? offset)
=> new MockAccount().GetState(address);

public IWorldState GetWorldState(BlockHash? offset)
{
throw new System.NotImplementedException();
}
=> new MockWorld();

public IReadOnlyList<IValue> GetStates(IReadOnlyList<Address> addresses, Address accountAddress, BlockHash? offset)
=> new MockAccount().GetStates(addresses);

public IAccountState GetAccountState(Address address, BlockHash? blockHash)
=> new MockAccount();
Expand All @@ -446,14 +442,14 @@ public ITrie GetTrie(HashDigest<SHA256>? hash)
throw new System.NotImplementedException();
}

public IWorldState GetBlockWorldState(BlockHash? offset)
=> new MockWorld();

public IWorldState GetWorldState(HashDigest<SHA256>? hash)
=> new MockWorld();

public IAccountState GetAccountState(HashDigest<SHA256>? hash)
=> new MockAccount();

public IValue? GetState(Address address, HashDigest<SHA256>? hash)
=> new MockAccount().GetState(address);
}

private class MockWorld : IWorld
Expand Down
Loading

0 comments on commit 6fe88e3

Please sign in to comment.