Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trie -> kv store #677

Merged
merged 3 commits into from
Jan 20, 2020
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,6 @@
url = https://github.com/status-im/nim-bearssl.git
ignore = dirty
branch = master
[submodule "vendor/lmdb"]
path = vendor/lmdb
url = https://github.com/status-im/lmdb.git
8 changes: 1 addition & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ cache:
directories:
- vendor/nimbus-build-system/vendor/Nim/bin
- vendor/go/bin
- rocksdbCache
- jsonTestsCache

git:
Expand All @@ -27,7 +26,6 @@ matrix:
before_install:
- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib"
- sudo apt-get -q update
- sudo apt-get install -y librocksdb-dev
- os: linux
arch: arm64
sudo: required
Expand All @@ -37,14 +35,10 @@ matrix:
before_install:
- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib"
- sudo apt-get -q update
- sudo apt-get install -y libpcre3-dev librocksdb-dev
- sudo apt-get install -y libpcre3-dev
- os: osx
env:
- NPROC=2
before_install:
- launchctl setenv LIBRARY_PATH /usr/local/lib # for RocksDB
# build our own rocksdb to test with a fixed version that we think works
- vendor/nimbus-build-system/scripts/build_rocksdb.sh rocksdbCache


install:
Expand Down
26 changes: 6 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ Nimbus has 4 external dependencies:

* Go 1.12 (for compiling libp2p daemon - being phased out)
* Developer tools (C compiler, Make, Bash, Git)
* [RocksDB](https://github.com/facebook/rocksdb/)
* PCRE

Nim is not an external dependency, Nimbus will build its own local copy.
Expand All @@ -62,28 +61,28 @@ Nim is not an external dependency, Nimbus will build its own local copy.
On common Linux distributions the dependencies can be installed with:
```sh
# Debian and Ubuntu
sudo apt-get install build-essential git golang-go librocksdb-dev libpcre3-dev
sudo apt-get install build-essential git golang-go libpcre3-dev

# Fedora
dnf install @development-tools go rocksdb-devel pcre
dnf install @development-tools go pcre

# Archlinux, using an AUR manager for pcre-static
yourAURmanager -S base-devel go rocksdb pcre-static
yourAURmanager -S base-devel go pcre-static
```

### MacOS

Assuming you use [Homebrew](https://brew.sh/) to manage packages

```sh
brew install go rocksdb pcre
brew install go pcre
```

### Windows

* install [Go](https://golang.org/doc/install#windows)
You can install the developer tools by following the instruction in our [Windows dev environment section](#windows-dev-environment).
It also provides a downloading script for prebuilt PCRE and RocksDB.
It also provides a downloading script for prebuilt PCRE.

If you choose to install Go from source, both Go and Nimbus requires the same initial steps of installing Mingw.

Expand Down Expand Up @@ -220,7 +219,7 @@ Variables -> Path -> Edit -> New -> C:\mingw-w64\mingw64\bin (it's "C:\mingw-w64

Install [Git for Windows](https://gitforwindows.org/) and use a "Git Bash" shell to clone and build nim-beacon-chain.

If you don't want to compile RocksDB and SQLite separately, you can fetch pre-compiled DLLs with:
If you don't want to compile PCRE separately, you can fetch pre-compiled DLLs with:
```bash
mingw32-make # this first invocation will update the Git submodules
mingw32-make fetch-dlls # this will place the right DLLs for your architecture in the "build/" directory
Expand Down Expand Up @@ -286,19 +285,6 @@ sudo apt-get install git libgflags-dev libsnappy-dev libpcre3-dev
mkdir status
cd status

# Install rocksdb
git clone https://github.com/facebook/rocksdb.git
cd rocksdb
make shared_lib
sudo make install-shared
cd ..

# Raspberry pi doesn't include /usr/local/lib in library search path
# Add it to your profile
echo '# Local compiles (nimbus - rocksdb)' >> ~/.profile
echo 'export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH' >> ~/.profile
echo '' >> ~/.profile

# Install Go at least 1.12 (Buster only includes up to 1.11)
# Raspbian is 32-bit, so the package is go1.XX.X.linux-armv6l.tar.gz (and not arm64)
curl -O https://storage.googleapis.com/golang/go1.13.3.linux-armv6l.tar.gz
Expand Down
26 changes: 11 additions & 15 deletions beacon_chain/beacon_chain_db.nim
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import
json, tables, options,
chronicles, serialization, json_serialization, eth/common/eth_types_json_serialization,
options,
serialization,
spec/[datatypes, digest, crypto],
eth/trie/db, ssz
kvstore, ssz

type
BeaconChainDB* = ref object
## Database storing resolved blocks and states - resolved blocks are such
## blocks that form a chain back to the tail block.
backend: TrieDatabaseRef
backend: KVStoreRef

DbKeyKind = enum
kHashToState
Expand Down Expand Up @@ -61,7 +61,7 @@ func subkey(root: Eth2Digest, slot: Slot): auto =

ret

proc init*(T: type BeaconChainDB, backend: TrieDatabaseRef): BeaconChainDB =
proc init*(T: type BeaconChainDB, backend: KVStoreRef): BeaconChainDB =
T(backend: backend)

proc putBlock*(db: BeaconChainDB, key: Eth2Digest, value: SignedBeaconBlock) =
Expand Down Expand Up @@ -99,22 +99,18 @@ proc putTailBlock*(db: BeaconChainDB, key: Eth2Digest) =
db.backend.put(subkey(kTailBlock), key.data)

proc get(db: BeaconChainDB, key: auto, T: typedesc): Option[T] =
let res = db.backend.get(key)
if res.len != 0:
var res: Option[T]
discard db.backend.get(key, proc (data: openArray[byte]) =
try:
some(SSZ.decode(res, T))
res = some(SSZ.decode(data, T))
except SerializationError:
none(T)
else:
none(T)
discard
)
res

proc getBlock*(db: BeaconChainDB, key: Eth2Digest): Option[SignedBeaconBlock] =
db.get(subkey(SignedBeaconBlock, key), SignedBeaconBlock)

proc getBlock*(db: BeaconChainDB, slot: Slot): Option[SignedBeaconBlock] =
# TODO implement this
discard

proc getState*(db: BeaconChainDB, key: Eth2Digest): Option[BeaconState] =
db.get(subkey(BeaconState, key), BeaconState)

Expand Down
6 changes: 3 additions & 3 deletions beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import
os, net, tables, random, strutils, times, sequtils,

# Nimble packages
stew/[objects, bitseqs, byteutils], stew/ranges/ptr_arith,
stew/[objects, bitseqs, byteutils],
chronos, chronicles, confutils, metrics,
json_serialization/std/[options, sets], serialization/errors,
eth/trie/db, eth/trie/backends/rocksdb_backend, eth/async_utils,
kvstore, kvstore_lmdb, eth/async_utils,

# Local modules
spec/[datatypes, digest, crypto, beaconstate, helpers, validator, network],
Expand Down Expand Up @@ -135,7 +135,7 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async
networkId = getPersistentNetIdentity(conf)
nickname = if conf.nodeName == "auto": shortForm(networkId)
else: conf.nodeName
db = BeaconChainDB.init(trieDB newChainDb(conf.databaseDir))
db = BeaconChainDB.init(kvStore LmdbStoreRef.init(conf.databaseDir))

var mainchainMonitor: MainchainMonitor

Expand Down
94 changes: 94 additions & 0 deletions beacon_chain/kvstore.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Simple Key-Value store database interface
Copy link
Contributor

Choose a reason for hiding this comment

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

TrieDatabaseRef may have been named inappropriately, but it's already a simple key-value store. The only difference is that it comes with an additional support for discardable in-memory transactions, which doesn't complicate the API at all when it's not used.

I assume the get proc signature was changed as an optimisation (to avoid copying the result bytes to a newly allocated sequence). This optimisation could have been added to TrieDatabaseRef as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

it also has logic for maintaining an empty rlp item (thus pulling in the RLP code) and does refcounting in memory, none of which are needed here - this PR is about eliminating surface area for bugs by making sure the code does what it needs to do in the simplest way possible.

get changed for that reason, but there's a semantic change as well which in theory allows for empty values to happen. I do not have enough confidence in the test suite to perform this change on nim-eth itself - that can easily be done by someone more familiar with that code if need be, but with that said nim-beacon-chain and nimbus1 have completely different storage needs - they are likely to continue using different storage engines..

Copy link
Member Author

Choose a reason for hiding this comment

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

fwiw, a practical outcome of the complexity of TrieDatabaseRef is that every implementation of it is faulty in the face of exceptions: leaks, reader/writer transaction blocking - the code raises exceptions but is not written for exception safety


import
tables, hashes, sets

type
MemoryStoreRef* = ref object of RootObj
records: Table[seq[byte], seq[byte]]

DataProc* = proc(val: openArray[byte])
PutProc = proc (db: RootRef, key, val: openArray[byte]) {.gcsafe.}
GetProc = proc (db: RootRef, key: openArray[byte], onData: DataProc): bool {.gcsafe.}
DelProc = proc (db: RootRef, key: openArray[byte]) {.gcsafe.}
ContainsProc = proc (db: RootRef, key: openArray[byte]): bool {.gcsafe.}

KVStoreRef* = ref object
## Key-Value store virtual interface
obj: RootRef
putProc: PutProc
getProc: GetProc
delProc: DelProc
containsProc: ContainsProc

template put*(db: KVStoreRef, key, val: openArray[byte]) =
## Store ``value`` at ``key`` - overwrites existing value if already present
db.putProc(db.obj, key, val)

template get*(db: KVStoreRef, key: openArray[byte], onData: untyped): 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.
db.getProc(db.obj, key, onData)

template del*(db: KVStoreRef, key: openArray[byte]) =
## Remove value at ``key`` from store - do nothing if the value is not present
db.delProc(db.obj, key)

template contains*(db: KVStoreRef, key: openArray[byte]): bool =
## Return true iff ``key`` has a value in store
db.containsProc(db.obj, key)

proc get*(db: MemoryStoreRef, key: openArray[byte], onData: DataProc): bool =
let key = @key
db.records.withValue(key, v):
onData(v[])
return true

proc del*(db: MemoryStoreRef, key: openArray[byte]) =
# TODO: This is quite inefficient and it won't be necessary once
# https://github.com/nim-lang/Nim/issues/7457 is developed.
let key = @key
db.records.del(key)

proc contains*(db: MemoryStoreRef, key: openArray[byte]): bool =
db.records.contains(@key)

proc put*(db: MemoryStoreRef, key, val: openArray[byte]) =
# TODO: This is quite inefficient and it won't be necessary once
# https://github.com/nim-lang/Nim/issues/7457 is developed.
let key = @key
db.records[key] = @val

proc init*(T: type MemoryStoreRef): T =
T(
records: initTable[seq[byte], seq[byte]]()
)

proc putImpl[T](db: RootRef, key, val: openArray[byte]) =
mixin put
put(T(db), key, val)

proc getImpl[T](db: RootRef, key: openArray[byte], onData: DataProc): bool =
mixin get
get(T(db), key, onData)

proc delImpl[T](db: RootRef, key: openArray[byte]) =
mixin del
del(T(db), key)

proc containsImpl[T](db: RootRef, key: openArray[byte]): 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]
)
Loading