package paych import ( "context" "fmt" logging "github.com/ipfs/go-log" "github.com/filecoin-project/go-lotus/chain/actors" "github.com/filecoin-project/go-lotus/chain/address" "github.com/filecoin-project/go-lotus/chain/stmgr" "github.com/filecoin-project/go-lotus/chain/types" ) var log = logging.Logger("paych") type Manager struct { store *Store sm *stmgr.StateManager } func NewManager(sm *stmgr.StateManager, pchstore *Store) *Manager { return &Manager{ store: pchstore, sm: sm, } } func (pm *Manager) TrackInboundChannel(ctx context.Context, ch address.Address) error { _, st, err := pm.loadPaychState(ctx, ch) if err != nil { return err } return pm.store.TrackChannel(&ChannelInfo{ Channel: ch, Direction: DirInbound, ControlAddr: st.To, }) } func (pm *Manager) TrackOutboundChannel(ctx context.Context, ch address.Address) error { _, st, err := pm.loadPaychState(ctx, ch) if err != nil { return err } return pm.store.TrackChannel(&ChannelInfo{ Channel: ch, Direction: DirOutbound, ControlAddr: st.From, }) } func (pm *Manager) ListChannels() ([]address.Address, error) { return pm.store.ListChannels() } func (pm *Manager) GetChannelInfo(addr address.Address) (*ChannelInfo, error) { return pm.store.getChannelInfo(addr) } // checks if the given voucher is valid (is or could become spendable at some point) func (pm *Manager) CheckVoucherValid(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error { act, pca, err := pm.loadPaychState(ctx, ch) if err != nil { return err } // verify signature vb, err := sv.SigningBytes() if err != nil { return err } // TODO: technically, either party may create and sign a voucher. // However, for now, we only accept them from the channel creator. // More complex handling logic can be added later if err := sv.Signature.Verify(pca.From, vb); err != nil { return err } sendAmount := sv.Amount // now check the lane state // TODO: should check against vouchers in our local store too // there might be something conflicting ls, ok := pca.LaneStates[fmt.Sprint(sv.Lane)] if !ok { } else { if ls.Closed { return fmt.Errorf("voucher is on a closed lane") } if ls.Nonce >= sv.Nonce { return fmt.Errorf("nonce too low") } sendAmount = types.BigSub(sv.Amount, ls.Redeemed) } // TODO: also account for vouchers on other lanes we've received newTotal := types.BigAdd(sendAmount, pca.ToSend) if types.BigCmp(act.Balance, newTotal) < 0 { return fmt.Errorf("not enough funds in channel to cover voucher") } if len(sv.Merges) != 0 { return fmt.Errorf("dont currently support paych lane merges") } return nil } // checks if the given voucher is currently spendable func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address, sv *types.SignedVoucher, secret []byte, proof []byte) (bool, error) { owner, err := pm.getPaychOwner(ctx, ch) if err != nil { return false, err } if sv.Extra != nil && proof == nil { known, err := pm.ListVouchers(ctx, ch) if err != nil { return false, err } for _, v := range known { if v.Proof != nil && v.Voucher.Equals(sv) { log.Info("CheckVoucherSpendable: using stored proof") proof = v.Proof break } } if proof == nil { log.Warn("CheckVoucherSpendable: nil proof for voucher with validation") } } enc, err := actors.SerializeParams(&actors.PCAUpdateChannelStateParams{ Sv: *sv, Secret: secret, Proof: proof, }) if err != nil { return false, err } ret, err := stmgr.Call(ctx, pm.sm, &types.Message{ From: owner, To: ch, Method: actors.PCAMethods.UpdateChannelState, Params: enc, }, nil) if err != nil { return false, err } if ret.ExitCode != 0 { return false, nil } return true, nil } func (pm *Manager) loadPaychState(ctx context.Context, ch address.Address) (*types.Actor, *actors.PaymentChannelActorState, error) { var pcast actors.PaymentChannelActorState act, err := pm.sm.LoadActorState(ctx, ch, &pcast) if err != nil { return nil, nil, err } return act, &pcast, nil } func (pm *Manager) getPaychOwner(ctx context.Context, ch address.Address) (address.Address, error) { ret, err := stmgr.Call(ctx, pm.sm, &types.Message{ From: ch, To: ch, Method: actors.PCAMethods.GetOwner, }, nil) if err != nil { return address.Undef, err } if ret.ExitCode != 0 { return address.Undef, fmt.Errorf("failed to get payment channel owner (exit code %d)", ret.ExitCode) } return address.NewFromBytes(ret.Return) } func (pm *Manager) AddVoucher(ctx context.Context, ch address.Address, sv *types.SignedVoucher, proof []byte) error { if err := pm.CheckVoucherValid(ctx, ch, sv); err != nil { return err } return pm.store.AddVoucher(ch, sv, proof) } func (pm *Manager) ListVouchers(ctx context.Context, ch address.Address) ([]*VoucherInfo, error) { // TODO: just having a passthrough method like this feels odd. Seems like // there should be some filtering we're doing here return pm.store.VouchersForPaych(ch) } func (pm *Manager) NextNonceForLane(ctx context.Context, ch address.Address, lane uint64) (uint64, error) { vouchers, err := pm.store.VouchersForPaych(ch) if err != nil { return 0, err } var maxnonce uint64 for _, v := range vouchers { if v.Voucher.Lane == lane { if v.Voucher.Nonce > maxnonce { maxnonce = v.Voucher.Nonce } } } return maxnonce + 1, nil }