Ejemplo n.º 1
0
 public WalletEntry(IImmutableList <MonitoredWalletAddress> addresses, EnumWalletEntryType type, ChainPosition chainPosition, UInt64 value)
 {
     this.addresses     = addresses;
     this.type          = type;
     this.chainPosition = chainPosition;
     this.value         = value;
 }
Ejemplo n.º 2
0
        public void Mint(Transaction tx, ChainedHeader chainedHeader, bool isCoinbase)
        {
            // there exist two duplicate coinbases in the blockchain, which the design assumes to be impossible
            // ignore the first occurrences of these duplicates so that they do not need to later be deleted from the utxo, an unsupported operation
            // no other duplicates will occur again, it is now disallowed
            if ((chainedHeader.Height == DUPE_COINBASE_1_HEIGHT && tx.Hash == DUPE_COINBASE_1_HASH) ||
                (chainedHeader.Height == DUPE_COINBASE_2_HEIGHT && tx.Hash == DUPE_COINBASE_2_HASH))
            {
                return;
            }

            // verify transaction does not already exist in utxo
            if (this.chainStateBuilderStorage.ContainsTransaction(tx.Hash))
            {
                // duplicate transaction output
                this.logger.Warn("Duplicate transaction at block {0:#,##0}, {1}, coinbase".Format2(chainedHeader.Height, chainedHeader.Hash.ToHexNumberString()));
                throw new ValidationException(chainedHeader.Hash);
            }

            // add transaction to the utxo
            this.chainStateBuilderStorage.AddTransaction(tx.Hash, new UnspentTx(chainedHeader.Hash, tx.Outputs.Count, OutputState.Unspent));

            // add transaction outputs to the utxo
            foreach (var output in tx.Outputs.Select((x, i) => new KeyValuePair <TxOutputKey, TxOutput>(new TxOutputKey(tx.Hash, (UInt32)i), x)))
            {
                this.chainStateBuilderStorage.AddOutput(output.Key, output.Value);

                // MONITOR: MintTxOutput
                if (this.chainStateMonitor != null)
                {
                    this.chainStateMonitor.MintTxOutput(ChainPosition.Fake(), output.Key, output.Value, GetOutputScripHash(output.Value), isCoinbase);
                }
            }
        }
Ejemplo n.º 3
0
        private void ScanForEntry(ChainPosition chainPosition, EnumWalletEntryType walletEntryType, TxOutput txOutput, UInt256 outputScriptHash)
        {
            var matchingAddresses = ImmutableList.CreateBuilder <MonitoredWalletAddress>();

            // test hash addresses
            List <MonitoredWalletAddress> addresses;

            if (this.addressesByOutputScriptHash.TryGetValue(outputScriptHash, out addresses))
            {
                matchingAddresses.AddRange(addresses);
            }

            // test matcher addresses
            foreach (var address in this.matcherAddresses)
            {
                if (address.Address.MatchesTxOutput(txOutput, outputScriptHash))
                {
                    matchingAddresses.Add(address);
                }
            }

            if (matchingAddresses.Count > 0)
            {
                var entry = new WalletEntry
                            (
                    addresses: matchingAddresses.ToImmutable(),
                    type: walletEntryType,
                    chainPosition: chainPosition,
                    value: txOutput.Value
                            );

                this.entriesLock.DoWrite(() =>
                {
                    this.logger.Debug("{0,-10}   {1,20:#,##0.000_000_00} BTC, Entries: {2:#,##0}".Format2(walletEntryType.ToString() + ":", txOutput.Value / (decimal)(100.MILLION()), this.entries.Count));

                    this.entries.Add(entry);
                    if (walletEntryType == EnumWalletEntryType.Spend)
                    {
                        this.bitBalance -= entry.BitValue;
                    }
                    else
                    {
                        this.bitBalance += entry.BitValue;
                    }
                });

                var handler = this.OnEntryAdded;
                if (handler != null)
                {
                    handler(entry);
                }
            }
        }
Ejemplo n.º 4
0
        private void ScanForEntry(ChainPosition chainPosition, EnumWalletEntryType walletEntryType, TxOutput txOutput, UInt256 outputScriptHash)
        {
            var matchingAddresses = ImmutableList.CreateBuilder <MonitoredWalletAddress>();

            // test hash addresses
            List <MonitoredWalletAddress> addresses;

            if (this.addressesByOutputScriptHash.TryGetValue(outputScriptHash, out addresses))
            {
                matchingAddresses.AddRange(addresses);
            }

            // test matcher addresses
            foreach (var address in this.matcherAddresses)
            {
                if (address.Address.MatchesTxOutput(txOutput, outputScriptHash))
                {
                    matchingAddresses.Add(address);
                }
            }

            if (matchingAddresses.Count > 0)
            {
                var entry = new WalletEntry
                            (
                    addresses: matchingAddresses.ToImmutable(),
                    type: walletEntryType,
                    chainPosition: chainPosition,
                    value: txOutput.Value
                            );

                lock (this.entries)
                {
                    if (keepEntries)
                    {
                        this.entries.Add(entry);
                    }

                    this.entriesCount++;
                }
                this.bitBalance += entry.BitValue * walletEntryType.Direction();

                logger.Debug($"{walletEntryType + ":",-10}   {txOutput.Value / (decimal)(100.MILLION()),20:#,##0.000_000_00} BTC, Entries: {this.entriesCount:#,##0}");

                this.OnEntryAdded?.Invoke(entry);
            }
        }
Ejemplo n.º 5
0
        private async Task ScanBlock(ICoreStorage coreStorage, IChainState chainState, ChainedHeader scanBlock, bool forward, CancellationToken cancelToken = default(CancellationToken))
        {
            var replayTxes = BlockReplayer.ReplayBlock(coreStorage, chainState, scanBlock.Hash, forward, cancelToken);

            var txScanner = new ActionBlock <ValidatableTx>(
                validatableTx =>
            {
                var tx      = validatableTx.Transaction;
                var txIndex = validatableTx.Index;

                if (!validatableTx.IsCoinbase)
                {
                    for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++)
                    {
                        var input                = tx.Inputs[inputIndex];
                        var prevOutput           = validatableTx.PrevTxOutputs[inputIndex];
                        var prevOutputScriptHash = new UInt256(SHA256Static.ComputeHash(prevOutput.ScriptPublicKey));

                        var chainPosition = ChainPosition.Fake();
                        var entryType     = forward ? EnumWalletEntryType.Spend : EnumWalletEntryType.UnSpend;

                        ScanForEntry(chainPosition, entryType, (TxOutput)prevOutput, prevOutputScriptHash);
                    }
                }

                for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++)
                {
                    var output           = tx.Outputs[outputIndex];
                    var outputScriptHash = new UInt256(SHA256Static.ComputeHash(output.ScriptPublicKey));

                    var chainPosition = ChainPosition.Fake();
                    var entryType     =
                        validatableTx.IsCoinbase ?
                        (forward ? EnumWalletEntryType.Mine : EnumWalletEntryType.UnMine)
                                : (forward ? EnumWalletEntryType.Receive : EnumWalletEntryType.UnReceieve);

                    ScanForEntry(chainPosition, entryType, output, outputScriptHash);
                }
            });

            replayTxes.LinkTo(txScanner, new DataflowLinkOptions {
                PropagateCompletion = true
            });
            await txScanner.Completion;
        }
Ejemplo n.º 6
0
        public void Unmint(Transaction tx, ChainedHeader chainedHeader, bool isCoinbase)
        {
            // ignore duplicate coinbases
            if ((chainedHeader.Height == DUPE_COINBASE_1_HEIGHT && tx.Hash == DUPE_COINBASE_1_HASH) ||
                (chainedHeader.Height == DUPE_COINBASE_2_HEIGHT && tx.Hash == DUPE_COINBASE_2_HASH))
            {
                return;
            }

            // check that transaction exists
            UnspentTx unspentTx;

            if (!this.chainStateBuilderStorage.TryGetTransaction(tx.Hash, out unspentTx))
            {
                // missing transaction output
                this.logger.Warn("Missing transaction at block {0:#,##0}, {1}, tx {2}".Format2(chainedHeader.Height, chainedHeader.Hash.ToHexNumberString(), tx.Hash));
                throw new ValidationException(chainedHeader.Hash);
            }

            //TODO verify blockheight

            // verify all outputs are unspent before unminting
            if (!unspentTx.OutputStates.All(x => x == OutputState.Unspent))
            {
                throw new ValidationException(chainedHeader.Hash);
            }

            // remove the transaction
            this.chainStateBuilderStorage.RemoveTransaction(tx.Hash);

            // remove the transaction outputs
            for (var outputIndex = 0; outputIndex < tx.Outputs.Count; outputIndex++)
            {
                var txOutput    = tx.Outputs[outputIndex];
                var txOutputKey = new TxOutputKey(tx.Hash, (UInt32)outputIndex);

                this.chainStateBuilderStorage.RemoveOutput(txOutputKey);

                // MONITOR: UnspendTxOutput
                if (this.chainStateMonitor != null)
                {
                    this.chainStateMonitor.UnmintTxOutput(ChainPosition.Fake(), txOutputKey, txOutput, GetOutputScripHash(txOutput), isCoinbase);
                }
            }
        }
Ejemplo n.º 7
0
        //TODO thread safety
        //TODO need to rescan utxo when addresses are added as well
        public void AddAddress(IWalletAddress address)
        {
            //TODO add to queue, cannot monitor address until chain position moves
            var startChainPosition = ChainPosition.Fake();
            var monitoredRange = new[] { Tuple.Create(startChainPosition, startChainPosition) }.ToList();

            foreach (var outputScriptHash in address.GetOutputScriptHashes())
            {
                List <MonitoredWalletAddress> addresses;
                if (!this.addressesByOutputScriptHash.TryGetValue(outputScriptHash, out addresses))
                {
                    addresses = new List <MonitoredWalletAddress>();
                    this.addressesByOutputScriptHash.Add(outputScriptHash, addresses);
                }

                addresses.Add(new MonitoredWalletAddress(address, monitoredRange));
            }

            if (address.IsMatcher)
            {
                this.matcherAddresses.Add(new MonitoredWalletAddress(address, monitoredRange));
            }
        }
Ejemplo n.º 8
0
 public override void SpendTxOutput(ChainPosition chainPosition, ChainedHeader chainedHeader, Transaction tx, TxInput txInput, TxOutputKey txOutputKey, TxOutput txOutput, UInt256 outputScriptHash)
 {
     this.ScanForEntry(chainPosition, EnumWalletEntryType.Spend, txOutput, outputScriptHash);
 }
Ejemplo n.º 9
0
 public override void MintTxOutput(ChainPosition chainPosition, TxOutputKey txOutputKey, TxOutput txOutput, UInt256 outputScriptHash, bool isCoinbase)
 {
     this.ScanForEntry(chainPosition, isCoinbase ? EnumWalletEntryType.Mine : EnumWalletEntryType.Receive, txOutput, outputScriptHash);
 }
Ejemplo n.º 10
0
        public void Unspend(TxInput input, ChainedHeader chainedHeader, Dictionary <UInt256, SpentTx> spentTransactions, Dictionary <TxOutputKey, TxOutput> spentOutputs)
        {
            //TODO currently a MissingDataException will get thrown if the rollback information is missing
            //TODO rollback is still possible if any resurrecting transactions can be found
            //TODO the network does not allow arbitrary transaction lookup, but if the transactions can be retrieved then this code should allow it

            //// retrieve rollback information
            //UInt256 prevTxBlockHash;
            //if (!spentTransactions.TryGetValue(input.PreviousTxOutputKey.TxHash, out prevTxBlockHash))
            //{
            //    //TODO throw should indicate rollback info is missing
            //    throw new MissingDataException(null);
            //}

            // retrieve previous output
            TxOutput prevTxOutput;

            if (!spentOutputs.TryGetValue(input.PreviousTxOutputKey, out prevTxOutput))
            {
                throw new Exception("TODO - corruption");
            }

            // retrieve transaction output states, if not found then a fully spent transaction is being resurrected
            UnspentTx unspentTx;

            if (!this.chainStateBuilderStorage.TryGetTransaction(input.PreviousTxOutputKey.TxHash, out unspentTx))
            {
                // retrieve spent transaction
                SpentTx prevSpentTx;
                if (!spentTransactions.TryGetValue(input.PreviousTxOutputKey.TxHash, out prevSpentTx))
                {
                    throw new Exception("TODO - corruption");
                }

                // create fully spent transaction output state
                unspentTx = new UnspentTx(prevSpentTx.ConfirmedBlockHash, prevSpentTx.OutputCount, OutputState.Spent);
            }

            // retrieve previous output index
            var outputIndex = unchecked ((int)input.PreviousTxOutputKey.TxOutputIndex);

            if (outputIndex < 0 || outputIndex >= unspentTx.OutputStates.Length)
            {
                throw new Exception("TODO - corruption");
            }

            // check that output isn't already considered unspent
            if (unspentTx.OutputStates[outputIndex] == OutputState.Unspent)
            {
                throw new ValidationException(chainedHeader.Hash);
            }

            // mark output as unspent
            this.chainStateBuilderStorage.UpdateTransaction(input.PreviousTxOutputKey.TxHash, unspentTx.SetOutputState(outputIndex, OutputState.Unspent));

            // add transaction output back to utxo
            this.chainStateBuilderStorage.AddOutput(input.PreviousTxOutputKey, prevTxOutput);

            // MONITOR: UnspendTxOutput
            if (this.chainStateMonitor != null)
            {
                this.chainStateMonitor.UnspendTxOutput(ChainPosition.Fake(), input, input.PreviousTxOutputKey, prevTxOutput, GetOutputScripHash(prevTxOutput));
            }
        }
Ejemplo n.º 11
0
        //TODO with the rollback information that's now being stored, rollback could be down without needing the block
        private void RollbackUtxo(Block block)
        {
            //TODO currently a MissingDataException will get thrown if the rollback information is missing
            //TODO rollback is still possible if any resurrecting transactions can be found
            //TODO the network does not allow arbitrary transaction lookup, but if the transactions can be retrieved then this code should allow it
            //TODO this should be handled by a distinct worker that rebuilds rollback information

            var spentTransactions = new Dictionary <UInt256, SpentTx>();

            spentTransactions.AddRange(this.spentTransactionsCache[block.Hash]);

            var spentOutputs = new Dictionary <TxOutputKey, TxOutput>();

            spentOutputs.AddRange(this.spentOutputsCache[block.Hash]);

            for (var txIndex = block.Transactions.Count - 1; txIndex >= 1; txIndex--)
            {
                var tx = block.Transactions[txIndex];

                // MONITOR: BeforeRemoveTransaction
                if (this.chainStateMonitor != null)
                {
                    this.chainStateMonitor.BeforeRemoveTransaction(ChainPosition.Fake(), tx);
                }

                // remove outputs
                this.Unmint(tx, this.LastBlock, isCoinbase: false);

                // remove inputs in reverse order
                for (var inputIndex = tx.Inputs.Count - 1; inputIndex >= 0; inputIndex--)
                {
                    var input = tx.Inputs[inputIndex];
                    this.Unspend(input, this.LastBlock, spentTransactions, spentOutputs);
                }

                // MONITOR: AfterRemoveTransaction
                if (this.chainStateMonitor != null)
                {
                    this.chainStateMonitor.AfterRemoveTransaction(ChainPosition.Fake(), tx);
                }
            }

            var coinbaseTx = block.Transactions[0];

            // MONITOR: BeforeRemoveTransaction
            if (this.chainStateMonitor != null)
            {
                this.chainStateMonitor.BeforeRemoveTransaction(ChainPosition.Fake(), coinbaseTx);
            }

            // remove coinbase outputs
            this.Unmint(coinbaseTx, this.LastBlock, isCoinbase: true);

            for (var inputIndex = coinbaseTx.Inputs.Count - 1; inputIndex >= 0; inputIndex--)
            {
                // MONITOR: UnCoinbaseInput
                if (this.chainStateMonitor != null)
                {
                    this.chainStateMonitor.UnCoinbaseInput(ChainPosition.Fake(), coinbaseTx.Inputs[inputIndex]);
                }
            }

            // MONITOR: AfterRemoveTransaction
            if (this.chainStateMonitor != null)
            {
                this.chainStateMonitor.AfterRemoveTransaction(ChainPosition.Fake(), coinbaseTx);
            }
        }
Ejemplo n.º 12
0
        private void CalculateUtxo(ChainedBlock chainedBlock, out long txCount, out long inputCount)
        {
            // don't include genesis block coinbase in utxo
            if (chainedBlock.Height <= 0)
            {
                throw new InvalidOperationException();
            }

            txCount    = 1;
            inputCount = 0;

            //using (var txInputQueue = new ProducerConsumer<Tuple<Transaction, int, TxInput, int, TxOutput>>())
            //using (var validateScriptsTask = Task.Factory.StartNew(() => this.ValidateTransactionScripts(chainedBlock, txInputQueue)))
            //using (var txOutputQueue = new ProducerConsumer<Tuple<int, ChainPosition, TxOutput>>())
            //using (var scannerTask = Task.Factory.StartNew(() => this.ScanTransactions(txOutputQueue, monitors)))
            //try
            //{

            //TODO apply real coinbase rule
            // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219
            var coinbaseTx = chainedBlock.Transactions[0];

            // MONITOR: BeforeAddTransaction
            if (this.chainStateMonitor != null)
            {
                this.chainStateMonitor.BeforeAddTransaction(ChainPosition.Fake(), coinbaseTx);
            }

            // MONITOR: CoinbaseInput
            if (this.chainStateMonitor != null)
            {
                foreach (var input in coinbaseTx.Inputs)
                {
                    this.chainStateMonitor.CoinbaseInput(ChainPosition.Fake(), input);
                }
            }

            this.Mint(coinbaseTx, chainedBlock.ChainedHeader, isCoinbase: true);

            // MONITOR: AfterAddTransaction
            if (this.chainStateMonitor != null)
            {
                this.chainStateMonitor.AfterAddTransaction(ChainPosition.Fake(), coinbaseTx);
            }

            // check for double spends
            for (var txIndex = 1; txIndex < chainedBlock.Transactions.Count; txIndex++)
            {
                var tx = chainedBlock.Transactions[txIndex];
                txCount++;

                // MONITOR: BeforeAddTransaction
                if (this.chainStateMonitor != null)
                {
                    this.chainStateMonitor.BeforeAddTransaction(ChainPosition.Fake(), tx);
                }

                for (var inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++)
                {
                    var input = tx.Inputs[inputIndex];
                    inputCount++;

                    this.Spend(txIndex, tx, inputIndex, input, chainedBlock.ChainedHeader);

                    // MEASURE: Input Rate
                    this.stats.inputRateMeasure.Tick();
                }

                this.Mint(tx, chainedBlock.ChainedHeader, isCoinbase: false);

                // MONITOR: AfterAddTransaction
                if (this.chainStateMonitor != null)
                {
                    this.chainStateMonitor.AfterAddTransaction(ChainPosition.Fake(), tx);
                }

                // MEASURE: Transaction Rate
                this.stats.txRateMeasure.Tick();
            }

            //}
            //finally
            //{
            //    // ensure that started tasks always complete using a finally block
            //    // any exceptions will be propagated by Task.WaitAll()
            //    //TODO unwrap aggregation exception into something more specific
            //    txInputQueue.CompleteAdding();
            //    txOutputQueue.CompleteAdding();
            //    Task.WaitAll(validateScriptsTask, scannerTask);
            //}
        }