channeldb: save outgoing payments
Add structure for outgoing payments. Saving payment in DB after successful payment send. Add RPC call for listing all payments.
This commit is contained in:
1 parent
343cd7779f
commit
eb4d0e035e
12 files changed
+1117
-168
No files matched your search
@@ -14,6 +14,8 @@ var (
|
||||
ErrNoInvoicesCreated = fmt.Errorf("there are no existing invoices")
|
||||
ErrDuplicateInvoice = fmt.Errorf("invoice with payment hash already exists")
|
||||
|
||||
ErrNoPaymentsCreated = fmt.Errorf("there are no existing payments")
|
||||
|
||||
ErrNodeNotFound = fmt.Errorf("link node with target identity not found")
|
||||
ErrMetaNotFound = fmt.Errorf("unable to locate meta information")
|
||||
|
||||
|
||||
+12
-5
@@ -98,11 +98,7 @@ type Invoice struct {
|
||||
Terms ContractTerm
|
||||
}
|
||||
|
||||
// AddInvoice inserts the targeted invoice into the database. If the invoice
|
||||
// has *any* payment hashes which already exists within the database, then the
|
||||
// insertion will be aborted and rejected due to the strict policy banning any
|
||||
// duplicate payment hashes.
|
||||
func (d *DB) AddInvoice(i *Invoice) error {
|
||||
func validateInvoice(i *Invoice) error {
|
||||
if len(i.Memo) > MaxMemoSize {
|
||||
return fmt.Errorf("max length a memo is %v, and invoice "+
|
||||
"of length %v was provided", MaxMemoSize, len(i.Memo))
|
||||
@@ -112,7 +108,18 @@ func (d *DB) AddInvoice(i *Invoice) error {
|
||||
"of length %v was provided", MaxReceiptSize,
|
||||
len(i.Receipt))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddInvoice inserts the targeted invoice into the database. If the invoice
|
||||
// has *any* payment hashes which already exists within the database, then the
|
||||
// insertion will be aborted and rejected due to the strict policy banning any
|
||||
// duplicate payment hashes.
|
||||
func (d *DB) AddInvoice(i *Invoice) error {
|
||||
err := validateInvoice(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.Update(func(tx *bolt.Tx) error {
|
||||
invoices, err := tx.CreateBucketIfNotExists(invoiceBucket)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"github.com/roasbeef/btcutil"
|
||||
"io"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/boltdb/bolt"
|
||||
"encoding/binary"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
var (
|
||||
// invoiceBucket is the name of the bucket within the database that
|
||||
// stores all data related to payments.
|
||||
// Within the payments bucket, each invoice is keyed by its invoice ID
|
||||
// which is a monotonically increasing uint64.
|
||||
// BoltDB sequence feature is used for generating monotonically increasing
|
||||
// id.
|
||||
paymentBucket = []byte("payments")
|
||||
)
|
||||
|
||||
type OutgoingPayment struct {
|
||||
Invoice
|
||||
// Total fee paid
|
||||
Fee btcutil.Amount
|
||||
// Path including starting and ending nodes
|
||||
Path [][]byte
|
||||
TimeLockLength uint64
|
||||
// We probably need both RHash and Preimage
|
||||
// because we start knowing only RHash
|
||||
RHash [32]byte
|
||||
}
|
||||
|
||||
func validatePayment(p *OutgoingPayment) error {
|
||||
err := validateInvoice(&p.Invoice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPayment adds payment to DB.
|
||||
// There is no checking that payment with the same hash already exist.
|
||||
func (db *DB) AddPayment(p *OutgoingPayment) error {
|
||||
err := validatePayment(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// We serialize before writing to database
|
||||
// so no db access in the case of serialization errors
|
||||
b := new(bytes.Buffer)
|
||||
err = serializeOutgoingPayment(b, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paymentBytes := b.Bytes()
|
||||
return db.Update(func (tx *bolt.Tx) error {
|
||||
payments, err := tx.CreateBucketIfNotExists(paymentBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paymentId, err := payments.NextSequence()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// We use BigEndian for keys because
|
||||
// it orders keys in ascending order
|
||||
paymentIdBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(paymentIdBytes, paymentId)
|
||||
err = payments.Put(paymentIdBytes, paymentBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// FetchAllPayments returns all outgoing payments in DB.
|
||||
func (db *DB) FetchAllPayments() ([]*OutgoingPayment, error) {
|
||||
var payments []*OutgoingPayment
|
||||
err := db.View(func (tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(paymentBucket)
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
err := bucket.ForEach(func (k, v []byte) error {
|
||||
// Value can be nil if it is a sub-backet
|
||||
// so simply ignore it.
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
r := bytes.NewReader(v)
|
||||
payment, err := deserializeOutgoingPayment(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payments = append(payments, payment)
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payments, nil
|
||||
}
|
||||
|
||||
// DeleteAllPayments deletes all payments from DB.
|
||||
// If payments bucket does not exist it will create
|
||||
// new bucket without error.
|
||||
func (db *DB) DeleteAllPayments() error {
|
||||
return db.Update(func (tx *bolt.Tx) error {
|
||||
err := tx.DeleteBucket(paymentBucket)
|
||||
if err != nil && err != bolt.ErrBucketNotFound {
|
||||
return err
|
||||
}
|
||||
_, err = tx.CreateBucket(paymentBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func serializeOutgoingPayment(w io.Writer, p *OutgoingPayment) error {
|
||||
err := serializeInvoice(w, &p.Invoice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Serialize fee.
|
||||
feeBytes := make([]byte, 8)
|
||||
byteOrder.PutUint64(feeBytes, uint64(p.Fee))
|
||||
_, err = w.Write(feeBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Serialize path.
|
||||
pathLen := uint32(len(p.Path))
|
||||
pathLenBytes := make([]byte, 4)
|
||||
byteOrder.PutUint32(pathLenBytes, pathLen)
|
||||
_, err = w.Write(pathLenBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := uint32(0); i < pathLen; i++ {
|
||||
err := wire.WriteVarBytes(w, 0, p.Path[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Serialize TimeLockLength
|
||||
timeLockLengthBytes := make([]byte, 8)
|
||||
byteOrder.PutUint64(timeLockLengthBytes, p.TimeLockLength)
|
||||
_, err = w.Write(timeLockLengthBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Serialize RHash
|
||||
_, err = w.Write(p.RHash[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deserializeOutgoingPayment(r io.Reader) (*OutgoingPayment, error) {
|
||||
p := &OutgoingPayment{}
|
||||
// Deserialize invoice
|
||||
inv, err := deserializeInvoice(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Invoice = *inv
|
||||
// Deserialize fee
|
||||
feeBytes := make([]byte, 8)
|
||||
_, err = r.Read(feeBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Fee = btcutil.Amount(byteOrder.Uint64(feeBytes))
|
||||
// Deserialize path
|
||||
pathLenBytes := make([]byte, 4)
|
||||
_, err = r.Read(pathLenBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pathLen := byteOrder.Uint32(pathLenBytes)
|
||||
path := make([][]byte, pathLen)
|
||||
for i := uint32(0); i<pathLen; i++ {
|
||||
// Each node in path have 33 bytes. It may be changed in future.
|
||||
// So put 100 here.
|
||||
path[i], err = wire.ReadVarBytes(r, 0, 100, "Node id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
p.Path = path
|
||||
// Deserialize TimeLockLength
|
||||
timeLockLengthBytes := make([]byte, 8)
|
||||
_, err = r.Read(timeLockLengthBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.TimeLockLength = byteOrder.Uint64(timeLockLengthBytes)
|
||||
// Deserialize RHash
|
||||
_, err = r.Read(p.RHash[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
"github.com/roasbeef/btcutil"
|
||||
"bytes"
|
||||
"reflect"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"math/rand"
|
||||
"fmt"
|
||||
"github.com/btcsuite/fastsha256"
|
||||
)
|
||||
|
||||
func makeFakePayment() *OutgoingPayment {
|
||||
// Create a fake invoice which we'll use several times in the tests
|
||||
// below.
|
||||
fakeInvoice := &Invoice{
|
||||
CreationDate: time.Now(),
|
||||
}
|
||||
fakeInvoice.Memo = []byte("memo")
|
||||
fakeInvoice.Receipt = []byte("recipt")
|
||||
copy(fakeInvoice.Terms.PaymentPreimage[:], rev[:])
|
||||
fakeInvoice.Terms.Value = btcutil.Amount(10000)
|
||||
// Make fake path
|
||||
fakePath := make([][]byte, 3)
|
||||
for i:=0; i<3; i++ {
|
||||
fakePath[i] = make([]byte, 33)
|
||||
for j:=0; j<33; j++ {
|
||||
fakePath[i][j] = byte(i)
|
||||
}
|
||||
}
|
||||
var rHash [32]byte = fastsha256.Sum256(rev[:])
|
||||
fakePayment := & OutgoingPayment{
|
||||
Invoice: *fakeInvoice,
|
||||
Fee: 101,
|
||||
Path: fakePath,
|
||||
TimeLockLength: 1000,
|
||||
RHash: rHash,
|
||||
}
|
||||
return fakePayment
|
||||
}
|
||||
|
||||
// randomBytes creates random []byte with length
|
||||
// in range [minLen, maxLen)
|
||||
func randomBytes(minLen, maxLen int) []byte {
|
||||
l := minLen + rand.Intn(maxLen-minLen)
|
||||
b := make([]byte, l)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Internal error. Cannot generate random string: %v", err))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func makeRandomFakePayment() *OutgoingPayment {
|
||||
// Create a fake invoice which we'll use several times in the tests
|
||||
// below.
|
||||
fakeInvoice := &Invoice{
|
||||
CreationDate: time.Now(),
|
||||
}
|
||||
fakeInvoice.Memo = randomBytes(1, 50)
|
||||
fakeInvoice.Receipt = randomBytes(1, 50)
|
||||
copy(fakeInvoice.Terms.PaymentPreimage[:], randomBytes(32, 33))
|
||||
fakeInvoice.Terms.Value = btcutil.Amount(rand.Intn(10000))
|
||||
// Make fake path
|
||||
fakePathLen := 1 + rand.Intn(5)
|
||||
fakePath := make([][]byte, fakePathLen)
|
||||
for i:=0; i<fakePathLen; i++ {
|
||||
fakePath[i] = randomBytes(33, 34)
|
||||
}
|
||||
var rHash [32]byte = fastsha256.Sum256(
|
||||
fakeInvoice.Terms.PaymentPreimage[:],
|
||||
)
|
||||
fakePayment := & OutgoingPayment{
|
||||
Invoice: *fakeInvoice,
|
||||
Fee: btcutil.Amount(rand.Intn(1001)),
|
||||
Path: fakePath,
|
||||
TimeLockLength: uint64(rand.Intn(10000)),
|
||||
RHash: rHash,
|
||||
}
|
||||
return fakePayment
|
||||
}
|
||||
|
||||
func TestOutgoingPaymentSerialization(t *testing.T) {
|
||||
fakePayment := makeFakePayment()
|
||||
b := new(bytes.Buffer)
|
||||
err := serializeOutgoingPayment(b, fakePayment)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't serialize outgoing payment: %v", err)
|
||||
}
|
||||
newPayment, err := deserializeOutgoingPayment(b)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't deserialize outgoing payment: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(fakePayment, newPayment) {
|
||||
t.Fatalf("Payments do not match after serialization/deserialization %v vs %v",
|
||||
spew.Sdump(fakePayment),
|
||||
spew.Sdump(newPayment),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutgoingPaymentWorkflow(t *testing.T) {
|
||||
db, cleanUp, err := makeTestDB()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make test db: %v", err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
fakePayment := makeFakePayment()
|
||||
err = db.AddPayment(fakePayment)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't put payment in DB: %v", err)
|
||||
}
|
||||
|
||||
payments, err := db.FetchAllPayments()
|
||||
if err != nil {
|
||||
t.Fatalf("Can't get payments from DB: %v", err)
|
||||
}
|
||||
correctPayments := []*OutgoingPayment{fakePayment}
|
||||
if !reflect.DeepEqual(payments, correctPayments) {
|
||||
t.Fatalf("Wrong payments after reading from DB."+
|
||||
"Got %v, want %v",
|
||||
spew.Sdump(payments),
|
||||
spew.Sdump(correctPayments),
|
||||
)
|
||||
}
|
||||
|
||||
// Make some random payments
|
||||
for i:=0; i<5; i++ {
|
||||
randomPayment := makeRandomFakePayment()
|
||||
err := db.AddPayment(randomPayment)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't put payment in DB: %v", err)
|
||||
}
|
||||
correctPayments = append(correctPayments, randomPayment)
|
||||
}
|
||||
payments, err = db.FetchAllPayments()
|
||||
if err != nil {
|
||||
t.Fatalf("Can't get payments from DB: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(payments, correctPayments) {
|
||||
t.Fatalf("Wrong payments after reading from DB."+
|
||||
"Got %v, want %v",
|
||||
spew.Sdump(payments),
|
||||
spew.Sdump(correctPayments),
|
||||
)
|
||||
}
|
||||
|
||||
// Delete all payments.
|
||||
err = db.DeleteAllPayments()
|
||||
if err != nil {
|
||||
t.Fatalf("Can't delete payments from DB: %v", err)
|
||||
}
|
||||
paymentsAfterDeletion, err := db.FetchAllPayments()
|
||||
if err != nil {
|
||||
t.Fatalf("Can't get payments after deletion: %v", err)
|
||||
}
|
||||
if len(paymentsAfterDeletion) != 0 {
|
||||
t.Fatalf("After deletion DB has %v payments, want %v", len(paymentsAfterDeletion), 0)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user