package settler import ( "context" "sync" "github.com/filecoin-project/lotus/paychmgr" "go.uber.org/fx" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/impl/full" payapi "github.com/filecoin-project/lotus/node/impl/paych" "github.com/filecoin-project/lotus/node/modules/helpers" ) var log = logging.Logger("payment-channel-settler") // API are the dependencies need to run the payment channel settler type API struct { fx.In full.ChainAPI full.StateAPI payapi.PaychAPI } type settlerAPI interface { PaychList(context.Context) ([]address.Address, error) PaychStatus(context.Context, address.Address) (*api.PaychStatus, error) PaychVoucherCheckSpendable(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error) PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error) PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error) StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) } type paymentChannelSettler struct { ctx context.Context api settlerAPI } // SettlePaymentChannels checks the chain for events related to payment channels settling and // submits any vouchers for inbound channels tracked for this node func SettlePaymentChannels(mctx helpers.MetricsCtx, lc fx.Lifecycle, papi API) error { ctx := helpers.LifecycleCtx(mctx, lc) lc.Append(fx.Hook{ OnStart: func(context.Context) error { pcs := newPaymentChannelSettler(ctx, &papi) ev := events.NewEvents(ctx, papi) return ev.Called(pcs.check, pcs.messageHandler, pcs.revertHandler, int(build.MessageConfidence+1), events.NoTimeout, pcs.matcher) }, }) return nil } func newPaymentChannelSettler(ctx context.Context, api settlerAPI) *paymentChannelSettler { return &paymentChannelSettler{ ctx: ctx, api: api, } } func (pcs *paymentChannelSettler) check(ts *types.TipSet) (done bool, more bool, err error) { return false, true, nil } func (pcs *paymentChannelSettler) messageHandler(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) { // Ignore unsuccessful settle messages if rec.ExitCode != 0 { return true, nil } bestByLane, err := paychmgr.BestSpendableByLane(pcs.ctx, pcs.api, msg.To) if err != nil { return true, err } var wg sync.WaitGroup wg.Add(len(bestByLane)) for _, voucher := range bestByLane { submitMessageCID, err := pcs.api.PaychVoucherSubmit(pcs.ctx, msg.To, voucher, nil, nil) if err != nil { return true, err } go func(voucher *paych.SignedVoucher, submitMessageCID cid.Cid) { defer wg.Done() msgLookup, err := pcs.api.StateWaitMsg(pcs.ctx, submitMessageCID, build.MessageConfidence, api.LookbackNoLimit, true) if err != nil { log.Errorf("submitting voucher: %s", err.Error()) return } if msgLookup.Receipt.ExitCode != 0 { log.Errorf("failed submitting voucher: %+v", voucher) } }(voucher, submitMessageCID) } wg.Wait() return true, nil } func (pcs *paymentChannelSettler) revertHandler(ctx context.Context, ts *types.TipSet) error { return nil } func (pcs *paymentChannelSettler) matcher(msg *types.Message) (matched bool, err error) { // Check if this is a settle payment channel message if msg.Method != paych.Methods.Settle { return false, nil } // Check if this payment channel is of concern to this node (i.e. tracked in payment channel store), // and its inbound (i.e. we're getting vouchers that we may need to redeem) trackedAddresses, err := pcs.api.PaychList(pcs.ctx) if err != nil { return false, err } for _, addr := range trackedAddresses { if msg.To == addr { status, err := pcs.api.PaychStatus(pcs.ctx, addr) if err != nil { return false, err } if status.Direction == api.PCHInbound { return true, nil } } } return false, nil }