From eb4d0e035ed717bd43752761fbb67b3e4bf403b7 Mon Sep 17 00:00:00 2001 From: BitfuryLightning Date: Mon, 5 Dec 2016 06:59:36 -0500 Subject: [PATCH] channeldb: save outgoing payments Add structure for outgoing payments. Saving payment in DB after successful payment send. Add RPC call for listing all payments. --- channeldb/error.go | 2 + channeldb/invoices.go | 17 +- channeldb/payments.go | 211 +++++++++++++++++ channeldb/payments_test.go | 163 +++++++++++++ cmd/lncli/commands.go | 24 ++ cmd/lncli/main.go | 1 + lnd_test.go | 128 ++++++++++ lnrpc/rpc.pb.go | 466 +++++++++++++++++++++++++------------ lnrpc/rpc.pb.gw.go | 82 +++++++ lnrpc/rpc.proto | 36 +++ lnrpc/rpc.swagger.json | 78 +++++++ rpcserver.go | 77 ++++-- 12 files changed, 1117 insertions(+), 168 deletions(-) create mode 100644 channeldb/payments.go create mode 100644 channeldb/payments_test.go diff --git a/channeldb/error.go b/channeldb/error.go index 036dca01..91422dd2 100644 --- a/channeldb/error.go +++ b/channeldb/error.go @@ -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") diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 56b7eed5..92ab9330 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -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 { diff --git a/channeldb/payments.go b/channeldb/payments.go new file mode 100644 index 00000000..5c91300d --- /dev/null +++ b/channeldb/payments.go @@ -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