From c3fcfd153093a43624cf97228fde880e8974d0b0 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Tue, 12 May 2020 16:02:46 +0200 Subject: [PATCH] etcd: add namespace support to separate key spaces This commit extends etcd db with namespaces without additional storage space requirements. This is simply done by instead of using an all zero root bucket id, we use the sha256 hash of the name space as our root bucket id. --- channeldb/kvdb/etcd/bucket.go | 6 ------ channeldb/kvdb/etcd/bucket_test.go | 6 ++++-- channeldb/kvdb/etcd/db.go | 19 +++++++++++++++---- channeldb/kvdb/etcd/readwrite_bucket.go | 2 +- channeldb/kvdb/etcd/readwrite_tx.go | 17 +++++++++++------ channeldb/kvdb/kvdb_etcd.go | 3 ++- channeldb/kvdb/kvdb_no_etcd.go | 2 +- lncfg/db.go | 9 ++++++--- 8 files changed, 40 insertions(+), 24 deletions(-) diff --git a/channeldb/kvdb/etcd/bucket.go b/channeldb/kvdb/etcd/bucket.go index fd4a37015..3bc087dbf 100644 --- a/channeldb/kvdb/etcd/bucket.go +++ b/channeldb/kvdb/etcd/bucket.go @@ -16,12 +16,6 @@ var ( sequencePrefix = []byte("$") ) -// rootBucketId returns a zero filled 32 byte array -func rootBucketID() []byte { - var rootID [bucketIDLength]byte - return rootID[:] -} - // makeBucketID returns a deterministic key for the passed byte slice. // Currently it returns the sha256 hash of the slice. func makeBucketID(key []byte) [bucketIDLength]byte { diff --git a/channeldb/kvdb/etcd/bucket_test.go b/channeldb/kvdb/etcd/bucket_test.go index c9df28471..e68821f1e 100644 --- a/channeldb/kvdb/etcd/bucket_test.go +++ b/channeldb/kvdb/etcd/bucket_test.go @@ -7,7 +7,8 @@ package etcd func bkey(buckets ...string) string { var bucketKey []byte - parent := rootBucketID() + rootID := makeBucketID([]byte("")) + parent := rootID[:] for _, bucketName := range buckets { bucketKey = makeBucketKey(parent, []byte(bucketName)) @@ -28,7 +29,8 @@ func bval(buckets ...string) string { // vkey is a helper function used in tests to create a value key from the // passed key and bucket list. func vkey(key string, buckets ...string) string { - bucket := rootBucketID() + rootID := makeBucketID([]byte("")) + bucket := rootID[:] for _, bucketName := range buckets { bucketKey := makeBucketKey(bucket, []byte(bucketName)) diff --git a/channeldb/kvdb/etcd/db.go b/channeldb/kvdb/etcd/db.go index 0079d2c9c..a082e6109 100644 --- a/channeldb/kvdb/etcd/db.go +++ b/channeldb/kvdb/etcd/db.go @@ -143,6 +143,11 @@ type BackendConfig struct { // skip TLS verification. InsecureSkipVerify bool + // Prefix the hash of the prefix will be used as the root + // bucket id. This enables key space separation similar to + // name spaces. + Prefix string + // CollectCommitStats indicates wheter to commit commit stats. CollectCommitStats bool } @@ -203,7 +208,7 @@ func (db *db) getSTMOptions() []STMOptionFunc { // occur). func (db *db) View(f func(tx walletdb.ReadTx) error) error { apply := func(stm STM) error { - return f(newReadWriteTx(stm)) + return f(newReadWriteTx(stm, db.config.Prefix)) } return RunSTM(db.cli, apply, db.getSTMOptions()...) @@ -217,7 +222,7 @@ func (db *db) View(f func(tx walletdb.ReadTx) error) error { // returned. func (db *db) Update(f func(tx walletdb.ReadWriteTx) error) error { apply := func(stm STM) error { - return f(newReadWriteTx(stm)) + return f(newReadWriteTx(stm, db.config.Prefix)) } return RunSTM(db.cli, apply, db.getSTMOptions()...) @@ -234,12 +239,18 @@ func (db *db) PrintStats() string { // BeginReadTx opens a database read transaction. func (db *db) BeginReadWriteTx() (walletdb.ReadWriteTx, error) { - return newReadWriteTx(NewSTM(db.cli, db.getSTMOptions()...)), nil + return newReadWriteTx( + NewSTM(db.cli, db.getSTMOptions()...), + db.config.Prefix, + ), nil } // BeginReadWriteTx opens a database read+write transaction. func (db *db) BeginReadTx() (walletdb.ReadTx, error) { - return newReadWriteTx(NewSTM(db.cli, db.getSTMOptions()...)), nil + return newReadWriteTx( + NewSTM(db.cli, db.getSTMOptions()...), + db.config.Prefix, + ), nil } // Copy writes a copy of the database to the provided writer. This call will diff --git a/channeldb/kvdb/etcd/readwrite_bucket.go b/channeldb/kvdb/etcd/readwrite_bucket.go index 60a19cc18..e60d2cec3 100644 --- a/channeldb/kvdb/etcd/readwrite_bucket.go +++ b/channeldb/kvdb/etcd/readwrite_bucket.go @@ -24,7 +24,7 @@ type readWriteBucket struct { // newReadWriteBucket creates a new rw bucket with the passed transaction // and bucket id. func newReadWriteBucket(tx *readWriteTx, key, id []byte) *readWriteBucket { - if !bytes.Equal(id, rootBucketID()) { + if !bytes.Equal(id, tx.rootBucketID[:]) { // Add the bucket key/value to the lock set. tx.lock(string(key), string(id)) } diff --git a/channeldb/kvdb/etcd/readwrite_tx.go b/channeldb/kvdb/etcd/readwrite_tx.go index 37914e8c9..22d0ce421 100644 --- a/channeldb/kvdb/etcd/readwrite_tx.go +++ b/channeldb/kvdb/etcd/readwrite_tx.go @@ -11,6 +11,10 @@ type readWriteTx struct { // stm is the reference to the parent STM. stm STM + // rootBucketID holds the sha256 hash of the root bucket id, which is used + // for key space spearation. + rootBucketID [bucketIDLength]byte + // active is true if the transaction hasn't been committed yet. active bool @@ -24,18 +28,19 @@ type readWriteTx struct { } // newReadWriteTx creates an rw transaction with the passed STM. -func newReadWriteTx(stm STM) *readWriteTx { +func newReadWriteTx(stm STM, prefix string) *readWriteTx { return &readWriteTx{ - stm: stm, - active: true, - lset: make(map[string]string), + stm: stm, + active: true, + rootBucketID: makeBucketID([]byte(prefix)), + lset: make(map[string]string), } } // rooBucket is a helper function to return the always present -// root bucket. +// pseudo root bucket. func rootBucket(tx *readWriteTx) *readWriteBucket { - return newReadWriteBucket(tx, rootBucketID(), rootBucketID()) + return newReadWriteBucket(tx, tx.rootBucketID[:], tx.rootBucketID[:]) } // lock adds a key value to the lock set. diff --git a/channeldb/kvdb/kvdb_etcd.go b/channeldb/kvdb/kvdb_etcd.go index b2662e098..265e7daeb 100644 --- a/channeldb/kvdb/kvdb_etcd.go +++ b/channeldb/kvdb/kvdb_etcd.go @@ -12,7 +12,7 @@ const TestBackend = EtcdBackendName // GetEtcdBackend returns an etcd backend configured according to the // passed etcdConfig. -func GetEtcdBackend(etcdConfig *EtcdConfig) (Backend, error) { +func GetEtcdBackend(prefix string, etcdConfig *EtcdConfig) (Backend, error) { // Config translation is needed here in order to keep the // etcd package fully independent from the rest of the source tree. backendConfig := etcd.BackendConfig{ @@ -22,6 +22,7 @@ func GetEtcdBackend(etcdConfig *EtcdConfig) (Backend, error) { CertFile: etcdConfig.CertFile, KeyFile: etcdConfig.KeyFile, InsecureSkipVerify: etcdConfig.InsecureSkipVerify, + Prefix: prefix, CollectCommitStats: etcdConfig.CollectStats, } diff --git a/channeldb/kvdb/kvdb_no_etcd.go b/channeldb/kvdb/kvdb_no_etcd.go index 1758551f8..ea5de4275 100644 --- a/channeldb/kvdb/kvdb_no_etcd.go +++ b/channeldb/kvdb/kvdb_no_etcd.go @@ -13,7 +13,7 @@ const TestBackend = BoltBackendName var errEtcdNotAvailable = fmt.Errorf("etcd backend not available") // GetEtcdBackend is a stub returning nil and errEtcdNotAvailable error. -func GetEtcdBackend(etcdConfig *EtcdConfig) (Backend, error) { +func GetEtcdBackend(prefix string, etcdConfig *EtcdConfig) (Backend, error) { return nil, errEtcdNotAvailable } diff --git a/lncfg/db.go b/lncfg/db.go index c0f47fc7f..62de1f130 100644 --- a/lncfg/db.go +++ b/lncfg/db.go @@ -2,6 +2,7 @@ package lncfg import ( "fmt" + "path" "github.com/lightningnetwork/lnd/channeldb/kvdb" ) @@ -50,12 +51,14 @@ func (db *DB) Validate() error { } // GetBackend returns a kvdb.Backend as set in the DB config. -func (db *DB) GetBackend(path string) (kvdb.Backend, error) { +func (db *DB) GetBackend(dbPath string) (kvdb.Backend, error) { if db.Backend == etcdBackend { - return kvdb.GetEtcdBackend(db.Etcd) + // Prefix will separate key/values in the db. + prefix := path.Join(dbPath, dbName) + return kvdb.GetEtcdBackend(prefix, db.Etcd) } - return kvdb.GetBoltBackend(path, dbName, db.Bolt.NoFreeListSync) + return kvdb.GetBoltBackend(dbPath, dbName, db.Bolt.NoFreeListSync) } // Compile-time constraint to ensure Workers implements the Validator interface.