-
Notifications
You must be signed in to change notification settings - Fork 111
swap, swap/chain, contracts/swap: add transaction queue #2124
base: master
Are you sure you want to change the base?
Changes from all commits
422ae58
65c5899
145097c
f6cdcab
97ce00b
eea13b6
8a3d48c
47ee895
7486071
255927d
8a9ea5e
1a59564
c872d34
a67d6ea
2deea57
67fd670
f936138
d9e54ba
40327f9
be6d28c
89ceeb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ import ( | |
|
||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/metrics" | ||
contract "github.com/ethersphere/swarm/contracts/swap" | ||
"github.com/ethersphere/swarm/swap/chain" | ||
|
@@ -31,58 +32,117 @@ import ( | |
// CashChequeBeneficiaryTransactionCost is the expected gas cost of a CashChequeBeneficiary transaction | ||
const CashChequeBeneficiaryTransactionCost = 50000 | ||
|
||
// CashoutProcessor holds all relevant fields needed for processing cashouts | ||
type CashoutProcessor struct { | ||
backend chain.Backend // ethereum backend to use | ||
privateKey *ecdsa.PrivateKey // private key to use | ||
Logger Logger | ||
} | ||
// CashoutRequestHandlerID is the handlerID used by the CashoutProcessor for CashoutRequests | ||
const CashoutRequestHandlerID = "CashoutProcessor_CashoutRequest" | ||
|
||
// CashoutRequest represents a request for a cashout operation | ||
type CashoutRequest struct { | ||
Cheque Cheque // cheque to be cashed | ||
Destination common.Address // destination for the payout | ||
Logger Logger | ||
} | ||
|
||
// ActiveCashout stores the necessary information for a cashout in progess | ||
type ActiveCashout struct { | ||
Request CashoutRequest // the request that caused this cashout | ||
TransactionHash common.Hash // the hash of the current transaction for this request | ||
Logger Logger | ||
// CashoutProcessor holds all relevant fields needed for processing cashouts | ||
type CashoutProcessor struct { | ||
backend chain.Backend // ethereum backend to use | ||
txScheduler chain.TxScheduler // transaction queue to use | ||
cashoutResultHandler CashoutResultHandler | ||
logger Logger | ||
} | ||
|
||
// CashoutResultHandler is an interface which accepts CashChequeResults from a CashoutProcessor | ||
type CashoutResultHandler interface { | ||
// Called by the CashoutProcessor when a CashoutRequest was successfully executed | ||
// It will be called again if an error is returned | ||
Comment on lines
+54
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These comments seem to contradict each other There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They don't. The CashoutProcessor calls this when a CashoutRequest was successfully executed (during the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems to be a very vital feature of the queue and deserves (in my opinion) a somewhat more explicit comment here. I was following the code to actually verify that the txqueue keeps calling
And now I lost the track... Can you help me by explaining how the call proceeds from here? How and where is this trigger signal received and how/where can I see that indeed, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All notification queues are processed in their respective go routine started by the |
||
HandleCashoutResult(request *CashoutRequest, result *contract.CashChequeResult, receipt *types.Receipt) error | ||
} | ||
|
||
// newCashoutProcessor creates a new instance of CashoutProcessor | ||
func newCashoutProcessor(backend chain.Backend, privateKey *ecdsa.PrivateKey) *CashoutProcessor { | ||
return &CashoutProcessor{ | ||
backend: backend, | ||
privateKey: privateKey, | ||
func newCashoutProcessor(txScheduler chain.TxScheduler, backend chain.Backend, privateKey *ecdsa.PrivateKey, cashoutResultHandler CashoutResultHandler, logger Logger) *CashoutProcessor { | ||
c := &CashoutProcessor{ | ||
backend: backend, | ||
txScheduler: txScheduler, | ||
cashoutResultHandler: cashoutResultHandler, | ||
Eknir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
logger: logger, | ||
} | ||
} | ||
|
||
// cashCheque tries to cash the cheque specified in the request | ||
// after the transaction is sent it waits on its success | ||
func (c *CashoutProcessor) cashCheque(ctx context.Context, request *CashoutRequest) error { | ||
cheque := request.Cheque | ||
opts := bind.NewKeyedTransactor(c.privateKey) | ||
opts.Context = ctx | ||
txScheduler.SetHandlers(CashoutRequestHandlerID, &chain.TxRequestHandlers{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are only setting the handler for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are all used in tests at least. Technically all places which schedule transactions also should monitor at least Anyway those handlers are important already as otherwise we cannot properly test the error conditions during the txqueue tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. Maybe, for now, we can already register the handlers for now and just write a log output? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
NotifyReceipt: func(ctx context.Context, id uint64, notification *chain.TxReceiptNotification) error { | ||
var request *CashoutRequest | ||
err := c.txScheduler.GetExtraData(id, &request) | ||
Eknir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
otherSwap, err := contract.InstanceAt(request.Cheque.Contract, c.backend) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
receipt := ¬ification.Receipt | ||
if receipt.Status == 0 { | ||
c.logger.Error(CashChequeAction, "cheque cashing transaction reverted", "tx", receipt.TxHash) | ||
return nil | ||
} | ||
|
||
result := otherSwap.CashChequeBeneficiaryResult(receipt) | ||
return c.cashoutResultHandler.HandleCashoutResult(request, result, receipt) | ||
}, | ||
NotifyPending: func(ctx context.Context, id uint64, notification *chain.TxPendingNotification) error { | ||
c.logger.Debug(CashChequeAction, "cheque cashing transaction sent", "hash", notification.Transaction.Hash()) | ||
return nil | ||
}, | ||
NotifyCancelled: func(ctx context.Context, id uint64, notification *chain.TxCancelledNotification) error { | ||
c.logger.Warn(CashChequeAction, "cheque cashing transaction cancelled", "reason", notification.Reason) | ||
return nil | ||
}, | ||
NotifyStatusUnknown: func(ctx context.Context, id uint64, notification *chain.TxStatusUnknownNotification) error { | ||
c.logger.Error(CashChequeAction, "cheque cashing transaction status unknown", "reason", notification.Reason) | ||
return nil | ||
}, | ||
}) | ||
return c | ||
} | ||
|
||
otherSwap, err := contract.InstanceAt(cheque.Contract, c.backend) | ||
// submitCheque submits a cheque for cashout | ||
// the cheque might not be cashed if it is not deemed profitable | ||
func (c *CashoutProcessor) submitCheque(ctx context.Context, request *CashoutRequest) { | ||
expectedPayout, transactionCosts, err := c.estimatePayout(ctx, &request.Cheque) | ||
if err != nil { | ||
return err | ||
c.logger.Error(CashChequeAction, "could not estimate payout", "error", err) | ||
return | ||
} | ||
|
||
tx, err := otherSwap.CashChequeBeneficiaryStart(opts, request.Destination, cheque.CumulativePayout, cheque.Signature) | ||
costsMultiplier := uint256.FromUint64(2) | ||
costThreshold, err := uint256.New().Mul(transactionCosts, costsMultiplier) | ||
if err != nil { | ||
return err | ||
c.logger.Error(CashChequeAction, "overflow in transaction fee", "error", err) | ||
return | ||
} | ||
|
||
// this blocks until the cashout has been successfully processed | ||
return c.waitForAndProcessActiveCashout(&ActiveCashout{ | ||
Request: *request, | ||
TransactionHash: tx.Hash(), | ||
Logger: request.Logger, | ||
}) | ||
// do a payout transaction if we get more than 2 times the gas costs | ||
if expectedPayout.Cmp(costThreshold) == 1 { | ||
c.logger.Info(CashChequeAction, "queueing cashout", "cheque", &request.Cheque) | ||
|
||
cheque := request.Cheque | ||
otherSwap, err := contract.InstanceAt(cheque.Contract, c.backend) | ||
if err != nil { | ||
c.logger.Error(CashChequeAction, "could not get swap instance", "error", err) | ||
return | ||
} | ||
|
||
txRequest, err := otherSwap.CashChequeBeneficiaryRequest(cheque.Beneficiary, cheque.CumulativePayout, cheque.Signature) | ||
if err != nil { | ||
metrics.GetOrRegisterCounter("swap/cheques/cashed/errors", nil).Inc(1) | ||
c.logger.Error(CashChequeAction, "cashing cheque:", "error", err) | ||
return | ||
} | ||
|
||
_, err = c.txScheduler.ScheduleRequest(CashoutRequestHandlerID, *txRequest, request) | ||
Eknir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
metrics.GetOrRegisterCounter("swap/cheques/cashed/errors", nil).Inc(1) | ||
c.logger.Error(CashChequeAction, "cashing cheque:", "error", err) | ||
} | ||
} | ||
} | ||
|
||
// estimatePayout estimates the payout for a given cheque as well as the transaction cost | ||
|
@@ -128,31 +188,3 @@ func (c *CashoutProcessor) estimatePayout(ctx context.Context, cheque *Cheque) ( | |
|
||
return expectedPayout, transactionCosts, nil | ||
} | ||
|
||
// waitForAndProcessActiveCashout waits for activeCashout to complete | ||
func (c *CashoutProcessor) waitForAndProcessActiveCashout(activeCashout *ActiveCashout) error { | ||
ctx, cancel := context.WithTimeout(context.Background(), DefaultTransactionTimeout) | ||
defer cancel() | ||
|
||
receipt, err := chain.WaitMined(ctx, c.backend, activeCashout.TransactionHash) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
otherSwap, err := contract.InstanceAt(activeCashout.Request.Cheque.Contract, c.backend) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
result := otherSwap.CashChequeBeneficiaryResult(receipt) | ||
|
||
metrics.GetOrRegisterCounter("swap/cheques/cashed/honey", nil).Inc(result.TotalPayout.Int64()) | ||
|
||
if result.Bounced { | ||
metrics.GetOrRegisterCounter("swap/cheques/cashed/bounced", nil).Inc(1) | ||
activeCashout.Logger.Warn(CashChequeAction, "cheque bounced", "tx", receipt.TxHash) | ||
} | ||
|
||
activeCashout.Logger.Info(CashChequeAction, "cheque cashed", "honey", activeCashout.Request.Cheque.Honey) | ||
return nil | ||
} |
Uh oh!
There was an error while loading. Please reload this page.