public async Task <Tuple <int, DateTime> > GetBestAddressIndexBlockHeight() { using (var db = new ObsidianChainContext()) { var stats = await db.StatEntities.FindAsync("1"); return(new Tuple <int, DateTime>(stats.BestAdrIndexHeight, stats.ModifiedDate)); } }
static void InsertAddressIfShouldBeIndexed(AddressEntity address, ObsidianChainContext db) { if (address.Id.StartsWith("OP_RETURN", StringComparison.OrdinalIgnoreCase)) { address.Id = "OP_RETURN"; } if (address.Id != TransactionAdapter.NonStandardAddress) { db.AddressEntities.Add(address); } }
static void ProcessMoneyTransfer(Core.Domain.Transaction tx, ObsidianChainContext db) { IList <VIn> vins = tx.TransactionIn; List <string> inAdresses = new List <string>(); foreach (var vin in vins) { AddressEntity existing = db.AddressEntities.Find(vin.PrevVOutFetchedAddress); if (existing == null) // work around broken index till indexing is re-run { Console.WriteLine($"This should never happen: {vin.PrevVOutFetchedAddress} could not be found."); continue; } inAdresses.Add(existing.Id); existing.Balance -= vin.PrevVOutFetchedValue; existing.LastModifiedBlockHeight = (int)tx.Block.Height; UpdateTxIdBlog(existing, tx.TransactionId); } IList <Out> vouts = tx.TransactionsOut; foreach (var vout in vouts) { string outAdress = vout.Address; if (outAdress.StartsWith("OP_RETURN", StringComparison.OrdinalIgnoreCase)) { outAdress = "OP_RETURN"; } AddressEntity existing = db.AddressEntities.Find(outAdress); if (existing != null) { existing.Balance += vout.Value; if (!inAdresses.Contains(existing.Id)) { UpdateTxIdBlog(existing, tx.TransactionId); existing.LastModifiedBlockHeight = (int)tx.Block.Height; } } else { var newAddress = new AddressEntity { Id = vout.Address, Balance = vout.Value, LastModifiedBlockHeight = (int)tx.Block.Height, TxIdBlob = tx.TransactionId + CRLF }; InsertAddressIfShouldBeIndexed(newAddress, db); } } }
public async Task <List <Address> > GetTopList() { using (var db = new ObsidianChainContext()) { List <Address> addresses = await db.AddressEntities.AsNoTracking().OrderByDescending(adr => adr.Balance).Take(100) .Select(adr => new { Id = adr.Id, Balance = adr.Balance }) .Select(res => new Address() { Id = res.Id, Balance = res.Balance }) .ToListAsync(); return(addresses); } }
public async Task <Address> GetById(string id) { if (string.IsNullOrWhiteSpace(id)) { return(null); } id = id.Trim(); if (id.Length != 34 && !id.Equals("OP_RETURN", StringComparison.OrdinalIgnoreCase)) { return(null); } using (var db = new ObsidianChainContext()) { var addressEntity = await db.AddressEntities.FindAsync(id); if (addressEntity == null) { return(null); } List <Transaction> transactions = new List <Transaction>(); string[] txids = addressEntity.TxIdBlob.Split("\r\n", StringSplitOptions.RemoveEmptyEntries); foreach (var txid in txids) { var transaction = new Transaction { TransactionId = txid }; transactions.Add(transaction); } var address = new Address { Id = id, Balance = addressEntity.Balance, LastModifiedBlockHeight = addressEntity.LastModifiedBlockHeight, TxIds = txids, TotalTransactions = txids.Length, Transactions = null, ColoredAddress = null }; return(address); } }
static void ProcessStakingReward(Core.Domain.Transaction tx, ObsidianChainContext db) { var vin = tx.TransactionIn[0]; var inAddress = vin.PrevVOutFetchedAddress; var oldBalance = vin.PrevVOutFetchedValue; Debug.Assert(inAddress == tx.TransactionsOut[1].Address); Debug.Assert(inAddress == tx.TransactionsOut[2].Address); var outValue1 = tx.TransactionsOut[1].Value; var outValue2 = tx.TransactionsOut[2].Value; var change = outValue1 + outValue2 - oldBalance; // I assume that only pre-existing addresses get a staking reward! AddressEntity existing = db.AddressEntities.Find(inAddress); Debug.Assert(existing != null); existing.Balance += change; existing.LastModifiedBlockHeight = (int)tx.Block.Height; UpdateTxIdBlog(existing, tx.TransactionId); }
static void ProcessTransaction(Core.Domain.Transaction blockTx, ObsidianChainContext db) { switch (blockTx.TransactionType) { case TransactionType.PoW_Reward_Coinbase: ProcessPoWReward(blockTx, db); break; case TransactionType.PoS_Reward: ProcessStakingReward(blockTx, db); break; case TransactionType.Money: ProcessMoneyTransfer(blockTx, db); break; default: throw new IndexOutOfRangeException("Unsupported TransactionType."); } }
static void ProcessPoWReward(Core.Domain.Transaction tx, ObsidianChainContext db) { var vout = tx.TransactionsOut[0]; var address = vout.Address; var amount = vout.Value; AddressEntity existing = db.AddressEntities.Find(address); if (existing == null) { var newAddress = new AddressEntity { Id = address, Balance = amount, LastModifiedBlockHeight = (int)tx.Block.Height, TxIdBlob = tx.TransactionId + CRLF }; InsertAddressIfShouldBeIndexed(newAddress, db); return; } existing.Balance += amount; existing.LastModifiedBlockHeight = (int)tx.Block.Height; UpdateTxIdBlog(existing, tx.TransactionId); }
static void Run(CancellationToken token) { bool shouldWait = false; while (!token.IsCancellationRequested) { if (shouldWait) { Console.WriteLine("Going to sleep for 30 seconds..."); Thread.Sleep(30000); shouldWait = false; } try { using (var context = new ObsidianChainContext()) { using (IDbContextTransaction dbtx = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable)) { try { if (context.BlockEntities.Any()) { _currentBlockNumber = context.BlockEntities.Max(x => x.Id); _currentBlockHeight = context.BlockEntities.Max(x => x.Id) - 1; _currentBlockHeight++; _currentBlockNumber++; } string blockHash = _txAdapter.RpcClient.GetBlockHashAsync(_currentBlockHeight).GetAwaiter().GetResult(); if (blockHash == null) { Console.WriteLine($"Block at height {_currentBlockHeight} not found!"); shouldWait = true; continue; } var result = IndexBlock(context, blockHash); if (result != 0) { return; } var stats = context.StatEntities.Find("1"); // insert this row manually stats.BestAdrIndexHeight = _currentBlockHeight; stats.ModifiedDate = DateTime.UtcNow; context.SaveChanges(); // this is still necessary, even when using Commit() dbtx.Commit(); } catch (Exception ex) { Console.WriteLine($"Error within the transaction: {ex.Message}"); shouldWait = true; } } // Dispose db transaction } // Dispose db connection } catch (Exception e) { Console.WriteLine($"Error creating connection / transaction: {e.Message}"); Console.WriteLine("Pausing 30 seconds..."); Thread.Sleep(30000); Console.WriteLine("Reconnecting..."); } } }
static int IndexBlock(ObsidianChainContext db, string blockHash) { Console.WriteLine($"Processing block at height {_currentBlockHeight}: {blockHash}"); GetBlockRpcModel block = _txAdapter.RpcClient.GetBlockAsync(blockHash).GetAwaiter().GetResult(); if (block == null) { Console.WriteLine($"Error - could not retrieve block not at height {_currentBlockHeight}: {blockHash}"); return(-1); } var blockEntity = new BlockEntity { Id = _currentBlockNumber, Height = _currentBlockHeight, BlockHash = blockHash, BlockData = block.OriginalJson }; db.BlockEntities.Add(blockEntity); if (_currentBlockHeight == 0) { // for the tx in the genesis block, we can't pull transaction data, so we make an exception here var genesisBlockTransaction = new TransactionEntity { BlockEntity = blockEntity, Id = block.Tx[0] }; db.TransactionEntities.Add(genesisBlockTransaction); _currentBlockHeight++; _currentBlockNumber++; db.SaveChanges(); return(0); } // Get all transactions from the adapter, so that we can use the logic there string[] transactionIds = block.Tx; List <Core.Domain.Transaction> blockTransactions = new List <Core.Domain.Transaction>(); foreach (var txid in transactionIds) { var transaction = _txAdapter.GetTransaction(txid).GetAwaiter().GetResult(); transaction.Block = new Block { Height = _currentBlockHeight }; blockTransactions.Add(transaction); } // Queue all for insert to the db foreach (var blockTx in blockTransactions) { var transactionEntity = new TransactionEntity() { Id = blockTx.TransactionId, BlockEntity = blockEntity, TransactionData = blockTx.OriginalJson }; // db.TransactionEntities.Add(transactionEntity); } // Process all blockTransactions for accounting foreach (var blockTx in blockTransactions) { ProcessTransaction(blockTx, db); } db.SaveChanges(); _currentBlockHeight++; _currentBlockNumber++; return(0); }