diff --git a/chainstate/definitions.go b/chainstate/definitions.go index 2284ba1b..7ec42f9f 100644 --- a/chainstate/definitions.go +++ b/chainstate/definitions.go @@ -4,7 +4,6 @@ import ( "time" "github.com/BuxOrg/bux/utils" - "github.com/libsv/go-bc" ) // Chainstate configuration defaults @@ -52,17 +51,6 @@ const ( ProviderPulse = "pulse" // MerkleProof provider ) -// TransactionInfo is the universal information about the transaction found from a chain provider -type TransactionInfo struct { - BlockHash string `json:"block_hash,omitempty"` // mAPI, WOC - BlockHeight int64 `json:"block_height"` // mAPI, WOC - Confirmations int64 `json:"confirmations,omitempty"` // mAPI, WOC - ID string `json:"id"` // Transaction ID (Hex) - MinerID string `json:"miner_id,omitempty"` // mAPI ONLY - miner_id found - Provider string `json:"provider,omitempty"` // Provider is our internal source - MerkleProof *bc.MerkleProof `json:"merkle_proof,omitempty"` // mAPI 1.5 ONLY. Should be also supported by Arc in future -} - // DefaultFee is used when a fee has not been set by the user // This default is currently accepted by all BitcoinSV miners (50/1000) (7.27.23) // Actual TAAL FeeUnit - 1/1000, GorillaPool - 50/1000 (7.27.23) diff --git a/chainstate/transaction.go b/chainstate/transaction.go index da45ea11..1f2978b3 100644 --- a/chainstate/transaction.go +++ b/chainstate/transaction.go @@ -6,14 +6,15 @@ import ( "sync" "time" - "github.com/BuxOrg/bux/utils" "github.com/tonicpow/go-minercraft/v2" + + "github.com/BuxOrg/bux/utils" ) // query will try ALL providers in order and return the first "valid" response based on requirements func (c *Client) query(ctx context.Context, id string, requiredIn RequiredIn, - timeout time.Duration) *TransactionInfo { - + timeout time.Duration, +) *TransactionInfo { // Create a context (to cancel or timeout) ctxWithCancel, cancel := context.WithTimeout(ctx, timeout) defer cancel() @@ -50,8 +51,8 @@ func (c *Client) query(ctx context.Context, id string, requiredIn RequiredIn, // fastestQuery will try ALL providers on once and return the fastest "valid" response based on requirements func (c *Client) fastestQuery(ctx context.Context, id string, requiredIn RequiredIn, - timeout time.Duration) *TransactionInfo { - + timeout time.Duration, +) *TransactionInfo { // The channel for the internal results resultsChannel := make( chan *TransactionInfo, diff --git a/chainstate/transaction_info.go b/chainstate/transaction_info.go new file mode 100644 index 00000000..c34ef646 --- /dev/null +++ b/chainstate/transaction_info.go @@ -0,0 +1,22 @@ +package chainstate + +import ( + "github.com/libsv/go-bc" +) + +// TransactionInfo is the universal information about the transaction found from a chain provider +type TransactionInfo struct { + BlockHash string `json:"block_hash,omitempty"` // mAPI, WOC + BlockHeight int64 `json:"block_height"` // mAPI, WOC + Confirmations int64 `json:"confirmations,omitempty"` // mAPI, WOC + ID string `json:"id"` // Transaction ID (Hex) + MinerID string `json:"miner_id,omitempty"` // mAPI ONLY - miner_id found + Provider string `json:"provider,omitempty"` // Provider is our internal source + MerkleProof *bc.MerkleProof `json:"merkle_proof,omitempty"` // mAPI 1.5 ONLY. Should be also supported by Arc in future +} + +// Validate validates TransactionInfo by checking if it contains +// BlockHash and MerkleProof (from mAPI) or MerklePath (from Arc) +func (t *TransactionInfo) Valid() bool { + return !(t.BlockHash == "" || t.MerkleProof == nil || t.MerkleProof.TxOrID == "" || len(t.MerkleProof.Nodes) == 0) +} diff --git a/db_model_transactions.go b/db_model_transactions.go index 54f63215..b0d3230e 100644 --- a/db_model_transactions.go +++ b/db_model_transactions.go @@ -152,11 +152,6 @@ func (m *Transaction) Migrate(client datastore.ClientInterface) error { } } - err := m.migrateBUMP() - if err != nil { - return err - } - return client.IndexMetadata(tableName, xPubMetadataField) } @@ -215,17 +210,3 @@ func (m *Transaction) migrateMySQL(client datastore.ClientInterface, tableName s return nil } - -func (m *Transaction) migrateBUMP() error { - ctx := context.Background() - txs, err := getTransactionsToCalculateBUMP(ctx, nil, WithClient(m.client)) - if err != nil { - return err - } - for _, tx := range txs { - bump := tx.MerkleProof.ToBUMP(tx.BlockHeight) - tx.BUMP = bump - _ = tx.Save(ctx) - } - return nil -} diff --git a/model_bump.go b/model_bump.go index 9aa65cfb..150ed1bd 100644 --- a/model_bump.go +++ b/model_bump.go @@ -196,6 +196,10 @@ func getOffsetPair(offset uint64) uint64 { return offset - 1 } +func getParentOffset(offset uint64) uint64 { + return getOffsetPair(offset / 2) +} + func prepareNodes(baseLeaf BUMPLeaf, offset uint64, leafInPair BUMPLeaf, newOffset uint64) (string, string) { var baseLeafHash, pairLeafHash string @@ -342,3 +346,66 @@ func (bumps BUMPs) Value() (driver.Value, error) { return string(marshal), nil } + +// MerkleProofToBUMP transforms Merkle Proof to BUMP +func MerkleProofToBUMP(merkleProof *bc.MerkleProof, blockHeight uint64) BUMP { + bump := BUMP{BlockHeight: blockHeight} + + height := len(merkleProof.Nodes) + if height == 0 { + return bump + } + + offset := merkleProof.Index + pairOffset := getOffsetPair(offset) + + txIDPath1 := BUMPLeaf{ + Offset: offset, + Hash: merkleProof.TxOrID, + TxID: true, + } + txIDPath2 := createLeaf(getOffsetPair(offset), merkleProof.Nodes[0]) + + path := sortAndAddToPath(txIDPath1, offset, txIDPath2, pairOffset) + + for i := 1; i < height; i++ { + p := make([]BUMPLeaf, 0) + offset = getParentOffset(offset) + + leaf := createLeaf(offset, merkleProof.Nodes[i]) + + p = append(p, leaf) + path = append(path, p) + } + bump.Path = path + return bump +} + +func sortAndAddToPath(txIDPath1 BUMPLeaf, offset uint64, txIDPath2 BUMPLeaf, pairOffset uint64) [][]BUMPLeaf { + path := make([][]BUMPLeaf, 0) + txIDPath := make([]BUMPLeaf, 2) + + if offset < pairOffset { + txIDPath[0] = txIDPath1 + txIDPath[1] = txIDPath2 + } else { + txIDPath[0] = txIDPath2 + txIDPath[1] = txIDPath1 + } + + path = append(path, txIDPath) + return path +} + +func createLeaf(offset uint64, node string) BUMPLeaf { + leaf := BUMPLeaf{Offset: offset} + + isDuplicate := node == "*" + if !isDuplicate { + leaf.Hash = node + } else { + leaf.Duplicate = true + } + + return leaf +} diff --git a/model_bump_test.go b/model_bump_test.go index 64e91567..6d435bd0 100644 --- a/model_bump_test.go +++ b/model_bump_test.go @@ -3,6 +3,7 @@ package bux import ( "testing" + "github.com/libsv/go-bc" "github.com/stretchr/testify/assert" ) @@ -568,7 +569,7 @@ func TestBUMPModel_CalculateMergedBUMPAndHex(t *testing.T) { t.Run("Real Merkle Proof", func(t *testing.T) { // given - merkleProof := []MerkleProof{ + merkleProof := []bc.MerkleProof{ { Index: 1153, TxOrID: "2130b63dcbfe1356a30137fe9578691f59c6cf42d5e8928a800619de7f8e14da", @@ -797,7 +798,7 @@ func TestBUMPModel_CalculateMergedBUMPAndHex(t *testing.T) { // when bumps := make([]BUMP, 0) for _, mp := range merkleProof { - bumps = append(bumps, mp.ToBUMP(0)) + bumps = append(bumps, MerkleProofToBUMP(&mp, 0)) } bump, err := CalculateMergedBUMP(bumps) actualHex := bump.Hex() @@ -808,3 +809,123 @@ func TestBUMPModel_CalculateMergedBUMPAndHex(t *testing.T) { assert.Equal(t, expectedHex, actualHex) }) } + +// TestBUMPModel_MerkleProofToBUMP will test the method MerkleProofToBUMP() +func TestBUMPModel_MerkleProofToBUMP(t *testing.T) { + t.Parallel() + + t.Run("Valid Merkle Proof #1", func(t *testing.T) { + // given + blockHeight := uint64(0) + mp := bc.MerkleProof{ + Index: 1, + TxOrID: "txId", + Nodes: []string{"node0", "node1", "node2", "node3"}, + } + expectedBUMP := BUMP{ + BlockHeight: blockHeight, + Path: [][]BUMPLeaf{ + { + {Offset: 0, Hash: "node0"}, + {Offset: 1, Hash: "txId", TxID: true}, + }, + { + {Offset: 1, Hash: "node1"}, + }, + { + {Offset: 1, Hash: "node2"}, + }, + { + {Offset: 1, Hash: "node3"}, + }, + }, + } + + // when + actualBUMP := MerkleProofToBUMP(&mp, blockHeight) + + // then + assert.Equal(t, expectedBUMP, actualBUMP) + }) + + t.Run("Valid Merkle Proof #2", func(t *testing.T) { + // given + blockHeight := uint64(0) + mp := bc.MerkleProof{ + Index: 14, + TxOrID: "txId", + Nodes: []string{"node0", "node1", "node2", "node3", "node4"}, + } + expectedBUMP := BUMP{ + BlockHeight: blockHeight, + Path: [][]BUMPLeaf{ + { + {Offset: 14, Hash: "txId", TxID: true}, + {Offset: 15, Hash: "node0"}, + }, + { + {Offset: 6, Hash: "node1"}, + }, + { + {Offset: 2, Hash: "node2"}, + }, + { + {Offset: 0, Hash: "node3"}, + }, + { + {Offset: 1, Hash: "node4"}, + }, + }, + } + + // when + actualBUMP := MerkleProofToBUMP(&mp, blockHeight) + + // then + assert.Equal(t, expectedBUMP, actualBUMP) + }) + + t.Run("Valid Merkle Proof #3 - with *", func(t *testing.T) { + // given + blockHeight := uint64(0) + mp := bc.MerkleProof{ + Index: 14, + TxOrID: "txId", + Nodes: []string{"*", "node1", "node2", "node3", "node4"}, + } + expectedBUMP := BUMP{ + BlockHeight: blockHeight, + Path: [][]BUMPLeaf{ + { + {Offset: 14, Hash: "txId", TxID: true}, + {Offset: 15, Duplicate: true}, + }, + { + {Offset: 6, Hash: "node1"}, + }, + { + {Offset: 2, Hash: "node2"}, + }, + { + {Offset: 0, Hash: "node3"}, + }, + { + {Offset: 1, Hash: "node4"}, + }, + }, + } + + // when + actualBUMP := MerkleProofToBUMP(&mp, blockHeight) + + // then + assert.Equal(t, expectedBUMP, actualBUMP) + }) + + t.Run("Empty Merkle Proof", func(t *testing.T) { + blockHeight := uint64(0) + mp := bc.MerkleProof{} + actualBUMP := MerkleProofToBUMP(&mp, blockHeight) + assert.Equal(t, BUMP{BlockHeight: blockHeight}, actualBUMP) + }) +} diff --git a/model_incoming_transactions.go b/model_incoming_transactions.go index 60b01651..90f4ca04 100644 --- a/model_incoming_transactions.go +++ b/model_incoming_transactions.go @@ -7,11 +7,12 @@ import ( "fmt" "time" - "github.com/BuxOrg/bux/chainstate" - "github.com/BuxOrg/bux/taskmanager" "github.com/libsv/go-bt/v2" "github.com/mrz1836/go-datastore" zLogger "github.com/mrz1836/go-logger" + + "github.com/BuxOrg/bux/chainstate" + "github.com/BuxOrg/bux/taskmanager" ) // IncomingTransaction is an object representing the incoming (external) transaction (for pre-processing) @@ -31,7 +32,6 @@ type IncomingTransaction struct { // newIncomingTransaction will start a new model func newIncomingTransaction(hex string, opts ...ModelOps) (tx *IncomingTransaction) { - // Create the model tx = &IncomingTransaction{ Model: *NewBaseModel(ModelIncomingTransaction, opts...), @@ -69,8 +69,8 @@ func getIncomingTransactionByID(ctx context.Context, id string, opts ...ModelOps // getIncomingTransactionsToProcess will get the incoming transactions to process func getIncomingTransactionsToProcess(ctx context.Context, queryParams *datastore.QueryParams, - opts ...ModelOps) ([]*IncomingTransaction, error) { - + opts ...ModelOps, +) ([]*IncomingTransaction, error) { // Construct an empty model var models []IncomingTransaction conditions := map[string]interface{}{ @@ -220,7 +220,6 @@ func (m *IncomingTransaction) Migrate(client datastore.ClientInterface) error { // RegisterTasks will register the model specific tasks on client initialization func (m *IncomingTransaction) RegisterTasks() error { - // No task manager loaded? tm := m.Client().Taskmanager() if tm == nil { @@ -255,8 +254,8 @@ func (m *IncomingTransaction) RegisterTasks() error { // processIncomingTransactions will process incoming transaction records func processIncomingTransactions(ctx context.Context, logClient zLogger.GormLoggerInterface, maxTransactions int, - opts ...ModelOps) error { - + opts ...ModelOps, +) error { queryParams := &datastore.QueryParams{Page: 1, PageSize: maxTransactions} // Get x records: @@ -287,8 +286,8 @@ func processIncomingTransactions(ctx context.Context, logClient zLogger.GormLogg // processIncomingTransaction will process the incoming transaction record into a transaction, or save the failure func processIncomingTransaction(ctx context.Context, logClient zLogger.GormLoggerInterface, - incomingTx *IncomingTransaction) error { - + incomingTx *IncomingTransaction, +) error { if logClient == nil { logClient = incomingTx.client.Logger() } @@ -341,7 +340,7 @@ func processIncomingTransaction(ctx context.Context, logClient zLogger.GormLogge } // validate txInfo - if txInfo.BlockHash == "" || txInfo.MerkleProof == nil || txInfo.MerkleProof.TxOrID == "" || len(txInfo.MerkleProof.Nodes) == 0 { + if !txInfo.Valid() { logClient.Warn(ctx, fmt.Sprintf("processIncomingTransaction(): txInfo for %s is invalid, will try again later", incomingTx.ID)) if incomingTx.client.IsDebug() { diff --git a/model_merkle_proof.go b/model_merkle_proof.go deleted file mode 100644 index fb351181..00000000 --- a/model_merkle_proof.go +++ /dev/null @@ -1,116 +0,0 @@ -package bux - -import ( - "bytes" - "database/sql/driver" - "encoding/json" - "fmt" - "reflect" - - "github.com/libsv/go-bc" -) - -// MerkleProof represents Merkle Proof type -type MerkleProof bc.MerkleProof - -func offsetPair(offset uint64) uint64 { - if offset%2 == 0 { - return offset + 1 - } - return offset - 1 -} - -func parentOffset(offset uint64) uint64 { - return offsetPair(offset / 2) -} - -// Scan scan value into Json, implements sql.Scanner interface -func (m *MerkleProof) Scan(value interface{}) error { - if value == nil { - return nil - } - - xType := fmt.Sprintf("%T", value) - var byteValue []byte - if xType == ValueTypeString { - byteValue = []byte(value.(string)) - } else { - byteValue = value.([]byte) - } - if bytes.Equal(byteValue, []byte("")) || bytes.Equal(byteValue, []byte("\"\"")) { - return nil - } - - return json.Unmarshal(byteValue, &m) -} - -// Value return json value, implement driver.Valuer interface -func (m MerkleProof) Value() (driver.Value, error) { - if reflect.DeepEqual(m, MerkleProof{}) { - return nil, nil - } - marshal, err := json.Marshal(m) - if err != nil { - return nil, err - } - - return string(marshal), nil -} - -// ToBUMP transform Merkle Proof to BUMP -func (m *MerkleProof) ToBUMP(blockHeight uint64) BUMP { - bump := BUMP{BlockHeight: blockHeight} - - height := len(m.Nodes) - if height == 0 { - return bump - } - - path := make([][]BUMPLeaf, 0) - txIDPath := make([]BUMPLeaf, 2) - - offset := m.Index - pairOffset := offsetPair(offset) - - txIDPath1 := BUMPLeaf{ - Offset: offset, - Hash: m.TxOrID, - TxID: true, - } - txIDPath2 := BUMPLeaf{ - Offset: offsetPair(offset), - } - if m.Nodes[0] != "*" { - txIDPath2.Hash = m.Nodes[0] - } else { - txIDPath2.Duplicate = true - } - - if offset < pairOffset { - txIDPath[0] = txIDPath1 - txIDPath[1] = txIDPath2 - } else { - txIDPath[0] = txIDPath2 - txIDPath[1] = txIDPath1 - } - - path = append(path, txIDPath) - for i := 1; i < height; i++ { - p := make([]BUMPLeaf, 0) - offset = parentOffset(offset) - - leaf := BUMPLeaf{Offset: offset} - - isDuplicate := m.Nodes[i] == "*" - if !isDuplicate { - leaf.Hash = m.Nodes[i] - } else { - leaf.Duplicate = true - } - - p = append(p, leaf) - path = append(path, p) - } - bump.Path = path - return bump -} diff --git a/model_merkle_proof_test.go b/model_merkle_proof_test.go deleted file mode 100644 index 19d8a737..00000000 --- a/model_merkle_proof_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package bux - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// TestMerkleProofModel_ToBUMP will test the method ToBUMP() -func TestMerkleProofModel_ToBUMP(t *testing.T) { - t.Parallel() - - t.Run("Valid Merkle Proof #1", func(t *testing.T) { - // given - blockHeight := uint64(0) - mp := MerkleProof{ - Index: 1, - TxOrID: "txId", - Nodes: []string{"node0", "node1", "node2", "node3"}, - } - expectedBUMP := BUMP{ - BlockHeight: blockHeight, - Path: [][]BUMPLeaf{ - { - {Offset: 0, Hash: "node0"}, - {Offset: 1, Hash: "txId", TxID: true}, - }, - { - {Offset: 1, Hash: "node1"}, - }, - { - {Offset: 1, Hash: "node2"}, - }, - { - {Offset: 1, Hash: "node3"}, - }, - }, - } - - // when - actualBUMP := mp.ToBUMP(blockHeight) - - // then - assert.Equal(t, expectedBUMP, actualBUMP) - }) - - t.Run("Valid Merkle Proof #2", func(t *testing.T) { - // given - blockHeight := uint64(0) - mp := MerkleProof{ - Index: 14, - TxOrID: "txId", - Nodes: []string{"node0", "node1", "node2", "node3", "node4"}, - } - expectedBUMP := BUMP{ - BlockHeight: blockHeight, - Path: [][]BUMPLeaf{ - { - {Offset: 14, Hash: "txId", TxID: true}, - {Offset: 15, Hash: "node0"}, - }, - { - {Offset: 6, Hash: "node1"}, - }, - { - {Offset: 2, Hash: "node2"}, - }, - { - {Offset: 0, Hash: "node3"}, - }, - { - {Offset: 1, Hash: "node4"}, - }, - }, - } - - // when - actualBUMP := mp.ToBUMP(blockHeight) - - // then - assert.Equal(t, expectedBUMP, actualBUMP) - }) - - t.Run("Valid Merkle Proof #3 - with *", func(t *testing.T) { - // given - blockHeight := uint64(0) - mp := MerkleProof{ - Index: 14, - TxOrID: "txId", - Nodes: []string{"*", "node1", "node2", "node3", "node4"}, - } - expectedBUMP := BUMP{ - BlockHeight: blockHeight, - Path: [][]BUMPLeaf{ - { - {Offset: 14, Hash: "txId", TxID: true}, - {Offset: 15, Duplicate: true}, - }, - { - {Offset: 6, Hash: "node1"}, - }, - { - {Offset: 2, Hash: "node2"}, - }, - { - {Offset: 0, Hash: "node3"}, - }, - { - {Offset: 1, Hash: "node4"}, - }, - }, - } - - // when - actualBUMP := mp.ToBUMP(blockHeight) - - // then - assert.Equal(t, expectedBUMP, actualBUMP) - }) - - t.Run("Empty Merkle Proof", func(t *testing.T) { - blockHeight := uint64(0) - mp := MerkleProof{} - actualBUMP := mp.ToBUMP(blockHeight) - assert.Equal(t, BUMP{BlockHeight: blockHeight}, actualBUMP) - }) -} diff --git a/model_transactions.go b/model_transactions.go index e0af5923..58af4f74 100644 --- a/model_transactions.go +++ b/model_transactions.go @@ -3,10 +3,11 @@ package bux import ( "context" + "github.com/libsv/go-bt/v2" + "github.com/BuxOrg/bux/chainstate" "github.com/BuxOrg/bux/taskmanager" "github.com/BuxOrg/bux/utils" - "github.com/libsv/go-bt/v2" ) // TransactionBase is the same fields share between multiple transaction models @@ -54,7 +55,6 @@ type Transaction struct { TotalValue uint64 `json:"total_value" toml:"total_value" yaml:"total_value" gorm:"<-create;type:bigint" bson:"total_value,omitempty"` XpubMetadata XpubMetadata `json:"-" toml:"xpub_metadata" gorm:"<-;type:json;xpub_id specific metadata" bson:"xpub_metadata,omitempty"` XpubOutputValue XpubOutputValue `json:"-" toml:"xpub_output_value" gorm:"<-;type:json;xpub_id specific value" bson:"xpub_output_value,omitempty"` - MerkleProof MerkleProof `json:"merkle_proof" toml:"merkle_proof" yaml:"merkle_proof" gorm:"<-;type:text;comment:Merkle Proof payload from mAPI" bson:"merkle_proof,omitempty"` BUMP BUMP `json:"bump" toml:"bump" yaml:"bump" gorm:"<-;type:text;comment:BSV Unified Merkle Path (BUMP) Format" bson:"bump,omitempty"` // Virtual Fields @@ -230,17 +230,12 @@ func (m *Transaction) isExternal() bool { func (m *Transaction) setChainInfo(txInfo *chainstate.TransactionInfo) { m.BlockHash = txInfo.BlockHash m.BlockHeight = uint64(txInfo.BlockHeight) - m.setMerkleRoot(txInfo) + m.setBUMP(txInfo) } -func (m *Transaction) setMerkleRoot(txInfo *chainstate.TransactionInfo) { - if txInfo.MerkleProof != nil { - mp := MerkleProof(*txInfo.MerkleProof) - m.MerkleProof = mp - - bump := mp.ToBUMP(uint64(txInfo.BlockHeight)) - m.BUMP = bump - } +func (m *Transaction) setBUMP(txInfo *chainstate.TransactionInfo) { + bump := MerkleProofToBUMP(txInfo.MerkleProof, uint64(txInfo.BlockHeight)) + m.BUMP = bump } // IsXpubAssociated will check if this key is associated to this transaction diff --git a/sync_tx_service.go b/sync_tx_service.go index b7b714db..5fb376b2 100644 --- a/sync_tx_service.go +++ b/sync_tx_service.go @@ -10,11 +10,12 @@ import ( "sync" "time" - "github.com/BuxOrg/bux/chainstate" - "github.com/BuxOrg/bux/notifications" "github.com/bitcoin-sv/go-paymail" "github.com/mrz1836/go-datastore" customTypes "github.com/mrz1836/go-datastore/custom_types" + + "github.com/BuxOrg/bux/chainstate" + "github.com/BuxOrg/bux/notifications" ) // processSyncTransactions will process sync transaction records @@ -120,7 +121,6 @@ func broadcastSyncTransaction(ctx context.Context, syncTx *SyncTransaction) erro transaction, err := getTransactionByID( ctx, "", syncTx.ID, syncTx.GetOptions(false)..., ) - if err != nil { return err } @@ -221,8 +221,7 @@ func _syncTxDataFromChain(ctx context.Context, syncTx *SyncTransaction, transact return err } - // validate txInfo - if txInfo.BlockHash == "" || txInfo.MerkleProof == nil || txInfo.MerkleProof.TxOrID == "" || len(txInfo.MerkleProof.Nodes) == 0 { + if !txInfo.Valid() { syncTx.client.Logger().Warn(ctx, fmt.Sprintf("processSyncTransaction(): txInfo for %s is invalid, will try again later", syncTx.ID)) if syncTx.client.IsDebug() { @@ -401,7 +400,6 @@ func _groupByXpub(scTxs []*SyncTransaction) map[string][]*SyncTransaction { func _bailAndSaveSyncTransaction(ctx context.Context, syncTx *SyncTransaction, status SyncStatus, action, provider, message string, ) { - if action == syncActionSync { syncTx.SyncStatus = status } else if action == syncActionP2P {