mirror of https://github.com/lightninglabs/loop
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
473 lines
9.9 KiB
Go
473 lines
9.9 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/coreos/bbolt"
|
|
"github.com/lightninglabs/loop/utils"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
)
|
|
|
|
var (
|
|
dbFileName = "swapclient.db"
|
|
|
|
// unchargeSwapsBucketKey is a bucket that contains all swaps that are
|
|
// currently pending or completed.
|
|
//
|
|
// maps: swap_hash -> UnchargeContract
|
|
unchargeSwapsBucketKey = []byte("uncharge-swaps")
|
|
|
|
// unchargeUpdatesBucketKey is a bucket that contains all updates
|
|
// pertaining to a swap. This list only ever grows.
|
|
//
|
|
// maps: update_nr -> time | state
|
|
updatesBucketKey = []byte("updates")
|
|
|
|
// contractKey is the key that stores the serialized swap contract.
|
|
contractKey = []byte("contract")
|
|
|
|
byteOrder = binary.BigEndian
|
|
|
|
keyLength = 33
|
|
)
|
|
|
|
// boltSwapClientStore stores swap data in boltdb.
|
|
type boltSwapClientStore struct {
|
|
db *bbolt.DB
|
|
}
|
|
|
|
// newBoltSwapClientStore creates a new client swap store.
|
|
func newBoltSwapClientStore(dbPath string) (*boltSwapClientStore, error) {
|
|
if !utils.FileExists(dbPath) {
|
|
if err := os.MkdirAll(dbPath, 0700); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
path := filepath.Join(dbPath, dbFileName)
|
|
bdb, err := bbolt.Open(path, 0600, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = bdb.Update(func(tx *bbolt.Tx) error {
|
|
_, err := tx.CreateBucketIfNotExists(unchargeSwapsBucketKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = tx.CreateBucketIfNotExists(updatesBucketKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = tx.CreateBucketIfNotExists(metaBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = syncVersions(bdb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &boltSwapClientStore{
|
|
db: bdb,
|
|
}, nil
|
|
}
|
|
|
|
// getUnchargeSwaps returns all swaps currently in the store.
|
|
func (s *boltSwapClientStore) getUnchargeSwaps() ([]*PersistentUncharge, error) {
|
|
var swaps []*PersistentUncharge
|
|
err := s.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
bucket := tx.Bucket(unchargeSwapsBucketKey)
|
|
if bucket == nil {
|
|
return errors.New("bucket does not exist")
|
|
}
|
|
|
|
err := bucket.ForEach(func(k, _ []byte) error {
|
|
swapBucket := bucket.Bucket(k)
|
|
if swapBucket == nil {
|
|
return fmt.Errorf("swap bucket %x not found",
|
|
k)
|
|
}
|
|
|
|
contractBytes := swapBucket.Get(contractKey)
|
|
if contractBytes == nil {
|
|
return errors.New("contract not found")
|
|
}
|
|
|
|
contract, err := deserializeUnchargeContract(
|
|
contractBytes,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stateBucket := swapBucket.Bucket(updatesBucketKey)
|
|
if stateBucket == nil {
|
|
return errors.New("updates bucket not found")
|
|
}
|
|
var updates []*PersistentUnchargeEvent
|
|
err = stateBucket.ForEach(func(k, v []byte) error {
|
|
event, err := deserializeUnchargeUpdate(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
updates = append(updates, event)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var hash lntypes.Hash
|
|
copy(hash[:], k)
|
|
|
|
swap := PersistentUncharge{
|
|
Contract: contract,
|
|
Hash: hash,
|
|
Events: updates,
|
|
}
|
|
|
|
swaps = append(swaps, &swap)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return swaps, nil
|
|
}
|
|
|
|
// createUncharge adds an initiated swap to the store.
|
|
func (s *boltSwapClientStore) createUncharge(hash lntypes.Hash,
|
|
swap *UnchargeContract) error {
|
|
|
|
if hash != swap.Preimage.Hash() {
|
|
return errors.New("hash and preimage do not match")
|
|
}
|
|
|
|
return s.db.Update(func(tx *bbolt.Tx) error {
|
|
bucket, err := tx.CreateBucketIfNotExists(unchargeSwapsBucketKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if bucket.Get(hash[:]) != nil {
|
|
return fmt.Errorf("swap %v already exists", swap.Preimage)
|
|
}
|
|
|
|
// Create bucket for swap.
|
|
swapBucket, err := bucket.CreateBucket(hash[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
contract, err := serializeUnchargeContract(swap)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Store contact.
|
|
if err := swapBucket.Put(contractKey, contract); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create empty updates bucket.
|
|
_, err = swapBucket.CreateBucket(updatesBucketKey)
|
|
return err
|
|
})
|
|
}
|
|
|
|
// updateUncharge stores a swap updateUncharge.
|
|
func (s *boltSwapClientStore) updateUncharge(hash lntypes.Hash, time time.Time,
|
|
state SwapState) error {
|
|
|
|
return s.db.Update(func(tx *bbolt.Tx) error {
|
|
bucket := tx.Bucket(unchargeSwapsBucketKey)
|
|
if bucket == nil {
|
|
return errors.New("bucket does not exist")
|
|
}
|
|
|
|
swapBucket := bucket.Bucket(hash[:])
|
|
if swapBucket == nil {
|
|
return errors.New("swap not found")
|
|
}
|
|
|
|
updateBucket := swapBucket.Bucket(updatesBucketKey)
|
|
if updateBucket == nil {
|
|
return errors.New("udpate bucket not found")
|
|
}
|
|
|
|
id, err := updateBucket.NextSequence()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updateValue, err := serializeUnchargeUpdate(time, state)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return updateBucket.Put(itob(id), updateValue)
|
|
})
|
|
}
|
|
|
|
// Close closes the underlying bolt db.
|
|
func (s *boltSwapClientStore) close() error {
|
|
return s.db.Close()
|
|
}
|
|
|
|
func deserializeUnchargeContract(value []byte) (*UnchargeContract, error) {
|
|
r := bytes.NewReader(value)
|
|
|
|
contract, err := deserializeContract(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
swap := UnchargeContract{
|
|
SwapContract: *contract,
|
|
}
|
|
|
|
addr, err := wire.ReadVarString(r, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
swap.DestAddr, err = btcutil.DecodeAddress(addr, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
swap.SwapInvoice, err = wire.ReadVarString(r, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Read(r, byteOrder, &swap.SweepConfTarget); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Read(r, byteOrder, &swap.MaxSwapRoutingFee); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var unchargeChannel uint64
|
|
if err := binary.Read(r, byteOrder, &unchargeChannel); err != nil {
|
|
return nil, err
|
|
}
|
|
if unchargeChannel != 0 {
|
|
swap.UnchargeChannel = &unchargeChannel
|
|
}
|
|
|
|
return &swap, nil
|
|
}
|
|
|
|
func serializeUnchargeContract(swap *UnchargeContract) (
|
|
[]byte, error) {
|
|
|
|
var b bytes.Buffer
|
|
|
|
serializeContract(&swap.SwapContract, &b)
|
|
|
|
addr := swap.DestAddr.String()
|
|
if err := wire.WriteVarString(&b, 0, addr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := wire.WriteVarString(&b, 0, swap.SwapInvoice); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(&b, byteOrder, swap.SweepConfTarget); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(&b, byteOrder, swap.MaxSwapRoutingFee); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var unchargeChannel uint64
|
|
if swap.UnchargeChannel != nil {
|
|
unchargeChannel = *swap.UnchargeChannel
|
|
}
|
|
if err := binary.Write(&b, byteOrder, unchargeChannel); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func deserializeContract(r io.Reader) (*SwapContract, error) {
|
|
swap := SwapContract{}
|
|
var err error
|
|
var unixNano int64
|
|
if err := binary.Read(r, byteOrder, &unixNano); err != nil {
|
|
return nil, err
|
|
}
|
|
swap.InitiationTime = time.Unix(0, unixNano)
|
|
|
|
if err := binary.Read(r, byteOrder, &swap.Preimage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
binary.Read(r, byteOrder, &swap.AmountRequested)
|
|
|
|
swap.PrepayInvoice, err = wire.ReadVarString(r, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
n, err := r.Read(swap.SenderKey[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n != keyLength {
|
|
return nil, fmt.Errorf("sender key has invalid length")
|
|
}
|
|
|
|
n, err = r.Read(swap.ReceiverKey[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n != keyLength {
|
|
return nil, fmt.Errorf("receiver key has invalid length")
|
|
}
|
|
|
|
if err := binary.Read(r, byteOrder, &swap.CltvExpiry); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := binary.Read(r, byteOrder, &swap.MaxMinerFee); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Read(r, byteOrder, &swap.MaxSwapFee); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Read(r, byteOrder, &swap.MaxPrepayRoutingFee); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := binary.Read(r, byteOrder, &swap.InitiationHeight); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &swap, nil
|
|
}
|
|
|
|
func serializeContract(swap *SwapContract, b *bytes.Buffer) error {
|
|
if err := binary.Write(b, byteOrder, swap.InitiationTime.UnixNano()); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Write(b, byteOrder, swap.Preimage); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Write(b, byteOrder, swap.AmountRequested); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := wire.WriteVarString(b, 0, swap.PrepayInvoice); err != nil {
|
|
return err
|
|
}
|
|
|
|
n, err := b.Write(swap.SenderKey[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n != keyLength {
|
|
return fmt.Errorf("sender key has invalid length")
|
|
}
|
|
|
|
n, err = b.Write(swap.ReceiverKey[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n != keyLength {
|
|
return fmt.Errorf("receiver key has invalid length")
|
|
}
|
|
|
|
if err := binary.Write(b, byteOrder, swap.CltvExpiry); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Write(b, byteOrder, swap.MaxMinerFee); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Write(b, byteOrder, swap.MaxSwapFee); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Write(b, byteOrder, swap.MaxPrepayRoutingFee); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Write(b, byteOrder, swap.InitiationHeight); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func serializeUnchargeUpdate(time time.Time, state SwapState) (
|
|
[]byte, error) {
|
|
|
|
var b bytes.Buffer
|
|
|
|
if err := binary.Write(&b, byteOrder, time.UnixNano()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(&b, byteOrder, state); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func deserializeUnchargeUpdate(value []byte) (*PersistentUnchargeEvent, error) {
|
|
update := &PersistentUnchargeEvent{}
|
|
|
|
r := bytes.NewReader(value)
|
|
|
|
var unixNano int64
|
|
if err := binary.Read(r, byteOrder, &unixNano); err != nil {
|
|
return nil, err
|
|
}
|
|
update.Time = time.Unix(0, unixNano)
|
|
|
|
if err := binary.Read(r, byteOrder, &update.State); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return update, nil
|
|
}
|
|
|
|
// itob returns an 8-byte big endian representation of v.
|
|
func itob(v uint64) []byte {
|
|
b := make([]byte, 8)
|
|
byteOrder.PutUint64(b, v)
|
|
return b
|
|
}
|