You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
231 lines
6.4 KiB
231 lines
6.4 KiB
package channeldb |
|
|
|
import ( |
|
"fmt" |
|
|
|
"github.com/lightningnetwork/lnd/channeldb/kvdb" |
|
"github.com/lightningnetwork/lnd/lntypes" |
|
) |
|
|
|
var ( |
|
// ErrNoWitnesses is an error that's returned when no new witnesses have |
|
// been added to the WitnessCache. |
|
ErrNoWitnesses = fmt.Errorf("no witnesses") |
|
|
|
// ErrUnknownWitnessType is returned if a caller attempts to |
|
ErrUnknownWitnessType = fmt.Errorf("unknown witness type") |
|
) |
|
|
|
// WitnessType is enum that denotes what "type" of witness is being |
|
// stored/retrieved. As the WitnessCache itself is agnostic and doesn't enforce |
|
// any structure on added witnesses, we use this type to partition the |
|
// witnesses on disk, and also to know how to map a witness to its look up key. |
|
type WitnessType uint8 |
|
|
|
var ( |
|
// Sha256HashWitness is a witness that is simply the pre image to a |
|
// hash image. In order to map to its key, we'll use sha256. |
|
Sha256HashWitness WitnessType = 1 |
|
) |
|
|
|
// toDBKey is a helper method that maps a witness type to the key that we'll |
|
// use to store it within the database. |
|
func (w WitnessType) toDBKey() ([]byte, error) { |
|
switch w { |
|
|
|
case Sha256HashWitness: |
|
return []byte{byte(w)}, nil |
|
|
|
default: |
|
return nil, ErrUnknownWitnessType |
|
} |
|
} |
|
|
|
var ( |
|
// witnessBucketKey is the name of the bucket that we use to store all |
|
// witnesses encountered. Within this bucket, we'll create a sub-bucket for |
|
// each witness type. |
|
witnessBucketKey = []byte("byte") |
|
) |
|
|
|
// WitnessCache is a persistent cache of all witnesses we've encountered on the |
|
// network. In the case of multi-hop, multi-step contracts, a cache of all |
|
// witnesses can be useful in the case of partial contract resolution. If |
|
// negotiations break down, we may be forced to locate the witness for a |
|
// portion of the contract on-chain. In this case, we'll then add that witness |
|
// to the cache so the incoming contract can fully resolve witness. |
|
// Additionally, as one MUST always use a unique witness on the network, we may |
|
// use this cache to detect duplicate witnesses. |
|
// |
|
// TODO(roasbeef): need expiry policy? |
|
// * encrypt? |
|
type WitnessCache struct { |
|
db *DB |
|
} |
|
|
|
// NewWitnessCache returns a new instance of the witness cache. |
|
func (d *DB) NewWitnessCache() *WitnessCache { |
|
return &WitnessCache{ |
|
db: d, |
|
} |
|
} |
|
|
|
// witnessEntry is a key-value struct that holds each key -> witness pair, used |
|
// when inserting records into the cache. |
|
type witnessEntry struct { |
|
key []byte |
|
witness []byte |
|
} |
|
|
|
// AddSha256Witnesses adds a batch of new sha256 preimages into the witness |
|
// cache. This is an alias for AddWitnesses that uses Sha256HashWitness as the |
|
// preimages' witness type. |
|
func (w *WitnessCache) AddSha256Witnesses(preimages ...lntypes.Preimage) error { |
|
// Optimistically compute the preimages' hashes before attempting to |
|
// start the db transaction. |
|
entries := make([]witnessEntry, 0, len(preimages)) |
|
for i := range preimages { |
|
hash := preimages[i].Hash() |
|
entries = append(entries, witnessEntry{ |
|
key: hash[:], |
|
witness: preimages[i][:], |
|
}) |
|
} |
|
|
|
return w.addWitnessEntries(Sha256HashWitness, entries) |
|
} |
|
|
|
// addWitnessEntries inserts the witnessEntry key-value pairs into the cache, |
|
// using the appropriate witness type to segment the namespace of possible |
|
// witness types. |
|
func (w *WitnessCache) addWitnessEntries(wType WitnessType, |
|
entries []witnessEntry) error { |
|
|
|
// Exit early if there are no witnesses to add. |
|
if len(entries) == 0 { |
|
return nil |
|
} |
|
|
|
return kvdb.Batch(w.db.Backend, func(tx kvdb.RwTx) error { |
|
witnessBucket, err := tx.CreateTopLevelBucket(witnessBucketKey) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
witnessTypeBucketKey, err := wType.toDBKey() |
|
if err != nil { |
|
return err |
|
} |
|
witnessTypeBucket, err := witnessBucket.CreateBucketIfNotExists( |
|
witnessTypeBucketKey, |
|
) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
for _, entry := range entries { |
|
err = witnessTypeBucket.Put(entry.key, entry.witness) |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
|
|
return nil |
|
}) |
|
} |
|
|
|
// LookupSha256Witness attempts to lookup the preimage for a sha256 hash. If |
|
// the witness isn't found, ErrNoWitnesses will be returned. |
|
func (w *WitnessCache) LookupSha256Witness(hash lntypes.Hash) (lntypes.Preimage, error) { |
|
witness, err := w.lookupWitness(Sha256HashWitness, hash[:]) |
|
if err != nil { |
|
return lntypes.Preimage{}, err |
|
} |
|
|
|
return lntypes.MakePreimage(witness) |
|
} |
|
|
|
// lookupWitness attempts to lookup a witness according to its type and also |
|
// its witness key. In the case that the witness isn't found, ErrNoWitnesses |
|
// will be returned. |
|
func (w *WitnessCache) lookupWitness(wType WitnessType, witnessKey []byte) ([]byte, error) { |
|
var witness []byte |
|
err := kvdb.View(w.db, func(tx kvdb.RTx) error { |
|
witnessBucket := tx.ReadBucket(witnessBucketKey) |
|
if witnessBucket == nil { |
|
return ErrNoWitnesses |
|
} |
|
|
|
witnessTypeBucketKey, err := wType.toDBKey() |
|
if err != nil { |
|
return err |
|
} |
|
witnessTypeBucket := witnessBucket.NestedReadBucket(witnessTypeBucketKey) |
|
if witnessTypeBucket == nil { |
|
return ErrNoWitnesses |
|
} |
|
|
|
dbWitness := witnessTypeBucket.Get(witnessKey) |
|
if dbWitness == nil { |
|
return ErrNoWitnesses |
|
} |
|
|
|
witness = make([]byte, len(dbWitness)) |
|
copy(witness[:], dbWitness) |
|
|
|
return nil |
|
}, func() { |
|
witness = nil |
|
}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return witness, nil |
|
} |
|
|
|
// DeleteSha256Witness attempts to delete a sha256 preimage identified by hash. |
|
func (w *WitnessCache) DeleteSha256Witness(hash lntypes.Hash) error { |
|
return w.deleteWitness(Sha256HashWitness, hash[:]) |
|
} |
|
|
|
// deleteWitness attempts to delete a particular witness from the database. |
|
func (w *WitnessCache) deleteWitness(wType WitnessType, witnessKey []byte) error { |
|
return kvdb.Batch(w.db.Backend, func(tx kvdb.RwTx) error { |
|
witnessBucket, err := tx.CreateTopLevelBucket(witnessBucketKey) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
witnessTypeBucketKey, err := wType.toDBKey() |
|
if err != nil { |
|
return err |
|
} |
|
witnessTypeBucket, err := witnessBucket.CreateBucketIfNotExists( |
|
witnessTypeBucketKey, |
|
) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
return witnessTypeBucket.Delete(witnessKey) |
|
}) |
|
} |
|
|
|
// DeleteWitnessClass attempts to delete an *entire* class of witnesses. After |
|
// this function return with a non-nil error, |
|
func (w *WitnessCache) DeleteWitnessClass(wType WitnessType) error { |
|
return kvdb.Batch(w.db.Backend, func(tx kvdb.RwTx) error { |
|
witnessBucket, err := tx.CreateTopLevelBucket(witnessBucketKey) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
witnessTypeBucketKey, err := wType.toDBKey() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
return witnessBucket.DeleteNestedBucket(witnessTypeBucketKey) |
|
}) |
|
}
|
|
|