diff --git a/CHANGES.md b/CHANGES.md index a4736e5198..a0686d30b1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,7 @@ To be released. - Added `IActionContext.NewGuId()` method. [[#371], [#439]] - `Address(byte[])` became to throw `ArgumentNullException` instead of `NullReferenceException`. [[#443]] + - Removed `FileStore` class. [[#446]] ### Added interfaces @@ -157,6 +158,7 @@ To be released. [#442]: https://github.com/planetarium/libplanet/issues/442 [#443]: https://github.com/planetarium/libplanet/pull/443 [#445]: https://github.com/planetarium/libplanet/pull/445 +[#446]: https://github.com/planetarium/libplanet/pull/446 [LiteDB #1268]: https://github.com/mbdavid/LiteDB/issues/1268 [floating-point determinism]: https://wp.me/p1fTCO-kT diff --git a/Libplanet.Benchmarks/MineBlock.cs b/Libplanet.Benchmarks/MineBlock.cs index 0792fcb4dd..56f4c4a5d2 100644 --- a/Libplanet.Benchmarks/MineBlock.cs +++ b/Libplanet.Benchmarks/MineBlock.cs @@ -10,12 +10,12 @@ namespace Libplanet.Benchmarks { public class MineBlock { - private FileStoreFixture _fx; + private StoreFixture _fx; private BlockChain _blockChain; public MineBlock() { - _fx = new FileStoreFixture(); + _fx = new LiteDBStoreFixture(); _blockChain = new BlockChain( new NullPolicy(), _fx.Store diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.cs b/Libplanet.Tests/Blockchain/BlockChainTest.cs index 1962e89476..8d19e32b34 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -18,12 +18,12 @@ namespace Libplanet.Tests.Blockchain { public class BlockChainTest : IDisposable { - private FileStoreFixture _fx; + private StoreFixture _fx; private BlockChain _blockChain; public BlockChainTest() { - _fx = new FileStoreFixture(); + _fx = new LiteDBStoreFixture(); _blockChain = new BlockChain( new BlockPolicy(new MinerReward(1)), _fx.Store diff --git a/Libplanet.Tests/Blockchain/Policies/BlockPolicyTest.cs b/Libplanet.Tests/Blockchain/Policies/BlockPolicyTest.cs index 1dc5e614d2..4d2f15d05c 100644 --- a/Libplanet.Tests/Blockchain/Policies/BlockPolicyTest.cs +++ b/Libplanet.Tests/Blockchain/Policies/BlockPolicyTest.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; -using Libplanet.Blockchain; using Libplanet.Blockchain.Policies; using Libplanet.Blocks; using Libplanet.Tests.Common.Action; @@ -13,7 +12,7 @@ namespace Libplanet.Tests.Blockchain.Policies { - public class BlockPolicyTest : IClassFixture + public class BlockPolicyTest : IClassFixture { private static readonly DateTimeOffset FixtureEpoch = new DateTimeOffset(2018, 1, 1, 0, 0, 0, TimeSpan.Zero); diff --git a/Libplanet.Tests/Net/SwarmTest.cs b/Libplanet.Tests/Net/SwarmTest.cs index 282eae2122..e0af999b70 100644 --- a/Libplanet.Tests/Net/SwarmTest.cs +++ b/Libplanet.Tests/Net/SwarmTest.cs @@ -31,10 +31,9 @@ public class SwarmTest : IDisposable private const int DisposeTimeout = 5 * 1000; private readonly ITestOutputHelper _output; - - private readonly FileStoreFixture _fx1; - private readonly FileStoreFixture _fx2; - private readonly FileStoreFixture _fx3; + private readonly StoreFixture _fx1; + private readonly StoreFixture _fx2; + private readonly StoreFixture _fx3; private readonly List> _blockchains; private readonly List> _swarms; @@ -53,9 +52,9 @@ public SwarmTest(ITestOutputHelper output) _output = output; var policy = new BlockPolicy(new MinerReward(1)); - _fx1 = new FileStoreFixture(); - _fx2 = new FileStoreFixture(); - _fx3 = new FileStoreFixture(); + _fx1 = new LiteDBStoreFixture(); + _fx2 = new LiteDBStoreFixture(); + _fx3 = new LiteDBStoreFixture(); _blockchains = new List> { @@ -341,7 +340,7 @@ public async Task DetectAppProtocolVersion() host: IPAddress.Loopback.ToString(), appProtocolVersion: 2); var d = new Swarm( - new BlockChain(_blockchains[0].Policy, new FileStoreFixture().Store), + new BlockChain(_blockchains[0].Policy, new LiteDBStoreFixture().Store), new PrivateKey(), host: IPAddress.Loopback.ToString(), appProtocolVersion: 3); diff --git a/Libplanet.Tests/Store/BlockSetTest.cs b/Libplanet.Tests/Store/BlockSetTest.cs index 95ac801456..3a4777cfb0 100644 --- a/Libplanet.Tests/Store/BlockSetTest.cs +++ b/Libplanet.Tests/Store/BlockSetTest.cs @@ -10,12 +10,12 @@ namespace Libplanet.Tests.Store { public class BlockSetTest : IDisposable { - private readonly FileStoreFixture _fx; + private readonly StoreFixture _fx; private readonly BlockSet _set; public BlockSetTest() { - _fx = new FileStoreFixture(); + _fx = new LiteDBStoreFixture(); _set = new BlockSet(_fx.Store); } diff --git a/Libplanet.Tests/Store/FileStoreFixture.cs b/Libplanet.Tests/Store/FileStoreFixture.cs deleted file mode 100644 index 658fb454dc..0000000000 --- a/Libplanet.Tests/Store/FileStoreFixture.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using Libplanet.Store; -using Serilog; - -namespace Libplanet.Tests.Store -{ - public class FileStoreFixture : StoreFixture, IDisposable - { - public FileStoreFixture() - { - string postfix = Guid.NewGuid().ToString(); - Path = System.IO.Path.Combine( - System.IO.Path.GetTempPath(), $"filestore_test_{postfix}"); - Store = new FileStore(Path); - } - - public string Path { get; } - - public void Dispose() - { - while (Directory.Exists(Path)) - { - try - { - Directory.Delete(Path, true); - } - catch (IOException) - { - Log.Debug("IOException occurred during dispose()"); - } - - Thread.Sleep(0); - } - } - } -} diff --git a/Libplanet.Tests/Store/FileStoreTest.cs b/Libplanet.Tests/Store/FileStoreTest.cs deleted file mode 100644 index 1b55ce5019..0000000000 --- a/Libplanet.Tests/Store/FileStoreTest.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using Libplanet.Action; -using Libplanet.Blocks; -using Libplanet.Store; -using Libplanet.Tests.Common.Action; -using Libplanet.Tx; -using Xunit; - -namespace Libplanet.Tests.Store -{ - public class FileStoreTest : StoreTest, IDisposable - { - private readonly FileStoreFixture _fx; - private readonly FileStore _fileStore; - - public FileStoreTest() - { - _fx = new FileStoreFixture(); - Fx = _fx; - _fileStore = (FileStore)_fx.Store; - } - - [Fact] - public void ReturnTransactionPath() - { - Assert.Equal( - Path.Combine( - _fx.Path, - "tx", - "45a2", - "2187e2d8850bb357886958bc3e8560929ccc886958bc3e8560929ccc9ccc" - ), - _fileStore.GetTransactionPath(_fx.TxId1) - ); - } - - [Fact] - public void ReturnBlockPath() - { - Assert.Equal( - Path.Combine( - _fx.Path, - "blocks", - "45a2", - "2187e2d8850bb357886958bc3e8560929ccc886958bc3e8560929ccc9ccc" - ), - _fileStore.GetBlockPath(_fx.Hash1) - ); - } - - [Fact] - public void ReturnStagedTransactionPath() - { - Assert.Equal( - Path.Combine( - _fx.Path, - "stage", - "45a22187e2d8850bb357886958bc3e8560929ccc886958bc3e8560929ccc9ccc" - ), - _fileStore.GetStagedTransactionPath(_fx.TxId1) - ); - } - - [Fact] - public void ReturnStatesPath() - { - var hash = new HashDigest(new byte[] - { - 0x45, 0xa2, 0x21, 0x87, 0xe2, 0xd8, 0x85, 0x0b, 0xb3, 0x57, - 0x88, 0x69, 0x58, 0xbc, 0x3e, 0x85, 0x60, 0x92, 0x9c, 0xcc, - 0x88, 0x69, 0x58, 0xbc, 0x3e, 0x85, 0x60, 0x92, 0x9c, 0xcc, - 0x9c, 0xcc, - }); - Assert.Equal( - Path.Combine( - _fx.Path, - "states", - "45a2", - "2187e2d8850bb357886958bc3e8560929ccc886958bc3e8560929ccc9ccc" - ), - _fileStore.GetStatesPath(hash) - ); - } - - [Fact] - public void GetStateReferencePath() - { - string expected = Path.Combine( - _fx.Path, - "stateref", - Fx.StoreNamespace, - "45a2", - "2187e2D8850bb357886958bC3E8560929ccc"); - string actual = _fileStore.GetStateReferencePath( - Fx.StoreNamespace, - _fx.Address1); - - Assert.Equal(expected, actual); - } - - [Fact] - public void StoreStateReference() - { - Address address = address; - Block prevBlock = _fx.Block3; - - Transaction transaction = _fx.MakeTransaction( - new List(), - new HashSet
{ address }.ToImmutableHashSet()); - var txs = new[] { transaction }; - - var blocks = new List> - { - TestUtils.MineNext(prevBlock, txs), - }; - blocks.Add(TestUtils.MineNext(blocks[0], txs)); - - string path = _fileStore.GetStateReferencePath(Fx.StoreNamespace, address); - var stateReferenceFile = new FileInfo(path); - Assert.False(stateReferenceFile.Exists); - - foreach (Block block in blocks) - { - var updatedAddresses = new HashSet
{ address }; - _fx.Store.StoreStateReference( - Fx.StoreNamespace, updatedAddresses.ToImmutableHashSet(), block); - } - - stateReferenceFile = new FileInfo(path); - Assert.True(stateReferenceFile.Exists); - - using (Stream stream = stateReferenceFile.OpenRead()) - { - int hashSize = HashDigest.Size; - int blockInfoSize = hashSize + sizeof(long); - var buffer = new byte[blockInfoSize]; - - foreach (var block in blocks) - { - stream.Read(buffer, 0, blockInfoSize); - var blockHash = new HashDigest( - buffer.Take(hashSize).ToArray()); - var blockIndex = BitConverter.ToInt64(buffer, hashSize); - - Assert.Equal(block.Hash, blockHash); - Assert.Equal(block.Index, blockIndex); - } - } - } - - public void Dispose() - { - _fx.Dispose(); - } - } -} diff --git a/Libplanet.Tests/Store/LiteDBStoreFixture.cs b/Libplanet.Tests/Store/LiteDBStoreFixture.cs index 5f637d2846..a29306c5d2 100644 --- a/Libplanet.Tests/Store/LiteDBStoreFixture.cs +++ b/Libplanet.Tests/Store/LiteDBStoreFixture.cs @@ -16,7 +16,7 @@ public LiteDBStoreFixture() public string Path { get; } - public void Dispose() + public override void Dispose() { (Store as LiteDBStore)?.Dispose(); File.Delete(Path); diff --git a/Libplanet.Tests/Store/StoreExtensionTest.cs b/Libplanet.Tests/Store/StoreExtensionTest.cs index 0ed24080d1..fe8ab81ef8 100644 --- a/Libplanet.Tests/Store/StoreExtensionTest.cs +++ b/Libplanet.Tests/Store/StoreExtensionTest.cs @@ -20,7 +20,6 @@ public static IEnumerable StoreFixtures { get { - yield return new object[] { new FileStoreFixture() }; yield return new object[] { new LiteDBStoreFixture() }; } } diff --git a/Libplanet.Tests/Store/StoreFixture.cs b/Libplanet.Tests/Store/StoreFixture.cs index e9ecc8e2b3..5e611e867e 100644 --- a/Libplanet.Tests/Store/StoreFixture.cs +++ b/Libplanet.Tests/Store/StoreFixture.cs @@ -10,7 +10,7 @@ namespace Libplanet.Tests.Store { - public abstract class StoreFixture + public abstract class StoreFixture : IDisposable { public StoreFixture() { @@ -107,6 +107,8 @@ public StoreFixture() public IStore Store { get; set; } + public abstract void Dispose(); + public Transaction MakeTransaction( IEnumerable actions = null, ImmutableHashSet
updatedAddresses = null, diff --git a/Libplanet.Tests/Store/StoreTrackerTest.cs b/Libplanet.Tests/Store/StoreTrackerTest.cs index a6b6b73bb3..24f3f253a2 100644 --- a/Libplanet.Tests/Store/StoreTrackerTest.cs +++ b/Libplanet.Tests/Store/StoreTrackerTest.cs @@ -5,12 +5,12 @@ namespace Libplanet.Tests.Store { public sealed class StoreTrackerTest { - private readonly FileStoreFixture _fx; + private readonly StoreFixture _fx; private readonly StoreTracker _tracker; public StoreTrackerTest() { - _fx = new FileStoreFixture(); + _fx = new LiteDBStoreFixture(); _tracker = new StoreTracker(_fx.Store); } diff --git a/Libplanet.Tests/Store/TransactionSetTest.cs b/Libplanet.Tests/Store/TransactionSetTest.cs index f7d1172182..eb5246c690 100644 --- a/Libplanet.Tests/Store/TransactionSetTest.cs +++ b/Libplanet.Tests/Store/TransactionSetTest.cs @@ -9,12 +9,12 @@ namespace Libplanet.Tests.Store { public class TransactionSetTest : IDisposable { - private readonly FileStoreFixture _fx; + private readonly StoreFixture _fx; private readonly TransactionSet _set; public TransactionSetTest() { - _fx = new FileStoreFixture(); + _fx = new LiteDBStoreFixture(); _set = new TransactionSet(_fx.Store); } diff --git a/Libplanet/Store/FileStore.cs b/Libplanet/Store/FileStore.cs deleted file mode 100644 index df4983448d..0000000000 --- a/Libplanet/Store/FileStore.cs +++ /dev/null @@ -1,1031 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Formatters.Binary; -using System.Security.Cryptography; -using System.Text.RegularExpressions; -using System.Threading; -using Libplanet.Action; -using Libplanet.Blocks; -using Libplanet.Serialization; -using Libplanet.Tx; - -namespace Libplanet.Store -{ - public class FileStore : BaseStore - { - private const string _transactionsDir = "tx"; - private const string _blocksDir = "blocks"; - private const string _stagedTransactionsDir = "stage"; - private const string _statesDir = "states"; - private const string _indicesDir = "indices"; - private const string _stateReferenceDir = "stateref"; - private const string _txNonceDir = "txnonce"; - - private static readonly string[] BuiltinDirs = - { - _blocksDir, - _transactionsDir, - _stagedTransactionsDir, - _statesDir, - _indicesDir, - _stateReferenceDir, - _txNonceDir, - }; - - private readonly string _path; - - private readonly ReaderWriterLockSlim _txLock; - - public FileStore(string path) - { - if (File.Exists(path) && !Directory.Exists(path)) - { - throw new ArgumentException( - $"expected a directory path or path " + - $"that does not exist yet: {path}" - ); - } - - _path = path; - - foreach (string dir in BuiltinDirs) - { - Directory.CreateDirectory(Path.Combine(_path, dir)); - } - - _txLock = new ReaderWriterLockSlim(); - } - - public string GetTransactionPath(TxId txid) - { - var txidHex = txid.ToString(); - return Path.Combine( - GetTransactionPath(), - txidHex.Substring(0, 4), - txidHex.Substring(4)); - } - - public string GetTransactionPath() - { - return Path.Combine( - _path, - _transactionsDir); - } - - public string GetBlockPath(HashDigest blockHash) - { - var keyHex = blockHash.ToString(); - return Path.Combine( - GetBlockPath(), - keyHex.Substring(0, 4), - keyHex.Substring(4)); - } - - public string GetBlockPath() - { - return Path.Combine( - _path, - _blocksDir); - } - - public string GetStagedTransactionPath(TxId txid) - { - return Path.Combine( - GetStagedTransactionPath(), - txid.ToString() - ); - } - - public string GetStagedTransactionPath() - { - return Path.Combine( - _path, - _stagedTransactionsDir); - } - - public string GetStatesPath(HashDigest key) - { - var keyHex = key.ToString(); - return Path.Combine( - GetStatesPath(), - keyHex.Substring(0, 4), - keyHex.Substring(4)); - } - - public string GetStatesPath() - { - return Path.Combine( - _path, - _statesDir); - } - - public string GetIndexPath(string @namespace) - { - return Path.Combine( - _path, - _indicesDir, - @namespace - ); - } - - public string GetCanonicalNamespacePath() - { - return Path.Combine(_path, "canon.txt"); - } - - public string GetStateReferencePath(string @namespace) - { - return Path.Combine( - _path, - _stateReferenceDir, - @namespace); - } - - public string GetStateReferencePath( - string @namespace, - Address address) - { - string addressHex = address.ToHex(); - return Path.Combine( - GetStateReferencePath(@namespace), - addressHex.Substring(0, 4), - addressHex.Substring(4)); - } - - public string GetTxNoncePath(string @namespace) - { - return Path.Combine( - _path, - _txNonceDir, - @namespace); - } - - public string GetTxNoncePath( - string @namespace, - Address address) - { - string addressHex = address.ToHex(); - return Path.Combine( - GetTxNoncePath(@namespace), - addressHex.Substring(0, 4), - addressHex.Substring(4)); - } - - /// - public override IEnumerable ListNamespaces() - { - var indicesPath = - new DirectoryInfo(Path.Combine(_path, _indicesDir)); - foreach (FileInfo p in indicesPath.EnumerateFiles()) - { - yield return p.Name; - } - } - - /// - public override string GetCanonicalNamespace() - { - var file = new FileInfo(GetCanonicalNamespacePath()); - try - { - using (StreamReader reader = file.OpenText()) - { - return reader.ReadToEnd(); - } - } - catch (FileNotFoundException) - { - return null; - } - } - - /// - public override void SetCanonicalNamespace(string @namespace) - { - if (@namespace is null) - { - throw new ArgumentNullException(nameof(@namespace)); - } - - var file = new FileInfo(GetCanonicalNamespacePath()); - using (StreamWriter writer = file.CreateText()) - { - writer.Write(@namespace); - } - } - - /// - public override void DeleteNamespace(string @namespace) - { - var indexFile = new FileInfo(GetIndexPath(@namespace)); - var txNonceDir = new DirectoryInfo(GetTxNoncePath(@namespace)); - var stateRefsDir = new DirectoryInfo(GetStateReferencePath(@namespace)); - - if (indexFile.Exists) - { - indexFile.Delete(); - } - - if (txNonceDir.Exists) - { - txNonceDir.Delete(true); - } - - if (stateRefsDir.Exists) - { - stateRefsDir.Delete(true); - } - } - - public override long AppendIndex( - string @namespace, - HashDigest hash - ) - { - var indexFile = new FileInfo(GetIndexPath(@namespace)); - - using (Stream stream = indexFile.Open( - FileMode.Append, FileAccess.Write)) - { - var offset = stream.Position; - stream.Write(hash.ToByteArray(), 0, hash.ToByteArray().Length); - return offset / HashDigest.Size; - } - } - - public override bool DeleteIndex( - string @namespace, - HashDigest hash - ) - { - bool found = false; - using (var writer = new MemoryStream()) - { - foreach (var idx in IterateIndex(@namespace, 0, int.MaxValue)) - { - if (!found && idx.Equals(hash)) - { - found = true; - } - else - { - writer.Write( - idx.ToByteArray(), 0, HashDigest.Size); - } - } - - // FIXME We can't this if the index is too large to buffer... - File.WriteAllBytes(GetIndexPath(@namespace), writer.ToArray()); - } - - return found; - } - - public override long CountIndex(string @namespace) - { - var indexFile = new FileInfo(GetIndexPath(@namespace)); - if (indexFile.Exists) - { - var indexSize = indexFile.Length; - if (indexSize % HashDigest.Size != 0) - { - throw new FileLoadException( - $"Index size {indexSize} should be a multiple " + - $"of HashSize {HashDigest.Size}" - ); - } - - return indexSize / HashDigest.Size; - } - - return 0; - } - - /// - public override IEnumerable
ListAddresses(string @namespace) - { - string dirPath = GetStateReferencePath(@namespace); - var dir = new DirectoryInfo(dirPath); - if (!dir.Exists) - { - yield break; - } - - foreach (DirectoryInfo upper in dir.EnumerateDirectories()) - { - foreach (FileInfo lower in upper.EnumerateFiles()) - { - string hex = upper.Name + lower.Name; - Address address; - try - { - address = new Address(hex); - } - catch (ArgumentException) - { - continue; - } - - yield return address; - } - } - } - - public override bool DeleteBlock(HashDigest blockHash) - { - var blockFile = new FileInfo(GetBlockPath(blockHash)); - if (blockFile.Exists) - { - blockFile.Delete(); - return true; - } - - return false; - } - - public override bool DeleteTransaction(TxId txid) - { - var txFile = new FileInfo(GetTransactionPath(txid)); - _txLock.EnterUpgradeableReadLock(); - try - { - if (!txFile.Exists) - { - return false; - } - - _txLock.EnterWriteLock(); - try - { - txFile.Delete(); - } - finally - { - _txLock.ExitWriteLock(); - } - } - finally - { - _txLock.ExitUpgradeableReadLock(); - } - - return true; - } - - public override Transaction GetTransaction(TxId txid) - { - var txFile = new FileInfo(GetTransactionPath(txid)); - if (!txFile.Exists) - { - return null; - } - - using (Stream stream = txFile.OpenRead()) - { - var formatter = new BencodexFormatter>(); - return (Transaction)formatter.Deserialize(stream); - } - } - - public override HashDigest? IndexBlockHash( - string @namespace, - long index - ) - { - if (index < 0) - { - index += CountIndex(@namespace); - - if (index < 0) - { - return null; - } - } - else if (CountIndex(@namespace) < index) - { - return null; - } - - if (!File.Exists(GetIndexPath(@namespace))) - { - return null; - } - - using (Stream stream = IndexFileStream(@namespace)) - { - var blockHash = new byte[HashDigest.Size]; - stream.Seek( - HashDigest.Size * (int)index, - SeekOrigin.Begin - ); - var bytesRead = stream.Read( - blockHash, - 0, - HashDigest.Size - ); - Trace.Assert(bytesRead == HashDigest.Size); - - return new HashDigest(blockHash); - } - } - - public override IEnumerable> IterateBlockHashes() - { - var prefixRegex = new Regex( - @"^[a-f0-9]{4}$", - RegexOptions.IgnoreCase - ); - var restRegex = new Regex( - @"^[a-f0-9]{60}$", - RegexOptions.IgnoreCase - ); - var rootDir = new DirectoryInfo(GetBlockPath()); - var prefixes = rootDir.EnumerateDirectories(); - foreach (DirectoryInfo prefix in prefixes) - { - if (!prefixRegex.IsMatch(prefix.Name)) - { - continue; - } - - foreach (FileInfo rest in prefix.EnumerateFiles()) - { - if (!restRegex.IsMatch(rest.Name)) - { - continue; - } - - yield return new HashDigest( - ByteUtil.ParseHex(prefix.Name + rest.Name) - ); - } - } - } - - public override IEnumerable> IterateIndex( - string @namespace, - int offset, - int? limit) - { - if (!File.Exists(GetIndexPath(@namespace))) - { - yield break; - } - - using (Stream stream = IndexFileStream(@namespace)) - { - int skipSize = HashDigest.Size * offset; - - if (skipSize > stream.Length) - { - yield break; - } - - stream.Seek(skipSize, SeekOrigin.Begin); - - while (true) - { - if (limit < 1) - { - yield break; - } - - var blockHash = new byte[HashDigest.Size]; - var bytesRead = stream.Read( - blockHash, - 0, - HashDigest.Size - ); - - if (bytesRead == 0) - { - yield break; - } - - yield return new HashDigest(blockHash); - - if (!(limit is null)) - { - limit -= 1; - } - } - } - } - - /// - public override IEnumerable IterateStagedTransactionIds(bool toBroadcast) - { - string stagedTxPath = GetStagedTransactionPath(); - var stagingDirectory = new DirectoryInfo(stagedTxPath); - - if (!stagingDirectory.Exists) - { - yield break; - } - - var dir = new DirectoryInfo(stagedTxPath); - var buffer = new byte[sizeof(bool)]; - - foreach (var staged in dir.EnumerateFiles()) - { - bool broadcast = false; - - if (toBroadcast) - { - using (Stream stream = staged.OpenRead()) - { - stream.Read(buffer, 0, buffer.Length); - broadcast = BitConverter.ToBoolean(buffer, 0); - } - } - - if (!toBroadcast || broadcast) - { - yield return new TxId(ByteUtil.ParseHex(staged.Name)); - } - } - } - - public override IEnumerable IterateTransactionIds() - { - // TODO: refactor - var prefixRegex = new Regex( - @"^[a-f0-9]{4}$", - RegexOptions.IgnoreCase - ); - var restRegex = new Regex( - @"^[a-f0-9]{60}$", - RegexOptions.IgnoreCase - ); - var rootDir = new DirectoryInfo(GetTransactionPath()); - _txLock.EnterReadLock(); - try - { - foreach (DirectoryInfo prefix in rootDir.EnumerateDirectories()) - { - if (!prefixRegex.IsMatch(prefix.Name)) - { - continue; - } - - foreach (FileInfo rest in prefix.EnumerateFiles()) - { - if (!restRegex.IsMatch(rest.Name)) - { - continue; - } - - yield return new TxId(ByteUtil.ParseHex(prefix.Name + rest.Name)); - } - } - } - finally - { - _txLock.ExitReadLock(); - } - } - - /// - public override void PutBlock(Block block) - { - foreach (var tx in block.Transactions) - { - PutTransaction(tx); - } - - var blockFile = new FileInfo(GetBlockPath(block.Hash)); - blockFile.Directory.Create(); - using (Stream stream = blockFile.Open( - FileMode.OpenOrCreate, FileAccess.Write)) - { - byte[] blockBytes = block.ToBencodex( - true, - transactionData: false - ); - stream.Write(blockBytes, 0, blockBytes.Length); - } - } - - public override void PutTransaction(Transaction tx) - { - byte[] txBytes = tx.ToBencodex(true); - var txFile = new FileInfo(GetTransactionPath(tx.Id)); - _txLock.EnterUpgradeableReadLock(); - try - { - txFile.Directory.Create(); - _txLock.EnterWriteLock(); - try - { - using (Stream stream = txFile.Open(FileMode.OpenOrCreate, FileAccess.Write)) - { - stream.Write(txBytes, 0, txBytes.Length); - } - } - finally - { - _txLock.ExitWriteLock(); - } - } - finally - { - _txLock.ExitUpgradeableReadLock(); - } - } - - /// - public override void StageTransactionIds(IDictionary txids) - { - foreach (var kv in txids) - { - TxId txId = kv.Key; - bool broadcast = kv.Value; - string stagedPath = GetStagedTransactionPath(txId); - var stagedFile = new FileInfo(stagedPath); - stagedFile.Directory.Create(); - using (Stream stream = stagedFile.OpenWrite()) - { - stream.Write(BitConverter.GetBytes(broadcast), 0, sizeof(bool)); - } - } - } - - public override void UnstageTransactionIds(ISet txids) - { - foreach (TxId txid in txids) - { - File.Delete(GetStagedTransactionPath(txid)); - } - } - - public override AddressStateMap GetBlockStates( - HashDigest blockHash - ) - { - var statesFile = new FileInfo(GetStatesPath(blockHash)); - - if (!statesFile.Exists) - { - return null; - } - - using (Stream stream = statesFile.OpenRead()) - { - var formatter = new BinaryFormatter(); - return (AddressStateMap)formatter.Deserialize(stream); - } - } - - public override void SetBlockStates( - HashDigest blockHash, - AddressStateMap states - ) - { - var statesFile = new FileInfo(GetStatesPath(blockHash)); - if (!statesFile.Directory.Exists) - { - statesFile.Directory.Create(); - } - - using (Stream stream = statesFile.OpenWrite()) - { - var formatter = new BinaryFormatter(); - formatter.Serialize(stream, states); - } - } - - /// - public override IEnumerable, long>> IterateStateReferences( - string @namespace, Address address) - { - var addrFile = new FileInfo( - GetStateReferencePath(@namespace, address)); - - if (!addrFile.Exists || addrFile.Length == 0) - { - yield break; - } - - int stateReferenceSize = HashDigest.Size + sizeof(long); - if (addrFile.Length % stateReferenceSize != 0) - { - throw new FileLoadException( - $"State references file's size ({addrFile.Length}) should be multiple of " + - $"state reference entry size {stateReferenceSize})." - ); - } - - using (Stream stream = addrFile.OpenRead()) - { - foreach (var (hashBytes, index) in GetStateReferences(stream)) - { - yield return Tuple.Create( - new HashDigest(hashBytes), - index - ); - } - } - } - - /// - public override void StoreStateReference( - string @namespace, - IImmutableSet
addresses, - Block block) - { - HashDigest blockHash = block.Hash; - long blockIndex = block.Index; - int hashSize = HashDigest.Size; - - foreach (Address address in addresses) - { - var stateReferenceFile = new FileInfo( - GetStateReferencePath(@namespace, address)); - - if (!stateReferenceFile.Directory.Exists) - { - stateReferenceFile.Directory.Create(); - } - - using (Stream stream = stateReferenceFile.Open( - FileMode.Append, FileAccess.Write)) - { - stream.Write(blockHash.ToByteArray(), 0, hashSize); - stream.Write( - BitConverter.GetBytes(blockIndex), 0, sizeof(long)); - } - } - } - - /// - public override void ForkStateReferences( - string sourceNamespace, - string destinationNamespace, - Block branchPoint, - IImmutableSet
addressesToStrip) - { - ForkIndexedStack( - sourceNamespace, - destinationNamespace, - branchPoint.Index, - addressesToStrip, - GetStateReferencePath, - GetStateReferencePath, - s => GetStateReferences(s).Select(t => t.Item2)); - } - - /// - public override IEnumerable> ListTxNonces(string @namespace) - { - var dir = new DirectoryInfo(GetTxNoncePath(@namespace)); - if (!dir.Exists) - { - yield break; - } - - foreach (DirectoryInfo upper in dir.GetDirectories()) - { - if (upper.Name.Length != 4) - { - continue; - } - - foreach (FileInfo lower in upper.GetFiles()) - { - Address address; - try - { - address = new Address(upper.Name + lower.Name); - } - catch (ArgumentException) - { - continue; - } - - using (Stream stream = lower.OpenRead()) - { - var buffer = new byte[sizeof(long)]; - stream.Read(buffer, 0, buffer.Length); - long txNonce = BitConverter.ToInt64(buffer, 0); - if (txNonce > 0) - { - yield return new KeyValuePair(address, txNonce); - } - } - } - } - } - - /// - public override long GetTxNonce(string @namespace, Address address) - { - var nonceFile = new FileInfo( - GetTxNoncePath(@namespace, address)); - - if (!nonceFile.Exists || nonceFile.Length == 0) - { - return 0; - } - - using (Stream stream = nonceFile.OpenRead()) - { - var buffer = new byte[sizeof(long)]; - stream.Read(buffer, 0, buffer.Length); - return BitConverter.ToInt64(buffer, 0); - } - } - - /// - public override void IncreaseTxNonce(string @namespace, Address signer, long delta = 1) - { - long nextNonce = GetTxNonce(@namespace, signer) + delta; - var nonceFile = new FileInfo(GetTxNoncePath(@namespace, signer)); - - if (!nonceFile.Directory.Exists) - { - nonceFile.Directory.Create(); - } - - using (Stream stream = nonceFile.OpenWrite()) - { - byte[] nonceBytes = BitConverter.GetBytes(nextNonce); - stream.Write(nonceBytes, 0, nonceBytes.Length); - } - } - - internal override RawBlock? GetRawBlock(HashDigest blockHash) - { - var blockFile = new FileInfo(GetBlockPath(blockHash)); - if (!blockFile.Exists) - { - return null; - } - - using (Stream stream = blockFile.OpenRead()) - { - var formatter = new BencodexFormatter(); - return (RawBlock)formatter.Deserialize(stream); - } - } - - private void ForkIndexedStack( - string sourceNamespace, - string destinationNamespace, - long branchPointIndex, - IImmutableSet
addressesToStrip, - Func getPath, - Func getAddressPath, - Func> getIndexedStack) - { - string sourceDir = getPath(sourceNamespace); - string targetDir = getPath(destinationNamespace); - bool copied = CopyDirectory(sourceDir, targetDir); - - if (!copied && addressesToStrip.Any()) - { - throw new NamespaceNotFoundException( - sourceNamespace, - "The source namespace to be forked does not exist."); - } - - foreach (Address address in addressesToStrip) - { - StripIndexedStack( - getAddressPath(destinationNamespace, address), - branchPointIndex, - getIndexedStack); - } - } - - private void StripIndexedStack( - string path, - long stripAfter, - Func> getIndexedStack) - { - var file = new FileInfo(path); - - if (!file.Exists) - { - return; - } - - using (Stream stream = file.Open( - FileMode.Open, FileAccess.ReadWrite)) - { - foreach (long index in getIndexedStack(stream)) - { - if (index <= stripAfter) - { - break; - } - } - - stream.SetLength(stream.Position); - } - } - - private IEnumerable<(byte[], long)> GetStateReferences(Stream stream) - { - int hashSize = HashDigest.Size; - int blockInfoSize = hashSize + sizeof(long); - var buffer = new byte[blockInfoSize]; - - long position = stream.Seek(0, SeekOrigin.End); - - for (var i = 1; position - buffer.Length >= 0; i++) - { - 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); - - yield return (hashBytes, index); - } - - stream.Seek(position, SeekOrigin.Begin); - } - - private IEnumerable<(byte[], long, long)> GetTxNonces(Stream stream) - { - int hashSize = HashDigest.Size; - int blockIndexSize = sizeof(long); - int nonceSize = sizeof(long); - int nonceEntrySize = hashSize + blockIndexSize + nonceSize; - var buffer = new byte[nonceEntrySize]; - - long position = stream.Seek(0, SeekOrigin.End); - - for (var i = 1; position - buffer.Length >= 0; i++) - { - 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); - long nonce = BitConverter.ToInt64( - buffer, hashSize + blockIndexSize); - - yield return (hashBytes, index, nonce); - } - - stream.Seek(position, SeekOrigin.Begin); - } - - private FileStream IndexFileStream(string @namespace) - { - var stream = new FileStream( - GetIndexPath(@namespace), - FileMode.Open, - FileAccess.Read - ); - if (stream.Length % HashDigest.Size != 0) - { - throw new FileLoadException( - $"Index file size {stream.Length} should be a multiple " + - $"of HashSize {HashDigest.Size}" - ); - } - - return stream; - } - - private bool CopyDirectory( - string sourceDirName, - string destDirName) - { - var dir = new DirectoryInfo(sourceDirName); - - if (!dir.Exists) - { - return false; - } - - if (!Directory.Exists(destDirName)) - { - Directory.CreateDirectory(destDirName); - } - - FileInfo[] files = dir.GetFiles(); - foreach (FileInfo file in files) - { - string temppath = Path.Combine(destDirName, file.Name); - file.CopyTo(temppath, false); - } - - DirectoryInfo[] dirs = dir.GetDirectories(); - foreach (DirectoryInfo subdir in dirs) - { - string temppath = Path.Combine(destDirName, subdir.Name); - CopyDirectory(subdir.FullName, temppath); - } - - return true; - } - } -}