From d8ae780b7034054b12646c52a7bc1c865d1ebc82 Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Thu, 30 Sep 2021 09:20:52 +0100 Subject: [PATCH] tidying up with some helper methods and returning a new ErrMissingOutput error if an expected output isn't present on a parent tx --- spv/envelope.go | 17 +++++++++++++++++ spv/errors.go | 3 +++ spv/verifypayment.go | 28 +++++++++++----------------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/spv/envelope.go b/spv/envelope.go index 9643c63..b829e90 100644 --- a/spv/envelope.go +++ b/spv/envelope.go @@ -1,6 +1,9 @@ package spv import ( + "github.com/libsv/go-bt/v2" + "github.com/pkg/errors" + "github.com/libsv/go-bc" ) @@ -19,3 +22,17 @@ type Envelope struct { func (e *Envelope) IsAnchored() bool { return e.Proof != nil } + +// HasParents returns true if this envelope has immediate parents. +func (e *Envelope) HasParents() bool { + return e.Parents == nil || len(e.Parents) == 0 +} + +// ParentTX will return a parent if found and convert the rawTx to a bt.TX, otherwise a ErrNotAllInputsSupplied error is returned. +func (e *Envelope) ParentTX(txID string) (*bt.Tx, error) { + env, ok := e.Parents[txID] + if !ok { + return nil, errors.Wrapf(ErrNotAllInputsSupplied, "expected parent tx %s is missing", txID) + } + return bt.NewTxFromString(env.RawTx) +} diff --git a/spv/errors.go b/spv/errors.go index d3a58b1..9934437 100644 --- a/spv/errors.go +++ b/spv/errors.go @@ -43,4 +43,7 @@ var ( // ErrInvalidProof is returned if the merkle proof validation fails. ErrInvalidProof = errors.New("invalid merkle proof, payment invalid") + + // ErrMissingOutput is returned when checking fees if an output in a parent tx is missing. + ErrMissingOutput = errors.New("expected output used in payment tx missing") ) diff --git a/spv/verifypayment.go b/spv/verifypayment.go index cce57e0..9a2d0cf 100644 --- a/spv/verifypayment.go +++ b/spv/verifypayment.go @@ -61,20 +61,20 @@ func (v *verifier) VerifyPayment(ctx context.Context, initialPayment *Envelope, // // If there are no parents the method will fail, also, if there are no fees the method will fail. func (v *verifier) verifyFees(initialPayment *Envelope, tx *bt.Tx, opts *verifyOptions) error { - if initialPayment.Parents == nil || len(initialPayment.Parents) == 0 { + if initialPayment.HasParents() { return ErrCannotCalculateFeePaid } if opts.feeQuote == nil { return ErrNoFeeQuoteSupplied } for _, input := range tx.Inputs { - pTx, err := bt.NewTxFromString(initialPayment.Parents[input.PreviousTxIDStr()].RawTx) + parent, err := initialPayment.ParentTX(input.PreviousTxIDStr()) if err != nil { - return err + return errors.Wrapf(err, "tx %s failed to get parent tx", tx.TxID()) } - out := pTx.OutputIdx(int(input.PreviousTxOutIndex)) + out := parent.OutputIdx(int(input.PreviousTxOutIndex)) if out == nil { - continue + return ErrMissingOutput } input.PreviousTxSatoshis = out.Satoshis } @@ -90,7 +90,7 @@ func (v *verifier) verifyFees(initialPayment *Envelope, tx *bt.Tx, opts *verifyO func (v *verifier) verifyTxs(ctx context.Context, payment *Envelope, opts *verifyOptions) error { // If at the beginning or middle of the tx chain and tx is unconfirmed, fail and error. - if opts.proofs && !payment.IsAnchored() && (payment.Parents == nil || len(payment.Parents) == 0) { + if opts.proofs && !payment.IsAnchored() && payment.HasParents() { return errors.Wrapf(ErrNoConfirmedTransaction, "tx %s has no confirmed/anchored tx", payment.TxID) } @@ -108,7 +108,7 @@ func (v *verifier) verifyTxs(ctx context.Context, payment *Envelope, opts *verif // If a Merkle Proof is provided, assume we are at the anchor/beginning of the tx chain. // Verify and return the result. - if payment.IsAnchored() || payment.Parents == nil || len(payment.Parents) == 0 { + if payment.IsAnchored() || payment.HasParents() { if opts.proofs { return v.verifyTxAnchor(ctx, payment) } @@ -160,23 +160,17 @@ func (v *verifier) verifyUnconfirmedTx(tx *bt.Tx, payment *Envelope) error { } for _, input := range tx.Inputs { - parent, ok := payment.Parents[input.PreviousTxIDStr()] - if !ok { - return errors.Wrapf(ErrNotAllInputsSupplied, "tx %s is missing input %s in its envelope", tx.TxID(), input.PreviousTxIDStr()) - } - - parentTx, err := bt.NewTxFromString(parent.RawTx) + parent, err := payment.ParentTX(input.PreviousTxIDStr()) if err != nil { - return err + return errors.Wrapf(err, "tx %s missing parent", tx.TxID()) } - // If the input is indexing an output that is out of bounds, fail and error - if int(input.PreviousTxOutIndex) > len(parentTx.Outputs)-1 { + if int(input.PreviousTxOutIndex) > len(parent.Outputs)-1 { return errors.Wrapf(ErrInputRefsOutOfBoundsOutput, "input %s is referring out of bounds output %d", input.PreviousTxIDStr(), input.PreviousTxOutIndex) } - output := parentTx.Outputs[int(input.PreviousTxOutIndex)] + output := parent.OutputIdx(int(input.PreviousTxOutIndex)) // TODO: verify script using input and previous output _ = output }