From 8996f50dc2434e0da2919ca13f26a36976b40c58 Mon Sep 17 00:00:00 2001 From: wregulski Date: Fri, 10 Nov 2023 14:45:47 +0100 Subject: [PATCH 01/12] feat: finish first part of getting parent txs --- beef_tx.go | 191 +++++++++++++++++++++++++++++++++++++++--------- beef_tx_test.go | 51 ++++++++----- paymail.go | 15 ++-- 3 files changed, 198 insertions(+), 59 deletions(-) diff --git a/beef_tx.go b/beef_tx.go index e58137b2..3fc937fd 100644 --- a/beef_tx.go +++ b/beef_tx.go @@ -9,11 +9,48 @@ import ( "github.com/libsv/go-bt/v2" ) +type ToConstructBUMP struct { + BUMPInputs []*BUMPInputs +} + +type BUMPInputs struct { + Input *bt.Tx + HasBUMP bool + ParentTransactions []*bt.Tx +} + const maxBeefVer = uint32(0xFFFF) // value from BRC-62 -// ToBeefHex generates BEEF Hex for transaction -func ToBeefHex(ctx context.Context, tx *Transaction) (string, error) { - beef, err := newBeefTx(ctx, 1, tx) +func ToBeef(ctx context.Context, tx *Transaction) (string, error) { + // get inputs parent transactions + // inputs := tx.draftTransaction.Configuration.Inputs + // transactions := make([]*bt.Tx, 0, len(inputs)+1) + + // for _, input := range inputs { + // var prevTxs []*bt.Tx + // prevTxs, err = getParentTransactionsForInput(ctx, tx.client, input) + // if err != nil { + // return nil, fmt.Errorf("retrieve input parent transaction failed: %w", err) + // } + + // transactions = append(transactions, prevTxs...) + // } + + // // add current transaction + // var btTx *bt.Tx + // btTx, err = bt.NewTxFromString(tx.Hex) + // if err != nil { + // return nil, fmt.Errorf("cannot convert new transaction to bt.Tx from hex (tx.ID: %s). Reason: %w", tx.ID, err) + // } + // transactions = append(transactions, btTx) + + // khanTopologicalSortTransactions(transactions) + return "", nil +} + +// toBeefHex generates BEEF Hex for transaction +func toBeefHex(ctx context.Context, tx *Transaction, parentTxs []*bt.Tx) (string, error) { + beef, err := newBeefTx(ctx, 1, tx, parentTxs) if err != nil { return "", fmt.Errorf("ToBeefHex() error: %w", err) } @@ -32,7 +69,7 @@ type beefTx struct { transactions []*bt.Tx } -func newBeefTx(ctx context.Context, version uint32, tx *Transaction) (*beefTx, error) { +func newBeefTx(ctx context.Context, version uint32, tx *Transaction, parentTxs []*bt.Tx) (*beefTx, error) { if version > maxBeefVer { return nil, fmt.Errorf("version above 0x%X", maxBeefVer) } @@ -46,32 +83,10 @@ func newBeefTx(ctx context.Context, version uint32, tx *Transaction) (*beefTx, e return nil, err } - // get inputs parent transactions - inputs := tx.draftTransaction.Configuration.Inputs - transactions := make([]*bt.Tx, 0, len(inputs)+1) - - for _, input := range inputs { - var prevTxs []*bt.Tx - prevTxs, err = getParentTransactionsForInput(ctx, tx.client, input) - if err != nil { - return nil, fmt.Errorf("retrieve input parent transaction failed: %w", err) - } - - transactions = append(transactions, prevTxs...) - } - - // add current transaction - var btTx *bt.Tx - btTx, err = bt.NewTxFromString(tx.Hex) - if err != nil { - return nil, fmt.Errorf("cannot convert new transaction to bt.Tx from hex (tx.ID: %s). Reason: %w", tx.ID, err) - } - transactions = append(transactions, btTx) - beef := &beefTx{ version: version, bumps: tx.draftTransaction.BUMPs, - transactions: kahnTopologicalSortTransactions(transactions), + transactions: parentTxs, } return beef, nil @@ -107,20 +122,128 @@ func validateBumps(bumps BUMPs) error { return nil } -func getParentTransactionsForInput(ctx context.Context, client ClientInterface, input *TransactionInput) ([]*bt.Tx, error) { - inputTx, err := client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) - if err != nil { - return nil, err +// func getParentTransactionsForInput(ctx context.Context, client ClientInterface, input *TransactionInput) ([]*bt.Tx, error) { +// inputTx, err := client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) +// if err != nil { +// return nil, err +// } +// inputBtTx, err := bt.NewTxFromString(inputTx.Hex) + +// if inputTx.MerkleProof.TxOrID != "" || inputTx.BUMP.BlockHeight != 0 { +// if err != nil { +// return nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) +// } + +// return []*bt.Tx{inputBtTx}, nil +// } else { +// parentInputs := inputBtTx.Inputs +// parentInputsIds := make([]string, 0, len(parentInputs)) +// for _, parentInput := range parentInputs { +// parentInputsIds = append(parentInputsIds, parentInput.PreviousTxIDStr()) +// } + +// parentInputs := client.GetTransactions(ctx, parentInputsIds) + +// // sprawdź czy któryś z parentów nie spełnia tego warunku: +// // if inputTx.MerkleProof.TxOrID != "" || inputTx.BUMP.BlockHeight != 0 + +// // jeśli tak to weź jego parentów i postępuj tak do momentu az wszystkie parenty na danym poziomie beda spelniac ten warunek +// // zwroc wszystkie transakcje ktore maja bumpa i merkle proof + +// return nil, nil +// } + +// return nil, fmt.Errorf("transaction is not mined yet (tx.ID: %s)", inputTx.ID) // TODO: handle it in next iterration +// } + +// getClientTransactions is a helper function to get transactions in batch to reduce API calls. +func getClientTransactions(ctx context.Context, client ClientInterface, txIds []string) (map[string]*bt.Tx, error) { + transactions := make(map[string]*bt.Tx) + for _, txId := range txIds { + tx, err := client.GetTransactionByID(ctx, txId) + if err != nil { + return nil, err + } + btTx, err := bt.NewTxFromString(tx.Hex) + if err != nil { + return nil, err + } + transactions[txId] = btTx } + return transactions, nil +} + +// getParentTransactionsForInput is a recursive function to find all parent transactions +// with a valid MerkleProof or BUMP. +func getParentTransactionsForInput(ctx context.Context, client ClientInterface, inputs []*TransactionInput) (*ToConstructBUMP, error) { + var toConstructBUMP ToConstructBUMP + + for _, input := range inputs { + inputTx, err := client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) + if err != nil { + return nil, err + } - if inputTx.MerkleProof.TxOrID != "" { inputBtTx, err := bt.NewTxFromString(inputTx.Hex) if err != nil { return nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) } - return []*bt.Tx{inputBtTx}, nil + if inputTx.MerkleProof.TxOrID != "" || inputTx.BUMP.BlockHeight != 0 { + toConstructBUMP.BUMPInputs = append(toConstructBUMP.BUMPInputs, &BUMPInputs{ + Input: inputBtTx, + HasBUMP: true, + }) + } else { + parentTransactions, err := checkParentTransactions(ctx, client, inputTx) + if err != nil { + return nil, err + } + toConstructBUMP.BUMPInputs = append(toConstructBUMP.BUMPInputs, &BUMPInputs{ + Input: inputBtTx, + HasBUMP: false, + ParentTransactions: parentTransactions, + }) + } + } + + return &toConstructBUMP, nil +} + +// checkParentTransactions is a helper recursive function to check the parent transactions. +func checkParentTransactions(ctx context.Context, client ClientInterface, inputTx *Transaction) ([]*bt.Tx, error) { + btTx, err := bt.NewTxFromString(inputTx.Hex) + if err != nil { + return nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) + } + + var validTxs []*bt.Tx + for _, txIn := range btTx.Inputs { + parentTx, err := client.GetTransactionByID(ctx, txIn.PreviousTxIDStr()) + if err != nil { + return nil, err + } + + // If the parent transaction has a MerkleProof or a BUMP, add it to the list. + if parentTx.MerkleProof.TxOrID != "" || parentTx.BUMP.BlockHeight != 0 { + parentBtTx, err := bt.NewTxFromString(parentTx.Hex) + if err != nil { + return nil, err + } + validTxs = append(validTxs, parentBtTx) + } else { + // Otherwise, recursively check the parents of this parent. + parentValidTxs, err := checkParentTransactions(ctx, client, parentTx) + if err != nil { + return nil, err + } + validTxs = append(validTxs, parentValidTxs...) + } + } + + if len(validTxs) == 0 { + return nil, fmt.Errorf("transaction is not mined yet (tx.ID: %s)", inputTx.ID) } - return nil, fmt.Errorf("transaction is not mined yet (tx.ID: %s)", inputTx.ID) // TODO: handle it in next iterration + return validTxs, nil } diff --git a/beef_tx_test.go b/beef_tx_test.go index 313e401a..6ecc337a 100644 --- a/beef_tx_test.go +++ b/beef_tx_test.go @@ -9,29 +9,46 @@ import ( "github.com/stretchr/testify/require" ) -func Test_ToBeefHex(t *testing.T) { - // TOOD: prepare tests in BUX-168 +// TODO: BUX-168 - fix this test +// func Test_ToBeefHex(t *testing.T) { +// t.Run("all parents txs are already mined", func(t *testing.T) { +// // given +// ctx, client, deferMe := initSimpleTestCase(t) +// defer deferMe() - t.Run("some parents txs are not mined yet", func(t *testing.T) { - // Error expected! this should be changed in the future. right now the test case has been written to make sure the system doesn't panic in such a situation +// ancestorTx := addGrandpaTx(ctx, t, client) +// minedParentTx := createTxWithDraft(ctx, t, client, ancestorTx, true) - // given - ctx, client, deferMe := initSimpleTestCase(t) - defer deferMe() +// newTx := createTxWithDraft(ctx, t, client, minedParentTx, false) - ancestorTx := addGrandpaTx(ctx, t, client) - notMinedParentTx := createTxWithDraft(ctx, t, client, ancestorTx, false) +// // when +// hex, err := ToBeefHex(ctx, newTx) - newTx := createTxWithDraft(ctx, t, client, notMinedParentTx, false) +// // then +// assert.NoError(t, err) +// assert.NotEmpty(t, hex) +// }) - // when - hex, err := ToBeefHex(ctx, newTx) +// t.Run("some parents txs are not mined yet", func(t *testing.T) { +// // Error expected! this should be changed in the future. right now the test case has been written to make sure the system doesn't panic in such a situation - // then - assert.Error(t, err) - assert.Empty(t, hex) - }) -} +// // given +// ctx, client, deferMe := initSimpleTestCase(t) +// defer deferMe() + +// ancestorTx := addGrandpaTx(ctx, t, client) +// notMinedParentTx := createTxWithDraft(ctx, t, client, ancestorTx, false) + +// newTx := createTxWithDraft(ctx, t, client, notMinedParentTx, false) + +// // when +// hex, err := ToBeefHex(ctx, newTx) + +// // then +// assert.Error(t, err) +// assert.Empty(t, hex) +// }) +// } func addGrandpaTx(ctx context.Context, t *testing.T, client ClientInterface) *Transaction { // great ancestor diff --git a/paymail.go b/paymail.go index 078ca631..502a9a11 100644 --- a/paymail.go +++ b/paymail.go @@ -13,8 +13,8 @@ import ( // getCapabilities is a utility function to retrieve capabilities for a Paymail provider func getCapabilities(ctx context.Context, cs cachestore.ClientInterface, client paymail.ClientInterface, - domain string) (*paymail.CapabilitiesPayload, error) { - + domain string, +) (*paymail.CapabilitiesPayload, error) { // Attempt to get from cachestore // todo: allow user to configure the time that they want to cache the capabilities (if they want to cache or not) capabilities := new(paymail.CapabilitiesPayload) @@ -91,8 +91,8 @@ func hasP2P(capabilities *paymail.CapabilitiesPayload) (success bool, p2pDestina // // Deprecated: this is already deprecated by TSC, use P2P or the new P4 func resolvePaymailAddress(ctx context.Context, cs cachestore.ClientInterface, client paymail.ClientInterface, - capabilities *paymail.CapabilitiesPayload, alias, domain, purpose, senderPaymail string) (*paymail.ResolutionPayload, error) { - + capabilities *paymail.CapabilitiesPayload, alias, domain, purpose, senderPaymail string, +) (*paymail.ResolutionPayload, error) { // Attempt to get from cachestore // todo: allow user to configure the time that they want to cache the address resolution (if they want to cache or not) resolution := new(paymail.ResolutionPayload) @@ -139,8 +139,8 @@ func resolvePaymailAddress(ctx context.Context, cs cachestore.ClientInterface, c // startP2PTransaction will start the P2P transaction, returning the reference ID and outputs func startP2PTransaction(client paymail.ClientInterface, - alias, domain, p2pDestinationURL string, satoshis uint64) (*paymail.PaymentDestinationPayload, error) { - + alias, domain, p2pDestinationURL string, satoshis uint64, +) (*paymail.PaymentDestinationPayload, error) { // Start the P2P transaction request response, err := client.GetP2PPaymentDestination( p2pDestinationURL, @@ -191,8 +191,7 @@ func buildP2pTx(ctx context.Context, p4 *PaymailP4, transaction *Transaction) (* switch p4.Format { case BeefPaymailPayloadFormat: - beef, err := ToBeefHex(ctx, transaction) - + beef, err := ToBeef(ctx, transaction) if err != nil { return nil, err } From 9608c27f62107b485d79db2b9973bc7d870e618d Mon Sep 17 00:00:00 2001 From: wregulski Date: Fri, 10 Nov 2023 16:08:57 +0100 Subject: [PATCH 02/12] feat: beef for parent inputs --- beef_bump.go | 141 +++++++++++++++++++++++++ beef_tx.go | 199 ++++-------------------------------- model_draft_transactions.go | 52 ++++------ 3 files changed, 180 insertions(+), 212 deletions(-) create mode 100644 beef_bump.go diff --git a/beef_bump.go b/beef_bump.go new file mode 100644 index 00000000..d37b0e82 --- /dev/null +++ b/beef_bump.go @@ -0,0 +1,141 @@ +package bux + +import ( + "context" + "errors" + "fmt" + + "github.com/libsv/go-bt/v2" +) + +func calculateMergedBUMP(txs []*Transaction) (BUMPs, error) { + bumps := make(map[uint64][]BUMP) + mergedBUMPs := make(BUMPs, 0) + + for _, tx := range txs { + if tx.BUMP.BlockHeight == 0 || len(tx.BUMP.Path) == 0 { + return nil, fmt.Errorf("BUMP is not valid (tx.ID: %s)", tx.ID) + } + + bumps[tx.BlockHeight] = append(bumps[tx.BlockHeight], tx.BUMP) + } + for _, b := range bumps { + bump, err := CalculateMergedBUMP(b) + if err != nil { + return nil, fmt.Errorf("Error while calculating Merged BUMP: %s", err.Error()) + } + if bump == nil { + continue + } + mergedBUMPs = append(mergedBUMPs, bump) + } + + return mergedBUMPs, nil +} + +func validateBumps(bumps BUMPs) error { + if len(bumps) == 0 { + return errors.New("empty bump paths slice") + } + + for _, p := range bumps { + if len(p.Path) == 0 { + return errors.New("one of bump path is empty") + } + } + + return nil +} + +// getClientTransactions is a helper function to get transactions in batch to reduce API calls. +func getClientTransactions(ctx context.Context, client ClientInterface, txIds []string) (map[string]*bt.Tx, error) { + transactions := make(map[string]*bt.Tx) + for _, txId := range txIds { + tx, err := client.GetTransactionByID(ctx, txId) + if err != nil { + return nil, err + } + btTx, err := bt.NewTxFromString(tx.Hex) + if err != nil { + return nil, err + } + transactions[txId] = btTx + } + return transactions, nil +} + +// prepareBUMPFactors is a recursive function to find all parent transactions +// with a valid MerkleProof or BUMP. +func prepareBUMPFactors(ctx context.Context, client ClientInterface, inputs []*TransactionInput) ([]*bt.Tx, []*Transaction, error) { + var btTxsNeededForBUMP []*bt.Tx + var txsNeededForBUMP []*Transaction + + for _, input := range inputs { + // TODO: Before finishing I will try to move to client.GetTransactions to reduce calls. Need to dig into the metadata construction. + inputTx, err := client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) + if err != nil { + return nil, nil, err + } + + inputBtTx, err := bt.NewTxFromString(inputTx.Hex) + if err != nil { + return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) + } + + if inputTx.MerkleProof.TxOrID != "" || inputTx.BUMP.BlockHeight != 0 || len(inputTx.BUMP.Path) != 0 { + txsNeededForBUMP = append(txsNeededForBUMP, inputTx) + btTxsNeededForBUMP = append(btTxsNeededForBUMP, inputBtTx) + } else { + parentBtTransactions, parentTransactions, err := checkParentTransactions(ctx, client, inputTx) + if err != nil { + return nil, nil, err + } + + txsNeededForBUMP = append(txsNeededForBUMP, parentTransactions...) + btTxsNeededForBUMP = append(btTxsNeededForBUMP, parentBtTransactions...) + } + } + + return btTxsNeededForBUMP, txsNeededForBUMP, nil +} + +// checkParentTransactions is a helper recursive function to check the parent transactions. +func checkParentTransactions(ctx context.Context, client ClientInterface, inputTx *Transaction) ([]*bt.Tx, []*Transaction, error) { + btTx, err := bt.NewTxFromString(inputTx.Hex) + if err != nil { + return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) + } + + var validTxs []*Transaction + var validBtTxs []*bt.Tx + for _, txIn := range btTx.Inputs { + parentTx, err := client.GetTransactionByID(ctx, txIn.PreviousTxIDStr()) + if err != nil { + return nil, nil, err + } + + // If the parent transaction has a MerkleProof or a BUMP, add it to the list. + if parentTx.MerkleProof.TxOrID != "" || parentTx.BUMP.BlockHeight != 0 { + parentBtTx, err := bt.NewTxFromString(parentTx.Hex) + if err != nil { + return nil, nil, err + } + validTxs = append(validTxs, parentTx) + validBtTxs = append(validBtTxs, parentBtTx) + } else { + // Otherwise, recursively check the parents of this parent. + parentValidBtTxs, parentValidTxs, err := checkParentTransactions(ctx, client, parentTx) + if err != nil { + return nil, nil, err + } + validTxs = append(validTxs, parentValidTxs...) + validBtTxs = append(validBtTxs, parentValidBtTxs...) + } + } + + if len(validBtTxs) == 0 { + return nil, nil, fmt.Errorf("transaction is not mined yet (tx.ID: %s)", inputTx.ID) + } + + return validBtTxs, validTxs, nil +} diff --git a/beef_tx.go b/beef_tx.go index 3fc937fd..93cbc2aa 100644 --- a/beef_tx.go +++ b/beef_tx.go @@ -3,49 +3,34 @@ package bux import ( "context" "encoding/hex" - "errors" "fmt" "github.com/libsv/go-bt/v2" ) -type ToConstructBUMP struct { - BUMPInputs []*BUMPInputs -} +const maxBeefVer = uint32(0xFFFF) // value from BRC-62 -type BUMPInputs struct { - Input *bt.Tx - HasBUMP bool - ParentTransactions []*bt.Tx +type beefTx struct { + version uint32 + bumps BUMPs + transactions []*bt.Tx } -const maxBeefVer = uint32(0xFFFF) // value from BRC-62 - func ToBeef(ctx context.Context, tx *Transaction) (string, error) { - // get inputs parent transactions - // inputs := tx.draftTransaction.Configuration.Inputs - // transactions := make([]*bt.Tx, 0, len(inputs)+1) - - // for _, input := range inputs { - // var prevTxs []*bt.Tx - // prevTxs, err = getParentTransactionsForInput(ctx, tx.client, input) - // if err != nil { - // return nil, fmt.Errorf("retrieve input parent transaction failed: %w", err) - // } - - // transactions = append(transactions, prevTxs...) - // } - - // // add current transaction - // var btTx *bt.Tx - // btTx, err = bt.NewTxFromString(tx.Hex) - // if err != nil { - // return nil, fmt.Errorf("cannot convert new transaction to bt.Tx from hex (tx.ID: %s). Reason: %w", tx.ID, err) - // } - // transactions = append(transactions, btTx) - - // khanTopologicalSortTransactions(transactions) - return "", nil + inputs := tx.draftTransaction.Configuration.Inputs + bumpBtFactors, bumpFactors, err := prepareBUMPFactors(ctx, tx.client, inputs) + if err != nil { + return "", fmt.Errorf("prepareBUMPFactors() error: %w", err) + } + + tx.draftTransaction.BUMPs, err = calculateMergedBUMP(bumpFactors) + sortedTxs := kahnTopologicalSortTransactions(bumpBtFactors) + beefHex, err := toBeefHex(ctx, tx, sortedTxs) + if err != nil { + return "", fmt.Errorf("ToBeef() error: %w", err) + } + + return beefHex, nil } // toBeefHex generates BEEF Hex for transaction @@ -63,12 +48,6 @@ func toBeefHex(ctx context.Context, tx *Transaction, parentTxs []*bt.Tx) (string return hex.EncodeToString(beefBytes), nil } -type beefTx struct { - version uint32 - bumps BUMPs - transactions []*bt.Tx -} - func newBeefTx(ctx context.Context, version uint32, tx *Transaction, parentTxs []*bt.Tx) (*beefTx, error) { if version > maxBeefVer { return nil, fmt.Errorf("version above 0x%X", maxBeefVer) @@ -107,143 +86,3 @@ func hydrateTransaction(ctx context.Context, tx *Transaction) error { return nil } - -func validateBumps(bumps BUMPs) error { - if len(bumps) == 0 { - return errors.New("empty bump paths slice") - } - - for _, p := range bumps { - if len(p.Path) == 0 { - return errors.New("one of bump path is empty") - } - } - - return nil -} - -// func getParentTransactionsForInput(ctx context.Context, client ClientInterface, input *TransactionInput) ([]*bt.Tx, error) { -// inputTx, err := client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) -// if err != nil { -// return nil, err -// } -// inputBtTx, err := bt.NewTxFromString(inputTx.Hex) - -// if inputTx.MerkleProof.TxOrID != "" || inputTx.BUMP.BlockHeight != 0 { -// if err != nil { -// return nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) -// } - -// return []*bt.Tx{inputBtTx}, nil -// } else { -// parentInputs := inputBtTx.Inputs -// parentInputsIds := make([]string, 0, len(parentInputs)) -// for _, parentInput := range parentInputs { -// parentInputsIds = append(parentInputsIds, parentInput.PreviousTxIDStr()) -// } - -// parentInputs := client.GetTransactions(ctx, parentInputsIds) - -// // sprawdź czy któryś z parentów nie spełnia tego warunku: -// // if inputTx.MerkleProof.TxOrID != "" || inputTx.BUMP.BlockHeight != 0 - -// // jeśli tak to weź jego parentów i postępuj tak do momentu az wszystkie parenty na danym poziomie beda spelniac ten warunek -// // zwroc wszystkie transakcje ktore maja bumpa i merkle proof - -// return nil, nil -// } - -// return nil, fmt.Errorf("transaction is not mined yet (tx.ID: %s)", inputTx.ID) // TODO: handle it in next iterration -// } - -// getClientTransactions is a helper function to get transactions in batch to reduce API calls. -func getClientTransactions(ctx context.Context, client ClientInterface, txIds []string) (map[string]*bt.Tx, error) { - transactions := make(map[string]*bt.Tx) - for _, txId := range txIds { - tx, err := client.GetTransactionByID(ctx, txId) - if err != nil { - return nil, err - } - btTx, err := bt.NewTxFromString(tx.Hex) - if err != nil { - return nil, err - } - transactions[txId] = btTx - } - return transactions, nil -} - -// getParentTransactionsForInput is a recursive function to find all parent transactions -// with a valid MerkleProof or BUMP. -func getParentTransactionsForInput(ctx context.Context, client ClientInterface, inputs []*TransactionInput) (*ToConstructBUMP, error) { - var toConstructBUMP ToConstructBUMP - - for _, input := range inputs { - inputTx, err := client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) - if err != nil { - return nil, err - } - - inputBtTx, err := bt.NewTxFromString(inputTx.Hex) - if err != nil { - return nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) - } - - if inputTx.MerkleProof.TxOrID != "" || inputTx.BUMP.BlockHeight != 0 { - toConstructBUMP.BUMPInputs = append(toConstructBUMP.BUMPInputs, &BUMPInputs{ - Input: inputBtTx, - HasBUMP: true, - }) - } else { - parentTransactions, err := checkParentTransactions(ctx, client, inputTx) - if err != nil { - return nil, err - } - toConstructBUMP.BUMPInputs = append(toConstructBUMP.BUMPInputs, &BUMPInputs{ - Input: inputBtTx, - HasBUMP: false, - ParentTransactions: parentTransactions, - }) - } - } - - return &toConstructBUMP, nil -} - -// checkParentTransactions is a helper recursive function to check the parent transactions. -func checkParentTransactions(ctx context.Context, client ClientInterface, inputTx *Transaction) ([]*bt.Tx, error) { - btTx, err := bt.NewTxFromString(inputTx.Hex) - if err != nil { - return nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) - } - - var validTxs []*bt.Tx - for _, txIn := range btTx.Inputs { - parentTx, err := client.GetTransactionByID(ctx, txIn.PreviousTxIDStr()) - if err != nil { - return nil, err - } - - // If the parent transaction has a MerkleProof or a BUMP, add it to the list. - if parentTx.MerkleProof.TxOrID != "" || parentTx.BUMP.BlockHeight != 0 { - parentBtTx, err := bt.NewTxFromString(parentTx.Hex) - if err != nil { - return nil, err - } - validTxs = append(validTxs, parentBtTx) - } else { - // Otherwise, recursively check the parents of this parent. - parentValidTxs, err := checkParentTransactions(ctx, client, parentTx) - if err != nil { - return nil, err - } - validTxs = append(validTxs, parentValidTxs...) - } - } - - if len(validTxs) == 0 { - return nil, fmt.Errorf("transaction is not mined yet (tx.ID: %s)", inputTx.ID) - } - - return validTxs, nil -} diff --git a/model_draft_transactions.go b/model_draft_transactions.go index f82f79c9..1b644766 100644 --- a/model_draft_transactions.go +++ b/model_draft_transactions.go @@ -377,53 +377,41 @@ func (m *DraftTransaction) createTransactionHex(ctx context.Context) (err error) return } - // final sanity check - inputValue := uint64(0) + if err = validateOutputsInputs(m.Configuration.Inputs, m.Configuration.Outputs, m.Configuration.Fee); err != nil { + return + } + + // Create the final hex (without signatures) + m.Hex = tx.String() + + return +} + +func validateOutputsInputs(inputs []*TransactionInput, outputs []*TransactionOutput, fee uint64) error { usedUtxos := make([]string, 0) - bumps := make(map[uint64][]BUMP) - for _, input := range m.Configuration.Inputs { - // check whether an utxo was used twice, this is not valid + inputValue := uint64(0) + outputValue := uint64(0) + + for _, input := range inputs { if utils.StringInSlice(input.Utxo.ID, usedUtxos) { return ErrDuplicateUTXOs } usedUtxos = append(usedUtxos, input.Utxo.ID) inputValue += input.Satoshis - tx, err := m.client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) - if err != nil { - return err - } - if len(tx.BUMP.Path) != 0 { - bumps[tx.BlockHeight] = append(bumps[tx.BlockHeight], tx.BUMP) - } } - outputValue := uint64(0) - for _, output := range m.Configuration.Outputs { + + for _, output := range outputs { outputValue += output.Satoshis } if inputValue < outputValue { return ErrOutputValueTooHigh } - if m.Configuration.Fee <= 0 { - return ErrTransactionFeeInvalid - } - if inputValue-outputValue != m.Configuration.Fee { + + if inputValue-outputValue != fee { return ErrTransactionFeeInvalid } - for _, b := range bumps { - bump, err := CalculateMergedBUMP(b) - if err != nil { - return fmt.Errorf("Error while calculating Merged BUMP: %s", err.Error()) - } - if bump == nil { - continue - } - m.BUMPs = append(m.BUMPs, bump) - } - // Create the final hex (without signatures) - m.Hex = tx.String() - - return + return nil } // addIncludeUtxos will add the included utxos From b97fd60345f8de472e2f8c0c66ff78eed2547185 Mon Sep 17 00:00:00 2001 From: wregulski Date: Wed, 15 Nov 2023 12:12:38 +0100 Subject: [PATCH 03/12] fix: add processed tx to working collections --- beef_bump.go | 46 +++++++++++++++++++++++----------------------- beef_tx.go | 14 ++++++-------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/beef_bump.go b/beef_bump.go index d37b0e82..3e7d4732 100644 --- a/beef_bump.go +++ b/beef_bump.go @@ -47,32 +47,17 @@ func validateBumps(bumps BUMPs) error { return nil } -// getClientTransactions is a helper function to get transactions in batch to reduce API calls. -func getClientTransactions(ctx context.Context, client ClientInterface, txIds []string) (map[string]*bt.Tx, error) { - transactions := make(map[string]*bt.Tx) - for _, txId := range txIds { - tx, err := client.GetTransactionByID(ctx, txId) - if err != nil { - return nil, err - } - btTx, err := bt.NewTxFromString(tx.Hex) - if err != nil { - return nil, err - } - transactions[txId] = btTx - } - return transactions, nil -} - // prepareBUMPFactors is a recursive function to find all parent transactions // with a valid MerkleProof or BUMP. -func prepareBUMPFactors(ctx context.Context, client ClientInterface, inputs []*TransactionInput) ([]*bt.Tx, []*Transaction, error) { - var btTxsNeededForBUMP []*bt.Tx - var txsNeededForBUMP []*Transaction +func prepareBUMPFactors(ctx context.Context, tx *Transaction) ([]*bt.Tx, []*Transaction, error) { + btTxsNeededForBUMP, txsNeededForBUMP, err := initializeRequiredTxsCollection(tx) + if err != nil { + return nil, nil, err + } - for _, input := range inputs { + for _, input := range tx.draftTransaction.Configuration.Inputs { // TODO: Before finishing I will try to move to client.GetTransactions to reduce calls. Need to dig into the metadata construction. - inputTx, err := client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) + inputTx, err := tx.client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) if err != nil { return nil, nil, err } @@ -86,7 +71,7 @@ func prepareBUMPFactors(ctx context.Context, client ClientInterface, inputs []*T txsNeededForBUMP = append(txsNeededForBUMP, inputTx) btTxsNeededForBUMP = append(btTxsNeededForBUMP, inputBtTx) } else { - parentBtTransactions, parentTransactions, err := checkParentTransactions(ctx, client, inputTx) + parentBtTransactions, parentTransactions, err := checkParentTransactions(ctx, tx.client, inputTx) if err != nil { return nil, nil, err } @@ -99,6 +84,21 @@ func prepareBUMPFactors(ctx context.Context, client ClientInterface, inputs []*T return btTxsNeededForBUMP, txsNeededForBUMP, nil } +func initializeRequiredTxsCollection(tx *Transaction) ([]*bt.Tx, []*Transaction, error) { + var btTxsNeededForBUMP []*bt.Tx + var txsNeededForBUMP []*Transaction + + processedBtTx, err := bt.NewTxFromString(tx.Hex) + if err != nil { + return nil, nil, fmt.Errorf("cannot convert processed tx to bt.Tx from hex (tx.ID: %s). Reason: %w", tx.ID, err) + } + + btTxsNeededForBUMP = append(btTxsNeededForBUMP, processedBtTx) + txsNeededForBUMP = append(txsNeededForBUMP, tx) + + return btTxsNeededForBUMP, txsNeededForBUMP, nil +} + // checkParentTransactions is a helper recursive function to check the parent transactions. func checkParentTransactions(ctx context.Context, client ClientInterface, inputTx *Transaction) ([]*bt.Tx, []*Transaction, error) { btTx, err := bt.NewTxFromString(inputTx.Hex) diff --git a/beef_tx.go b/beef_tx.go index 93cbc2aa..a32abf5a 100644 --- a/beef_tx.go +++ b/beef_tx.go @@ -17,8 +17,11 @@ type beefTx struct { } func ToBeef(ctx context.Context, tx *Transaction) (string, error) { - inputs := tx.draftTransaction.Configuration.Inputs - bumpBtFactors, bumpFactors, err := prepareBUMPFactors(ctx, tx.client, inputs) + if err := hydrateTransaction(ctx, tx); err != nil { + return "", err + } + + bumpBtFactors, bumpFactors, err := prepareBUMPFactors(ctx, tx) if err != nil { return "", fmt.Errorf("prepareBUMPFactors() error: %w", err) } @@ -53,12 +56,7 @@ func newBeefTx(ctx context.Context, version uint32, tx *Transaction, parentTxs [ return nil, fmt.Errorf("version above 0x%X", maxBeefVer) } - var err error - if err = hydrateTransaction(ctx, tx); err != nil { - return nil, err - } - - if err = validateBumps(tx.draftTransaction.BUMPs); err != nil { + if err := validateBumps(tx.draftTransaction.BUMPs); err != nil { return nil, err } From a228c2df83475b5581d5bacd22ad885a36ac2cfe Mon Sep 17 00:00:00 2001 From: wregulski Date: Tue, 21 Nov 2023 14:02:53 +0100 Subject: [PATCH 04/12] feat: add tests for beef happy path --- beef_bump.go | 76 +++++++++--------- beef_fixtures.go | 6 ++ beef_tx.go | 7 +- beef_tx_mock.go | 29 +++++++ beef_tx_test.go | 177 ++++++++++++++++++++++++------------------ model_transactions.go | 4 + paymail.go | 2 +- 7 files changed, 179 insertions(+), 122 deletions(-) create mode 100644 beef_fixtures.go create mode 100644 beef_tx_mock.go diff --git a/beef_bump.go b/beef_bump.go index 3e7d4732..3d118894 100644 --- a/beef_bump.go +++ b/beef_bump.go @@ -14,7 +14,7 @@ func calculateMergedBUMP(txs []*Transaction) (BUMPs, error) { for _, tx := range txs { if tx.BUMP.BlockHeight == 0 || len(tx.BUMP.Path) == 0 { - return nil, fmt.Errorf("BUMP is not valid (tx.ID: %s)", tx.ID) + continue } bumps[tx.BlockHeight] = append(bumps[tx.BlockHeight], tx.BUMP) @@ -47,19 +47,16 @@ func validateBumps(bumps BUMPs) error { return nil } -// prepareBUMPFactors is a recursive function to find all parent transactions -// with a valid MerkleProof or BUMP. -func prepareBUMPFactors(ctx context.Context, tx *Transaction) ([]*bt.Tx, []*Transaction, error) { +func prepareBUMPFactors(ctx context.Context, tx *Transaction, store TransactionGetter) ([]*bt.Tx, []*Transaction, error) { btTxsNeededForBUMP, txsNeededForBUMP, err := initializeRequiredTxsCollection(tx) if err != nil { return nil, nil, err } for _, input := range tx.draftTransaction.Configuration.Inputs { - // TODO: Before finishing I will try to move to client.GetTransactions to reduce calls. Need to dig into the metadata construction. - inputTx, err := tx.client.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) + inputTx, err := store.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("cannot get transaction by ID (tx.ID: %s). Reason: %w", input.UtxoPointer.TransactionID, err) } inputBtTx, err := bt.NewTxFromString(inputTx.Hex) @@ -67,11 +64,11 @@ func prepareBUMPFactors(ctx context.Context, tx *Transaction) ([]*bt.Tx, []*Tran return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) } - if inputTx.MerkleProof.TxOrID != "" || inputTx.BUMP.BlockHeight != 0 || len(inputTx.BUMP.Path) != 0 { - txsNeededForBUMP = append(txsNeededForBUMP, inputTx) - btTxsNeededForBUMP = append(btTxsNeededForBUMP, inputBtTx) - } else { - parentBtTransactions, parentTransactions, err := checkParentTransactions(ctx, tx.client, inputTx) + txsNeededForBUMP = append(txsNeededForBUMP, inputTx) + btTxsNeededForBUMP = append(btTxsNeededForBUMP, inputBtTx) + + if inputTx.BUMP.BlockHeight == 0 && len(inputTx.BUMP.Path) == 0 { + parentBtTransactions, parentTransactions, err := checkParentTransactions(ctx, store, inputTx) if err != nil { return nil, nil, err } @@ -84,23 +81,7 @@ func prepareBUMPFactors(ctx context.Context, tx *Transaction) ([]*bt.Tx, []*Tran return btTxsNeededForBUMP, txsNeededForBUMP, nil } -func initializeRequiredTxsCollection(tx *Transaction) ([]*bt.Tx, []*Transaction, error) { - var btTxsNeededForBUMP []*bt.Tx - var txsNeededForBUMP []*Transaction - - processedBtTx, err := bt.NewTxFromString(tx.Hex) - if err != nil { - return nil, nil, fmt.Errorf("cannot convert processed tx to bt.Tx from hex (tx.ID: %s). Reason: %w", tx.ID, err) - } - - btTxsNeededForBUMP = append(btTxsNeededForBUMP, processedBtTx) - txsNeededForBUMP = append(txsNeededForBUMP, tx) - - return btTxsNeededForBUMP, txsNeededForBUMP, nil -} - -// checkParentTransactions is a helper recursive function to check the parent transactions. -func checkParentTransactions(ctx context.Context, client ClientInterface, inputTx *Transaction) ([]*bt.Tx, []*Transaction, error) { +func checkParentTransactions(ctx context.Context, store TransactionGetter, inputTx *Transaction) ([]*bt.Tx, []*Transaction, error) { btTx, err := bt.NewTxFromString(inputTx.Hex) if err != nil { return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) @@ -109,22 +90,20 @@ func checkParentTransactions(ctx context.Context, client ClientInterface, inputT var validTxs []*Transaction var validBtTxs []*bt.Tx for _, txIn := range btTx.Inputs { - parentTx, err := client.GetTransactionByID(ctx, txIn.PreviousTxIDStr()) + parentTx, err := store.GetTransactionByID(ctx, txIn.PreviousTxIDStr()) if err != nil { return nil, nil, err } - // If the parent transaction has a MerkleProof or a BUMP, add it to the list. - if parentTx.MerkleProof.TxOrID != "" || parentTx.BUMP.BlockHeight != 0 { - parentBtTx, err := bt.NewTxFromString(parentTx.Hex) - if err != nil { - return nil, nil, err - } - validTxs = append(validTxs, parentTx) - validBtTxs = append(validBtTxs, parentBtTx) - } else { - // Otherwise, recursively check the parents of this parent. - parentValidBtTxs, parentValidTxs, err := checkParentTransactions(ctx, client, parentTx) + parentBtTx, err := bt.NewTxFromString(parentTx.Hex) + if err != nil { + return nil, nil, err + } + validTxs = append(validTxs, parentTx) + validBtTxs = append(validBtTxs, parentBtTx) + + if parentTx.BUMP.BlockHeight == 0 && len(parentTx.BUMP.Path) == 0 { + parentValidBtTxs, parentValidTxs, err := checkParentTransactions(ctx, store, parentTx) if err != nil { return nil, nil, err } @@ -139,3 +118,18 @@ func checkParentTransactions(ctx context.Context, client ClientInterface, inputT return validBtTxs, validTxs, nil } + +func initializeRequiredTxsCollection(tx *Transaction) ([]*bt.Tx, []*Transaction, error) { + var btTxsNeededForBUMP []*bt.Tx + var txsNeededForBUMP []*Transaction + + processedBtTx, err := bt.NewTxFromString(tx.Hex) + if err != nil { + return nil, nil, fmt.Errorf("cannot convert processed tx to bt.Tx from hex (tx.ID: %s). Reason: %w", tx.ID, err) + } + + btTxsNeededForBUMP = append(btTxsNeededForBUMP, processedBtTx) + txsNeededForBUMP = append(txsNeededForBUMP, tx) + + return btTxsNeededForBUMP, txsNeededForBUMP, nil +} diff --git a/beef_fixtures.go b/beef_fixtures.go new file mode 100644 index 00000000..b2afb7ea --- /dev/null +++ b/beef_fixtures.go @@ -0,0 +1,6 @@ +package bux + +// Fixtures for beef_tx_test.go +var expectedBeefHex = map[int]string{ + 1: "0100beef02fe6b7c0c000f02fdda220062491611373407dc17e5573ecad5bdb531db9b9bb71a951b94b143f9ccb60e6cfddb220278d4414f46d4d09539e047dec4663461c33a7b850a9b06b9daac4b818bb4b35301fd6c1100c5d681ac9df54fe9d9c75188f7b419ba1920867a50fdff18dc96634887b0c5d601fdb70800aee19cb3ddbdf7d67dfd3bff9236acec0fd1189a63846f503770f6d6718fceec01fd5a0400d8585319e6e340836271778be4a3ff0526d9dccbaa9961ea2f43d0b89071ce0201fd2c02006aeee3cd84cddb4b73295952151c6e9def82cfb5765630b0df7513562396114c01fd17010040cb32f4a48211f33e034b5ce69ec1c7d251684d6caef24fe9d8421ab1e8a270018a00ad590026a37b5e54f00da3728076629da679b9791860db84dba2a8f69c8343a20144009080275ff7017b0773ed9a274c75d609367ceb32dcdd9e0aa318f861d7f8200c012300e298823da876f0532a877a68a2174e8c4e4cc3decd1d13ef69e2ecf3b10d98190110008078027ea7e86b451d4a1260af715a036da1000e85491cc11e8aa3d3e7d8f3ad01090046a147bde0b556e157e057422b7746133317dc2f06e074671a501ff40441753501050099877c6d79f9c9fc551854f274e0dd554b7903668674a9c23676d1eaa5158ae7010300acd1f494a49763626b3ff6711ed49a0a65a92ab8941ac3233469cf17d0e68916010000f6fec86accf0160a219f047e824fec177ebea343ec559196d431437a3ef7117801010045d10eb879d68142de3a7813f42d1d024395daf0f324b76a7acee98f2420fb67fee97b0c000f02fd78220057973546441901ea0e87fec236906ce6687530b705c4420a84bf2bcd2f2f4f9efd7922027a7123cf30268df225315c47b6ccbfd3e4f5c19f765bb9abc8fbeed134eb0b7f01fd3d11004493c9c27a50cd269436c6a653aef0a9a2a4e7ef7a11bfbb0056105fc9031cc101fd9f080077a51963071eb4d67fb0af5b9fade313f1089f792a75b480fef7a64de15db1d501fd4e04006fdd07e182b182bc03aceafaa2947197f0b0def806f147919d80726cd076778201fd2602003e4bc826ad114a9ed017eef519f54a5909c5b9cb4676bb2ed46e0c5daac6728a01fd12010020117d428183bd57155cfb57b2bc342b6134bb7a13466baea3df45555b0007b1018800f5e3eec3220abce6400b77b73c37d78205bda2e48114246ef8a34152b4ef65c00145004d7105c00153b22d8c6dd4098460b2ea853984326f6e440a909ca95307166bd2012300f633acb2385ad6089bee9fb7e1de0bb8a969341d2ed5fa4ce67a167bf512cbd8011000eaea22d749a8d99624e8240f3e35aaf685382d6f68b1e574b2b355d88db2282a0109009f7d7a3490c64399c46504d455dd8c435c71c5b7a4fb747ec91a32449e13001e0105009239929a42f2746ccb461fbf5c0cf91fd28fd7282a7befae7c7995cf96870cac0103000a430e0ec6db214a4d3ec8dd56d79986f32c8cb7be183b17be34bd081f2a41c50100007c2b529b1dd321d37a3e41dd11258682b93ddb1141609f997b38402dce45fe220101005c1e0d2299cf4c1655bdd3e433d5d179764311e9db173313f3e2399baa0ab7200301000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17034d800c2f71646c6e6b2f3285a22ac180341a80c10000ffffffff01c63b4125000000001976a9146bacd757abf099715c1251e7a3388eacb64781ca88ac0000000000010000000469dc4cc3fdb0d34b01e9f7dbae0af803395928f787e35604003f351f4f7f5252000000006b483045022100ec548fb2c6b2a8190bae147f2c41491213e159bef12853cde6d5729b75baf15f022050b8b7490885c6cd444a0c1c0e12d98e24cd2e2bbdba70489cee949af8d11b4c41210391507ea71726708c34141395e08c4a5ea8d6a3e5ab23943ea6faa36174fd4660ffffffffdf96c0a17051236f5d2154d30a0021ecdfa963dc47c0db86efffb90c30195871010000006b483045022100a71e76781a0734ea4d9909cb0e15e7ac63c1a269dcb03b7f0679c784e25aa831022006508da0c72a48a42d67654d4bc002f1fda530cbc9da98d745e94a163ee4c4a3412103f8c774b6331950021b3ecb445b4a5fe046e78def008061963a1b0aa9901e652fffffffff3ef7bba32922a00f3c4893560303c728cf3bbc68ffb9de43ae6b9a040cf039a8000000006a47304402207e7a4aed790fa0f9d25776aeca3aa9eed6796107da41fd15950273589aea838f0220030c3aaf19d34425bc5cb44a488a7ad57fa817a8b8be378b6a28efd40d25913c4121032e2f6e29622309c4dd4daecc16fe6aaa37b848657f2b0a88b9b589525bb27babffffffffe3b23939f7a29999c106a154e8ac555229aa9a039d9a10cb58ec299e2371af99000000006a47304402201b521759f7f42f69915005bf23dd32596026202f6da51418d4a90964e6b924ad02207ac2e3cf7e81b0f5a86ca42d40bcb77a725afc5f694061915e3801b074ff89b5412102657168443ef2b22d747a98131fe686fc46ac0119527b33cbeaafa79670acad92ffffffff0234080000000000001976a9141c052e355d972d64db87b7a207ada864ff1dc05d88ac2e000000000000001976a914cce55c2204a23c80fcdecc6dfb4ddc06bc8f042488ac0000000001010100000005d2e6252f996ab1a6fcbe8911e8f865bb719c0e11787397fd818b5bb1ff554a3c000000006a47304402206bfb91eb220f1fccae581abf080ad559bd2a14c7a2616f7e20dcfebb6403c1fa022013038e4d8748b8493e325c7d86cc3b6544433a2a0bb9ed2b1922cc8e78f36c1541210277e25e8e4ab96e46d94a037411d195b537810b1d7301c2d72e9eb40bb47aae34ffffffff827f1758c64b4a0b1226c54316ddeed4618500f026602ebca3cd4b96174a690a000000006a47304402207b6086ca2547a8bf5ec57eb665b085040758a4dc28f560bb6b2ea3c6934df4310220344eadd086a40fe1007c246a3135e907382080dc58c06b9be30abff476e25662412103138a3aac623a5fc9789cd9476efad0605dea61f3e7cd5089195eb0b446b6ab4effffffff0dacf934645c462a155ca35453ab578e7d510687fda689a565e000fc4df11cd5000000006a47304402201f11732e7ed47381ea09af3004e9e1f69a1281cc530f3825fd320fc9e331ff6802200f61b9274f980df180c89f32ff6e30d4676d78000012092d44158cb2a3e8aff9412103ba49c495254796f5ccf0e28f205f62965fafc33367b2b8d6609e5de30c206ad4ffffffff213e4fca3103f812ffcba253caf452c6811947ff6f2fb99b4e18baa1233e84b6010000006b483045022100d676805d8077746d58d79f0199a6f2a0fcf8cc772b149cf77f60ed9b6dd6a60902200c4d805e84f2e0a50d73acffefb3d712a6891f78e9eafddd8bcba872032a88464121035d1d732dbe247c0886753c84dc3d2fc96a9eac26662e8664fe9ce8f67ab6dd98ffffffffe5232220aee5069017d31cc30818bcc971de3e6418f6e62b8cc9a3430d64f3e9010000006b483045022100ed3d4fda64717c43a5fd7329e333d249204e1a25c064bd6bd6b9c945b747befc02205458f90b37aea82c011d4d96c26b808af4b9c19bc07ada196a7a82ac7a3b7eb441210343caa07997898400cefe7a28445b233d30463d13359c1d87ac42ea5da61432a0ffffffff01ce070000000000001976a9141b6b173a4880836d9641b9218ab119a11918684588ac000000000100", +} diff --git a/beef_tx.go b/beef_tx.go index a32abf5a..4e1ef33a 100644 --- a/beef_tx.go +++ b/beef_tx.go @@ -16,12 +16,13 @@ type beefTx struct { transactions []*bt.Tx } -func ToBeef(ctx context.Context, tx *Transaction) (string, error) { +// ToBeef generates BEEF Hex for transaction +func ToBeef(ctx context.Context, tx *Transaction, store TransactionGetter) (string, error) { if err := hydrateTransaction(ctx, tx); err != nil { return "", err } - bumpBtFactors, bumpFactors, err := prepareBUMPFactors(ctx, tx) + bumpBtFactors, bumpFactors, err := prepareBUMPFactors(ctx, tx, store) if err != nil { return "", fmt.Errorf("prepareBUMPFactors() error: %w", err) } @@ -29,6 +30,7 @@ func ToBeef(ctx context.Context, tx *Transaction) (string, error) { tx.draftTransaction.BUMPs, err = calculateMergedBUMP(bumpFactors) sortedTxs := kahnTopologicalSortTransactions(bumpBtFactors) beefHex, err := toBeefHex(ctx, tx, sortedTxs) + fmt.Printf("beefHex: %s\n", beefHex) if err != nil { return "", fmt.Errorf("ToBeef() error: %w", err) } @@ -36,7 +38,6 @@ func ToBeef(ctx context.Context, tx *Transaction) (string, error) { return beefHex, nil } -// toBeefHex generates BEEF Hex for transaction func toBeefHex(ctx context.Context, tx *Transaction, parentTxs []*bt.Tx) (string, error) { beef, err := newBeefTx(ctx, 1, tx, parentTxs) if err != nil { diff --git a/beef_tx_mock.go b/beef_tx_mock.go new file mode 100644 index 00000000..fb1381b2 --- /dev/null +++ b/beef_tx_mock.go @@ -0,0 +1,29 @@ +package bux + +import ( + "context" + "fmt" +) + +type MockTransactionGetter struct { + Transactions map[string]*Transaction +} + +func NewMockTransactionGetter() *MockTransactionGetter { + return &MockTransactionGetter{ + Transactions: make(map[string]*Transaction), + } +} + +func (m *MockTransactionGetter) Init(transactions []*Transaction) { + for _, tx := range transactions { + m.Transactions[tx.ID] = tx + } +} + +func (m *MockTransactionGetter) GetTransactionByID(ctx context.Context, txID string) (*Transaction, error) { + if tx, exists := m.Transactions[txID]; exists { + return tx, nil + } + return nil, fmt.Errorf("no records found") +} diff --git a/beef_tx_test.go b/beef_tx_test.go index 6ecc337a..b5c8e32b 100644 --- a/beef_tx_test.go +++ b/beef_tx_test.go @@ -2,83 +2,94 @@ package bux import ( "context" + "encoding/json" "testing" - "github.com/libsv/go-bc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +const hexForProcessedTx = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17034d800c2f71646c6e6b2f3285a22ac180341a80c10000ffffffff01c63b4125000000001976a9146bacd757abf099715c1251e7a3388eacb64781ca88ac00000000" + +type beefTestCase struct { + testID int + name string + outputValue uint64 + receiverAddress string + ancestors []*beefTestCaseAncestor + expectedError bool + expectedErrorMessage string +} + +type beefTestCaseAncestor struct { + hex string + isMined bool + bumpJSON string + blockHeight int +} + // TODO: BUX-168 - fix this test -// func Test_ToBeefHex(t *testing.T) { -// t.Run("all parents txs are already mined", func(t *testing.T) { -// // given -// ctx, client, deferMe := initSimpleTestCase(t) -// defer deferMe() - -// ancestorTx := addGrandpaTx(ctx, t, client) -// minedParentTx := createTxWithDraft(ctx, t, client, ancestorTx, true) - -// newTx := createTxWithDraft(ctx, t, client, minedParentTx, false) - -// // when -// hex, err := ToBeefHex(ctx, newTx) - -// // then -// assert.NoError(t, err) -// assert.NotEmpty(t, hex) -// }) - -// t.Run("some parents txs are not mined yet", func(t *testing.T) { -// // Error expected! this should be changed in the future. right now the test case has been written to make sure the system doesn't panic in such a situation - -// // given -// ctx, client, deferMe := initSimpleTestCase(t) -// defer deferMe() - -// ancestorTx := addGrandpaTx(ctx, t, client) -// notMinedParentTx := createTxWithDraft(ctx, t, client, ancestorTx, false) - -// newTx := createTxWithDraft(ctx, t, client, notMinedParentTx, false) - -// // when -// hex, err := ToBeefHex(ctx, newTx) - -// // then -// assert.Error(t, err) -// assert.Empty(t, hex) -// }) -// } - -func addGrandpaTx(ctx context.Context, t *testing.T, client ClientInterface) *Transaction { - // great ancestor - grandpaTx := newTransaction(testTx2Hex, append(client.DefaultModelOptions(), New())...) - grandpaTx.BlockHeight = 1 - // mark it as mined - grandpaTxMp := bc.MerkleProof{ - TxOrID: "cefffc5415620292081f7e941bb74d11a3188144312c4d7550c462b2a151c64d", - Nodes: []string{ - "6cf512411d03ab9b61643515e7aa9afd005bf29e1052ade95410b3475f02820c", - "cd73c0c6bb645581816fa960fd2f1636062fcbf23cb57981074ab8d708a76e3b", - "b4c8d919190a090e77b73ffcd52b85babaaeeb62da000473102aca7f070facef", - "3470d882cf556a4b943639eba15dc795dffdbebdc98b9a98e3637fda96e3811e", +func Test_ToBeef(t *testing.T) { + testCases := []beefTestCase{ + { + testID: 1, + name: "all parents txs are already mined", + ancestors: []*beefTestCaseAncestor{ + { + hex: "0100000005d2e6252f996ab1a6fcbe8911e8f865bb719c0e11787397fd818b5bb1ff554a3c000000006a47304402206bfb91eb220f1fccae581abf080ad559bd2a14c7a2616f7e20dcfebb6403c1fa022013038e4d8748b8493e325c7d86cc3b6544433a2a0bb9ed2b1922cc8e78f36c1541210277e25e8e4ab96e46d94a037411d195b537810b1d7301c2d72e9eb40bb47aae34ffffffff827f1758c64b4a0b1226c54316ddeed4618500f026602ebca3cd4b96174a690a000000006a47304402207b6086ca2547a8bf5ec57eb665b085040758a4dc28f560bb6b2ea3c6934df4310220344eadd086a40fe1007c246a3135e907382080dc58c06b9be30abff476e25662412103138a3aac623a5fc9789cd9476efad0605dea61f3e7cd5089195eb0b446b6ab4effffffff0dacf934645c462a155ca35453ab578e7d510687fda689a565e000fc4df11cd5000000006a47304402201f11732e7ed47381ea09af3004e9e1f69a1281cc530f3825fd320fc9e331ff6802200f61b9274f980df180c89f32ff6e30d4676d78000012092d44158cb2a3e8aff9412103ba49c495254796f5ccf0e28f205f62965fafc33367b2b8d6609e5de30c206ad4ffffffff213e4fca3103f812ffcba253caf452c6811947ff6f2fb99b4e18baa1233e84b6010000006b483045022100d676805d8077746d58d79f0199a6f2a0fcf8cc772b149cf77f60ed9b6dd6a60902200c4d805e84f2e0a50d73acffefb3d712a6891f78e9eafddd8bcba872032a88464121035d1d732dbe247c0886753c84dc3d2fc96a9eac26662e8664fe9ce8f67ab6dd98ffffffffe5232220aee5069017d31cc30818bcc971de3e6418f6e62b8cc9a3430d64f3e9010000006b483045022100ed3d4fda64717c43a5fd7329e333d249204e1a25c064bd6bd6b9c945b747befc02205458f90b37aea82c011d4d96c26b808af4b9c19bc07ada196a7a82ac7a3b7eb441210343caa07997898400cefe7a28445b233d30463d13359c1d87ac42ea5da61432a0ffffffff01ce070000000000001976a9141b6b173a4880836d9641b9218ab119a11918684588ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"818283","path":[[{"offset":"8922","hash":"6c0eb6ccf943b1941b951ab79b9bdb31b5bdd5ca3e57e517dc07343711164962"},{"offset":"8923","hash":"53b3b48b814bacdab9069b0a857b3ac3613466c4de47e03995d0d4464f41d478","txid":true}],[{"offset":"4460","hash":"d6c5b087486396dc18fffd507a862019ba19b4f78851c7d9e94ff59dac81d6c5"}],[{"offset":"2231","hash":"ecce8f71d6f67037506f84639a18d10fecac3692ff3bfd7dd6f7bdddb39ce1ae"}],[{"offset":"1114","hash":"02ce7190b8d0432fea6199aacbdcd92605ffa3e48b7771628340e3e6195358d8"}],[{"offset":"556","hash":"4c119623561375dfb0305676b5cf82ef9d6e1c15525929734bdbcd84cde3ee6a"}],[{"offset":"279","hash":"70a2e8b11a42d8e94ff2ae6c4d6851d2c7c19ee65c4b033ef31182a4f432cb40"}],[{"offset":"138","hash":"a243839cf6a8a2db84db601879b979a69d62768072a30df0545e7ba3260059ad"}],[{"offset":"68","hash":"0c20f8d761f818a30a9edddc32eb7c3609d6754c279aed73077b01f75f278090"}],[{"offset":"35","hash":"19980db1f3ece269ef131dcddec34c4e8c4e17a2687a872a53f076a83d8298e2"}],[{"offset":"16","hash":"adf3d8e7d3a38a1ec11c49850e00a16d035a71af60124a1d456be8a77e027880"}],[{"offset":"9","hash":"35754104f41f501a6774e0062fdc17331346772b4257e057e156b5e0bd47a146"}],[{"offset":"5","hash":"e78a15a5ead17636c2a974866603794b55dde074f2541855fcc9f9796d7c8799"}],[{"offset":"3","hash":"1689e6d017cf693423c31a94b82aa9650a9ad41e71f63f6b626397a494f4d1ac"}],[{"offset":"0","hash":"7811f73e7a4331d4969155ec43a3be7e17ec4f827e049f210a16f0cc6ac8fef6"}],[{"offset":"1","hash":"67fb20248fe9ce7a6ab724f3f0da9543021d2df413783ade4281d679b80ed145"}]]}`, + blockHeight: 818283, + }, + { + hex: "010000000469dc4cc3fdb0d34b01e9f7dbae0af803395928f787e35604003f351f4f7f5252000000006b483045022100ec548fb2c6b2a8190bae147f2c41491213e159bef12853cde6d5729b75baf15f022050b8b7490885c6cd444a0c1c0e12d98e24cd2e2bbdba70489cee949af8d11b4c41210391507ea71726708c34141395e08c4a5ea8d6a3e5ab23943ea6faa36174fd4660ffffffffdf96c0a17051236f5d2154d30a0021ecdfa963dc47c0db86efffb90c30195871010000006b483045022100a71e76781a0734ea4d9909cb0e15e7ac63c1a269dcb03b7f0679c784e25aa831022006508da0c72a48a42d67654d4bc002f1fda530cbc9da98d745e94a163ee4c4a3412103f8c774b6331950021b3ecb445b4a5fe046e78def008061963a1b0aa9901e652fffffffff3ef7bba32922a00f3c4893560303c728cf3bbc68ffb9de43ae6b9a040cf039a8000000006a47304402207e7a4aed790fa0f9d25776aeca3aa9eed6796107da41fd15950273589aea838f0220030c3aaf19d34425bc5cb44a488a7ad57fa817a8b8be378b6a28efd40d25913c4121032e2f6e29622309c4dd4daecc16fe6aaa37b848657f2b0a88b9b589525bb27babffffffffe3b23939f7a29999c106a154e8ac555229aa9a039d9a10cb58ec299e2371af99000000006a47304402201b521759f7f42f69915005bf23dd32596026202f6da51418d4a90964e6b924ad02207ac2e3cf7e81b0f5a86ca42d40bcb77a725afc5f694061915e3801b074ff89b5412102657168443ef2b22d747a98131fe686fc46ac0119527b33cbeaafa79670acad92ffffffff0234080000000000001976a9141c052e355d972d64db87b7a207ada864ff1dc05d88ac2e000000000000001976a914cce55c2204a23c80fcdecc6dfb4ddc06bc8f042488ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"818153","path":[[{"offset":"8824","hash":"9e4f2f2fcd2bbf840a42c405b7307568e66c9036c2fe870eea01194446359757"},{"offset":"8825","hash":"7f0beb34d1eefbc8abb95b769fc1f5e4d3bfccb6475c3125f28d2630cf23717a","txid":true}],[{"offset":"4413","hash":"c11c03c95f105600bbbf117aefe7a4a2a9f0ae53a6c6369426cd507ac2c99344"}],[{"offset":"2207","hash":"d5b15de14da6f7fe80b4752a799f08f113e3ad9f5bafb07fd6b41e076319a577"}],[{"offset":"1102","hash":"827776d06c72809d9147f106f8deb0f0977194a2faeaac03bc82b182e107dd6f"}],[{"offset":"550","hash":"8a72c6aa5d0c6ed42ebb7646cbb9c509594af519f5ee17d09e4a11ad26c84b3e"}],[{"offset":"274","hash":"b107005b5545dfa3ae6b46137abb34612b34bcb257fb5c1557bd8381427d1120"}],[{"offset":"136","hash":"c065efb45241a3f86e241481e4a2bd0582d7373cb7770b40e6bc0a22c3eee3f5"}],[{"offset":"69","hash":"d26b160753a99c900a446e6f32843985eab2608409d46d8c2db25301c005714d"}],[{"offset":"35","hash":"d8cb12f57b167ae64cfad52e1d3469a9b80bdee1b79fee9b08d65a38b2ac33f6"}],[{"offset":"16","hash":"2a28b28dd855b3b274e5b1686f2d3885f6aa353e0f24e82496d9a849d722eaea"}],[{"offset":"9","hash":"1e00139e44321ac97e74fba4b7c5715c438cdd55d40465c49943c690347a7d9f"}],[{"offset":"5","hash":"ac0c8796cf95797caeef7b2a28d78fd21ff90c5cbf1f46cb6c74f2429a923992"}],[{"offset":"3","hash":"c5412a1f08bd34be173b18beb78c2cf38699d756ddc83e4d4a21dbc60e0e430a"}],[{"offset":"0","hash":"22fe45ce2d40387b999f604111db3db982862511dd413e7ad321d31d9b522b7c"}],[{"offset":"1","hash":"20b70aaa9b39e2f3133317dbe911437679d1d533e4d3bd55164ccf99220d1e5c"}]]}`, + blockHeight: 818153, + }, + }, + expectedError: false, + receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", + outputValue: 3000, + expectedErrorMessage: "", }, } - grandpaTx.MerkleProof = MerkleProof(grandpaTxMp) - grandpaTx.BUMP = grandpaTx.MerkleProof.ToBUMP(grandpaTx.BlockHeight) - err := grandpaTx.Save(ctx) - require.NoError(t, err) - return grandpaTx + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // given + ctx, client, deferMe := initSimpleTestCase(t) + defer deferMe() + + var ancestors []*Transaction + for _, ancestor := range tc.ancestors { + ancestors = append(ancestors, addAncestor(ctx, ancestor, client, t)) + } + newTx := createProcessedTx(ctx, t, client, &tc, ancestors) + + mockGetter := NewMockTransactionGetter() + mockGetter.Init(ancestors) + + // when + result, err := ToBeef(ctx, newTx, mockGetter) + + // then + if tc.expectedError { + assert.Equal(t, tc.expectedErrorMessage, err.Error()) + } + + assert.Equal(t, expectedBeefHex[tc.testID], result) + }) + } } -func createTxWithDraft(ctx context.Context, t *testing.T, client ClientInterface, parentTx *Transaction, mined bool) *Transaction { - draftTransaction := newDraftTransaction( +func createProcessedTx(ctx context.Context, t *testing.T, client ClientInterface, testCase *beefTestCase, ancestors []*Transaction) *Transaction { + draftTx := newDraftTransaction( testXPub, &TransactionConfig{ - Inputs: []*TransactionInput{{Utxo: *newUtxoFromTxID(parentTx.GetID(), 0, append(client.DefaultModelOptions(), New())...)}}, + Inputs: createInputsUsingAncestors(ancestors, client), Outputs: []*TransactionOutput{{ - To: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", - Satoshis: 1000, + To: testCase.receiverAddress, + Satoshis: testCase.outputValue, }}, ChangeNumberOfDestinations: 1, Sync: &SyncConfig{ @@ -91,24 +102,36 @@ func createTxWithDraft(ctx context.Context, t *testing.T, client ClientInterface append(client.DefaultModelOptions(), New())..., ) - err := draftTransaction.Save(ctx) - require.NoError(t, err) + transaction := newTransaction(hexForProcessedTx, append(client.DefaultModelOptions(), New())...) + transaction.draftTransaction = draftTx + transaction.DraftID = draftTx.ID - var transaction *Transaction - transaction, err = client.RecordTransaction(ctx, testXPub, draftTransaction.Hex, draftTransaction.ID, client.DefaultModelOptions()...) - require.NoError(t, err) assert.NotEmpty(t, transaction) - if mined { - transaction.BlockHeight = 128 - mp := bc.MerkleProof{ - TxOrID: "423542156234627frafserg6gtrdsbd", Nodes: []string{"n1", "n2"}, - } - transaction.MerkleProof = MerkleProof(mp) + return transaction +} + +func addAncestor(ctx context.Context, testCase *beefTestCaseAncestor, client ClientInterface, t *testing.T) *Transaction { + grandpaTx := newTransaction(testCase.hex, append(client.DefaultModelOptions(), New())...) + + if testCase.isMined { + grandpaTx.BlockHeight = uint64(testCase.blockHeight) + + var bump BUMP + err := json.Unmarshal([]byte(testCase.bumpJSON), &bump) + require.NoError(t, err) + grandpaTx.BUMP = bump } - err = transaction.Save(ctx) - require.NoError(t, err) + return grandpaTx +} - return transaction +func createInputsUsingAncestors(ancestors []*Transaction, client ClientInterface) []*TransactionInput { + var inputs []*TransactionInput + + for _, input := range ancestors { + inputs = append(inputs, &TransactionInput{Utxo: *newUtxoFromTxID(input.GetID(), 0, append(client.DefaultModelOptions(), New())...)}) + } + + return inputs } diff --git a/model_transactions.go b/model_transactions.go index 0e149c57..2a330112 100644 --- a/model_transactions.go +++ b/model_transactions.go @@ -72,6 +72,10 @@ type Transaction struct { beforeCreateCalled bool `gorm:"-" bson:"-"` // Private information that the transaction lifecycle method BeforeCreate was already called } +type TransactionGetter interface { + GetTransactionByID(ctx context.Context, txID string) (*Transaction, error) +} + // newTransactionBase creates the standard transaction model base func newTransactionBase(hex string, opts ...ModelOps) *Transaction { return &Transaction{ diff --git a/paymail.go b/paymail.go index 502a9a11..8a0e2092 100644 --- a/paymail.go +++ b/paymail.go @@ -191,7 +191,7 @@ func buildP2pTx(ctx context.Context, p4 *PaymailP4, transaction *Transaction) (* switch p4.Format { case BeefPaymailPayloadFormat: - beef, err := ToBeef(ctx, transaction) + beef, err := ToBeef(ctx, transaction, transaction.client) if err != nil { return nil, err } From 278896a613045bc0d4b9a274ae92b3cac4466eca Mon Sep 17 00:00:00 2001 From: wregulski Date: Tue, 21 Nov 2023 14:35:19 +0100 Subject: [PATCH 05/12] feat: add proper test case with topologically sorted txs --- beef_fixtures.go | 2 +- beef_tx_test.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/beef_fixtures.go b/beef_fixtures.go index b2afb7ea..d0dad82a 100644 --- a/beef_fixtures.go +++ b/beef_fixtures.go @@ -2,5 +2,5 @@ package bux // Fixtures for beef_tx_test.go var expectedBeefHex = map[int]string{ - 1: "0100beef02fe6b7c0c000f02fdda220062491611373407dc17e5573ecad5bdb531db9b9bb71a951b94b143f9ccb60e6cfddb220278d4414f46d4d09539e047dec4663461c33a7b850a9b06b9daac4b818bb4b35301fd6c1100c5d681ac9df54fe9d9c75188f7b419ba1920867a50fdff18dc96634887b0c5d601fdb70800aee19cb3ddbdf7d67dfd3bff9236acec0fd1189a63846f503770f6d6718fceec01fd5a0400d8585319e6e340836271778be4a3ff0526d9dccbaa9961ea2f43d0b89071ce0201fd2c02006aeee3cd84cddb4b73295952151c6e9def82cfb5765630b0df7513562396114c01fd17010040cb32f4a48211f33e034b5ce69ec1c7d251684d6caef24fe9d8421ab1e8a270018a00ad590026a37b5e54f00da3728076629da679b9791860db84dba2a8f69c8343a20144009080275ff7017b0773ed9a274c75d609367ceb32dcdd9e0aa318f861d7f8200c012300e298823da876f0532a877a68a2174e8c4e4cc3decd1d13ef69e2ecf3b10d98190110008078027ea7e86b451d4a1260af715a036da1000e85491cc11e8aa3d3e7d8f3ad01090046a147bde0b556e157e057422b7746133317dc2f06e074671a501ff40441753501050099877c6d79f9c9fc551854f274e0dd554b7903668674a9c23676d1eaa5158ae7010300acd1f494a49763626b3ff6711ed49a0a65a92ab8941ac3233469cf17d0e68916010000f6fec86accf0160a219f047e824fec177ebea343ec559196d431437a3ef7117801010045d10eb879d68142de3a7813f42d1d024395daf0f324b76a7acee98f2420fb67fee97b0c000f02fd78220057973546441901ea0e87fec236906ce6687530b705c4420a84bf2bcd2f2f4f9efd7922027a7123cf30268df225315c47b6ccbfd3e4f5c19f765bb9abc8fbeed134eb0b7f01fd3d11004493c9c27a50cd269436c6a653aef0a9a2a4e7ef7a11bfbb0056105fc9031cc101fd9f080077a51963071eb4d67fb0af5b9fade313f1089f792a75b480fef7a64de15db1d501fd4e04006fdd07e182b182bc03aceafaa2947197f0b0def806f147919d80726cd076778201fd2602003e4bc826ad114a9ed017eef519f54a5909c5b9cb4676bb2ed46e0c5daac6728a01fd12010020117d428183bd57155cfb57b2bc342b6134bb7a13466baea3df45555b0007b1018800f5e3eec3220abce6400b77b73c37d78205bda2e48114246ef8a34152b4ef65c00145004d7105c00153b22d8c6dd4098460b2ea853984326f6e440a909ca95307166bd2012300f633acb2385ad6089bee9fb7e1de0bb8a969341d2ed5fa4ce67a167bf512cbd8011000eaea22d749a8d99624e8240f3e35aaf685382d6f68b1e574b2b355d88db2282a0109009f7d7a3490c64399c46504d455dd8c435c71c5b7a4fb747ec91a32449e13001e0105009239929a42f2746ccb461fbf5c0cf91fd28fd7282a7befae7c7995cf96870cac0103000a430e0ec6db214a4d3ec8dd56d79986f32c8cb7be183b17be34bd081f2a41c50100007c2b529b1dd321d37a3e41dd11258682b93ddb1141609f997b38402dce45fe220101005c1e0d2299cf4c1655bdd3e433d5d179764311e9db173313f3e2399baa0ab7200301000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17034d800c2f71646c6e6b2f3285a22ac180341a80c10000ffffffff01c63b4125000000001976a9146bacd757abf099715c1251e7a3388eacb64781ca88ac0000000000010000000469dc4cc3fdb0d34b01e9f7dbae0af803395928f787e35604003f351f4f7f5252000000006b483045022100ec548fb2c6b2a8190bae147f2c41491213e159bef12853cde6d5729b75baf15f022050b8b7490885c6cd444a0c1c0e12d98e24cd2e2bbdba70489cee949af8d11b4c41210391507ea71726708c34141395e08c4a5ea8d6a3e5ab23943ea6faa36174fd4660ffffffffdf96c0a17051236f5d2154d30a0021ecdfa963dc47c0db86efffb90c30195871010000006b483045022100a71e76781a0734ea4d9909cb0e15e7ac63c1a269dcb03b7f0679c784e25aa831022006508da0c72a48a42d67654d4bc002f1fda530cbc9da98d745e94a163ee4c4a3412103f8c774b6331950021b3ecb445b4a5fe046e78def008061963a1b0aa9901e652fffffffff3ef7bba32922a00f3c4893560303c728cf3bbc68ffb9de43ae6b9a040cf039a8000000006a47304402207e7a4aed790fa0f9d25776aeca3aa9eed6796107da41fd15950273589aea838f0220030c3aaf19d34425bc5cb44a488a7ad57fa817a8b8be378b6a28efd40d25913c4121032e2f6e29622309c4dd4daecc16fe6aaa37b848657f2b0a88b9b589525bb27babffffffffe3b23939f7a29999c106a154e8ac555229aa9a039d9a10cb58ec299e2371af99000000006a47304402201b521759f7f42f69915005bf23dd32596026202f6da51418d4a90964e6b924ad02207ac2e3cf7e81b0f5a86ca42d40bcb77a725afc5f694061915e3801b074ff89b5412102657168443ef2b22d747a98131fe686fc46ac0119527b33cbeaafa79670acad92ffffffff0234080000000000001976a9141c052e355d972d64db87b7a207ada864ff1dc05d88ac2e000000000000001976a914cce55c2204a23c80fcdecc6dfb4ddc06bc8f042488ac0000000001010100000005d2e6252f996ab1a6fcbe8911e8f865bb719c0e11787397fd818b5bb1ff554a3c000000006a47304402206bfb91eb220f1fccae581abf080ad559bd2a14c7a2616f7e20dcfebb6403c1fa022013038e4d8748b8493e325c7d86cc3b6544433a2a0bb9ed2b1922cc8e78f36c1541210277e25e8e4ab96e46d94a037411d195b537810b1d7301c2d72e9eb40bb47aae34ffffffff827f1758c64b4a0b1226c54316ddeed4618500f026602ebca3cd4b96174a690a000000006a47304402207b6086ca2547a8bf5ec57eb665b085040758a4dc28f560bb6b2ea3c6934df4310220344eadd086a40fe1007c246a3135e907382080dc58c06b9be30abff476e25662412103138a3aac623a5fc9789cd9476efad0605dea61f3e7cd5089195eb0b446b6ab4effffffff0dacf934645c462a155ca35453ab578e7d510687fda689a565e000fc4df11cd5000000006a47304402201f11732e7ed47381ea09af3004e9e1f69a1281cc530f3825fd320fc9e331ff6802200f61b9274f980df180c89f32ff6e30d4676d78000012092d44158cb2a3e8aff9412103ba49c495254796f5ccf0e28f205f62965fafc33367b2b8d6609e5de30c206ad4ffffffff213e4fca3103f812ffcba253caf452c6811947ff6f2fb99b4e18baa1233e84b6010000006b483045022100d676805d8077746d58d79f0199a6f2a0fcf8cc772b149cf77f60ed9b6dd6a60902200c4d805e84f2e0a50d73acffefb3d712a6891f78e9eafddd8bcba872032a88464121035d1d732dbe247c0886753c84dc3d2fc96a9eac26662e8664fe9ce8f67ab6dd98ffffffffe5232220aee5069017d31cc30818bcc971de3e6418f6e62b8cc9a3430d64f3e9010000006b483045022100ed3d4fda64717c43a5fd7329e333d249204e1a25c064bd6bd6b9c945b747befc02205458f90b37aea82c011d4d96c26b808af4b9c19bc07ada196a7a82ac7a3b7eb441210343caa07997898400cefe7a28445b233d30463d13359c1d87ac42ea5da61432a0ffffffff01ce070000000000001976a9141b6b173a4880836d9641b9218ab119a11918684588ac000000000100", + 1: "0100beef02fea6790c000f02fda82c000703145efd05fec7c2edc1827ec685755a2d05208486e9bc2461268e0f5e533bfda92c02cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e301fd551600b7b53a09331453b5966589dc473b45c87823f109417593a82ff8ffe7ddc6f96e01fd2b0b0080085c6f18f35d5f0a231eda36c1be3f031734bb6cb6987978ef2aad007ea6da01fd940500d26d24b44097ed3f0c2927413dd2f1fb78bba948803abdb7f2fb51d9807a77bc01fdcb0200bad82dfd55455709713ea8390a7c76be5c076da9cd487b75f558728ef8572b7601fd64010022553e159788764f6b3c1e27324f99a13abee9c7069ce34b8a4fcabc45b7aabb01b300debfa516f54f4331ddf74067403d8a70915973e8300c298ff8dbe5bdcc94768101580079a933dbaeaee5dfb6af1ce9bd7a9ef40584e1844a938e398d19f94aba525bed012d005287b1c986e6495d00c103082618d9e0a30c0c620fd2d232eb9c31d59dec510e01170074639f74ebdaaac679e8fed504ae62a6f42c633a549a99e21940aff14e69ab08010a008eb3fde752d9e5c8e67c26daa95f8b9480ec11e608de7afae04eb19775f42342010400eea8e24927e93a0cf9ca50b315e48efca1f0b77643f7110c6efd94220dcedab6010300682c5f78d5f89e5e5d13619f543aacb00d1e4e85043f4d453bd6f6eb14755e790100006df75ca701801279bcb9b91579ac74b2131913ba35a0c3cb313c49062c8f453f0101009fe83bf3febfc2af4641b9bc8c1f76ff9b2ae7de9f4ab5374e672ac70cf8b9b9fec27f0c000b02fd8802000e40280d05fedfd66af3edb59a94f0512e804093f855c025b9d964ca23ca1b12fd890202624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff00401fd45010090f9751ef8c4daa8a15d0cc75d1a89e7fad8b8ba258f36da56fbb8faef725b1e01a3002863542b3fe0a1a8fdd6711f7b9af6d9fa2986bf6c67460e5e7fc544a3fa7ec90150003a0b1e497aae08b126ec790f9214517e32cff1a57cf3abe92e9580f634f369500129006d6f372a6b54acb13e4c0ce2b6ff53120685ac23bf8e1953f8358b87ebf90ca4011500c310dbdea5f87b557964e09c67926b6d756c9ea821948e36298544bf761bf9b5010b00a267e36fe2eef4ce3582e359d6b0b93df7d76afd00e19d4abff77d7cf2acf6db01040080c16fd797c5b4f8463e5bffc6ffacd7dae409c9908b4748ff28dabd8bbe8fe0010300b7e09ca039e8b8052f179d5f2c3d1d6e98bb983470784d62f03438621effe80a010000cf5b6da719ca8b752c50b5b9b4d7659cc5aaed7fe429d96d2270617414fb551f010100dc01860ec79aac9c4465b6afb0b0641bbf0b1c8a1c23bf7b920a1b662366ab40030100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac0000000001010100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac0000000001000100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac0000000000", } diff --git a/beef_tx_test.go b/beef_tx_test.go index b5c8e32b..e8e1dc34 100644 --- a/beef_tx_test.go +++ b/beef_tx_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -const hexForProcessedTx = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17034d800c2f71646c6e6b2f3285a22ac180341a80c10000ffffffff01c63b4125000000001976a9146bacd757abf099715c1251e7a3388eacb64781ca88ac00000000" +const hexForProcessedTx = "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000" type beefTestCase struct { testID int @@ -36,16 +36,16 @@ func Test_ToBeef(t *testing.T) { name: "all parents txs are already mined", ancestors: []*beefTestCaseAncestor{ { - hex: "0100000005d2e6252f996ab1a6fcbe8911e8f865bb719c0e11787397fd818b5bb1ff554a3c000000006a47304402206bfb91eb220f1fccae581abf080ad559bd2a14c7a2616f7e20dcfebb6403c1fa022013038e4d8748b8493e325c7d86cc3b6544433a2a0bb9ed2b1922cc8e78f36c1541210277e25e8e4ab96e46d94a037411d195b537810b1d7301c2d72e9eb40bb47aae34ffffffff827f1758c64b4a0b1226c54316ddeed4618500f026602ebca3cd4b96174a690a000000006a47304402207b6086ca2547a8bf5ec57eb665b085040758a4dc28f560bb6b2ea3c6934df4310220344eadd086a40fe1007c246a3135e907382080dc58c06b9be30abff476e25662412103138a3aac623a5fc9789cd9476efad0605dea61f3e7cd5089195eb0b446b6ab4effffffff0dacf934645c462a155ca35453ab578e7d510687fda689a565e000fc4df11cd5000000006a47304402201f11732e7ed47381ea09af3004e9e1f69a1281cc530f3825fd320fc9e331ff6802200f61b9274f980df180c89f32ff6e30d4676d78000012092d44158cb2a3e8aff9412103ba49c495254796f5ccf0e28f205f62965fafc33367b2b8d6609e5de30c206ad4ffffffff213e4fca3103f812ffcba253caf452c6811947ff6f2fb99b4e18baa1233e84b6010000006b483045022100d676805d8077746d58d79f0199a6f2a0fcf8cc772b149cf77f60ed9b6dd6a60902200c4d805e84f2e0a50d73acffefb3d712a6891f78e9eafddd8bcba872032a88464121035d1d732dbe247c0886753c84dc3d2fc96a9eac26662e8664fe9ce8f67ab6dd98ffffffffe5232220aee5069017d31cc30818bcc971de3e6418f6e62b8cc9a3430d64f3e9010000006b483045022100ed3d4fda64717c43a5fd7329e333d249204e1a25c064bd6bd6b9c945b747befc02205458f90b37aea82c011d4d96c26b808af4b9c19bc07ada196a7a82ac7a3b7eb441210343caa07997898400cefe7a28445b233d30463d13359c1d87ac42ea5da61432a0ffffffff01ce070000000000001976a9141b6b173a4880836d9641b9218ab119a11918684588ac00000000", + hex: "0100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac00000000", isMined: true, - bumpJSON: `{"blockHeight":"818283","path":[[{"offset":"8922","hash":"6c0eb6ccf943b1941b951ab79b9bdb31b5bdd5ca3e57e517dc07343711164962"},{"offset":"8923","hash":"53b3b48b814bacdab9069b0a857b3ac3613466c4de47e03995d0d4464f41d478","txid":true}],[{"offset":"4460","hash":"d6c5b087486396dc18fffd507a862019ba19b4f78851c7d9e94ff59dac81d6c5"}],[{"offset":"2231","hash":"ecce8f71d6f67037506f84639a18d10fecac3692ff3bfd7dd6f7bdddb39ce1ae"}],[{"offset":"1114","hash":"02ce7190b8d0432fea6199aacbdcd92605ffa3e48b7771628340e3e6195358d8"}],[{"offset":"556","hash":"4c119623561375dfb0305676b5cf82ef9d6e1c15525929734bdbcd84cde3ee6a"}],[{"offset":"279","hash":"70a2e8b11a42d8e94ff2ae6c4d6851d2c7c19ee65c4b033ef31182a4f432cb40"}],[{"offset":"138","hash":"a243839cf6a8a2db84db601879b979a69d62768072a30df0545e7ba3260059ad"}],[{"offset":"68","hash":"0c20f8d761f818a30a9edddc32eb7c3609d6754c279aed73077b01f75f278090"}],[{"offset":"35","hash":"19980db1f3ece269ef131dcddec34c4e8c4e17a2687a872a53f076a83d8298e2"}],[{"offset":"16","hash":"adf3d8e7d3a38a1ec11c49850e00a16d035a71af60124a1d456be8a77e027880"}],[{"offset":"9","hash":"35754104f41f501a6774e0062fdc17331346772b4257e057e156b5e0bd47a146"}],[{"offset":"5","hash":"e78a15a5ead17636c2a974866603794b55dde074f2541855fcc9f9796d7c8799"}],[{"offset":"3","hash":"1689e6d017cf693423c31a94b82aa9650a9ad41e71f63f6b626397a494f4d1ac"}],[{"offset":"0","hash":"7811f73e7a4331d4969155ec43a3be7e17ec4f827e049f210a16f0cc6ac8fef6"}],[{"offset":"1","hash":"67fb20248fe9ce7a6ab724f3f0da9543021d2df413783ade4281d679b80ed145"}]]}`, - blockHeight: 818283, + bumpJSON: `{"blockHeight":"817574","path":[[{"offset":"11432","hash":"3b535e0f8e266124bce9868420052d5a7585c67e82c1edc2c7fe05fd5e140307"},{"offset":"11433","hash":"e3642405a9d00efcefd1dc94e8ae94b6dfb66c8b35fb609ab594fc4f425335cb","txid":true}],[{"offset":"5717","hash":"6ef9c6dde7fff82fa893754109f12378c8453b47dc896596b5531433093ab5b7"}],[{"offset":"2859","hash":"daa67e00ad2aef787998b66cbb3417033fbec136da1e230a5f5df3186f5c0880"}],[{"offset":"1428","hash":"bc777a80d951fbf2b7bd3a8048a9bb78fbf1d23d4127290c3fed9740b4246dd2"}],[{"offset":"715","hash":"762b57f88e7258f5757b48cda96d075cbe767c0a39a83e7109574555fd2dd8ba"}],[{"offset":"356","hash":"bbaab745bcca4f8a4be39c06c7e9be3aa1994f32271e3c6b4f768897153e5522"}],[{"offset":"179","hash":"817694ccbde5dbf88f290c30e8735991708a3d406740f7dd31434ff516a5bfde"}],[{"offset":"88","hash":"ed5b52ba4af9198d398e934a84e18405f49e7abde91cafb6dfe5aeaedb33a979"}],[{"offset":"45","hash":"0e51ec9dd5319ceb32d2d20f620c0ca3e0d918260803c1005d49e686c9b18752"}],[{"offset":"23","hash":"08ab694ef1af4019e2999a543a632cf4a662ae04d5fee879c6aadaeb749f6374"}],[{"offset":"10","hash":"4223f47597b14ee0fa7ade08e611ec80948b5fa9da267ce6c8e5d952e7fdb38e"}],[{"offset":"4","hash":"b6dace0d2294fd6e0c11f74376b7f0a1fc8ee415b350caf90c3ae92749e2a8ee"}],[{"offset":"3","hash":"795e7514ebf6d63b454d3f04854e1e0db0ac3a549f61135d5e9ef8d5785f2c68"}],[{"offset":"0","hash":"3f458f2c06493c31cbc3a035ba131913b274ac7915b9b9bc79128001a75cf76d"}],[{"offset":"1","hash":"b9b9f80cc72a674e37b54a9fdee72a9bff761f8cbcb94146afc2bffef33be89f"}]]}`, + blockHeight: 817574, }, { - hex: "010000000469dc4cc3fdb0d34b01e9f7dbae0af803395928f787e35604003f351f4f7f5252000000006b483045022100ec548fb2c6b2a8190bae147f2c41491213e159bef12853cde6d5729b75baf15f022050b8b7490885c6cd444a0c1c0e12d98e24cd2e2bbdba70489cee949af8d11b4c41210391507ea71726708c34141395e08c4a5ea8d6a3e5ab23943ea6faa36174fd4660ffffffffdf96c0a17051236f5d2154d30a0021ecdfa963dc47c0db86efffb90c30195871010000006b483045022100a71e76781a0734ea4d9909cb0e15e7ac63c1a269dcb03b7f0679c784e25aa831022006508da0c72a48a42d67654d4bc002f1fda530cbc9da98d745e94a163ee4c4a3412103f8c774b6331950021b3ecb445b4a5fe046e78def008061963a1b0aa9901e652fffffffff3ef7bba32922a00f3c4893560303c728cf3bbc68ffb9de43ae6b9a040cf039a8000000006a47304402207e7a4aed790fa0f9d25776aeca3aa9eed6796107da41fd15950273589aea838f0220030c3aaf19d34425bc5cb44a488a7ad57fa817a8b8be378b6a28efd40d25913c4121032e2f6e29622309c4dd4daecc16fe6aaa37b848657f2b0a88b9b589525bb27babffffffffe3b23939f7a29999c106a154e8ac555229aa9a039d9a10cb58ec299e2371af99000000006a47304402201b521759f7f42f69915005bf23dd32596026202f6da51418d4a90964e6b924ad02207ac2e3cf7e81b0f5a86ca42d40bcb77a725afc5f694061915e3801b074ff89b5412102657168443ef2b22d747a98131fe686fc46ac0119527b33cbeaafa79670acad92ffffffff0234080000000000001976a9141c052e355d972d64db87b7a207ada864ff1dc05d88ac2e000000000000001976a914cce55c2204a23c80fcdecc6dfb4ddc06bc8f042488ac00000000", + hex: "0100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000", isMined: true, - bumpJSON: `{"blockHeight":"818153","path":[[{"offset":"8824","hash":"9e4f2f2fcd2bbf840a42c405b7307568e66c9036c2fe870eea01194446359757"},{"offset":"8825","hash":"7f0beb34d1eefbc8abb95b769fc1f5e4d3bfccb6475c3125f28d2630cf23717a","txid":true}],[{"offset":"4413","hash":"c11c03c95f105600bbbf117aefe7a4a2a9f0ae53a6c6369426cd507ac2c99344"}],[{"offset":"2207","hash":"d5b15de14da6f7fe80b4752a799f08f113e3ad9f5bafb07fd6b41e076319a577"}],[{"offset":"1102","hash":"827776d06c72809d9147f106f8deb0f0977194a2faeaac03bc82b182e107dd6f"}],[{"offset":"550","hash":"8a72c6aa5d0c6ed42ebb7646cbb9c509594af519f5ee17d09e4a11ad26c84b3e"}],[{"offset":"274","hash":"b107005b5545dfa3ae6b46137abb34612b34bcb257fb5c1557bd8381427d1120"}],[{"offset":"136","hash":"c065efb45241a3f86e241481e4a2bd0582d7373cb7770b40e6bc0a22c3eee3f5"}],[{"offset":"69","hash":"d26b160753a99c900a446e6f32843985eab2608409d46d8c2db25301c005714d"}],[{"offset":"35","hash":"d8cb12f57b167ae64cfad52e1d3469a9b80bdee1b79fee9b08d65a38b2ac33f6"}],[{"offset":"16","hash":"2a28b28dd855b3b274e5b1686f2d3885f6aa353e0f24e82496d9a849d722eaea"}],[{"offset":"9","hash":"1e00139e44321ac97e74fba4b7c5715c438cdd55d40465c49943c690347a7d9f"}],[{"offset":"5","hash":"ac0c8796cf95797caeef7b2a28d78fd21ff90c5cbf1f46cb6c74f2429a923992"}],[{"offset":"3","hash":"c5412a1f08bd34be173b18beb78c2cf38699d756ddc83e4d4a21dbc60e0e430a"}],[{"offset":"0","hash":"22fe45ce2d40387b999f604111db3db982862511dd413e7ad321d31d9b522b7c"}],[{"offset":"1","hash":"20b70aaa9b39e2f3133317dbe911437679d1d533e4d3bd55164ccf99220d1e5c"}]]}`, - blockHeight: 818153, + bumpJSON: `{"blockHeight":"819138","path":[[{"offset":"648","hash":"121bca23ca64d9b925c055f89340802e51f0949ab5edf36ad6dffe050d28400e"},{"offset":"649","hash":"04f01fd4cef5a7bcb6328b08133094e7b9f6feb4b856f46123168de6b4bc4f62","txid":true}],[{"offset":"325","hash":"1e5b72effab8fb56da368f25bab8d8fae7891a5dc70c5da1a8dac4f81e75f990"}],[{"offset":"163","hash":"c97efaa344c57f5e0e46676cbf8629fad9f69a7b1f71d6fda8a1e03f2b546328"}],[{"offset":"80","hash":"5069f334f680952ee9abf37ca5f1cf327e5114920f79ec26b108ae7a491e0b3a"}],[{"offset":"41","hash":"a40cf9eb878b35f853198ebf23ac85061253ffb6e20c4c3eb1ac546b2a376f6d"}],[{"offset":"21","hash":"b5f91b76bf448529368e9421a89e6c756d6b92679ce06479557bf8a5dedb10c3"}],[{"offset":"11","hash":"dbf6acf27c7df7bf4a9de100fd6ad7f73db9b0d659e38235cef4eee26fe367a2"}],[{"offset":"4","hash":"e08fbe8bbdda28ff48478b90c909e4dad7acffc6ff5b3e46f8b4c597d76fc180"}],[{"offset":"3","hash":"0ae8ff1e623834f0624d78703498bb986e1d3d2c5f9d172f05b8e839a09ce0b7"}],[{"offset":"0","hash":"1f55fb14746170226dd929e47fedaac59c65d7b4b9b5502c758bca19a76d5bcf"}],[{"offset":"1","hash":"40ab6623661b0a927bbf231c8a1c0bbf1b64b0b0afb665449cac9ac70e8601dc"}]]}`, + blockHeight: 819138, }, }, expectedError: false, From 2018c457440d0bcd2aebd59fb486c3b3df74679f Mon Sep 17 00:00:00 2001 From: wregulski Date: Wed, 22 Nov 2023 11:42:24 +0100 Subject: [PATCH 06/12] feat: add test cases for happy paths --- beef_fixtures.go | 2 + beef_tx_mock.go | 14 +++--- beef_tx_test.go | 117 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 99 insertions(+), 34 deletions(-) diff --git a/beef_fixtures.go b/beef_fixtures.go index d0dad82a..635debee 100644 --- a/beef_fixtures.go +++ b/beef_fixtures.go @@ -3,4 +3,6 @@ package bux // Fixtures for beef_tx_test.go var expectedBeefHex = map[int]string{ 1: "0100beef02fea6790c000f02fda82c000703145efd05fec7c2edc1827ec685755a2d05208486e9bc2461268e0f5e533bfda92c02cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e301fd551600b7b53a09331453b5966589dc473b45c87823f109417593a82ff8ffe7ddc6f96e01fd2b0b0080085c6f18f35d5f0a231eda36c1be3f031734bb6cb6987978ef2aad007ea6da01fd940500d26d24b44097ed3f0c2927413dd2f1fb78bba948803abdb7f2fb51d9807a77bc01fdcb0200bad82dfd55455709713ea8390a7c76be5c076da9cd487b75f558728ef8572b7601fd64010022553e159788764f6b3c1e27324f99a13abee9c7069ce34b8a4fcabc45b7aabb01b300debfa516f54f4331ddf74067403d8a70915973e8300c298ff8dbe5bdcc94768101580079a933dbaeaee5dfb6af1ce9bd7a9ef40584e1844a938e398d19f94aba525bed012d005287b1c986e6495d00c103082618d9e0a30c0c620fd2d232eb9c31d59dec510e01170074639f74ebdaaac679e8fed504ae62a6f42c633a549a99e21940aff14e69ab08010a008eb3fde752d9e5c8e67c26daa95f8b9480ec11e608de7afae04eb19775f42342010400eea8e24927e93a0cf9ca50b315e48efca1f0b77643f7110c6efd94220dcedab6010300682c5f78d5f89e5e5d13619f543aacb00d1e4e85043f4d453bd6f6eb14755e790100006df75ca701801279bcb9b91579ac74b2131913ba35a0c3cb313c49062c8f453f0101009fe83bf3febfc2af4641b9bc8c1f76ff9b2ae7de9f4ab5374e672ac70cf8b9b9fec27f0c000b02fd8802000e40280d05fedfd66af3edb59a94f0512e804093f855c025b9d964ca23ca1b12fd890202624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff00401fd45010090f9751ef8c4daa8a15d0cc75d1a89e7fad8b8ba258f36da56fbb8faef725b1e01a3002863542b3fe0a1a8fdd6711f7b9af6d9fa2986bf6c67460e5e7fc544a3fa7ec90150003a0b1e497aae08b126ec790f9214517e32cff1a57cf3abe92e9580f634f369500129006d6f372a6b54acb13e4c0ce2b6ff53120685ac23bf8e1953f8358b87ebf90ca4011500c310dbdea5f87b557964e09c67926b6d756c9ea821948e36298544bf761bf9b5010b00a267e36fe2eef4ce3582e359d6b0b93df7d76afd00e19d4abff77d7cf2acf6db01040080c16fd797c5b4f8463e5bffc6ffacd7dae409c9908b4748ff28dabd8bbe8fe0010300b7e09ca039e8b8052f179d5f2c3d1d6e98bb983470784d62f03438621effe80a010000cf5b6da719ca8b752c50b5b9b4d7659cc5aaed7fe429d96d2270617414fb551f010100dc01860ec79aac9c4465b6afb0b0641bbf0b1c8a1c23bf7b920a1b662366ab40030100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac0000000001010100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac0000000001000100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac0000000000", + 2: "0100beef01fe41800c000b06fd2c0402e01e74cab9a0571ab5a7d86794826f756a9c65dd0dea3bb3720c4051c488cf50fd2d0400d9060c543afb1c0faafb96667ed788324d4d1c338142a0841fe3ab9c30922cb4fd90040208c461a39a8877db46472f5cc59e5a108e417b1c9ea3091b71b65346d218f471fd910400ff8fa1e395088748feae2d7729ab9d5da0225f5ed80b2f295625f7c77da087f4fdcc05022256c94d07451664749e440f55cec8a37da1c46cf30a97579e2f9696b84ad484fdcd0500aecbe0a519d483bad8758c3a69cdc0dc12b19a363fded579ebc993edf510746503fd170200dd5d795e63f8777ef7a82453946150946435e52d4076089ce0cb15d8a1237c84fd4902000f3311a938e3f7977bb7a2db5ca912e4e0f26bd12744051333cd22cd3a2fad89fde702008695a1dfeec9393365a21690b089018c9d7dd94bbbf85b62f48701424e0e611c03fd0a010022373d021864aba56583c796bf9131c804a2ea40acede728b279af38b48dfdd2fd2501005e5f986a28e1cdf2b55b6e5bbcfa34742c45e016f7a920518f376c4b0cbfa868fd7201001287267e0f74f28a0dc5e3e0376fbf28c5ab06424a4dcfd02bb7a65b62d9849d038400a8109eb92b03a106ef15c9d120d7c34ff07ac280636632561e42280499f020659300a60aa07079a19a3600e7fc87cc6a72455f0c2f2735dd3d4039bdaf498469c4d3b800685a26978dfc493d1e03efd9a1c9cac0122d6d0bf027b523be85a4e2a2df3df90343006f78c5d6f4372c65cad0546446bd893db8c47f65e3eae2803ced606f4fd272924800b4a9bae40c785222f38e4127a169fbcbe45085b3e9c59d9631032d5a48dab2a35d00559d25e90b990524eb274251a22508ae04b36125dc894c4e1be21c43c19ab93c03200046bc3f2f79d2aa7da31093e690bb6c10a011a67f2a382937c5eaf423b903df5325006daf88be61cc906f104ac405b04d19f4771a63857a25915e376b53250abe112e2f0037461e9fa1f435caca254303b400b21cc452343572a68ad80d9d3287c2bd8f0f031100f14eebaa20670ebb3d9dab73d074550e3a93cfcb29d90c56dcc205aa8b6a51ab1300597b51a0440a0afe4c346f3b89c5f7aaa7478449f3eb6283e1e1f55b24e54b3b16001009f8ce41536d05ace952e35ce67fc94da2e97b6550f55fbc1d5aa5f3266829030800b668767e12637b80a04de0decb4b96b980b19bd0480557adebfc0c6a46cff1140900d24cec3667bedd9ff7e8bc26dff6ec5fcf8af5cc09f500cad08fdfa2ab2ccf870a00ebea0722a541a4f9e7c4659fdcad062e5806b8abba40cba82eba6882896a763d02040036de4d36e7fc3f273ddd83171a030a19c8668a1f5e03dd62ad53866f3afc127705005c9dc967c9a6dd0dd9c80660dd8e86faa3d7f070ed086f1b2d137147d0f52af00103008e9919d62be144a097dd23e1bf924b2e468022c12ccf50db6ceab3d043cdfd8d010000ae98483a460252d92b031d49f591d571e29f1c8b0ae9e2596e4cd24b1c549d3c0401000000019ed68f94dfa952554d777dbaa9e5c01acb3df767e40cabad7b6fb7547bfa871a010000006a4730440220287534d6ff51166e014ad91a2b677be4bd88cf08785624006cdb66553eafc8cf02204862f38e9d2982a5ee95a7850222f2208bff38637349ecfe41abe185498e4ead4121035ca1a2c6d2b46c61fd29e7697018f5ce2bae1ae735e23627046a2dd17ca8fb24ffffffff02de000000000000001976a914f5c9505bf02a4a2fb591e3568183f9c53cf157be88aca62b0000000000001976a91489b5e639bce3209e0888ea8b7eb4203de1c6148888ac000000000100010000000154aa46f1b3b7bde36c02e293b74d53e6c6eaed7411d286183b1dca766f42879a010000006b483045022100cd21d346073b4a0788018ff6938c44395d14cf5759fcc35a0899a8fe35a3c2a0022064eb9a005c3d0be03b61ab0e1c8757ed566dd935dacac37fcd1452adba4994b541210272d67492c31d0e6bead28c934fb1c9bb50ba9b46f886209fe95fb6a3e43bb27bffffffff0257040000000000001976a9140501308b6409cca5a7b5768c18ff2de8da4c1fa388ac39420000000000001976a91417e3d89f4aeacd5b4929fe04edc32c79b6182e1988ac0000000001000100000001e230ab1b300ac3ce334590fc308fee93ddbb252f6e4645e0a20f7e30dd541289010000006b483045022100a611fdf01eca42289d80e1265584e5bd487faa72e6142ebbc140a676f7c5037c0220409282aaadf580f458d97d61db43c94ac343e0b40674a80fd3ac47f43fd0c66c4121020a87e70cc26f7d5fe775f622d2705f27cfd6f5d2b574fea75401d6412a58b91affffffff02d2040000000000001976a9145d2117c4f66bdb335ce2707a74c46fa46d02cdb388acf23b0000000000001976a914effd80ee9df812990a8d7834fa8610491cbeb91688ac0000000001000100000003e01e74cab9a0571ab5a7d86794826f756a9c65dd0dea3bb3720c4051c488cf50000000006b483045022100bc7fc6ace1a5b1ab8601599d56b3adad4a11b7f11757f3225e96b46ca1ab7f7c0220324d6074aa987a7c63c404ac5b03c26e55d3c4209e298b4ca9df0e90aca43ef3412103ee05b34332b5662830c600b73f9c908bb8bff1813bc9b2690e9cad00fad23d3cffffffff08c461a39a8877db46472f5cc59e5a108e417b1c9ea3091b71b65346d218f471000000006b483045022100a936c496423ec03b1ad0f3bfe2348572d7b29ab14e4435c0c8e2ee093d930fde02203d9e86647ea18043c150289f74c6cf2ceb9ca3b228ae31c7b19c4eef813fb68d412103a19014bcc672ccdf18abb6972dd699367baed89c29b704385253ce2ae0eddad5ffffffff2256c94d07451664749e440f55cec8a37da1c46cf30a97579e2f9696b84ad484000000006b48304502210091b0bcf2e84d9ee65de437e8396b379941345e4cffac331af2ae29b8a16968a602205a00eed18a7ffe36f59ae6eb477d9002324cfc249c875260e6ade5bce852692d4121021446bd1df2b61952088a22a516550e43cd95e47ca2a778822d21268bd8b1cebeffffffff02c4090000000000001976a91497ebeffef6d9dd88ffbce922f1df97cbcd7f88d388ac42000000000000001976a91449457f2c101859d1c8ff90096385d3cc30e5488388ac0000000000", + 3: "0100beef02fea6790c000f02fda82c000703145efd05fec7c2edc1827ec685755a2d05208486e9bc2461268e0f5e533bfda92c02cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e301fd551600b7b53a09331453b5966589dc473b45c87823f109417593a82ff8ffe7ddc6f96e01fd2b0b0080085c6f18f35d5f0a231eda36c1be3f031734bb6cb6987978ef2aad007ea6da01fd940500d26d24b44097ed3f0c2927413dd2f1fb78bba948803abdb7f2fb51d9807a77bc01fdcb0200bad82dfd55455709713ea8390a7c76be5c076da9cd487b75f558728ef8572b7601fd64010022553e159788764f6b3c1e27324f99a13abee9c7069ce34b8a4fcabc45b7aabb01b300debfa516f54f4331ddf74067403d8a70915973e8300c298ff8dbe5bdcc94768101580079a933dbaeaee5dfb6af1ce9bd7a9ef40584e1844a938e398d19f94aba525bed012d005287b1c986e6495d00c103082618d9e0a30c0c620fd2d232eb9c31d59dec510e01170074639f74ebdaaac679e8fed504ae62a6f42c633a549a99e21940aff14e69ab08010a008eb3fde752d9e5c8e67c26daa95f8b9480ec11e608de7afae04eb19775f42342010400eea8e24927e93a0cf9ca50b315e48efca1f0b77643f7110c6efd94220dcedab6010300682c5f78d5f89e5e5d13619f543aacb00d1e4e85043f4d453bd6f6eb14755e790100006df75ca701801279bcb9b91579ac74b2131913ba35a0c3cb313c49062c8f453f0101009fe83bf3febfc2af4641b9bc8c1f76ff9b2ae7de9f4ab5374e672ac70cf8b9b9fe73780c000c02cc005d2fa529262bd8c5451e2e9e91ff0cffa1100b144bddbb537db6cef3868d68cccd02a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb016700672bf5eb61032d3a9404ff6b2045a7ee3444a0612dd5f810fbf8ea363aae0b0b013200f62a9a8d99f570bfa8eb48ef0da91c84ab8110f826f67b72db053513b78baf430118000fec193c7d2ab70f1c74b7b2f4413db12a2da66cf40630ea0e57884d885dbf18010d0027f259788828eb8461da4e67e89c321c14be138576b039663da2dd84f84bcffc010700ebf4dd6d5eb43dcf25ef8025b17072ad3de6db031866c94570d86696bdb9533301020068d3cb01545e515692b45db66d8cf9f5020b0473fda254b2adc45308eee8db3801000072913db72c489f740085cb7aabe3c31271a2ea908b0c8498a75e89cab82a54c0010100491b7787ad57f9b034c950466026e844826a31d1227131dd54ffe965b3612bc20101001f83fda6372ec5bc5f4fb776506ebc01c14841d5173c4444cc3f37ab0539e00d010100cebf33ed7c7afce3c7c873d863ebccfc98a9450cefef843789c3683ab4dbf2c1010100dc365f1d99781e5053f678bb9e06d4a17bc6ed9a2258e2c69a2908800e4a7f2a04010000000150965003ea3d2c08bc79b116c9ffe7e730c9f9cf0a61e3df07868b24eac6f8d3000000006b4830450221009d3489f9e76ff3b043708972c52f85519e50a5fc35563d405e04b668780bf2ba0220024188508fc9c6870b2fc4f40b9484ae4163481199a5b4a7a338b86ec8952fee4121036a8b9d796ce2dee820d1f6d7a6ba07037dab4758f16028654fe4bc3a5c430b40ffffffff022a200000000000001976a91484c73348a8fbbc44cfa34f8f5441fc104f3bc78588ac162f0000000000001976a914590b1df63948c2c4e7a12a6e52012b36e25daa9888ac0000000001010100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000000100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac0000000001000100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac0000000000", } diff --git a/beef_tx_mock.go b/beef_tx_mock.go index fb1381b2..5aa5fe40 100644 --- a/beef_tx_mock.go +++ b/beef_tx_mock.go @@ -5,23 +5,21 @@ import ( "fmt" ) -type MockTransactionGetter struct { +type MockTransactionStore struct { Transactions map[string]*Transaction } -func NewMockTransactionGetter() *MockTransactionGetter { - return &MockTransactionGetter{ +func NewMockTransactionStore() *MockTransactionStore { + return &MockTransactionStore{ Transactions: make(map[string]*Transaction), } } -func (m *MockTransactionGetter) Init(transactions []*Transaction) { - for _, tx := range transactions { - m.Transactions[tx.ID] = tx - } +func (m *MockTransactionStore) AddToStore(tx *Transaction) { + m.Transactions[tx.ID] = tx } -func (m *MockTransactionGetter) GetTransactionByID(ctx context.Context, txID string) (*Transaction, error) { +func (m *MockTransactionStore) GetTransactionByID(ctx context.Context, txID string) (*Transaction, error) { if tx, exists := m.Transactions[txID]; exists { return tx, nil } diff --git a/beef_tx_test.go b/beef_tx_test.go index e8e1dc34..04ddc4c7 100644 --- a/beef_tx_test.go +++ b/beef_tx_test.go @@ -9,31 +9,32 @@ import ( "github.com/stretchr/testify/require" ) -const hexForProcessedTx = "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000" - type beefTestCase struct { testID int name string + hexForProcessedTx string outputValue uint64 receiverAddress string ancestors []*beefTestCaseAncestor - expectedError bool expectedErrorMessage string } type beefTestCaseAncestor struct { - hex string - isMined bool - bumpJSON string - blockHeight int + // reverse condition to not set this value every time + doNotAddToStore bool + hex string + isMined bool + bumpJSON string + blockHeight int + parents []*beefTestCaseAncestor } -// TODO: BUX-168 - fix this test -func Test_ToBeef(t *testing.T) { +func Test_ToBeef_HappyPaths(t *testing.T) { testCases := []beefTestCase{ { - testID: 1, - name: "all parents txs are already mined", + testID: 1, + hexForProcessedTx: "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000", + name: "all inputs are already mined - 2 inputs on the same level", ancestors: []*beefTestCaseAncestor{ { hex: "0100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac00000000", @@ -48,7 +49,64 @@ func Test_ToBeef(t *testing.T) { blockHeight: 819138, }, }, - expectedError: false, + receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", + outputValue: 3000, + expectedErrorMessage: "", + }, + { + testID: 2, + hexForProcessedTx: "0100000003e01e74cab9a0571ab5a7d86794826f756a9c65dd0dea3bb3720c4051c488cf50000000006b483045022100bc7fc6ace1a5b1ab8601599d56b3adad4a11b7f11757f3225e96b46ca1ab7f7c0220324d6074aa987a7c63c404ac5b03c26e55d3c4209e298b4ca9df0e90aca43ef3412103ee05b34332b5662830c600b73f9c908bb8bff1813bc9b2690e9cad00fad23d3cffffffff08c461a39a8877db46472f5cc59e5a108e417b1c9ea3091b71b65346d218f471000000006b483045022100a936c496423ec03b1ad0f3bfe2348572d7b29ab14e4435c0c8e2ee093d930fde02203d9e86647ea18043c150289f74c6cf2ceb9ca3b228ae31c7b19c4eef813fb68d412103a19014bcc672ccdf18abb6972dd699367baed89c29b704385253ce2ae0eddad5ffffffff2256c94d07451664749e440f55cec8a37da1c46cf30a97579e2f9696b84ad484000000006b48304502210091b0bcf2e84d9ee65de437e8396b379941345e4cffac331af2ae29b8a16968a602205a00eed18a7ffe36f59ae6eb477d9002324cfc249c875260e6ade5bce852692d4121021446bd1df2b61952088a22a516550e43cd95e47ca2a778822d21268bd8b1cebeffffffff02c4090000000000001976a91497ebeffef6d9dd88ffbce922f1df97cbcd7f88d388ac42000000000000001976a91449457f2c101859d1c8ff90096385d3cc30e5488388ac00000000", + name: "all inputs are already mined - 3 inputs on the same level", + ancestors: []*beefTestCaseAncestor{ + { + hex: "01000000019ed68f94dfa952554d777dbaa9e5c01acb3df767e40cabad7b6fb7547bfa871a010000006a4730440220287534d6ff51166e014ad91a2b677be4bd88cf08785624006cdb66553eafc8cf02204862f38e9d2982a5ee95a7850222f2208bff38637349ecfe41abe185498e4ead4121035ca1a2c6d2b46c61fd29e7697018f5ce2bae1ae735e23627046a2dd17ca8fb24ffffffff02de000000000000001976a914f5c9505bf02a4a2fb591e3568183f9c53cf157be88aca62b0000000000001976a91489b5e639bce3209e0888ea8b7eb4203de1c6148888ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"819265","path":[[{"offset":"1484","hash":"84d44ab896962f9e57970af36cc4a17da3c8ce550f449e74641645074dc95622","txid":true},{"offset":"1485","hash":"657410f5ed93c9eb79d5de3f369ab112dcc0cd693a8c75d8ba83d419a5e0cbae"}],[{"offset":"743","hash":"1c610e4e420187f4625bf8bb4bd97d9d8c0189b09016a2653339c9eedfa19586"}],[{"offset":"370","hash":"9d84d9625ba6b72bd0cf4d4a4206abc528bf6f37e0e3c50d8af2740f7e268712"}],[{"offset":"184","hash":"f93ddfa2e2a485be23b527f00b6d2d12c0cac9a1d9ef031e3d49fc8d97265a68"}],[{"offset":"93","hash":"3cb99ac1431ce21b4e4c89dc2561b304ae0825a2514227eb2405990be9259d55"}],[{"offset":"47","hash":"0f8fbdc287329d0dd88aa672353452c41cb200b4034325caca35f4a19f1e4637"}],[{"offset":"22","hash":"296826f3a55a1dbc5ff550657be9a24dc97fe65ce352e9ac056d5341cef80910"}],[{"offset":"10","hash":"3d766a898268ba2ea8cb40baabb806582e06addc9f65c4e7f9a441a52207eaeb"}],[{"offset":"4","hash":"7712fc3a6f8653ad62dd035e1f8a66c8190a031a1783dd3d273ffce7364dde36"}],[{"offset":"3","hash":"8dfdcd43d0b3ea6cdb50cf2cc12280462e4b92bfe123dd97a044e12bd619998e"}],[{"offset":"0","hash":"3c9d541c4bd24c6e59e2e90a8b1c9fe271d591f5491d032bd95202463a4898ae"}]]}`, + blockHeight: 819265, + }, + { + hex: "010000000154aa46f1b3b7bde36c02e293b74d53e6c6eaed7411d286183b1dca766f42879a010000006b483045022100cd21d346073b4a0788018ff6938c44395d14cf5759fcc35a0899a8fe35a3c2a0022064eb9a005c3d0be03b61ab0e1c8757ed566dd935dacac37fcd1452adba4994b541210272d67492c31d0e6bead28c934fb1c9bb50ba9b46f886209fe95fb6a3e43bb27bffffffff0257040000000000001976a9140501308b6409cca5a7b5768c18ff2de8da4c1fa388ac39420000000000001976a91417e3d89f4aeacd5b4929fe04edc32c79b6182e1988ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"819265","path":[[{"offset":"1168","hash":"71f418d24653b6711b09a39e1c7b418e105a9ec55c2f4746db77889aa361c408","txid":true},{"offset":"1169","hash":"f487a07dc7f72556292f0bd85e5f22a05d9dab29772daefe48870895e3a18fff"}],[{"offset":"585","hash":"89ad2f3acd22cd3313054427d16bf2e0e412a95cdba2b77b97f7e338a911330f"}],[{"offset":"293","hash":"68a8bf0c4b6c378f5120a9f716e0452c7434fabc5b6e5bb5f2cde1286a985f5e"}],[{"offset":"147","hash":"d3c4698449afbd39403ddd35272f0c5f45726acc87fce700369aa17970a00aa6"}],[{"offset":"72","hash":"a3b2da485a2d0331969dc5e9b38550e4cbfb69a127418ef32252780ce4baa9b4"}],[{"offset":"37","hash":"2e11be0a25536b375e91257a85631a77f4194db005c44a106f90cc61be88af6d"}],[{"offset":"19","hash":"3b4be5245bf5e1e18362ebf3498447a7aaf7c5893b6f344cfe0a0a44a0517b59"}],[{"offset":"8","hash":"14f1cf466a0cfcebad570548d09bb180b9964bcbdee04da0807b63127e7668b6"}],[{"offset":"5","hash":"f02af5d04771132d1b6f08ed70f0d7a3fa868edd6006c8d90ddda6c967c99d5c"}],[{"offset":"3","hash":"8dfdcd43d0b3ea6cdb50cf2cc12280462e4b92bfe123dd97a044e12bd619998e"}],[{"offset":"0","hash":"3c9d541c4bd24c6e59e2e90a8b1c9fe271d591f5491d032bd95202463a4898ae"}]]}`, + blockHeight: 819265, + }, + { + hex: "0100000001e230ab1b300ac3ce334590fc308fee93ddbb252f6e4645e0a20f7e30dd541289010000006b483045022100a611fdf01eca42289d80e1265584e5bd487faa72e6142ebbc140a676f7c5037c0220409282aaadf580f458d97d61db43c94ac343e0b40674a80fd3ac47f43fd0c66c4121020a87e70cc26f7d5fe775f622d2705f27cfd6f5d2b574fea75401d6412a58b91affffffff02d2040000000000001976a9145d2117c4f66bdb335ce2707a74c46fa46d02cdb388acf23b0000000000001976a914effd80ee9df812990a8d7834fa8610491cbeb91688ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"819265","path":[[{"offset":"1068","hash":"50cf88c451400c72b33bea0ddd659c6a756f829467d8a7b51a57a0b9ca741ee0","txid":true},{"offset":"1069","hash":"b42c92309cabe31f84a04281331c4d4d3288d77e6696fbaa0f1cfb3a540c06d9"}],[{"offset":"535","hash":"847c23a1d815cbe09c0876402de53564945061945324a8f77e77f8635e795ddd"}],[{"offset":"266","hash":"d2fd8db438af79b228e7edac40eaa204c83191bf96c78365a5ab6418023d3722"}],[{"offset":"132","hash":"6520f0990428421e5632666380c27af04fc3d720d1c915ef06a1032bb99e10a8"}],[{"offset":"67","hash":"9272d24f6f60ed3c80e2eae3657fc4b83d89bd466454d0ca652c37f4d6c5786f"}],[{"offset":"32","hash":"53df03b923f4eac53729382a7fa611a0106cbb90e69310a37daad2792f3fbc46"}],[{"offset":"17","hash":"ab516a8baa05c2dc560cd929cbcf933a0e5574d073ab9d3dbb0e6720aaeb4ef1"}],[{"offset":"9","hash":"87cf2caba2df8fd0ca00f509ccf58acf5fecf6df26bce8f79fddbe6736ec4cd2"}],[{"offset":"5","hash":"f02af5d04771132d1b6f08ed70f0d7a3fa868edd6006c8d90ddda6c967c99d5c"}],[{"offset":"3","hash":"8dfdcd43d0b3ea6cdb50cf2cc12280462e4b92bfe123dd97a044e12bd619998e"}],[{"offset":"0","hash":"3c9d541c4bd24c6e59e2e90a8b1c9fe271d591f5491d032bd95202463a4898ae"}]]}`, + blockHeight: 819265, + }, + }, + receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", + outputValue: 4000, + expectedErrorMessage: "", + }, + { + testID: 3, + hexForProcessedTx: "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000", + name: "not all inputs are mined but all required ancestors are mined", + ancestors: []*beefTestCaseAncestor{ + { + hex: "0100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"817574","path":[[{"offset":"11432","hash":"3b535e0f8e266124bce9868420052d5a7585c67e82c1edc2c7fe05fd5e140307"},{"offset":"11433","hash":"e3642405a9d00efcefd1dc94e8ae94b6dfb66c8b35fb609ab594fc4f425335cb","txid":true}],[{"offset":"5717","hash":"6ef9c6dde7fff82fa893754109f12378c8453b47dc896596b5531433093ab5b7"}],[{"offset":"2859","hash":"daa67e00ad2aef787998b66cbb3417033fbec136da1e230a5f5df3186f5c0880"}],[{"offset":"1428","hash":"bc777a80d951fbf2b7bd3a8048a9bb78fbf1d23d4127290c3fed9740b4246dd2"}],[{"offset":"715","hash":"762b57f88e7258f5757b48cda96d075cbe767c0a39a83e7109574555fd2dd8ba"}],[{"offset":"356","hash":"bbaab745bcca4f8a4be39c06c7e9be3aa1994f32271e3c6b4f768897153e5522"}],[{"offset":"179","hash":"817694ccbde5dbf88f290c30e8735991708a3d406740f7dd31434ff516a5bfde"}],[{"offset":"88","hash":"ed5b52ba4af9198d398e934a84e18405f49e7abde91cafb6dfe5aeaedb33a979"}],[{"offset":"45","hash":"0e51ec9dd5319ceb32d2d20f620c0ca3e0d918260803c1005d49e686c9b18752"}],[{"offset":"23","hash":"08ab694ef1af4019e2999a543a632cf4a662ae04d5fee879c6aadaeb749f6374"}],[{"offset":"10","hash":"4223f47597b14ee0fa7ade08e611ec80948b5fa9da267ce6c8e5d952e7fdb38e"}],[{"offset":"4","hash":"b6dace0d2294fd6e0c11f74376b7f0a1fc8ee415b350caf90c3ae92749e2a8ee"}],[{"offset":"3","hash":"795e7514ebf6d63b454d3f04854e1e0db0ac3a549f61135d5e9ef8d5785f2c68"}],[{"offset":"0","hash":"3f458f2c06493c31cbc3a035ba131913b274ac7915b9b9bc79128001a75cf76d"}],[{"offset":"1","hash":"b9b9f80cc72a674e37b54a9fdee72a9bff761f8cbcb94146afc2bffef33be89f"}]]}`, + blockHeight: 817574, + }, + { + hex: "0100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000", + isMined: false, + bumpJSON: ``, + blockHeight: -1, + parents: []*beefTestCaseAncestor{ + { + hex: "010000000150965003ea3d2c08bc79b116c9ffe7e730c9f9cf0a61e3df07868b24eac6f8d3000000006b4830450221009d3489f9e76ff3b043708972c52f85519e50a5fc35563d405e04b668780bf2ba0220024188508fc9c6870b2fc4f40b9484ae4163481199a5b4a7a338b86ec8952fee4121036a8b9d796ce2dee820d1f6d7a6ba07037dab4758f16028654fe4bc3a5c430b40ffffffff022a200000000000001976a91484c73348a8fbbc44cfa34f8f5441fc104f3bc78588ac162f0000000000001976a914590b1df63948c2c4e7a12a6e52012b36e25daa9888ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"817267","path":[[{"offset":"204","hash":"cc688d86f3ceb67d53bbdd4b140b10a1ff0cff919e2e1e45c5d82b2629a52f5d"},{"offset":"205","hash":"eb44a6070ec5d101ad1b4eeeaf77bd978ca10aa15a75871d85badeb8dec714a1","txid":true}],[{"offset":"103","hash":"0b0bae3a36eaf8fb10f8d52d61a04434eea745206bff04943a2d0361ebf52b67"}],[{"offset":"50","hash":"43af8bb7133505db727bf626f81081ab841ca90def48eba8bf70f5998d9a2af6"}],[{"offset":"24","hash":"18bf5d884d88570eea3006f46ca62d2ab13d41f4b2b7741c0fb72a7d3c19ec0f"}],[{"offset":"13","hash":"fccf4bf884dda23d6639b0768513be141c329ce8674eda6184eb28887859f227"}],[{"offset":"7","hash":"3353b9bd9666d87045c9661803dbe63dad7270b12580ef25cf3db45e6dddf4eb"}],[{"offset":"2","hash":"38dbe8ee0853c4adb254a2fd73040b02f5f98c6db65db49256515e5401cbd368"}],[{"offset":"0","hash":"c0542ab8ca895ea798840c8b90eaa27112c3e3ab7acb8500749f482cb73d9172"}],[{"offset":"1","hash":"c22b61b365e9ff54dd317122d1316a8244e826604650c934b0f957ad87771b49"}],[{"offset":"1","hash":"0de03905ab373fcc44443c17d54148c101bc6e5076b74f5fbcc52e37a6fd831f"}],[{"offset":"1","hash":"c1f2dbb43a68c3893784efef0c45a998fccceb63d873c8c7e3fc7a7ced33bfce"}],[{"offset":"1","hash":"2a7f4a0e8008299ac6e258229aedc67ba1d4069ebb78f653501e78991d5f36dc"}]]}`, + blockHeight: 817267, + }, + }, + }, + }, receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", outputValue: 3000, expectedErrorMessage: "", @@ -59,26 +117,23 @@ func Test_ToBeef(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // given ctx, client, deferMe := initSimpleTestCase(t) + store := NewMockTransactionStore() defer deferMe() var ancestors []*Transaction for _, ancestor := range tc.ancestors { - ancestors = append(ancestors, addAncestor(ctx, ancestor, client, t)) + ancestors = append(ancestors, addAncestor(ctx, ancestor, client, store, t)) } newTx := createProcessedTx(ctx, t, client, &tc, ancestors) - mockGetter := NewMockTransactionGetter() - mockGetter.Init(ancestors) - // when - result, err := ToBeef(ctx, newTx, mockGetter) + result, err := ToBeef(ctx, newTx, store) // then - if tc.expectedError { - assert.Equal(t, tc.expectedErrorMessage, err.Error()) - } assert.Equal(t, expectedBeefHex[tc.testID], result) + // happy paths, should not return error + assert.NoError(t, nil, err) }) } } @@ -102,7 +157,7 @@ func createProcessedTx(ctx context.Context, t *testing.T, client ClientInterface append(client.DefaultModelOptions(), New())..., ) - transaction := newTransaction(hexForProcessedTx, append(client.DefaultModelOptions(), New())...) + transaction := newTransaction(testCase.hexForProcessedTx, append(client.DefaultModelOptions(), New())...) transaction.draftTransaction = draftTx transaction.DraftID = draftTx.ID @@ -111,19 +166,29 @@ func createProcessedTx(ctx context.Context, t *testing.T, client ClientInterface return transaction } -func addAncestor(ctx context.Context, testCase *beefTestCaseAncestor, client ClientInterface, t *testing.T) *Transaction { - grandpaTx := newTransaction(testCase.hex, append(client.DefaultModelOptions(), New())...) +func addAncestor(ctx context.Context, testCase *beefTestCaseAncestor, client ClientInterface, store *MockTransactionStore, t *testing.T) *Transaction { + ancestor := newTransaction(testCase.hex, append(client.DefaultModelOptions(), New())...) if testCase.isMined { - grandpaTx.BlockHeight = uint64(testCase.blockHeight) + ancestor.BlockHeight = uint64(testCase.blockHeight) var bump BUMP err := json.Unmarshal([]byte(testCase.bumpJSON), &bump) require.NoError(t, err) - grandpaTx.BUMP = bump + ancestor.BUMP = bump + } else { + // if we marked transaction as not mined, we need to add it's parents + for _, parent := range testCase.parents { + // no need a result from this func - we just want to add ancestors from level 1 and above to database if required + _ = addAncestor(ctx, parent, client, store, t) + } + } + + if !testCase.doNotAddToStore { + store.AddToStore(ancestor) } - return grandpaTx + return ancestor } func createInputsUsingAncestors(ancestors []*Transaction, client ClientInterface) []*TransactionInput { From f60aaa056482490bd0e65001a54cf4f4b349ae91 Mon Sep 17 00:00:00 2001 From: wregulski Date: Wed, 22 Nov 2023 16:01:18 +0100 Subject: [PATCH 07/12] feat: add sorting of bumps and error paths tests --- beef_bump.go | 19 ++++-- beef_fixtures.go | 3 +- beef_tx.go | 1 - beef_tx_test.go | 152 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 164 insertions(+), 11 deletions(-) diff --git a/beef_bump.go b/beef_bump.go index 3d118894..f42bfc60 100644 --- a/beef_bump.go +++ b/beef_bump.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "sort" "github.com/libsv/go-bt/v2" ) @@ -19,8 +20,16 @@ func calculateMergedBUMP(txs []*Transaction) (BUMPs, error) { bumps[tx.BlockHeight] = append(bumps[tx.BlockHeight], tx.BUMP) } - for _, b := range bumps { - bump, err := CalculateMergedBUMP(b) + + // ensure that BUMPs are sorted by block height and will always be put in beef in the same order + mapKeys := make([]uint64, 0, len(bumps)) + for k := range bumps { + mapKeys = append(mapKeys, k) + } + sort.Slice(mapKeys, func(i, j int) bool { return mapKeys[i] < mapKeys[j] }) + + for _, k := range mapKeys { + bump, err := CalculateMergedBUMP(bumps[k]) if err != nil { return nil, fmt.Errorf("Error while calculating Merged BUMP: %s", err.Error()) } @@ -92,12 +101,12 @@ func checkParentTransactions(ctx context.Context, store TransactionGetter, input for _, txIn := range btTx.Inputs { parentTx, err := store.GetTransactionByID(ctx, txIn.PreviousTxIDStr()) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("cannot get parent transaction by ID (tx.ID: %s). Reason: %w", txIn.PreviousTxIDStr(), err) } parentBtTx, err := bt.NewTxFromString(parentTx.Hex) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) } validTxs = append(validTxs, parentTx) validBtTxs = append(validBtTxs, parentBtTx) @@ -113,7 +122,7 @@ func checkParentTransactions(ctx context.Context, store TransactionGetter, input } if len(validBtTxs) == 0 { - return nil, nil, fmt.Errorf("transaction is not mined yet (tx.ID: %s)", inputTx.ID) + return nil, nil, fmt.Errorf("transaction is not mined yet and their parents are not present or mined (tx.ID: %s)", inputTx.ID) } return validBtTxs, validTxs, nil diff --git a/beef_fixtures.go b/beef_fixtures.go index 635debee..4c622e61 100644 --- a/beef_fixtures.go +++ b/beef_fixtures.go @@ -4,5 +4,6 @@ package bux var expectedBeefHex = map[int]string{ 1: "0100beef02fea6790c000f02fda82c000703145efd05fec7c2edc1827ec685755a2d05208486e9bc2461268e0f5e533bfda92c02cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e301fd551600b7b53a09331453b5966589dc473b45c87823f109417593a82ff8ffe7ddc6f96e01fd2b0b0080085c6f18f35d5f0a231eda36c1be3f031734bb6cb6987978ef2aad007ea6da01fd940500d26d24b44097ed3f0c2927413dd2f1fb78bba948803abdb7f2fb51d9807a77bc01fdcb0200bad82dfd55455709713ea8390a7c76be5c076da9cd487b75f558728ef8572b7601fd64010022553e159788764f6b3c1e27324f99a13abee9c7069ce34b8a4fcabc45b7aabb01b300debfa516f54f4331ddf74067403d8a70915973e8300c298ff8dbe5bdcc94768101580079a933dbaeaee5dfb6af1ce9bd7a9ef40584e1844a938e398d19f94aba525bed012d005287b1c986e6495d00c103082618d9e0a30c0c620fd2d232eb9c31d59dec510e01170074639f74ebdaaac679e8fed504ae62a6f42c633a549a99e21940aff14e69ab08010a008eb3fde752d9e5c8e67c26daa95f8b9480ec11e608de7afae04eb19775f42342010400eea8e24927e93a0cf9ca50b315e48efca1f0b77643f7110c6efd94220dcedab6010300682c5f78d5f89e5e5d13619f543aacb00d1e4e85043f4d453bd6f6eb14755e790100006df75ca701801279bcb9b91579ac74b2131913ba35a0c3cb313c49062c8f453f0101009fe83bf3febfc2af4641b9bc8c1f76ff9b2ae7de9f4ab5374e672ac70cf8b9b9fec27f0c000b02fd8802000e40280d05fedfd66af3edb59a94f0512e804093f855c025b9d964ca23ca1b12fd890202624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff00401fd45010090f9751ef8c4daa8a15d0cc75d1a89e7fad8b8ba258f36da56fbb8faef725b1e01a3002863542b3fe0a1a8fdd6711f7b9af6d9fa2986bf6c67460e5e7fc544a3fa7ec90150003a0b1e497aae08b126ec790f9214517e32cff1a57cf3abe92e9580f634f369500129006d6f372a6b54acb13e4c0ce2b6ff53120685ac23bf8e1953f8358b87ebf90ca4011500c310dbdea5f87b557964e09c67926b6d756c9ea821948e36298544bf761bf9b5010b00a267e36fe2eef4ce3582e359d6b0b93df7d76afd00e19d4abff77d7cf2acf6db01040080c16fd797c5b4f8463e5bffc6ffacd7dae409c9908b4748ff28dabd8bbe8fe0010300b7e09ca039e8b8052f179d5f2c3d1d6e98bb983470784d62f03438621effe80a010000cf5b6da719ca8b752c50b5b9b4d7659cc5aaed7fe429d96d2270617414fb551f010100dc01860ec79aac9c4465b6afb0b0641bbf0b1c8a1c23bf7b920a1b662366ab40030100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac0000000001010100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac0000000001000100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac0000000000", 2: "0100beef01fe41800c000b06fd2c0402e01e74cab9a0571ab5a7d86794826f756a9c65dd0dea3bb3720c4051c488cf50fd2d0400d9060c543afb1c0faafb96667ed788324d4d1c338142a0841fe3ab9c30922cb4fd90040208c461a39a8877db46472f5cc59e5a108e417b1c9ea3091b71b65346d218f471fd910400ff8fa1e395088748feae2d7729ab9d5da0225f5ed80b2f295625f7c77da087f4fdcc05022256c94d07451664749e440f55cec8a37da1c46cf30a97579e2f9696b84ad484fdcd0500aecbe0a519d483bad8758c3a69cdc0dc12b19a363fded579ebc993edf510746503fd170200dd5d795e63f8777ef7a82453946150946435e52d4076089ce0cb15d8a1237c84fd4902000f3311a938e3f7977bb7a2db5ca912e4e0f26bd12744051333cd22cd3a2fad89fde702008695a1dfeec9393365a21690b089018c9d7dd94bbbf85b62f48701424e0e611c03fd0a010022373d021864aba56583c796bf9131c804a2ea40acede728b279af38b48dfdd2fd2501005e5f986a28e1cdf2b55b6e5bbcfa34742c45e016f7a920518f376c4b0cbfa868fd7201001287267e0f74f28a0dc5e3e0376fbf28c5ab06424a4dcfd02bb7a65b62d9849d038400a8109eb92b03a106ef15c9d120d7c34ff07ac280636632561e42280499f020659300a60aa07079a19a3600e7fc87cc6a72455f0c2f2735dd3d4039bdaf498469c4d3b800685a26978dfc493d1e03efd9a1c9cac0122d6d0bf027b523be85a4e2a2df3df90343006f78c5d6f4372c65cad0546446bd893db8c47f65e3eae2803ced606f4fd272924800b4a9bae40c785222f38e4127a169fbcbe45085b3e9c59d9631032d5a48dab2a35d00559d25e90b990524eb274251a22508ae04b36125dc894c4e1be21c43c19ab93c03200046bc3f2f79d2aa7da31093e690bb6c10a011a67f2a382937c5eaf423b903df5325006daf88be61cc906f104ac405b04d19f4771a63857a25915e376b53250abe112e2f0037461e9fa1f435caca254303b400b21cc452343572a68ad80d9d3287c2bd8f0f031100f14eebaa20670ebb3d9dab73d074550e3a93cfcb29d90c56dcc205aa8b6a51ab1300597b51a0440a0afe4c346f3b89c5f7aaa7478449f3eb6283e1e1f55b24e54b3b16001009f8ce41536d05ace952e35ce67fc94da2e97b6550f55fbc1d5aa5f3266829030800b668767e12637b80a04de0decb4b96b980b19bd0480557adebfc0c6a46cff1140900d24cec3667bedd9ff7e8bc26dff6ec5fcf8af5cc09f500cad08fdfa2ab2ccf870a00ebea0722a541a4f9e7c4659fdcad062e5806b8abba40cba82eba6882896a763d02040036de4d36e7fc3f273ddd83171a030a19c8668a1f5e03dd62ad53866f3afc127705005c9dc967c9a6dd0dd9c80660dd8e86faa3d7f070ed086f1b2d137147d0f52af00103008e9919d62be144a097dd23e1bf924b2e468022c12ccf50db6ceab3d043cdfd8d010000ae98483a460252d92b031d49f591d571e29f1c8b0ae9e2596e4cd24b1c549d3c0401000000019ed68f94dfa952554d777dbaa9e5c01acb3df767e40cabad7b6fb7547bfa871a010000006a4730440220287534d6ff51166e014ad91a2b677be4bd88cf08785624006cdb66553eafc8cf02204862f38e9d2982a5ee95a7850222f2208bff38637349ecfe41abe185498e4ead4121035ca1a2c6d2b46c61fd29e7697018f5ce2bae1ae735e23627046a2dd17ca8fb24ffffffff02de000000000000001976a914f5c9505bf02a4a2fb591e3568183f9c53cf157be88aca62b0000000000001976a91489b5e639bce3209e0888ea8b7eb4203de1c6148888ac000000000100010000000154aa46f1b3b7bde36c02e293b74d53e6c6eaed7411d286183b1dca766f42879a010000006b483045022100cd21d346073b4a0788018ff6938c44395d14cf5759fcc35a0899a8fe35a3c2a0022064eb9a005c3d0be03b61ab0e1c8757ed566dd935dacac37fcd1452adba4994b541210272d67492c31d0e6bead28c934fb1c9bb50ba9b46f886209fe95fb6a3e43bb27bffffffff0257040000000000001976a9140501308b6409cca5a7b5768c18ff2de8da4c1fa388ac39420000000000001976a91417e3d89f4aeacd5b4929fe04edc32c79b6182e1988ac0000000001000100000001e230ab1b300ac3ce334590fc308fee93ddbb252f6e4645e0a20f7e30dd541289010000006b483045022100a611fdf01eca42289d80e1265584e5bd487faa72e6142ebbc140a676f7c5037c0220409282aaadf580f458d97d61db43c94ac343e0b40674a80fd3ac47f43fd0c66c4121020a87e70cc26f7d5fe775f622d2705f27cfd6f5d2b574fea75401d6412a58b91affffffff02d2040000000000001976a9145d2117c4f66bdb335ce2707a74c46fa46d02cdb388acf23b0000000000001976a914effd80ee9df812990a8d7834fa8610491cbeb91688ac0000000001000100000003e01e74cab9a0571ab5a7d86794826f756a9c65dd0dea3bb3720c4051c488cf50000000006b483045022100bc7fc6ace1a5b1ab8601599d56b3adad4a11b7f11757f3225e96b46ca1ab7f7c0220324d6074aa987a7c63c404ac5b03c26e55d3c4209e298b4ca9df0e90aca43ef3412103ee05b34332b5662830c600b73f9c908bb8bff1813bc9b2690e9cad00fad23d3cffffffff08c461a39a8877db46472f5cc59e5a108e417b1c9ea3091b71b65346d218f471000000006b483045022100a936c496423ec03b1ad0f3bfe2348572d7b29ab14e4435c0c8e2ee093d930fde02203d9e86647ea18043c150289f74c6cf2ceb9ca3b228ae31c7b19c4eef813fb68d412103a19014bcc672ccdf18abb6972dd699367baed89c29b704385253ce2ae0eddad5ffffffff2256c94d07451664749e440f55cec8a37da1c46cf30a97579e2f9696b84ad484000000006b48304502210091b0bcf2e84d9ee65de437e8396b379941345e4cffac331af2ae29b8a16968a602205a00eed18a7ffe36f59ae6eb477d9002324cfc249c875260e6ade5bce852692d4121021446bd1df2b61952088a22a516550e43cd95e47ca2a778822d21268bd8b1cebeffffffff02c4090000000000001976a91497ebeffef6d9dd88ffbce922f1df97cbcd7f88d388ac42000000000000001976a91449457f2c101859d1c8ff90096385d3cc30e5488388ac0000000000", - 3: "0100beef02fea6790c000f02fda82c000703145efd05fec7c2edc1827ec685755a2d05208486e9bc2461268e0f5e533bfda92c02cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e301fd551600b7b53a09331453b5966589dc473b45c87823f109417593a82ff8ffe7ddc6f96e01fd2b0b0080085c6f18f35d5f0a231eda36c1be3f031734bb6cb6987978ef2aad007ea6da01fd940500d26d24b44097ed3f0c2927413dd2f1fb78bba948803abdb7f2fb51d9807a77bc01fdcb0200bad82dfd55455709713ea8390a7c76be5c076da9cd487b75f558728ef8572b7601fd64010022553e159788764f6b3c1e27324f99a13abee9c7069ce34b8a4fcabc45b7aabb01b300debfa516f54f4331ddf74067403d8a70915973e8300c298ff8dbe5bdcc94768101580079a933dbaeaee5dfb6af1ce9bd7a9ef40584e1844a938e398d19f94aba525bed012d005287b1c986e6495d00c103082618d9e0a30c0c620fd2d232eb9c31d59dec510e01170074639f74ebdaaac679e8fed504ae62a6f42c633a549a99e21940aff14e69ab08010a008eb3fde752d9e5c8e67c26daa95f8b9480ec11e608de7afae04eb19775f42342010400eea8e24927e93a0cf9ca50b315e48efca1f0b77643f7110c6efd94220dcedab6010300682c5f78d5f89e5e5d13619f543aacb00d1e4e85043f4d453bd6f6eb14755e790100006df75ca701801279bcb9b91579ac74b2131913ba35a0c3cb313c49062c8f453f0101009fe83bf3febfc2af4641b9bc8c1f76ff9b2ae7de9f4ab5374e672ac70cf8b9b9fe73780c000c02cc005d2fa529262bd8c5451e2e9e91ff0cffa1100b144bddbb537db6cef3868d68cccd02a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb016700672bf5eb61032d3a9404ff6b2045a7ee3444a0612dd5f810fbf8ea363aae0b0b013200f62a9a8d99f570bfa8eb48ef0da91c84ab8110f826f67b72db053513b78baf430118000fec193c7d2ab70f1c74b7b2f4413db12a2da66cf40630ea0e57884d885dbf18010d0027f259788828eb8461da4e67e89c321c14be138576b039663da2dd84f84bcffc010700ebf4dd6d5eb43dcf25ef8025b17072ad3de6db031866c94570d86696bdb9533301020068d3cb01545e515692b45db66d8cf9f5020b0473fda254b2adc45308eee8db3801000072913db72c489f740085cb7aabe3c31271a2ea908b0c8498a75e89cab82a54c0010100491b7787ad57f9b034c950466026e844826a31d1227131dd54ffe965b3612bc20101001f83fda6372ec5bc5f4fb776506ebc01c14841d5173c4444cc3f37ab0539e00d010100cebf33ed7c7afce3c7c873d863ebccfc98a9450cefef843789c3683ab4dbf2c1010100dc365f1d99781e5053f678bb9e06d4a17bc6ed9a2258e2c69a2908800e4a7f2a04010000000150965003ea3d2c08bc79b116c9ffe7e730c9f9cf0a61e3df07868b24eac6f8d3000000006b4830450221009d3489f9e76ff3b043708972c52f85519e50a5fc35563d405e04b668780bf2ba0220024188508fc9c6870b2fc4f40b9484ae4163481199a5b4a7a338b86ec8952fee4121036a8b9d796ce2dee820d1f6d7a6ba07037dab4758f16028654fe4bc3a5c430b40ffffffff022a200000000000001976a91484c73348a8fbbc44cfa34f8f5441fc104f3bc78588ac162f0000000000001976a914590b1df63948c2c4e7a12a6e52012b36e25daa9888ac0000000001010100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000000100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac0000000001000100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac0000000000", + 3: "0100beef02fe73780c000c02cc005d2fa529262bd8c5451e2e9e91ff0cffa1100b144bddbb537db6cef3868d68cccd02a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb016700672bf5eb61032d3a9404ff6b2045a7ee3444a0612dd5f810fbf8ea363aae0b0b013200f62a9a8d99f570bfa8eb48ef0da91c84ab8110f826f67b72db053513b78baf430118000fec193c7d2ab70f1c74b7b2f4413db12a2da66cf40630ea0e57884d885dbf18010d0027f259788828eb8461da4e67e89c321c14be138576b039663da2dd84f84bcffc010700ebf4dd6d5eb43dcf25ef8025b17072ad3de6db031866c94570d86696bdb9533301020068d3cb01545e515692b45db66d8cf9f5020b0473fda254b2adc45308eee8db3801000072913db72c489f740085cb7aabe3c31271a2ea908b0c8498a75e89cab82a54c0010100491b7787ad57f9b034c950466026e844826a31d1227131dd54ffe965b3612bc20101001f83fda6372ec5bc5f4fb776506ebc01c14841d5173c4444cc3f37ab0539e00d010100cebf33ed7c7afce3c7c873d863ebccfc98a9450cefef843789c3683ab4dbf2c1010100dc365f1d99781e5053f678bb9e06d4a17bc6ed9a2258e2c69a2908800e4a7f2afea6790c000f02fda82c000703145efd05fec7c2edc1827ec685755a2d05208486e9bc2461268e0f5e533bfda92c02cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e301fd551600b7b53a09331453b5966589dc473b45c87823f109417593a82ff8ffe7ddc6f96e01fd2b0b0080085c6f18f35d5f0a231eda36c1be3f031734bb6cb6987978ef2aad007ea6da01fd940500d26d24b44097ed3f0c2927413dd2f1fb78bba948803abdb7f2fb51d9807a77bc01fdcb0200bad82dfd55455709713ea8390a7c76be5c076da9cd487b75f558728ef8572b7601fd64010022553e159788764f6b3c1e27324f99a13abee9c7069ce34b8a4fcabc45b7aabb01b300debfa516f54f4331ddf74067403d8a70915973e8300c298ff8dbe5bdcc94768101580079a933dbaeaee5dfb6af1ce9bd7a9ef40584e1844a938e398d19f94aba525bed012d005287b1c986e6495d00c103082618d9e0a30c0c620fd2d232eb9c31d59dec510e01170074639f74ebdaaac679e8fed504ae62a6f42c633a549a99e21940aff14e69ab08010a008eb3fde752d9e5c8e67c26daa95f8b9480ec11e608de7afae04eb19775f42342010400eea8e24927e93a0cf9ca50b315e48efca1f0b77643f7110c6efd94220dcedab6010300682c5f78d5f89e5e5d13619f543aacb00d1e4e85043f4d453bd6f6eb14755e790100006df75ca701801279bcb9b91579ac74b2131913ba35a0c3cb313c49062c8f453f0101009fe83bf3febfc2af4641b9bc8c1f76ff9b2ae7de9f4ab5374e672ac70cf8b9b904010000000150965003ea3d2c08bc79b116c9ffe7e730c9f9cf0a61e3df07868b24eac6f8d3000000006b4830450221009d3489f9e76ff3b043708972c52f85519e50a5fc35563d405e04b668780bf2ba0220024188508fc9c6870b2fc4f40b9484ae4163481199a5b4a7a338b86ec8952fee4121036a8b9d796ce2dee820d1f6d7a6ba07037dab4758f16028654fe4bc3a5c430b40ffffffff022a200000000000001976a91484c73348a8fbbc44cfa34f8f5441fc104f3bc78588ac162f0000000000001976a914590b1df63948c2c4e7a12a6e52012b36e25daa9888ac0000000001000100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000000100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac0000000001010100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac0000000000", + 4: "0100beef02fedd770c000b025a0250965003ea3d2c08bc79b116c9ffe7e730c9f9cf0a61e3df07868b24eac6f8d35b00d23a2585c30d0aa45dc2da97ea2da65d6e8a89600d719ada673861ab65ad525b012c000c4ffef5e63cde231502a66acf392337114b6daea97ca2f7ca5cf6a7be38c384011700dd80513a91875591df2265dae8cdc909a01113459ad6d8d9a5c051e092f26058010a0078ef0896fb0077e619d1a4942ba6272b23ff2f48d345826aaf6393688ab03f6301040052ebb99aa1adce1852250a49fd22e18a1798d08840eb13c3b8b1eb0c13bb804b010300cf10a22ebde10e1d98515d5288305e027e3cc0a28cf24c8b3c22a3dd35033dcf0100005bd338bdd23417971c877571c083b8f7fb2a7c0b179b77bed1ab30252c46c7990101007f673d90f535d5875ba5694d23d2a9653b4ad4ca3ef4e61b1b282654e3c7669a0101005b544e50fd2f878b1d7cc0b5c7b63377552e4ad7f01e5dada39984016141e3400101008713a257ca2ba89608cc62133eb6a3eeab15aec4f2e3d7b1a23e4d92d7b1e6dc0101007ccb140e81af58f6a301bd798024ee9515a53094c58b32dab99c18972b4efee1fea6790c000f02fda82c000703145efd05fec7c2edc1827ec685755a2d05208486e9bc2461268e0f5e533bfda92c02cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e301fd551600b7b53a09331453b5966589dc473b45c87823f109417593a82ff8ffe7ddc6f96e01fd2b0b0080085c6f18f35d5f0a231eda36c1be3f031734bb6cb6987978ef2aad007ea6da01fd940500d26d24b44097ed3f0c2927413dd2f1fb78bba948803abdb7f2fb51d9807a77bc01fdcb0200bad82dfd55455709713ea8390a7c76be5c076da9cd487b75f558728ef8572b7601fd64010022553e159788764f6b3c1e27324f99a13abee9c7069ce34b8a4fcabc45b7aabb01b300debfa516f54f4331ddf74067403d8a70915973e8300c298ff8dbe5bdcc94768101580079a933dbaeaee5dfb6af1ce9bd7a9ef40584e1844a938e398d19f94aba525bed012d005287b1c986e6495d00c103082618d9e0a30c0c620fd2d232eb9c31d59dec510e01170074639f74ebdaaac679e8fed504ae62a6f42c633a549a99e21940aff14e69ab08010a008eb3fde752d9e5c8e67c26daa95f8b9480ec11e608de7afae04eb19775f42342010400eea8e24927e93a0cf9ca50b315e48efca1f0b77643f7110c6efd94220dcedab6010300682c5f78d5f89e5e5d13619f543aacb00d1e4e85043f4d453bd6f6eb14755e790100006df75ca701801279bcb9b91579ac74b2131913ba35a0c3cb313c49062c8f453f0101009fe83bf3febfc2af4641b9bc8c1f76ff9b2ae7de9f4ab5374e672ac70cf8b9b9050100000002787a565270ec00b1bf6ed20100223176656705dc0cfe5ef9d1810ca6569f12d1020000006a47304402203cfe36be7ff5c2ac939bb6a625e4a1226be242f1f9950672b5f696ec58a3358902202a48d6c6e81e5950dc49d0dd1a35b46fa8f919b109b0e7c05deaef3db6051890412102fb130326dbd7c43841cde467196e5f289b9d8596e237725df84f768468426d8bffffffff008d9db2a5c8c310e6394c24c1f3c23b3adbdd6ab4a719e917a4a0ed78768773020000006a473044022049c80385f7f69e8ba6039ebe84fe5e6578f4c3c83eb622442a96219c59ac1a750220317fe2b47838dff11f88d909732d0846eba20acff57cb357a3ff39b5a7b61b3741210322b79b40a759c485eac318eabba60a73a49ec3307ded79ba8c47204405bb2f3fffffffff05414f0000000000001976a91400414bcf2602f309171901d837b4a155adbfb5ce88ac50c30000000000001976a91489ef778cc07c77cce1ad3ff6274615afe15f20c088ac204e0000000000001976a914971b76df1dc6acf01e8e7d2f8bfb3c86e69bc64c88acef250000000000001976a9144b4a836b444d5ed8d245ddb1aa878908e36cd6b588ac9d860100000000001976a9144405da67e318e9cfd9d6ce9dffce27af5f60522888ac000000000100010000000150965003ea3d2c08bc79b116c9ffe7e730c9f9cf0a61e3df07868b24eac6f8d3000000006b4830450221009d3489f9e76ff3b043708972c52f85519e50a5fc35563d405e04b668780bf2ba0220024188508fc9c6870b2fc4f40b9484ae4163481199a5b4a7a338b86ec8952fee4121036a8b9d796ce2dee820d1f6d7a6ba07037dab4758f16028654fe4bc3a5c430b40ffffffff022a200000000000001976a91484c73348a8fbbc44cfa34f8f5441fc104f3bc78588ac162f0000000000001976a914590b1df63948c2c4e7a12a6e52012b36e25daa9888ac00000000000100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000000100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac0000000001010100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac0000000000", } diff --git a/beef_tx.go b/beef_tx.go index 4e1ef33a..425c31ab 100644 --- a/beef_tx.go +++ b/beef_tx.go @@ -30,7 +30,6 @@ func ToBeef(ctx context.Context, tx *Transaction, store TransactionGetter) (stri tx.draftTransaction.BUMPs, err = calculateMergedBUMP(bumpFactors) sortedTxs := kahnTopologicalSortTransactions(bumpBtFactors) beefHex, err := toBeefHex(ctx, tx, sortedTxs) - fmt.Printf("beefHex: %s\n", beefHex) if err != nil { return "", fmt.Errorf("ToBeef() error: %w", err) } diff --git a/beef_tx_test.go b/beef_tx_test.go index 04ddc4c7..0383152a 100644 --- a/beef_tx_test.go +++ b/beef_tx_test.go @@ -84,7 +84,7 @@ func Test_ToBeef_HappyPaths(t *testing.T) { { testID: 3, hexForProcessedTx: "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000", - name: "not all inputs are mined but all required ancestors are mined", + name: "not all inputs are mined but all required ancestors are mined - one level below inputs", ancestors: []*beefTestCaseAncestor{ { hex: "0100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac00000000", @@ -108,7 +108,45 @@ func Test_ToBeef_HappyPaths(t *testing.T) { }, }, receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", - outputValue: 3000, + outputValue: 3500, + expectedErrorMessage: "", + }, + { + testID: 4, + hexForProcessedTx: "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000", + name: "not all inputs are mined but all required ancestors are mined - two levels below inputs", + ancestors: []*beefTestCaseAncestor{ + { + hex: "0100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"817574","path":[[{"offset":"11432","hash":"3b535e0f8e266124bce9868420052d5a7585c67e82c1edc2c7fe05fd5e140307"},{"offset":"11433","hash":"e3642405a9d00efcefd1dc94e8ae94b6dfb66c8b35fb609ab594fc4f425335cb","txid":true}],[{"offset":"5717","hash":"6ef9c6dde7fff82fa893754109f12378c8453b47dc896596b5531433093ab5b7"}],[{"offset":"2859","hash":"daa67e00ad2aef787998b66cbb3417033fbec136da1e230a5f5df3186f5c0880"}],[{"offset":"1428","hash":"bc777a80d951fbf2b7bd3a8048a9bb78fbf1d23d4127290c3fed9740b4246dd2"}],[{"offset":"715","hash":"762b57f88e7258f5757b48cda96d075cbe767c0a39a83e7109574555fd2dd8ba"}],[{"offset":"356","hash":"bbaab745bcca4f8a4be39c06c7e9be3aa1994f32271e3c6b4f768897153e5522"}],[{"offset":"179","hash":"817694ccbde5dbf88f290c30e8735991708a3d406740f7dd31434ff516a5bfde"}],[{"offset":"88","hash":"ed5b52ba4af9198d398e934a84e18405f49e7abde91cafb6dfe5aeaedb33a979"}],[{"offset":"45","hash":"0e51ec9dd5319ceb32d2d20f620c0ca3e0d918260803c1005d49e686c9b18752"}],[{"offset":"23","hash":"08ab694ef1af4019e2999a543a632cf4a662ae04d5fee879c6aadaeb749f6374"}],[{"offset":"10","hash":"4223f47597b14ee0fa7ade08e611ec80948b5fa9da267ce6c8e5d952e7fdb38e"}],[{"offset":"4","hash":"b6dace0d2294fd6e0c11f74376b7f0a1fc8ee415b350caf90c3ae92749e2a8ee"}],[{"offset":"3","hash":"795e7514ebf6d63b454d3f04854e1e0db0ac3a549f61135d5e9ef8d5785f2c68"}],[{"offset":"0","hash":"3f458f2c06493c31cbc3a035ba131913b274ac7915b9b9bc79128001a75cf76d"}],[{"offset":"1","hash":"b9b9f80cc72a674e37b54a9fdee72a9bff761f8cbcb94146afc2bffef33be89f"}]]}`, + blockHeight: 817574, + }, + { + hex: "0100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000", + isMined: false, + bumpJSON: ``, + blockHeight: -1, + parents: []*beefTestCaseAncestor{ + { + hex: "010000000150965003ea3d2c08bc79b116c9ffe7e730c9f9cf0a61e3df07868b24eac6f8d3000000006b4830450221009d3489f9e76ff3b043708972c52f85519e50a5fc35563d405e04b668780bf2ba0220024188508fc9c6870b2fc4f40b9484ae4163481199a5b4a7a338b86ec8952fee4121036a8b9d796ce2dee820d1f6d7a6ba07037dab4758f16028654fe4bc3a5c430b40ffffffff022a200000000000001976a91484c73348a8fbbc44cfa34f8f5441fc104f3bc78588ac162f0000000000001976a914590b1df63948c2c4e7a12a6e52012b36e25daa9888ac00000000", + isMined: false, + bumpJSON: ``, + blockHeight: -1, + parents: []*beefTestCaseAncestor{ + { + hex: "0100000002787a565270ec00b1bf6ed20100223176656705dc0cfe5ef9d1810ca6569f12d1020000006a47304402203cfe36be7ff5c2ac939bb6a625e4a1226be242f1f9950672b5f696ec58a3358902202a48d6c6e81e5950dc49d0dd1a35b46fa8f919b109b0e7c05deaef3db6051890412102fb130326dbd7c43841cde467196e5f289b9d8596e237725df84f768468426d8bffffffff008d9db2a5c8c310e6394c24c1f3c23b3adbdd6ab4a719e917a4a0ed78768773020000006a473044022049c80385f7f69e8ba6039ebe84fe5e6578f4c3c83eb622442a96219c59ac1a750220317fe2b47838dff11f88d909732d0846eba20acff57cb357a3ff39b5a7b61b3741210322b79b40a759c485eac318eabba60a73a49ec3307ded79ba8c47204405bb2f3fffffffff05414f0000000000001976a91400414bcf2602f309171901d837b4a155adbfb5ce88ac50c30000000000001976a91489ef778cc07c77cce1ad3ff6274615afe15f20c088ac204e0000000000001976a914971b76df1dc6acf01e8e7d2f8bfb3c86e69bc64c88acef250000000000001976a9144b4a836b444d5ed8d245ddb1aa878908e36cd6b588ac9d860100000000001976a9144405da67e318e9cfd9d6ce9dffce27af5f60522888ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"817117","path":[[{"offset":"90","hash":"d3f8c6ea248b8607dfe3610acff9c930e7e7ffc916b179bc082c3dea03509650","txid":true},{"offset":"91","hash":"5b52ad65ab613867da9a710d60898a6e5da62dea97dac25da40a0dc385253ad2"}],[{"offset":"44","hash":"84c338bea7f65ccaf7a27ca9ae6d4b11372339cf6aa6021523de3ce6f5fe4f0c"}],[{"offset":"23","hash":"5860f292e051c0a5d9d8d69a451311a009c9cde8da6522df915587913a5180dd"}],[{"offset":"10","hash":"633fb08a689363af6a8245d3482fff232b27a62b94a4d119e67700fb9608ef78"}],[{"offset":"4","hash":"4b80bb130cebb1b8c313eb4088d098178ae122fd490a255218ceada19ab9eb52"}],[{"offset":"3","hash":"cf3d0335dda3223c8b4cf28ca2c03c7e025e3088525d51981d0ee1bd2ea210cf"}],[{"offset":"0","hash":"99c7462c2530abd1be779b170b7c2afbf7b883c07175871c971734d2bd38d35b"}],[{"offset":"1","hash":"9a66c7e35426281b1be6f43ecad44a3b65a9d2234d69a55b87d535f5903d677f"}],[{"offset":"1","hash":"40e34161018499a3ad5d1ef0d74a2e557733b6c7b5c07c1d8b872ffd504e545b"}],[{"offset":"1","hash":"dce6b1d7924d3ea2b1d7e3f2c4ae15abeea3b63e1362cc0896a82bca57a21387"}],[{"offset":"1","hash":"e1fe4e2b97189cb9da328bc59430a51595ee248079bd01a3f658af810e14cb7c"}]]}`, + blockHeight: 817117, + }, + }, + }, + }, + }, + }, + receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", + outputValue: 4500, expectedErrorMessage: "", }, } @@ -130,14 +168,120 @@ func Test_ToBeef_HappyPaths(t *testing.T) { result, err := ToBeef(ctx, newTx, store) // then - assert.Equal(t, expectedBeefHex[tc.testID], result) - // happy paths, should not return error assert.NoError(t, nil, err) }) } } +func Test_ToBeef_ErrorPaths(t *testing.T) { + testCases := []beefTestCase{ + { + hexForProcessedTx: "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000", + name: "one input is mined and properly stored, the second one is missing in the store - should return error", + ancestors: []*beefTestCaseAncestor{ + { + hex: "0100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac00000000", + isMined: false, + bumpJSON: ``, + blockHeight: -1, + }, + { + hex: "0100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000", + isMined: false, + bumpJSON: ``, + blockHeight: -1, + }, + }, + receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", + outputValue: 3000, + expectedErrorMessage: "prepareBUMPFactors() error: cannot get parent transaction by ID (tx.ID: ee337f429d96fb92b5227dbb9a73ecb63e1cc69d0790f58cd58ed5dc3a9ec3cf). Reason: no records found", + }, + { + hexForProcessedTx: "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000", + name: "inputs not mined - no parents in store - should return error", + ancestors: []*beefTestCaseAncestor{ + { + hex: "0100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"817574","path":[[{"offset":"11432","hash":"3b535e0f8e266124bce9868420052d5a7585c67e82c1edc2c7fe05fd5e140307"},{"offset":"11433","hash":"e3642405a9d00efcefd1dc94e8ae94b6dfb66c8b35fb609ab594fc4f425335cb","txid":true}],[{"offset":"5717","hash":"6ef9c6dde7fff82fa893754109f12378c8453b47dc896596b5531433093ab5b7"}],[{"offset":"2859","hash":"daa67e00ad2aef787998b66cbb3417033fbec136da1e230a5f5df3186f5c0880"}],[{"offset":"1428","hash":"bc777a80d951fbf2b7bd3a8048a9bb78fbf1d23d4127290c3fed9740b4246dd2"}],[{"offset":"715","hash":"762b57f88e7258f5757b48cda96d075cbe767c0a39a83e7109574555fd2dd8ba"}],[{"offset":"356","hash":"bbaab745bcca4f8a4be39c06c7e9be3aa1994f32271e3c6b4f768897153e5522"}],[{"offset":"179","hash":"817694ccbde5dbf88f290c30e8735991708a3d406740f7dd31434ff516a5bfde"}],[{"offset":"88","hash":"ed5b52ba4af9198d398e934a84e18405f49e7abde91cafb6dfe5aeaedb33a979"}],[{"offset":"45","hash":"0e51ec9dd5319ceb32d2d20f620c0ca3e0d918260803c1005d49e686c9b18752"}],[{"offset":"23","hash":"08ab694ef1af4019e2999a543a632cf4a662ae04d5fee879c6aadaeb749f6374"}],[{"offset":"10","hash":"4223f47597b14ee0fa7ade08e611ec80948b5fa9da267ce6c8e5d952e7fdb38e"}],[{"offset":"4","hash":"b6dace0d2294fd6e0c11f74376b7f0a1fc8ee415b350caf90c3ae92749e2a8ee"}],[{"offset":"3","hash":"795e7514ebf6d63b454d3f04854e1e0db0ac3a549f61135d5e9ef8d5785f2c68"}],[{"offset":"0","hash":"3f458f2c06493c31cbc3a035ba131913b274ac7915b9b9bc79128001a75cf76d"}],[{"offset":"1","hash":"b9b9f80cc72a674e37b54a9fdee72a9bff761f8cbcb94146afc2bffef33be89f"}]]}`, + blockHeight: 817574, + }, + { + hex: "0100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000", + isMined: false, + bumpJSON: ``, + blockHeight: -1, + doNotAddToStore: true, + }, + }, + receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", + outputValue: 3000, + expectedErrorMessage: "prepareBUMPFactors() error: cannot get transaction by ID (tx.ID: 04f01fd4cef5a7bcb6328b08133094e7b9f6feb4b856f46123168de6b4bc4f62). Reason: no records found", + }, + { + hexForProcessedTx: "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000", + name: "last ancestor has corrupted hex in database - should return error", + ancestors: []*beefTestCaseAncestor{ + { + hex: "0100000001cfc39e3adcd58ed58cf590079dc61c3eb6ec739abb7d22b592fb969d427f33ee000000006a4730440220253e674e64028459457d55b444f5f3dc15c658425e3184c628016739e4921fd502207c8fe20eb34e55e4115fbd82c23878b4e54f01f6c6ad0811282dd0b1df863b5e41210310a4366fd997127ad972b14c56ca2e18f39ca631ac9e3e4ad3d9827865d0cc70ffffffff0264000000000000001976a914668a92ff9cb5785eb8fc044771837a0818b028b588acdc4e0000000000001976a914b073264927a61cf84327dea77414df6c28b11e5988ac00000000", + isMined: true, + bumpJSON: `{"blockHeight":"817574","path":[[{"offset":"11432","hash":"3b535e0f8e266124bce9868420052d5a7585c67e82c1edc2c7fe05fd5e140307"},{"offset":"11433","hash":"e3642405a9d00efcefd1dc94e8ae94b6dfb66c8b35fb609ab594fc4f425335cb","txid":true}],[{"offset":"5717","hash":"6ef9c6dde7fff82fa893754109f12378c8453b47dc896596b5531433093ab5b7"}],[{"offset":"2859","hash":"daa67e00ad2aef787998b66cbb3417033fbec136da1e230a5f5df3186f5c0880"}],[{"offset":"1428","hash":"bc777a80d951fbf2b7bd3a8048a9bb78fbf1d23d4127290c3fed9740b4246dd2"}],[{"offset":"715","hash":"762b57f88e7258f5757b48cda96d075cbe767c0a39a83e7109574555fd2dd8ba"}],[{"offset":"356","hash":"bbaab745bcca4f8a4be39c06c7e9be3aa1994f32271e3c6b4f768897153e5522"}],[{"offset":"179","hash":"817694ccbde5dbf88f290c30e8735991708a3d406740f7dd31434ff516a5bfde"}],[{"offset":"88","hash":"ed5b52ba4af9198d398e934a84e18405f49e7abde91cafb6dfe5aeaedb33a979"}],[{"offset":"45","hash":"0e51ec9dd5319ceb32d2d20f620c0ca3e0d918260803c1005d49e686c9b18752"}],[{"offset":"23","hash":"08ab694ef1af4019e2999a543a632cf4a662ae04d5fee879c6aadaeb749f6374"}],[{"offset":"10","hash":"4223f47597b14ee0fa7ade08e611ec80948b5fa9da267ce6c8e5d952e7fdb38e"}],[{"offset":"4","hash":"b6dace0d2294fd6e0c11f74376b7f0a1fc8ee415b350caf90c3ae92749e2a8ee"}],[{"offset":"3","hash":"795e7514ebf6d63b454d3f04854e1e0db0ac3a549f61135d5e9ef8d5785f2c68"}],[{"offset":"0","hash":"3f458f2c06493c31cbc3a035ba131913b274ac7915b9b9bc79128001a75cf76d"}],[{"offset":"1","hash":"b9b9f80cc72a674e37b54a9fdee72a9bff761f8cbcb94146afc2bffef33be89f"}]]}`, + blockHeight: 817574, + }, + { + hex: "0100000001a114c7deb8deba851d87755aa10aa18c97bd77afee4e1bad01d1c50e07a644eb010000006a473044022041abd4f93bd1db1d0097f2d467ae183801d7842d23d0605fa9568040d245167402201be66c96bef4d6d051304f6df2aecbdfe23a8a05af0908ef2117ab5388d8903c412103c08545a40c819f6e50892e31e792d221b6df6da96ebdba9b6fe39305cc6cc768ffffffff0263040000000000001976a91454097d9d921f9a1f55084a943571d868552e924f88acb22a0000000000001976a914c36b3fca5159231033f3fbdca1cde942096d379f88ac00000000", + isMined: false, + bumpJSON: ``, + blockHeight: -1, + parents: []*beefTestCaseAncestor{ + { + hex: "010000000150965003ea3d2c08bc79b116c9ffe7e730c9f9cf0a61e3df07868b24eac6f8d3000000006b4830450221009d3489f9e76ff3b043708972c52f85519e50a5fc35563d405e04b668780bf2ba0220024188508fc9c6870b2fc4f40b9484ae4163481199a5b4a7a338b86ec8952fee4121036a8b9d796ce2dee820d1f6d7a6ba07037dab4758f16028654fe4bc3a5c430b40ffffffff022a200000000000001976a91484c73348a8fbbc44cfa34f8f5441fc104f3bc78588ac162f0000000000001976a914590b1df63948c2c4e7a12a6e52012b36e25daa9888ac00000000", + isMined: false, + bumpJSON: ``, + blockHeight: -1, + parents: []*beefTestCaseAncestor{ + { + hex: "0100000002787a565270ec00b1bf6ed20100223176656705dc0cfe5ef9d1810ca6569f12d1020000006a47304402203cfe36be7ff5c2ac939bb6a625e4a1226be242f1f9950672b5f696ec58a3358902202a48d6c6e81e5950dc49d0dd1a35b46fa8f919b109b0e7c05deaef3db6051890412102fb130326dbd7c43841cde467196e5f289b9d8596e237725df84f768468426d8bffffffff008d9db2a5c8c310e6394c24c1f3c23b3adbdd6ab4a719e917a4a0ed78768773020000006a473044022049c80385f7f69e8ba6039ebe84fe5e6578f4c3c83eb622442a96219c59ac1a750220317fe2b47838dff11f88d909732d0846eba20acff57cb357a3ff39b5a7b61b3741210322b79b40a759c485eac318eabba60a73a49ec3307ded79ba8c47204405bb2f3fffffffff05414f0000000000001976a91400414bcf2602f309171901d837b4a155adbfb5ce88ac50c30000000000001976a91489ef778cc07c77cce1ad3ff6274615afe15f20c088ac204e0000000000001976a914971b76df1dc6acf01e8e7d2f8bfb3c86e69bc64c88acef250000000000001976a9144b4a836b444d5ed8d245ddb1aa87890", + isMined: true, + bumpJSON: `{"blockHeight":"817117","path":[[{"offset":"90","hash":"d3f8c6ea248b8607dfe3610acff9c930e7e7ffc916b179bc082c3dea03509650","txid":true},{"offset":"91","hash":"5b52ad65ab613867da9a710d60898a6e5da62dea97dac25da40a0dc385253ad2"}],[{"offset":"44","hash":"84c338bea7f65ccaf7a27ca9ae6d4b11372339cf6aa6021523de3ce6f5fe4f0c"}],[{"offset":"23","hash":"5860f292e051c0a5d9d8d69a451311a009c9cde8da6522df915587913a5180dd"}],[{"offset":"10","hash":"633fb08a689363af6a8245d3482fff232b27a62b94a4d119e67700fb9608ef78"}],[{"offset":"4","hash":"4b80bb130cebb1b8c313eb4088d098178ae122fd490a255218ceada19ab9eb52"}],[{"offset":"3","hash":"cf3d0335dda3223c8b4cf28ca2c03c7e025e3088525d51981d0ee1bd2ea210cf"}],[{"offset":"0","hash":"99c7462c2530abd1be779b170b7c2afbf7b883c07175871c971734d2bd38d35b"}],[{"offset":"1","hash":"9a66c7e35426281b1be6f43ecad44a3b65a9d2234d69a55b87d535f5903d677f"}],[{"offset":"1","hash":"40e34161018499a3ad5d1ef0d74a2e557733b6c7b5c07c1d8b872ffd504e545b"}],[{"offset":"1","hash":"dce6b1d7924d3ea2b1d7e3f2c4ae15abeea3b63e1362cc0896a82bca57a21387"}],[{"offset":"1","hash":"e1fe4e2b97189cb9da328bc59430a51595ee248079bd01a3f658af810e14cb7c"}]]}`, + blockHeight: 817117, + }, + }, + }, + }, + }, + }, + receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", + outputValue: 4500, + expectedErrorMessage: "prepareBUMPFactors() error: cannot get parent transaction by ID (tx.ID: d3f8c6ea248b8607dfe3610acff9c930e7e7ffc916b179bc082c3dea03509650). Reason: no records found", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // given + ctx, client, deferMe := initSimpleTestCase(t) + store := NewMockTransactionStore() + defer deferMe() + + var ancestors []*Transaction + for _, ancestor := range tc.ancestors { + ancestors = append(ancestors, addAncestor(ctx, ancestor, client, store, t)) + } + newTx := createProcessedTx(ctx, t, client, &tc, ancestors) + + // when + result, err := ToBeef(ctx, newTx, store) + + // then + assert.Equal(t, "", result) + assert.NotNil(t, err) + assert.Equal(t, tc.expectedErrorMessage, err.Error()) + }) + } +} + func createProcessedTx(ctx context.Context, t *testing.T, client ClientInterface, testCase *beefTestCase, ancestors []*Transaction) *Transaction { draftTx := newDraftTransaction( testXPub, &TransactionConfig{ From d8c5a1ae7287d3fe44bf77e36b90716f019f352f Mon Sep 17 00:00:00 2001 From: wregulski Date: Thu, 23 Nov 2023 11:34:24 +0100 Subject: [PATCH 08/12] feat: add method to call database once for multiple transactions --- action_transaction.go | 33 +++++++++++++++++++++++++++++++++ beef_tx_mock.go | 10 ++++++++++ interface.go | 1 + model_transactions.go | 1 + 4 files changed, 45 insertions(+) diff --git a/action_transaction.go b/action_transaction.go index 218ca51a..67a204a3 100644 --- a/action_transaction.go +++ b/action_transaction.go @@ -218,6 +218,25 @@ func (c *Client) GetTransactionByID(ctx context.Context, txID string) (*Transact return c.GetTransaction(ctx, "", txID) } +func (c *Client) GetTransactionsByIDs(ctx context.Context, txIDs []string) ([]*Transaction, error) { + // Check for existing NewRelic transaction + ctx = c.GetOrStartTxn(ctx, "get_transactions_by_ids") + + // Create the conditions + conditions := generateTxIdFilterConditions(txIDs) + + // Get the transactions by it's IDs + transactions, err := getTransactions( + ctx, nil, conditions, nil, + c.DefaultModelOptions()..., + ) + if err != nil { + return nil, err + } + + return transactions, nil +} + // GetTransactionByHex will get a transaction from the Datastore by its full hex string // uses GetTransaction func (c *Client) GetTransactionByHex(ctx context.Context, hex string) (*Transaction, error) { @@ -492,3 +511,17 @@ func (c *Client) RevertTransaction(ctx context.Context, id string) error { return err } + +func generateTxIdFilterConditions(txIDs []string) *map[string]interface{} { + orConditions := make([]map[string]interface{}, len(txIDs)) + + for i, txID := range txIDs { + orConditions[i] = map[string]interface{}{"id": txID} + } + + conditions := &map[string]interface{}{ + "$or": orConditions, + } + + return conditions +} diff --git a/beef_tx_mock.go b/beef_tx_mock.go index 5aa5fe40..c036bc9b 100644 --- a/beef_tx_mock.go +++ b/beef_tx_mock.go @@ -25,3 +25,13 @@ func (m *MockTransactionStore) GetTransactionByID(ctx context.Context, txID stri } return nil, fmt.Errorf("no records found") } + +func (m *MockTransactionStore) GetTransactionsByIDs(ctx context.Context, txIDs []string) ([]*Transaction, error) { + var txs []*Transaction + for _, txID := range txIDs { + if tx, exists := m.Transactions[txID]; exists { + txs = append(txs, tx) + } + } + return txs, nil +} diff --git a/interface.go b/interface.go index 2cffb355..d011d44d 100644 --- a/interface.go +++ b/interface.go @@ -133,6 +133,7 @@ type PaymailService interface { type TransactionService interface { GetTransaction(ctx context.Context, xPubID, txID string) (*Transaction, error) GetTransactionByID(ctx context.Context, txID string) (*Transaction, error) + GetTransactionsByIDs(ctx context.Context, txIDs []string) ([]*Transaction, error) GetTransactionByHex(ctx context.Context, hex string) (*Transaction, error) GetTransactions(ctx context.Context, metadata *Metadata, conditions *map[string]interface{}, queryParams *datastore.QueryParams, opts ...ModelOps) ([]*Transaction, error) diff --git a/model_transactions.go b/model_transactions.go index 2a330112..77cd1920 100644 --- a/model_transactions.go +++ b/model_transactions.go @@ -74,6 +74,7 @@ type Transaction struct { type TransactionGetter interface { GetTransactionByID(ctx context.Context, txID string) (*Transaction, error) + GetTransactionsByIDs(ctx context.Context, txIDs []string) ([]*Transaction, error) } // newTransactionBase creates the standard transaction model base From 0e784a12400e38ad59704f3283137448f73a7613 Mon Sep 17 00:00:00 2001 From: wregulski Date: Thu, 23 Nov 2023 12:09:07 +0100 Subject: [PATCH 09/12] feat: add getting batch transactions instead of one by one --- beef_bump.go | 59 +++++++++++++++++++++++++++++++++---------- beef_tx_mock.go | 8 ------ beef_tx_test.go | 6 ++--- model_transactions.go | 1 - 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/beef_bump.go b/beef_bump.go index f42bfc60..370945c3 100644 --- a/beef_bump.go +++ b/beef_bump.go @@ -62,12 +62,22 @@ func prepareBUMPFactors(ctx context.Context, tx *Transaction, store TransactionG return nil, nil, err } + var txIDs []string for _, input := range tx.draftTransaction.Configuration.Inputs { - inputTx, err := store.GetTransactionByID(ctx, input.UtxoPointer.TransactionID) - if err != nil { - return nil, nil, fmt.Errorf("cannot get transaction by ID (tx.ID: %s). Reason: %w", input.UtxoPointer.TransactionID, err) - } + txIDs = append(txIDs, input.UtxoPointer.TransactionID) + } + + inputTxs, err := store.GetTransactionsByIDs(ctx, txIDs) + if err != nil { + return nil, nil, fmt.Errorf("cannot get transactions from database: %w", err) + } + + if len(inputTxs) != len(txIDs) { + missingTxIDs := getMissingTxs(txIDs, inputTxs) + return nil, nil, fmt.Errorf("required transactions not found in database: %v", missingTxIDs) + } + for _, inputTx := range inputTxs { inputBtTx, err := bt.NewTxFromString(inputTx.Hex) if err != nil { return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) @@ -96,17 +106,27 @@ func checkParentTransactions(ctx context.Context, store TransactionGetter, input return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) } - var validTxs []*Transaction - var validBtTxs []*bt.Tx + var parentTxIDs []string for _, txIn := range btTx.Inputs { - parentTx, err := store.GetTransactionByID(ctx, txIn.PreviousTxIDStr()) - if err != nil { - return nil, nil, fmt.Errorf("cannot get parent transaction by ID (tx.ID: %s). Reason: %w", txIn.PreviousTxIDStr(), err) - } + parentTxIDs = append(parentTxIDs, txIn.PreviousTxIDStr()) + } + parentTxs, err := store.GetTransactionsByIDs(ctx, parentTxIDs) + if err != nil { + return nil, nil, fmt.Errorf("cannot get transactions from database: %w", err) + } + + if len(parentTxs) != len(parentTxIDs) { + missingTxIDs := getMissingTxs(parentTxIDs, parentTxs) + return nil, nil, fmt.Errorf("required transactions not found in database: %v", missingTxIDs) + } + + var validTxs []*Transaction + var validBtTxs []*bt.Tx + for _, parentTx := range parentTxs { parentBtTx, err := bt.NewTxFromString(parentTx.Hex) if err != nil { - return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) + return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", parentTx.ID, err) } validTxs = append(validTxs, parentTx) validBtTxs = append(validBtTxs, parentBtTx) @@ -121,11 +141,22 @@ func checkParentTransactions(ctx context.Context, store TransactionGetter, input } } - if len(validBtTxs) == 0 { - return nil, nil, fmt.Errorf("transaction is not mined yet and their parents are not present or mined (tx.ID: %s)", inputTx.ID) + return validBtTxs, validTxs, nil +} + +func getMissingTxs(txIDs []string, foundTxs []*Transaction) []string { + foundTxIDs := make(map[string]bool) + for _, tx := range foundTxs { + foundTxIDs[tx.ID] = true } - return validBtTxs, validTxs, nil + var missingTxIDs []string + for _, txID := range txIDs { + if !foundTxIDs[txID] { + missingTxIDs = append(missingTxIDs, txID) + } + } + return missingTxIDs } func initializeRequiredTxsCollection(tx *Transaction) ([]*bt.Tx, []*Transaction, error) { diff --git a/beef_tx_mock.go b/beef_tx_mock.go index c036bc9b..93ae46c6 100644 --- a/beef_tx_mock.go +++ b/beef_tx_mock.go @@ -2,7 +2,6 @@ package bux import ( "context" - "fmt" ) type MockTransactionStore struct { @@ -19,13 +18,6 @@ func (m *MockTransactionStore) AddToStore(tx *Transaction) { m.Transactions[tx.ID] = tx } -func (m *MockTransactionStore) GetTransactionByID(ctx context.Context, txID string) (*Transaction, error) { - if tx, exists := m.Transactions[txID]; exists { - return tx, nil - } - return nil, fmt.Errorf("no records found") -} - func (m *MockTransactionStore) GetTransactionsByIDs(ctx context.Context, txIDs []string) ([]*Transaction, error) { var txs []*Transaction for _, txID := range txIDs { diff --git a/beef_tx_test.go b/beef_tx_test.go index 0383152a..40bc6e2c 100644 --- a/beef_tx_test.go +++ b/beef_tx_test.go @@ -195,7 +195,7 @@ func Test_ToBeef_ErrorPaths(t *testing.T) { }, receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", outputValue: 3000, - expectedErrorMessage: "prepareBUMPFactors() error: cannot get parent transaction by ID (tx.ID: ee337f429d96fb92b5227dbb9a73ecb63e1cc69d0790f58cd58ed5dc3a9ec3cf). Reason: no records found", + expectedErrorMessage: "prepareBUMPFactors() error: required transactions not found in database: [ee337f429d96fb92b5227dbb9a73ecb63e1cc69d0790f58cd58ed5dc3a9ec3cf]", }, { hexForProcessedTx: "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000", @@ -217,7 +217,7 @@ func Test_ToBeef_ErrorPaths(t *testing.T) { }, receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", outputValue: 3000, - expectedErrorMessage: "prepareBUMPFactors() error: cannot get transaction by ID (tx.ID: 04f01fd4cef5a7bcb6328b08133094e7b9f6feb4b856f46123168de6b4bc4f62). Reason: no records found", + expectedErrorMessage: "prepareBUMPFactors() error: required transactions not found in database: [04f01fd4cef5a7bcb6328b08133094e7b9f6feb4b856f46123168de6b4bc4f62]", }, { hexForProcessedTx: "0100000002cb3553424ffc94b59a60fb358b6cb6dfb694aee894dcd1effc0ed0a9052464e3000000006a4730440220515c3bf93d38fa7cc164746fae4bec8b66c60a82509eb553751afa5971c3e41d0220321517fd5c997ab5f8ef0e59048ce9157de46f92b10d882bf898e62f3ee7343d4121038f1273fcb299405d8d140b4de9a2111ecb39291b2846660ebecd864d13bee575ffffffff624fbcb4e68d162361f456b8b4fef6b9e7943013088b32b6bca7f5ced41ff004010000006a47304402203fb24f6e00a6487cf88a3b39d8454786db63d649142ea76374c2f55990777e6302207fbb903d038cf43e13ffb496a64f36637ec7323e5ac48bb96bdb4a885100abca4121024b003d3cf49a8f48c1fe79b711b1d08e306c42a0ab8da004d97fccc4ced3343affffffff026f000000000000001976a914f232d38cd4c2f87c117af06542b04a7061b6640188aca62a0000000000001976a9146058e52d00e3b94211939f68cc2d9a3fc1e3db0f88ac00000000", @@ -254,7 +254,7 @@ func Test_ToBeef_ErrorPaths(t *testing.T) { }, receiverAddress: "1A1PjKqjWMNBzTVdcBru27EV1PHcXWc63W", outputValue: 4500, - expectedErrorMessage: "prepareBUMPFactors() error: cannot get parent transaction by ID (tx.ID: d3f8c6ea248b8607dfe3610acff9c930e7e7ffc916b179bc082c3dea03509650). Reason: no records found", + expectedErrorMessage: "prepareBUMPFactors() error: required transactions not found in database: [d3f8c6ea248b8607dfe3610acff9c930e7e7ffc916b179bc082c3dea03509650]", }, } diff --git a/model_transactions.go b/model_transactions.go index 77cd1920..e0af5923 100644 --- a/model_transactions.go +++ b/model_transactions.go @@ -73,7 +73,6 @@ type Transaction struct { } type TransactionGetter interface { - GetTransactionByID(ctx context.Context, txID string) (*Transaction, error) GetTransactionsByIDs(ctx context.Context, txIDs []string) ([]*Transaction, error) } From fa9dae4b2f954359a64275144d50f06964ce68e9 Mon Sep 17 00:00:00 2001 From: wregulski Date: Thu, 23 Nov 2023 14:03:45 +0100 Subject: [PATCH 10/12] fix: pull request changes --- beef_bump.go | 13 ++++--------- beef_tx.go | 16 ++++++++-------- model_draft_transactions.go | 1 - 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/beef_bump.go b/beef_bump.go index 370945c3..4222a8d2 100644 --- a/beef_bump.go +++ b/beef_bump.go @@ -56,7 +56,7 @@ func validateBumps(bumps BUMPs) error { return nil } -func prepareBUMPFactors(ctx context.Context, tx *Transaction, store TransactionGetter) ([]*bt.Tx, []*Transaction, error) { +func prepareBEEFFactors(ctx context.Context, tx *Transaction, store TransactionGetter) ([]*bt.Tx, []*Transaction, error) { btTxsNeededForBUMP, txsNeededForBUMP, err := initializeRequiredTxsCollection(tx) if err != nil { return nil, nil, err @@ -87,7 +87,7 @@ func prepareBUMPFactors(ctx context.Context, tx *Transaction, store TransactionG btTxsNeededForBUMP = append(btTxsNeededForBUMP, inputBtTx) if inputTx.BUMP.BlockHeight == 0 && len(inputTx.BUMP.Path) == 0 { - parentBtTransactions, parentTransactions, err := checkParentTransactions(ctx, store, inputTx) + parentBtTransactions, parentTransactions, err := checkParentTransactions(ctx, store, inputBtTx) if err != nil { return nil, nil, err } @@ -100,12 +100,7 @@ func prepareBUMPFactors(ctx context.Context, tx *Transaction, store TransactionG return btTxsNeededForBUMP, txsNeededForBUMP, nil } -func checkParentTransactions(ctx context.Context, store TransactionGetter, inputTx *Transaction) ([]*bt.Tx, []*Transaction, error) { - btTx, err := bt.NewTxFromString(inputTx.Hex) - if err != nil { - return nil, nil, fmt.Errorf("cannot convert to bt.Tx from hex (tx.ID: %s). Reason: %w", inputTx.ID, err) - } - +func checkParentTransactions(ctx context.Context, store TransactionGetter, btTx *bt.Tx) ([]*bt.Tx, []*Transaction, error) { var parentTxIDs []string for _, txIn := range btTx.Inputs { parentTxIDs = append(parentTxIDs, txIn.PreviousTxIDStr()) @@ -132,7 +127,7 @@ func checkParentTransactions(ctx context.Context, store TransactionGetter, input validBtTxs = append(validBtTxs, parentBtTx) if parentTx.BUMP.BlockHeight == 0 && len(parentTx.BUMP.Path) == 0 { - parentValidBtTxs, parentValidTxs, err := checkParentTransactions(ctx, store, parentTx) + parentValidBtTxs, parentValidTxs, err := checkParentTransactions(ctx, store, parentBtTx) if err != nil { return nil, nil, err } diff --git a/beef_tx.go b/beef_tx.go index 425c31ab..b8b60e69 100644 --- a/beef_tx.go +++ b/beef_tx.go @@ -22,14 +22,14 @@ func ToBeef(ctx context.Context, tx *Transaction, store TransactionGetter) (stri return "", err } - bumpBtFactors, bumpFactors, err := prepareBUMPFactors(ctx, tx, store) + bumpBtFactors, bumpFactors, err := prepareBEEFFactors(ctx, tx, store) if err != nil { return "", fmt.Errorf("prepareBUMPFactors() error: %w", err) } - tx.draftTransaction.BUMPs, err = calculateMergedBUMP(bumpFactors) + bumps, err := calculateMergedBUMP(bumpFactors) sortedTxs := kahnTopologicalSortTransactions(bumpBtFactors) - beefHex, err := toBeefHex(ctx, tx, sortedTxs) + beefHex, err := toBeefHex(ctx, bumps, sortedTxs) if err != nil { return "", fmt.Errorf("ToBeef() error: %w", err) } @@ -37,8 +37,8 @@ func ToBeef(ctx context.Context, tx *Transaction, store TransactionGetter) (stri return beefHex, nil } -func toBeefHex(ctx context.Context, tx *Transaction, parentTxs []*bt.Tx) (string, error) { - beef, err := newBeefTx(ctx, 1, tx, parentTxs) +func toBeefHex(ctx context.Context, bumps BUMPs, parentTxs []*bt.Tx) (string, error) { + beef, err := newBeefTx(ctx, 1, bumps, parentTxs) if err != nil { return "", fmt.Errorf("ToBeefHex() error: %w", err) } @@ -51,18 +51,18 @@ func toBeefHex(ctx context.Context, tx *Transaction, parentTxs []*bt.Tx) (string return hex.EncodeToString(beefBytes), nil } -func newBeefTx(ctx context.Context, version uint32, tx *Transaction, parentTxs []*bt.Tx) (*beefTx, error) { +func newBeefTx(ctx context.Context, version uint32, bumps BUMPs, parentTxs []*bt.Tx) (*beefTx, error) { if version > maxBeefVer { return nil, fmt.Errorf("version above 0x%X", maxBeefVer) } - if err := validateBumps(tx.draftTransaction.BUMPs); err != nil { + if err := validateBumps(bumps); err != nil { return nil, err } beef := &beefTx{ version: version, - bumps: tx.draftTransaction.BUMPs, + bumps: bumps, transactions: parentTxs, } diff --git a/model_draft_transactions.go b/model_draft_transactions.go index 1b644766..6d22c734 100644 --- a/model_draft_transactions.go +++ b/model_draft_transactions.go @@ -38,7 +38,6 @@ type DraftTransaction struct { Configuration TransactionConfig `json:"configuration" toml:"configuration" yaml:"configuration" gorm:"<-;type:text;comment:This is the configuration struct in JSON" bson:"configuration"` Status DraftStatus `json:"status" toml:"status" yaml:"status" gorm:"<-;type:varchar(10);index;comment:This is the status of the draft" bson:"status"` FinalTxID string `json:"final_tx_id,omitempty" toml:"final_tx_id" yaml:"final_tx_id" gorm:"<-;type:char(64);index;comment:This is the final tx ID" bson:"final_tx_id,omitempty"` - BUMPs BUMPs `json:"bumps,omitempty" toml:"bumps" yaml:"bumps" gorm:"<-;type:text;comment:Slice of BUMPs (BSV Unified Merkle Paths)" bson:"bumps,omitempty"` } // newDraftTransaction will start a new draft tx From aa79e40003b30da1615f322cfb859a3e9f89c2dc Mon Sep 17 00:00:00 2001 From: wregulski Date: Thu, 23 Nov 2023 14:20:37 +0100 Subject: [PATCH 11/12] fix: remove redundand func call --- beef_bump.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/beef_bump.go b/beef_bump.go index 4222a8d2..939d5b52 100644 --- a/beef_bump.go +++ b/beef_bump.go @@ -67,14 +67,9 @@ func prepareBEEFFactors(ctx context.Context, tx *Transaction, store TransactionG txIDs = append(txIDs, input.UtxoPointer.TransactionID) } - inputTxs, err := store.GetTransactionsByIDs(ctx, txIDs) + inputTxs, err := getRequiredTransactions(ctx, txIDs, store) if err != nil { - return nil, nil, fmt.Errorf("cannot get transactions from database: %w", err) - } - - if len(inputTxs) != len(txIDs) { - missingTxIDs := getMissingTxs(txIDs, inputTxs) - return nil, nil, fmt.Errorf("required transactions not found in database: %v", missingTxIDs) + return nil, nil, err } for _, inputTx := range inputTxs { @@ -106,14 +101,9 @@ func checkParentTransactions(ctx context.Context, store TransactionGetter, btTx parentTxIDs = append(parentTxIDs, txIn.PreviousTxIDStr()) } - parentTxs, err := store.GetTransactionsByIDs(ctx, parentTxIDs) + parentTxs, err := getRequiredTransactions(ctx, parentTxIDs, store) if err != nil { - return nil, nil, fmt.Errorf("cannot get transactions from database: %w", err) - } - - if len(parentTxs) != len(parentTxIDs) { - missingTxIDs := getMissingTxs(parentTxIDs, parentTxs) - return nil, nil, fmt.Errorf("required transactions not found in database: %v", missingTxIDs) + return nil, nil, err } var validTxs []*Transaction @@ -139,6 +129,20 @@ func checkParentTransactions(ctx context.Context, store TransactionGetter, btTx return validBtTxs, validTxs, nil } +func getRequiredTransactions(ctx context.Context, txIds []string, store TransactionGetter) ([]*Transaction, error) { + txs, err := store.GetTransactionsByIDs(ctx, txIds) + if err != nil { + return nil, fmt.Errorf("cannot get transactions from database: %w", err) + } + + if len(txs) != len(txIds) { + missingTxIDs := getMissingTxs(txIds, txs) + return nil, fmt.Errorf("required transactions not found in database: %v", missingTxIDs) + } + + return txs, nil +} + func getMissingTxs(txIDs []string, foundTxs []*Transaction) []string { foundTxIDs := make(map[string]bool) for _, tx := range foundTxs { From 9a7b689b649bd12fd2360fe03bd2f90ecd928695 Mon Sep 17 00:00:00 2001 From: wregulski Date: Fri, 24 Nov 2023 09:23:54 +0100 Subject: [PATCH 12/12] fix: removes old reference --- model_bump.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model_bump.go b/model_bump.go index 059fb1cc..9aa65cfb 100644 --- a/model_bump.go +++ b/model_bump.go @@ -35,7 +35,7 @@ type BUMPLeaf struct { Duplicate bool `json:"duplicate,omitempty"` } -// CalculateMergedBUMP calculates Merged BUMP from a slice of Merkle Proofs +// CalculateMergedBUMP calculates Merged BUMP from a slice of BUMPs func CalculateMergedBUMP(bumps []BUMP) (*BUMP, error) { if len(bumps) == 0 || bumps == nil { return nil, nil