package paychmgr import ( "bytes" "context" "github.com/filecoin-project/specs-actors/actors/builtin" init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" "github.com/filecoin-project/specs-actors/actors/builtin/paych" "github.com/ipfs/go-cid" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/types" ) func (pm *Manager) createPaych(ctx context.Context, from, to address.Address, amt types.BigInt) (cid.Cid, error) { params, aerr := actors.SerializeParams(&paych.ConstructorParams{From: from, To: to}) if aerr != nil { return cid.Undef, aerr } enc, aerr := actors.SerializeParams(&init_.ExecParams{ CodeCID: builtin.PaymentChannelActorCodeID, ConstructorParams: params, }) if aerr != nil { return cid.Undef, aerr } msg := &types.Message{ To: builtin.InitActorAddr, From: from, Value: amt, Method: builtin.MethodsInit.Exec, Params: enc, GasLimit: 1000000, GasPrice: types.NewInt(0), } smsg, err := pm.mpool.MpoolPushMessage(ctx, msg) if err != nil { return cid.Undef, xerrors.Errorf("initializing paych actor: %w", err) } mcid := smsg.Cid() go pm.waitForPaychCreateMsg(ctx, mcid) return mcid, nil } // WaitForPaychCreateMsg waits for mcid to appear on chain and returns the robust address of the // created payment channel // TODO: wait outside the store lock! // (tricky because we need to setup channel tracking before we know its address) func (pm *Manager) waitForPaychCreateMsg(ctx context.Context, mcid cid.Cid) { defer pm.store.lk.Unlock() mwait, err := pm.state.StateWaitMsg(ctx, mcid) if err != nil { log.Errorf("wait msg: %w", err) return } if mwait.Receipt.ExitCode != 0 { log.Errorf("payment channel creation failed (exit code %d)", mwait.Receipt.ExitCode) return } var decodedReturn init_.ExecReturn err = decodedReturn.UnmarshalCBOR(bytes.NewReader(mwait.Receipt.Return)) if err != nil { log.Error(err) return } paychaddr := decodedReturn.RobustAddress ci, err := pm.loadOutboundChannelInfo(ctx, paychaddr) if err != nil { log.Errorf("loading channel info: %w", err) return } if err := pm.store.trackChannel(ci); err != nil { log.Errorf("tracking channel: %w", err) } } func (pm *Manager) addFunds(ctx context.Context, ch address.Address, from address.Address, amt types.BigInt) (cid.Cid, error) { msg := &types.Message{ To: ch, From: from, Value: amt, Method: 0, GasLimit: 1000000, GasPrice: types.NewInt(0), } smsg, err := pm.mpool.MpoolPushMessage(ctx, msg) if err != nil { return cid.Undef, err } mcid := smsg.Cid() go pm.waitForAddFundsMsg(ctx, mcid) return mcid, nil } // WaitForAddFundsMsg waits for mcid to appear on chain and returns error, if any // TODO: wait outside the store lock! // (tricky because we need to setup channel tracking before we know it's address) func (pm *Manager) waitForAddFundsMsg(ctx context.Context, mcid cid.Cid) { defer pm.store.lk.Unlock() mwait, err := pm.state.StateWaitMsg(ctx, mcid) if err != nil { log.Error(err) } if mwait.Receipt.ExitCode != 0 { log.Errorf("voucher channel creation failed: adding funds (exit code %d)", mwait.Receipt.ExitCode) } } func (pm *Manager) GetPaych(ctx context.Context, from, to address.Address, ensureFree types.BigInt) (address.Address, cid.Cid, error) { pm.store.lk.Lock() // unlock only on err; wait funcs will defer unlock var mcid cid.Cid ch, err := pm.store.findChan(func(ci *ChannelInfo) bool { if ci.Direction != DirOutbound { return false } return ci.Control == from && ci.Target == to }) if err != nil { pm.store.lk.Unlock() return address.Undef, cid.Undef, xerrors.Errorf("findChan: %w", err) } if ch != address.Undef { // TODO: Track available funds mcid, err = pm.addFunds(ctx, ch, from, ensureFree) } else { mcid, err = pm.createPaych(ctx, from, to, ensureFree) } if err != nil { pm.store.lk.Unlock() } return ch, mcid, err }