private void SetBlockHash(Types.SessionWrapper session, HashHeightPair nextBlockHash) { this.blockHash = nextBlockHash; var lastblockKey = new Types.StoreKey { tableType = "BlockHash", key = blockHashKey }; var lastBlockvalue = new Types.StoreValue { value = nextBlockHash.ToBytes() }; Types.StoreContext context = new Types.StoreContext(); var blkStatus = session.Session.Upsert(ref lastblockKey, ref lastBlockvalue, context, 1); // TODO: use height a serial number? if (blkStatus != Status.OK) { throw new Exception(); } }
private HashHeightPair GetTipHash(Types.SessionWrapper session) { if (this.blockHash == null) { Types.StoreInput input = new Types.StoreInput(); Types.StoreOutput output = new Types.StoreOutput(); var lastblockKey = new Types.StoreKey { tableType = "BlockHash", key = blockHashKey }; Types.StoreContext context = new Types.StoreContext(); var blkStatus = session.Session.Read(ref lastblockKey, ref input, ref output, context, 1); // TODO: use height a serial number? if (blkStatus == Status.OK) { this.blockHash = new HashHeightPair(); this.blockHash.FromBytes(output.value.value); } } return(this.blockHash); }
public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { FetchCoinsResponse res = new FetchCoinsResponse(); using (var session = this.db.NewSession()) { using (new StopwatchDisposable(o => this.performanceCounter.AddQueryTime(o))) { this.performanceCounter.AddQueriedEntities(utxos.Length); Types.StoreInput input = new Types.StoreInput(); Types.StoreOutput output = new Types.StoreOutput(); Types.StoreContext context = new Types.StoreContext(); var readKey = new Types.StoreKey { tableType = "Coins" }; foreach (OutPoint outPoint in utxos) { output.value = null; readKey.key = outPoint.ToBytes(); var addStatus = session.Read(ref readKey, ref input, ref output, context, 1); if (addStatus == Status.PENDING) { session.CompletePending(true); context.FinalizeRead(ref addStatus, ref output); } Coins outputs = addStatus == Status.OK ? this.dataStoreSerializer.Deserialize <Coins>(output.value.value) : null; this.logger.LogDebug("Outputs for '{0}' were {1}.", outPoint, outputs == null ? "NOT loaded" : "loaded"); res.UnspentOutputs.Add(outPoint, new UnspentOutput(outPoint, outputs)); } } } return(res); }
public HashHeightPair Rewind() { HashHeightPair res = null; using (var session = this.db.NewSession()) { var wrapper = new Types.SessionWrapper { Session = session }; HashHeightPair current = this.GetTipHash(wrapper); Types.StoreInput input1 = new Types.StoreInput(); Types.StoreOutput output1 = new Types.StoreOutput(); Types.StoreContext context = new Types.StoreContext(); var readKey = new Types.StoreKey { tableType = "Rewind", key = BitConverter.GetBytes(current.Height) }; var addStatus = session.Read(ref readKey, ref input1, ref output1, context, 1); if (addStatus == Status.PENDING) { session.CompletePending(true); context.FinalizeRead(ref addStatus, ref output1); } if (addStatus != Status.OK) { throw new InvalidOperationException($"No rewind data found for block `{current}`"); } var deteletKey = new Types.StoreKey { tableType = "Rewind", key = BitConverter.GetBytes(current.Height) }; Types.StoreContext contextDel = new Types.StoreContext(); var deleteStatus = session.Delete(ref readKey, contextDel, 1); if (deleteStatus != Status.OK) { throw new Exception(); } var rewindData = this.dataStoreSerializer.Deserialize <RewindData>(output1.value.value); this.SetBlockHash(wrapper, rewindData.PreviousBlockHash); foreach (OutPoint outPoint in rewindData.OutputsToRemove) { this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); deteletKey = new Types.StoreKey { tableType = "Coins", key = outPoint.ToBytes() }; contextDel = new Types.StoreContext(); deleteStatus = session.Delete(ref readKey, contextDel, 1); if (deleteStatus != Status.OK) { throw new Exception(); } } foreach (RewindDataOutput rewindDataOutput in rewindData.OutputsToRestore) { this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); var upsertKey = new Types.StoreKey { tableType = "Coins", key = rewindDataOutput.OutPoint.ToBytes() }; var upsertValue = new Types.StoreValue { value = this.dataStoreSerializer.Serialize(rewindDataOutput.Coins) }; Types.StoreContext context2 = new Types.StoreContext(); addStatus = session.Upsert(ref upsertKey, ref upsertValue, context2, 1); if (addStatus != Status.OK) { throw new Exception(); } } res = rewindData.PreviousBlockHash; } return(res); }
public void SaveChanges(IList <UnspentOutput> unspentOutputs, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List <RewindData> rewindDataList = null) { int insertedEntities = 0; using (var session = this.db.NewSession()) { using (new StopwatchDisposable(o => this.performanceCounter.AddInsertTime(o))) { var wrapper = new Types.SessionWrapper { Session = session }; HashHeightPair current = this.GetTipHash(wrapper); if (current != oldBlockHash) { this.logger.LogTrace("(-)[BLOCKHASH_MISMATCH]"); throw new InvalidOperationException("Invalid oldBlockHash"); } this.SetBlockHash(wrapper, nextBlockHash); // Here we'll add items to be inserted in a second pass. List <UnspentOutput> toInsert = new List <UnspentOutput>(); foreach (var coin in unspentOutputs.OrderBy(utxo => utxo.OutPoint, new OutPointComparer())) { if (coin.Coins == null) { this.logger.LogDebug("Outputs of transaction ID '{0}' are prunable and will be removed from the database.", coin.OutPoint); var deteletKey = new Types.StoreKey { tableType = "Coins", key = coin.OutPoint.ToBytes() }; Types.StoreContext context = new Types.StoreContext(); var deleteStatus = session.Delete(ref deteletKey, context, 1); if (deleteStatus != Status.OK) { throw new Exception(); } } else { // Add the item to another list that will be used in the second pass. // This is for performance reasons: dBreeze is optimized to run the same kind of operations, sorted. toInsert.Add(coin); } } for (int i = 0; i < toInsert.Count; i++) { var coin = toInsert[i]; this.logger.LogDebug("Outputs of transaction ID '{0}' are NOT PRUNABLE and will be inserted into the database. {1}/{2}.", coin.OutPoint, i, toInsert.Count); var upsertKey = new Types.StoreKey { tableType = "Coins", key = coin.OutPoint.ToBytes() }; var upsertValue = new Types.StoreValue { value = this.dataStoreSerializer.Serialize(coin.Coins) }; Types.StoreContext context2 = new Types.StoreContext(); var addStatus = session.Upsert(ref upsertKey, ref upsertValue, context2, 1); if (addStatus != Status.OK) { throw new Exception(); } } if (rewindDataList != null) { foreach (RewindData rewindData in rewindDataList) { var nextRewindIndex = rewindData.PreviousBlockHash.Height + 1; this.logger.LogDebug("Rewind state #{0} created.", nextRewindIndex); var upsertKey = new Types.StoreKey { tableType = "Rewind", key = BitConverter.GetBytes(nextRewindIndex) }; var upsertValue = new Types.StoreValue { value = this.dataStoreSerializer.Serialize(rewindData) }; Types.StoreContext context2 = new Types.StoreContext(); var addStatus = session.Upsert(ref upsertKey, ref upsertValue, context2, 1); if (addStatus != Status.OK) { throw new Exception(); } } } insertedEntities += unspentOutputs.Count; this.Checkpoint(); } } this.performanceCounter.AddInsertedEntities(insertedEntities); }