Co-authored-by: Steven Allen <steven@stebalien.com> Co-authored-by: Raul Kripalani <raulk@users.noreply.github.com> Co-authored-by: Kevin Li <ychiaoli18@users.noreply.github.com> Co-authored-by: vyzo <vyzo@hackzen.org> Co-authored-by: Ian Davis <nospam@iandavis.com> Co-authored-by: Aayush Rajasekaran <arajasek94@gmail.com> Co-authored-by: Jiaying Wang <42981373+jennijuju@users.noreply.github.com> Co-authored-by: Jennifer Wang <jiayingw703@gmail.com> Co-authored-by: Geoff Stuart <geoff.vball@gmail.com> Co-authored-by: Shrenuj Bansal <shrenuj.bansal@protocol.ai> Co-authored-by: Shrenuj Bansal <108157875+shrenujbansal@users.noreply.github.com> Co-authored-by: Geoff Stuart <geoffrey.stuart@protocol.ai> Co-authored-by: Aayush Rajasekaran <aayushrajasekaran@Aayushs-MacBook-Pro.local> Co-authored-by: ZenGround0 <5515260+ZenGround0@users.noreply.github.com> Co-authored-by: zenground0 <ZenGround0@users.noreply.github.com>
		
			
				
	
	
		
			378 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			378 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package paychmgr
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/ipfs/go-cid"
 | |
| 	"github.com/ipfs/go-datastore"
 | |
| 	logging "github.com/ipfs/go-log/v2"
 | |
| 	"golang.org/x/xerrors"
 | |
| 
 | |
| 	"github.com/filecoin-project/go-address"
 | |
| 	"github.com/filecoin-project/go-state-types/abi"
 | |
| 	paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych"
 | |
| 	"github.com/filecoin-project/go-state-types/crypto"
 | |
| 	"github.com/filecoin-project/go-state-types/network"
 | |
| 
 | |
| 	"github.com/filecoin-project/lotus/api"
 | |
| 	"github.com/filecoin-project/lotus/chain/actors/builtin/paych"
 | |
| 	"github.com/filecoin-project/lotus/chain/stmgr"
 | |
| 	"github.com/filecoin-project/lotus/chain/types"
 | |
| )
 | |
| 
 | |
| var log = logging.Logger("paych")
 | |
| 
 | |
| var errProofNotSupported = errors.New("payment channel proof parameter is not supported")
 | |
| 
 | |
| // stateManagerAPI defines the methods needed from StateManager
 | |
| type stateManagerAPI interface {
 | |
| 	ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error)
 | |
| 	GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error)
 | |
| 	Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error)
 | |
| }
 | |
| 
 | |
| // paychAPI defines the API methods needed by the payment channel manager
 | |
| type PaychAPI interface {
 | |
| 	StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error)
 | |
| 	StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error)
 | |
| 	MpoolPushMessage(ctx context.Context, msg *types.Message, maxFee *api.MessageSendSpec) (*types.SignedMessage, error)
 | |
| 	WalletHas(ctx context.Context, addr address.Address) (bool, error)
 | |
| 	WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error)
 | |
| 	StateNetworkVersion(context.Context, types.TipSetKey) (network.Version, error)
 | |
| }
 | |
| 
 | |
| // managerAPI defines all methods needed by the manager
 | |
| type managerAPI interface {
 | |
| 	stateManagerAPI
 | |
| 	PaychAPI
 | |
| }
 | |
| 
 | |
| // managerAPIImpl is used to create a composite that implements managerAPI
 | |
| type managerAPIImpl struct {
 | |
| 	stmgr.StateManagerAPI
 | |
| 	PaychAPI
 | |
| }
 | |
| 
 | |
| type Manager struct {
 | |
| 	// The Manager context is used to terminate wait operations on shutdown
 | |
| 	ctx      context.Context
 | |
| 	shutdown context.CancelFunc
 | |
| 
 | |
| 	store  *Store
 | |
| 	sa     *stateAccessor
 | |
| 	pchapi managerAPI
 | |
| 
 | |
| 	lk       sync.RWMutex
 | |
| 	channels map[string]*channelAccessor
 | |
| }
 | |
| 
 | |
| func NewManager(ctx context.Context, shutdown func(), sm stmgr.StateManagerAPI, pchstore *Store, api PaychAPI) *Manager {
 | |
| 	impl := &managerAPIImpl{StateManagerAPI: sm, PaychAPI: api}
 | |
| 	return &Manager{
 | |
| 		ctx:      ctx,
 | |
| 		shutdown: shutdown,
 | |
| 		store:    pchstore,
 | |
| 		sa:       &stateAccessor{sm: impl},
 | |
| 		channels: make(map[string]*channelAccessor),
 | |
| 		pchapi:   impl,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // newManager is used by the tests to supply mocks
 | |
| func newManager(pchstore *Store, pchapi managerAPI) (*Manager, error) {
 | |
| 	pm := &Manager{
 | |
| 		store:    pchstore,
 | |
| 		sa:       &stateAccessor{sm: pchapi},
 | |
| 		channels: make(map[string]*channelAccessor),
 | |
| 		pchapi:   pchapi,
 | |
| 	}
 | |
| 	pm.ctx, pm.shutdown = context.WithCancel(context.Background())
 | |
| 	return pm, pm.Start()
 | |
| }
 | |
| 
 | |
| // Start restarts tracking of any messages that were sent to chain.
 | |
| func (pm *Manager) Start() error {
 | |
| 	return pm.restartPending(pm.ctx)
 | |
| }
 | |
| 
 | |
| // Stop shuts down any processes used by the manager
 | |
| func (pm *Manager) Stop() error {
 | |
| 	pm.shutdown()
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| type GetOpts struct {
 | |
| 	Reserve  bool
 | |
| 	OffChain bool
 | |
| }
 | |
| 
 | |
| func (pm *Manager) GetPaych(ctx context.Context, from, to address.Address, amt types.BigInt, opts GetOpts) (address.Address, cid.Cid, error) {
 | |
| 	if !opts.Reserve && opts.OffChain {
 | |
| 		return address.Undef, cid.Undef, xerrors.Errorf("can't fund payment channels without on-chain operations")
 | |
| 	}
 | |
| 
 | |
| 	chanAccessor, err := pm.accessorByFromTo(from, to)
 | |
| 	if err != nil {
 | |
| 		return address.Undef, cid.Undef, err
 | |
| 	}
 | |
| 
 | |
| 	return chanAccessor.getPaych(ctx, amt, opts)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) AvailableFunds(ctx context.Context, ch address.Address) (*api.ChannelAvailableFunds, error) {
 | |
| 	ca, err := pm.accessorByAddress(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	ci, err := ca.getChannelInfo(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return ca.availableFunds(ctx, ci.ChannelID)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) AvailableFundsByFromTo(ctx context.Context, from address.Address, to address.Address) (*api.ChannelAvailableFunds, error) {
 | |
| 	ca, err := pm.accessorByFromTo(from, to)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	ci, err := ca.outboundActiveByFromTo(ctx, from, to)
 | |
| 	if err == ErrChannelNotTracked {
 | |
| 		// If there is no active channel between from / to we still want to
 | |
| 		// return an empty ChannelAvailableFunds, so that clients can check
 | |
| 		// for the existence of a channel between from / to without getting
 | |
| 		// an error.
 | |
| 		return &api.ChannelAvailableFunds{
 | |
| 			Channel:             nil,
 | |
| 			From:                from,
 | |
| 			To:                  to,
 | |
| 			ConfirmedAmt:        types.NewInt(0),
 | |
| 			PendingAmt:          types.NewInt(0),
 | |
| 			NonReservedAmt:      types.NewInt(0),
 | |
| 			PendingAvailableAmt: types.NewInt(0),
 | |
| 			PendingWaitSentinel: nil,
 | |
| 			QueuedAmt:           types.NewInt(0),
 | |
| 			VoucherReedeemedAmt: types.NewInt(0),
 | |
| 		}, nil
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return ca.availableFunds(ctx, ci.ChannelID)
 | |
| }
 | |
| 
 | |
| // GetPaychWaitReady waits until the create channel / add funds message with the
 | |
| // given message CID arrives.
 | |
| // The returned channel address can safely be used against the Manager methods.
 | |
| func (pm *Manager) GetPaychWaitReady(ctx context.Context, mcid cid.Cid) (address.Address, error) {
 | |
| 	// Find the channel associated with the message CID
 | |
| 	pm.lk.Lock()
 | |
| 	ci, err := pm.store.ByMessageCid(ctx, mcid)
 | |
| 	pm.lk.Unlock()
 | |
| 
 | |
| 	if err != nil {
 | |
| 		if err == datastore.ErrNotFound {
 | |
| 			return address.Undef, xerrors.Errorf("Could not find wait msg cid %s", mcid)
 | |
| 		}
 | |
| 		return address.Undef, err
 | |
| 	}
 | |
| 
 | |
| 	chanAccessor, err := pm.accessorByFromTo(ci.Control, ci.Target)
 | |
| 	if err != nil {
 | |
| 		return address.Undef, err
 | |
| 	}
 | |
| 
 | |
| 	return chanAccessor.getPaychWaitReady(ctx, mcid)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) ListChannels(ctx context.Context) ([]address.Address, error) {
 | |
| 	// Need to take an exclusive lock here so that channel operations can't run
 | |
| 	// in parallel (see channelLock)
 | |
| 	pm.lk.Lock()
 | |
| 	defer pm.lk.Unlock()
 | |
| 
 | |
| 	return pm.store.ListChannels(ctx)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) GetChannelInfo(ctx context.Context, addr address.Address) (*ChannelInfo, error) {
 | |
| 	ca, err := pm.accessorByAddress(ctx, addr)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return ca.getChannelInfo(ctx, addr)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) CreateVoucher(ctx context.Context, ch address.Address, voucher paychtypes.SignedVoucher) (*api.VoucherCreateResult, error) {
 | |
| 	ca, err := pm.accessorByAddress(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return ca.createVoucher(ctx, ch, voucher)
 | |
| }
 | |
| 
 | |
| // CheckVoucherValid checks if the given voucher is valid (is or could become spendable at some point).
 | |
| // If the channel is not in the store, fetches the channel from state (and checks that
 | |
| // the channel To address is owned by the wallet).
 | |
| func (pm *Manager) CheckVoucherValid(ctx context.Context, ch address.Address, sv *paychtypes.SignedVoucher) error {
 | |
| 	// Get an accessor for the channel, creating it from state if necessary
 | |
| 	ca, err := pm.inboundChannelAccessor(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	_, err = ca.checkVoucherValid(ctx, ch, sv)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // CheckVoucherSpendable checks if the given voucher is currently spendable
 | |
| func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address, sv *paychtypes.SignedVoucher, secret []byte, proof []byte) (bool, error) {
 | |
| 	if len(proof) > 0 {
 | |
| 		return false, errProofNotSupported
 | |
| 	}
 | |
| 	ca, err := pm.accessorByAddress(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 
 | |
| 	return ca.checkVoucherSpendable(ctx, ch, sv, secret)
 | |
| }
 | |
| 
 | |
| // AddVoucherOutbound adds a voucher for an outbound channel.
 | |
| // Returns an error if the channel is not already in the store.
 | |
| func (pm *Manager) AddVoucherOutbound(ctx context.Context, ch address.Address, sv *paychtypes.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) {
 | |
| 	if len(proof) > 0 {
 | |
| 		return types.NewInt(0), errProofNotSupported
 | |
| 	}
 | |
| 	ca, err := pm.accessorByAddress(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return types.NewInt(0), err
 | |
| 	}
 | |
| 	return ca.addVoucher(ctx, ch, sv, minDelta)
 | |
| }
 | |
| 
 | |
| // AddVoucherInbound adds a voucher for an inbound channel.
 | |
| // If the channel is not in the store, fetches the channel from state (and checks that
 | |
| // the channel To address is owned by the wallet).
 | |
| func (pm *Manager) AddVoucherInbound(ctx context.Context, ch address.Address, sv *paychtypes.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) {
 | |
| 	if len(proof) > 0 {
 | |
| 		return types.NewInt(0), errProofNotSupported
 | |
| 	}
 | |
| 	// Get an accessor for the channel, creating it from state if necessary
 | |
| 	ca, err := pm.inboundChannelAccessor(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return types.BigInt{}, err
 | |
| 	}
 | |
| 	return ca.addVoucher(ctx, ch, sv, minDelta)
 | |
| }
 | |
| 
 | |
| // inboundChannelAccessor gets an accessor for the given channel. The channel
 | |
| // must either exist in the store, or be an inbound channel that can be created
 | |
| // from state.
 | |
| func (pm *Manager) inboundChannelAccessor(ctx context.Context, ch address.Address) (*channelAccessor, error) {
 | |
| 	// Make sure channel is in store, or can be fetched from state, and that
 | |
| 	// the channel To address is owned by the wallet
 | |
| 	ci, err := pm.trackInboundChannel(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// This is an inbound channel, so To is the Control address (this node)
 | |
| 	from := ci.Target
 | |
| 	to := ci.Control
 | |
| 	return pm.accessorByFromTo(from, to)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) trackInboundChannel(ctx context.Context, ch address.Address) (*ChannelInfo, error) {
 | |
| 	// Need to take an exclusive lock here so that channel operations can't run
 | |
| 	// in parallel (see channelLock)
 | |
| 	pm.lk.Lock()
 | |
| 	defer pm.lk.Unlock()
 | |
| 
 | |
| 	// Check if channel is in store
 | |
| 	ci, err := pm.store.ByAddress(ctx, ch)
 | |
| 	if err == nil {
 | |
| 		// Channel is in store, so it's already being tracked
 | |
| 		return ci, nil
 | |
| 	}
 | |
| 
 | |
| 	// If there's an error (besides channel not in store) return err
 | |
| 	if err != ErrChannelNotTracked {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Channel is not in store, so get channel from state
 | |
| 	stateCi, err := pm.sa.loadStateChannelInfo(ctx, ch, DirInbound)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Check that channel To address is in wallet
 | |
| 	to := stateCi.Control // Inbound channel so To addr is Control (this node)
 | |
| 	toKey, err := pm.pchapi.StateAccountKey(ctx, to, types.EmptyTSK)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	has, err := pm.pchapi.WalletHas(ctx, toKey)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if !has {
 | |
| 		msg := "cannot add voucher for channel %s: wallet does not have key for address %s"
 | |
| 		return nil, xerrors.Errorf(msg, ch, to)
 | |
| 	}
 | |
| 
 | |
| 	// Save channel to store
 | |
| 	return pm.store.TrackChannel(ctx, stateCi)
 | |
| }
 | |
| 
 | |
| // TODO: secret vs proof doesn't make sense, there is only one, not two
 | |
| func (pm *Manager) SubmitVoucher(ctx context.Context, ch address.Address, sv *paychtypes.SignedVoucher, secret []byte, proof []byte) (cid.Cid, error) {
 | |
| 	if len(proof) > 0 {
 | |
| 		return cid.Undef, errProofNotSupported
 | |
| 	}
 | |
| 	ca, err := pm.accessorByAddress(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return cid.Undef, err
 | |
| 	}
 | |
| 	return ca.submitVoucher(ctx, ch, sv, secret)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) AllocateLane(ctx context.Context, ch address.Address) (uint64, error) {
 | |
| 	ca, err := pm.accessorByAddress(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	return ca.allocateLane(ctx, ch)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) ListVouchers(ctx context.Context, ch address.Address) ([]*VoucherInfo, error) {
 | |
| 	ca, err := pm.accessorByAddress(ctx, ch)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return ca.listVouchers(ctx, ch)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) Settle(ctx context.Context, addr address.Address) (cid.Cid, error) {
 | |
| 	ca, err := pm.accessorByAddress(ctx, addr)
 | |
| 	if err != nil {
 | |
| 		return cid.Undef, err
 | |
| 	}
 | |
| 	return ca.settle(ctx, addr)
 | |
| }
 | |
| 
 | |
| func (pm *Manager) Collect(ctx context.Context, addr address.Address) (cid.Cid, error) {
 | |
| 	ca, err := pm.accessorByAddress(ctx, addr)
 | |
| 	if err != nil {
 | |
| 		return cid.Undef, err
 | |
| 	}
 | |
| 	return ca.collect(ctx, addr)
 | |
| }
 |