diff --git a/action_transaction.go b/action_transaction.go index 74aaa3cd..158bfafd 100644 --- a/action_transaction.go +++ b/action_transaction.go @@ -11,17 +11,11 @@ import ( "github.com/BuxOrg/bux/utils" "github.com/bitcoin-sv/go-broadcast-client/broadcast" "github.com/libsv/go-bc" - "github.com/libsv/go-bt" + "github.com/libsv/go-bt/v2" "github.com/mrz1836/go-datastore" ) -// RecordTransaction will parse the transaction and save it into the Datastore -// -// Internal (known) transactions: there is a corresponding `draft_transaction` via `draft_id` -// External (known) transactions: there are output(s) related to the destination `reference_id`, tx is valid (mempool/on-chain) -// External (unknown) transactions: no reference id but some output(s) match known outputs, tx is valid (mempool/on-chain) -// Unknown transactions: no matching outputs, tx will be disregarded -// +// RecordTransaction will parse the outgoing transaction and save it into the Datastore // xPubKey is the raw public xPub // txHex is the raw transaction hex // draftID is the unique draft id from a previously started New() transaction (draft_transaction.ID) @@ -29,7 +23,12 @@ import ( func (c *Client) RecordTransaction(ctx context.Context, xPubKey, txHex, draftID string, opts ...ModelOps) (*Transaction, error) { ctx = c.GetOrStartTxn(ctx, "record_transaction") - rts, err := getRecordTxStrategy(ctx, c, xPubKey, txHex, draftID) + tx, err := bt.NewTxFromString(txHex) + if err != nil { + return nil, fmt.Errorf("invalid hex: %w", err) + } + + rts, err := getOutgoingTxRecordStrategy(xPubKey, tx, draftID) if err != nil { return nil, err } @@ -135,7 +134,7 @@ func (c *Client) GetTransactionByHex(ctx context.Context, hex string) (*Transact return nil, err } - return c.GetTransaction(ctx, "", tx.GetTxID()) + return c.GetTransaction(ctx, "", tx.TxID()) } // GetTransactions will get all the transactions from the Datastore diff --git a/chainstate/broadcast.go b/chainstate/broadcast.go index 552c8f22..fd28b0a4 100644 --- a/chainstate/broadcast.go +++ b/chainstate/broadcast.go @@ -45,7 +45,7 @@ var ( // // NOTE: if successful (in-mempool), no error will be returned // NOTE: function register the fastest successful broadcast into 'completeChannel' so client doesn't need to wait for other providers -func (c *Client) broadcast(ctx context.Context, id, hex string, timeout time.Duration, completeChannel, errorChannel chan string) { +func (c *Client) broadcast(ctx context.Context, id, hex string, format HexFormatFlag, timeout time.Duration, completeChannel, errorChannel chan string) { // Create a context (to cancel or timeout) ctxWithCancel, cancel := context.WithTimeout(ctx, timeout) defer cancel() @@ -55,7 +55,7 @@ func (c *Client) broadcast(ctx context.Context, id, hex string, timeout time.Dur resultsChannel := make(chan broadcastResult) status := newBroadcastStatus(completeChannel) - for _, broadcastProvider := range createActiveProviders(c, id, hex) { + for _, broadcastProvider := range createActiveProviders(c, id, hex, format) { wg.Add(1) go func(provider txBroadcastProvider) { defer wg.Done() @@ -85,11 +85,15 @@ func (c *Client) broadcast(ctx context.Context, id, hex string, timeout time.Dur } } -func createActiveProviders(c *Client, txID, txHex string) []txBroadcastProvider { - providers := make([]txBroadcastProvider, 0, 10) +func createActiveProviders(c *Client, txID, txHex string, format HexFormatFlag) []txBroadcastProvider { + providers := make([]txBroadcastProvider, 0) switch c.ActiveProvider() { case ProviderMinercraft: + if format != RawTx { + panic("MAPI doesn't support other broadcast format than RawTx") + } + for _, miner := range c.options.config.minercraftConfig.broadcastMiners { if miner == nil { continue @@ -99,7 +103,7 @@ func createActiveProviders(c *Client, txID, txHex string) []txBroadcastProvider providers = append(providers, &pvdr) } case ProviderBroadcastClient: - pvdr := broadcastClientProvider{txID: txID, txHex: txHex} + pvdr := broadcastClientProvider{txID: txID, txHex: txHex, format: format} providers = append(providers, &pvdr) default: c.options.logger.Warn().Msg("no active provider for broadcast") diff --git a/chainstate/broadcast_providers.go b/chainstate/broadcast_providers.go index e66bc419..2264db3a 100644 --- a/chainstate/broadcast_providers.go +++ b/chainstate/broadcast_providers.go @@ -22,11 +22,11 @@ type mapiBroadcastProvider struct { txID, txHex string } -func (provider mapiBroadcastProvider) getName() string { +func (provider *mapiBroadcastProvider) getName() string { return provider.miner.Name } -func (provider mapiBroadcastProvider) broadcast(ctx context.Context, c *Client) error { +func (provider *mapiBroadcastProvider) broadcast(ctx context.Context, c *Client) error { return broadcastMAPI(ctx, c, provider.miner, provider.txID, provider.txHex) } @@ -83,36 +83,46 @@ func emptyBroadcastResponseErr(txID string) error { // BroadcastClient provider type broadcastClientProvider struct { txID, txHex string + format HexFormatFlag } -func (provider broadcastClientProvider) getName() string { +func (provider *broadcastClientProvider) getName() string { return ProviderBroadcastClient } // Broadcast using BroadcastClient -func (provider broadcastClientProvider) broadcast(ctx context.Context, c *Client) error { - return broadcastWithBroadcastClient(ctx, c, provider.txID, provider.txHex) -} - -func broadcastWithBroadcastClient(ctx context.Context, client *Client, txID, hex string) error { - debugLog(client, txID, "executing broadcast request for "+ProviderBroadcastClient) +func (provider *broadcastClientProvider) broadcast(ctx context.Context, c *Client) error { + c.options.logger.Debug(). + Str("txID", provider.txID). + Msgf("executing broadcast request for %s", provider.getName()) tx := broadcast.Transaction{ - Hex: hex, + Hex: provider.txHex, + } + + formatOpt := broadcast.WithRawFormat() + if provider.format.Contains(Ef) { + formatOpt = broadcast.WithEfFormat() } - result, err := client.BroadcastClient().SubmitTransaction( + result, err := c.BroadcastClient().SubmitTransaction( ctx, &tx, - broadcast.WithRawFormat(), - broadcast.WithCallback(client.options.config.callbackURL, client.options.config.callbackToken), + formatOpt, + broadcast.WithCallback(c.options.config.callbackURL, c.options.config.callbackToken), ) + if err != nil { - debugLog(client, txID, "error broadcast request for "+ProviderBroadcastClient+" failed: "+err.Error()) + c.options.logger.Debug(). + Str("txID", provider.txID). + Msgf("error broadcast request for %s failed: %s", provider.getName(), err.Error()) + return err } - debugLog(client, txID, "result broadcast request for "+ProviderBroadcastClient+" blockhash: "+result.BlockHash+" status: "+result.TxStatus.String()) + c.options.logger.Debug(). + Str("txID", provider.txID). + Msgf("result broadcast request for %s blockhash: %s status: %s", provider.getName(), result.BlockHash, result.TxStatus.String()) return nil } diff --git a/chainstate/broadcast_test.go b/chainstate/broadcast_test.go index 27942abc..7fde17a7 100644 --- a/chainstate/broadcast_test.go +++ b/chainstate/broadcast_test.go @@ -39,7 +39,7 @@ func TestClient_Broadcast(t *testing.T) { // when provider, err := c.Broadcast( - context.Background(), "", onChainExample1TxHex, defaultBroadcastTimeOut, + context.Background(), "", onChainExample1TxHex, RawTx, defaultBroadcastTimeOut, ) // then @@ -55,7 +55,7 @@ func TestClient_Broadcast(t *testing.T) { // when provider, err := c.Broadcast( - context.Background(), onChainExample1TxID, "", defaultBroadcastTimeOut, + context.Background(), onChainExample1TxID, "", RawTx, defaultBroadcastTimeOut, ) // then @@ -78,7 +78,7 @@ func TestClient_Broadcast_MAPI(t *testing.T) { // when providers, err := c.Broadcast( - context.Background(), broadcastExample1TxID, broadcastExample1TxHex, defaultBroadcastTimeOut, + context.Background(), broadcastExample1TxID, broadcastExample1TxHex, RawTx, defaultBroadcastTimeOut, ) // then @@ -112,7 +112,7 @@ func TestClient_Broadcast_BroadcastClient(t *testing.T) { // when providers, err := c.Broadcast( - context.Background(), broadcastExample1TxID, broadcastExample1TxHex, defaultBroadcastTimeOut, + context.Background(), broadcastExample1TxID, broadcastExample1TxHex, RawTx, defaultBroadcastTimeOut, ) // then diff --git a/chainstate/chainstate.go b/chainstate/chainstate.go index 99452acc..d2bc0f90 100644 --- a/chainstate/chainstate.go +++ b/chainstate/chainstate.go @@ -9,8 +9,32 @@ import ( "time" ) +type HexFormatFlag byte + +const ( + RawTx HexFormatFlag = 1 + Ef HexFormatFlag = 3 +) + +func (flag HexFormatFlag) Contains(other HexFormatFlag) bool { + return (flag & other) == other +} + // Broadcast will attempt to broadcast a transaction using the given providers -func (c *Client) Broadcast(ctx context.Context, id, txHex string, timeout time.Duration) (string, error) { +func (c *Client) SupportedBroadcastFormats() HexFormatFlag { + switch c.ActiveProvider() { + case ProviderMinercraft: + return RawTx + + case ProviderBroadcastClient: + return RawTx + Ef + + default: + return RawTx + } +} + +func (c *Client) Broadcast(ctx context.Context, id, txHex string, format HexFormatFlag, timeout time.Duration) (string, error) { // Basic validation if len(id) < 50 { return "", ErrInvalidTransactionID @@ -18,15 +42,11 @@ func (c *Client) Broadcast(ctx context.Context, id, txHex string, timeout time.D return "", ErrInvalidTransactionHex } - // Debug the id and hex - c.DebugLog("tx_id: " + id) - c.DebugLog("tx_hex: " + txHex) - // Broadcast or die successCompleteCh := make(chan string) errorCh := make(chan string) - go c.broadcast(ctx, id, txHex, timeout, successCompleteCh, errorCh) + go c.broadcast(ctx, id, txHex, format, timeout, successCompleteCh, errorCh) // wait for first success success := <-successCompleteCh diff --git a/chainstate/interface.go b/chainstate/interface.go index 473b4c1c..a175ecfc 100644 --- a/chainstate/interface.go +++ b/chainstate/interface.go @@ -17,7 +17,8 @@ type HTTPInterface interface { // ChainService is the chain related methods type ChainService interface { - Broadcast(ctx context.Context, id, txHex string, timeout time.Duration) (string, error) + SupportedBroadcastFormats() HexFormatFlag + Broadcast(ctx context.Context, id, txHex string, format HexFormatFlag, timeout time.Duration) (string, error) QueryTransaction( ctx context.Context, id string, requiredIn RequiredIn, timeout time.Duration, ) (*TransactionInfo, error) diff --git a/chainstate/transaction.go b/chainstate/transaction.go index 4f018ed1..d67ad116 100644 --- a/chainstate/transaction.go +++ b/chainstate/transaction.go @@ -78,14 +78,14 @@ func (c *Client) fastestQuery(ctx context.Context, id string, requiredIn Require } case ProviderBroadcastClient: wg.Add(1) - go func(ctx context.Context, client *Client, id string, requiredIn RequiredIn) { + go func(ctx context.Context, client *Client, wg *sync.WaitGroup, id string, requiredIn RequiredIn) { defer wg.Done() if resp, err := queryBroadcastClient( ctx, client, id, ); err == nil && checkRequirementArc(requiredIn, id, resp) { resultsChannel <- resp } - }(ctxWithCancel, c, id, requiredIn) + }(ctxWithCancel, c, &wg, id, requiredIn) default: c.options.logger.Warn().Msg("no active provider for fastestQuery") } diff --git a/ef_tx.go b/ef_tx.go new file mode 100644 index 00000000..34a6709d --- /dev/null +++ b/ef_tx.go @@ -0,0 +1,68 @@ +package bux + +import ( + "context" + "encoding/hex" + + "github.com/libsv/go-bt/v2" +) + +func ToEfHex(ctx context.Context, tx *Transaction, store TransactionGetter) (efHex string, ok bool) { + btTx := tx.parsedTx + + if btTx == nil { + var err error + btTx, err = bt.NewTxFromString(tx.Hex) + if err != nil { + return "", false + } + } + + needToHydrate := false + for _, input := range btTx.Inputs { + if input.PreviousTxScript == nil { + needToHydrate = true + break + } + } + + if needToHydrate { + if ok := hydrate(ctx, btTx, store); !ok { + return "", false + } + } + + return hex.EncodeToString(btTx.ExtendedBytes()), true +} + +func hydrate(ctx context.Context, tx *bt.Tx, store TransactionGetter) (ok bool) { + txToGet := make([]string, 0, len(tx.Inputs)) + + for _, input := range tx.Inputs { + txToGet = append(txToGet, input.PreviousTxIDStr()) + } + + parentTxs, err := store.GetTransactionsByIDs(ctx, txToGet) + if err != nil { + return false + } + if len(parentTxs) != len(tx.Inputs) { + return false + } + + for _, input := range tx.Inputs { + prevTxId := input.PreviousTxIDStr() + pTx := find(parentTxs, func(tx *Transaction) bool { return tx.ID == prevTxId }) + + pbtTx, err := bt.NewTxFromString((*pTx).Hex) + if err != nil { + return false + } + + o := pbtTx.Outputs[input.PreviousTxOutIndex] + input.PreviousTxSatoshis = o.Satoshis + input.PreviousTxScript = o.LockingScript + } + + return true +} diff --git a/mock_chainstate_test.go b/mock_chainstate_test.go index 3180cfc1..6ff6ca44 100644 --- a/mock_chainstate_test.go +++ b/mock_chainstate_test.go @@ -66,7 +66,7 @@ type chainStateEverythingInMempool struct { chainStateBase } -func (c *chainStateEverythingInMempool) Broadcast(context.Context, string, string, time.Duration) (string, error) { +func (c *chainStateEverythingInMempool) Broadcast(context.Context, string, string, chainstate.HexFormatFlag, time.Duration) (string, error) { return "", nil } @@ -102,6 +102,10 @@ type chainStateEverythingOnChain struct { chainStateEverythingInMempool } +func (c *chainStateEverythingOnChain) SupportedBroadcastFormats() chainstate.HexFormatFlag { + return chainstate.RawTx +} + func (c *chainStateEverythingOnChain) BroadcastClient() broadcast.Client { return nil } diff --git a/model_transactions.go b/model_transactions.go index a39d2eb9..74128202 100644 --- a/model_transactions.go +++ b/model_transactions.go @@ -128,6 +128,15 @@ func newTransactionWithDraftID(txHex, draftID string, opts ...ModelOps) (*Transa return tx, nil } +func txFromBtTx(btTx *bt.Tx, opts ...ModelOps) *Transaction { + tx := emptyTx(opts...) + tx.ID = btTx.TxID() + tx.Hex = btTx.String() + tx.parsedTx = btTx + + return tx +} + // setXPubID will set the xPub ID on the model func (m *Transaction) setXPubID() { if len(m.rawXpubKey) > 0 && len(m.XPubID) == 0 { diff --git a/paymail_service_provider.go b/paymail_service_provider.go index 4b27837a..16ad490f 100644 --- a/paymail_service_provider.go +++ b/paymail_service_provider.go @@ -14,6 +14,7 @@ import ( "github.com/bitcoin-sv/go-paymail/spv" "github.com/bitcoinschema/go-bitcoin/v2" "github.com/libsv/go-bk/bec" + "github.com/libsv/go-bt/v2" ) // PaymailDefaultServiceProvider is an interface for overriding the paymail actions in go-paymail/server @@ -155,13 +156,11 @@ func (p *PaymailDefaultServiceProvider) RecordTransaction(ctx context.Context, metadata[ReferenceIDField] = p2pTx.Reference // Record the transaction - rts, err := getIncomingTxRecordStrategy(ctx, p.client, p2pTx.Hex) + btTx := buildBtTx(p2pTx) + rts, err := getIncomingTxRecordStrategy(ctx, p.client, btTx) if err != nil { return nil, err } - if err := rts.Validate(); err != nil { - return nil, err - } rts.ForceBroadcast(true) @@ -187,6 +186,25 @@ func (p *PaymailDefaultServiceProvider) RecordTransaction(ctx context.Context, }, nil } +func buildBtTx(p2pTx *paymail.P2PTransaction) *bt.Tx { + if p2pTx.DecodedBeef == nil { + res := p2pTx.DecodedBeef.GetLatestTx() + + for _, input := range res.Inputs { + prevTxDt := find(p2pTx.DecodedBeef.Transactions, func(tx *beef.TxData) bool { return tx.Transaction.TxID() == input.PreviousTxIDStr() }) + + o := (*prevTxDt).Transaction.Outputs[input.PreviousTxOutIndex] + input.PreviousTxSatoshis = o.Satoshis + input.PreviousTxScript = o.LockingScript + } + + return res + } + + res, _ := bt.NewTxFromString(p2pTx.Hex) + return res +} + // VerifyMerkleRoots will verify the merkle roots by checking them in external header service - Pulse func (p *PaymailDefaultServiceProvider) VerifyMerkleRoots( ctx context.Context, diff --git a/record_tx.go b/record_tx.go index a6c34cbf..833e4c16 100644 --- a/record_tx.go +++ b/record_tx.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "time" + + "github.com/libsv/go-bt/v2" ) type recordTxStrategy interface { @@ -36,18 +38,11 @@ func recordTransaction(ctx context.Context, c ClientInterface, strategy recordTx return } -func getRecordTxStrategy(ctx context.Context, c ClientInterface, xPubKey, txHex, draftID string) (recordTxStrategy, error) { - var rts recordTxStrategy - - if draftID != "" { - rts = getOutgoingTxRecordStrategy(xPubKey, txHex, draftID) - } else { - var err error - rts, err = getIncomingTxRecordStrategy(ctx, c, txHex) - - if err != nil { - return nil, err - } +func getOutgoingTxRecordStrategy(xPubKey string, btTx *bt.Tx, draftID string) (recordTxStrategy, error) { + rts := &outgoingTx{ + BtTx: btTx, + RelatedDraftID: draftID, + XPubKey: xPubKey, } if err := rts.Validate(); err != nil { @@ -57,16 +52,8 @@ func getRecordTxStrategy(ctx context.Context, c ClientInterface, xPubKey, txHex, return rts, nil } -func getOutgoingTxRecordStrategy(xPubKey, txHex, draftID string) recordTxStrategy { - return &outgoingTx{ - Hex: txHex, - RelatedDraftID: draftID, - XPubKey: xPubKey, - } -} - -func getIncomingTxRecordStrategy(ctx context.Context, c ClientInterface, txHex string) (recordIncomingTxStrategy, error) { - tx, err := getTransactionByHex(ctx, txHex, c.DefaultModelOptions()...) +func getIncomingTxRecordStrategy(ctx context.Context, c ClientInterface, btTx *bt.Tx) (recordIncomingTxStrategy, error) { + tx, err := getTransactionByHex(ctx, btTx.String(), c.DefaultModelOptions()...) if err != nil { return nil, err } @@ -74,19 +61,23 @@ func getIncomingTxRecordStrategy(ctx context.Context, c ClientInterface, txHex s var rts recordIncomingTxStrategy if tx != nil { + tx.parsedTx = btTx rts = &internalIncomingTx{ Tx: tx, broadcastNow: false, } } else { rts = &externalIncomingTx{ - Hex: txHex, + BtTx: btTx, broadcastNow: false, } } - return rts, nil + if err := rts.Validate(); err != nil { + return nil, err + } + return rts, nil } func waitForRecordTxWriteLock(ctx context.Context, c ClientInterface, key string) func() { diff --git a/record_tx_strategy_external_incoming_tx.go b/record_tx_strategy_external_incoming_tx.go index a1c3df42..5425594d 100644 --- a/record_tx_strategy_external_incoming_tx.go +++ b/record_tx_strategy_external_incoming_tx.go @@ -9,9 +9,11 @@ import ( ) type externalIncomingTx struct { - Hex string + BtTx *bt.Tx broadcastNow bool // e.g. BEEF must be broadcasted now allowBroadcastErrors bool // only BEEF cannot allow for broadcast errors + + txId string } func (strategy *externalIncomingTx) Name() string { @@ -53,20 +55,19 @@ func (strategy *externalIncomingTx) Execute(ctx context.Context, c ClientInterfa } func (strategy *externalIncomingTx) Validate() error { - if strategy.Hex == "" { - return ErrMissingFieldHex - } - if _, err := bt.NewTxFromString(strategy.Hex); err != nil { - return fmt.Errorf("invalid hex: %w", err) + if strategy.BtTx == nil { + return ErrMissingFieldHex // TODO } return nil // is valid } func (strategy *externalIncomingTx) TxID() string { - btTx, _ := bt.NewTxFromString(strategy.Hex) - return btTx.TxID() + if strategy.txId == "" { + strategy.txId = strategy.BtTx.TxID() + } + return strategy.txId } func (strategy *externalIncomingTx) LockKey() string { @@ -83,18 +84,14 @@ func (strategy *externalIncomingTx) FailOnBroadcastError(forceFail bool) { func _createExternalTxToRecord(ctx context.Context, eTx *externalIncomingTx, c ClientInterface, opts []ModelOps) (*Transaction, error) { // Create NEW tx model - tx, err := txFromHex(eTx.Hex, c.DefaultModelOptions(append(opts, New())...)...) - if err != nil { - return nil, err - } - + tx := txFromBtTx(eTx.BtTx, c.DefaultModelOptions(append(opts, New())...)...) _hydrateExternalWithSync(tx) if !tx.TransactionBase.hasOneKnownDestination(ctx, c) { return nil, ErrNoMatchingOutputs } - if err = tx.processUtxos(ctx); err != nil { + if err := tx.processUtxos(ctx); err != nil { return nil, err } diff --git a/record_tx_strategy_internal_incoming_tx.go b/record_tx_strategy_internal_incoming_tx.go index 5f14fc63..89debc5f 100644 --- a/record_tx_strategy_internal_incoming_tx.go +++ b/record_tx_strategy_internal_incoming_tx.go @@ -23,7 +23,7 @@ func (strategy *internalIncomingTx) Execute(ctx context.Context, c ClientInterfa logger := c.Logger() logger.Info(). Str("txID", strategy.Tx.ID). - Msg("start recording transaction") + Msg("start recording internal incoming transaction") // process transaction := strategy.Tx @@ -48,7 +48,7 @@ func (strategy *internalIncomingTx) Execute(ctx context.Context, c ClientInterfa logger.Info(). Str("txID", transaction.ID). - Msg("complete") + Msg("complete recording internal incoming transaction") return transaction, nil } diff --git a/record_tx_strategy_outgoing_tx.go b/record_tx_strategy_outgoing_tx.go index 96fa08a0..8c73ce98 100644 --- a/record_tx_strategy_outgoing_tx.go +++ b/record_tx_strategy_outgoing_tx.go @@ -10,9 +10,11 @@ import ( ) type outgoingTx struct { - Hex string + BtTx *bt.Tx RelatedDraftID string XPubKey string + + txId string } func (strategy *outgoingTx) Name() string { @@ -23,7 +25,7 @@ func (strategy *outgoingTx) Execute(ctx context.Context, c ClientInterface, opts logger := c.Logger() logger.Info(). Str("txID", strategy.TxID()). - Msg("start recording transaction") + Msg("start recording outgoing transaction") // create transaction, err := _createOutgoingTxToRecord(ctx, strategy, c, opts) @@ -60,24 +62,21 @@ func (strategy *outgoingTx) Execute(ctx context.Context, c ClientInterface, opts } if syncTx.BroadcastStatus == SyncStatusReady { + transaction.syncTransaction = syncTx _outgoingBroadcast(ctx, logger, transaction) // ignore error } logger.Info(). Str("txID", transaction.ID). - Msgf("complete, TxID: %s", transaction.ID) + Msgf("complete recording outgoing transaction") return transaction, nil } func (strategy *outgoingTx) Validate() error { - if strategy.Hex == "" { + if strategy.BtTx == nil { return ErrMissingFieldHex } - if _, err := bt.NewTxFromString(strategy.Hex); err != nil { - return fmt.Errorf("invalid hex: %w", err) - } - if strategy.RelatedDraftID == "" { return errors.New("empty RelatedDraftID") } @@ -90,8 +89,10 @@ func (strategy *outgoingTx) Validate() error { } func (strategy *outgoingTx) TxID() string { - btTx, _ := bt.NewTxFromString(strategy.Hex) - return btTx.TxID() + if strategy.txId == "" { + strategy.txId = strategy.BtTx.TxID() + } + return strategy.txId } func (strategy *outgoingTx) LockKey() string { @@ -101,15 +102,11 @@ func (strategy *outgoingTx) LockKey() string { func _createOutgoingTxToRecord(ctx context.Context, oTx *outgoingTx, c ClientInterface, opts []ModelOps) (*Transaction, error) { // Create NEW transaction model newOpts := c.DefaultModelOptions(append(opts, WithXPub(oTx.XPubKey), New())...) - tx, err := newTransactionWithDraftID( - oTx.Hex, oTx.RelatedDraftID, newOpts..., - ) - if err != nil { - return nil, err - } + tx := txFromBtTx(oTx.BtTx, newOpts...) + tx.DraftID = oTx.RelatedDraftID // hydrate - if err = _hydrateOutgoingWithDraft(ctx, tx); err != nil { + if err := _hydrateOutgoingWithDraft(ctx, tx); err != nil { return nil, err } diff --git a/sync_tx_service.go b/sync_tx_service.go index 2d240107..fd88a223 100644 --- a/sync_tx_service.go +++ b/sync_tx_service.go @@ -110,42 +110,33 @@ func broadcastSyncTransaction(ctx context.Context, syncTx *SyncTransaction) erro return err } - // Get the transaction HEX - var txHex string - if syncTx.transaction != nil && syncTx.transaction.Hex != "" { - // the transaction has already been retrieved and added to the syncTx object, just use that - txHex = syncTx.transaction.Hex - } else { - // else get hex from DB - transaction, err := getTransactionByID( - ctx, "", syncTx.ID, syncTx.GetOptions(false)..., - ) - if err != nil { - return err - } + client := syncTx.Client() + chainstateSrv := client.Chainstate() - if transaction == nil { - return errors.New("transaction was expected but not found, using ID: " + syncTx.ID) + // Get the transaction HEX + tx := syncTx.transaction + if tx == nil || tx.Hex == "" { + if tx, err = _getTransaction(ctx, syncTx.ID, syncTx.GetOptions(false)); err != nil { + return nil } - - txHex = transaction.Hex } + txHex, hexFormat := _getTxHexInFormat(ctx, tx, chainstateSrv.SupportedBroadcastFormats(), client) + // Broadcast var provider string - if provider, err = syncTx.Client().Chainstate().Broadcast( - ctx, syncTx.ID, txHex, defaultBroadcastTimeout, + if provider, err = chainstateSrv.Broadcast( + ctx, syncTx.ID, txHex, hexFormat, defaultBroadcastTimeout, ); err != nil { _bailAndSaveSyncTransaction(ctx, syncTx, SyncStatusReady, syncActionBroadcast, provider, err.Error()) return err } - // Create status message - message := "broadcast success" - // Update the sync information + statusMsg := "broadcast success" + syncTx.BroadcastStatus = SyncStatusComplete - syncTx.Results.LastMessage = message + syncTx.Results.LastMessage = statusMsg syncTx.LastAttempt = customTypes.NullTime{ NullTime: sql.NullTime{ Time: time.Now().UTC(), @@ -157,7 +148,7 @@ func broadcastSyncTransaction(ctx context.Context, syncTx *SyncTransaction) erro Action: syncActionBroadcast, ExecutedAt: time.Now().UTC(), Provider: provider, - StatusMessage: message, + StatusMessage: statusMsg, }) // Update sync status to be ready now @@ -179,6 +170,38 @@ func broadcastSyncTransaction(ctx context.Context, syncTx *SyncTransaction) erro return nil } +func _getTxHexInFormat(ctx context.Context, tx *Transaction, prefferedFormat chainstate.HexFormatFlag, store TransactionGetter) (txHex string, actualFormat chainstate.HexFormatFlag) { + + if prefferedFormat.Contains(chainstate.Ef) { + efHex, ok := ToEfHex(ctx, tx, store) + + if ok { + txHex = efHex + actualFormat = chainstate.Ef + return + } + } + + // return rawtx hex + txHex = tx.Hex + actualFormat = chainstate.RawTx + + return +} + +func _getTransaction(ctx context.Context, id string, opts []ModelOps) (*Transaction, error) { + transaction, err := getTransactionByID(ctx, "", id, opts...) + if err != nil { + return nil, err + } + + if transaction == nil { + return nil, ErrMissingTransaction + } + + return transaction, nil +} + ///////////////// // _syncTxDataFromChain will process the sync transaction record, or save the failure @@ -188,19 +211,12 @@ func _syncTxDataFromChain(ctx context.Context, syncTx *SyncTransaction, transact var err error - // Get the transaction if transaction == nil { - if transaction, err = getTransactionByID( - ctx, "", syncTx.ID, syncTx.GetOptions(false)..., - ); err != nil { - return err + if transaction, err = _getTransaction(ctx, syncTx.ID, syncTx.GetOptions(false)); err != nil { + return nil } } - if transaction == nil { - return ErrMissingTransaction - } - // Find on-chain var txInfo *chainstate.TransactionInfo // only mAPI currently provides merkle proof, so QueryTransaction should be used here