mirror of
https://github.com/lightninglabs/pool.git
synced 2026-08-15 12:50:29 +02:00
sidecar+clientdb: add sidecar storage
Both the trader offering to buy a sidecar channel for another node as well as the recipient node will need to store the involved sidecar tickets in their local databases. We add a simple store interface and its implementation that will be used in future commits.
This commit is contained in:
parent
363ddd5a8e
commit
f84debaeff
4 changed files with 267 additions and 0 deletions
|
|
@ -97,6 +97,10 @@ func initDB(filepath string, firstInit bool) (*bbolt.DB, error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.CreateBucketIfNotExists(sidecarsBucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.CreateBucketIfNotExists(batchBucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
173
clientdb/sidecar.go
Normal file
173
clientdb/sidecar.go
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
package clientdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/lightninglabs/pool/sidecar"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoSidecar is the error returned if no sidecar with the given
|
||||
// multisig pubkey exists in the store.
|
||||
ErrNoSidecar = errors.New("no sidecar found")
|
||||
|
||||
// sidecarsBucketKey is a bucket that contains all sidecars that are
|
||||
// currently pending or completed. This bucket is keyed by the ticket ID
|
||||
// and offer signing pubkey of a sidecar.
|
||||
sidecarsBucketKey = []byte("sidecars")
|
||||
)
|
||||
|
||||
const (
|
||||
// sidecarKeyLen is the length of a sidecar ticket's key. It is the
|
||||
// length of the sidecar ID (8 bytes) plus the length of a compressed
|
||||
// public key (33 bytes).
|
||||
sidecarKeyLen = 8 + 33
|
||||
)
|
||||
|
||||
// A compile time check to make sure we satisfy the sidecar.Store interface.
|
||||
var _ sidecar.Store = (*DB)(nil)
|
||||
|
||||
// getSidecarKey returns the key for a sidecar.
|
||||
func getSidecarKey(id [8]byte, offerSignPubKey *btcec.PublicKey) ([]byte,
|
||||
error) {
|
||||
|
||||
if offerSignPubKey == nil {
|
||||
return nil, fmt.Errorf("offer signing pubkey cannot be nil")
|
||||
}
|
||||
|
||||
var result [sidecarKeyLen]byte
|
||||
|
||||
copy(result[:], id[:])
|
||||
copy(result[8:], offerSignPubKey.SerializeCompressed())
|
||||
|
||||
return result[:], nil
|
||||
}
|
||||
|
||||
// AddSidecar adds a record for the sidecar to the database.
|
||||
func (db *DB) AddSidecar(ticket *sidecar.Ticket) error {
|
||||
sidecarKey, err := getSidecarKey(ticket.ID, ticket.Offer.SignPubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.Update(func(tx *bbolt.Tx) error {
|
||||
sidecarBucket, err := getBucket(tx, sidecarsBucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sidecarValue := sidecarBucket.Get(sidecarKey)
|
||||
if len(sidecarValue) != 0 {
|
||||
return fmt.Errorf("sidecar for key %x already exists",
|
||||
sidecarKey)
|
||||
}
|
||||
|
||||
return storeSidecar(sidecarBucket, sidecarKey, ticket)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSidecar updates a sidecar in the database.
|
||||
func (db *DB) UpdateSidecar(ticket *sidecar.Ticket) error {
|
||||
sidecarKey, err := getSidecarKey(ticket.ID, ticket.Offer.SignPubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.Update(func(tx *bbolt.Tx) error {
|
||||
sidecarBucket, err := getBucket(tx, sidecarsBucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sidecarValue := sidecarBucket.Get(sidecarKey)
|
||||
if len(sidecarValue) == 0 {
|
||||
return ErrNoSidecar
|
||||
}
|
||||
|
||||
return storeSidecar(sidecarBucket, sidecarKey, ticket)
|
||||
})
|
||||
}
|
||||
|
||||
// Sidecar retrieves a specific sidecar by its ID and provider signing key
|
||||
// (offer signature pubkey) or returns ErrNoSidecar if it's not found.
|
||||
func (db *DB) Sidecar(id [8]byte,
|
||||
offerSignPubKey *btcec.PublicKey) (*sidecar.Ticket, error) {
|
||||
|
||||
sidecarKey, err := getSidecarKey(id, offerSignPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var s *sidecar.Ticket
|
||||
err = db.View(func(tx *bbolt.Tx) error {
|
||||
sidecarBucket, err := getBucket(tx, sidecarsBucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err = readSidecar(sidecarBucket, sidecarKey)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Sidecars retrieves all known sidecars from the database.
|
||||
func (db *DB) Sidecars() ([]*sidecar.Ticket, error) {
|
||||
var res []*sidecar.Ticket
|
||||
err := db.View(func(tx *bbolt.Tx) error {
|
||||
sidecarBucket, err := getBucket(tx, sidecarsBucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sidecarBucket.ForEach(func(k, v []byte) error {
|
||||
// We don't expect any sub-buckets with sidecars.
|
||||
if v == nil {
|
||||
return fmt.Errorf("nil value for key %x", k)
|
||||
}
|
||||
|
||||
s, err := readSidecar(sidecarBucket, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res = append(res, s)
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func storeSidecar(targetBucket *bbolt.Bucket, key []byte,
|
||||
ticket *sidecar.Ticket) error {
|
||||
|
||||
var sidecarBuf bytes.Buffer
|
||||
if err := sidecar.SerializeTicket(&sidecarBuf, ticket); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return targetBucket.Put(key, sidecarBuf.Bytes())
|
||||
}
|
||||
|
||||
func readSidecar(sourceBucket *bbolt.Bucket, id []byte) (*sidecar.Ticket,
|
||||
error) {
|
||||
|
||||
sidecarBytes := sourceBucket.Get(id)
|
||||
if sidecarBytes == nil {
|
||||
return nil, ErrNoSidecar
|
||||
}
|
||||
|
||||
return sidecar.DeserializeTicket(bytes.NewReader(sidecarBytes))
|
||||
}
|
||||
72
clientdb/sidecar_test.go
Normal file
72
clientdb/sidecar_test.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package clientdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightninglabs/pool/order"
|
||||
"github.com/lightninglabs/pool/sidecar"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func assertSidecarExists(t *testing.T, db *DB, expected *sidecar.Ticket) {
|
||||
t.Helper()
|
||||
|
||||
found, err := db.Sidecar(expected.ID, expected.Offer.SignPubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, expected, found)
|
||||
}
|
||||
|
||||
// TestSidecars ensures that all database operations involving sidecars run as
|
||||
// expected.
|
||||
func TestSidecars(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, cleanup := newTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create a test sidecar we'll use to interact with the database.
|
||||
s := &sidecar.Ticket{
|
||||
ID: [8]byte{12, 34, 56},
|
||||
State: sidecar.StateRegistered,
|
||||
Offer: sidecar.Offer{
|
||||
Capacity: 1000000,
|
||||
PushAmt: 200000,
|
||||
SignPubKey: testTraderKey,
|
||||
LeaseDurationBlocks: 2016,
|
||||
},
|
||||
Recipient: &sidecar.Recipient{
|
||||
MultiSigPubKey: testTraderKey,
|
||||
MultiSigKeyIndex: 7,
|
||||
},
|
||||
}
|
||||
|
||||
// First, we'll add it to the database. We should be able to retrieve
|
||||
// after.
|
||||
err := db.AddSidecar(s)
|
||||
require.NoError(t, err)
|
||||
assertSidecarExists(t, db, s)
|
||||
|
||||
// Transition the sidecar state from SidecarInitialized to
|
||||
// SidecarExpectingChannel and add the required information for that
|
||||
// state.
|
||||
s.State = sidecar.StateExpectingChannel
|
||||
s.Order = &sidecar.Order{
|
||||
BidNonce: order.Nonce{1, 2, 3},
|
||||
}
|
||||
err = db.UpdateSidecar(s)
|
||||
require.NoError(t, err)
|
||||
assertSidecarExists(t, db, s)
|
||||
|
||||
// Retrieving all sidecars should show that we only have one sidecar,
|
||||
// the same one.
|
||||
sidecars, err := db.Sidecars()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sidecars, 1)
|
||||
require.Contains(t, sidecars, s)
|
||||
|
||||
// Make sure we can query a sidecar ticket by its ID and offer pubkey.
|
||||
updatedTicket, err := db.Sidecar([8]byte{12, 34, 56}, testTraderKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, s, updatedTicket)
|
||||
}
|
||||
|
|
@ -264,3 +264,21 @@ func (t *Ticket) OrderDigest() ([32]byte, error) {
|
|||
}
|
||||
return sha256.Sum256(msg.Bytes()), nil
|
||||
}
|
||||
|
||||
// Store is the interface a persistent storage must implement for storing and
|
||||
// retrieving sidecar tickets.
|
||||
type Store interface {
|
||||
// AddSidecar adds a record for the sidecar order to the database.
|
||||
AddSidecar(sidecar *Ticket) error
|
||||
|
||||
// UpdateSidecar updates a sidecar order in the database.
|
||||
UpdateSidecar(sidecar *Ticket) error
|
||||
|
||||
// Sidecar retrieves a specific sidecar by its ID and provider signing
|
||||
// key (offer signature pubkey) or returns ErrNoSidecar if it's not
|
||||
// found.
|
||||
Sidecar(id [8]byte, offerSignPubKey *btcec.PublicKey) (*Ticket, error)
|
||||
|
||||
// Sidecars retrieves all known sidecar orders from the database.
|
||||
Sidecars() ([]*Ticket, error)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue