示例#1
0
        /// <summary>
        ///     Processes raw bitcoin blocks
        ///     - decodes the block, transactions, output scripts, and output addresses
        /// </summary>
        /// <param name="data">raw block byte array</param>
        public override void DoWork(byte[] data)
        {
            // attempt to decode the block (also decodes transactions, output scripts, and addresses)
            Block block;

            try
            {
                block = new Block(data)
                {
                    FirstSeen = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                };
            }
            catch (Exception e)
            {
                Console.WriteLine("Unable to decode block!");
                return;
            }

            Console.WriteLine("Received Block with " + block.Transactions.Length + " transactions.\n" +
                              " Previous Block: " + block.Header.PrevBlockHash + "\n" +
                              " Block Hash: " + block.BlockHash);


            // Add block data to internal db, and check for re-org
            Program.Database.EnqueueTask(new DatabaseWrite(block), 0);

            // check all transactions
            foreach (var transaction in block.Transactions)
            {
                // check all outputs of the transaction
                SubscriptionCheck.CheckForSubscription(transaction);
            }
        }
示例#2
0
        /// <summary>
        ///     Processes raw bitcoin transactions
        ///     - decodes the transaction, output scripts, and output addresses
        /// </summary>
        /// <param name="data">raw transaction byte array</param>
        public override void DoWork(byte[] data)
        {
            // hex version of the transaction
            var txHex = ByteToHex.ByteArrayToHex(data);

            // attempt to decode the transaction (also decodes output scripts and addresses)
            Transaction transaction;

            try
            {
                transaction = new Transaction(data)
                {
                    FirstSeen   = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                    LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
                };
            }
            catch (Exception e)
            {
                Console.WriteLine("Unable to decode transaction: \n" + txHex);
                return;
            }

            // check all outputs of the transaction
            SubscriptionCheck.CheckForSubscription(transaction);

            /*
             * Console.WriteLine("Received Transaction with " + transaction.Outputs.Length + " outputs and "
             + transaction.Inputs.Length + " inputs. HasWitness = " +
             +                (transaction.HasWitness ? "YES" : "NO") +
             +                ". Output Scripts:");
             +
             +
             + // iterate over each output in the transaction
             + foreach (var output in transaction.Outputs)
             + {
             +  // write out the raw output script as hex
             +  Console.WriteLine(ByteToHex.ByteArrayToHex(output.Script));
             +  var script = new Script(output.Script);
             +  var dataCount = 0;
             +  // write out the ASM version of the output
             +  foreach (var opCode in script.OpCodes)
             +      if (opCode == OpCodeType.OP_DATA)
             +          Console.Write(" " + ByteToHex.ByteArrayToHex(script.DataChunks[dataCount++]));
             +      else
             +          Console.Write(" " + opCode);
             +  Console.WriteLine();
             +  // write out the type and address (if the output is a known payment type)
             +  Console.WriteLine(" Type = " + output.Type + (output.Address == "" ? "" : ". Address = " + output.Address));
             + }
             + Console.WriteLine();
             */
        }
示例#3
0
        private static void Main(string[] args)
        {
            // get internal litedb database filename from app settings
            var databaseFileName = ConfigurationManager.AppSettings["LiteDBFileName"];

            // init the database
            Database = new DatabaseConsumer(databaseFileName);
            // get saved subscriptions
            Subscriptions = Database.GetSubscriptions().ToList();

            // get RPC connection params from app settings
            var rpcURL      = ConfigurationManager.AppSettings["RPCURI"];
            var rpcUsername = ConfigurationManager.AppSettings["RPCUsername"];
            var rpcPassword = ConfigurationManager.AppSettings["RPCPassword"];

            // init RPC connection
            RPCClient = new RPCClient(new Uri(rpcURL), rpcUsername, rpcPassword);
            // get info for the last block that was processed
            var lastBlock  = Database.GetLastBlock();
            var blockCount = RPCClient.GetBlockCount();

            // write out of the current block height, and last processed block height
            Console.Write("Current block: " + blockCount + ". " + (lastBlock == null ? " No previous block data found." : " Last block processed: " + lastBlock.Height));
            // if we already have block data, fetch transactions since the last seen block
            if (lastBlock != null && lastBlock.Height < blockCount)
            {
                Console.WriteLine(". processing " + (blockCount - lastBlock.Height) + " blocks...");
                // record how long it takes to process the block data
                var stopWatch = new Stopwatch();
                stopWatch.Start();
                // look at all blocks from the last block that was processed until the current height
                for (var blockIndex = lastBlock.Height; blockIndex <= blockCount; blockIndex++)
                {
                    // fetch raw block data by height
                    var blockHash = RPCClient.GetBlockHash(blockIndex);
                    var blockData = RPCClient.GetBlockData(blockHash);
                    // decode the block
                    var block = new Block(ByteToHex.StringToByteArray(blockData))
                    {
                        FirstSeen = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
                    };
                    // add the block to the database
                    Database.EnqueueTask(new DatabaseWrite(block), 0);
                    // process all transactions that occured in the block
                    foreach (var transaction in block.Transactions)
                    {
                        // does this transaction contain an output we are watching?
                        SubscriptionCheck.CheckForSubscription(transaction);
                    }
                }

                stopWatch.Stop();
                var elapsed = stopWatch.Elapsed;
                Console.Write("Processed blocks in " + $"{elapsed.Hours:00}:{elapsed.Minutes:00}:{elapsed.Seconds:00}.{elapsed.Milliseconds / 10:00}");
            }
            Console.WriteLine();

            // get websocket listen address/port from app settings
            var websocketListen = ConfigurationManager.AppSettings["WebSocketListen"];

            // start websocket server
            WebSocketServer = new WebSocket.Server(websocketListen);

            // get ZMQ server address from app settings
            var zmqServerTX    = ConfigurationManager.AppSettings["ZMQPublisherRawTX"];
            var zmqServerBlock = ConfigurationManager.AppSettings["ZMQPublisherRawBlock"];

            // start ZMQ subscribers
            new ZMQ.Subscriber(zmqServerTX, "rawtx", new TXConsumer());
            new ZMQ.Subscriber(zmqServerBlock, "rawblock", new BlockConsumer());

            // skip scanning the mempool if there is no saved block data yet (TODO: maybe it should still scan?)
            if (lastBlock != null)
            {
                // fetch the mempool
                var memPool = RPCClient.GetMemPool();
                Console.WriteLine("Mempool contains " + memPool.Length + " transactions; processing...");

                // record how long it takes to process the mempool
                var stopWatch2 = new Stopwatch();
                stopWatch2.Start();
                // process all mempool transactions
                foreach (var txid in memPool)
                {
                    var rawTransaction = RPCClient.GetRawTransaction(txid);
                    var transaction    = new Transaction(rawTransaction)
                    {
                        FirstSeen   = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                        LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
                    };

                    // does this transaction contain an output we are watching?
                    SubscriptionCheck.CheckForSubscription(transaction);
                }

                stopWatch2.Stop();
                var elapsed2 = stopWatch2.Elapsed;
                Console.WriteLine("Processed mempool in " +
                                  $"{elapsed2.Hours:00}:{elapsed2.Minutes:00}:{elapsed2.Seconds:00}.{elapsed2.Milliseconds / 10:00}");
            }
            else
            {
                Console.WriteLine("Skipping mempool scan on first run.");
            }
        }
        private void HandleBlock(Block block)
        {
            // have we seen this block before?
            var blockSearch = _blocks.FindOne(x => x.BlockHash == block.BlockHash);

            if (blockSearch != null)
            {
                // yes; we've seen the block before. So, no need to handle it.
                return;
            }

            // is this the first block we've stored?
            blockSearch = _blocks.FindOne(x => x.IsChainTip);
            if (blockSearch == null)
            {
                // yes; so, just store it without PrevHash check
                // use JSON RPC to fetch block height
                var blockHeight = Program.RPCClient.GetBlockHeight(block.BlockHash);
                block.Height = blockHeight;
                // mark as best known chain tip
                _chainTipHash    = block.BlockHash;
                block.IsChainTip = true;
                // save to db
                _blocks.Insert(block);
                return;
            }

            // no; so, check if we have the previous block in the chain
            blockSearch = _blocks.FindOne(x => x.BlockHash == block.Header.PrevBlockHash);
            if (blockSearch == null)
            {
                // we are missing the prev block; this shouldn't happen...
                // queue blocks backwards until we find a prev-block we have, and track the missing blocks
                var missingBlocks = new List <Block> {
                    block
                };
                var prevBlockHash = block.Header.PrevBlockHash;
                // loop until we have already have the previous block
                while (!_blocks.Exists(x => x.BlockHash == prevBlockHash))
                {
                    var prevBlockData = Program.RPCClient.GetBlockData(block.Header.PrevBlockHash);
                    var prevBlock     = new Block(ByteToHex.StringToByteArray(prevBlockData));
                    missingBlocks.Add(prevBlock);
                    prevBlockHash = prevBlock.Header.PrevBlockHash;
                }
                // missingBlocks is now an ordered list of blocks we are missing - queue them in reverse
                missingBlocks.Reverse();
                foreach (var missingBlock in missingBlocks)
                {
                    Program.Database.EnqueueTask(new DatabaseWrite(missingBlock), 0);
                }

                // discard (don't save) this block, as it will be re-processed in order
                return;
            }

            // we already have the previous block in the chain...
            // so, this new block's height is prevblock height + 1
            block.Height = blockSearch.Height + 1;
            var chainTipBlock = _blocks.FindOne(x => x.IsChainTip);

            // check if the prevHash block is our chaintip
            if (_chainTipHash != block.Header.PrevBlockHash)
            {
                // no; so, there was a re-org!
                // we need to invalidate transaction inclusions, back to the forking block
                var orphanedBlock  = chainTipBlock;
                var newChainBlock  = block;
                var orphanedBlocks = new List <Block> {
                    orphanedBlock
                };
                var newChainBlocks = new List <Block> {
                    newChainBlock
                };
                // step backwards on each chain in turn until the two sides of the fork are at the same height
                while (orphanedBlock.Height > newChainBlock.Height)
                {
                    orphanedBlock = _blocks.FindOne(x => x.BlockHash == orphanedBlock.Header.PrevBlockHash);
                    orphanedBlocks.Add(orphanedBlock);
                }
                while (orphanedBlock.Height < newChainBlock.Height)
                {
                    newChainBlock = _blocks.FindOne(x => x.BlockHash == newChainBlock.Header.PrevBlockHash);
                    newChainBlocks.Add(newChainBlock);
                }
                // orphaned chain and new chain are the same height now
                // step back both chains at the same time until we have a matching prevBlockHash
                while (orphanedBlock.Header.PrevBlockHash != newChainBlock.Header.PrevBlockHash)
                {
                    orphanedBlock = _blocks.FindOne(x => x.BlockHash == orphanedBlock.Header.PrevBlockHash);
                    orphanedBlocks.Add(orphanedBlock);
                    newChainBlock = _blocks.FindOne(x => x.BlockHash == newChainBlock.Header.PrevBlockHash);
                    newChainBlocks.Add(newChainBlock);
                }
                // prevBlockHash is now the forking block;
                // roll-back transaction inclusions
                var transactions = _transactions.Find(x => x.IncludedAtBlockHeight >= orphanedBlock.Height);
                foreach (var transaction in transactions)
                {
                    transaction.IncludedAtBlockHeight = 0;
                    transaction.IncludedInBlockHex    = "";
                }

                // mark all blocks on the orphaned side as orphaned, and vice-versa
                foreach (var blk in orphanedBlocks)
                {
                    blk.Orphaned = true;
                }
                // this is needed in the case of re-re-orgs
                foreach (var blk in newChainBlocks)
                {
                    blk.Orphaned = false;
                }

                // we need to re-scan transactions in higher blocks
                // (skip the transactions in this block itself, as they will be queued behind this insert)
                // for most re-orgs, this won't actually have anything to process
                foreach (var blk in newChainBlocks.Where(x => x.BlockHash != block.BlockHash))
                {
                    // check all transactions in the block
                    foreach (var transaction in block.Transactions)
                    {
                        SubscriptionCheck.CheckForSubscription(transaction);
                    }
                }

                // re-org is handled, and this is the new chaintip, so fall thru to insert the block normally
            }

            // this is a regular block insert - we have previous block, and the previous block is our last known chaintip
            chainTipBlock.IsChainTip = false;
            _blocks.Update(chainTipBlock);
            block.IsChainTip = true;
            _chainTipHash    = block.BlockHash;
            // save to db
            _blocks.Insert(block);
        }