This repository was archived by the owner on Apr 2, 2024. It is now read-only.
generated from mrz1836/go-template
-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathrecord_tx_strategy_outgoing_tx.go
230 lines (180 loc) · 5.67 KB
/
record_tx_strategy_outgoing_tx.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package bux
import (
"context"
"errors"
"fmt"
"github.com/libsv/go-bt/v2"
"github.com/rs/zerolog"
)
type outgoingTx struct {
Hex string
RelatedDraftID string
XPubKey string
}
func (strategy *outgoingTx) Name() string {
return "outgoing_tx"
}
func (strategy *outgoingTx) Execute(ctx context.Context, c ClientInterface, opts []ModelOps) (*Transaction, error) {
logger := c.Logger()
logger.Info().
Str("txID", strategy.TxID()).
Msg("start recording transaction")
// create
transaction, err := _createOutgoingTxToRecord(ctx, strategy, c, opts)
if err != nil {
return nil, fmt.Errorf("creation of outgoing tx failed. Reason: %w", err)
}
if err = transaction.Save(ctx); err != nil {
return nil, fmt.Errorf("saving of Transaction failed. Reason: %w", err)
}
// process
if transaction.syncTransaction.P2PStatus == SyncStatusReady {
if err = _outgoingNotifyP2p(ctx, logger, transaction); err != nil {
// reject transaction if P2P notification failed
logger.Error().
Str("txID", transaction.ID).
Msgf("transaction rejected by P2P provider, try to revert transaction. Reason: %s", err)
if revertErr := c.RevertTransaction(ctx, transaction.ID); revertErr != nil {
logger.Error().
Str("txID", transaction.ID).
Msgf("FATAL! Reverting transaction after failed P2P notification failed. Reason: %s", err)
}
return nil, err
}
}
// get newest syncTx from DB - if it's an internal tx it could be broadcasted by us already
syncTx, err := GetSyncTransactionByID(ctx, transaction.ID, transaction.GetOptions(false)...)
if err != nil || syncTx == nil {
return nil, fmt.Errorf("getting syncTx failed. Reason: %w", err)
}
if syncTx.BroadcastStatus == SyncStatusReady {
_outgoingBroadcast(ctx, logger, transaction) // ignore error
}
logger.Info().
Str("txID", transaction.ID).
Msgf("complete, TxID: %s", transaction.ID)
return transaction, nil
}
func (strategy *outgoingTx) Validate() error {
if strategy.Hex == "" {
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")
}
if strategy.XPubKey == "" {
return errors.New("empty xPubKey")
}
return nil // is valid
}
func (strategy *outgoingTx) TxID() string {
btTx, _ := bt.NewTxFromString(strategy.Hex)
return btTx.TxID()
}
func (strategy *outgoingTx) LockKey() string {
return fmt.Sprintf("outgoing-%s", strategy.TxID())
}
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
}
// hydrate
if err = _hydrateOutgoingWithDraft(ctx, tx); err != nil {
return nil, err
}
_hydrateOutgoingWithSync(tx)
if err := tx.processUtxos(ctx); err != nil {
return nil, err
}
return tx, nil
}
func _hydrateOutgoingWithDraft(ctx context.Context, tx *Transaction) error {
draft, err := getDraftTransactionID(ctx, tx.XPubID, tx.DraftID, tx.GetOptions(false)...)
if err != nil {
return err
}
if draft == nil {
return ErrDraftNotFound
}
if len(draft.Configuration.Outputs) == 0 {
return errors.New("corresponding draft transaction has no outputs")
}
if draft.Configuration.Sync == nil {
draft.Configuration.Sync = tx.Client().DefaultSyncConfig()
}
tx.draftTransaction = draft
return nil // success
}
func _hydrateOutgoingWithSync(tx *Transaction) {
sync := newSyncTransaction(tx.ID, tx.draftTransaction.Configuration.Sync, tx.GetOptions(true)...)
// setup synchronization
sync.BroadcastStatus = _getBroadcastSyncStatus(tx)
sync.P2PStatus = _getP2pSyncStatus(tx)
sync.SyncStatus = SyncStatusPending // wait until transaction is broadcasted or P2P provider is notified
sync.Metadata = tx.Metadata
sync.transaction = tx
tx.syncTransaction = sync
}
func _getBroadcastSyncStatus(tx *Transaction) SyncStatus {
// immediately broadcast if is not BEEF
broadcast := SyncStatusReady // broadcast immediately
outputs := tx.draftTransaction.Configuration.Outputs
for _, o := range outputs {
if o.PaymailP4 != nil {
if o.PaymailP4.Format == BeefPaymailPayloadFormat {
broadcast = SyncStatusSkipped // postpone broadcasting if tx contains outputs in BEEF
break
}
}
}
return broadcast
}
func _getP2pSyncStatus(tx *Transaction) SyncStatus {
p2pStatus := SyncStatusSkipped
outputs := tx.draftTransaction.Configuration.Outputs
for _, o := range outputs {
if o.PaymailP4 != nil && o.PaymailP4.ResolutionType == ResolutionTypeP2P {
p2pStatus = SyncStatusReady // notify p2p immediately
break
}
}
return p2pStatus
}
func _outgoingNotifyP2p(ctx context.Context, logger *zerolog.Logger, tx *Transaction) error {
logger.Info().
Str("txID", tx.ID).
Msg("start p2p")
if err := processP2PTransaction(ctx, tx); err != nil {
logger.Error().
Str("txID", tx.ID).
Msgf("processP2PTransaction failed. Reason: %s", err)
return err
}
logger.Info().
Str("txID", tx.ID).
Msg("p2p complete")
return nil
}
func _outgoingBroadcast(ctx context.Context, logger *zerolog.Logger, tx *Transaction) {
logger.Info().
Str("txID", tx.ID).
Msg("start broadcast")
if err := broadcastSyncTransaction(ctx, tx.syncTransaction); err != nil {
// ignore error, transaction will be broadcasted by cron task
logger.Warn().
Str("txID", tx.ID).
Msgf("broadcasting failed, next try will be handled by task manager. Reason: %s", err)
} else {
logger.Info().
Str("txID", tx.ID).
Msg("broadcast complete")
}
}