diff --git a/api/api.go b/api/api.go index 9610ec674..45bd2c1b3 100644 --- a/api/api.go +++ b/api/api.go @@ -114,7 +114,7 @@ type FullNode interface { PaychVoucherCheckValid(context.Context, address.Address, *types.SignedVoucher) error PaychVoucherCheckSpendable(context.Context, address.Address, *types.SignedVoucher, []byte, []byte) (bool, error) PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*types.SignedVoucher, error) - PaychVoucherAdd(context.Context, address.Address, *types.SignedVoucher) error + PaychVoucherAdd(context.Context, address.Address, *types.SignedVoucher, []byte) error PaychVoucherList(context.Context, address.Address) ([]*types.SignedVoucher, error) PaychVoucherSubmit(context.Context, address.Address, *types.SignedVoucher) (cid.Cid, error) } diff --git a/api/struct.go b/api/struct.go index aeb1857d9..1c5311f1f 100644 --- a/api/struct.go +++ b/api/struct.go @@ -86,7 +86,7 @@ type FullNodeStruct struct { PaychVoucherCheck func(context.Context, *types.SignedVoucher) error `perm:"read"` PaychVoucherCheckValid func(context.Context, address.Address, *types.SignedVoucher) error `perm:"read"` PaychVoucherCheckSpendable func(context.Context, address.Address, *types.SignedVoucher, []byte, []byte) (bool, error) `perm:"read"` - PaychVoucherAdd func(context.Context, address.Address, *types.SignedVoucher) error `perm:"write"` + PaychVoucherAdd func(context.Context, address.Address, *types.SignedVoucher, []byte) error `perm:"write"` PaychVoucherCreate func(context.Context, address.Address, types.BigInt, uint64) (*types.SignedVoucher, error) `perm:"sign"` PaychVoucherList func(context.Context, address.Address) ([]*types.SignedVoucher, error) `perm:"write"` PaychVoucherSubmit func(context.Context, address.Address, *types.SignedVoucher) (cid.Cid, error) `perm:"sign"` @@ -303,8 +303,8 @@ func (c *FullNodeStruct) PaychVoucherCheckSpendable(ctx context.Context, addr ad return c.Internal.PaychVoucherCheckSpendable(ctx, addr, sv, secret, proof) } -func (c *FullNodeStruct) PaychVoucherAdd(ctx context.Context, addr address.Address, sv *types.SignedVoucher) error { - return c.Internal.PaychVoucherAdd(ctx, addr, sv) +func (c *FullNodeStruct) PaychVoucherAdd(ctx context.Context, addr address.Address, sv *types.SignedVoucher, proof []byte) error { + return c.Internal.PaychVoucherAdd(ctx, addr, sv, proof) } func (c *FullNodeStruct) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*types.SignedVoucher, error) { diff --git a/chain/types/voucher.go b/chain/types/voucher.go index 5403572e5..dc92f36a0 100644 --- a/chain/types/voucher.go +++ b/chain/types/voucher.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "encoding/base64" "github.com/filecoin-project/go-lotus/chain/address" @@ -42,6 +43,24 @@ func (sv *SignedVoucher) EncodedString() (string, error) { return base64.RawURLEncoding.EncodeToString(data), nil } +func (sv *SignedVoucher) Equals(other *SignedVoucher) bool { + // TODO: make this less bad + + selfB, err := cbor.DumpObject(sv) + if err != nil { + log.Errorf("SignedVoucher.Equals: dump self: %s", err) + return false + } + + otherB, err := cbor.DumpObject(other) + if err != nil { + log.Errorf("SignedVoucher.Equals: dump other: %s", err) + return false + } + + return bytes.Equal(selfB, otherB) +} + func DecodeSignedVoucher(s string) (*SignedVoucher, error) { data, err := base64.RawURLEncoding.DecodeString(s) if err != nil { diff --git a/node/impl/full/paych.go b/node/impl/full/paych.go index eebd91350..e86f158e1 100644 --- a/node/impl/full/paych.go +++ b/node/impl/full/paych.go @@ -154,14 +154,14 @@ func (a *PaychAPI) PaychVoucherCheckSpendable(ctx context.Context, ch address.Ad return a.PaychMgr.CheckVoucherSpendable(ctx, ch, sv, secret, proof) } -func (a *PaychAPI) PaychVoucherAdd(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error { - _ = a.PaychMgr.TrackInboundChannel(ctx, ch) +func (a *PaychAPI) PaychVoucherAdd(ctx context.Context, ch address.Address, sv *types.SignedVoucher, proof []byte) error { + _ = a.PaychMgr.TrackInboundChannel(ctx, ch) // TODO: expose those calls if err := a.PaychVoucherCheckValid(ctx, ch, sv); err != nil { return err } - return a.PaychMgr.AddVoucher(ctx, ch, sv) + return a.PaychMgr.AddVoucher(ctx, ch, sv, proof) } // PaychVoucherCreate creates a new signed voucher on the given payment channel @@ -199,7 +199,7 @@ func (a *PaychAPI) paychVoucherCreate(ctx context.Context, pch address.Address, sv.Signature = sig - if err := a.PaychMgr.AddVoucher(ctx, pch, sv); err != nil { + if err := a.PaychMgr.AddVoucher(ctx, pch, sv, nil); err != nil { return nil, xerrors.Errorf("failed to persist voucher: %w", err) } @@ -207,7 +207,17 @@ func (a *PaychAPI) paychVoucherCreate(ctx context.Context, pch address.Address, } func (a *PaychAPI) PaychVoucherList(ctx context.Context, pch address.Address) ([]*types.SignedVoucher, error) { - return a.PaychMgr.ListVouchers(ctx, pch) + vi, err := a.PaychMgr.ListVouchers(ctx, pch) + if err != nil { + return nil, err + } + + out := make([]*types.SignedVoucher, len(vi)) + for k, v := range vi { + out[k] = v.Voucher + } + + return out, nil } func (a *PaychAPI) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *types.SignedVoucher) (cid.Cid, error) { diff --git a/paych/paych.go b/paych/paych.go index 43b75a8c7..358d0720d 100644 --- a/paych/paych.go +++ b/paych/paych.go @@ -4,16 +4,19 @@ import ( "context" "fmt" + hamt "github.com/ipfs/go-hamt-ipld" + 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/state" "github.com/filecoin-project/go-lotus/chain/store" "github.com/filecoin-project/go-lotus/chain/types" "github.com/filecoin-project/go-lotus/chain/vm" - - hamt "github.com/ipfs/go-hamt-ipld" ) +var log = logging.Logger("paych") + type Manager struct { chain *store.ChainStore store *Store @@ -118,6 +121,24 @@ func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address 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, @@ -186,15 +207,15 @@ func (pm *Manager) getPaychOwner(ctx context.Context, ch address.Address) (addre return address.NewFromBytes(ret.Return) } -func (pm *Manager) AddVoucher(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error { +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) + return pm.store.AddVoucher(ch, sv, proof) } -func (pm *Manager) ListVouchers(ctx context.Context, ch address.Address) ([]*types.SignedVoucher, error) { +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) @@ -208,9 +229,9 @@ func (pm *Manager) NextNonceForLane(ctx context.Context, ch address.Address, lan var maxnonce uint64 for _, v := range vouchers { - if v.Lane == lane { - if v.Nonce > maxnonce { - maxnonce = v.Nonce + if v.Voucher.Lane == lane { + if v.Voucher.Nonce > maxnonce { + maxnonce = v.Voucher.Nonce } } } diff --git a/paych/store.go b/paych/store.go index 4356a429e..20077075e 100644 --- a/paych/store.go +++ b/paych/store.go @@ -1,6 +1,7 @@ package paych import ( + "bytes" "errors" "fmt" "strings" @@ -18,6 +19,7 @@ import ( var ErrChannelNotTracked = errors.New("channel not tracked") func init() { + cbor.RegisterCborType(VoucherInfo{}) cbor.RegisterCborType(ChannelInfo{}) } @@ -37,11 +39,16 @@ const ( DirOutbound = 2 ) +type VoucherInfo struct { + Voucher *types.SignedVoucher + Proof []byte +} + type ChannelInfo struct { Channel address.Address ControlAddr address.Address Direction int - Vouchers []*types.SignedVoucher + Vouchers []*VoucherInfo } func dskeyForChannel(addr address.Address) datastore.Key { @@ -118,18 +125,53 @@ func (ps *Store) ListChannels() ([]address.Address, error) { return out, nil } -func (ps *Store) AddVoucher(ch address.Address, sv *types.SignedVoucher) error { +func (ps *Store) AddVoucher(ch address.Address, sv *types.SignedVoucher, proof []byte) error { ci, err := ps.getChannelInfo(ch) if err != nil { return err } - ci.Vouchers = append(ci.Vouchers, sv) + svs, err := sv.EncodedString() + if err != nil { + return err + } + + // look for duplicates + for i, v := range ci.Vouchers { + osvs, err := v.Voucher.EncodedString() + if err != nil { + return err + } + if osvs != svs { + continue + } + if v.Proof != nil { + if !bytes.Equal(v.Proof, proof) { + log.Warnf("AddVoucher: multiple proofs for single voucher: v:'%s', storing both", svs) + break + } + log.Warnf("AddVoucher: voucher re-added with matching proof: v:'%s'", svs) + return nil + } + + log.Warnf("AddVoucher: adding proof to stored voucher") + ci.Vouchers[i] = &VoucherInfo{ + Voucher: v.Voucher, + Proof: proof, + } + + return ps.putChannelInfo(ci) + } + + ci.Vouchers = append(ci.Vouchers, &VoucherInfo{ + Voucher: sv, + Proof: proof, + }) return ps.putChannelInfo(ci) } -func (ps *Store) VouchersForPaych(ch address.Address) ([]*types.SignedVoucher, error) { +func (ps *Store) VouchersForPaych(ch address.Address) ([]*VoucherInfo, error) { ci, err := ps.getChannelInfo(ch) if err != nil { return nil, err diff --git a/paych/watcher.go b/paych/watcher.go new file mode 100644 index 000000000..943a4af7c --- /dev/null +++ b/paych/watcher.go @@ -0,0 +1,29 @@ +package paych + +import ( + "github.com/filecoin-project/go-lotus/chain/address" + "github.com/filecoin-project/go-lotus/chain/events" + "github.com/filecoin-project/go-lotus/chain/types" +) + +type watchedLane struct { + bestVoucher *types.SignedVoucher + closed bool +} + +type inboundWatcher struct { + ev *events.Events + + paych *address.Address + + control address.Address + + lanes map[uint64]*watchedLane + + closeAt uint64 // at what H do we plan to call close + collectAt uint64 // at what H do we plan to call collect + +} + + +