Skip to content

Commit

Permalink
Merge pull request #970 from dahlia/action-renderer
Browse files Browse the repository at this point in the history
Short-circuit evaluation of stale actions on reorg
  • Loading branch information
dahlia authored Aug 29, 2020
2 parents c861efc + fbf1e7f commit 635d3a4
Show file tree
Hide file tree
Showing 20 changed files with 1,218 additions and 679 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
"secp256k1",
"struct",
"unrender",
"unrendered",
]
}
8 changes: 7 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ To be released.
- Removed `BlockChain<T>.TipChanged` event, which was replaced by
`IRenderer<T>.RenderBlock()`.
- Removed `PolymorphicAction<T>.Render()` and `Unrender()` methods.
- Removed `BlockChain<T>.TipChangedEventArgs` class.
- Added methods related fungible asset states to `IAccountStateDelta`:
[[#861], [#900], [#954]]
- `UpdatedFungibleAssetsAccounts` property
Expand Down Expand Up @@ -171,10 +172,13 @@ To be released.
- Added `FungibleAssetStateCompleters<T>` static class.
- Added `Swarm<T>.GetTrustedStateCompleterAsync()` method.
- Added `IRenderer<T>` interface. [[#959], [#963]]
- Added `PolymorphicRenderer<T>` class. [[#959], [#963]]
- Added `IActionRenderer<T>` interface. [[#959], [#967], [#970]]
- Added `AnonymousRenderer<T>` class. [[#959], [#963]]
- Added `AnonymousActionRenderer<T>` interface. [[#959], [#967], [#970]]
- Added `LoggedRenderer<T>` class. [[#959], [#963]]
- Added `LoggedActionRenderer<T>` interface. [[#959], [#967], [#970]]
- Added `BlockChain<T>.Renderers` property. [[#945], [#959], [#963]]
- Added `BlockChain<T>.ActionRenderers` property. [[#959], [#967], [#970]]
- Added `Swarm<T>.AppProtocolVersion` property. [[#949]]
- `DefaultStore` became to implement `IBlockStatesStore`. [[#950]]
- Added `IStateStore` interface. [[#950]]
Expand Down Expand Up @@ -293,6 +297,8 @@ To be released.
[#963]: https://github.com/planetarium/libplanet/pull/963
[#964]: https://github.com/planetarium/libplanet/pull/964
[#965]: https://github.com/planetarium/libplanet/pull/965
[#967]: https://github.com/planetarium/libplanet/issues/967
[#970]: https://github.com/planetarium/libplanet/pull/970
[#972]: https://github.com/planetarium/libplanet/pull/972
[sleep mode]: https://en.wikipedia.org/wiki/Sleep_mode

Expand Down
9 changes: 5 additions & 4 deletions Docs/articles/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,16 @@ Although this method works, there are still some problems because reflecting the
- If multiple actions were executed in a short period of time, they would not be handled accurately.

Libplanet provides a rendering mechanism called
@"Libplanet.Blockchain.Renderers.IRenderer`1" to solve this problem.
@"Libplanet.Blockchain.Renderers.IRenderer`1.RenderAction(Libplanet.Action.IAction,Libplanet.Action.IActionContext,Libplanet.Action.IAccountStateDelta)"
@"Libplanet.Blockchain.Renderers.IRenderer`1" and its subtype
@"Libplanet.Blockchain.Renderers.IActionRenderer`1" to solve this problem.
@"Libplanet.Blockchain.Renderers.IActionRenderer`1.RenderAction(Libplanet.Action.IAction,Libplanet.Action.IActionContext,Libplanet.Action.IAccountStateDelta)"
is called after a @"Libplanet.Blocks.Block`1" with the corresponding
@"Libplanet.Action.IAction"s is confirmed and the state is transitioned.
The following code has been re-implemented using
@"Libplanet.Blockchain.Renderers.IRenderer`1.RenderAction(Libplanet.Action.IAction,Libplanet.Action.IActionContext,Libplanet.Action.IAccountStateDelta)".
@"Libplanet.Blockchain.Renderers.IActionRenderer`1.RenderAction(Libplanet.Action.IAction,Libplanet.Action.IActionContext,Libplanet.Action.IAccountStateDelta)".

```csharp
public class WinRenderer : IRenderer<Win>
public class WinRenderer : IActionRenderer<Win>
{
// ...
Expand Down
60 changes: 56 additions & 4 deletions Libplanet.Tests/Blockchain/BlockChainTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public BlockChainTest(ITestOutputHelper output)
_fx.Store,
_fx.StateStore,
_fx.GenesisBlock,
renderers: new[] { new LoggedRenderer<DumbAction>(_renderer, Log.Logger) }
renderers: new[] { new LoggedActionRenderer<DumbAction>(_renderer, Log.Logger) }
);
_renderer.ResetRecords();
}
Expand Down Expand Up @@ -397,6 +397,58 @@ public async void ProcessActions()
Assert.NotNull(state);
}

[Fact]
public void ShortCircuitActionEvaluationForUnrenderWithNoActionRenderers()
{
IEnumerable<ExecuteRecord> NonRehearsalExecutions() =>
DumbAction.ExecuteRecords.Value.Where(r => !r.Rehearsal);

var policy = new BlockPolicy<DumbAction>();
var key = new PrivateKey();
Address miner = key.ToAddress();

using (var fx = new DefaultStoreFixture(memory: true))
{
var emptyRenderer = new AnonymousRenderer<DumbAction>();
var chain = new BlockChain<DumbAction>(
policy,
fx.Store,
fx.StateStore,
fx.GenesisBlock,
renderers: new[] { emptyRenderer }
);
var actions = new[] { new DumbAction(miner, "foo") };
var tx = chain.MakeTransaction(key, actions);
var block = TestUtils.MineNext(
chain.Genesis,
new[] { tx },
miner: miner,
difficulty: _blockChain.Policy.GetNextBlockDifficulty(_blockChain));
chain.Append(block);
var forked = chain.Fork(chain.Genesis.Hash);
forked.Append(block);

DumbAction.ExecuteRecords.Value = ImmutableList<ExecuteRecord>.Empty;
Assert.Empty(DumbAction.ExecuteRecords.Value);

// Stale actions shouldn't be evaluated because the "renderer" here is not an
// IActionRenderer<T> but a vanilla IRenderer<T> which means they don't have to
// be unrendered.
var renderer = new DumbRenderer<DumbAction>();
var newChain = new BlockChain<DumbAction>(
policy,
fx.Store,
fx.StateStore,
Guid.NewGuid(),
fx.GenesisBlock,
renderers: new[] { renderer }
);
chain.Swap(newChain, true);
Assert.Empty(renderer.ActionRecords);
Assert.Empty(NonRehearsalExecutions());
}
}

[Fact]
public void Append()
{
Expand Down Expand Up @@ -653,16 +705,16 @@ public void UnstageAfterAppendComplete()
}

[Fact]
public async Task RenderAfterAppendComplete()
public async Task RenderActionsAfterAppendComplete()
{
var policy = new NullPolicy<DumbAction>();
var store = new DefaultStore(null);
IRenderer<DumbAction> renderer = new AnonymousRenderer<DumbAction>
IActionRenderer<DumbAction> renderer = new AnonymousActionRenderer<DumbAction>
{
ActionRenderer = (_, __, nextStates) =>
throw new SomeException("thrown by renderer"),
};
renderer = new LoggedRenderer<DumbAction>(renderer, Log.Logger);
renderer = new LoggedActionRenderer<DumbAction>(renderer, Log.Logger);
BlockChain<DumbAction> blockChain =
TestUtils.MakeBlockChain(policy, store, renderers: new[] { renderer });
var privateKey = new PrivateKey();
Expand Down
194 changes: 194 additions & 0 deletions Libplanet.Tests/Blockchain/Renderers/AnonymousActionRendererTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using System;
using Libplanet.Action;
using Libplanet.Blockchain.Renderers;
using Libplanet.Blocks;
using Libplanet.Tests.Common.Action;
using Xunit;

namespace Libplanet.Tests.Blockchain.Renderers
{
public class AnonymousActionRendererTest
{
private static IAction _action = new DumbAction();

private static IAccountStateDelta _stateDelta =
new AccountStateDeltaImpl(_ => null, (_, __) => default, default);

private static IActionContext _actionContext =
new ActionContext(default, default, default, _stateDelta, default);

private static Exception _exception = new Exception();

private static Block<DumbAction> _genesis =
TestUtils.MineGenesis<DumbAction>(default(Address));

private static Block<DumbAction> _blockA = TestUtils.MineNext(_genesis);

private static Block<DumbAction> _blockB = TestUtils.MineNext(_genesis);

[Fact]
public void ActionRenderer()
{
(IAction, IActionContext, IAccountStateDelta)? record = null;
var renderer = new AnonymousActionRenderer<DumbAction>
{
ActionRenderer = (action, context, nextStates) =>
record = (action, context, nextStates),
};

renderer.UnrenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.RenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.UnrenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.RenderBlock(_genesis, _blockA);
Assert.Null(record);
renderer.RenderReorg(_blockA, _blockB, _genesis);
Assert.Null(record);

renderer.RenderAction(_action, _actionContext, _stateDelta);
Assert.NotNull(record);
Assert.Same(_action, record?.Item1);
Assert.Same(_actionContext, record?.Item2);
Assert.Same(_stateDelta, record?.Item3);
}

[Fact]
public void ActionUnrenderer()
{
(IAction, IActionContext, IAccountStateDelta)? record = null;
var renderer = new AnonymousActionRenderer<DumbAction>
{
ActionUnrenderer = (action, context, nextStates) =>
record = (action, context, nextStates),
};

renderer.RenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.RenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.UnrenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.RenderBlock(_genesis, _blockA);
Assert.Null(record);
renderer.RenderReorg(_blockA, _blockB, _genesis);
Assert.Null(record);

renderer.UnrenderAction(_action, _actionContext, _stateDelta);
Assert.NotNull(record);
Assert.Same(_action, record?.Item1);
Assert.Same(_actionContext, record?.Item2);
Assert.Same(_stateDelta, record?.Item3);
}

[Fact]
public void ActionErrorRenderer()
{
(IAction, IActionContext, Exception)? record = null;
var renderer = new AnonymousActionRenderer<DumbAction>
{
ActionErrorRenderer = (action, context, exception) =>
record = (action, context, exception),
};

renderer.RenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.UnrenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.UnrenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.RenderBlock(_genesis, _blockA);
Assert.Null(record);
renderer.RenderReorg(_blockA, _blockB, _genesis);
Assert.Null(record);

renderer.RenderActionError(_action, _actionContext, _exception);
Assert.NotNull(record);
Assert.Same(_action, record?.Item1);
Assert.Same(_actionContext, record?.Item2);
Assert.Same(_exception, record?.Item3);
}

[Fact]
public void ActionErrorUnrenderer()
{
(IAction, IActionContext, Exception)? record = null;
var renderer = new AnonymousActionRenderer<DumbAction>
{
ActionErrorUnrenderer = (action, context, exception) =>
record = (action, context, exception),
};

renderer.RenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.UnrenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.RenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.RenderBlock(_genesis, _blockA);
Assert.Null(record);
renderer.RenderReorg(_blockA, _blockB, _genesis);
Assert.Null(record);

renderer.UnrenderActionError(_action, _actionContext, _exception);
Assert.NotNull(record);
Assert.Same(_action, record?.Item1);
Assert.Same(_actionContext, record?.Item2);
Assert.Same(_exception, record?.Item3);
}

[Fact]
public void BlockRenderer()
{
(Block<DumbAction> Old, Block<DumbAction> New)? record = null;
var renderer = new AnonymousActionRenderer<DumbAction>
{
BlockRenderer = (oldTip, newTip) => record = (oldTip, newTip),
};

renderer.RenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.UnrenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.RenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.UnrenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.RenderReorg(_blockA, _blockB, _genesis);
Assert.Null(record);

renderer.RenderBlock(_genesis, _blockA);
Assert.NotNull(record);
Assert.Same(_genesis, record?.Old);
Assert.Same(_blockA, record?.New);
}

[Fact]
public void BlockReorg()
{
(Block<DumbAction> Old, Block<DumbAction> New, Block<DumbAction> Bp)? record = null;
var renderer = new AnonymousActionRenderer<DumbAction>
{
ReorgRenderer = (oldTip, newTip, bp) => record = (oldTip, newTip, bp),
};

renderer.RenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.UnrenderAction(_action, _actionContext, _stateDelta);
Assert.Null(record);
renderer.RenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.UnrenderActionError(_action, _actionContext, _exception);
Assert.Null(record);
renderer.RenderBlock(_genesis, _blockA);
Assert.Null(record);

renderer.RenderReorg(_blockA, _blockB, _genesis);
Assert.NotNull(record);
Assert.Same(_blockA, record?.Old);
Assert.Same(_blockB, record?.New);
Assert.Same(_genesis, record?.Bp);
}
}
}
Loading

0 comments on commit 635d3a4

Please sign in to comment.