From 67bb26a4857d87fceb98f8dda896a3154c270846 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 26 Jun 2019 16:44:14 +0900 Subject: [PATCH 1/7] Store tx nonce as a simple long int instead of a stack --- CHANGES.md | 8 ++ Libplanet.Tests/Blockchain/BlockChainTest.cs | 17 ++- Libplanet.Tests/Store/StoreTest.cs | 70 +--------- Libplanet.Tests/Store/StoreTracker.cs | 19 +-- Libplanet/Blockchain/BlockChain.cs | 52 +++++-- Libplanet/Store/BaseStore.cs | 13 +- Libplanet/Store/FileStore.cs | 72 ++-------- Libplanet/Store/IStore.cs | 49 ++----- Libplanet/Store/LiteDBStore.cs | 135 +++---------------- 9 files changed, 108 insertions(+), 327 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 92bc6a794b5..3b8b2034c83 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,13 @@ 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(string, Block)` 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. ### Added interfaces @@ -93,6 +100,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 diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.cs b/Libplanet.Tests/Blockchain/BlockChainTest.cs index a979642e6a9..51f71d471b4 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -1026,11 +1026,21 @@ public void MakeTransaction() (x, y) => x.SetItems(y.GetUpdatedStates()) ); + void BuildIndex(Guid id, Block block) + { + string idString = id.ToString(); + foreach (Transaction tx in block.Transactions) + { + store.IncreaseTxNonce(idString, tx.Signer); + } + + store.AppendIndex(idString, block.Hash); + } + // Build the store has incomplete states Block b = TestUtils.MineGenesis(); chain.Blocks[b.Hash] = b; - store.IncreaseTxNonce(chainId.ToString(), b); - store.AppendIndex(chainId.ToString(), b.Hash); + BuildIndex(chainId, b); IImmutableDictionary dirty = GetDirty(b.Evaluate(DateTimeOffset.UtcNow, _ => null)); const int accountsCount = 5; @@ -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); } } diff --git a/Libplanet.Tests/Store/StoreTest.cs b/Libplanet.Tests/Store/StoreTest.cs index fbdc89f6b28..1d63e3949cb 100644 --- a/Libplanet.Tests/Store/StoreTest.cs +++ b/Libplanet.Tests/Store/StoreTest.cs @@ -426,75 +426,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 block1 = TestUtils.MineNext( - TestUtils.MineGenesis(), - 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 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)); - } - - [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 prevBlock = Fx.Block3; - const string targetNamespace = "dummy"; - - var blocks = new List> - { - 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) })); + Assert.Equal(5, Fx.Store.GetTxNonce(Fx.StoreNamespace, Fx.Transaction2.Signer)); - foreach (Block 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)); } } } diff --git a/Libplanet.Tests/Store/StoreTracker.cs b/Libplanet.Tests/Store/StoreTracker.cs index 4db015861f1..ecaddb3ca8a 100644 --- a/Libplanet.Tests/Store/StoreTracker.cs +++ b/Libplanet.Tests/Store/StoreTracker.cs @@ -190,23 +190,10 @@ public long GetTxNonce(string @namespace, Address address) return _store.GetTxNonce(@namespace, address); } - public void IncreaseTxNonce(string @namespace, Block block) - where T : IAction, new() - { - _logs.Add((nameof(IncreaseTxNonce), block.Hash, null)); - _store.IncreaseTxNonce(@namespace, block); - } - - public void ForkTxNonce( - string sourceNamespace, - string destinationNamespace, - Block branchPoint, - IImmutableSet
addressesToStrip) - where T : IAction, new() + public void IncreaseTxNonce(string @namespace, Address address, long delta = 1) { - _logs.Add((nameof(ForkTxNonce), null, null)); - _store.ForkTxNonce( - sourceNamespace, destinationNamespace, branchPoint, addressesToStrip); + _logs.Add((nameof(IncreaseTxNonce), address, delta)); + _store.IncreaseTxNonce(@namespace, address, delta); } public void StageTransactionIds(IDictionary txids) diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index 4301161cc9d..1fad3232e50 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -638,12 +638,14 @@ internal BlockChain Fork(HashDigest point) DateTimeOffset currentTime) { var forked = new BlockChain(Policy, Store, Guid.NewGuid()); + string id = Id.ToString(); + string forkedId = forked.Id.ToString(); try { _rwlock.EnterReadLock(); - foreach (var index in Store.IterateIndex(Id.ToString())) + foreach (var index in Store.IterateIndex(id)) { - Store.AppendIndex(forked.Id.ToString(), index); + Store.AppendIndex(forkedId, index); if (index.Equals(point)) { break; @@ -653,7 +655,7 @@ internal BlockChain Fork(HashDigest point) Block pointBlock = Blocks[point]; var addressesToStrip = new HashSet
(); - var signersToStrip = new HashSet
(); + var signersToStrip = new Dictionary(); for ( Block block = Tip; @@ -666,13 +668,19 @@ internal BlockChain Fork(HashDigest point) .Select(kv => kv.Key) .ToImmutableHashSet(); - ImmutableHashSet
signers = block + addressesToStrip.UnionWith(addresses); + + IEnumerable<(Address, int)> signers = block .Transactions - .Select(tx => tx.Signer) - .ToImmutableHashSet(); + .GroupBy(tx => tx.Signer) + .Select(g => (g.Key, g.Count())); - addressesToStrip.UnionWith(addresses); - signersToStrip.UnionWith(signers); + foreach ((Address address, int txCount) in signers) + { + int existingValue = 0; + signersToStrip.TryGetValue(address, out existingValue); + signersToStrip[address] = existingValue + txCount; + } } Store.ForkStateReferences( @@ -680,11 +688,22 @@ internal BlockChain Fork(HashDigest point) forked.Id.ToString(), pointBlock, addressesToStrip.ToImmutableHashSet()); - Store.ForkTxNonce( - Id.ToString(), - forked.Id.ToString(), - pointBlock, - signersToStrip.ToImmutableHashSet()); + + foreach (KeyValuePair pair in signersToStrip) + { + long existingNonce = Store.GetTxNonce(id, pair.Key); + long forkedNonce = existingNonce - pair.Value; + if (forkedNonce < 0) + { + throw new InvalidOperationException( + $"A tx nonce for {pair.Key} in the store seems broken.\n" + + $"Existing tx nonce: {existingNonce}\n" + + $"Forked tx nonce: {forkedNonce} (delta: {pair.Value})" + ); + } + + Store.IncreaseTxNonce(forkedId, pair.Key, forkedNonce); + } } finally { @@ -856,7 +875,12 @@ bool buildIndices { string chainId = Id.ToString(); Store.StoreStateReference(chainId, updatedAddresses, block); - Store.IncreaseTxNonce(chainId, block); + IEnumerable<(Address, int)> signers = block + .Transactions.GroupBy(tx => tx.Signer).Select(g => (g.Key, g.Count())); + foreach ((Address signer, int txCount) in signers) + { + Store.IncreaseTxNonce(chainId, signer, txCount); + } } } } diff --git a/Libplanet/Store/BaseStore.cs b/Libplanet/Store/BaseStore.cs index 84b3c06e9cc..ba059a78af8 100644 --- a/Libplanet/Store/BaseStore.cs +++ b/Libplanet/Store/BaseStore.cs @@ -100,18 +100,7 @@ AddressStateMap states public abstract long GetTxNonce(string @namespace, Address address); /// - public abstract void IncreaseTxNonce( - string @namespace, - Block block) - where T : IAction, new(); - - /// - public abstract void ForkTxNonce( - string sourceNamespace, - string destinationNamespace, - Block branchPoint, - IImmutableSet
addressesToStrip) - where T : IAction, new(); + public abstract void IncreaseTxNonce(string @namespace, Address signer, long delta = 1); public long CountTransactions() { diff --git a/Libplanet/Store/FileStore.cs b/Libplanet/Store/FileStore.cs index 28a309407e8..e024b7b7cbc 100644 --- a/Libplanet/Store/FileStore.cs +++ b/Libplanet/Store/FileStore.cs @@ -709,78 +709,30 @@ public override long GetTxNonce(string @namespace, Address address) return 0; } - int hashSize = HashDigest.Size; - int blockIndexSize = sizeof(long); - int nonceSize = sizeof(long); - int nonceEntrySize = hashSize + blockIndexSize + nonceSize; - using (Stream stream = nonceFile.OpenRead()) { - if (stream.Length % nonceEntrySize != 0) - { - throw new FileLoadException( - $"Nonce file size {stream.Length} should be " + - $"a multiple of nonce entry size {nonceEntrySize}"); - } - - var buffer = new byte[nonceEntrySize]; - - stream.Seek(-buffer.Length, SeekOrigin.End); + var buffer = new byte[sizeof(long)]; stream.Read(buffer, 0, buffer.Length); - - return BitConverter.ToInt64(buffer, hashSize + blockIndexSize); + return BitConverter.ToInt64(buffer, 0); } } /// - public override void IncreaseTxNonce( - string @namespace, - Block block) + public override void IncreaseTxNonce(string @namespace, Address signer, long delta = 1) { - IEnumerable
signers = block - .Transactions - .Select(tx => tx.Signer); - int hashSize = HashDigest.Size; - long blockIndex = block.Index; + long nextNonce = GetTxNonce(@namespace, signer) + delta; + var nonceFile = new FileInfo(GetTxNoncePath(@namespace, signer)); - foreach (Address signer in signers) + if (!nonceFile.Directory.Exists) { - long nextNonce = GetTxNonce(@namespace, signer) + 1; - var nonceFile = new FileInfo( - GetTxNoncePath(@namespace, signer)); - - if (!nonceFile.Directory.Exists) - { - nonceFile.Directory.Create(); - } - - using (Stream stream = nonceFile.Open( - FileMode.Append, FileAccess.Write)) - { - stream.Write(block.Hash.ToByteArray(), 0, hashSize); - stream.Write( - BitConverter.GetBytes(blockIndex), 0, sizeof(long)); - stream.Write( - BitConverter.GetBytes(nextNonce), 0, sizeof(long)); - } + nonceFile.Directory.Create(); } - } - /// - public override void ForkTxNonce( - string sourceNamespace, - string destinationNamespace, - Block branchPoint, - IImmutableSet
addressesToStrip) - { - ForkIndexedStack( - sourceNamespace, - destinationNamespace, - branchPoint.Index, - addressesToStrip, - GetTxNoncePath, - GetTxNoncePath, - s => GetTxNonces(s).Select(t => t.Item2)); + using (Stream stream = nonceFile.OpenWrite()) + { + byte[] nonceBytes = BitConverter.GetBytes(nextNonce); + stream.Write(nonceBytes, 0, nonceBytes.Length); + } } private void ForkIndexedStack( diff --git a/Libplanet/Store/IStore.cs b/Libplanet/Store/IStore.cs index c036d7e5127..20e710037de 100644 --- a/Libplanet/Store/IStore.cs +++ b/Libplanet/Store/IStore.cs @@ -193,53 +193,20 @@ AddressStateMap states /// /// A nonce. If there is no /// previous , return 0. - /// + /// long GetTxNonce(string @namespace, Address address); /// - /// Increases of - /// in the . + /// Increases (or decreases if a negative is given) + /// the tx nonce counter for . /// /// The namespace to increase /// nonce. - /// The which has the - /// s. - /// An class used with - /// . - /// - void IncreaseTxNonce(string @namespace, Block block) - where T : IAction, new(); - - /// - /// Forks nonces from - /// to - /// . - /// This method copies nonces from - /// to - /// and strips - /// of nonces after - /// . - /// - /// The namespace of - /// nonces to fork. - /// The namespace of destination - /// nonces. - /// The branch point - /// to fork. - /// The set of es - /// to strip nonces after forking. - /// An class used with - /// . - /// Thrown when the given - /// does not exist. - /// - /// - void ForkTxNonce( - string sourceNamespace, - string destinationNamespace, - Block branchPoint, - IImmutableSet
addressesToStrip) - where T : IAction, new(); + /// The address of the account to increase tx nonce. + /// How many to incrase the counter. A negative number decreases + /// the counter. 1 by default. + /// + void IncreaseTxNonce(string @namespace, Address signer, long delta = 1); long CountTransactions(); diff --git a/Libplanet/Store/LiteDBStore.cs b/Libplanet/Store/LiteDBStore.cs index aee7daab2e9..9ca92f3e0bf 100644 --- a/Libplanet/Store/LiteDBStore.cs +++ b/Libplanet/Store/LiteDBStore.cs @@ -519,136 +519,39 @@ public long GetTxNonce(string @namespace, Address address) return 0; } - int hashSize = HashDigest.Size; - int blockIndexSize = sizeof(long); - int nonceSize = sizeof(long); - int nonceEntrySize = hashSize + blockIndexSize + nonceSize; - - using (var stream = new MemoryStream()) + using (var stream = file.OpenRead()) { - file.CopyTo(stream); - - if (stream.Length % nonceEntrySize != 0) - { - throw new FileLoadException( - $"Nonce file size {stream.Length} should be " + - $"a multiple of nonce entry size {nonceEntrySize}"); - } - - var buffer = new byte[nonceEntrySize]; - stream.Seek(stream.Length - buffer.Length, SeekOrigin.Begin); + var buffer = new byte[sizeof(long)]; stream.Read(buffer, 0, buffer.Length); - - return BitConverter.ToInt64(buffer, hashSize + blockIndexSize); + return BitConverter.ToInt64(buffer, 0); } } /// - public void IncreaseTxNonce(string @namespace, Block block) - where T : IAction, new() + public void IncreaseTxNonce(string @namespace, Address signer, long delta = 1) { - IEnumerable
signers = block - .Transactions - .Select(tx => tx.Signer); - int hashSize = HashDigest.Size; - long blockIndex = block.Index; + var fileId = $"{NonceIdPrefix}{@namespace}/{signer.ToHex()}"; + long nextNonce = GetTxNonce(@namespace, signer) + delta; - foreach (Address signer in signers) + if (!_db.FileStorage.Exists(fileId)) { - var fileId = $"{NonceIdPrefix}{@namespace}/{signer.ToHex()}"; - long nextNonce = GetTxNonce(@namespace, signer) + 1; - - if (!_db.FileStorage.Exists(fileId)) - { - _db.FileStorage.Upload( - fileId, - signer.ToHex(), - new MemoryStream()); - } - - LiteFileInfo file = _db.FileStorage.FindById(fileId); - using (var temp = new MemoryStream()) - { - file.CopyTo(temp); - temp.Seek(0, SeekOrigin.Begin); - byte[] prev = temp.ToArray(); - - using (LiteFileStream stream = file.OpenWrite()) - { - stream.Write(prev, 0, prev.Length); - stream.Write(block.Hash.ToByteArray(), 0, hashSize); - stream.Write( - BitConverter.GetBytes(blockIndex), 0, sizeof(long)); - stream.Write( - BitConverter.GetBytes(nextNonce), 0, sizeof(long)); - } - } - } - } - - /// - public void ForkTxNonce( - string sourceNamespace, - string destinationNamespace, - Block branchPoint, - IImmutableSet
addressesToStrip) - where T : IAction, new() - { - long branchPointIndex = branchPoint.Index; - List files = - _db.FileStorage - .Find($"{NonceIdPrefix}{sourceNamespace}") - .ToList(); - - if (!files.Any() && addressesToStrip.Any()) - { - throw new NamespaceNotFoundException( - sourceNamespace, - "The source namespace to be forked does not exist."); - } - - foreach (LiteFileInfo srcFile in files) - { - string destId = - $"{NonceIdPrefix}{destinationNamespace}/{srcFile.Filename}"; _db.FileStorage.Upload( - destId, - srcFile.Filename, + fileId, + signer.ToHex(), new MemoryStream()); + } - LiteFileInfo destFile = _db.FileStorage.FindById(destId); - using (LiteFileStream srcStream = srcFile.OpenRead()) - using (LiteFileStream destStream = destFile.OpenWrite()) - { - while (srcStream.Position < srcStream.Length) - { - var hashBytes = new byte[HashDigest.Size]; - var indexBytes = new byte[sizeof(long)]; - var nonceBytes = new byte[sizeof(long)]; - - srcStream.Read(hashBytes, 0, hashBytes.Length); - srcStream.Read(indexBytes, 0, indexBytes.Length); - srcStream.Read(nonceBytes, 0, nonceBytes.Length); - - long currentIndex = - BitConverter.ToInt64(indexBytes, 0); - - if (currentIndex <= branchPointIndex) - { - destStream.Write(hashBytes, 0, hashBytes.Length); - destStream.Write(indexBytes, 0, indexBytes.Length); - destStream.Write(nonceBytes, 0, nonceBytes.Length); - } - else - { - break; - } - } - } + LiteFileInfo file = _db.FileStorage.FindById(fileId); + using (var temp = new MemoryStream()) + { + file.CopyTo(temp); + temp.Seek(0, SeekOrigin.Begin); + byte[] prev = temp.ToArray(); - if (destFile.Length == 0) + using (LiteFileStream stream = file.OpenWrite()) { - _db.FileStorage.Delete(destId); + byte[] nonceBytes = BitConverter.GetBytes(nextNonce); + stream.Write(nonceBytes, 0, nonceBytes.Length); } } } From 964a09b2d4c0760ba73ba49cd41f749050f3b06d Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 26 Jun 2019 20:04:34 +0900 Subject: [PATCH 2/7] Look up multiple state references in a stack --- CHANGES.md | 7 ++ Libplanet.Tests/Store/StoreExtensionTest.cs | 77 +++++++++++++++++++++ Libplanet.Tests/Store/StoreTest.cs | 36 +++++----- Libplanet.Tests/Store/StoreTracker.cs | 16 +++-- Libplanet/Store/BaseStore.cs | 7 +- Libplanet/Store/FileStore.cs | 39 ++++------- Libplanet/Store/IStore.cs | 35 ++++------ Libplanet/Store/LiteDBStore.cs | 44 +++++------- Libplanet/Store/StoreExtension.cs | 54 +++++++++++++++ 9 files changed, 216 insertions(+), 99 deletions(-) create mode 100644 Libplanet.Tests/Store/StoreExtensionTest.cs create mode 100644 Libplanet/Store/StoreExtension.cs diff --git a/CHANGES.md b/CHANGES.md index 3b8b2034c83..43189fb2c02 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,6 +29,12 @@ To be released. - 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()` method. + Instead, a newly added static class `StoreExtension` provides + an extension method of the same name. + - Added `IStore.IterateStateReferences()` method. ### Added interfaces @@ -44,6 +50,7 @@ To be released. IImmutableSet
, DateTimeOffset?)` method. [[#294]] - Added `BlockChain.GetNextTxNonce()` method which counts staged transactions too during nonce computation. [[#270], [#294]] + - Added `StoreExtension` static class. [[#272], [#307]] ### Behavioral changes diff --git a/Libplanet.Tests/Store/StoreExtensionTest.cs b/Libplanet.Tests/Store/StoreExtensionTest.cs new file mode 100644 index 00000000000..b6e145b92de --- /dev/null +++ b/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 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 tx4 = fx.MakeTransaction( + new DumbAction[] { new DumbAction(address, "foo") } + ); + Block block4 = TestUtils.MineNext(fx.Block3, new[] { tx4 }); + + Transaction tx5 = fx.MakeTransaction( + new DumbAction[] { new DumbAction(address, "bar") } + ); + Block block5 = TestUtils.MineNext(block4, new[] { tx5 }); + + Block block6 = TestUtils.MineNext(block5, new Transaction[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) + ); + } + } +} diff --git a/Libplanet.Tests/Store/StoreTest.cs b/Libplanet.Tests/Store/StoreTest.cs index 1d63e3949cb..71bd817140f 100644 --- a/Libplanet.Tests/Store/StoreTest.cs +++ b/Libplanet.Tests/Store/StoreTest.cs @@ -230,29 +230,33 @@ public void DeleteIndex() } [Fact] - public void LookupStateReference() + public void IterateStateReferences() { - Address address = Fx.Address1; - Block prevBlock = Fx.Block3; + Address address = this.Fx.Address1; - Assert.Null(Fx.Store.LookupStateReference(Fx.StoreNamespace, address, prevBlock)); + Transaction tx4 = Fx.MakeTransaction( + new DumbAction[] { new DumbAction(address, "foo") } + ); + Block block4 = TestUtils.MineNext(Fx.Block3, new[] { tx4 }); - Transaction transaction = Fx.MakeTransaction( - new List(), - new HashSet
{ address }.ToImmutableHashSet()); + Transaction tx5 = Fx.MakeTransaction( + new DumbAction[] { new DumbAction(address, "bar") } + ); + Block block5 = TestUtils.MineNext(block4, new[] { tx5 }); - Block block = TestUtils.MineNext( - prevBlock, - new[] { transaction }); + Assert.Empty(this.Fx.Store.IterateStateReferences(this.Fx.StoreNamespace, address)); - var updatedAddresses = new HashSet
{ address }; - Fx.Store.StoreStateReference( - Fx.StoreNamespace, updatedAddresses.ToImmutableHashSet(), block); + Fx.Store.StoreStateReference(Fx.StoreNamespace, tx4.UpdatedAddresses, block4); + Assert.Equal( + new[] { (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[] { (block5.Hash, block5.Index), (block4.Hash, block4.Index) }, + this.Fx.Store.IterateStateReferences(this.Fx.StoreNamespace, address) + ); } [InlineData(0)] diff --git a/Libplanet.Tests/Store/StoreTracker.cs b/Libplanet.Tests/Store/StoreTracker.cs index ecaddb3ca8a..4673d07e13d 100644 --- a/Libplanet.Tests/Store/StoreTracker.cs +++ b/Libplanet.Tests/Store/StoreTracker.cs @@ -152,14 +152,13 @@ AddressStateMap states _store.SetBlockStates(blockHash, states); } - public HashDigest? LookupStateReference( + public IEnumerable<(HashDigest, long)> IterateStateReferences( string @namespace, - Address address, - Block lookupUntil) - where T : IAction, new() + Address address + ) { - _logs.Add((nameof(LookupStateReference), address, null)); - return _store.LookupStateReference(@namespace, address, lookupUntil); + _logs.Add((nameof(IterateStateReferences), @namespace, address)); + return _store.IterateStateReferences(@namespace, address); } public void StoreStateReference( @@ -168,6 +167,7 @@ AddressStateMap states Block block) where T : IAction, new() { + // FIXME: Log arguments properly (including @namespace). _logs.Add((nameof(StoreStateReference), block.Hash, null)); _store.StoreStateReference(@namespace, addresses, block); } @@ -179,6 +179,7 @@ AddressStateMap states IImmutableSet
addressesToStrip) where T : IAction, new() { + // FIXME: Log arguments properly. _logs.Add((nameof(ForkStateReferences), null, null)); _store.ForkStateReferences( sourceNamespace, destinationNamespace, branchPoint, addressesToStrip); @@ -186,12 +187,13 @@ AddressStateMap states public long GetTxNonce(string @namespace, Address address) { - _logs.Add((nameof(GetTxNonce), address, null)); + _logs.Add((nameof(GetTxNonce), @namespace, address)); return _store.GetTxNonce(@namespace, address); } public void IncreaseTxNonce(string @namespace, Address address, long delta = 1) { + // FIXME: Log arguments properly (including @namespace). _logs.Add((nameof(IncreaseTxNonce), address, delta)); _store.IncreaseTxNonce(@namespace, address, delta); } diff --git a/Libplanet/Store/BaseStore.cs b/Libplanet/Store/BaseStore.cs index ba059a78af8..c41cb15e368 100644 --- a/Libplanet/Store/BaseStore.cs +++ b/Libplanet/Store/BaseStore.cs @@ -75,11 +75,8 @@ AddressStateMap states ); /// - public abstract HashDigest? LookupStateReference( - string @namespace, - Address address, - Block lookupUntil) - where T : IAction, new(); + public abstract IEnumerable<(HashDigest, long)> + IterateStateReferences(string @namespace, Address address); /// public abstract void StoreStateReference( diff --git a/Libplanet/Store/FileStore.cs b/Libplanet/Store/FileStore.cs index e024b7b7cbc..7e74cbcb4b7 100644 --- a/Libplanet/Store/FileStore.cs +++ b/Libplanet/Store/FileStore.cs @@ -611,44 +611,33 @@ AddressStateMap states } /// - public override HashDigest? LookupStateReference( - string @namespace, - Address address, - Block lookupUntil) + public override IEnumerable<(HashDigest, long)> + IterateStateReferences(string @namespace, Address address) { var addrFile = new FileInfo( GetStateReferencePath(@namespace, address)); - long lookupUntilIndex = lookupUntil.Index; if (!addrFile.Exists || addrFile.Length == 0) { - return null; + yield break; } - using (Stream stream = addrFile.OpenRead()) + int stateReferenceSize = HashDigest.Size + sizeof(long); + if (addrFile.Length % stateReferenceSize != 0) { - int stateReferenceSize = HashDigest.Size + sizeof(long); - - if (stream.Length % stateReferenceSize != 0) - { - throw new FileLoadException( - $"State reference file size {stream.Length} " + - "should be multiple of state reference entry size " + - $"{stateReferenceSize}"); - } + throw new FileLoadException( + $"State references file's size ({addrFile.Length}) should be multiple of " + + $"state reference entry size {stateReferenceSize})." + ); + } - foreach ( - var (hashBytes, index) - in GetStateReferences(stream)) + using (Stream stream = addrFile.OpenRead()) + { + foreach (var (hashBytes, index) in GetStateReferences(stream)) { - if (index <= lookupUntilIndex) - { - return new HashDigest(hashBytes); - } + yield return (new HashDigest(hashBytes), index); } } - - return null; } /// diff --git a/Libplanet/Store/IStore.cs b/Libplanet/Store/IStore.cs index 20e710037de..8c26a4cc1b0 100644 --- a/Libplanet/Store/IStore.cs +++ b/Libplanet/Store/IStore.cs @@ -108,26 +108,19 @@ AddressStateMap states ); /// - /// Looks up a state reference, which is a - /// that has the state of the . + /// Gets pairs of a state reference and a corresponding of + /// the requested in the specified . /// - /// The namespace to look up a state reference. - /// - /// The to look up. - /// - /// The upper bound (i.e., the latest block) - /// of the search range. s after - /// are ignored. - /// A which has the state of the - /// . - /// An class used with - /// . - /// - HashDigest? LookupStateReference( + /// The chain namespace. + /// The to get state references. + /// Ordered pairs of a state reference and a corresponding + /// . The highest index (i.e., the closest to the tip) go last, + /// and the lowest index (i.e., the closest to the genesis) go first. + /// + IEnumerable<(HashDigest, long)> IterateStateReferences( string @namespace, - Address address, - Block lookupUntil) - where T : IAction, new(); + Address address + ); /// /// Stores a state reference, which is a @@ -142,7 +135,7 @@ AddressStateMap states /// of the . /// An class used with /// . - /// + /// void StoreStateReference( string @namespace, IImmutableSet
addresses, @@ -172,8 +165,8 @@ AddressStateMap states /// . /// Thrown when the given /// does not exist. - /// - /// + /// + /// void ForkStateReferences( string sourceNamespace, string destinationNamespace, diff --git a/Libplanet/Store/LiteDBStore.cs b/Libplanet/Store/LiteDBStore.cs index 9ca92f3e0bf..6306d6e7fc7 100644 --- a/Libplanet/Store/LiteDBStore.cs +++ b/Libplanet/Store/LiteDBStore.cs @@ -354,54 +354,48 @@ public AddressStateMap GetBlockStates(HashDigest blockHash) } /// - public HashDigest? LookupStateReference( + public IEnumerable<(HashDigest, long)> IterateStateReferences( string @namespace, - Address address, - Block lookupUntil) - where T : IAction, new() + Address address + ) { var fileId = $"{StateRefIdPrefix}{@namespace}/{address.ToHex()}"; LiteFileInfo file = _db.FileStorage.FindById(fileId); if (file is null || file.Length == 0) { - return null; + yield break; + } + + int hashSize = HashDigest.Size; + int stateReferenceSize = hashSize + sizeof(long); + if (file.Length % stateReferenceSize != 0) + { + throw new FileLoadException( + $"State references file's size ({file.Length}) should be multiple of " + + $"state reference entry size {stateReferenceSize})." + ); } using (var stream = new MemoryStream()) { + // Note that a stream made by file.OpenRead() does not support + // .Seek() operation --- although it implements the interface, + // the method throws a NotSupportedException. file.CopyTo(stream); - int hashSize = HashDigest.Size; - int stateReferenceSize = hashSize + sizeof(long); var buffer = new byte[stateReferenceSize]; - - if (stream.Length % stateReferenceSize != 0) - { - throw new FileLoadException( - $"State reference file size {stream.Length} " + - "should be multiple of state reference entry size " + - $"{stateReferenceSize}"); - } - long position = stream.Seek(0, SeekOrigin.End); for (var i = 1; position - buffer.Length >= 0; i++) { - position = stream.Seek( - -buffer.Length * i, SeekOrigin.End); + position = stream.Seek(-buffer.Length * i, SeekOrigin.End); stream.Read(buffer, 0, buffer.Length); byte[] hashBytes = buffer.Take(hashSize).ToArray(); long index = BitConverter.ToInt64(buffer, hashSize); - - if (index <= lookupUntil.Index) - { - return new HashDigest(hashBytes); - } + yield return (new HashDigest(hashBytes), index); } } - - return null; } /// diff --git a/Libplanet/Store/StoreExtension.cs b/Libplanet/Store/StoreExtension.cs new file mode 100644 index 00000000000..5a1200eb740 --- /dev/null +++ b/Libplanet/Store/StoreExtension.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Security.Cryptography; +using Libplanet; +using Libplanet.Action; +using Libplanet.Blocks; + +namespace Libplanet.Store +{ + public static class StoreExtension + { + /// + /// Looks up a state reference, which is a block's that contains + /// an action mutating the ' state. + /// + /// The store object expected to contain the state reference. + /// The namespace to look up a state reference. + /// The to look up. + /// The upper bound (i.e., the latest block) of the search range. + /// s after are ignored. + /// A which has the state of + /// the . + /// An class used with + /// . + /// + /// + public static HashDigest? LookupStateReference( + this IStore store, + string @namespace, + Address address, + Block lookupUntil) + where T : IAction, new() + { + if (lookupUntil is null) + { + throw new ArgumentNullException(nameof(lookupUntil)); + } + + IEnumerable<(HashDigest, long)> stateRefs = + store.IterateStateReferences(@namespace, address); + foreach ((HashDigest refHash, long refIndex) in stateRefs) + { + if (refIndex <= lookupUntil.Index) + { + return refHash; + } + } + + return null; + } + } +} From d61d59133204292fc51e201da53cc2cc24d6b768 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 26 Jun 2019 20:05:27 +0900 Subject: [PATCH 3/7] Column guideline for Visual Studio Code --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9119366d191..2ea9513b176 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "editor.rulers": [80] + "editor.rulers": [80, 100] } From 5f07b07b33ae0cbc13827dff055c02699c345254 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 26 Jun 2019 22:10:50 +0900 Subject: [PATCH 4/7] Store tx nonces into collections instead of files --- Libplanet/Store/LiteDBStore.cs | 48 +++++++--------------------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/Libplanet/Store/LiteDBStore.cs b/Libplanet/Store/LiteDBStore.cs index 6306d6e7fc7..6ba553fb201 100644 --- a/Libplanet/Store/LiteDBStore.cs +++ b/Libplanet/Store/LiteDBStore.cs @@ -28,7 +28,7 @@ public class LiteDBStore : IStore, IDisposable private const string StateRefIdPrefix = "stateref/"; - private const string NonceIdPrefix = "nonce/"; + private const string NonceIdPrefix = "nonce_"; private readonly LiteDatabase _db; @@ -505,49 +505,21 @@ Address address /// public long GetTxNonce(string @namespace, Address address) { - var fileId = $"{NonceIdPrefix}{@namespace}/{address.ToHex()}"; - LiteFileInfo file = _db.FileStorage.FindById(fileId); - - if (file is null || file.Length == 0) - { - return 0; - } - - using (var stream = file.OpenRead()) - { - var buffer = new byte[sizeof(long)]; - stream.Read(buffer, 0, buffer.Length); - return BitConverter.ToInt64(buffer, 0); - } + var collectionId = $"{NonceIdPrefix}{@namespace}"; + LiteCollection collection = _db.GetCollection(collectionId); + var docId = new BsonValue(address.ToByteArray()); + BsonDocument doc = collection.FindById(docId); + return doc is null ? 0 : (doc.TryGetValue("v", out BsonValue v) ? v.AsInt64 : 0); } /// public void IncreaseTxNonce(string @namespace, Address signer, long delta = 1) { - var fileId = $"{NonceIdPrefix}{@namespace}/{signer.ToHex()}"; long nextNonce = GetTxNonce(@namespace, signer) + delta; - - if (!_db.FileStorage.Exists(fileId)) - { - _db.FileStorage.Upload( - fileId, - signer.ToHex(), - new MemoryStream()); - } - - LiteFileInfo file = _db.FileStorage.FindById(fileId); - using (var temp = new MemoryStream()) - { - file.CopyTo(temp); - temp.Seek(0, SeekOrigin.Begin); - byte[] prev = temp.ToArray(); - - using (LiteFileStream stream = file.OpenWrite()) - { - byte[] nonceBytes = BitConverter.GetBytes(nextNonce); - stream.Write(nonceBytes, 0, nonceBytes.Length); - } - } + var collectionId = $"{NonceIdPrefix}{@namespace}"; + LiteCollection collection = _db.GetCollection(collectionId); + var docId = new BsonValue(signer.ToByteArray()); + collection.Upsert(docId, new BsonDocument() { ["v"] = new BsonValue(nextNonce) }); } /// From e022d46f77c17f568942b606b6d2441fe4570e90 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 27 Jun 2019 18:26:28 +0900 Subject: [PATCH 5/7] Use Tuple instead of ValueTuple for Unity compatibility --- Libplanet.Tests/Store/StoreTest.cs | 9 +++++++-- Libplanet.Tests/Store/StoreTracker.cs | 6 +++--- Libplanet/Store/BaseStore.cs | 5 +++-- Libplanet/Store/FileStore.cs | 9 ++++++--- Libplanet/Store/IStore.cs | 6 +++--- Libplanet/Store/LiteDBStore.cs | 10 ++++++---- Libplanet/Store/StoreExtension.cs | 9 ++++----- 7 files changed, 32 insertions(+), 22 deletions(-) diff --git a/Libplanet.Tests/Store/StoreTest.cs b/Libplanet.Tests/Store/StoreTest.cs index 71bd817140f..81961fec3b8 100644 --- a/Libplanet.Tests/Store/StoreTest.cs +++ b/Libplanet.Tests/Store/StoreTest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -248,13 +249,17 @@ public void IterateStateReferences() Fx.Store.StoreStateReference(Fx.StoreNamespace, tx4.UpdatedAddresses, block4); Assert.Equal( - new[] { (block4.Hash, block4.Index) }, + 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( - new[] { (block5.Hash, block5.Index), (block4.Hash, block4.Index) }, + new[] + { + Tuple.Create(block5.Hash, block5.Index), + Tuple.Create(block4.Hash, block4.Index), + }, this.Fx.Store.IterateStateReferences(this.Fx.StoreNamespace, address) ); } diff --git a/Libplanet.Tests/Store/StoreTracker.cs b/Libplanet.Tests/Store/StoreTracker.cs index 4673d07e13d..7f9440a1005 100644 --- a/Libplanet.Tests/Store/StoreTracker.cs +++ b/Libplanet.Tests/Store/StoreTracker.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Security.Cryptography; @@ -152,10 +153,9 @@ AddressStateMap states _store.SetBlockStates(blockHash, states); } - public IEnumerable<(HashDigest, long)> IterateStateReferences( + public IEnumerable, long>> IterateStateReferences( string @namespace, - Address address - ) + Address address) { _logs.Add((nameof(IterateStateReferences), @namespace, address)); return _store.IterateStateReferences(@namespace, address); diff --git a/Libplanet/Store/BaseStore.cs b/Libplanet/Store/BaseStore.cs index c41cb15e368..2cf64e15a00 100644 --- a/Libplanet/Store/BaseStore.cs +++ b/Libplanet/Store/BaseStore.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -75,8 +76,8 @@ AddressStateMap states ); /// - public abstract IEnumerable<(HashDigest, long)> - IterateStateReferences(string @namespace, Address address); + public abstract IEnumerable, long>> IterateStateReferences( + string @namespace, Address address); /// public abstract void StoreStateReference( diff --git a/Libplanet/Store/FileStore.cs b/Libplanet/Store/FileStore.cs index 7e74cbcb4b7..5ef77bfa18f 100644 --- a/Libplanet/Store/FileStore.cs +++ b/Libplanet/Store/FileStore.cs @@ -611,8 +611,8 @@ AddressStateMap states } /// - public override IEnumerable<(HashDigest, long)> - IterateStateReferences(string @namespace, Address address) + public override IEnumerable, long>> IterateStateReferences( + string @namespace, Address address) { var addrFile = new FileInfo( GetStateReferencePath(@namespace, address)); @@ -635,7 +635,10 @@ AddressStateMap states { foreach (var (hashBytes, index) in GetStateReferences(stream)) { - yield return (new HashDigest(hashBytes), index); + yield return Tuple.Create( + new HashDigest(hashBytes), + index + ); } } } diff --git a/Libplanet/Store/IStore.cs b/Libplanet/Store/IStore.cs index 8c26a4cc1b0..0f0aec3ab8d 100644 --- a/Libplanet/Store/IStore.cs +++ b/Libplanet/Store/IStore.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Security.Cryptography; @@ -117,10 +118,9 @@ AddressStateMap states /// . The highest index (i.e., the closest to the tip) go last, /// and the lowest index (i.e., the closest to the genesis) go first. /// - IEnumerable<(HashDigest, long)> IterateStateReferences( + IEnumerable, long>> IterateStateReferences( string @namespace, - Address address - ); + Address address); /// /// Stores a state reference, which is a diff --git a/Libplanet/Store/LiteDBStore.cs b/Libplanet/Store/LiteDBStore.cs index 6ba553fb201..2d64f4c82e6 100644 --- a/Libplanet/Store/LiteDBStore.cs +++ b/Libplanet/Store/LiteDBStore.cs @@ -354,10 +354,9 @@ public AddressStateMap GetBlockStates(HashDigest blockHash) } /// - public IEnumerable<(HashDigest, long)> IterateStateReferences( + public IEnumerable, long>> IterateStateReferences( string @namespace, - Address address - ) + Address address) { var fileId = $"{StateRefIdPrefix}{@namespace}/{address.ToHex()}"; LiteFileInfo file = _db.FileStorage.FindById(fileId); @@ -393,7 +392,10 @@ Address address stream.Read(buffer, 0, buffer.Length); byte[] hashBytes = buffer.Take(hashSize).ToArray(); long index = BitConverter.ToInt64(buffer, hashSize); - yield return (new HashDigest(hashBytes), index); + yield return Tuple.Create( + new HashDigest(hashBytes), + index + ); } } } diff --git a/Libplanet/Store/StoreExtension.cs b/Libplanet/Store/StoreExtension.cs index 5a1200eb740..8bad806a122 100644 --- a/Libplanet/Store/StoreExtension.cs +++ b/Libplanet/Store/StoreExtension.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Security.Cryptography; -using Libplanet; using Libplanet.Action; using Libplanet.Blocks; @@ -38,13 +37,13 @@ public static class StoreExtension throw new ArgumentNullException(nameof(lookupUntil)); } - IEnumerable<(HashDigest, long)> stateRefs = + IEnumerable, long>> stateRefs = store.IterateStateReferences(@namespace, address); - foreach ((HashDigest refHash, long refIndex) in stateRefs) + foreach (Tuple, long> pair in stateRefs) { - if (refIndex <= lookupUntil.Index) + if (pair.Item2 <= lookupUntil.Index) { - return refHash; + return pair.Item1; } } From 7573dc52c7fffce690136b9a1a25c36aabd39d9a Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 27 Jun 2019 18:27:00 +0900 Subject: [PATCH 6/7] Unity test manual --- CONTRIBUTING.md | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c62b3fb53cf..fcb09cc8aea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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* @@ -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/ @@ -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 to .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 assumming 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 ---------------- From 17e937a915cbe50813256fccd043772f0651f97b Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 27 Jun 2019 18:47:10 +0900 Subject: [PATCH 7/7] Reword docs Co-Authored-By: Seunghun Lee --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fcb09cc8aea..0e85f772419 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -129,7 +129,7 @@ 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 to .NET Framework 4.6. +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 @@ -139,7 +139,7 @@ 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 assumming Linux): +To sum up, the instruction is like below (the example is assuming Linux): msbuild -r xunit-unity-runner/StandaloneLinux64 \