Skip to content

Commit

Permalink
Merge pull request #192 from dahlia/fix-get-states-unnecessary-deep-down
Browse files Browse the repository at this point in the history
Make BlockChain<T>.GetStates() to only drill down until all requested addresses are found
  • Loading branch information
dahlia authored Apr 12, 2019
2 parents c5366d9 + d447970 commit 952149e
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 16 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.rulers": [80]
}
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ Version 0.2.2

To be released.

- Fixed a bug that `BlockChain<T>.GetStates()` had returned slower than
necessary for many addresses. [[#189], [#192]]

[#189]: https://github.com/planetarium/libplanet/issues/189
[#192]: https://github.com/planetarium/libplanet/pull/192


Version 0.2.1
-------------
Expand Down
64 changes: 64 additions & 0 deletions Libplanet.Tests/Blockchain/BlockChainTest.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Schema;
using Libplanet.Action;
using Libplanet.Blockchain;
using Libplanet.Blockchain.Policies;
Expand Down Expand Up @@ -226,6 +228,56 @@ public void CanGetBlockLocator()
Assert.Equal(expected, actual);
}

// This is a regression test for:
// https://github.com/planetarium/libplanet/issues/189#issuecomment-482443607.
[Fact]
public void GetStatesOnlyDrillsDownUntilRequestedAddressesAreFound()
{
var tracker = new StoreTracker(_fx.Store);
var chain = new BlockChain<DumbAction>(
new NullPolicy<DumbAction>(),
tracker
);

Block<DumbAction> b = TestUtils.MineGenesis<DumbAction>();
chain.Append(b);
Address[] addresses = new Address[30];
for (int i = 0; i < addresses.Length; ++i)
{
var privateKey = new PrivateKey();
Address address = privateKey.PublicKey.ToAddress();
addresses[i] = address;
DumbAction[] actions =
{
new DumbAction(address, "foo"),
new DumbAction(i < 1 ? address : addresses[i - 1], "bar"),
};
Transaction<DumbAction>[] txs =
{
Transaction<DumbAction>.Create(privateKey, actions),
};
b = TestUtils.MineNext(b, txs);
chain.Append(b);
}

tracker.ClearLogs();
int testingDepth = addresses.Length / 2;
Address[] targetAddresses = Enumerable.Range(
testingDepth,
Math.Min(10, addresses.Length - testingDepth - 1)
).Select(i => addresses[i]).ToArray();
AddressStateMap result = chain.GetStates(targetAddresses);
foreach (Address targetAddress in targetAddresses)
{
Assert.True(result.ContainsKey(targetAddress));
}

var callCount = tracker.Logs.Where(
triple => triple.Item1.Equals("GetBlockStates")
).Select(triple => triple.Item2).Count();
Assert.True(testingDepth >= callCount);
}

[Fact]
public void EvaluateActions()
{
Expand Down Expand Up @@ -264,6 +316,18 @@ public void EvaluateActions()
Assert.Equal(states[TestEvaluateAction.BlockIndexKey], blockIndex);
}

private sealed class NullPolicy<T> : IBlockPolicy<T>
where T : IAction, new()
{
public int GetNextBlockDifficulty(IEnumerable<Block<T>> blocks) =>
blocks.Any() ? 1 : 0;

public InvalidBlockException ValidateBlocks(
IEnumerable<Block<T>> blocks,
DateTimeOffset currentTime
) => null;
}

private sealed class TestEvaluateAction : IAction
{
public static readonly Address SignerKey =
Expand Down
5 changes: 3 additions & 2 deletions Libplanet.Tests/Common/Action/DumbAction.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Immutable;
using System.Threading;
using Libplanet.Action;
Expand Down Expand Up @@ -34,7 +35,7 @@ public DumbAction(
public IImmutableDictionary<string, object> PlainValue =>
ImmutableDictionary<string, object>.Empty
.Add("item", Item)
.Add("target_address", TargetAddress)
.Add("target_address", TargetAddress.ToByteArray())
.Add("record_rehearsal", RecordRehearsal);

public IAccountStateDelta Execute(IActionContext context)
Expand Down Expand Up @@ -69,7 +70,7 @@ IImmutableDictionary<string, object> plainValue
)
{
Item = (string)plainValue["item"];
TargetAddress = (Address)plainValue["target_address"];
TargetAddress = new Address((byte[])plainValue["target_address"]);
RecordRehearsal = (bool)plainValue["record_rehearsal"];
}
}
Expand Down
161 changes: 161 additions & 0 deletions Libplanet.Tests/Store/StoreTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Security.Cryptography;
using Libplanet.Action;
using Libplanet.Blocks;
using Libplanet.Store;
using Libplanet.Tx;

namespace Libplanet.Tests.Store
{
public sealed class StoreTracker : IStore
{
private readonly IStore _store;

private readonly List<(string, object, object)> _logs;

public StoreTracker(IStore store)
{
_store = store;
_logs = new List<(string, object, object)>();
}

public IImmutableList<(string, object, object)> Logs =>
_logs.ToImmutableList();

public void ClearLogs() => _logs.Clear();

public long AppendIndex(string @namespace, HashDigest<SHA256> hash)
{
_logs.Add((nameof(AppendIndex), @namespace, hash));
return _store.AppendIndex(@namespace, hash);
}

public long CountBlocks()
{
_logs.Add((nameof(CountBlocks), null, null));
return _store.CountBlocks();
}

public long CountIndex(string @namespace)
{
_logs.Add((nameof(CountIndex), @namespace, null));
return _store.CountIndex(@namespace);
}

public long CountTransactions()
{
_logs.Add((nameof(CountTransactions), null, null));
return _store.CountTransactions();
}

public bool DeleteBlock(HashDigest<SHA256> blockHash)
{
_logs.Add((nameof(DeleteBlock), blockHash, null));
return _store.DeleteBlock(blockHash);
}

public bool DeleteIndex(string @namespace, HashDigest<SHA256> hash)
{
_logs.Add((nameof(DeleteIndex), @namespace, hash));
return _store.DeleteIndex(@namespace, hash);
}

public bool DeleteTransaction(TxId txid)
{
_logs.Add((nameof(DeleteTransaction), txid, null));
return _store.DeleteTransaction(txid);
}

public Block<T> GetBlock<T>(HashDigest<SHA256> blockHash)
where T : IAction, new()
{
_logs.Add((nameof(GetBlock), blockHash, null));
return _store.GetBlock<T>(blockHash);
}

public AddressStateMap GetBlockStates(HashDigest<SHA256> blockHash)
{
_logs.Add((nameof(GetBlockStates), blockHash, null));
return _store.GetBlockStates(blockHash);
}

public Transaction<T> GetTransaction<T>(TxId txid)
where T : IAction, new()
{
_logs.Add((nameof(GetTransaction), txid, null));
return _store.GetTransaction<T>(txid);
}

public HashDigest<SHA256>? IndexBlockHash(string @namespace, long index)
{
_logs.Add((nameof(IndexBlockHash), @namespace, index));
return _store.IndexBlockHash(@namespace, index);
}

public IEnumerable<HashDigest<SHA256>> IterateBlockHashes()
{
_logs.Add((nameof(IterateBlockHashes), null, null));
return _store.IterateBlockHashes();
}

public IEnumerable<HashDigest<SHA256>> IterateIndex(string @namespace)
{
_logs.Add((nameof(IterateIndex), @namespace, null));
return _store.IterateIndex(@namespace);
}

public IEnumerable<TxId> IterateStagedTransactionIds()
{
_logs.Add((nameof(IterateStagedTransactionIds), null, null));
return _store.IterateStagedTransactionIds();
}

public IEnumerable<TxId> IterateTransactionIds()
{
_logs.Add((nameof(IterateTransactionIds), null, null));
return _store.IterateTransactionIds();
}

public IEnumerable<string> ListNamespaces()
{
_logs.Add((nameof(ListNamespaces), null, null));
return _store.ListNamespaces();
}

public void PutBlock<T>(Block<T> block)
where T : IAction, new()
{
_logs.Add((nameof(PutBlock), block, null));
_store.PutBlock<T>(block);
}

public void PutTransaction<T>(Transaction<T> tx)
where T : IAction, new()
{
_logs.Add((nameof(PutTransaction), tx, null));
_store.PutTransaction<T>(tx);
}

public void SetBlockStates(
HashDigest<SHA256> blockHash,
AddressStateMap states
)
{
_logs.Add((nameof(SetBlockStates), blockHash, states));
_store.SetBlockStates(blockHash, states);
}

public void StageTransactionIds(ISet<TxId> txids)
{
_logs.Add((nameof(StageTransactionIds), txids, null));
_store.StageTransactionIds(txids);
}

public void UnstageTransactionIds(ISet<TxId> txids)
{
_logs.Add((nameof(UnstageTransactionIds), txids, null));
_store.UnstageTransactionIds(txids);
}
}
}
47 changes: 47 additions & 0 deletions Libplanet.Tests/Store/StoreTrackerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using Xunit;

namespace Libplanet.Tests.Store
{
public sealed class StoreTrackerTest
{
private readonly FileStoreFixture _fx;
private readonly StoreTracker _tracker;

public StoreTrackerTest()
{
_fx = new FileStoreFixture();
_tracker = new StoreTracker(_fx.Store);
}

[Fact]
public void LogsAreEmptyAtFirst()
{
Assert.Empty(_tracker.Logs);
}

[Fact]
public void MethodCallsAreLogged()
{
_tracker.ListNamespaces();
Assert.Equal(1, _tracker.Logs.Count);
Assert.Equal(("ListNamespaces", null, null), _tracker.Logs[0]);

var ns = Guid.NewGuid();
_tracker.CountIndex(ns.ToString());
Assert.Equal(2, _tracker.Logs.Count);
Assert.Equal(("CountIndex", ns.ToString(), null), _tracker.Logs[1]);
}

[Fact]
public void ClearLogs()
{
_tracker.ListNamespaces();
_tracker.CountIndex(Guid.NewGuid().ToString());
Assert.Equal(2, _tracker.Logs.Count);

_tracker.ClearLogs();
Assert.Empty(_tracker.Logs);
}
}
}
24 changes: 12 additions & 12 deletions Libplanet.Tests/Tx/TransactionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,25 @@ public void Create()
AssertBytesEqual(
new byte[]
{
0x30, 0x45, 0x02, 0x21, 0x00, 0xba, 0xc1, 0x7f, 0x21, 0x0a,
0x15, 0xdd, 0x99, 0x6b, 0xda, 0x34, 0x2c, 0x8c, 0x39, 0x21,
0xd1, 0x27, 0xec, 0x22, 0xf7, 0xea, 0xaf, 0x8b, 0xc4, 0xd7,
0x82, 0x6e, 0xfb, 0x5a, 0xdd, 0xb6, 0xb5, 0x02, 0x20, 0x7c,
0x0a, 0x3b, 0x5c, 0x4d, 0x83, 0xa1, 0xa3, 0xfc, 0x1b, 0xf8,
0xdd, 0xbc, 0xbc, 0x78, 0x60, 0x46, 0xf9, 0x61, 0xdd, 0x5a,
0x35, 0xab, 0xb1, 0x61, 0x84, 0x32, 0x89, 0x2e, 0x3d, 0xb4,
0xdc,
0x30, 0x45, 0x02, 0x21, 0x00, 0xaa, 0x8d, 0xa2, 0x68, 0x6e,
0xc0, 0x1d, 0x89, 0x96, 0x4a, 0xdb, 0x9b, 0xe8, 0x4b, 0x3c,
0xb7, 0x18, 0xed, 0xdf, 0x78, 0x01, 0x78, 0xf2, 0x5c, 0x84,
0x59, 0x3b, 0x2e, 0xd8, 0xf4, 0xa7, 0xa5, 0x02, 0x20, 0x38,
0x4a, 0x89, 0x8f, 0x97, 0xbb, 0xc8, 0xb9, 0x22, 0x53, 0xb1,
0xe2, 0x19, 0xd0, 0x3b, 0xfb, 0xac, 0xa3, 0xb3, 0xe8, 0x80,
0x21, 0x52, 0x2d, 0x83, 0x63, 0x66, 0xc3, 0x13, 0x89, 0x3b,
0x39,
},
tx.Signature
);
AssertBytesEqual(
new TxId(
new byte[]
{
0xe0, 0xd3, 0xa8, 0xf4, 0x28, 0x22, 0xab, 0x58, 0x4e,
0x04, 0x3f, 0x97, 0x07, 0xac, 0xb5, 0x77, 0x72, 0x13,
0x3f, 0xb2, 0xd9, 0x07, 0x4a, 0xc3, 0x79, 0xcc, 0xbc,
0xae, 0x74, 0x12, 0x60, 0x95,
0x0c, 0x31, 0x7c, 0x05, 0x5f, 0xe4, 0xbc, 0x92, 0x5f,
0x6b, 0x82, 0x62, 0x7e, 0x7f, 0x9e, 0xa8, 0x43, 0xbb,
0x46, 0x43, 0x55, 0xde, 0x89, 0xbe, 0xe3, 0xcc, 0x1b,
0x3a, 0x76, 0x32, 0x81, 0xaa,
}
),
tx.Id
Expand Down
6 changes: 4 additions & 2 deletions Libplanet/Blockchain/BlockChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,19 @@ public AddressStateMap GetStates(
_rwlock.ExitReadLock();
}

ImmutableHashSet<Address> requestedAddresses =
addresses.ToImmutableHashSet();
var states = new AddressStateMap();
while (offset != null)
{
states = (AddressStateMap)states.SetItems(
Store.GetBlockStates(offset.Value)
.Where(
kv => addresses.Contains(kv.Key) &&
kv => requestedAddresses.Contains(kv.Key) &&
!states.ContainsKey(kv.Key))
);

if (states.Keys.SequenceEqual(addresses))
if (requestedAddresses.SetEquals(states.Keys))
{
break;
}
Expand Down

0 comments on commit 952149e

Please sign in to comment.