Skip to content

Commit

Permalink
stores
Browse files Browse the repository at this point in the history
  • Loading branch information
xlc committed Dec 6, 2024
1 parent 9902dd7 commit 9e49f87
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 26 deletions.
48 changes: 48 additions & 0 deletions Database/Sources/Database/JamCoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Blockchain
import Codec
import Foundation
import RocksDBSwift

struct JamCoder<Key: Encodable, Value: Codable>: StoreCoder {
typealias Key = Key
typealias Value = Value

private let config: ProtocolConfigRef
private let prefix: Data

init(config: ProtocolConfigRef, prefix: Data = Data()) {
self.config = config
self.prefix = prefix
}

func encode(key: Key) throws -> Data {
let encoder = JamEncoder(prefix)
try encoder.encode(key)
return encoder.data
}

func encode(value: Value) throws -> Data {
try JamEncoder.encode(value)
}

func decode(data: Data) throws -> Value {
try JamDecoder.decode(Value.self, from: data, withConfig: config)
}
}

struct NoopCoder: StoreCoder {
typealias Key = Data
typealias Value = Data

func encode(key: Key) throws -> Data {
key
}

func encode(value: Value) throws -> Data {
value
}

func decode(data: Data) throws -> Value {
data
}
}
113 changes: 94 additions & 19 deletions Database/Sources/Database/RocksDBBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,103 @@ import Foundation
import RocksDBSwift
import Utils

enum StoreId: UInt8, ColumnFamilyKey {
// metadata and configurations
case meta
// blocks
// blockHash => blockBody
case blocks
// timeslot => blockHash
// blockNumber => blockHash
case blockIndexes
// state trie
// hash => trie node
// value hash => state value
case state
// ref count
// node hash => ref count
// value hash => ref count
case stateRefs
public enum RocksDBBackendError: Error {
case genesisHashMismatch(expected: Data32, actual: Data)
}

public final class RocksDBBackend: StateBackendProtocol {
public init() {}
public final class RocksDBBackend {
private let db: RocksDB<StoreId>
private let meta: Store<StoreId, NoopCoder>
private let blocks: Store<StoreId, JamCoder<Data32, BlockRef>>

public let genesisBlockHash: Data32

public init(path: URL, config: ProtocolConfigRef, genesisBlockHash: Data32) throws {
db = try RocksDB(path: path)
meta = Store(db: db, column: .meta, coder: NoopCoder())
blocks = Store(db: db, column: .blocks, coder: JamCoder(config: config))

self.genesisBlockHash = genesisBlockHash

let genesis = try meta.get(key: MetaKey.genesisHash.key)
if let genesis {
guard genesis == genesisBlockHash.data else {
throw RocksDBBackendError.genesisHashMismatch(expected: genesisBlockHash, actual: genesis)
}
} else {
// must be a new db
try meta.put(key: MetaKey.genesisHash.key, value: genesisBlockHash.data)
}
}
}

extension RocksDBBackend: BlockchainDataProviderProtocol {
public func hasBlock(hash _: Data32) async throws -> Bool {
fatalError("unimplemented")
}

public func hasState(hash _: Data32) async throws -> Bool {
fatalError("unimplemented")
}

public func isHead(hash _: Data32) async throws -> Bool {
fatalError("unimplemented")
}

public func getBlockNumber(hash _: Data32) async throws -> UInt32 {
fatalError("unimplemented")
}

public func getHeader(hash _: Data32) async throws -> HeaderRef {
fatalError("unimplemented")
}

public func getBlock(hash _: Data32) async throws -> BlockRef {
fatalError("unimplemented")
}

public func getState(hash _: Data32) async throws -> StateRef {
fatalError("unimplemented")
}

public func getFinalizedHead() async throws -> Data32 {
fatalError("unimplemented")
}

public func getHeads() async throws -> Set<Data32> {
fatalError("unimplemented")
}

public func getBlockHash(byTimeslot _: TimeslotIndex) async throws -> Set<Data32> {
fatalError("unimplemented")
}

public func getBlockHash(byNumber _: UInt32) async throws -> Set<Data32> {
fatalError("unimplemented")
}

public func add(block _: BlockRef) async throws {
fatalError("unimplemented")
}

public func add(state _: StateRef) async throws {
fatalError("unimplemented")
}

public func setFinalizedHead(hash _: Data32) async throws {
fatalError("unimplemented")
}

public func updateHead(hash _: Data32, parent _: Data32) async throws {
fatalError("unimplemented")
}

public func remove(hash _: Data32) async throws {
fatalError("unimplemented")
}
}

extension RocksDBBackend: StateBackendProtocol {
public func read(key _: Data) async throws -> Data? {
fatalError("unimplemented")
}
Expand Down
33 changes: 33 additions & 0 deletions Database/Sources/Database/Stores.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Blockchain
import Foundation
import RocksDBSwift
import Utils

enum StoreId: UInt8, ColumnFamilyKey {
// metadata and configurations
case meta = 0
// blocks
// blockHash => blockBody
case blocks = 1
// timeslot => blockHash
// blockNumber => blockHash
case blockIndexes = 2
// state trie
// hash => trie node
// value hash => state value
case state = 3
// ref count
// node hash => ref count
// value hash => ref count
case stateRefs = 4
}

enum MetaKey: UInt8 {
case genesisHash = 0 // Data32
case heads = 1 // Set<Data32>
case finalizedHead = 2 // Data32

var key: Data {
Data([rawValue])
}
}
19 changes: 12 additions & 7 deletions Database/Sources/RocksDBSwift/RocksDB.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ import Utils

public protocol ColumnFamilyKey: Sendable, CaseIterable, Hashable, RawRepresentable<UInt8> {}

public final class RocksDB<CFKey: ColumnFamilyKey>: Sendable {
public enum BatchOperation {
case delete(column: CFKey, key: Data)
case put(column: CFKey, key: Data, value: Data)
}
public enum BatchOperation {
case delete(column: UInt8, key: Data)
case put(column: UInt8, key: Data, value: Data)
}

public final class RocksDB<CFKey: ColumnFamilyKey>: Sendable {
public enum Error: Swift.Error {
case openFailed(message: String)
case putFailed(message: String)
case getFailed(message: String)
case deleteFailed(message: String)
case batchFailed(message: String)
case noData
case invalidColumn(UInt8)
}

private let writeOptions: WriteOptions
Expand Down Expand Up @@ -146,6 +147,10 @@ extension RocksDB {
private func getHandle(column: CFKey) -> OpaquePointer {
cfHandles[Int(column.rawValue)].value
}

private func getHandle(column: UInt8) -> OpaquePointer? {
cfHandles[safe: Int(column)]?.value
}
}

// MARK: - public methods
Expand Down Expand Up @@ -203,14 +208,14 @@ extension RocksDB {
for operation in operations {
switch operation {
case let .delete(column, key):
let handle = getHandle(column: column)
let handle = try getHandle(column: column).unwrap(orError: Error.invalidColumn(column))
try Self.call(key) { ptrs in
let key = ptrs[0]
rocksdb_writebatch_delete_cf(writeBatch, handle, key.ptr, key.count)
}

case let .put(column, key, value):
let handle = getHandle(column: column)
let handle = try getHandle(column: column).unwrap(orError: Error.invalidColumn(column))
try Self.call(key, value) { ptrs in
let key = ptrs[0]
let value = ptrs[1]
Expand Down
30 changes: 30 additions & 0 deletions Database/Sources/RocksDBSwift/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public protocol StoreCoder<Key, Value>: Sendable {
associatedtype Value: Decodable

func encode(key: Key) throws -> Data
func encode(value: Value) throws -> Data
func decode(data: Data) throws -> Value
}

Expand All @@ -26,4 +27,33 @@ public final class Store<CFKey: ColumnFamilyKey, Coder: StoreCoder>: Sendable {

return try data.map { try coder.decode(data: $0) }
}

public func put(key: Coder.Key, value: Coder.Value) throws {
let encodedKey = try coder.encode(key: key)
let encodedValue = try coder.encode(value: value)

try db.put(column: column, key: encodedKey, value: encodedValue)
}

public func delete(key: Coder.Key) throws {
let encodedKey = try coder.encode(key: key)
try db.delete(column: column, key: encodedKey)
}

public func exists(key: Coder.Key) throws -> Bool {
let encodedKey = try coder.encode(key: key)
// it seems like
return try db.get(column: column, key: encodedKey) != nil
}

public func putOperation(key: Coder.Key, value: Coder.Value) throws -> BatchOperation {
let encodedKey = try coder.encode(key: key)
let encodedValue = try coder.encode(value: value)
return .put(column: column.rawValue, key: encodedKey, value: encodedValue)
}

public func deleteOperation(key: Coder.Key) throws -> BatchOperation {
let encodedKey = try coder.encode(key: key)
return .delete(column: column.rawValue, key: encodedKey)
}
}

0 comments on commit 9e49f87

Please sign in to comment.