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);
        }