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

Make BlockChain<T>.GetStates() to only drill down until all requested addresses are found #192

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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