public static void BuildCache(PerformContext context) { //TODO: Update this to work in a loop and store seperate db files per 10,000 blocks... //should improve performance and make re-sync'ing / re-caching much easier... /* * Some things to consider though: * 1. Need a check mechanism to check that no files in the sequence are missing. * 2. The way we check "last cached tx" needs to be changed so it gets the latest file.. * 3. Will also need to update the Blockchain Controller to get Tx's from the right cache file */ // So, first we need to //first, we need to know the current BC Height //get bc height from RPC var currentHeight = 0; try { currentHeight = RpcHelper.Request <GetHeightResp>("getheight").Height - 1; } catch { currentHeight = 0; } var startHeight = 1; var endHeight = Convert.ToInt32(Math.Ceiling((double)(currentHeight / 10000) * 10000)) + 10000; //now, splt the current height into blocks of 10000 for (int i = startHeight; i <= endHeight; i += 10000) { var start = i; var end = i + 10000 - 1; //retreive, transform and cache the blockchain and store in LiteDB using (var db = new LiteDatabase(string.Concat(AppContext.BaseDirectory, @"App_Data\", "transactions_", start, "-", end, ".db"))) { var transactions = db.GetCollection <CachedTx>("cached_txs"); var date = DateTime.Now; // Index document using height's, hash and Id transactions.EnsureIndex(x => x.height); transactions.EnsureIndex(x => x.Id); transactions.EnsureIndex(x => x.hash); try { //get the maxTxId var lastTxId = transactions.Max(x => x.Id).AsInt32; //get the last Tx we cached var lastTx = transactions.FindOne(x => x.Id == lastTxId); if (lastTx != null) { start = lastTx.height; if (start == end) { continue; //move to the next file... } } else { start = i; } } catch (Exception ex) { //todo: add logging } var counter = 1; var gCounter = start; var txHashes = new List <string>(); if (currentHeight > start) { if (end > currentHeight) { end = currentHeight; } for (var j = start; j <= end; j++) { var height_args = new int[] { j }; var blockHash = ""; try { blockHash = RpcHelper.RequestJson <HashResp>("on_getblockhash", height_args).result; } catch { } if (!string.IsNullOrEmpty(blockHash)) { //then, get the blockHash for the height we're currently processing... var hash_args = new Dictionary <string, object>(); hash_args.Add("hash", blockHash); txHashes.AddRange(RpcHelper.RequestJson <BlockJsonResp>("f_block_json", hash_args).result.block.transactions.Select(x => x.hash).ToList <string>()); //next, get the block itself and extract all the tx hashes.... if (counter == 50 || gCounter == currentHeight) { var tx_args = new Dictionary <string, object>(); tx_args.Add("transactionHashes", txHashes.ToArray()); var txs = RpcHelper.Request <TxDetailResp>("get_transaction_details_by_hashes", tx_args); var transactionsToInsert = new List <CachedTx>(); foreach (var tx in txs.transactions) { var cachedTx = TransactionHelpers.MapTx(tx); //persist tx's to cache if (cachedTx != null && !transactions.Find(x => x.hash == cachedTx.hash).Any()) { transactionsToInsert.Add(cachedTx); } } if (transactionsToInsert.Any()) { transactions.InsertBulk(transactionsToInsert); //temp monitoring var heights = transactionsToInsert.Select(x => x.height).Distinct().OrderBy(x => x).ToList(); System.IO.File.AppendAllText(string.Concat(AppContext.BaseDirectory, @"App_Data\", "moniter.txt"), counter + ", " + gCounter + ", " + string.Join(", ", heights) + Environment.NewLine); //output csv of heights captured to check against } counter = 0; txHashes.Clear(); } gCounter++; counter++; } } } //else there's nothing to do } } //finally, schedule the next check in 30 seconds time BackgroundJob.Schedule(() => BlockchainCache.BuildCache(null), TimeSpan.FromSeconds(30)); }
public static void BuildCache(PerformContext context) { try { //first, we need to try again to fetch any failed or missing Tx's... using (var db = new LiteDatabase(string.Concat(AppContext.BaseDirectory, @"App_Data/", "failedHashes.db"))) { var failedTransactions = db.GetCollection <FailedHash>("failed_txs"); failedTransactions.EnsureIndex(x => x.height); failedTransactions.EnsureIndex(x => x.hash); var allFails = failedTransactions.FindAll().ToList(); foreach (var failedHash in allFails) { using (var txDb = new LiteDatabase(failedHash.DbFile)) { var transactions = db.GetCollection <CachedTx>("cached_txs"); var date = DateTime.Now; // Index document using height's, hash and Id transactions.EnsureIndex(x => x.height); transactions.EnsureIndex(x => x.Id); transactions.EnsureIndex(x => x.hash); //try fetch the transaction again... var tx_args = new Dictionary <string, object>(); tx_args.Add("transactionHashes", new string[] { failedHash.hash }); try { var txs = RpcHelper.Request <TxDetailResp>("get_transaction_details_by_hashes", tx_args); var transactionsToInsert = new List <CachedTx>(); foreach (var tx in txs.transactions) { var cachedTx = TransactionHelpers.MapTx(tx); //persist tx's to cache if (cachedTx != null && !transactions.Find(x => x.hash == cachedTx.hash).Any()) { transactionsToInsert.Add(cachedTx); } } if (transactionsToInsert.Any()) { transactions.InsertBulk(transactionsToInsert); logger.Log(LogLevel.Information, $"Added {transactionsToInsert.Count} transactions to cache."); //remove from failedTx DB foreach (var tx in transactionsToInsert) { var failedTx = failedTransactions.FindOne(x => x.hash == tx.hash); if (failedTx != null) { failedTransactions.Delete(failedTx.Id); } } } } catch (Exception ex) { failedHash.FetchAttempts++; logger.Log(LogLevel.Information, $"Failed to add single transaction {failedHash.hash} on attempt number {failedHash.FetchAttempts}."); LogException(ex); //update the failed Tx and restore it in DB to try again failedTransactions.Update(failedHash); } } } } //now, we're processing the pending transactions, we need to know the current BC Height //get bc height from RPC var currentHeight = 0; try { currentHeight = RpcHelper.Request <GetHeightResp>("getheight").Height - 1; } catch { currentHeight = 0; } var startHeight = 1; var endHeight = Convert.ToInt32(Math.Ceiling((double)(currentHeight / 100) * 100)) + 100; logger.Log(LogLevel.Information, $"Processing transactions from blocks {startHeight} to {endHeight}"); //now, splt the current height into blocks of 100 for (int i = startHeight; i <= endHeight; i += 100) { var start = i; var end = i + 100 - 1; //retreive, transform and cache the blockchain and store in LiteDB using (var db = new LiteDatabase(string.Concat(AppContext.BaseDirectory, @"App_Data/", "transactions_", start, "-", end, ".db"))) { var transactions = db.GetCollection <CachedTx>("cached_txs"); var date = DateTime.Now; // Index document using height's, hash and Id transactions.EnsureIndex(x => x.height); transactions.EnsureIndex(x => x.Id); transactions.EnsureIndex(x => x.hash); try { //get the maxTxId var lastTxId = transactions.Max(x => x.Id).AsInt32; //get the last Tx we cached var lastTx = transactions.FindOne(x => x.Id == lastTxId); if (lastTx != null) { start = lastTx.height; if (start == end) { logger.Log(LogLevel.Information, $"Already cached transactions from blocks {startHeight} to {endHeight}"); continue; //move to the next file... } } else { start = i; } } catch (Exception ex) { LogException(ex); } var counter = 1; var gCounter = start; var txHashes = new List <string>(); if (currentHeight > start) { if (end > currentHeight) { end = currentHeight; } for (var j = start; j <= end; j++) { var height_args = new int[] { j }; var blockHash = ""; try { blockHash = RpcHelper.RequestJson <HashResp>("on_getblockhash", height_args).result; } catch { } if (!string.IsNullOrEmpty(blockHash)) { //then, get the blockHash for the height we're currently processing... var hash_args = new Dictionary <string, object>(); hash_args.Add("hash", blockHash); //if this fails we want it to exit, wait 30 seconds and startup again txHashes.AddRange(RpcHelper.RequestJson <BlockJsonResp>("f_block_json", hash_args).result.block.transactions.Select(x => x.hash).ToList <string>()); //next, get the block itself and extract all the tx hashes.... if (counter == 50 || gCounter == currentHeight) { logger.Log(LogLevel.Information, $"Caching transactions at height {gCounter}"); var tx_args = new Dictionary <string, object>(); tx_args.Add("transactionHashes", txHashes.ToArray()); //ok, so what seems to be happening here is that the query to get multiple block hashes is timing out. //Two things have been changed //RPC request timeout has been increased from 60 to 320ms //secondly, if we get an error here, we revert to trying to fetch this batche of Tx's individually try { var txs = RpcHelper.Request <TxDetailResp>("get_transaction_details_by_hashes", tx_args); var transactionsToInsert = new List <CachedTx>(); foreach (var tx in txs.transactions) { var cachedTx = TransactionHelpers.MapTx(tx); //persist tx's to cache if (cachedTx != null && !transactions.Find(x => x.hash == cachedTx.hash).Any()) { transactionsToInsert.Add(cachedTx); } } if (transactionsToInsert.Any()) { transactions.InsertBulk(transactionsToInsert); logger.Log(LogLevel.Information, $"Added {transactionsToInsert.Count} transactions to cache."); var distinctTxCount = transactionsToInsert.Select(x => x.height).Distinct().Count(); if (distinctTxCount != 50 && gCounter != currentHeight) { logger.Log(LogLevel.Warning, $"Potentially missing transactions at height {gCounter}, expected min 50, found {distinctTxCount}"); // so what do we do here, go back and try re-add the individual Tx's ? //fetch them all individually and try insert one by one? } else { //we've added the 50 hashes, so now clear the Tx Cache txHashes.Clear(); // clear the current set of hashes } } } catch (Exception ex) { //so the fetching of TX's has failed, we need to loop the individual hashes and get them one by one to insert logger.Log(LogLevel.Warning, $"Failed to fetch transactions at {gCounter}, reverting to individual hashes"); foreach (var hash in txHashes) { var cachedTx = AddSingleTransaction(hash); if (cachedTx != null) { if (!transactions.Find(x => x.hash == cachedTx.hash).Any()) { transactions.Insert(cachedTx); } } else { logger.LogError($"failed to fetch single transaction: {hash}"); //log the failed hash and come back to try add it later on. var failedHash = new FailedHash() { DbFile = string.Concat(AppContext.BaseDirectory, @"App_Data/", "transactions_", start, "-", end, ".db"), hash = hash, height = gCounter, FetchAttempts = 0 }; using (var failedHashDb = new LiteDatabase(string.Concat(AppContext.BaseDirectory, @"App_Data/", "failedHashes.db"))) { var failedTransactions = db.GetCollection <FailedHash>("failed_txs"); failedTransactions.EnsureIndex(x => x.height); failedTransactions.EnsureIndex(x => x.hash); failedTransactions.Insert(failedHash); } } } } counter = 0; txHashes.Clear(); } gCounter++; counter++; } } } //else there's nothing to do } } } catch (Exception ex) { LogException(ex); } finally { //finally, schedule the next check in 30 seconds time BackgroundJob.Schedule(() => BlockchainCache.BuildCache(null), TimeSpan.FromSeconds(30)); } }
public static void BuildCache(PerformContext context) { try { //first, we need to know the current BC Height //get bc height from RPC var currentHeight = 0; try { currentHeight = RpcHelper.Request <GetHeightResp>("getheight").Height - 1; } catch { currentHeight = 0; } var startHeight = 1; var endHeight = Convert.ToInt32(Math.Ceiling((double)(currentHeight / 10000) * 10000)) + 10000; //now, splt the current height into blocks of 10000 for (int i = startHeight; i <= endHeight; i += 10000) { var start = i; var end = i + 10000 - 1; //retreive, transform and cache the blockchain and store in LiteDB using (var db = new LiteDatabase(string.Concat(AppContext.BaseDirectory, @"App_Data\", "transactions_", start, "-", end, ".db"))) { var transactions = db.GetCollection <CachedTx>("cached_txs"); var date = DateTime.Now; // Index document using height's, hash and Id transactions.EnsureIndex(x => x.height); transactions.EnsureIndex(x => x.Id); transactions.EnsureIndex(x => x.hash); try { //get the maxTxId var lastTxId = transactions.Max(x => x.Id).AsInt32; //get the last Tx we cached var lastTx = transactions.FindOne(x => x.Id == lastTxId); if (lastTx != null) { start = lastTx.height; if (start == end) { continue; //move to the next file... } } else { start = i; } } catch (Exception ex) { //todo: add logging } var counter = 1; var gCounter = start; var txHashes = new List <string>(); if (currentHeight > start) { if (end > currentHeight) { end = currentHeight; } for (var j = start; j <= end; j++) { var height_args = new int[] { j }; var blockHash = ""; try { blockHash = RpcHelper.RequestJson <HashResp>("on_getblockhash", height_args).result; } catch { } if (!string.IsNullOrEmpty(blockHash)) { //then, get the blockHash for the height we're currently processing... var hash_args = new Dictionary <string, object>(); hash_args.Add("hash", blockHash); //if it fails here we need to exit the loop txHashes.AddRange(RpcHelper.RequestJson <BlockJsonResp>("f_block_json", hash_args).result.block.transactions.Select(x => x.hash).ToList <string>()); if (counter == 50 || gCounter == currentHeight) { var tx_args = new Dictionary <string, object>(); tx_args.Add("transactionHashes", txHashes.ToArray()); var txs = RpcHelper.Request <TxDetailResp>("get_transaction_details_by_hashes", tx_args); var transactionsToInsert = new List <CachedTx>(); foreach (var tx in txs.transactions) { var cachedTx = TransactionHelpers.MapTx(tx); //persist tx's to cache if (cachedTx != null && !transactions.Find(x => x.hash == cachedTx.hash).Any()) { transactionsToInsert.Add(cachedTx); } } if (transactionsToInsert.Any()) { transactions.InsertBulk(transactionsToInsert); var heights = transactionsToInsert.Select(x => x.height).Distinct().OrderBy(x => x).ToList(); } counter = 0; txHashes.Clear(); } gCounter++; counter++; } } } //else there's nothing to do } } } catch (Exception ex) { //todo: add logging } finally { //finally, schedule the next check in 30 seconds time BackgroundJob.Schedule(() => BlockchainCache.BuildCache(null), TimeSpan.FromSeconds(30)); } }