Skip to content
Permalink
Browse files

IAccountStateDelta interface and its impl

  • Loading branch information...
dahlia committed Mar 5, 2019
1 parent dcee565 commit 8040c5c7a566b74b43e231fb13423b4827a13d60
@@ -12,6 +12,9 @@ To be released.
methods were also renamed too.
- Added `IAccountStateView` interface, and made `BlockChain<T>` implements it.
The interface purposes to provide a read-only view to account states.
- Added `IAccountStateDelta` interface.
The interface purposes to provide a read-write view to account states
with maintaining `UpdatedAddresses` (so-called "dirty").
- The type of `Peer.Urls` property was changed from `Uri` to `IPEndPoint`.
- Since we decided to depend on TURN ([RFC 5766]) and STUN ([RFC 5389]) to
work around NAT so that `Peer`'s endpoints don't have to be multiple,
@@ -0,0 +1,97 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using Libplanet.Action;
using Libplanet.Crypto;
using Uno.Extensions;
using Xunit;

namespace Libplanet.Tests.Action
{
public class AccountStateDeltaImplTest
{
private readonly IAccountStateView _view;
private readonly Address[] _addr;

public AccountStateDeltaImplTest()
{
Address Addr() => new PrivateKey().PublicKey.ToAddress();

_addr = new[]
{
Addr(),
Addr(),
Addr(),
};

_view = new DumbAccountStateView
{
[_addr[0]] = "a",
[_addr[1]] = "b",
};
}

[Fact]
public void CreateNullDelta()
{
IAccountStateDelta delta = new AccountStateDeltaImpl(_view);
Assert.Empty(delta.UpdatedAddresses);
Assert.Equal("a", delta.GetState(_addr[0]));
Assert.Equal("b", delta.GetState(_addr[1]));
Assert.Null(delta.GetState(_addr[2]));
}

[Fact]
public void GetSetState()
{
IAccountStateDelta init = new AccountStateDeltaImpl(_view);
IAccountStateDelta a = init.SetState(_addr[0], "A");
Assert.Equal("A", a.GetState(_addr[0]));
Assert.Equal("a", init.GetState(_addr[0]));
Assert.Equal("b", a.GetState(_addr[1]));
Assert.Equal("b", init.GetState(_addr[1]));
Assert.Null(a.GetState(_addr[2]));
Assert.Null(init.GetState(_addr[2]));
Assert.Equal(
new[] { _addr[0] }.ToImmutableHashSet(),
a.UpdatedAddresses
);
Assert.Empty(init.UpdatedAddresses);

IAccountStateDelta b = a.SetState(_addr[0], "z");
Assert.Equal("z", b.GetState(_addr[0]));
Assert.Equal("A", a.GetState(_addr[0]));
Assert.Equal("a", init.GetState(_addr[0]));
Assert.Equal("b", b.GetState(_addr[1]));
Assert.Equal("b", a.GetState(_addr[1]));
Assert.Null(b.GetState(_addr[2]));
Assert.Null(a.GetState(_addr[2]));
Assert.Equal(
new[] { _addr[0] }.ToImmutableHashSet(),
a.UpdatedAddresses
);
Assert.Empty(init.UpdatedAddresses);

IAccountStateDelta c = b.SetState(_addr[0], "a");
Assert.Equal("a", c.GetState(_addr[0]));
Assert.Equal("z", b.GetState(_addr[0]));
Assert.Empty(c.UpdatedAddresses);
Assert.Empty(init.UpdatedAddresses);
}

private class DumbAccountStateView
: Dictionary<Address, object>, IAccountStateView
{
public object GetAccountState(Address address)
{
try
{
return this[address];
}
catch (KeyNotFoundException)
{
return null;
}
}
}
}
}
@@ -6,8 +6,11 @@
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

<s:Boolean x:Key="/Default/UserDictionary/Words/=Bitcoin/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=changeset/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ciphertext/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ethereum/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=indeterministic/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PRNG/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Libplanet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=secp256k1/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>
@@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Collections.Immutable;

namespace Libplanet.Action
{
/// <summary>
/// An internal implementation of <see cref="IAccountStateDelta"/>.
/// </summary>
internal class AccountStateDeltaImpl : IAccountStateDelta
{
private readonly IAccountStateView _accountStateView;
private IImmutableDictionary<Address, object> _updatedStates;

/// <summary>
/// Creates a null delta from the given
/// <paramref name="accountStateView"/>.
/// </summary>
/// <param name="accountStateView">A view to the &#x201c;epoch&#x201d;
/// states.</param>
internal AccountStateDeltaImpl(IAccountStateView accountStateView)
{
_accountStateView = accountStateView;
_updatedStates = ImmutableDictionary<Address, object>.Empty;
}

/// <inheritdoc/>
IImmutableSet<Address> IAccountStateDelta.UpdatedAddresses =>
_updatedStates.Keys.ToImmutableHashSet();

/// <inheritdoc/>
object IAccountStateDelta.GetState(Address address)
{
try
{
return _updatedStates[address];
}
catch (KeyNotFoundException)
{
return _accountStateView.GetAccountState(address);
}
}

/// <inheritdoc/>
IAccountStateDelta IAccountStateDelta.SetState(
Address address,
object state
)
{
IImmutableDictionary<Address, object> newState =
_updatedStates.SetItem(address, state);
foreach (Address addr in newState.Keys)
{
object epochState = _accountStateView.GetAccountState(addr);
if (ReferenceEquals(epochState, state) ||
epochState.Equals(state))
{
newState = newState.Remove(addr);
}
}

return new AccountStateDeltaImpl(_accountStateView)
{
_updatedStates = newState,
};
}
}
}
@@ -0,0 +1,97 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Libplanet.Action
{
/// <summary>
/// An interface to manipulate account states with
/// maintaining the set of <see cref="UpdatedAddresses"/>.
/// <para>It is like a map which is virtually initialized such
/// that every possible <see cref="Address"/> exists and
/// is mapped to <c>null</c>. That means that:</para>
/// <list type="bullet">
/// <item>
/// <description>it does not have length,</description>
/// </item>
/// <item>
/// <description>its index getter never throws
/// <see cref="KeyNotFoundException"/>,
/// but returns <c>null</c> instead, and</description>
/// </item>
/// <item>
/// <description>filling an <see cref="Address"/> with
/// <c>null</c> state cannot be distinguished from
/// the <see cref="Address"/> having never been set to
/// any state.</description>
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// This interface is immutable. <see cref="SetState(Address, object)"/>
/// method does not manipulate the instance, but returns a new
/// <see cref="IAccountStateDelta"/> instance with updated states.
/// </remarks>
public interface IAccountStateDelta
{
/// <summary>
/// <seealso cref="Address"/>es of the accounts that have
/// been updated since then.
/// </summary>
[Pure]
IImmutableSet<Address> UpdatedAddresses { get; }

/// <summary>
/// Gets the account state of the given <paramref name="address"/>.
/// </summary>
/// <param name="address">The <see cref="Address"/> referring
/// the account to get its state.</param>
/// <returns>The account state of the given <paramref name="address"/>.
/// If it has never been set to any state it returns <c>null</c>
/// instead.</returns>
[Pure]
object GetState(Address address);

/// <summary>
/// Gets a new instance that the account state of the given
/// <paramref name="address"/> is set to the given
/// <paramref name="state"/>.
/// </summary>
/// <param name="address">The <see cref="Address"/> referring
/// the account to set its state.</param>
/// <param name="state">The new state to fill the account with.</param>
/// <returns>A new <see cref="IAccountStateDelta"/> instance that
/// the account state of the given <paramref name="address"/>
/// is set to the given <paramref name="state"/>.</returns>
/// <remarks>
/// This method method does not manipulate the instance,
/// but returns a new <see cref="IAccountStateDelta"/> instance
/// with updated states instead.
/// </remarks>
[Pure]
IAccountStateDelta SetState(Address address, object state);
}

internal static class AccountStateDeltaExtensions
{
// TODO: This method should be a part of IAccountStateDelta interface
// and the current implementation should be its default implementation
// (when C# 8 comes out). Although the current implementation is
// an only way using only generic methods/properties that
// the IAccountStateDelta interface exposes, it is quite inefficient
// when an implementing class maintains their own dirty dictionary.
// (See also AccountStateDeltaImpl.UpdatedStates field.)
public static IImmutableDictionary<Address, object> GetUpdatedStates(
this IAccountStateDelta delta
)
{
return delta.UpdatedAddresses.Select(address =>
new KeyValuePair<Address, object>(
address,
delta.GetState(address)
)
).ToImmutableDictionary();
}
}
}
@@ -8,11 +8,15 @@ namespace Libplanet.Action
public interface IAccountStateView
{
/// <summary>
/// Gets an account state of the given <see cref="Address"/>.
/// Gets an account state of the given <paramref name="address"/>.
/// <para>If the given <paramref name="address"/> has never been set
/// its account status, returns <c>null</c> instead of throwing
/// any exception.</para>
/// </summary>
/// <param name="address">An address of the account to read
/// its state.</param>
/// <returns>The account state.</returns>
/// <returns>The account state if exists. Otherwise <c>null</c>.
/// </returns>
object GetAccountState(Address address);
}
}

0 comments on commit 8040c5c

Please sign in to comment.
You can’t perform that action at this time.