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:
BitfuryLightning authored and Olaoluwa Osuntokun committed 2016-12-27 16:16:23 -08:00
1 parent 343cd7779f
commit eb4d0e035e
12 files changed
+1117 -168

No files matched your search

+2
View File
@@ -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
View File
@@ -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 {
+211
View File
@@ -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
}
+163
View File
@@ -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)
}
}