Skip to content

Commit

Permalink
feat(internal/client/db) Introduce api.Backend interface and `db.Ba…
Browse files Browse the repository at this point in the history
…ckend` implementation (#4405)

Co-authored-by: Haiko Schol <hs@haikoschol.com>
  • Loading branch information
timwu20 and haikoschol committed Feb 13, 2025
1 parent 7d2ee78 commit ecdd433
Show file tree
Hide file tree
Showing 72 changed files with 6,627 additions and 568 deletions.
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ require (
github.com/jpillora/ipfilter v1.2.9
github.com/karlseguin/ccache/v3 v3.0.6
github.com/klauspost/compress v1.17.11
github.com/li1234yun/gods-generic v0.0.0-20230420031820-5b9f08f4846b
github.com/libp2p/go-libp2p v0.38.2
github.com/libp2p/go-libp2p-kad-dht v0.29.0
github.com/minio/sha256-simd v1.0.1
Expand All @@ -45,6 +44,7 @@ require (
github.com/tetratelabs/wazero v1.1.0
github.com/tidwall/btree v1.7.0
github.com/tyler-smith/go-bip39 v1.1.0
github.com/ugurcsen/gods-generic v0.10.4
go.uber.org/mock v0.5.0
golang.org/x/crypto v0.33.0
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
Expand Down Expand Up @@ -229,3 +229,7 @@ toolchain go1.23.2
replace github.com/tetratelabs/wazero => github.com/ChainSafe/wazero v0.0.0-20240319130522-78b21a59bd5f

replace github.com/centrifuge/go-substrate-rpc-client/v4 => github.com/timwu20/go-substrate-rpc-client/v4 v4.0.0-20231110032757-3d8e441b7303

replace github.com/elastic/go-freelru => github.com/timwu20/go-freelru v0.0.0-20241023201517-deb64adeae4c

replace github.com/ugurcsen/gods-generic => github.com/timwu20/gods-generic v0.0.0-20241206024616-791a209639f8
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,6 @@ github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9pu
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elastic/go-freelru v0.15.0 h1:Jo1aY8JAvpyxbTDJEudrsBfjFDaALpfVv8mxuh9sfvI=
github.com/elastic/go-freelru v0.15.0/go.mod h1:bSdWT4M0lW79K8QbX6XY2heQYSCqD7THoYf82pT/H3I=
github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo=
github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
Expand Down Expand Up @@ -344,8 +342,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/li1234yun/gods-generic v0.0.0-20230420031820-5b9f08f4846b h1:qMDBhRBx0FqFOH9qVZT2Y/7nXjFPqBYHtDdVjnasFFE=
github.com/li1234yun/gods-generic v0.0.0-20230420031820-5b9f08f4846b/go.mod h1:DZQmZyoCj20lR+owZ4HFwOmJ9pMiQtXpGYln6DGuv6Q=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=
Expand Down Expand Up @@ -630,8 +626,12 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/timwu20/go-freelru v0.0.0-20241023201517-deb64adeae4c h1:WKEqrNAA0DIVpTHGV70TonOX7pN0tB8CSkT8Z8Jbxjw=
github.com/timwu20/go-freelru v0.0.0-20241023201517-deb64adeae4c/go.mod h1:bSdWT4M0lW79K8QbX6XY2heQYSCqD7THoYf82pT/H3I=
github.com/timwu20/go-substrate-rpc-client/v4 v4.0.0-20231110032757-3d8e441b7303 h1:FX7wMjDD0sWGWsC9k+stJaYwThbaq6BDT7ArlInU0KI=
github.com/timwu20/go-substrate-rpc-client/v4 v4.0.0-20231110032757-3d8e441b7303/go.mod h1:1p5145LS4BacYYKFstnHScydK9MLjZ15l72v8mbngPQ=
github.com/timwu20/gods-generic v0.0.0-20241206024616-791a209639f8 h1:woES76T+jY3c6JCKAsJGi4X7DdNoBzhnK3xVm3kqvSM=
github.com/timwu20/gods-generic v0.0.0-20241206024616-791a209639f8/go.mod h1:mGYOa88Y5sbw+ADXLpScxjJ7s5iHoWya/YHyeQ4f6c4=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
Expand Down
207 changes: 207 additions & 0 deletions internal/client/api/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Copyright 2024 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package api

import (
"sync"

"github.com/ChainSafe/gossamer/internal/primitives/blockchain"
"github.com/ChainSafe/gossamer/internal/primitives/core/offchain"
"github.com/ChainSafe/gossamer/internal/primitives/runtime"
statemachine "github.com/ChainSafe/gossamer/internal/primitives/state-machine"
"github.com/ChainSafe/gossamer/internal/primitives/storage"
)

// NewBlockState is the state of a new block.
type NewBlockState uint8

const (
// NewBlockStateNormal is a normal block.
NewBlockStateNormal NewBlockState = iota
// NewBlockStateBest is a new best block.
NewBlockStateBest
// NewBlockStateFinal is a newly finalized block.
NewBlockStateFinal
)

// IsBest returns true if equal to [NewBlockStateBest]
func (nbs NewBlockState) IsBest() bool {
return nbs == NewBlockStateBest
}

// IsFinal returns true if equal to [NewBlockStateFinal]
func (nbs NewBlockState) IsFinal() bool {
return nbs == NewBlockStateFinal
}

// BlockImportOperation keeps hold of the inserted block state and data.
type BlockImportOperation[
N runtime.Number,
H runtime.Hash,
Hasher runtime.Hasher[H],
Header runtime.Header[N, H],
E runtime.Extrinsic,
] interface {
// State returns the pending state.
// Returns nil for backends with locally-unavailable state data.
State() (statemachine.Backend[H, Hasher], error)

// SetBlockData will set block data to the transaction.
SetBlockData(
header Header,
body []E,
indexedBody [][]byte,
justifications runtime.Justifications,
state NewBlockState,
) error

// UpdateDBStorage will inject storage data into the database.
UpdateDBStorage(update statemachine.BackendTransaction[H, Hasher]) error

// SetGenesisState will set genesis state. If commit is false the state is saved in memory, but is not written
// to the database.
SetGenesisState(storage storage.Storage, commit bool, stateVersion storage.StateVersion) (H, error)

// ResetStorage will inject storage data into the database replacing any existing data.
ResetStorage(storage storage.Storage, stateVersion storage.StateVersion) (H, error)

// UpdateStorage will set storage changes.
UpdateStorage(update statemachine.StorageCollection, childUpdate statemachine.ChildStorageCollection) error

// UpdateOffchainStorage will write offchain storage changes to the database.
UpdateOffchainStorage(offchainUpdate statemachine.OffchainChangesCollection) error

// InsertAux will insert auxiliary keys.
// Values that are nil respresent the keys should be deleted.
InsertAux(ops AuxDataOperations) error

// MarkFinalized marks a block as finalized.
MarkFinalized(hash H, justification *runtime.Justification) error

// MarkHead marks a block as the new head. If the block import changes the head and MarkHead is called with
// a different block hash, MarkHead will override the changed head as a result of the block import.
MarkHead(hash H) error

// UpdateTransactionIndex adds a transaction index operation.
UpdateTransactionIndex(index []statemachine.IndexOperation) error
}

type KeyValue struct {
Key []byte
Value []byte
}

// AuxStore provides access to an auxiliary database.
//
// This is a simple global database not aware of forks. Can be used for storing auxiliary
// information like total block weight/difficulty for fork resolution purposes as a common use
// case.
type AuxStore interface {
// Insert auxiliary data into key-value store.
//
// Deletions occur after insertions.
InsertAux(insert []KeyValue, delete [][]byte) error

// Query auxiliary data from key-value store.
GetAux(key []byte) ([]byte, error)
}

// Backend is the client backend.
//
// Manages the data layer.
//
// # State Pruning
//
// While an object from StateAt is alive, the state
// should not be pruned. The backend should internally reference-count
// its state objects.
//
// The same applies for live BlockImportOperation instances: while an import operation building on a
// parent P is alive, the state for P should not be pruned.
//
// # Block Pruning
//
// Users can pin blocks in memory by calling PinBlock. When
// a block would be pruned, its value is kept in an in-memory cache
// until it is unpinned via UnpinBlock.
//
// While a block is pinned, its state is also preserved.
//
// The backend should internally reference count the number of pin / unpin calls.
type Backend[
H runtime.Hash,
N runtime.Number,
Hasher runtime.Hasher[H],
Header runtime.Header[N, H],
E runtime.Extrinsic,
] interface {
AuxStore // Insert auxiliary data into key-value store.

// BeginOperation begins a new block insertion transaction with given parent block id.
// When constructing the genesis, this is called with all-zero hash.
BeginOperation() (BlockImportOperation[N, H, Hasher, Header, E], error)

// BeginStateOperation notes an operation to contain state transition.
BeginStateOperation(operation BlockImportOperation[N, H, Hasher, Header, E], block H) error

// CommitOperation will commit block insertion.
CommitOperation(transaction BlockImportOperation[N, H, Hasher, Header, E]) error

// FinalizeBlock will finalize block with given hash.
//
// This should only be called if the parent of the given block has been finalized.
FinalizeBlock(hash H, justification *runtime.Justification) error

// AppendJustification appends justification to the block with the given hash.
//
// This should only be called for blocks that are already finalized.
AppendJustification(hash H, justification runtime.Justification) error

// Blockchain returns reference to blockchain backend.
Blockchain() blockchain.Backend[H, N, Header, E]

// OffchainStorage returns a pointer to offchain storage.
OffchainStorage() offchain.OffchainStorage

// PinBlock pins the block to keep body, justification and state available after pruning.
// Number of pins are reference counted. Users need to make sure to perform
// one call to UnpinBlock per call to PinBlock.
PinBlock(hash H) error

// Unpin the block to allow pruning.
UnpinBlock(hash H)

// HaveStateAt returns true if state for given block is available.
HaveStateAt(hash H, number N) bool

// StateAt returns state backend with post-state of given block.
StateAt(hash H) (statemachine.Backend[H, Hasher], error)

// Revert attempts to revert the chain by n blocks. If revertFinalized is set it will attempt to
// revert past any finalized block. This is unsafe and can potentially leave the node in an
// inconsistent state. All blocks higher than the best block are also reverted and not counting
// towards n.
//
// Returns the number of blocks that were successfully reverted and the list of finalized
// blocks that has been reverted.
Revert(n N, revertFinalized bool) (N, map[H]any, error)

// RemoveLeafBlock discards non-best, unfinalized leaf block.
RemoveLeafBlock(hash H) error

// GetImportLock returns access to the import lock for this backend.
//
// NOTE: Backend isn't expected to acquire the lock by itself ever. Rather
// the using components should acquire and hold the lock whenever they do
// something that the import of a block would interfere with, e.g. importing
// a new block or calculating the best head.
GetImportLock() *sync.RWMutex

// RequiresFullSync tells whether the backend requires full-sync mode.
RequiresFullSync() bool

// UsageInfo returns current usage statistics.
// TODO: implement UsageInfo if we require it
// UsageInfo() *UsageInfo
}
14 changes: 14 additions & 0 deletions internal/client/api/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2024 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package api

// AuxDataOperation is a slice of operations to be performed on storage aux data.
// Key is the encoded data key.
// Value is the encoded optional data to write.
// If Value is nil, the key and the associated data are deleted from storage.
type AuxDataOperation struct {
Key []byte
Data []byte
}
type AuxDataOperations []AuxDataOperation
48 changes: 26 additions & 22 deletions internal/client/api/leaves.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ type leafSetItem[H comparable, N runtime.Number] struct {
number N
}

// ImportOutcome privately contains the inserted and removed leaves after an import action.
// ImportOutcome contains the inserted and removed leaves after an import action.
type ImportOutcome[H comparable, N runtime.Number] struct {
inserted leafSetItem[H, N]
removed *H
}

// RemoveOutcome privates contains the inserted and removed leaves after a remove action.
// RemoveOutcome contains the inserted and removed leaves after a remove action.
type RemoveOutcome[H comparable, N runtime.Number] struct {
inserted *H
removed leafSetItem[H, N]
Expand All @@ -38,20 +38,20 @@ type FinalizationOutcome[H comparable, N runtime.Number] struct {
// Leaves returns the leaves that were removed after a finalization action.
func (fo FinalizationOutcome[H, N]) Leaves() []H {
leaves := make([]H, 0)
for _, hashes := range fo.removed.Values() {
leaves = append(leaves, hashes...)
}
fo.removed.Reverse(func(key N, value []H) bool {
leaves = append(leaves, value...)
return true
})
return leaves
}

// list of leaf hashes ordered by number (descending).
// stored in memory for fast access.
// this allows very fast checking and modification of active leaves.
// LeafSet is the list of leaf hashes ordered by number (descending) stored in memory for fast access.
// This allows very fast checking and modification of active leaves.
type LeafSet[H comparable, N runtime.Number] struct {
storage btree.Map[N, []H]
}

// NewLeafSet is constructor for a new, blank `LeafSet`.
// NewLeafSet is constructor for a new, blank [LeafSet].
func NewLeafSet[H comparable, N runtime.Number]() LeafSet[H, N] {
return LeafSet[H, N]{
storage: *btree.NewMap[N, []H](0),
Expand Down Expand Up @@ -104,11 +104,11 @@ func (ls *LeafSet[H, N]) Import(hash H, number N, parentHash H) ImportOutcome[H,
// Remove will update the leaf list on removal.
//
// Note that the leaves set structure doesn't have the information to decide if the
// leaf we're removing is the last children of the parent. Follows that this method requires
// the caller to check this condition and optionally pass the `parentHash` if `hash` is
// leaf we're removing is the last child of the parent. Follows that this method requires
// the caller to check this condition and optionally pass the parentHash if hash is
// its last child.
//
// Returns `nil` if no modifications are applied.
// Returns nil if no modifications are applied.
func (ls *LeafSet[H, N]) Remove(hash H, number N, parentHash *H) *RemoveOutcome[H, N] {
if !ls.removeLeaf(number, hash) {
return nil
Expand All @@ -132,10 +132,10 @@ func (ls *LeafSet[H, N]) Remove(hash H, number N, parentHash *H) *RemoveOutcome[
}

// FinalizeHeight will note a block height finalized, displacing all leaves with number less than the finalized
// block's.
// block number.
//
// Although it would be more technically correct to also prune out leaves at the
// same number as the finalized block, but with different hashes, the current behavior
// same number as the finalized block with different hashes, the current behavior
// is simpler and our assumptions about how finalization works means that those leaves
// will be pruned soon afterwards anyway.
func (ls *LeafSet[H, N]) FinalizeHeight(number N) FinalizationOutcome[H, N] {
Expand All @@ -145,15 +145,19 @@ func (ls *LeafSet[H, N]) FinalizeHeight(number N) FinalizationOutcome[H, N] {
}
boundary := number - 1
belowBoundary := btree.NewMap[N, []H](0)
ls.storage.Ascend(boundary, func(key N, value []H) bool {
belowBoundary.Set(key, value)
ls.storage.Delete(key)
return false

ls.storage.Reverse(func(key N, value []H) bool {
if key <= boundary {
belowBoundary.Set(key, value)
ls.storage.Delete(key)
}
return true
})

return FinalizationOutcome[H, N]{removed: *belowBoundary}
}

// DisplacedByFinalHeight is the same as `FinalizeHeight()`, but it only simulates the operation.
// DisplacedByFinalHeight is the same as FinalizeHeight(), but it only simulates the operation.
//
// This means that no changes are done.
//
Expand All @@ -165,16 +169,16 @@ func (ls *LeafSet[H, N]) DisplacedByFinalHeight(number N) FinalizationOutcome[H,
}
boundary := number - 1
belowBoundary := btree.NewMap[N, []H](0)
ls.storage.Ascend(boundary, func(key N, value []H) bool {
ls.storage.Descend(boundary, func(key N, value []H) bool {
belowBoundary.Set(key, value)
return false
return true
})
return FinalizationOutcome[H, N]{removed: *belowBoundary}
}

// Undo all pending operations.
//
// This returns an `Undo` struct, where any
// This returns an [Undo] struct, where any
// outcomes objects that have returned by previous method calls
// should be passed to via the appropriate methods. Otherwise,
// the on-disk state may get out of sync with in-memory state.
Expand Down
Loading

0 comments on commit ecdd433

Please sign in to comment.