public void TestCanSpend_Spent() { // prepare utxo storage var chain = Chain.CreateForGenesisBlock(new FakeHeaders().GenesisChained()); var unspentTransactions = ImmutableSortedDictionary.CreateBuilder<UInt256, UnspentTx>(); // prepare spent output var txHash = new UInt256(0); unspentTransactions.Add(txHash, new UnspentTx(txHash, blockIndex: 0, txIndex: 0, txVersion: 0, isCoinbase: false, length: 1, state: OutputState.Spent)); // prepare utxo var memoryStorage = new MemoryStorageManager(unspentTransactions: unspentTransactions.ToImmutable()); var chainStateStorage = memoryStorage.OpenChainStateCursor().Item; chainStateStorage.BeginTransaction(); chainStateStorage.ChainTip = chain.GenesisBlock; chainStateStorage.CommitTransaction(); var utxo = new ChainState(chain, memoryStorage); // prepare output reference var prevTxOutput = new TxOutputKey(txHash, txOutputIndex: 0); // check if output can be spent var canSpend = utxo.CanSpend(prevTxOutput); // verify output cannot be spent Assert.IsFalse(canSpend); }
public void TestDoubleSpend() { // prepare block var fakeHeaders = new FakeHeaders(); var chainedHeader0 = fakeHeaders.GenesisChained(); var chainedHeader1 = fakeHeaders.NextChained(); var chainedHeader2 = fakeHeaders.NextChained(); var chain = Chain.CreateForGenesisBlock(chainedHeader0).ToBuilder(); var emptyCoinbaseTx0 = new BlockTx(0, new Transaction(0, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0)); var emptyCoinbaseTx1 = new BlockTx(0, new Transaction(1, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0)); // initialize memory utxo builder storage var memoryStorage = new MemoryStorageManager(); var memoryChainStateCursor = memoryStorage.OpenChainStateCursor().Item; memoryChainStateCursor.BeginTransaction(); // initialize utxo builder var utxoBuilder = new UtxoBuilder(); // prepare an unspent transaction var txHash = new UInt256(100); var unspentTx = new UnspentTx(txHash, chainedHeader1.Height, 0, 1, OutputState.Unspent); // add the unspent transaction memoryChainStateCursor.TryAddUnspentTx(unspentTx); // create an input to spend the unspent transaction var input = new TxInput(new TxOutputKey(txHash, txOutputIndex: 0), ImmutableArray.Create<byte>(), 0); var tx = new BlockTx(1, new Transaction(0, ImmutableArray.Create(input), ImmutableArray.Create<TxOutput>(), 0)); // spend the input chain.AddBlock(chainedHeader1); utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx0, tx }.ToBufferBlock()).ToEnumerable().ToList(); // verify utxo storage UnspentTx actualUnspentTx; Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTx(txHash, out actualUnspentTx)); Assert.IsTrue(actualUnspentTx.IsFullySpent); // attempt to spend the input again, validation exception should be thrown chain.AddBlock(chainedHeader2); AssertMethods.AssertAggregateThrows<ValidationException>(() => utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx1, tx }.ToBufferBlock()).ToEnumerable().ToList()); }
public void TestCanSpend_Missing() { // prepare utxo var chain = Chain.CreateForGenesisBlock(new FakeHeaders().GenesisChained()); var memoryStorage = new MemoryStorageManager(); var chainStateStorage = memoryStorage.OpenChainStateCursor().Item; chainStateStorage.BeginTransaction(); chainStateStorage.ChainTip = chain.GenesisBlock; chainStateStorage.CommitTransaction(); var utxo = new ChainState(chain, memoryStorage); // prepare output reference var prevTxOutput = new TxOutputKey(txHash: UInt256.Zero, txOutputIndex: 0); // check if output can be spent var canSpend = utxo.CanSpend(prevTxOutput); // verify output cannot be spent Assert.IsFalse(canSpend); }
public LevelDbStorageManager(string baseDirectory, ulong? blocksCacheSize = null, ulong? blocksWriteCacheSize = null, ulong? blockTxesCacheSize = null, ulong? blockTxesWriteCacheSize = null, ulong? chainStateCacheSize = null, ulong? chainStateWriteCacheSize = null, string[] blockTxesStorageLocations = null) { this.baseDirectory = baseDirectory; this.blockTxesStorageLocations = blockTxesStorageLocations; blockStorage = new Lazy<LevelDb.LevelDbBlockStorage>(() => new LevelDbBlockStorage(this.baseDirectory, blocksCacheSize, blocksWriteCacheSize)); blockTxesStorage = new Lazy<IBlockTxesStorage>(() => { if (blockTxesStorageLocations == null) return new LevelDbBlockTxesStorage(this.baseDirectory, blockTxesCacheSize, blockTxesWriteCacheSize); else return new SplitBlockTxesStorage(blockTxesStorageLocations, path => new LevelDbBlockTxesStorage(path, blockTxesCacheSize, blockTxesWriteCacheSize)); }); chainStateManager = new Lazy<LevelDbChainStateManager>(() => new LevelDbChainStateManager(this.baseDirectory, chainStateCacheSize, chainStateWriteCacheSize)); this.memoryStorageManager = new Lazy<MemoryStorageManager>(() => { // create memory storage with the unconfirmed txes chain tip already in sync with chain state ChainedHeader chainTip; using (var handle = OpenChainStateCursor()) { handle.Item.BeginTransaction(readOnly: true); chainTip = handle.Item.ChainTip; } var memoryStorageManager = new MemoryStorageManager(); using (var handle = memoryStorageManager.OpenUnconfirmedTxesCursor()) { handle.Item.BeginTransaction(); handle.Item.ChainTip = chainTip; handle.Item.CommitTransaction(); } return memoryStorageManager; }); }
public EsentStorageManager(string baseDirectory, string[] blockTxesStorageLocations = null) { this.baseDirectory = baseDirectory; this.blockTxesStorageLocations = blockTxesStorageLocations; this.blockStorage = new Lazy<Esent.EsentBlockStorage>(() => new EsentBlockStorage(this.baseDirectory)); this.blockTxesStorage = new Lazy<IBlockTxesStorage>(() => { if (blockTxesStorageLocations == null) return new EsentBlockTxesStorage(this.baseDirectory); else return new SplitBlockTxesStorage(blockTxesStorageLocations, path => new EsentBlockTxesStorage(path)); }); this.chainStateManager = new Lazy<EsentChainStateManager>(() => new EsentChainStateManager(this.baseDirectory)); this.memoryStorageManager = new Lazy<MemoryStorageManager>(() => { // create memory storage with the unconfirmed txes chain tip already in sync with chain state ChainedHeader chainTip; using (var handle = OpenChainStateCursor()) { handle.Item.BeginTransaction(readOnly: true); chainTip = handle.Item.ChainTip; } var memoryStorageManager = new MemoryStorageManager(); using (var handle = memoryStorageManager.OpenUnconfirmedTxesCursor()) { handle.Item.BeginTransaction(); handle.Item.ChainTip = chainTip; handle.Item.CommitTransaction(); } return memoryStorageManager; }); }
private CoreDaemon CreateExampleDaemon(out BlockProvider embeddedBlocks, out IStorageManager storageManager, int? maxHeight = null) { // retrieve first 10,000 testnet3 blocks embeddedBlocks = new BlockProvider("BitSharp.Examples.Blocks.TestNet3.zip"); // initialize in-memory storage storageManager = new MemoryStorageManager(); // intialize testnet3 rules (ignore script errors, script engine is not and is not intended to be complete) var rules = new Testnet3Rules { IgnoreScriptErrors = true }; // initialize & start core daemon var coreDaemon = new CoreDaemon(rules, storageManager) { MaxHeight = maxHeight, IsStarted = true }; // add embedded blocks coreDaemon.CoreStorage.AddBlocks(embeddedBlocks.ReadBlocks()); // wait for core daemon to finish processing any available data coreDaemon.WaitForUpdate(); return coreDaemon; }
public void TestPruneAllData() { var logger = LogManager.CreateNullLogger(); // create genesis block var genesisblock = CreateFakeBlock(1); var genesisHeader = new ChainedHeader(genesisblock.Header, height: 0, totalWork: genesisblock.Header.CalculateWork(), dateSeen: DateTimeOffset.Now); // create a block var txCount = 100; var block = CreateFakeBlock(txCount, genesisblock.Hash); var chainedHeader = ChainedHeader.CreateFromPrev(genesisHeader, block.Header, dateSeen: DateTimeOffset.Now); // create a long chain based off the block, to account for pruning buffer var fakeHeaders = new FakeHeaders(new[] { genesisHeader, chainedHeader }); var chain = new ChainBuilder(Enumerable.Concat(new[] { genesisHeader, chainedHeader }, Enumerable.Range(0, 2000).Select(x => fakeHeaders.NextChained()))).ToImmutable(); // mock core daemon to return the chain var coreDaemon = new Mock<ICoreDaemon>(); coreDaemon.Setup(x => x.CurrentChain).Returns(chain); // create memory storage with the block var storageManager = new MemoryStorageManager(); storageManager.BlockTxesStorage.TryAddBlockTransactions(block.Hash, block.BlockTxes); // initialize the pruning worker var workerConfig = new WorkerConfig(initialNotify: false, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.MaxValue); using (var pruningWorker = new PruningWorker(workerConfig, coreDaemon.Object, storageManager, null)) // get a chain state cursor using (var handle = storageManager.OpenChainStateCursor()) { var chainStateCursor = handle.Item; // set the pruning worker to prune all data pruningWorker.Mode = PruningMode.TxIndex | PruningMode.BlockSpentIndex | PruningMode.BlockTxesPreserveMerkle; // wire event to wait for work var workFinishedEvent = new AutoResetEvent(false); pruningWorker.OnWorkFinished += () => workFinishedEvent.Set(); // wire event to track exceptions Exception workException = null; pruningWorker.OnWorkError += e => workException = e; // start the worker pruningWorker.Start(); // pick a random pruning order var random = new Random(); var pruneOrderSource = Enumerable.Range(0, txCount).ToList(); var pruneOrder = new List<int>(txCount); while (pruneOrderSource.Count > 0) { var randomIndex = random.Next(pruneOrderSource.Count); pruneOrder.Add(pruneOrderSource[randomIndex]); pruneOrderSource.RemoveAt(randomIndex); } // add an unspent tx for each transaction to storage var unspentTxes = new UnspentTx[block.Transactions.Length]; chainStateCursor.BeginTransaction(); for (var txIndex = 0; txIndex < block.Transactions.Length; txIndex++) { var tx = block.Transactions[txIndex]; var unspentTx = new UnspentTx(tx.Hash, blockIndex: 1, txIndex: txIndex, txVersion: tx.Version, isCoinbase: txIndex == 0, outputStates: new OutputStates(1, OutputState.Spent)); unspentTxes[txIndex] = unspentTx; chainStateCursor.TryAddUnspentTx(unspentTx); } chainStateCursor.CommitTransaction(); // create a memory pruning cursor to verify expected pruning results var pruneCursor = new MemoryMerkleTreePruningCursor<BlockTxNode>(block.BlockTxes.Select(x => (BlockTxNode)x)); // prune each transaction in random order var pruneHeight = 0; foreach (var pruneTxIndex in pruneOrder) { // create a spent tx to prune the transaction var pruneTx = block.Transactions[pruneTxIndex]; var spentTxes = BlockSpentTxes.CreateRange(new[] { unspentTxes[pruneTxIndex].ToSpentTx() }); // store the spent txes for the current pruning block pruneHeight++; chainStateCursor.BeginTransaction(); Assert.IsTrue(chainStateCursor.TryAddBlockSpentTxes(pruneHeight, spentTxes)); pruningWorker.PrunableHeight = pruneHeight; // verify unspent tx is present before pruning Assert.IsTrue(chainStateCursor.ContainsUnspentTx(pruneTx.Hash)); chainStateCursor.CommitTransaction(); // notify the pruning worker and wait pruningWorker.NotifyWork(); workFinishedEvent.WaitOne(); // verify unspent tx is removed after pruning chainStateCursor.BeginTransaction(); Assert.IsFalse(chainStateCursor.ContainsUnspentTx(pruneTx.Hash)); // verify unspent tx outputs are removed after pruning for (var outputIndex = 0; outputIndex < pruneTx.Outputs.Length; outputIndex++) Assert.IsFalse(chainStateCursor.ContainsUnspentTxOutput(new TxOutputKey(pruneTx.Hash, (uint)outputIndex))); // verify the spent txes were removed Assert.IsFalse(chainStateCursor.ContainsBlockSpentTxes(pruneHeight)); chainStateCursor.RollbackTransaction(); // prune to determine expected results MerkleTree.PruneNode(pruneCursor, pruneTxIndex); var expectedPrunedTxes = pruneCursor.ReadNodes().ToList(); // retrieve the actual transaction after pruning IEnumerator<BlockTxNode> actualPrunedTxNodes; Assert.IsTrue(storageManager.BlockTxesStorage.TryReadBlockTxNodes(block.Hash, out actualPrunedTxNodes)); // verify the actual pruned transactions match the expected results CollectionAssert.AreEqual(expectedPrunedTxes, actualPrunedTxNodes.UsingAsEnumerable().ToList()); } // verify all unspent txes were removed chainStateCursor.BeginTransaction(); Assert.AreEqual(0, chainStateCursor.ReadUnspentTransactions().Count()); chainStateCursor.CommitTransaction(); // verify final block with all transactions pruned IEnumerator<BlockTxNode> finalPrunedTxNodes; Assert.IsTrue(storageManager.BlockTxesStorage.TryReadBlockTxNodes(block.Hash, out finalPrunedTxNodes)); var finalPrunedTxesList = finalPrunedTxNodes.UsingAsEnumerable().ToList(); Assert.AreEqual(1, finalPrunedTxesList.Count); Assert.AreEqual(block.Header.MerkleRoot, finalPrunedTxesList.Single().Hash); // verify no work exceptions occurred Assert.IsNull(workException); } }
public void TestSimpleSpend() { // prepare block var fakeHeaders = new FakeHeaders(); var chainedHeader0 = fakeHeaders.GenesisChained(); var chainedHeader1 = fakeHeaders.NextChained(); var chainedHeader2 = fakeHeaders.NextChained(); var chainedHeader3 = fakeHeaders.NextChained(); var chain = Chain.CreateForGenesisBlock(chainedHeader0).ToBuilder(); var emptyCoinbaseTx0 = BlockTx.Create(0, Transaction.Create(0, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0)); var emptyCoinbaseTx1 = BlockTx.Create(1, Transaction.Create(1, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0)); var emptyCoinbaseTx2 = BlockTx.Create(2, Transaction.Create(2, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0)); // initialize memory utxo builder storage var memoryStorage = new MemoryStorageManager(); var memoryChainStateCursor = memoryStorage.OpenChainStateCursor().Item; memoryChainStateCursor.BeginTransaction(); // initialize utxo builder var utxoBuilder = new UtxoBuilder(); // prepare an unspent transaction var txHash = new UInt256(100); var unspentTx = new UnspentTx(txHash, chainedHeader1.Height, 0, 0, false, 3, OutputState.Unspent); var txOutput1Key = new TxOutputKey(txHash, 0); var txOutput1 = new TxOutput(0, ImmutableArray<byte>.Empty); var txOutput2Key = new TxOutputKey(txHash, 1); var txOutput2 = new TxOutput(1, ImmutableArray<byte>.Empty); var txOutput3Key = new TxOutputKey(txHash, 2); var txOutput3 = new TxOutput(2, ImmutableArray<byte>.Empty); // prepare unspent output var unspentTransactions = ImmutableDictionary.Create<UInt256, UnspentTx>().Add(txHash, unspentTx); // add the unspent transaction memoryChainStateCursor.TryAddUnspentTx(unspentTx); memoryChainStateCursor.TryAddUnspentTxOutput(txOutput1Key, txOutput1); memoryChainStateCursor.TryAddUnspentTxOutput(txOutput2Key, txOutput2); memoryChainStateCursor.TryAddUnspentTxOutput(txOutput3Key, txOutput3); // create an input to spend the unspent transaction's first output var input0 = new TxInput(txHash, 0, ImmutableArray.Create<byte>(), 0); var tx0 = BlockTx.Create(1, Transaction.Create(0, ImmutableArray.Create(input0), ImmutableArray.Create<TxOutput>(), 0)); // spend the input chain.AddBlock(chainedHeader1); utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx0, tx0 }.ToBufferBlock()).ToEnumerable().ToList(); // verify utxo storage UnspentTx actualUnspentTx; TxOutput actualTxOutput; Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTx(txHash, out actualUnspentTx)); Assert.IsTrue(actualUnspentTx.OutputStates.Length == 3); Assert.IsTrue(actualUnspentTx.OutputStates[0] == OutputState.Spent); Assert.IsTrue(actualUnspentTx.OutputStates[1] == OutputState.Unspent); Assert.IsTrue(actualUnspentTx.OutputStates[2] == OutputState.Unspent); Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTxOutput(txOutput1Key, out actualTxOutput)); Assert.AreEqual(txOutput1, actualTxOutput); Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTxOutput(txOutput2Key, out actualTxOutput)); Assert.AreEqual(txOutput2, actualTxOutput); Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTxOutput(txOutput3Key, out actualTxOutput)); Assert.AreEqual(txOutput3, actualTxOutput); // create an input to spend the unspent transaction's second output var input1 = new TxInput(txHash, 1, ImmutableArray.Create<byte>(), 0); var tx1 = BlockTx.Create(1, Transaction.Create(0, ImmutableArray.Create(input1), ImmutableArray.Create<TxOutput>(), 0)); // spend the input chain.AddBlock(chainedHeader2); utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx1, tx1 }.ToBufferBlock()).ToEnumerable().ToList(); // verify utxo storage Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTx(txHash, out actualUnspentTx)); Assert.IsTrue(actualUnspentTx.OutputStates.Length == 3); Assert.IsTrue(actualUnspentTx.OutputStates[0] == OutputState.Spent); Assert.IsTrue(actualUnspentTx.OutputStates[1] == OutputState.Spent); Assert.IsTrue(actualUnspentTx.OutputStates[2] == OutputState.Unspent); // create an input to spend the unspent transaction's third output var input2 = new TxInput(txHash, 2, ImmutableArray.Create<byte>(), 0); var tx2 = BlockTx.Create(2, Transaction.Create(0, ImmutableArray.Create(input2), ImmutableArray.Create<TxOutput>(), 0)); // spend the input chain.AddBlock(chainedHeader3); utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx2, tx2 }.ToBufferBlock()).ToEnumerable().ToList(); // verify utxo storage Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTx(txHash, out actualUnspentTx)); Assert.IsTrue(actualUnspentTx.IsFullySpent); }