Skip to content

Commit 3f38c5b

Browse files
gballetjsignholimanrjl493456442
authored andcommitted
core: add an end-to-end verkle test (ethereum#29262)
core: add a simple verkle test triedb, core: skip hash comparison in verkle core: remove legacy daoFork logic in verkle chain maker fix: nil pointer in tests triedb/pathdb: add blob hex core: less defensive Co-authored-by: Ignacio Hagopian <[email protected]> Co-authored-by: Martin HS <[email protected]> Co-authored-by: Gary Rong <[email protected]>
1 parent b3d09ec commit 3f38c5b

File tree

15 files changed

+358
-102
lines changed

15 files changed

+358
-102
lines changed

core/blockchain.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,11 @@ type CacheConfig struct {
147147
}
148148

149149
// triedbConfig derives the configures for trie database.
150-
func (c *CacheConfig) triedbConfig() *triedb.Config {
151-
config := &triedb.Config{Preimages: c.Preimages}
150+
func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config {
151+
config := &triedb.Config{
152+
Preimages: c.Preimages,
153+
IsVerkle: isVerkle,
154+
}
152155
if c.StateScheme == rawdb.HashScheme {
153156
config.HashDB = &hashdb.Config{
154157
CleanCacheSize: c.TrieCleanLimit * 1024 * 1024,
@@ -265,7 +268,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
265268
cacheConfig = defaultCacheConfig
266269
}
267270
// Open trie database with provided config
268-
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig())
271+
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(genesis != nil && genesis.IsVerkle()))
269272

270273
// Setup the genesis block, commit the provided genesis specification
271274
// to database if the genesis block is not present yet, or load the

core/chain_makers.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/ethereum/go-ethereum/ethdb"
3333
"github.com/ethereum/go-ethereum/params"
3434
"github.com/ethereum/go-ethereum/triedb"
35+
"github.com/gballet/go-verkle"
3536
"github.com/holiman/uint256"
3637
)
3738

@@ -418,6 +419,112 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
418419
return db, blocks, receipts
419420
}
420421

422+
func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, trdb *triedb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) {
423+
if config == nil {
424+
config = params.TestChainConfig
425+
}
426+
proofs := make([]*verkle.VerkleProof, 0, n)
427+
keyvals := make([]verkle.StateDiff, 0, n)
428+
cm := newChainMaker(parent, config, engine)
429+
430+
genblock := func(i int, parent *types.Block, triedb *triedb.Database, statedb *state.StateDB) (*types.Block, types.Receipts) {
431+
b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: engine}
432+
b.header = cm.makeHeader(parent, statedb, b.engine)
433+
434+
// TODO uncomment when proof generation is merged
435+
// Save pre state for proof generation
436+
// preState := statedb.Copy()
437+
438+
// TODO uncomment when the 2935 PR is merged
439+
// if config.IsPrague(b.header.Number, b.header.Time) {
440+
// if !config.IsPrague(b.parent.Number(), b.parent.Time()) {
441+
// Transition case: insert all 256 ancestors
442+
// InsertBlockHashHistoryAtEip2935Fork(statedb, b.header.Number.Uint64()-1, b.header.ParentHash, chainreader)
443+
// } else {
444+
// ProcessParentBlockHash(statedb, b.header.Number.Uint64()-1, b.header.ParentHash)
445+
// }
446+
// }
447+
// Execute any user modifications to the block
448+
if gen != nil {
449+
gen(i, b)
450+
}
451+
body := &types.Body{
452+
Transactions: b.txs,
453+
Uncles: b.uncles,
454+
Withdrawals: b.withdrawals,
455+
}
456+
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, body, b.receipts)
457+
if err != nil {
458+
panic(err)
459+
}
460+
461+
// Write state changes to db
462+
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number))
463+
if err != nil {
464+
panic(fmt.Sprintf("state write error: %v", err))
465+
}
466+
if err = triedb.Commit(root, false); err != nil {
467+
panic(fmt.Sprintf("trie write error: %v", err))
468+
}
469+
470+
// TODO uncomment when proof generation is merged
471+
// proofs = append(proofs, block.ExecutionWitness().VerkleProof)
472+
// keyvals = append(keyvals, block.ExecutionWitness().StateDiff)
473+
474+
return block, b.receipts
475+
}
476+
477+
for i := 0; i < n; i++ {
478+
statedb, err := state.New(parent.Root(), state.NewDatabaseWithNodeDB(db, trdb), nil)
479+
if err != nil {
480+
panic(err)
481+
}
482+
block, receipts := genblock(i, parent, trdb, statedb)
483+
484+
// Post-process the receipts.
485+
// Here we assign the final block hash and other info into the receipt.
486+
// In order for DeriveFields to work, the transaction and receipt lists need to be
487+
// of equal length. If AddUncheckedTx or AddUncheckedReceipt are used, there will be
488+
// extra ones, so we just trim the lists here.
489+
receiptsCount := len(receipts)
490+
txs := block.Transactions()
491+
if len(receipts) > len(txs) {
492+
receipts = receipts[:len(txs)]
493+
} else if len(receipts) < len(txs) {
494+
txs = txs[:len(receipts)]
495+
}
496+
var blobGasPrice *big.Int
497+
if block.ExcessBlobGas() != nil {
498+
blobGasPrice = eip4844.CalcBlobFee(*block.ExcessBlobGas())
499+
}
500+
if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, txs); err != nil {
501+
panic(err)
502+
}
503+
504+
// Re-expand to ensure all receipts are returned.
505+
receipts = receipts[:receiptsCount]
506+
507+
// Advance the chain.
508+
cm.add(block, receipts)
509+
parent = block
510+
}
511+
return cm.chain, cm.receipts, proofs, keyvals
512+
}
513+
514+
func GenerateVerkleChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) {
515+
db := rawdb.NewMemoryDatabase()
516+
cacheConfig := DefaultCacheConfigWithScheme(rawdb.PathScheme)
517+
cacheConfig.SnapshotLimit = 0
518+
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
519+
defer triedb.Close()
520+
genesisBlock, err := genesis.Commit(db, triedb)
521+
if err != nil {
522+
panic(err)
523+
}
524+
blocks, receipts, proofs, keyvals := GenerateVerkleChain(genesis.Config, genesisBlock, engine, db, triedb, n, gen)
525+
return db, blocks, receipts, proofs, keyvals
526+
}
527+
421528
func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
422529
time := parent.Time() + 10 // block time is fixed at 10 seconds
423530
header := &types.Header{

core/state/database.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
209209
switch t := t.(type) {
210210
case *trie.StateTrie:
211211
return t.Copy()
212+
case *trie.VerkleTrie:
213+
return t.Copy()
212214
default:
213215
panic(fmt.Errorf("unknown trie type %T", t))
214216
}

core/state/statedb.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,11 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) error {
11561156
return nil
11571157
}
11581158

1159+
// GetTrie returns the account trie.
1160+
func (s *StateDB) GetTrie() Trie {
1161+
return s.trie
1162+
}
1163+
11591164
// Commit writes the state to the underlying in-memory trie database.
11601165
// Once the state is committed, tries cached in stateDB (including account
11611166
// trie, storage tries) will no longer be functional. A new state instance

core/state_processor_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,108 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
422422
}
423423
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
424424
}
425+
426+
var (
427+
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
428+
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true)
429+
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
430+
// will not contain that copied data.
431+
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
432+
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
433+
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true)
434+
)
435+
436+
func TestProcessVerkle(t *testing.T) {
437+
var (
438+
config = &params.ChainConfig{
439+
ChainID: big.NewInt(1),
440+
HomesteadBlock: big.NewInt(0),
441+
EIP150Block: big.NewInt(0),
442+
EIP155Block: big.NewInt(0),
443+
EIP158Block: big.NewInt(0),
444+
ByzantiumBlock: big.NewInt(0),
445+
ConstantinopleBlock: big.NewInt(0),
446+
PetersburgBlock: big.NewInt(0),
447+
IstanbulBlock: big.NewInt(0),
448+
MuirGlacierBlock: big.NewInt(0),
449+
BerlinBlock: big.NewInt(0),
450+
LondonBlock: big.NewInt(0),
451+
Ethash: new(params.EthashConfig),
452+
ShanghaiTime: u64(0),
453+
VerkleTime: u64(0),
454+
TerminalTotalDifficulty: common.Big0,
455+
TerminalTotalDifficultyPassed: true,
456+
// TODO uncomment when proof generation is merged
457+
// ProofInBlocks: true,
458+
}
459+
signer = types.LatestSigner(config)
460+
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
461+
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
462+
coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
463+
gspec = &Genesis{
464+
Config: config,
465+
Alloc: GenesisAlloc{
466+
coinbase: GenesisAccount{
467+
Balance: big.NewInt(1000000000000000000), // 1 ether
468+
Nonce: 0,
469+
},
470+
},
471+
}
472+
)
473+
// Verkle trees use the snapshot, which must be enabled before the
474+
// data is saved into the tree+database.
475+
// genesis := gspec.MustCommit(bcdb, triedb)
476+
cacheConfig := DefaultCacheConfigWithScheme("path")
477+
cacheConfig.SnapshotLimit = 0
478+
blockchain, _ := NewBlockChain(bcdb, cacheConfig, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil)
479+
defer blockchain.Stop()
480+
481+
txCost1 := params.TxGas
482+
txCost2 := params.TxGas
483+
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */)
484+
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(293644 /* execution costs */)
485+
blockGasUsagesExpected := []uint64{
486+
txCost1*2 + txCost2,
487+
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
488+
}
489+
_, chain, _, _, _ := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) {
490+
gen.SetPoS()
491+
492+
// TODO need to check that the tx cost provided is the exact amount used (no remaining left-over)
493+
tx, _ := types.SignTx(types.NewTransaction(uint64(i)*3, common.Address{byte(i), 2, 3}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey)
494+
gen.AddTx(tx)
495+
tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+1, common.Address{}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey)
496+
gen.AddTx(tx)
497+
tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+2, common.Address{}, big.NewInt(0), txCost2, big.NewInt(875000000), nil), signer, testKey)
498+
gen.AddTx(tx)
499+
500+
// Add two contract creations in block #2
501+
if i == 1 {
502+
tx, _ = types.SignTx(types.NewContractCreation(6, big.NewInt(16), 3000000, big.NewInt(875000000), code), signer, testKey)
503+
gen.AddTx(tx)
504+
505+
tx, _ = types.SignTx(types.NewContractCreation(7, big.NewInt(0), 3000000, big.NewInt(875000000), codeWithExtCodeCopy), signer, testKey)
506+
gen.AddTx(tx)
507+
}
508+
})
509+
510+
t.Log("inserting blocks into the chain")
511+
512+
endnum, err := blockchain.InsertChain(chain)
513+
if err != nil {
514+
t.Fatalf("block %d imported with error: %v", endnum, err)
515+
}
516+
517+
for i := 0; i < 2; i++ {
518+
b := blockchain.GetBlockByNumber(uint64(i) + 1)
519+
if b == nil {
520+
t.Fatalf("expected block %d to be present in chain", i+1)
521+
}
522+
if b.Hash() != chain[i].Hash() {
523+
t.Fatalf("block #%d not found at expected height", b.NumberU64())
524+
}
525+
if b.GasUsed() != blockGasUsagesExpected[i] {
526+
t.Fatalf("expected block #%d txs to use %d, got %d\n", b.NumberU64(), blockGasUsagesExpected[i], b.GasUsed())
527+
}
528+
}
529+
}

triedb/database.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
108108
log.Crit("Both 'hash' and 'path' mode are configured")
109109
}
110110
if config.PathDB != nil {
111-
db.backend = pathdb.New(diskdb, config.PathDB)
111+
db.backend = pathdb.New(diskdb, config.PathDB, config.IsVerkle)
112112
} else {
113113
var resolver hashdb.ChildResolver
114114
if config.IsVerkle {
115115
// TODO define verkle resolver
116-
log.Crit("Verkle node resolver is not defined")
116+
log.Crit("verkle does not use a hash db")
117117
} else {
118118
resolver = trie.MerkleResolver{}
119119
}

triedb/pathdb/database.go

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ var (
5959
// layer is the interface implemented by all state layers which includes some
6060
// public methods and some additional methods for internal usage.
6161
type layer interface {
62-
// Node retrieves the trie node with the node info. An error will be returned
63-
// if the read operation exits abnormally. For example, if the layer is already
64-
// stale, or the associated state is regarded as corrupted. Notably, no error
65-
// will be returned if the requested node is not found in database.
66-
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
62+
// node retrieves the trie node with the node info. An error will be returned
63+
// if the read operation exits abnormally. Specifically, if the layer is
64+
// already stale.
65+
//
66+
// Note, no error will be returned if the requested node is not found in database.
67+
node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error)
6768

6869
// rootHash returns the root hash for which this layer was made.
6970
rootHash() common.Hash
@@ -132,6 +133,7 @@ type Database struct {
132133
// the shutdown to reject all following unexpected mutations.
133134
readOnly bool // Flag if database is opened in read only mode
134135
waitSync bool // Flag if database is deactivated due to initial state sync
136+
isVerkle bool // Flag if database is used for verkle tree
135137
bufferSize int // Memory allowance (in bytes) for caching dirty nodes
136138
config *Config // Configuration for database
137139
diskdb ethdb.Database // Persistent storage for matured trie nodes
@@ -143,14 +145,15 @@ type Database struct {
143145
// New attempts to load an already existing layer from a persistent key-value
144146
// store (with a number of memory layers from a journal). If the journal is not
145147
// matched with the base persistent layer, all the recorded diff layers are discarded.
146-
func New(diskdb ethdb.Database, config *Config) *Database {
148+
func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
147149
if config == nil {
148150
config = Defaults
149151
}
150152
config = config.sanitize()
151153

152154
db := &Database{
153155
readOnly: config.ReadOnly,
156+
isVerkle: isVerkle,
154157
bufferSize: config.DirtyCacheSize,
155158
config: config,
156159
diskdb: diskdb,
@@ -208,15 +211,6 @@ func New(diskdb ethdb.Database, config *Config) *Database {
208211
return db
209212
}
210213

211-
// Reader retrieves a layer belonging to the given state root.
212-
func (db *Database) Reader(root common.Hash) (layer, error) {
213-
l := db.tree.get(root)
214-
if l == nil {
215-
return nil, fmt.Errorf("state %#x is not available", root)
216-
}
217-
return l, nil
218-
}
219-
220214
// Update adds a new layer into the tree, if that can be linked to an existing
221215
// old parent. It is disallowed to insert a disk layer (the origin of all). Apart
222216
// from that this function will flatten the extra diff layers at bottom into disk

triedb/pathdb/database_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func newTester(t *testing.T, historyLimit uint64) *tester {
106106
StateHistory: historyLimit,
107107
CleanCacheSize: 16 * 1024,
108108
DirtyCacheSize: 16 * 1024,
109-
})
109+
}, false)
110110
obj = &tester{
111111
db: db,
112112
preimages: make(map[common.Hash]common.Address),
@@ -550,7 +550,7 @@ func TestJournal(t *testing.T) {
550550
t.Errorf("Failed to journal, err: %v", err)
551551
}
552552
tester.db.Close()
553-
tester.db = New(tester.db.diskdb, nil)
553+
tester.db = New(tester.db.diskdb, nil, false)
554554

555555
// Verify states including disk layer and all diff on top.
556556
for i := 0; i < len(tester.roots); i++ {
@@ -588,7 +588,7 @@ func TestCorruptedJournal(t *testing.T) {
588588
rawdb.WriteTrieJournal(tester.db.diskdb, blob)
589589

590590
// Verify states, all not-yet-written states should be discarded
591-
tester.db = New(tester.db.diskdb, nil)
591+
tester.db = New(tester.db.diskdb, nil, false)
592592
for i := 0; i < len(tester.roots); i++ {
593593
if tester.roots[i] == root {
594594
if err := tester.verifyState(root); err != nil {
@@ -625,7 +625,7 @@ func TestTailTruncateHistory(t *testing.T) {
625625
defer tester.release()
626626

627627
tester.db.Close()
628-
tester.db = New(tester.db.diskdb, &Config{StateHistory: 10})
628+
tester.db = New(tester.db.diskdb, &Config{StateHistory: 10}, false)
629629

630630
head, err := tester.db.freezer.Ancients()
631631
if err != nil {

0 commit comments

Comments
 (0)