Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust how IStore deals with state references and tx nonce #307

Merged
merged 7 commits into from Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
@@ -1,3 +1,3 @@
{
"editor.rulers": [80]
"editor.rulers": [80, 100]
longfin marked this conversation as resolved.
Show resolved Hide resolved
}
15 changes: 15 additions & 0 deletions CHANGES.md
Expand Up @@ -22,6 +22,19 @@ To be released.
broadcast. [[#274], [#297]]
- `Swarm.StartAsync()` method became to receive `broadcastTxInterval`
(or `millisecondsBroadcastTxInterval`) parameter. [[#274], [#297]]
- `IStore` became to treat a "tx nonce" mere a `long` integer instead of
a stack of block hashes. [[#272], [#307]]
- `IStore.IncreaseTxNonce<T>(string, Block<T>)` method was replaced by
`IStore.IncreaseTxNonce(string, Address, long)` method.
- Removed `IStore.ForkTxNonce()` method.
- `FileStore` became to occupy fewer bytes for storing tx nonces.
This change broke file-level backward compatibility.
- `IStore` became possible to look up multiple state references in a stack.
[[#272], [#307]]
- Removed `IStore.LookupStateReference<T>()` method.
Instead, a newly added static class `StoreExtension` provides
an extension method of the same name.
- Added `IStore.IterateStateReferences()` method.

### Added interfaces

Expand All @@ -37,6 +50,7 @@ To be released.
IImmutableSet<Address>, DateTimeOffset?)` method. [[#294]]
- Added `BlockChain<T>.GetNextTxNonce()` method which counts staged
transactions too during nonce computation. [[#270], [#294]]
- Added `StoreExtension` static class. [[#272], [#307]]

### Behavioral changes

Expand Down Expand Up @@ -93,6 +107,7 @@ To be released.
[#294]: https://github.com/planetarium/libplanet/pull/294
[#297]: https://github.com/planetarium/libplanet/pull/297
[#303]: https://github.com/planetarium/libplanet/issues/303
[#307]: https://github.com/planetarium/libplanet/pull/307
[#308]: https://github.com/planetarium/libplanet/pull/308


Expand Down
46 changes: 44 additions & 2 deletions CONTRIBUTING.md
Expand Up @@ -71,7 +71,7 @@ Tests [![Build Status](https://dev.azure.com/planetarium/libplanet/_apis/build/s
-----

We write as complete tests as possible to the corresponding implementation code.
Going near to the [code coverage][2] 100% is one of our goals.
Going near to the [code coverage][3] 100% is one of our goals.

The *Libplanet* solution consists of 4 projects. *Libplanet* and
*Libplanet.Stun* are actual implementations. These are built to *Libplanet.dll*
Expand All @@ -89,7 +89,7 @@ To build and run unit tests at a time execute the below command:
dotnet test

[Azure Pipelines]: https://dev.azure.com/planetarium/libplanet/_build/latest?definitionId=1&branchName=master
[2]: https://codecov.io/gh/planetarium/libplanet
[3]: https://codecov.io/gh/planetarium/libplanet
[Xunit]: https://xunit.github.io/


Expand All @@ -107,6 +107,48 @@ or cloud offers like [Xirsys].
[Xirsys]: https://xirsys.com/


### [xunit-unity-runner]

Unity is one of our platforms we primarily target to support, so we've been
testing Libplanet on the actual Unity runtime, and you could see whether it's
passed on [Azure Pipelines].

However, if it fails and it does not fail on other platforms but only Unity,
you need to run Unity tests on your own machine so that you rapidily and
repeatedly tinker things, make a try, and immediately get feedback from them.

Here's how to run Unity tests on your machine. We've made and used
[xunit-unity-runner] to run [Xunit] tests on the actual Unity runtime,
and our build jobs on Azure Pipelines also use this. This test runner
is actually a Unity app, though it's not a game app. As of June 2019,
there are [executable binaries][4] for Linux, macOS, and Windows.
Its usage is simple. It's a CLI app that takes *absolute* paths to
.NET assemblies (*.dll*) that consist of test classes (based on Xunit).

You can build these assemblies using `msbuild -r` Mono or .NET Framework
provide.
*You can't build them using `dotnet build` command or `dotnet msbuild`,*
because the Unity runtime is not based on .NET Core but Mono,
which is compatible with .NET Framework 4.6.
Please be sure that Mono's *bin* directory is prior to .NET Core's one
(or it's okay too if .NET Core is not installed at all). Mono or .NET
Framework's `msbuild` could try to use .NET Core's version of several
utilities during build, and this could cause some errors.

The way to execute the runner binary depend on the platform. For details,
please read [xunit-unity-runner]'s README. FYI you can use `-h`/`--help`
option as well.

To sum up, the instruction is like below (the example is assuming Linux):

msbuild -r
xunit-unity-runner/StandaloneLinux64 \
"`pwd`"/*.Tests/bin/Debug/net461/*.Tests.dll

[xunit-unity-runner]: https://github.com/planetarium/xunit-unity-runner
[4]: https://github.com/planetarium/xunit-unity-runner/releases/latest


Style convention
----------------

Expand Down
17 changes: 13 additions & 4 deletions Libplanet.Tests/Blockchain/BlockChainTest.cs
Expand Up @@ -1026,11 +1026,21 @@ public void MakeTransaction()
(x, y) => x.SetItems(y.GetUpdatedStates())
);

void BuildIndex(Guid id, Block<DumbAction> block)
{
string idString = id.ToString();
foreach (Transaction<DumbAction> tx in block.Transactions)
{
store.IncreaseTxNonce(idString, tx.Signer);
}

store.AppendIndex(idString, block.Hash);
}

// Build the store has incomplete states
Block<DumbAction> b = TestUtils.MineGenesis<DumbAction>();
chain.Blocks[b.Hash] = b;
store.IncreaseTxNonce(chainId.ToString(), b);
store.AppendIndex(chainId.ToString(), b.Hash);
BuildIndex(chainId, b);
IImmutableDictionary<Address, object> dirty =
GetDirty(b.Evaluate(DateTimeOffset.UtcNow, _ => null));
const int accountsCount = 5;
Expand Down Expand Up @@ -1061,8 +1071,7 @@ public void MakeTransaction()
dirty.Keys.ToImmutableHashSet(),
b
);
store.IncreaseTxNonce(chainId.ToString(), b);
store.AppendIndex(chainId.ToString(), b.Hash);
BuildIndex(chainId, b);
}
}

Expand Down
77 changes: 77 additions & 0 deletions Libplanet.Tests/Store/StoreExtensionTest.cs
@@ -0,0 +1,77 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using Libplanet.Blocks;
using Libplanet.Tests.Common.Action;
using Libplanet.Tx;
using Xunit;

using static Libplanet.Store.StoreExtension;

namespace Libplanet.Tests.Store
{
public class StoreExtensionTest
{
public static IEnumerable<object[]> StoreFixtures
{
get
{
yield return new object[] { new FileStoreFixture() };
yield return new object[] { new LiteDBStoreFixture() };
}
}

[Theory]
[MemberData(nameof(StoreFixtures))]
public void LookupStateReference(StoreFixture fx)
{
Address address = fx.Address1;

Transaction<DumbAction> tx4 = fx.MakeTransaction(
new DumbAction[] { new DumbAction(address, "foo") }
);
Block<DumbAction> block4 = TestUtils.MineNext(fx.Block3, new[] { tx4 });

Transaction<DumbAction> tx5 = fx.MakeTransaction(
new DumbAction[] { new DumbAction(address, "bar") }
);
Block<DumbAction> block5 = TestUtils.MineNext(block4, new[] { tx5 });

Block<DumbAction> block6 = TestUtils.MineNext(block5, new Transaction<DumbAction>[0]);

Assert.Null(fx.Store.LookupStateReference(fx.StoreNamespace, address, fx.Block3));
Assert.Null(fx.Store.LookupStateReference(fx.StoreNamespace, address, block4));
Assert.Null(fx.Store.LookupStateReference(fx.StoreNamespace, address, block5));
Assert.Null(fx.Store.LookupStateReference(fx.StoreNamespace, address, block6));

fx.Store.StoreStateReference(fx.StoreNamespace, tx4.UpdatedAddresses, block4);
Assert.Null(fx.Store.LookupStateReference(fx.StoreNamespace, address, fx.Block3));
Assert.Equal(
block4.Hash,
fx.Store.LookupStateReference(fx.StoreNamespace, address, block4)
);
Assert.Equal(
block4.Hash,
fx.Store.LookupStateReference(fx.StoreNamespace, address, block5)
);
Assert.Equal(
block4.Hash,
fx.Store.LookupStateReference(fx.StoreNamespace, address, block6)
);

fx.Store.StoreStateReference(fx.StoreNamespace, tx5.UpdatedAddresses, block5);
Assert.Null(fx.Store.LookupStateReference(fx.StoreNamespace, address, fx.Block3));
Assert.Equal(
block4.Hash,
fx.Store.LookupStateReference(fx.StoreNamespace, address, block4)
);
Assert.Equal(
block5.Hash,
fx.Store.LookupStateReference(fx.StoreNamespace, address, block5)
);
Assert.Equal(
block5.Hash,
fx.Store.LookupStateReference(fx.StoreNamespace, address, block6)
);
}
}
}
111 changes: 31 additions & 80 deletions Libplanet.Tests/Store/StoreTest.cs
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand Down Expand Up @@ -230,29 +231,37 @@ public void DeleteIndex()
}

[Fact]
public void LookupStateReference()
public void IterateStateReferences()
{
Address address = Fx.Address1;
Block<DumbAction> prevBlock = Fx.Block3;
Address address = this.Fx.Address1;

Assert.Null(Fx.Store.LookupStateReference(Fx.StoreNamespace, address, prevBlock));
Transaction<DumbAction> tx4 = Fx.MakeTransaction(
new DumbAction[] { new DumbAction(address, "foo") }
);
Block<DumbAction> block4 = TestUtils.MineNext(Fx.Block3, new[] { tx4 });

Transaction<DumbAction> transaction = Fx.MakeTransaction(
new List<DumbAction>(),
new HashSet<Address> { address }.ToImmutableHashSet());
Transaction<DumbAction> tx5 = Fx.MakeTransaction(
new DumbAction[] { new DumbAction(address, "bar") }
);
Block<DumbAction> block5 = TestUtils.MineNext(block4, new[] { tx5 });

Block<DumbAction> block = TestUtils.MineNext(
prevBlock,
new[] { transaction });
Assert.Empty(this.Fx.Store.IterateStateReferences(this.Fx.StoreNamespace, address));

var updatedAddresses = new HashSet<Address> { address };
Fx.Store.StoreStateReference(
Fx.StoreNamespace, updatedAddresses.ToImmutableHashSet(), block);
Fx.Store.StoreStateReference(Fx.StoreNamespace, tx4.UpdatedAddresses, block4);
Assert.Equal(
new[] { Tuple.Create(block4.Hash, block4.Index) },
this.Fx.Store.IterateStateReferences(this.Fx.StoreNamespace, address)
);

Fx.Store.StoreStateReference(Fx.StoreNamespace, tx5.UpdatedAddresses, block5);
Assert.Equal(
block.Hash,
Fx.Store.LookupStateReference(Fx.StoreNamespace, address, block));
Assert.Null(Fx.Store.LookupStateReference(Fx.StoreNamespace, address, prevBlock));
new[]
{
Tuple.Create(block5.Hash, block5.Index),
Tuple.Create(block4.Hash, block4.Index),
},
this.Fx.Store.IterateStateReferences(this.Fx.StoreNamespace, address)
);
}

[InlineData(0)]
Expand Down Expand Up @@ -426,75 +435,17 @@ public void TxNonce()
Assert.Equal(0, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction1.Signer));
Assert.Equal(0, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction2.Signer));

Block<DumbAction> block1 = TestUtils.MineNext(
TestUtils.MineGenesis<DumbAction>(),
new[] { Fx.Transaction1 });
Fx.Store.IncreaseTxNonce(Fx.StoreNamespace, block1);

Fx.Store.IncreaseTxNonce(Fx.StoreNamespace, Fx.Transaction1.Signer);
Assert.Equal(1, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction1.Signer));
Assert.Equal(0, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction2.Signer));

Block<DumbAction> block2 = TestUtils.MineNext(
block1,
new[] { Fx.Transaction2 });
Fx.Store.IncreaseTxNonce(Fx.StoreNamespace, block2);

Fx.Store.IncreaseTxNonce(Fx.StoreNamespace, Fx.Transaction2.Signer, 5);
Assert.Equal(1, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction1.Signer));
Assert.Equal(1, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction2.Signer));
}
Assert.Equal(5, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction2.Signer));

[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[Theory]
public void ForkTxNonce(int branchPointIndex)
{
var privateKey1 = new PrivateKey();
var privateKey2 = new PrivateKey();
Address address1 = privateKey1.PublicKey.ToAddress();
Address address2 = privateKey2.PublicKey.ToAddress();
Block<DumbAction> prevBlock = Fx.Block3;
const string targetNamespace = "dummy";

var blocks = new List<Block<DumbAction>>
{
TestUtils.MineNext(
prevBlock,
new[] { Fx.MakeTransaction(privateKey: privateKey1) }),
};
blocks.Add(
TestUtils.MineNext(
blocks[0],
new[] { Fx.MakeTransaction(privateKey: privateKey1) }));
blocks.Add(
TestUtils.MineNext(
blocks[1],
new[] { Fx.MakeTransaction(privateKey: privateKey1) }));
blocks.Add(
TestUtils.MineNext(
blocks[2],
new[] { Fx.MakeTransaction(privateKey: privateKey2) }));

foreach (Block<DumbAction> block in blocks)
{
Fx.Store.IncreaseTxNonce(Fx.StoreNamespace, block);
}

var branchPoint = blocks[branchPointIndex];
Fx.Store.ForkTxNonce(
Fx.StoreNamespace,
targetNamespace,
branchPoint,
new[] { address1, address2 }.ToImmutableHashSet());
Assert.Equal(
3,
Fx.Store.GetTxNonce(Fx.StoreNamespace, address1));
Assert.Equal(
branchPointIndex + 1,
Fx.Store.GetTxNonce(targetNamespace, address1));
Assert.Equal(
0,
Fx.Store.GetTxNonce(targetNamespace, address2));
Fx.Store.IncreaseTxNonce(Fx.StoreNamespace, Fx.Transaction1.Signer, 2);
Assert.Equal(3, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction1.Signer));
Assert.Equal(5, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction2.Signer));
}
}
}