mirror of https://github.com/status-im/nim-eth.git
port kvstore from nim-beacon-chain (#230)
* port kvstore from nim-beacon-chain * remove old database backends * use kvstore in trie database * add sqlite dep * avoid template param double evaluation * clean up heterogenous lookup todo
This commit is contained in:
parent
28e684ce80
commit
205b57fe71
24
eth.nimble
24
eth.nimble
|
@ -13,7 +13,8 @@ requires "nim >= 1.2.0",
|
||||||
"chronicles",
|
"chronicles",
|
||||||
"stew",
|
"stew",
|
||||||
"nat_traversal",
|
"nat_traversal",
|
||||||
"metrics"
|
"metrics",
|
||||||
|
"sqlite3_abi"
|
||||||
|
|
||||||
proc runTest(path: string) =
|
proc runTest(path: string) =
|
||||||
echo "\nRunning: ", path
|
echo "\nRunning: ", path
|
||||||
|
@ -21,21 +22,13 @@ proc runTest(path: string) =
|
||||||
rmFile path
|
rmFile path
|
||||||
|
|
||||||
proc runKeyfileTests() =
|
proc runKeyfileTests() =
|
||||||
for filename in [
|
runTest("tests/keyfile/all_tests")
|
||||||
"test_keyfile",
|
|
||||||
"test_uuid",
|
|
||||||
]:
|
|
||||||
runTest("tests/keyfile/" & filename)
|
|
||||||
|
|
||||||
task test_keyfile, "run keyfile tests":
|
task test_keyfile, "run keyfile tests":
|
||||||
runKeyfileTests()
|
runKeyfileTests()
|
||||||
|
|
||||||
proc runKeysTests() =
|
proc runKeysTests() =
|
||||||
for filename in [
|
runTest("tests/keys/all_tests")
|
||||||
"test_keys",
|
|
||||||
"test_private_public_key_consistency"
|
|
||||||
]:
|
|
||||||
runTest("tests/keys/" & filename)
|
|
||||||
|
|
||||||
task test_keys, "run keys tests":
|
task test_keys, "run keys tests":
|
||||||
runKeysTests()
|
runKeysTests()
|
||||||
|
@ -77,10 +70,15 @@ proc runTrieTests() =
|
||||||
task test_trie, "run trie tests":
|
task test_trie, "run trie tests":
|
||||||
runTrieTests()
|
runTrieTests()
|
||||||
|
|
||||||
|
proc runDbTests() =
|
||||||
|
runTest("tests/db/all_tests")
|
||||||
|
|
||||||
|
task test_db, "run db tests":
|
||||||
|
runDbTests()
|
||||||
|
|
||||||
task test, "run tests":
|
task test, "run tests":
|
||||||
for filename in [
|
for filename in [
|
||||||
"test_bloom",
|
"test_bloom",
|
||||||
"test_common",
|
|
||||||
]:
|
]:
|
||||||
runTest("tests/" & filename)
|
runTest("tests/" & filename)
|
||||||
|
|
||||||
|
@ -89,4 +87,4 @@ task test, "run tests":
|
||||||
runP2pTests()
|
runP2pTests()
|
||||||
runRlpTests()
|
runRlpTests()
|
||||||
runTrieTests()
|
runTrieTests()
|
||||||
|
runDbTests()
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
# beacon_chain
|
||||||
|
# Copyright (c) 2018-2020 Status Research & Development GmbH
|
||||||
|
# Licensed and distributed under either of
|
||||||
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||||
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
## Simple Key-Value store database interface that allows creating multiple
|
||||||
|
## tables within each store
|
||||||
|
|
||||||
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
|
import
|
||||||
|
stew/results,
|
||||||
|
tables, hashes, sets
|
||||||
|
|
||||||
|
export results
|
||||||
|
|
||||||
|
type
|
||||||
|
MemStoreRef* = ref object of RootObj
|
||||||
|
records: Table[seq[byte], seq[byte]]
|
||||||
|
# TODO interaction with this table would benefit from heterogenous lookup
|
||||||
|
# (see `@key` below)
|
||||||
|
# https://github.com/nim-lang/Nim/issues/7457
|
||||||
|
|
||||||
|
KvResult*[T] = Result[T, string]
|
||||||
|
|
||||||
|
DataProc* = proc(val: openArray[byte]) {.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
|
PutProc = proc (db: RootRef, key, val: openArray[byte]): KvResult[void] {.nimcall, gcsafe, raises: [Defect].}
|
||||||
|
GetProc = proc (db: RootRef, key: openArray[byte], onData: DataProc): KvResult[bool] {.nimcall, gcsafe, raises: [Defect].}
|
||||||
|
DelProc = proc (db: RootRef, key: openArray[byte]): KvResult[void] {.nimcall, gcsafe, raises: [Defect].}
|
||||||
|
ContainsProc = proc (db: RootRef, key: openArray[byte]): KvResult[bool] {.nimcall, gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
|
KvStoreRef* = ref object
|
||||||
|
## Key-Value store virtual interface
|
||||||
|
obj: RootRef
|
||||||
|
putProc: PutProc
|
||||||
|
getProc: GetProc
|
||||||
|
delProc: DelProc
|
||||||
|
containsProc: ContainsProc
|
||||||
|
|
||||||
|
template put*(dbParam: KvStoreRef, key, val: openArray[byte]): KvResult[void] =
|
||||||
|
## Store ``value`` at ``key`` - overwrites existing value if already present
|
||||||
|
let db = dbParam
|
||||||
|
db.putProc(db.obj, key, val)
|
||||||
|
|
||||||
|
template get*(dbParam: KvStoreRef, key: openArray[byte], onData: untyped): KvResult[bool] =
|
||||||
|
## Retrive value at ``key`` and call ``onData`` with the value. The data is
|
||||||
|
## valid for the duration of the callback.
|
||||||
|
## ``onData``: ``proc(data: openArray[byte])``
|
||||||
|
## returns true if found and false otherwise.
|
||||||
|
let db = dbParam
|
||||||
|
db.getProc(db.obj, key, onData)
|
||||||
|
|
||||||
|
template del*(dbParam: KvStoreRef, key: openArray[byte]): KvResult[void] =
|
||||||
|
## Remove value at ``key`` from store - do nothing if the value is not present
|
||||||
|
let db = dbParam
|
||||||
|
db.delProc(db.obj, key)
|
||||||
|
|
||||||
|
template contains*(dbParam: KvStoreRef, key: openArray[byte]): KvResult[bool] =
|
||||||
|
## Return true iff ``key`` has a value in store
|
||||||
|
let db = dbParam
|
||||||
|
db.containsProc(db.obj, key)
|
||||||
|
|
||||||
|
proc putImpl[T](db: RootRef, key, val: openArray[byte]): KvResult[void] =
|
||||||
|
mixin put
|
||||||
|
put(T(db), key, val)
|
||||||
|
|
||||||
|
proc getImpl[T](db: RootRef, key: openArray[byte], onData: DataProc): KvResult[bool] =
|
||||||
|
mixin get
|
||||||
|
get(T(db), key, onData)
|
||||||
|
|
||||||
|
proc delImpl[T](db: RootRef, key: openArray[byte]): KvResult[void] =
|
||||||
|
mixin del
|
||||||
|
del(T(db), key)
|
||||||
|
|
||||||
|
proc containsImpl[T](db: RootRef, key: openArray[byte]): KvResult[bool] =
|
||||||
|
mixin contains
|
||||||
|
contains(T(db), key)
|
||||||
|
|
||||||
|
func kvStore*[T: RootRef](x: T): KvStoreRef =
|
||||||
|
mixin del, get, put, contains
|
||||||
|
|
||||||
|
KvStoreRef(
|
||||||
|
obj: x,
|
||||||
|
putProc: putImpl[T],
|
||||||
|
getProc: getImpl[T],
|
||||||
|
delProc: delImpl[T],
|
||||||
|
containsProc: containsImpl[T]
|
||||||
|
)
|
||||||
|
|
||||||
|
proc get*(db: MemStoreRef, key: openArray[byte], onData: DataProc): KvResult[bool] =
|
||||||
|
db.records.withValue(@key, v):
|
||||||
|
onData(v[])
|
||||||
|
return ok(true)
|
||||||
|
|
||||||
|
ok(false)
|
||||||
|
|
||||||
|
proc del*(db: MemStoreRef, key: openArray[byte]): KvResult[void] =
|
||||||
|
db.records.del(@key)
|
||||||
|
ok()
|
||||||
|
|
||||||
|
proc contains*(db: MemStoreRef, key: openArray[byte]): KvResult[bool] =
|
||||||
|
ok(db.records.contains(@key))
|
||||||
|
|
||||||
|
proc put*(db: MemStoreRef, key, val: openArray[byte]): KvResult[void] =
|
||||||
|
db.records[@key] = @val
|
||||||
|
ok()
|
||||||
|
|
||||||
|
proc init*(T: type MemStoreRef): T =
|
||||||
|
T(
|
||||||
|
records: initTable[seq[byte], seq[byte]]()
|
||||||
|
)
|
|
@ -0,0 +1,46 @@
|
||||||
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
|
import os, rocksdb, ./kvstore, stew/results
|
||||||
|
|
||||||
|
export results
|
||||||
|
|
||||||
|
const maxOpenFiles = 512
|
||||||
|
|
||||||
|
type
|
||||||
|
RocksStoreRef* = ref object of RootObj
|
||||||
|
store: RocksDBInstance
|
||||||
|
|
||||||
|
proc get*(db: RocksStoreRef, key: openarray[byte], onData: kvstore.DataProc): KvResult[bool] =
|
||||||
|
db.store.get(key, onData)
|
||||||
|
|
||||||
|
proc put*(db: RocksStoreRef, key, value: openarray[byte]): KvResult[void] =
|
||||||
|
db.store.put(key, value)
|
||||||
|
|
||||||
|
proc contains*(db: RocksStoreRef, key: openarray[byte]): KvResult[bool] =
|
||||||
|
db.store.contains(key)
|
||||||
|
|
||||||
|
proc del*(db: RocksStoreRef, key: openarray[byte]): KvResult[void] =
|
||||||
|
db.store.del(key)
|
||||||
|
|
||||||
|
proc close*(db: RocksStoreRef) =
|
||||||
|
db.store.close
|
||||||
|
|
||||||
|
proc init*(
|
||||||
|
T: type RocksStoreRef, basePath: string, name: string,
|
||||||
|
readOnly = false): KvResult[T] =
|
||||||
|
let
|
||||||
|
dataDir = basePath / name / "data"
|
||||||
|
backupsDir = basePath / name / "backups"
|
||||||
|
|
||||||
|
try:
|
||||||
|
createDir(dataDir)
|
||||||
|
createDir(backupsDir)
|
||||||
|
except OSError, IOError:
|
||||||
|
return err("rocksdb: cannot create database directory")
|
||||||
|
|
||||||
|
var store: RocksDBInstance
|
||||||
|
if (let v = store.init(
|
||||||
|
dataDir, backupsDir, readOnly, maxOpenFiles = maxOpenFiles); v.isErr):
|
||||||
|
return err(v.error)
|
||||||
|
|
||||||
|
ok(T(store: store))
|
|
@ -0,0 +1,158 @@
|
||||||
|
## Implementation of KvStore based on sqlite3
|
||||||
|
|
||||||
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
|
import
|
||||||
|
os,
|
||||||
|
sqlite3_abi,
|
||||||
|
./kvstore
|
||||||
|
|
||||||
|
export kvstore
|
||||||
|
|
||||||
|
type
|
||||||
|
SqStoreRef* = ref object of RootObj
|
||||||
|
env: ptr sqlite3
|
||||||
|
selectStmt, insertStmt, deleteStmt: ptr sqlite3_stmt
|
||||||
|
|
||||||
|
template checkErr(op, cleanup: untyped) =
|
||||||
|
if (let v = (op); v != SQLITE_OK):
|
||||||
|
cleanup
|
||||||
|
return err($sqlite3_errstr(v))
|
||||||
|
|
||||||
|
template checkErr(op) =
|
||||||
|
checkErr(op): discard
|
||||||
|
|
||||||
|
proc bindBlob(s: ptr sqlite3_stmt, n: int, blob: openarray[byte]): cint =
|
||||||
|
sqlite3_bind_blob(s, n.cint, unsafeAddr blob[0], blob.len.cint, nil)
|
||||||
|
|
||||||
|
proc get*(db: SqStoreRef, key: openarray[byte], onData: DataProc): KvResult[bool] =
|
||||||
|
checkErr sqlite3_reset(db.selectStmt)
|
||||||
|
checkErr sqlite3_clear_bindings(db.selectStmt)
|
||||||
|
checkErr bindBlob(db.selectStmt, 1, key)
|
||||||
|
|
||||||
|
let v = sqlite3_step(db.selectStmt)
|
||||||
|
case v
|
||||||
|
of SQLITE_ROW:
|
||||||
|
let
|
||||||
|
p = cast[ptr UncheckedArray[byte]](sqlite3_column_blob(db.selectStmt, 0))
|
||||||
|
l = sqlite3_column_bytes(db.selectStmt, 0)
|
||||||
|
onData(toOpenArray(p, 0, l-1))
|
||||||
|
ok(true)
|
||||||
|
of SQLITE_DONE:
|
||||||
|
ok(false)
|
||||||
|
else:
|
||||||
|
err($sqlite3_errstr(v))
|
||||||
|
|
||||||
|
proc put*(db: SqStoreRef, key, value: openarray[byte]): KvResult[void] =
|
||||||
|
checkErr sqlite3_reset(db.insertStmt)
|
||||||
|
checkErr sqlite3_clear_bindings(db.insertStmt)
|
||||||
|
|
||||||
|
checkErr bindBlob(db.insertStmt, 1, key)
|
||||||
|
checkErr bindBlob(db.insertStmt, 2, value)
|
||||||
|
|
||||||
|
if (let v = sqlite3_step(db.insertStmt); v != SQLITE_DONE):
|
||||||
|
err($sqlite3_errstr(v))
|
||||||
|
else:
|
||||||
|
ok()
|
||||||
|
|
||||||
|
proc contains*(db: SqStoreRef, key: openarray[byte]): KvResult[bool] =
|
||||||
|
checkErr sqlite3_reset(db.selectStmt)
|
||||||
|
checkErr sqlite3_clear_bindings(db.selectStmt)
|
||||||
|
|
||||||
|
checkErr bindBlob(db.selectStmt, 1, key)
|
||||||
|
|
||||||
|
let v = sqlite3_step(db.selectStmt)
|
||||||
|
case v
|
||||||
|
of SQLITE_ROW: ok(true)
|
||||||
|
of SQLITE_DONE: ok(false)
|
||||||
|
else: err($sqlite3_errstr(v))
|
||||||
|
|
||||||
|
proc del*(db: SqStoreRef, key: openarray[byte]): KvResult[void] =
|
||||||
|
checkErr sqlite3_reset(db.deleteStmt)
|
||||||
|
checkErr sqlite3_clear_bindings(db.deleteStmt)
|
||||||
|
|
||||||
|
checkErr bindBlob(db.deleteStmt, 1, key)
|
||||||
|
|
||||||
|
if (let v = sqlite3_step(db.deleteStmt); v != SQLITE_DONE):
|
||||||
|
err($sqlite3_errstr(v))
|
||||||
|
else:
|
||||||
|
ok()
|
||||||
|
|
||||||
|
proc close*(db: SqStoreRef) =
|
||||||
|
discard sqlite3_finalize(db.insertStmt)
|
||||||
|
discard sqlite3_finalize(db.selectStmt)
|
||||||
|
discard sqlite3_finalize(db.deleteStmt)
|
||||||
|
|
||||||
|
discard sqlite3_close(db.env)
|
||||||
|
|
||||||
|
db[] = SqStoreRef()[]
|
||||||
|
|
||||||
|
proc init*(
|
||||||
|
T: type SqStoreRef,
|
||||||
|
basePath: string,
|
||||||
|
name: string,
|
||||||
|
readOnly = false,
|
||||||
|
inMemory = false): KvResult[T] =
|
||||||
|
var
|
||||||
|
env: ptr sqlite3
|
||||||
|
|
||||||
|
let
|
||||||
|
name =
|
||||||
|
if inMemory: ":memory:"
|
||||||
|
else: basepath / name & ".sqlite3"
|
||||||
|
flags =
|
||||||
|
if readOnly: SQLITE_OPEN_READONLY
|
||||||
|
else: SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE
|
||||||
|
|
||||||
|
if not inMemory:
|
||||||
|
try:
|
||||||
|
createDir(basePath)
|
||||||
|
except OSError, IOError:
|
||||||
|
return err("`sqlite: cannot create database directory")
|
||||||
|
|
||||||
|
checkErr sqlite3_open_v2(name, addr env, flags.cint, nil)
|
||||||
|
|
||||||
|
template prepare(q: string, cleanup: untyped): ptr sqlite3_stmt =
|
||||||
|
var s: ptr sqlite3_stmt
|
||||||
|
checkErr sqlite3_prepare_v2(env, q, q.len.cint, addr s, nil):
|
||||||
|
cleanup
|
||||||
|
discard sqlite3_close(env)
|
||||||
|
s
|
||||||
|
|
||||||
|
template checkExec(q: string) =
|
||||||
|
let s = prepare(q): discard
|
||||||
|
|
||||||
|
if (let x = sqlite3_step(s); x != SQLITE_DONE):
|
||||||
|
discard sqlite3_finalize(s)
|
||||||
|
discard sqlite3_close(env)
|
||||||
|
return err($sqlite3_errstr(x))
|
||||||
|
|
||||||
|
if (let x = sqlite3_finalize(s); x != SQLITE_OK):
|
||||||
|
discard sqlite3_close(env)
|
||||||
|
return err($sqlite3_errstr(x))
|
||||||
|
|
||||||
|
# TODO: check current version and implement schema versioning
|
||||||
|
checkExec "PRAGMA user_version = 1;"
|
||||||
|
|
||||||
|
checkExec """
|
||||||
|
CREATE TABLE IF NOT EXISTS kvstore(
|
||||||
|
key BLOB PRIMARY KEY,
|
||||||
|
value BLOB
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
"""
|
||||||
|
|
||||||
|
let
|
||||||
|
selectStmt = prepare "SELECT value FROM kvstore WHERE key = ?;":
|
||||||
|
discard
|
||||||
|
insertStmt = prepare "INSERT OR REPLACE INTO kvstore(key, value) VALUES (?, ?);":
|
||||||
|
discard sqlite3_finalize(selectStmt)
|
||||||
|
deleteStmt = prepare "DELETE FROM kvstore WHERE key = ?;":
|
||||||
|
discard sqlite3_finalize(selectStmt)
|
||||||
|
discard sqlite3_finalize(insertStmt)
|
||||||
|
|
||||||
|
ok(SqStoreRef(
|
||||||
|
env: env,
|
||||||
|
selectStmt: selectStmt,
|
||||||
|
insertStmt: insertStmt,
|
||||||
|
deleteStmt: deleteStmt
|
||||||
|
))
|
|
@ -1,18 +0,0 @@
|
||||||
type
|
|
||||||
StorageError* = object of CatchableError
|
|
||||||
|
|
||||||
template raiseStorageInitError* =
|
|
||||||
raise newException(StorageError, "failure to initialize storage")
|
|
||||||
|
|
||||||
template raiseKeyReadError*(key: auto) =
|
|
||||||
raise newException(StorageError, "failed to read key " & $key)
|
|
||||||
|
|
||||||
template raiseKeyWriteError*(key: auto) =
|
|
||||||
raise newException(StorageError, "failed to write key " & $key)
|
|
||||||
|
|
||||||
template raiseKeySearchError*(key: auto) =
|
|
||||||
raise newException(StorageError, "failure during search for key " & $key)
|
|
||||||
|
|
||||||
template raiseKeyDeletionError*(key: auto) =
|
|
||||||
raise newException(StorageError, "failure to delete key " & $key)
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
import
|
|
||||||
tables, sets,
|
|
||||||
eth/trie/db
|
|
||||||
|
|
||||||
type
|
|
||||||
CachingDB* = ref object of RootObj
|
|
||||||
backing: TrieDatabaseRef
|
|
||||||
changed: Table[seq[byte], seq[byte]]
|
|
||||||
deleted: HashSet[seq[byte]]
|
|
||||||
|
|
||||||
proc newCachingDB*(backing: TrieDatabaseRef): CachingDB =
|
|
||||||
result.new()
|
|
||||||
result.backing = backing
|
|
||||||
result.changed = initTable[seq[byte], seq[byte]]()
|
|
||||||
result.deleted = initHashSet[seq[byte]]()
|
|
||||||
|
|
||||||
proc get*(db: CachingDB, key: openarray[byte]): seq[byte] =
|
|
||||||
let key = @key
|
|
||||||
result = db.changed.getOrDefault(key)
|
|
||||||
if result.len == 0 and key notin db.deleted:
|
|
||||||
result = db.backing.get(key)
|
|
||||||
|
|
||||||
proc put*(db: CachingDB, key, value: openarray[byte]) =
|
|
||||||
let key = @key
|
|
||||||
db.deleted.excl(key)
|
|
||||||
db.changed[key] = @value
|
|
||||||
|
|
||||||
proc contains*(db: CachingDB, key: openarray[byte]): bool =
|
|
||||||
let key = @key
|
|
||||||
result = key in db.changed
|
|
||||||
if not result and key notin db.deleted:
|
|
||||||
result = db.backing.contains(key)
|
|
||||||
|
|
||||||
proc del*(db: CachingDB, key: openarray[byte]) =
|
|
||||||
let key = @key
|
|
||||||
db.changed.del(key)
|
|
||||||
db.deleted.incl(key)
|
|
||||||
|
|
||||||
proc commit*(db: CachingDB) =
|
|
||||||
for k in db.deleted:
|
|
||||||
db.backing.del(k)
|
|
||||||
|
|
||||||
for k, v in db.changed:
|
|
||||||
db.backing.put(k, v)
|
|
||||||
|
|
|
@ -1,174 +0,0 @@
|
||||||
import os, eth/trie/[trie_defs, db_tracing]
|
|
||||||
import backend_defs
|
|
||||||
|
|
||||||
when defined(windows):
|
|
||||||
const Lib = "lmdb.dll"
|
|
||||||
elif defined(macosx):
|
|
||||||
const Lib = "liblmdb.dylib"
|
|
||||||
else:
|
|
||||||
const Lib = "liblmdb.so"
|
|
||||||
|
|
||||||
const
|
|
||||||
MDB_NOSUBDIR = 0x4000
|
|
||||||
MDB_RDONLY = 0x20000
|
|
||||||
MDB_NOTFOUND = -30798
|
|
||||||
|
|
||||||
# TODO This implementation has several issues on restricted platforms, possibly
|
|
||||||
# due to mmap restrictions - see:
|
|
||||||
# https://github.com/status-im/nim-beacon-chain/issues/732
|
|
||||||
# https://github.com/status-im/nim-beacon-chain/issues/688
|
|
||||||
# It also has other issues, including exception safety:
|
|
||||||
# https://github.com/status-im/nim-beacon-chain/pull/809
|
|
||||||
when defined(cpu64):
|
|
||||||
const LMDB_MAP_SIZE = 1024'u64 * 1024'u64 * 1024'u64 * 10'u64 # 10TB enough?
|
|
||||||
else:
|
|
||||||
const LMDB_MAP_SIZE = 1024'u64 * 1024'u64 * 1024'u64 # 32bit limitation
|
|
||||||
|
|
||||||
type
|
|
||||||
MDB_Env = distinct pointer
|
|
||||||
MDB_Txn = distinct pointer
|
|
||||||
MDB_Dbi = distinct cuint
|
|
||||||
|
|
||||||
MDB_val = object
|
|
||||||
mv_size: csize_t
|
|
||||||
mv_data: pointer
|
|
||||||
|
|
||||||
# this is only a subset of LMDB API needed in nimbus
|
|
||||||
proc mdb_env_create(env: var MDB_Env): cint {.cdecl, dynlib: Lib, importc: "mdb_env_create".}
|
|
||||||
proc mdb_env_open(env: MDB_Env, path: cstring, flags: cuint, mode: cint): cint {.cdecl, dynlib: Lib, importc: "mdb_env_open".}
|
|
||||||
proc mdb_txn_begin(env: MDB_Env, parent: MDB_Txn, flags: cuint, txn: var MDB_Txn): cint {.cdecl, dynlib: Lib, importc: "mdb_txn_begin".}
|
|
||||||
proc mdb_txn_commit(txn: MDB_Txn): cint {.cdecl, dynlib: Lib, importc: "mdb_txn_commit".}
|
|
||||||
proc mdb_dbi_open(txn: MDB_Txn, name: cstring, flags: cuint, dbi: var MDB_Dbi): cint {.cdecl, dynlib: Lib, importc: "mdb_dbi_open".}
|
|
||||||
proc mdb_dbi_close(env: MDB_Env, dbi: MDB_Dbi) {.cdecl, dynlib: Lib, importc: "mdb_dbi_close".}
|
|
||||||
proc mdb_env_close(env: MDB_Env) {.cdecl, dynlib: Lib, importc: "mdb_env_close".}
|
|
||||||
|
|
||||||
proc mdb_get(txn: MDB_Txn, dbi: MDB_Dbi, key: var MDB_val, data: var MDB_val): cint {.cdecl, dynlib: Lib, importc: "mdb_get".}
|
|
||||||
proc mdb_del(txn: MDB_Txn, dbi: MDB_Dbi, key: var MDB_val, data: ptr MDB_val): cint {.cdecl, dynlib: Lib, importc: "mdb_del".}
|
|
||||||
proc mdb_put(txn: MDB_Txn, dbi: MDB_Dbi, key: var MDB_val, data: var MDB_val, flags: cuint): cint {.cdecl, dynlib: Lib, importc: "mdb_put".}
|
|
||||||
|
|
||||||
proc mdb_env_set_mapsize(env: MDB_Env, size: uint64): cint {.cdecl, dynlib: Lib, importc: "mdb_env_set_mapsize".}
|
|
||||||
|
|
||||||
type
|
|
||||||
LmdbChainDB* = ref object of RootObj
|
|
||||||
env: MDB_Env
|
|
||||||
txn: MDB_Txn
|
|
||||||
dbi: MDB_Dbi
|
|
||||||
manualCommit: bool
|
|
||||||
|
|
||||||
ChainDB* = LmdbChainDB
|
|
||||||
|
|
||||||
# call txBegin and txCommit if you want to disable auto-commit
|
|
||||||
proc txBegin*(db: ChainDB, manualCommit = true): bool =
|
|
||||||
result = true
|
|
||||||
if manualCommit:
|
|
||||||
db.manualCommit = true
|
|
||||||
else:
|
|
||||||
if db.manualCommit: return
|
|
||||||
result = mdb_txn_begin(db.env, MDB_Txn(nil), 0, db.txn) == 0
|
|
||||||
result = result and mdb_dbi_open(db.txn, nil, 0, db.dbi) == 0
|
|
||||||
|
|
||||||
proc txCommit*(db: ChainDB, manualCommit = true): bool =
|
|
||||||
result = true
|
|
||||||
if manualCommit:
|
|
||||||
db.manualCommit = false
|
|
||||||
else:
|
|
||||||
if db.manualCommit: return
|
|
||||||
result = mdb_txn_commit(db.txn) == 0
|
|
||||||
mdb_dbi_close(db.env, db.dbi)
|
|
||||||
|
|
||||||
proc toMdbVal(val: openArray[byte]): MDB_Val =
|
|
||||||
result.mv_size = csize_t(val.len)
|
|
||||||
result.mv_data = unsafeAddr val[0]
|
|
||||||
|
|
||||||
proc get*(db: ChainDB, key: openarray[byte]): seq[byte] =
|
|
||||||
if key.len == 0: return
|
|
||||||
var
|
|
||||||
dbKey = toMdbVal(key)
|
|
||||||
dbVal: MDB_val
|
|
||||||
|
|
||||||
if not db.txBegin(false):
|
|
||||||
raiseKeyReadError(key)
|
|
||||||
|
|
||||||
var errCode = mdb_get(db.txn, db.dbi, dbKey, dbVal)
|
|
||||||
|
|
||||||
if not(errCode == 0 or errCode == MDB_NOTFOUND):
|
|
||||||
raiseKeyReadError(key)
|
|
||||||
|
|
||||||
if dbVal.mv_size > 0 and errCode == 0:
|
|
||||||
result = newSeq[byte](dbVal.mv_size.int)
|
|
||||||
copyMem(result[0].addr, dbVal.mv_data, result.len)
|
|
||||||
else:
|
|
||||||
result = @[]
|
|
||||||
|
|
||||||
traceGet key, result
|
|
||||||
if not db.txCommit(false):
|
|
||||||
raiseKeyReadError(key)
|
|
||||||
|
|
||||||
proc put*(db: ChainDB, key, value: openarray[byte]) =
|
|
||||||
tracePut key, value
|
|
||||||
if key.len == 0 or value.len == 0: return
|
|
||||||
var
|
|
||||||
dbKey = toMdbVal(key)
|
|
||||||
dbVal = toMdbVal(value)
|
|
||||||
|
|
||||||
if not db.txBegin(false):
|
|
||||||
raiseKeyWriteError(key)
|
|
||||||
|
|
||||||
var ok = mdb_put(db.txn, db.dbi, dbKey, dbVal, 0) == 0
|
|
||||||
if not ok:
|
|
||||||
raiseKeyWriteError(key)
|
|
||||||
|
|
||||||
if not db.txCommit(false):
|
|
||||||
raiseKeyWriteError(key)
|
|
||||||
|
|
||||||
proc contains*(db: ChainDB, key: openarray[byte]): bool =
|
|
||||||
if key.len == 0: return
|
|
||||||
var
|
|
||||||
dbKey = toMdbVal(key)
|
|
||||||
dbVal: MDB_val
|
|
||||||
|
|
||||||
if not db.txBegin(false):
|
|
||||||
raiseKeySearchError(key)
|
|
||||||
|
|
||||||
result = mdb_get(db.txn, db.dbi, dbKey, dbVal) == 0
|
|
||||||
|
|
||||||
if not db.txCommit(false):
|
|
||||||
raiseKeySearchError(key)
|
|
||||||
|
|
||||||
proc del*(db: ChainDB, key: openarray[byte]) =
|
|
||||||
traceDel key
|
|
||||||
if key.len == 0: return
|
|
||||||
var
|
|
||||||
dbKey = toMdbVal(key)
|
|
||||||
|
|
||||||
if not db.txBegin(false):
|
|
||||||
raiseKeyDeletionError(key)
|
|
||||||
|
|
||||||
var errCode = mdb_del(db.txn, db.dbi, dbKey, nil)
|
|
||||||
if not(errCode == 0 or errCode == MDB_NOTFOUND):
|
|
||||||
raiseKeyDeletionError(key)
|
|
||||||
|
|
||||||
if not db.txCommit(false):
|
|
||||||
raiseKeyDeletionError(key)
|
|
||||||
|
|
||||||
proc close*(db: ChainDB) =
|
|
||||||
mdb_env_close(db.env)
|
|
||||||
|
|
||||||
proc newChainDB*(basePath: string, readOnly = false): ChainDB =
|
|
||||||
result.new()
|
|
||||||
|
|
||||||
let dataDir = basePath / "nimbus.db"
|
|
||||||
var ok = mdb_env_create(result.env) == 0
|
|
||||||
if not ok: raiseStorageInitError()
|
|
||||||
|
|
||||||
ok = mdb_env_set_mapsize(result.env, LMDB_MAP_SIZE) == 0
|
|
||||||
if not ok: raiseStorageInitError()
|
|
||||||
|
|
||||||
var openFlags = MDB_NOSUBDIR
|
|
||||||
if readOnly: openFlags = openFlags or MDB_RDONLY
|
|
||||||
# file mode ignored on windows
|
|
||||||
ok = mdb_env_open(result.env, dataDir, openFlags.cuint, 0o664.cint) == 0
|
|
||||||
if not ok: raiseStorageInitError()
|
|
||||||
|
|
||||||
if not readOnly:
|
|
||||||
result.put(emptyRlpHash.data, emptyRlp)
|
|
|
@ -1,56 +0,0 @@
|
||||||
import os, rocksdb, eth/trie/[trie_defs, db_tracing]
|
|
||||||
import backend_defs
|
|
||||||
|
|
||||||
type
|
|
||||||
RocksChainDB* = ref object of RootObj
|
|
||||||
store: RocksDBInstance
|
|
||||||
|
|
||||||
ChainDB* = RocksChainDB
|
|
||||||
|
|
||||||
# Maximum open files for rocksdb, set to 512 to be safe for usual 1024 Linux
|
|
||||||
# limit per application
|
|
||||||
const maxOpenFiles = 512
|
|
||||||
|
|
||||||
proc get*(db: ChainDB, key: openarray[byte]): seq[byte] =
|
|
||||||
let s = db.store.getBytes(key)
|
|
||||||
if s.isOk:
|
|
||||||
result = s.value
|
|
||||||
traceGet key, result
|
|
||||||
elif s.error.len == 0:
|
|
||||||
discard
|
|
||||||
else:
|
|
||||||
raiseKeyReadError(key)
|
|
||||||
|
|
||||||
proc put*(db: ChainDB, key, value: openarray[byte]) =
|
|
||||||
tracePut key, value
|
|
||||||
let s = db.store.put(key, value)
|
|
||||||
if not s.isOk: raiseKeyWriteError(key)
|
|
||||||
|
|
||||||
proc contains*(db: ChainDB, key: openarray[byte]): bool =
|
|
||||||
let s = db.store.contains(key)
|
|
||||||
if not s.isOk: raiseKeySearchError(key)
|
|
||||||
return s.value
|
|
||||||
|
|
||||||
proc del*(db: ChainDB, key: openarray[byte]) =
|
|
||||||
traceDel key
|
|
||||||
let s = db.store.del(key)
|
|
||||||
if not s.isOk: raiseKeyDeletionError(key)
|
|
||||||
|
|
||||||
proc close*(db: ChainDB) =
|
|
||||||
db.store.close
|
|
||||||
|
|
||||||
proc newChainDB*(basePath: string, readOnly = false): ChainDB =
|
|
||||||
result.new()
|
|
||||||
let
|
|
||||||
dataDir = basePath / "data"
|
|
||||||
backupsDir = basePath / "backups"
|
|
||||||
|
|
||||||
createDir(dataDir)
|
|
||||||
createDir(backupsDir)
|
|
||||||
|
|
||||||
let s = result.store.init(dataDir, backupsDir, readOnly,
|
|
||||||
maxOpenFiles = maxOpenFiles)
|
|
||||||
if not s.isOk: raiseStorageInitError()
|
|
||||||
|
|
||||||
if not readOnly:
|
|
||||||
put(result, emptyRlpHash.data, emptyRlp)
|
|
|
@ -1,131 +0,0 @@
|
||||||
import
|
|
||||||
os, sqlite3, stew/ranges/ptr_arith, eth/trie/[db_tracing, trie_defs],
|
|
||||||
backend_defs
|
|
||||||
|
|
||||||
type
|
|
||||||
SqliteChainDB* = ref object of RootObj
|
|
||||||
store: PSqlite3
|
|
||||||
selectStmt, insertStmt, deleteStmt: PStmt
|
|
||||||
|
|
||||||
ChainDB* = SqliteChainDB
|
|
||||||
|
|
||||||
proc put*(db: ChainDB, key, value: openarray[byte])
|
|
||||||
|
|
||||||
proc newChainDB*(basePath: string, inMemory = false): ChainDB =
|
|
||||||
result.new()
|
|
||||||
let dbPath = if inMemory: ":memory:" else: basePath / "nimbus.db"
|
|
||||||
var s = sqlite3.open(dbPath, result.store)
|
|
||||||
if s != SQLITE_OK:
|
|
||||||
raiseStorageInitError()
|
|
||||||
|
|
||||||
template execQuery(q: string) =
|
|
||||||
var s: Pstmt
|
|
||||||
if prepare_v2(result.store, q, q.len.int32, s, nil) == SQLITE_OK:
|
|
||||||
if step(s) != SQLITE_DONE or finalize(s) != SQLITE_OK:
|
|
||||||
raiseStorageInitError()
|
|
||||||
else:
|
|
||||||
raiseStorageInitError()
|
|
||||||
|
|
||||||
# TODO: check current version and implement schema versioning
|
|
||||||
execQuery "PRAGMA user_version = 1;"
|
|
||||||
|
|
||||||
execQuery """
|
|
||||||
CREATE TABLE IF NOT EXISTS trie_nodes(
|
|
||||||
key BLOB PRIMARY KEY,
|
|
||||||
value BLOB
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
template prepare(q: string): PStmt =
|
|
||||||
var s: Pstmt
|
|
||||||
if prepare_v2(result.store, q, q.len.int32, s, nil) != SQLITE_OK:
|
|
||||||
raiseStorageInitError()
|
|
||||||
s
|
|
||||||
|
|
||||||
result.selectStmt = prepare "SELECT value FROM trie_nodes WHERE key = ?;"
|
|
||||||
|
|
||||||
if sqlite3.libversion_number() < 3024000:
|
|
||||||
result.insertStmt = prepare """
|
|
||||||
INSERT OR REPLACE INTO trie_nodes(key, value) VALUES (?, ?);
|
|
||||||
"""
|
|
||||||
else:
|
|
||||||
result.insertStmt = prepare """
|
|
||||||
INSERT INTO trie_nodes(key, value) VALUES (?, ?)
|
|
||||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value;
|
|
||||||
"""
|
|
||||||
|
|
||||||
result.deleteStmt = prepare "DELETE FROM trie_nodes WHERE key = ?;"
|
|
||||||
|
|
||||||
put(result, emptyRlpHash.data, emptyRlp)
|
|
||||||
|
|
||||||
proc bindBlob(s: Pstmt, n: int, blob: openarray[byte]): int32 =
|
|
||||||
sqlite3.bind_blob(s, n.int32, blob.baseAddr, blob.len.int32, nil)
|
|
||||||
|
|
||||||
proc get*(db: ChainDB, key: openarray[byte]): seq[byte] =
|
|
||||||
template check(op) =
|
|
||||||
let status = op
|
|
||||||
if status != SQLITE_OK: raiseKeyReadError(key)
|
|
||||||
|
|
||||||
check reset(db.selectStmt)
|
|
||||||
check clearBindings(db.selectStmt)
|
|
||||||
check bindBlob(db.selectStmt, 1, key)
|
|
||||||
|
|
||||||
case step(db.selectStmt)
|
|
||||||
of SQLITE_ROW:
|
|
||||||
var
|
|
||||||
resStart = columnBlob(db.selectStmt, 0)
|
|
||||||
resLen = columnBytes(db.selectStmt, 0)
|
|
||||||
result = newSeq[byte](resLen)
|
|
||||||
copyMem(result.baseAddr, resStart, resLen)
|
|
||||||
traceGet key, result
|
|
||||||
of SQLITE_DONE:
|
|
||||||
discard
|
|
||||||
else:
|
|
||||||
raiseKeyReadError(key)
|
|
||||||
|
|
||||||
proc put*(db: ChainDB, key, value: openarray[byte]) =
|
|
||||||
tracePut key, value
|
|
||||||
|
|
||||||
template check(op) =
|
|
||||||
let status = op
|
|
||||||
if status != SQLITE_OK: raiseKeyWriteError(key)
|
|
||||||
|
|
||||||
check reset(db.insertStmt)
|
|
||||||
check clearBindings(db.insertStmt)
|
|
||||||
check bindBlob(db.insertStmt, 1, key)
|
|
||||||
check bindBlob(db.insertStmt, 2, value)
|
|
||||||
|
|
||||||
if step(db.insertStmt) != SQLITE_DONE:
|
|
||||||
raiseKeyWriteError(key)
|
|
||||||
|
|
||||||
proc contains*(db: ChainDB, key: openarray[byte]): bool =
|
|
||||||
template check(op) =
|
|
||||||
let status = op
|
|
||||||
if status != SQLITE_OK: raiseKeySearchError(key)
|
|
||||||
|
|
||||||
check reset(db.selectStmt)
|
|
||||||
check clearBindings(db.selectStmt)
|
|
||||||
check bindBlob(db.selectStmt, 1, key)
|
|
||||||
|
|
||||||
case step(db.selectStmt)
|
|
||||||
of SQLITE_ROW: result = true
|
|
||||||
of SQLITE_DONE: result = false
|
|
||||||
else: raiseKeySearchError(key)
|
|
||||||
|
|
||||||
proc del*(db: ChainDB, key: openarray[byte]) =
|
|
||||||
traceDel key
|
|
||||||
|
|
||||||
template check(op) =
|
|
||||||
let status = op
|
|
||||||
if status != SQLITE_OK: raiseKeyDeletionError(key)
|
|
||||||
|
|
||||||
check reset(db.deleteStmt)
|
|
||||||
check clearBindings(db.deleteStmt)
|
|
||||||
check bindBlob(db.deleteStmt, 1, key)
|
|
||||||
|
|
||||||
if step(db.deleteStmt) != SQLITE_DONE:
|
|
||||||
raiseKeyDeletionError(key)
|
|
||||||
|
|
||||||
proc close*(db: ChainDB) =
|
|
||||||
discard sqlite3.close(db.store)
|
|
||||||
reset(db[])
|
|
|
@ -564,7 +564,7 @@ proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
||||||
if orig.isEmpty:
|
if orig.isEmpty:
|
||||||
return origWithNewValue()
|
return origWithNewValue()
|
||||||
|
|
||||||
doAssert orig.isTrieBranch
|
doAssert orig.isTrieBranch, $orig
|
||||||
if orig.listLen == 2:
|
if orig.listLen == 2:
|
||||||
let (isLeaf, k) = orig.extensionNodeKey
|
let (isLeaf, k) = orig.extensionNodeKey
|
||||||
var origValue = orig.listElem(1)
|
var origValue = orig.listElem(1)
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
import
|
||||||
|
test_kvstore_rocksdb,
|
||||||
|
test_kvstore_sqlite3,
|
||||||
|
test_kvstore
|
|
@ -0,0 +1,47 @@
|
||||||
|
{.used.}
|
||||||
|
|
||||||
|
import
|
||||||
|
unittest,
|
||||||
|
../../eth/db/kvstore
|
||||||
|
|
||||||
|
const
|
||||||
|
key = [0'u8, 1, 2, 3]
|
||||||
|
value = [3'u8, 2, 1, 0]
|
||||||
|
value2 = [5'u8, 2, 1, 0]
|
||||||
|
|
||||||
|
proc testKvStore*(db: KvStoreRef) =
|
||||||
|
check:
|
||||||
|
db != nil
|
||||||
|
|
||||||
|
not db.get(key, proc(data: openArray[byte]) = discard)[]
|
||||||
|
not db.contains(key)[]
|
||||||
|
|
||||||
|
db.del(key)[] # does nothing
|
||||||
|
|
||||||
|
db.put(key, value)[]
|
||||||
|
|
||||||
|
var v: seq[byte]
|
||||||
|
proc grab(data: openArray[byte]) =
|
||||||
|
v = @data
|
||||||
|
|
||||||
|
check:
|
||||||
|
db.contains(key)[]
|
||||||
|
db.get(key, grab)[]
|
||||||
|
v == value
|
||||||
|
|
||||||
|
db.put(key, value2)[] # overwrite old value
|
||||||
|
check:
|
||||||
|
db.contains(key)[]
|
||||||
|
db.get(key, grab)[]
|
||||||
|
v == value2
|
||||||
|
|
||||||
|
db.del(key)[]
|
||||||
|
check:
|
||||||
|
not db.get(key, proc(data: openArray[byte]) = discard)[]
|
||||||
|
not db.contains(key)[]
|
||||||
|
|
||||||
|
db.del(key)[] # does nothing
|
||||||
|
|
||||||
|
suite "MemoryStoreRef":
|
||||||
|
test "KvStore interface":
|
||||||
|
testKvStore(kvStore MemStoreRef.init())
|
|
@ -0,0 +1,17 @@
|
||||||
|
{.used.}
|
||||||
|
|
||||||
|
import
|
||||||
|
os, chronicles,
|
||||||
|
unittest,
|
||||||
|
../../eth/db/[kvstore, kvstore_rocksdb],
|
||||||
|
./test_kvstore
|
||||||
|
|
||||||
|
suite "RocksStoreRef":
|
||||||
|
test "KvStore interface":
|
||||||
|
let tmp = getTempDir() / "nimbus-test-db"
|
||||||
|
removeDir(tmp)
|
||||||
|
|
||||||
|
let db = RocksStoreRef.init(tmp, "test")[]
|
||||||
|
defer: db.close()
|
||||||
|
|
||||||
|
testKvStore(kvStore db)
|
|
@ -0,0 +1,14 @@
|
||||||
|
{.used.}
|
||||||
|
|
||||||
|
import
|
||||||
|
os,
|
||||||
|
unittest,
|
||||||
|
../../eth/db/[kvstore, kvstore_sqlite3],
|
||||||
|
./test_kvstore
|
||||||
|
|
||||||
|
suite "SqStoreRef":
|
||||||
|
test "KvStore interface":
|
||||||
|
let db = SqStoreRef.init("", "test", inMemory = true)[]
|
||||||
|
defer: db.close()
|
||||||
|
|
||||||
|
testKvStore(kvStore db)
|
|
@ -0,0 +1,3 @@
|
||||||
|
import
|
||||||
|
./test_keyfile,
|
||||||
|
./test_uuid
|
|
@ -7,6 +7,8 @@
|
||||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
# MIT license (LICENSE-MIT)
|
# MIT license (LICENSE-MIT)
|
||||||
|
|
||||||
|
{.used.}
|
||||||
|
|
||||||
import eth/keys, eth/keyfile/[keyfile], json, os, unittest
|
import eth/keys, eth/keyfile/[keyfile], json, os, unittest
|
||||||
|
|
||||||
# Test vectors copied from
|
# Test vectors copied from
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
# MIT license (LICENSE-MIT)
|
# MIT license (LICENSE-MIT)
|
||||||
|
|
||||||
|
{.used.}
|
||||||
|
|
||||||
import eth/keyfile/uuid, unittest
|
import eth/keyfile/uuid, unittest
|
||||||
|
|
||||||
suite "Cross-platform UUID test suite":
|
suite "Cross-platform UUID test suite":
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import
|
||||||
|
./test_keys,
|
||||||
|
./test_private_public_key_consistency
|
|
@ -7,6 +7,8 @@
|
||||||
# distribution, for details about the copyright.
|
# distribution, for details about the copyright.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
{.used.}
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import eth/keys
|
import eth/keys
|
||||||
import nimcrypto/hash, nimcrypto/keccak, nimcrypto/utils
|
import nimcrypto/hash, nimcrypto/keccak, nimcrypto/utils
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#
|
#
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
{.used.}
|
||||||
|
|
||||||
import eth/keys,
|
import eth/keys,
|
||||||
./config
|
./config
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import
|
import
|
||||||
./test_api_usage,
|
./test_api_usage,
|
||||||
|
./test_common,
|
||||||
./test_json_suite,
|
./test_json_suite,
|
||||||
./test_object_serialization
|
./test_object_serialization
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{.used.}
|
||||||
|
|
||||||
import unittest, eth/common, eth/rlp
|
import unittest, eth/common, eth/rlp
|
||||||
|
|
||||||
proc `==`(a, b: HashOrStatus): bool =
|
proc `==`(a, b: HashOrStatus): bool =
|
|
@ -2,11 +2,9 @@ import
|
||||||
test_bin_trie,
|
test_bin_trie,
|
||||||
test_binaries_utils,
|
test_binaries_utils,
|
||||||
test_branches_utils,
|
test_branches_utils,
|
||||||
test_caching_db_backend,
|
|
||||||
test_examples,
|
test_examples,
|
||||||
test_hexary_trie,
|
test_hexary_trie,
|
||||||
test_json_suite,
|
test_json_suite,
|
||||||
test_sparse_binary_trie,
|
test_sparse_binary_trie,
|
||||||
test_storage_backends,
|
|
||||||
test_transaction_db,
|
test_transaction_db,
|
||||||
test_trie_bitseq
|
test_trie_bitseq
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
{.used.}
|
|
||||||
|
|
||||||
import
|
|
||||||
unittest,
|
|
||||||
eth/trie/db,
|
|
||||||
eth/trie/backends/caching_backend
|
|
||||||
|
|
||||||
let
|
|
||||||
key1 = [0.byte, 0, 1]
|
|
||||||
key2 = [0.byte, 0, 2]
|
|
||||||
key3 = [0.byte, 0, 3]
|
|
||||||
key4 = [0.byte, 0, 4]
|
|
||||||
value1 = [1.byte, 0, 1]
|
|
||||||
value2 = [1.byte, 0, 2]
|
|
||||||
value3 = [1.byte, 0, 3]
|
|
||||||
value4 = [1.byte, 0, 4]
|
|
||||||
|
|
||||||
suite "Caching DB backend":
|
|
||||||
test "Basic test":
|
|
||||||
let mdb = newMemoryDB()
|
|
||||||
mdb.put(key1, value1)
|
|
||||||
mdb.put(key2, value2)
|
|
||||||
let cdb = newCachingDB(mdb)
|
|
||||||
check:
|
|
||||||
cdb.get(key1) == @value1
|
|
||||||
cdb.get(key2) == @value2
|
|
||||||
|
|
||||||
cdb.del(key1)
|
|
||||||
check:
|
|
||||||
key1 notin cdb
|
|
||||||
mdb.get(key1) == @value1
|
|
||||||
|
|
||||||
cdb.put(key3, value3)
|
|
||||||
check:
|
|
||||||
cdb.get(key3) == @value3
|
|
||||||
key3 notin mdb
|
|
||||||
|
|
||||||
cdb.put(key4, value4)
|
|
||||||
cdb.del(key4)
|
|
||||||
check(key4 notin cdb)
|
|
||||||
|
|
||||||
cdb.commit()
|
|
||||||
|
|
||||||
check:
|
|
||||||
key1 notin mdb
|
|
||||||
mdb.get(key2) == @value2
|
|
||||||
mdb.get(key3) == @value3
|
|
||||||
key4 notin mdb
|
|
|
@ -1,65 +0,0 @@
|
||||||
{.used.}
|
|
||||||
|
|
||||||
import
|
|
||||||
unittest, macros, os,
|
|
||||||
eth/trie/backends/[rocksdb_backend, sqlite_backend, lmdb_backend]
|
|
||||||
|
|
||||||
template dummyInstance(T: type SqliteChainDB): auto =
|
|
||||||
sqlite_backend.newChainDB(getTempDir(), inMemory = true)
|
|
||||||
|
|
||||||
template dummyInstance(T: type RocksChainDB): auto =
|
|
||||||
let tmp = getTempDir() / "nimbus-test-db"
|
|
||||||
removeDir(tmp)
|
|
||||||
rocksdb_backend.newChainDB(tmp)
|
|
||||||
|
|
||||||
template dummyInstance(T: type LmdbChainDB): auto =
|
|
||||||
# remove sqlite created database
|
|
||||||
let tmp = getTempDir() / "nimbus.db"
|
|
||||||
removeFile(tmp)
|
|
||||||
lmdb_backend.newChainDB(getTempDir())
|
|
||||||
|
|
||||||
template backendTests(DB) =
|
|
||||||
suite("storage tests: " & astToStr(DB)):
|
|
||||||
setup:
|
|
||||||
var db = dummyInstance(DB)
|
|
||||||
|
|
||||||
teardown:
|
|
||||||
close(db)
|
|
||||||
|
|
||||||
test "basic insertions and deletions":
|
|
||||||
var keyA = [1.byte, 2, 3]
|
|
||||||
var keyB = [1.byte, 2, 4]
|
|
||||||
var value1 = @[1.byte, 2, 3, 4, 5]
|
|
||||||
var value2 = @[7.byte, 8, 9, 10]
|
|
||||||
|
|
||||||
db.put(keyA, value1)
|
|
||||||
|
|
||||||
check:
|
|
||||||
keyA in db
|
|
||||||
keyB notin db
|
|
||||||
|
|
||||||
db.put(keyB, value2)
|
|
||||||
|
|
||||||
check:
|
|
||||||
keyA in db
|
|
||||||
keyB in db
|
|
||||||
|
|
||||||
check:
|
|
||||||
db.get(keyA) == value1
|
|
||||||
db.get(keyB) == value2
|
|
||||||
|
|
||||||
db.del(keyA)
|
|
||||||
db.put(keyB, value1)
|
|
||||||
|
|
||||||
check:
|
|
||||||
keyA notin db
|
|
||||||
keyB in db
|
|
||||||
|
|
||||||
check db.get(keyA).len == 0
|
|
||||||
|
|
||||||
check db.get(keyB) == value1
|
|
||||||
db.del(keyA)
|
|
||||||
|
|
||||||
backendTests(RocksChainDB)
|
|
||||||
backendTests(SqliteChainDB)
|
|
||||||
backendTests(LmdbChainDB)
|
|
Loading…
Reference in New Issue