lotus/paychmgr/paych.go

358 lines
9.1 KiB
Go
Raw Permalink Normal View History

package paychmgr
2019-08-09 21:42:56 +00:00
import (
2019-09-25 12:56:04 +00:00
"bytes"
2019-08-09 21:42:56 +00:00
"context"
"fmt"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/lotus/api"
2020-02-12 23:52:19 +00:00
cborutil "github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/account"
2020-02-12 23:52:19 +00:00
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
"golang.org/x/xerrors"
logging "github.com/ipfs/go-log/v2"
2019-09-16 16:40:26 +00:00
"go.uber.org/fx"
2019-09-09 13:59:07 +00:00
"github.com/filecoin-project/go-address"
2020-02-12 23:52:19 +00:00
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/sigs"
"github.com/filecoin-project/lotus/node/impl/full"
2019-08-09 21:42:56 +00:00
)
2019-09-09 13:59:07 +00:00
var log = logging.Logger("paych")
2019-09-16 16:40:26 +00:00
type ManagerApi struct {
fx.In
full.MpoolAPI
full.WalletAPI
full.StateAPI
2019-09-16 16:40:26 +00:00
}
type StateManagerApi interface {
LoadActorState(ctx context.Context, a address.Address, out interface{}, ts *types.TipSet) (*types.Actor, error)
Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error)
}
type Manager struct {
store *Store
2020-07-09 22:27:39 +00:00
sm StateManagerApi
2019-09-16 13:46:05 +00:00
2019-09-16 16:40:26 +00:00
mpool full.MpoolAPI
wallet full.WalletAPI
state full.StateAPI
2019-08-09 21:42:56 +00:00
}
2019-09-16 16:40:26 +00:00
func NewManager(sm *stmgr.StateManager, pchstore *Store, api ManagerApi) *Manager {
return &Manager{
2019-08-09 21:42:56 +00:00
store: pchstore,
sm: sm,
2019-09-16 16:40:26 +00:00
2019-09-16 21:26:19 +00:00
mpool: api.MpoolAPI,
2019-09-16 16:40:26 +00:00
wallet: api.WalletAPI,
state: api.StateAPI,
2019-08-09 21:42:56 +00:00
}
}
// Used by the tests to supply mocks
func newManager(sm StateManagerApi, pchstore *Store) *Manager {
return &Manager{
store: pchstore,
sm: sm,
}
}
2020-07-09 22:27:39 +00:00
func (pm *Manager) TrackOutboundChannel(ctx context.Context, ch address.Address) error {
return pm.trackChannel(ctx, ch, DirOutbound)
}
func (pm *Manager) TrackInboundChannel(ctx context.Context, ch address.Address) error {
2020-07-09 22:27:39 +00:00
return pm.trackChannel(ctx, ch, DirInbound)
2019-09-16 17:23:48 +00:00
}
2020-07-09 22:27:39 +00:00
func (pm *Manager) trackChannel(ctx context.Context, ch address.Address, dir uint64) error {
ci, err := pm.loadStateChannelInfo(ctx, ch, dir)
2019-09-16 17:23:48 +00:00
if err != nil {
return err
}
return pm.store.TrackChannel(ci)
}
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)
2019-08-09 21:42:56 +00:00
}
2020-07-09 22:49:43 +00:00
// CheckVoucherValid checks if the given voucher is valid (is or could become spendable at some point)
2020-02-25 21:09:22 +00:00
func (pm *Manager) CheckVoucherValid(ctx context.Context, ch address.Address, sv *paych.SignedVoucher) error {
2020-07-09 22:27:39 +00:00
_, err := pm.checkVoucherValid(ctx, ch, sv)
return err
}
func (pm *Manager) checkVoucherValid(ctx context.Context, ch address.Address, sv *paych.SignedVoucher) (map[uint64]*paych.LaneState, error) {
2020-07-15 09:12:03 +00:00
if sv.ChannelAddr != ch {
return nil, xerrors.Errorf("voucher ChannelAddr doesn't match channel address, got %s, expected %s", sv.ChannelAddr, ch)
}
act, pchState, err := pm.loadPaychState(ctx, ch)
2019-08-09 21:42:56 +00:00
if err != nil {
2020-07-09 22:27:39 +00:00
return nil, err
2019-08-09 21:42:56 +00:00
}
var account account.State
_, err = pm.sm.LoadActorState(ctx, pchState.From, &account, nil)
if err != nil {
2020-07-09 22:27:39 +00:00
return nil, err
}
from := account.Address
2019-08-09 21:42:56 +00:00
// verify signature
vb, err := sv.SigningBytes()
if err != nil {
2020-07-09 22:27:39 +00:00
return nil, err
2019-08-09 21:42:56 +00:00
}
2019-08-13 18:47:40 +00:00
// 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 := sigs.Verify(sv.Signature, from, vb); err != nil {
2020-07-09 22:27:39 +00:00
return nil, err
2019-08-09 21:42:56 +00:00
}
2020-07-09 22:27:39 +00:00
// Check the voucher against the highest known voucher nonce / value
laneStates, err := pm.laneState(pchState, ch)
2020-07-09 22:27:39 +00:00
if err != nil {
return nil, err
}
// If the new voucher nonce value is less than the highest known
// nonce for the lane
ls, lsExists := laneStates[sv.Lane]
if lsExists && sv.Nonce <= ls.Nonce {
2020-07-09 22:27:39 +00:00
return nil, fmt.Errorf("nonce too low")
}
2020-07-09 22:27:39 +00:00
// If the voucher amount is less than the highest known voucher amount
if lsExists && sv.Amount.LessThanEqual(ls.Redeemed) {
2020-07-09 22:27:39 +00:00
return nil, fmt.Errorf("voucher amount is lower than amount for voucher with lower nonce")
2019-08-09 21:42:56 +00:00
}
// Total redeemed is the total redeemed amount for all lanes, including
// the new voucher
// eg
//
// lane 1 redeemed: 3
// lane 2 redeemed: 2
// voucher for lane 1: 5
//
// Voucher supersedes lane 1 redeemed, therefore
// effective lane 1 redeemed: 5
//
// lane 1: 5
// lane 2: 2
// -
// total: 7
totalRedeemed, err := pm.totalRedeemedWithVoucher(laneStates, sv)
if err != nil {
return nil, err
}
2020-07-09 22:27:39 +00:00
// Total required balance = total redeemed + toSend
// Must not exceed actor balance
newTotal := types.BigAdd(totalRedeemed, pchState.ToSend)
2019-09-23 17:11:44 +00:00
if act.Balance.LessThan(newTotal) {
2020-07-09 22:27:39 +00:00
return nil, fmt.Errorf("not enough funds in channel to cover voucher")
2019-08-09 21:42:56 +00:00
}
if len(sv.Merges) != 0 {
2020-07-09 22:27:39 +00:00
return nil, fmt.Errorf("dont currently support paych lane merges")
2019-08-09 21:42:56 +00:00
}
return laneStates, nil
2019-08-09 21:42:56 +00:00
}
2020-07-09 22:49:43 +00:00
// CheckVoucherSpendable checks if the given voucher is currently spendable
2020-02-25 21:09:22 +00:00
func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (bool, error) {
2019-08-09 21:42:56 +00:00
owner, err := pm.getPaychOwner(ctx, ch)
if err != nil {
return false, err
}
2019-09-09 13:59:07 +00:00
if sv.Extra != nil && proof == nil {
known, err := pm.ListVouchers(ctx, ch)
if err != nil {
return false, err
}
for _, v := range known {
2020-02-12 23:52:19 +00:00
eq, err := cborutil.Equals(v.Voucher, sv)
if err != nil {
return false, err
}
if v.Proof != nil && eq {
2019-09-09 13:59:07 +00:00
log.Info("CheckVoucherSpendable: using stored proof")
proof = v.Proof
break
}
}
if proof == nil {
log.Warn("CheckVoucherSpendable: nil proof for voucher with validation")
}
}
2020-02-12 23:52:19 +00:00
enc, err := actors.SerializeParams(&paych.UpdateChannelStateParams{
2019-08-09 21:42:56 +00:00
Sv: *sv,
Secret: secret,
Proof: proof,
})
if err != nil {
return false, err
}
2019-09-09 20:03:10 +00:00
ret, err := pm.sm.Call(ctx, &types.Message{
2019-08-09 21:42:56 +00:00
From: owner,
To: ch,
2020-02-12 23:52:19 +00:00
Method: builtin.MethodsPaych.UpdateChannelState,
2019-08-09 21:42:56 +00:00
Params: enc,
}, nil)
if err != nil {
return false, err
}
if ret.MsgRct.ExitCode != 0 {
2019-08-09 21:42:56 +00:00
return false, nil
}
return true, nil
}
func (pm *Manager) getPaychOwner(ctx context.Context, ch address.Address) (address.Address, error) {
2020-02-12 23:52:19 +00:00
var state paych.State
if _, err := pm.sm.LoadActorState(ctx, ch, &state, nil); err != nil {
return address.Address{}, err
2019-08-09 21:42:56 +00:00
}
2020-02-12 23:52:19 +00:00
return state.From, nil
2019-08-09 21:42:56 +00:00
}
2020-02-25 21:09:22 +00:00
func (pm *Manager) AddVoucher(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) {
2019-09-25 12:56:04 +00:00
pm.store.lk.Lock()
defer pm.store.lk.Unlock()
ci, err := pm.store.getChannelInfo(ch)
if err != nil {
return types.NewInt(0), err
}
2020-07-09 22:27:39 +00:00
// Check if the voucher has already been added
2019-09-25 12:56:04 +00:00
for i, v := range ci.Vouchers {
eq, err := cborutil.Equals(sv, v.Voucher)
2020-02-12 23:52:19 +00:00
if err != nil {
return types.BigInt{}, err
}
if !eq {
2019-09-25 12:56:04 +00:00
continue
}
2020-07-09 22:27:39 +00:00
// This is a duplicate voucher.
// Update the proof on the existing voucher
if len(proof) > 0 && !bytes.Equal(v.Proof, proof) {
log.Warnf("AddVoucher: adding proof to stored voucher")
ci.Vouchers[i] = &VoucherInfo{
Voucher: v.Voucher,
Proof: proof,
2019-09-25 12:56:04 +00:00
}
2020-07-09 22:27:39 +00:00
return types.NewInt(0), pm.store.putChannelInfo(ci)
2019-09-25 12:56:04 +00:00
}
2020-07-09 22:27:39 +00:00
// Otherwise just ignore the duplicate voucher
log.Warnf("AddVoucher: voucher re-added with matching proof")
return types.NewInt(0), nil
}
// Check voucher validity
laneStates, err := pm.checkVoucherValid(ctx, ch, sv)
2020-07-09 22:27:39 +00:00
if err != nil {
return types.NewInt(0), err
}
// The change in value is the delta between the voucher amount and
// the highest previous voucher amount for the lane
laneState, exists := laneStates[sv.Lane]
redeemed := big.NewInt(0)
if exists {
redeemed = laneState.Redeemed
2019-09-25 12:56:04 +00:00
}
delta := types.BigSub(sv.Amount, redeemed)
2019-09-25 12:56:04 +00:00
if minDelta.GreaterThan(delta) {
return delta, xerrors.Errorf("addVoucher: supplied token amount too low; minD=%s, D=%s; laneAmt=%s; v.Amt=%s", minDelta, delta, redeemed, sv.Amount)
2019-09-25 12:56:04 +00:00
}
ci.Vouchers = append(ci.Vouchers, &VoucherInfo{
Voucher: sv,
Proof: proof,
})
2020-07-09 21:20:17 +00:00
if ci.NextLane <= sv.Lane {
2020-02-21 17:26:44 +00:00
ci.NextLane = sv.Lane + 1
2019-09-25 12:56:04 +00:00
}
return delta, pm.store.putChannelInfo(ci)
}
2019-08-12 19:51:01 +00:00
2020-02-21 17:26:44 +00:00
func (pm *Manager) AllocateLane(ch address.Address) (uint64, error) {
// TODO: should this take into account lane state?
return pm.store.AllocateLane(ch)
}
2019-09-09 13:59:07 +00:00
func (pm *Manager) ListVouchers(ctx context.Context, ch address.Address) ([]*VoucherInfo, error) {
2019-08-12 19:51:01 +00:00
// 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) OutboundChanTo(from, to address.Address) (address.Address, error) {
2019-09-16 13:46:05 +00:00
pm.store.lk.Lock()
defer pm.store.lk.Unlock()
return pm.store.findChan(func(ci *ChannelInfo) bool {
if ci.Direction != DirOutbound {
return false
}
return ci.Control == from && ci.Target == to
})
}
2019-08-12 19:51:01 +00:00
func (pm *Manager) NextNonceForLane(ctx context.Context, ch address.Address, lane uint64) (uint64, error) {
// TODO: should this take into account lane state?
2019-08-12 19:51:01 +00:00
vouchers, err := pm.store.VouchersForPaych(ch)
if err != nil {
return 0, err
}
var maxnonce uint64
for _, v := range vouchers {
2020-07-09 22:49:43 +00:00
if v.Voucher.Lane == lane {
if v.Voucher.Nonce > maxnonce {
maxnonce = v.Voucher.Nonce
2019-08-12 19:51:01 +00:00
}
}
}
return maxnonce + 1, nil
2019-08-12 19:51:01 +00:00
}