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

feat(internal/client/db) Introduce api.Backend interface and db.Backend implementation #4405

Merged
merged 28 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from 20 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
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.36.2
github.com/libp2p/go-libp2p-kad-dht v0.27.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.30.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
Expand Down Expand Up @@ -227,3 +227,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 @@ -633,8 +629,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
211 changes: 211 additions & 0 deletions internal/client/api/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// 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"
)

// State of a new block.
type NewBlockState uint

const (
// Normal block.
NewBlockStateNormal NewBlockState = iota
// New best block.
NewBlockStateBest
// Newly finalized block (implicitly best).
NewBlockStateFinal
)

func (nbs NewBlockState) IsBest() bool {
return nbs == NewBlockStateBest
}

func (nbs NewBlockState) IsFinal() bool {
return nbs == NewBlockStateFinal
}

// Block insertion operation.
//
// Keeps hold if 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 {
// Returns pending state.
//
// Returns nil for backends with locally-unavailable state data.
State() (statemachine.Backend[H, Hasher], error)

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

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

// 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)

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

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

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

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

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

// Mark a block as new head. If both block import and set head are specified, set head
// overrides block import's best block rule.
MarkHead(hash H) error

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

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

// 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)
}

// 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 {
// Insert auxiliary data into key-value store.
AuxStore

// Begin 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)

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

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

// 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

// Append 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

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

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

// Pin 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)

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

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

// 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)

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

// Gain access to the import lock around 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

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

// 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

// List 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
43 changes: 24 additions & 19 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,9 +38,10 @@ 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
}

Expand All @@ -51,7 +52,7 @@ 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 +105,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 +133,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 +146,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 +170,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
Loading