Exemplo n.º 1
0
        /// <summary>
        ///     Gets raw transaction data for a given txid
        /// </summary>
        /// <param name="txid">transaction id in hex</param>
        /// <returns>raw transaction byte data</returns>
        public byte[] GetRawTransaction(string txid)
        {
            var ret            = SendCommand("getrawtransaction", txid);
            var transactionHex = ret.GetValue("result").ToObject <string>();

            return(ByteToHex.StringToByteArray(transactionHex));
        }
Exemplo n.º 2
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.");
            }
        }
Exemplo n.º 3
0
        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);
        }