Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,16 @@ public interface ICoindb

public interface IStakedb : ICoindb
{
/// <summary>
/// Persists unsaved POS blocks information to the database.
/// </summary>
/// <param name="stakeEntries">List of POS block information to be examined and persists if unsaved.</param>
void PutStake(IEnumerable<StakeItem> stakeEntries);

/// <summary>
/// Retrieves POS blocks information from the database.
/// </summary>
/// <param name="blocklist">List of partially initialized POS block information that is to be fully initialized with the values from the database.</param>
void GetStake(IEnumerable<StakeItem> blocklist);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Stratis.Bitcoin.Builder;
using Stratis.Bitcoin.Configuration.Logging;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Database;
using Stratis.Bitcoin.Features.Consensus.CoinViews;
using Stratis.Bitcoin.Features.Consensus.Interfaces;
using Stratis.Bitcoin.Features.Consensus.ProvenBlockHeaders;
Expand Down Expand Up @@ -87,15 +88,15 @@ public static void ConfigureCoinDatabaseImplementation(this IServiceCollection s
break;

case DbType.Leveldb:
services.AddSingleton<ICoindb, LevelDbCoindb>();
services.AddSingleton<ICoindb, Coindb<LevelDb>>();
break;

case DbType.RocksDb:
services.AddSingleton<ICoindb, RocksDbCoindb>();
services.AddSingleton<ICoindb, Coindb<RocksDb>>();
break;

default:
services.AddSingleton<ICoindb, LevelDbCoindb>();
services.AddSingleton<ICoindb, Coindb<LevelDb>>();
break;
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Moq;
using NBitcoin;
using Stratis.Bitcoin.Configuration;
using Stratis.Bitcoin.Database;
using Stratis.Bitcoin.Features.Consensus.CoinViews;
using Stratis.Bitcoin.Interfaces;
using Stratis.Bitcoin.Tests.Common;
Expand All @@ -27,7 +28,7 @@ public NodeContext(object caller, string name, Network network)
this.FolderName = TestBase.CreateTestDir(caller, name);
var dateTimeProvider = new DateTimeProvider();
var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory);
this.Coindb = new LevelDbCoindb(network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock<IVersionProvider>().Object), serializer);
this.Coindb = new Coindb<LevelDb>(network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock<IVersionProvider>().Object), serializer);
this.Coindb.Initialize(new ChainedHeader(network.GetGenesis().Header, network.GenesisHash, 0));
this.cleanList = new List<IDisposable> { (IDisposable)this.Coindb };
}
Expand Down Expand Up @@ -65,7 +66,7 @@ public void ReloadPersistentCoinView(ChainedHeader chainTip)
this.cleanList.Remove((IDisposable)this.Coindb);
var dateTimeProvider = new DateTimeProvider();
var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory);
this.Coindb = new LevelDbCoindb(this.Network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(this.Network), new Mock<IVersionProvider>().Object), serializer);
this.Coindb = new Coindb<LevelDb>(this.Network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(this.Network), new Mock<IVersionProvider>().Object), serializer);

this.Coindb.Initialize(chainTip);
this.cleanList.Add((IDisposable)this.Coindb);
Expand Down
126 changes: 126 additions & 0 deletions src/Stratis.Bitcoin/Database/IDb.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;

namespace Stratis.Bitcoin.Database
{
/// <summary>
/// This interface and its relevant implementations provide a standardized interface to databases such as <see cref="RocksDb"/> and <see cref="LevelDb"/>, or other databases
/// capable of supporting key-based value retrieval and key iteration.
/// </summary>
/// <remarks>
/// The interface expects keys to be specified as separate table and key identifiers. Similarly iterators are expected to be constrained to operate within single tables.
/// </remarks>
public interface IDb : IDisposable
{
/// <summary>
/// Opens the database at the specified path.
/// </summary>
/// <param name="dbPath">The path where the database is located.</param>
void Open(string dbPath);

/// <summary>
/// Gets the value associated with a table and key.
/// </summary>
/// <param name="table">The table identifier.</param>
/// <param name="key">The key of the value to retrieve.</param>
/// <returns>The value for the specified table and key.</returns>
byte[] Get(byte table, byte[] key);

/// <summary>
/// Gets an iterator that allows iteration over keys in a table.
/// </summary>
/// <param name="table">The table that will be iterated.</param>
/// <returns>See <see cref="IDbIterator"/>.</returns>
IDbIterator GetIterator(byte table);

/// <summary>
/// Gets a batch that can be used to record changes that can be applied atomically.
/// </summary>
/// <remarks>The <see cref="IDb.Get"/> method will not reflect these changes until they are committed. Use
/// the <see cref="ReadWriteBatch"/> class if uncommitted changes need to be accessed.</remarks>
/// <returns>See <see cref="IDbBatch"/>.</returns>
IDbBatch GetWriteBatch();

/// <summary>
/// Removes all tables and their contents.
/// </summary>
void Clear();
}

/// <summary>
/// A batch that can be used to record changes that can be applied atomically.
/// </summary>
/// <remarks>The database's <see cref="Get"/> method will not reflect these changes until they are committed.</remarks>
public interface IDbBatch : IDisposable
{
/// <summary>
/// Records a value that will be written to the database when the <see cref="Write"/> method is invoked.
/// </summary>
/// <param name="table">The table that will be updated.</param>
/// <param name="key">The table key that identifies the value to be updated.</param>
/// <param name="value">The value to be written to the table.</param>
/// <returns>This class for fluent operations.</returns>
IDbBatch Put(byte table, byte[] key, byte[] value);

/// <summary>
/// Records a key that will be deleted from the database when the <see cref="Write"/> method is invoked.
/// </summary>
/// <param name="table">The table that will be updated.</param>
/// <param name="key">The table key that will be removed.</param>
/// <returns>This class for fluent operations.</returns>
IDbBatch Delete(byte table, byte[] key);

/// <summary>
/// Writes the recorded changes to the database.
/// </summary>
void Write();
}

/// <summary>
/// An iterator that can be used to iterate the keys and values in an <see cref="IDb"/> compliant database.
/// </summary>
public interface IDbIterator : IDisposable
{
/// <summary>
/// Seeks to a first key >= <paramref name="key"/> in the relevant table.
/// If no such key is found then <see cref="IsValid"/> will return <c>false</c>.
/// </summary>
/// <param name="key">The key to find.</param>
void Seek(byte[] key);

/// <summary>
/// Seeks to the last key in the relevant table.
/// If no such key is found then <see cref="IsValid"/> will return <c>false</c>.
/// </summary>
void SeekToLast();

/// <summary>
/// Seeks to the next key in the relevant table.
/// If no such key is found then <see cref="IsValid"/> will return <c>false</c>.
/// </summary>
void Next();

/// <summary>
/// Seeks to the previous key in the relevant table.
/// If no such key is found then <see cref="IsValid"/> will return <c>false</c>.
/// </summary>
void Prev();

/// <summary>
/// Determines if the current key is valid.
/// </summary>
/// <returns><c>true</c> if a <see cref="Seek"/>, <see cref="Next"/>, <see cref="SeekToLast"/> or <see cref="Prev"/> operation found a valid key. <c>false</c> otherwise.</returns>
bool IsValid();

/// <summary>
/// The current key.
/// </summary>
/// <returns>The key.</returns>
byte[] Key();

/// <summary>
/// The current value.
/// </summary>
/// <returns>The value.</returns>
byte[] Value();
}
}
130 changes: 130 additions & 0 deletions src/Stratis.Bitcoin/Database/LevelDb.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.Linq;
using LevelDB;
using NBitcoin;

namespace Stratis.Bitcoin.Database
{
/// <summary>A minimal LevelDb wrapper that makes it compliant with the <see cref="IDb"/> interface.</summary>
public class LevelDb : IDb
{
private string dbPath;

private DB db;

public IDbIterator GetIterator(byte table)
{
return new LevelDbIterator(table, this.db.CreateIterator());
}

public void Open(string dbPath)
{
this.dbPath = dbPath;
this.db = new DB(new Options() { CreateIfMissing = true }, dbPath);
}

public void Clear()
{
this.db.Dispose();
System.IO.Directory.Delete(this.dbPath, true);
this.db = new DB(new Options() { CreateIfMissing = true }, this.dbPath);
}

public IDbBatch GetWriteBatch() => new LevelDbBatch(this.db);

public byte[] Get(byte table, byte[] key)
{
return this.db.Get(new[] { table }.Concat(key).ToArray());
}

public void Dispose()
{
this.db.Dispose();
}
}

/// <summary>A minimal LevelDb wrapper that makes it compliant with the <see cref="IDbBatch"/> interface.</summary>
public class LevelDbBatch : WriteBatch, IDbBatch
{
private DB db;

public LevelDbBatch(DB db)
{
this.db = db;
}

public IDbBatch Put(byte table, byte[] key, byte[] value)
{
return (IDbBatch)this.Put(new[] { table }.Concat(key).ToArray(), value);
}

public IDbBatch Delete(byte table, byte[] key)
{
return (IDbBatch)this.Delete(new[] { table }.Concat(key).ToArray());
}

public void Write()
{
this.db.Write(this, new WriteOptions() { Sync = true });
}
}

/// <summary>A minimal LevelDb wrapper that makes it compliant with the <see cref="IDbIterator"/> interface.</summary>
public class LevelDbIterator : IDbIterator
{
private byte table;
private Iterator iterator;

public LevelDbIterator(byte table, Iterator iterator)
{
this.table = table;
this.iterator = iterator;
}

public void Seek(byte[] key)
{
this.iterator.Seek(new[] { this.table }.Concat(key).ToArray());
}

public void SeekToLast()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you give a high-level description of how this method works? It is not entirely intuitive to me how seeking based on a single-byte table identifier gives the last record. Is it the last record within the current table? I presume it has something to do with how records are arranged below the table identifiers.

{
// First seek past the last record in the table by attempting to seek to the start of the next table (if any).
this.iterator.Seek(new[] { (byte)(this.table + 1) });
if (!this.iterator.IsValid())
// If there is no next table then simply seek to the last record in the db as that will be the last record of 'table'.
this.iterator.SeekToLast();
else
// If we managed to seek to the start of the next table then go back one record to arrive at the last record of 'table'.
this.iterator.Prev();
}

public void Next()
{
this.iterator.Next();
}

public void Prev()
{
this.iterator.Prev();
}

public bool IsValid()
{
return this.iterator.IsValid() && this.iterator.Key()[0] == this.table;
}

public byte[] Key()
{
return this.iterator.Key().Skip(1).ToArray();
}

public byte[] Value()
{
return this.iterator.Value();
}

public void Dispose()
{
this.iterator.Dispose();
}
}
}
Loading