Skip to content

Commit d323c0f

Browse files
committed
Add compactdb command
1 parent 3ed5123 commit d323c0f

File tree

4 files changed

+206
-1
lines changed

4 files changed

+206
-1
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* [Overview](#overview)
77
* [Commands](#commands)
88
+ [chanbackup](#chanbackup)
9+
+ [compactdb](#compactdb)
910
+ [derivekey](#derivekey)
1011
+ [dumpbackup](#dumpbackup)
1112
+ [dumpchannels](#dumpchannels)
@@ -59,6 +60,7 @@ Help Options:
5960
6061
Available commands:
6162
chanbackup Create a channel.backup file from a channel database.
63+
compactdb Open a source channel.db database file in safe/read-only mode and copy it to a fresh database, compacting it in the process.
6264
derivekey Derive a key with a specific derivation path from the BIP32 HD root key.
6365
dumpbackup Dump the content of a channel.backup file.
6466
dumpchannels Dump all channel information from lnd's channel database.
@@ -97,6 +99,28 @@ chantools chanbackup --rootkey xprvxxxxxxxxxx \
9799
--multi_file new_channel_backup.backup
98100
```
99101

102+
### compactdb
103+
104+
```text
105+
Usage:
106+
chantools [OPTIONS] compactdb [compactdb-OPTIONS]
107+
108+
[compactdb command options]
109+
--txmaxsize= Maximum transaction size. (default 65536)
110+
--sourcedb= The lnd channel.db file to create the database backup from.
111+
--destdb= The lnd new channel.db file to copy the compacted database to.
112+
```
113+
114+
This command opens a database in read-only mode and tries to create a copy of it
115+
to a destination file, compacting it in the process.
116+
117+
Example command:
118+
119+
```bash
120+
chantools compactdb --sourcedb ~/.lnd/data/graph/mainnet/channel.db \
121+
--destdb ./results/compacted.db
122+
```
123+
100124
### derivekey
101125

102126
```text

cmd/chantools/compactdb.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/coreos/bbolt"
7+
)
8+
9+
const (
10+
dbFilePermission = 0600
11+
defaultTxMaxSize = 65536
12+
)
13+
14+
type compactDBCommand struct {
15+
TxMaxSize int64 `long:"txmaxsize" description:"Maximum transaction size. (default 65536)"`
16+
SourceDB string `long:"sourcedb" description:"The lnd channel.db file to create the database backup from."`
17+
DestDB string `long:"destdb" description:"The lnd new channel.db file to copy the compacted database to."`
18+
}
19+
20+
func (c *compactDBCommand) Execute(_ []string) error {
21+
// Check that we have a source and destination channel DB.
22+
if c.SourceDB == "" {
23+
return fmt.Errorf("source channel DB is required")
24+
}
25+
if c.DestDB == "" {
26+
return fmt.Errorf("destination channel DB is required")
27+
}
28+
if c.TxMaxSize <= 0 {
29+
c.TxMaxSize = defaultTxMaxSize
30+
}
31+
src, err := c.openDB(c.SourceDB, true)
32+
if err != nil {
33+
return fmt.Errorf("error opening source DB: %v", err)
34+
}
35+
dst, err := c.openDB(c.DestDB, false)
36+
if err != nil {
37+
return fmt.Errorf("error opening destination DB: %v", err)
38+
}
39+
err = c.compact(dst, src)
40+
if err != nil {
41+
return fmt.Errorf("error compacting DB: %v", err)
42+
}
43+
return nil
44+
}
45+
46+
func (c *compactDBCommand) openDB(path string, ro bool) (*bbolt.DB, error) {
47+
options := &bbolt.Options{
48+
NoFreelistSync: false,
49+
FreelistType: bbolt.FreelistMapType,
50+
ReadOnly: ro,
51+
}
52+
53+
bdb, err := bbolt.Open(path, dbFilePermission, options)
54+
if err != nil {
55+
return nil, err
56+
}
57+
return bdb, nil
58+
}
59+
60+
func (c *compactDBCommand) compact(dst, src *bbolt.DB) error {
61+
// commit regularly, or we'll run out of memory for large datasets if
62+
// using one transaction.
63+
var size int64
64+
tx, err := dst.Begin(true)
65+
if err != nil {
66+
return err
67+
}
68+
defer tx.Rollback()
69+
70+
if err := c.walk(src, func(keys [][]byte, k, v []byte, seq uint64) error {
71+
// On each key/value, check if we have exceeded tx size.
72+
sz := int64(len(k) + len(v))
73+
if size+sz > c.TxMaxSize && c.TxMaxSize != 0 {
74+
// Commit previous transaction.
75+
if err := tx.Commit(); err != nil {
76+
return err
77+
}
78+
79+
// Start new transaction.
80+
tx, err = dst.Begin(true)
81+
if err != nil {
82+
return err
83+
}
84+
size = 0
85+
}
86+
size += sz
87+
88+
// Create bucket on the root transaction if this is the first
89+
// level.
90+
nk := len(keys)
91+
if nk == 0 {
92+
bkt, err := tx.CreateBucket(k)
93+
if err != nil {
94+
return err
95+
}
96+
if err := bkt.SetSequence(seq); err != nil {
97+
return err
98+
}
99+
return nil
100+
}
101+
102+
// Create buckets on subsequent levels, if necessary.
103+
b := tx.Bucket(keys[0])
104+
if nk > 1 {
105+
for _, k := range keys[1:] {
106+
b = b.Bucket(k)
107+
}
108+
}
109+
110+
// Fill the entire page for best compaction.
111+
b.FillPercent = 1.0
112+
113+
// If there is no value then this is a bucket call.
114+
if v == nil {
115+
bkt, err := b.CreateBucket(k)
116+
if err != nil {
117+
return err
118+
}
119+
if err := bkt.SetSequence(seq); err != nil {
120+
return err
121+
}
122+
return nil
123+
}
124+
125+
// Otherwise treat it as a key/value pair.
126+
return b.Put(k, v)
127+
}); err != nil {
128+
return err
129+
}
130+
131+
return tx.Commit()
132+
}
133+
134+
// walkFunc is the type of the function called for keys (buckets and "normal"
135+
// values) discovered by Walk. keys is the list of keys to descend to the bucket
136+
// owning the discovered key/value pair k/v.
137+
type walkFunc func(keys [][]byte, k, v []byte, seq uint64) error
138+
139+
// walk walks recursively the bolt database db, calling walkFn for each key it
140+
// finds.
141+
func (c *compactDBCommand) walk(db *bbolt.DB, walkFn walkFunc) error {
142+
return db.View(func(tx *bbolt.Tx) error {
143+
return tx.ForEach(func(name []byte, b *bbolt.Bucket) error {
144+
return c.walkBucket(
145+
b, nil, name, nil, b.Sequence(), walkFn,
146+
)
147+
})
148+
})
149+
}
150+
151+
func (c *compactDBCommand) walkBucket(b *bbolt.Bucket, keypath [][]byte,
152+
k, v []byte, seq uint64, fn walkFunc) error {
153+
// Execute callback.
154+
if err := fn(keypath, k, v, seq); err != nil {
155+
return err
156+
}
157+
158+
// If this is not a bucket then stop.
159+
if v != nil {
160+
return nil
161+
}
162+
163+
// Iterate over each child key/value.
164+
keypath = append(keypath, k)
165+
return b.ForEach(func(k, v []byte) error {
166+
if v == nil {
167+
bkt := b.Bucket(k)
168+
return c.walkBucket(
169+
bkt, keypath, k, nil, bkt.Sequence(), fn,
170+
)
171+
}
172+
return c.walkBucket(b, keypath, k, v, b.Sequence(), fn)
173+
})
174+
}

cmd/chantools/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"bytes"
66
"encoding/json"
77
"fmt"
8-
"github.com/lightningnetwork/lnd/chanbackup"
98
"io/ioutil"
109
"os"
1110
"path"
@@ -20,6 +19,7 @@ import (
2019
"github.com/jessevdk/go-flags"
2120
"github.com/lightningnetwork/lnd/aezeed"
2221
"github.com/lightningnetwork/lnd/build"
22+
"github.com/lightningnetwork/lnd/chanbackup"
2323
"github.com/lightningnetwork/lnd/channeldb"
2424
"golang.org/x/crypto/ssh/terminal"
2525
)
@@ -122,6 +122,12 @@ func runCommandParser() error {
122122
"chanbackup", "Create a channel.backup file from a channel "+
123123
"database.", "", &chanBackupCommand{},
124124
)
125+
_, _ = parser.AddCommand(
126+
"compactdb", "Open a source channel.db database file in safe/"+
127+
"read-only mode and copy it to a fresh database, "+
128+
"compacting it in the process.", "",
129+
&compactDBCommand{},
130+
)
125131

126132
_, err := parser.Parse()
127133
return err

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/btcsuite/btcutil v0.0.0-20191219182022-e17c9730c422
99
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623
1010
github.com/btcsuite/btcwallet/walletdb v1.2.0
11+
github.com/coreos/bbolt v1.3.3
1112
github.com/davecgh/go-spew v1.1.1
1213
github.com/golang/protobuf v1.3.2 // indirect
1314
github.com/jessevdk/go-flags v1.4.0

0 commit comments

Comments
 (0)