diff --git a/chain/actors/builtin/paych/mock/mock.go b/chain/actors/builtin/paych/mock/mock.go new file mode 100644 index 000000000..31f7fba93 --- /dev/null +++ b/chain/actors/builtin/paych/mock/mock.go @@ -0,0 +1,89 @@ +package mock + +import ( + "io" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" +) + +type mockState struct { + from address.Address + to address.Address + settlingAt abi.ChainEpoch + toSend abi.TokenAmount + lanes map[uint64]paych.LaneState +} + +type mockLaneState struct { + redeemed big.Int + nonce uint64 +} + +// NewMockPayChState constructs a state for a payment channel with the set fixed values +// that satisfies the paych.State interface. +func NewMockPayChState(from address.Address, + to address.Address, + settlingAt abi.ChainEpoch, + toSend abi.TokenAmount, + lanes map[uint64]paych.LaneState, +) paych.State { + return &mockState{from, to, settlingAt, toSend, lanes} +} + +// NewMockLaneState constructs a state for a payment channel lane with the set fixed values +// that satisfies the paych.LaneState interface. Useful for populating lanes when +// calling NewMockPayChState +func NewMockLaneState(redeemed big.Int, nonce uint64) paych.LaneState { + return &mockLaneState{redeemed, nonce} +} + +func (ms *mockState) MarshalCBOR(io.Writer) error { + panic("not implemented") +} + +// Channel owner, who has funded the actor +func (ms *mockState) From() address.Address { + return ms.from +} + +// Recipient of payouts from channel +func (ms *mockState) To() address.Address { + return ms.to +} + +// Height at which the channel can be `Collected` +func (ms *mockState) SettlingAt() abi.ChainEpoch { + return ms.settlingAt +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (ms *mockState) ToSend() abi.TokenAmount { + return ms.toSend +} + +// Get total number of lanes +func (ms *mockState) LaneCount() (uint64, error) { + return uint64(len(ms.lanes)), nil +} + +// Iterate lane states +func (ms *mockState) ForEachLaneState(cb func(idx uint64, dl paych.LaneState) error) error { + var lastErr error + for lane, state := range ms.lanes { + if err := cb(lane, state); err != nil { + lastErr = err + } + } + return lastErr +} + +func (mls *mockLaneState) Redeemed() big.Int { + return mls.redeemed +} + +func (mls *mockLaneState) Nonce() uint64 { + return mls.nonce +} diff --git a/chain/actors/builtin/paych/paych.go b/chain/actors/builtin/paych/paych.go new file mode 100644 index 000000000..974d64fde --- /dev/null +++ b/chain/actors/builtin/paych/paych.go @@ -0,0 +1,56 @@ +package paych + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + big "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/cbor" + v0builtin "github.com/filecoin-project/specs-actors/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +// Load returns an abstract copy of payment channel state, irregardless of actor version +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { + case v0builtin.PaymentChannelActorCodeID: + out := v0State{store: store} + err := store.Get(store.Context(), act.Head, &out) + if err != nil { + return nil, err + } + return &out, nil + } + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +// State is an abstract version of payment channel state that works across +// versions +type State interface { + cbor.Marshaler + // Channel owner, who has funded the actor + From() address.Address + // Recipient of payouts from channel + To() address.Address + + // Height at which the channel can be `Collected` + SettlingAt() abi.ChainEpoch + + // Amount successfully redeemed through the payment channel, paid out on `Collect()` + ToSend() abi.TokenAmount + + // Get total number of lanes + LaneCount() (uint64, error) + + // Iterate lane states + ForEachLaneState(cb func(idx uint64, dl LaneState) error) error +} + +// LaneState is an abstract copy of the state of a single lane +type LaneState interface { + Redeemed() big.Int + Nonce() uint64 +} diff --git a/chain/actors/builtin/paych/v0.go b/chain/actors/builtin/paych/v0.go new file mode 100644 index 000000000..16a65bc9b --- /dev/null +++ b/chain/actors/builtin/paych/v0.go @@ -0,0 +1,89 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + big "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/specs-actors/actors/builtin/paych" + v0adt "github.com/filecoin-project/specs-actors/actors/util/adt" +) + +type v0State struct { + paych.State + store adt.Store + lsAmt *v0adt.Array +} + +// Channel owner, who has funded the actor +func (s *v0State) From() address.Address { + return s.State.From +} + +// Recipient of payouts from channel +func (s *v0State) To() address.Address { + return s.State.From +} + +// Height at which the channel can be `Collected` +func (s *v0State) SettlingAt() abi.ChainEpoch { + return s.State.SettlingAt +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *v0State) ToSend() abi.TokenAmount { + return s.State.ToSend +} + +func (s *v0State) getOrLoadLsAmt() (*v0adt.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := v0adt.AsArray(s.store, s.State.LaneStates) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *v0State) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +// Iterate lane states +func (s *v0State) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &v0LaneState{ls}) + }) +} + +type v0LaneState struct { + paych.LaneState +} + +func (ls *v0LaneState) Redeemed() big.Int { + return ls.LaneState.Redeemed +} + +func (ls *v0LaneState) Nonce() uint64 { + return ls.LaneState.Nonce +} diff --git a/chain/events/state/predicates.go b/chain/events/state/predicates.go index 0858793d8..e5caa41d2 100644 --- a/chain/events/state/predicates.go +++ b/chain/events/state/predicates.go @@ -7,13 +7,12 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "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/market" "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" typegen "github.com/whyrusleeping/cbor-gen" @@ -49,7 +48,7 @@ func NewStatePredicates(api ChainAPI) *StatePredicates { // - err type DiffTipSetKeyFunc func(ctx context.Context, oldState, newState types.TipSetKey) (changed bool, user UserData, err error) -type DiffActorStateFunc func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error) +type DiffActorStateFunc func(ctx context.Context, oldActorState *types.Actor, newActorState *types.Actor) (changed bool, user UserData, err error) // OnActorStateChanged calls diffStateFunc when the state changes for the given actor func (sp *StatePredicates) OnActorStateChanged(addr address.Address, diffStateFunc DiffActorStateFunc) DiffTipSetKeyFunc { @@ -66,7 +65,7 @@ func (sp *StatePredicates) OnActorStateChanged(addr address.Address, diffStateFu if oldActor.Head.Equals(newActor.Head) { return false, nil, nil } - return diffStateFunc(ctx, oldActor.Head, newActor.Head) + return diffStateFunc(ctx, oldActor, newActor) } } @@ -74,13 +73,13 @@ type DiffStorageMarketStateFunc func(ctx context.Context, oldState *market.State // OnStorageMarketActorChanged calls diffStorageMarketState when the state changes for the market actor func (sp *StatePredicates) OnStorageMarketActorChanged(diffStorageMarketState DiffStorageMarketStateFunc) DiffTipSetKeyFunc { - return sp.OnActorStateChanged(builtin.StorageMarketActorAddr, func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error) { + return sp.OnActorStateChanged(builtin.StorageMarketActorAddr, func(ctx context.Context, oldActorState, newActorState *types.Actor) (changed bool, user UserData, err error) { var oldState market.State - if err := sp.cst.Get(ctx, oldActorStateHead, &oldState); err != nil { + if err := sp.cst.Get(ctx, oldActorState.Head, &oldState); err != nil { return false, nil, err } var newState market.State - if err := sp.cst.Get(ctx, newActorStateHead, &newState); err != nil { + if err := sp.cst.Get(ctx, newActorState.Head, &newState); err != nil { return false, nil, err } return diffStorageMarketState(ctx, &oldState, &newState) @@ -408,13 +407,13 @@ func (sp *StatePredicates) AvailableBalanceChangedForAddresses(getAddrs func() [ type DiffMinerActorStateFunc func(ctx context.Context, oldState *miner.State, newState *miner.State) (changed bool, user UserData, err error) func (sp *StatePredicates) OnInitActorChange(diffInitActorState DiffInitActorStateFunc) DiffTipSetKeyFunc { - return sp.OnActorStateChanged(builtin.InitActorAddr, func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error) { + return sp.OnActorStateChanged(builtin.InitActorAddr, func(ctx context.Context, oldActorState, newActorState *types.Actor) (changed bool, user UserData, err error) { var oldState init_.State - if err := sp.cst.Get(ctx, oldActorStateHead, &oldState); err != nil { + if err := sp.cst.Get(ctx, oldActorState.Head, &oldState); err != nil { return false, nil, err } var newState init_.State - if err := sp.cst.Get(ctx, newActorStateHead, &newState); err != nil { + if err := sp.cst.Get(ctx, newActorState.Head, &newState); err != nil { return false, nil, err } return diffInitActorState(ctx, &oldState, &newState) @@ -423,13 +422,13 @@ func (sp *StatePredicates) OnInitActorChange(diffInitActorState DiffInitActorSta } func (sp *StatePredicates) OnMinerActorChange(minerAddr address.Address, diffMinerActorState DiffMinerActorStateFunc) DiffTipSetKeyFunc { - return sp.OnActorStateChanged(minerAddr, func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error) { + return sp.OnActorStateChanged(minerAddr, func(ctx context.Context, oldActorState, newActorState *types.Actor) (changed bool, user UserData, err error) { var oldState miner.State - if err := sp.cst.Get(ctx, oldActorStateHead, &oldState); err != nil { + if err := sp.cst.Get(ctx, oldActorState.Head, &oldState); err != nil { return false, nil, err } var newState miner.State - if err := sp.cst.Get(ctx, newActorStateHead, &newState); err != nil { + if err := sp.cst.Get(ctx, newActorState.Head, &newState); err != nil { return false, nil, err } return diffMinerActorState(ctx, &oldState, &newState) @@ -608,20 +607,20 @@ func (sp *StatePredicates) OnMinerPreCommitChange() DiffMinerActorStateFunc { } // DiffPaymentChannelStateFunc is function that compares two states for the payment channel -type DiffPaymentChannelStateFunc func(ctx context.Context, oldState *paych.State, newState *paych.State) (changed bool, user UserData, err error) +type DiffPaymentChannelStateFunc func(ctx context.Context, oldState paych.State, newState paych.State) (changed bool, user UserData, err error) // OnPaymentChannelActorChanged calls diffPaymentChannelState when the state changes for the the payment channel actor func (sp *StatePredicates) OnPaymentChannelActorChanged(paychAddr address.Address, diffPaymentChannelState DiffPaymentChannelStateFunc) DiffTipSetKeyFunc { - return sp.OnActorStateChanged(paychAddr, func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error) { - var oldState paych.State - if err := sp.cst.Get(ctx, oldActorStateHead, &oldState); err != nil { + return sp.OnActorStateChanged(paychAddr, func(ctx context.Context, oldActorState, newActorState *types.Actor) (changed bool, user UserData, err error) { + oldState, err := paych.Load(adt.WrapStore(ctx, sp.cst), oldActorState) + if err != nil { return false, nil, err } - var newState paych.State - if err := sp.cst.Get(ctx, newActorStateHead, &newState); err != nil { + newState, err := paych.Load(adt.WrapStore(ctx, sp.cst), newActorState) + if err != nil { return false, nil, err } - return diffPaymentChannelState(ctx, &oldState, &newState) + return diffPaymentChannelState(ctx, oldState, newState) }) } @@ -633,13 +632,13 @@ type PayChToSendChange struct { // OnToSendAmountChanges monitors changes on the total amount to send from one party to the other on a payment channel func (sp *StatePredicates) OnToSendAmountChanges() DiffPaymentChannelStateFunc { - return func(ctx context.Context, oldState *paych.State, newState *paych.State) (changed bool, user UserData, err error) { - if oldState.ToSend.Equals(newState.ToSend) { + return func(ctx context.Context, oldState paych.State, newState paych.State) (changed bool, user UserData, err error) { + if oldState.ToSend().Equals(newState.ToSend()) { return false, nil, nil } return true, &PayChToSendChange{ - OldToSend: oldState.ToSend, - NewToSend: newState.ToSend, + OldToSend: oldState.ToSend(), + NewToSend: newState.ToSend(), }, nil } } diff --git a/chain/events/state/predicates_test.go b/chain/events/state/predicates_test.go index a1dccfadc..7117a96cc 100644 --- a/chain/events/state/predicates_test.go +++ b/chain/events/state/predicates_test.go @@ -221,7 +221,7 @@ func TestMarketPredicates(t *testing.T) { // Test that OnActorStateChanged does not call the callback if the state has not changed mockAddr, err := address.NewFromString("t01") require.NoError(t, err) - actorDiffFn := preds.OnActorStateChanged(mockAddr, func(context.Context, cid.Cid, cid.Cid) (bool, UserData, error) { + actorDiffFn := preds.OnActorStateChanged(mockAddr, func(context.Context, *types.Actor, *types.Actor) (bool, UserData, error) { t.Fatal("No state change so this should not be called") return false, nil, nil }) diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index f71b788c8..ae8b47bce 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -25,6 +25,7 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" @@ -1145,3 +1146,21 @@ func (sm *StateManager) GetNtwkVersion(ctx context.Context, height abi.ChainEpoc return build.NewestNetworkVersion } + +func (sm *StateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, nil, err + } + + act, err := st.GetActor(addr) + if err != nil { + return nil, nil, err + } + + actState, err := paych.Load(sm.cs.Store(ctx), act) + if err != nil { + return nil, nil, err + } + return act, actState, nil +} diff --git a/cli/paych_test.go b/cli/paych_test.go index cccc80ff4..1497a54a6 100644 --- a/cli/paych_test.go +++ b/cli/paych_test.go @@ -24,7 +24,8 @@ import ( "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/api/apibstore" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" cbor "github.com/ipfs/go-ipld-cbor" "github.com/filecoin-project/go-address" @@ -88,7 +89,7 @@ func TestPaymentChannels(t *testing.T) { // Wait for the chain to reach the settle height chState := getPaychState(ctx, t, paymentReceiver, chAddr) - waitForHeight(ctx, t, paymentReceiver, chState.SettlingAt) + waitForHeight(ctx, t, paymentReceiver, chState.SettlingAt()) // receiver: paych collect cmd = []string{chAddr.String()} @@ -540,8 +541,7 @@ func getPaychState(ctx context.Context, t *testing.T, node test.TestNode, chAddr require.NoError(t, err) store := cbor.NewCborStore(apibstore.NewAPIBlockstore(node)) - var chState paych.State - err = store.Get(ctx, act.Head, &chState) + chState, err := paych.Load(adt.WrapStore(ctx, store), act) require.NoError(t, err) return chState diff --git a/paychmgr/manager.go b/paychmgr/manager.go index 4b102f062..4556c37be 100644 --- a/paychmgr/manager.go +++ b/paychmgr/manager.go @@ -4,28 +4,23 @@ import ( "context" "sync" - "github.com/filecoin-project/go-state-types/crypto" - - "github.com/filecoin-project/lotus/node/modules/helpers" - - "github.com/ipfs/go-datastore" - - xerrors "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/api" - - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" + xerrors "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + v0paych "github.com/filecoin-project/specs-actors/actors/builtin/paych" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/impl/full" + "github.com/filecoin-project/lotus/node/modules/helpers" ) var log = logging.Logger("paych") @@ -40,9 +35,9 @@ type PaychAPI struct { // stateManagerAPI defines the methods needed from StateManager type stateManagerAPI interface { - LoadActorState(ctx context.Context, a address.Address, out interface{}, ts *types.TipSet) (*types.Actor, error) + ResolveToKeyAddress(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) - AdtStore(ctx context.Context) adt.Store } // paychAPI defines the API methods needed by the payment channel manager @@ -226,7 +221,7 @@ func (pm *Manager) GetChannelInfo(addr address.Address) (*ChannelInfo, error) { return ca.getChannelInfo(addr) } -func (pm *Manager) CreateVoucher(ctx context.Context, ch address.Address, voucher paych.SignedVoucher) (*api.VoucherCreateResult, error) { +func (pm *Manager) CreateVoucher(ctx context.Context, ch address.Address, voucher v0paych.SignedVoucher) (*api.VoucherCreateResult, error) { ca, err := pm.accessorByAddress(ch) if err != nil { return nil, err @@ -238,7 +233,7 @@ func (pm *Manager) CreateVoucher(ctx context.Context, ch address.Address, vouche // 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 *paych.SignedVoucher) error { +func (pm *Manager) CheckVoucherValid(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher) error { // Get an accessor for the channel, creating it from state if necessary ca, err := pm.inboundChannelAccessor(ctx, ch) if err != nil { @@ -250,7 +245,7 @@ func (pm *Manager) CheckVoucherValid(ctx context.Context, ch address.Address, sv } // CheckVoucherSpendable checks if the given voucher is currently spendable -func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (bool, error) { +func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher, secret []byte, proof []byte) (bool, error) { ca, err := pm.accessorByAddress(ch) if err != nil { return false, err @@ -261,7 +256,7 @@ func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address // 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 *paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { +func (pm *Manager) AddVoucherOutbound(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { ca, err := pm.accessorByAddress(ch) if err != nil { return types.NewInt(0), err @@ -272,7 +267,7 @@ func (pm *Manager) AddVoucherOutbound(ctx context.Context, ch address.Address, s // 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 *paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { +func (pm *Manager) AddVoucherInbound(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { // Get an accessor for the channel, creating it from state if necessary ca, err := pm.inboundChannelAccessor(ctx, ch) if err != nil { @@ -341,7 +336,7 @@ func (pm *Manager) trackInboundChannel(ctx context.Context, ch address.Address) return pm.store.TrackChannel(stateCi) } -func (pm *Manager) SubmitVoucher(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (cid.Cid, error) { +func (pm *Manager) SubmitVoucher(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher, secret []byte, proof []byte) (cid.Cid, error) { ca, err := pm.accessorByAddress(ch) if err != nil { return cid.Undef, err diff --git a/paychmgr/mock_test.go b/paychmgr/mock_test.go index bc19de223..c761221d2 100644 --- a/paychmgr/mock_test.go +++ b/paychmgr/mock_test.go @@ -2,23 +2,18 @@ package paychmgr import ( "context" - "fmt" + "errors" "sync" - "github.com/filecoin-project/lotus/lib/sigs" - - "github.com/filecoin-project/go-state-types/crypto" - - cbornode "github.com/ipfs/go-ipld-cbor" - - "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/ipfs/go-cid" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/actors/builtin/account" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - "github.com/ipfs/go-cid" + "github.com/filecoin-project/lotus/lib/sigs" ) type mockManagerAPI struct { @@ -40,29 +35,23 @@ type mockPchState struct { type mockStateManager struct { lk sync.Mutex - accountState map[address.Address]account.State + accountState map[address.Address]address.Address paychState map[address.Address]mockPchState - store adt.Store response *api.InvocResult lastCall *types.Message } func newMockStateManager() *mockStateManager { return &mockStateManager{ - accountState: make(map[address.Address]account.State), + accountState: make(map[address.Address]address.Address), paychState: make(map[address.Address]mockPchState), - store: adt.WrapStore(context.Background(), cbornode.NewMemCborStore()), } } -func (sm *mockStateManager) AdtStore(ctx context.Context) adt.Store { - return sm.store -} - -func (sm *mockStateManager) setAccountState(a address.Address, state account.State) { +func (sm *mockStateManager) setAccountAddress(a address.Address, lookup address.Address) { sm.lk.Lock() defer sm.lk.Unlock() - sm.accountState[a] = state + sm.accountState[a] = lookup } func (sm *mockStateManager) setPaychState(a address.Address, actor *types.Actor, state paych.State) { @@ -71,31 +60,24 @@ func (sm *mockStateManager) setPaychState(a address.Address, actor *types.Actor, sm.paychState[a] = mockPchState{actor, state} } -func (sm *mockStateManager) storeLaneStates(laneStates map[uint64]paych.LaneState) (cid.Cid, error) { - arr := adt.MakeEmptyArray(sm.store) - for i, ls := range laneStates { - ls := ls - if err := arr.Set(i, &ls); err != nil { - return cid.Undef, err - } - } - return arr.Root() -} - -func (sm *mockStateManager) LoadActorState(ctx context.Context, a address.Address, out interface{}, ts *types.TipSet) (*types.Actor, error) { +func (sm *mockStateManager) ResolveToKeyAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { sm.lk.Lock() defer sm.lk.Unlock() + keyAddr, ok := sm.accountState[addr] + if !ok { + return address.Undef, errors.New("not found") + } + return keyAddr, nil +} - if outState, ok := out.(*account.State); ok { - *outState = sm.accountState[a] - return nil, nil +func (sm *mockStateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { + sm.lk.Lock() + defer sm.lk.Unlock() + info, ok := sm.paychState[addr] + if !ok { + return nil, nil, errors.New("not found") } - if outState, ok := out.(*paych.State); ok { - info := sm.paychState[a] - *outState = info.state - return info.actor, nil - } - panic(fmt.Sprintf("unexpected state type %v", out)) + return info.actor, info.state, nil } func (sm *mockStateManager) setCallResponse(response *api.InvocResult) { diff --git a/paychmgr/msglistener_test.go b/paychmgr/msglistener_test.go index 2c3ae16e4..4b8ae6f30 100644 --- a/paychmgr/msglistener_test.go +++ b/paychmgr/msglistener_test.go @@ -4,9 +4,7 @@ import ( "testing" "github.com/ipfs/go-cid" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" ) diff --git a/paychmgr/paych.go b/paychmgr/paych.go index 20d76b7fd..53f16b4fc 100644 --- a/paychmgr/paych.go +++ b/paychmgr/paych.go @@ -5,22 +5,20 @@ import ( "context" "fmt" - "github.com/filecoin-project/lotus/api" - - "github.com/filecoin-project/specs-actors/actors/util/adt" - "github.com/ipfs/go-cid" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" cborutil "github.com/filecoin-project/go-cbor-util" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/actors/builtin" + v0paych "github.com/filecoin-project/specs-actors/actors/builtin/paych" + + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/lib/sigs" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/account" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - "golang.org/x/xerrors" ) // insufficientFundsErr indicates that there are not enough funds in the @@ -45,6 +43,19 @@ func (e *ErrInsufficientFunds) Shortfall() types.BigInt { return e.shortfall } +type laneState struct { + redeemed big.Int + nonce uint64 +} + +func (ls laneState) Redeemed() big.Int { + return ls.redeemed +} + +func (ls laneState) Nonce() uint64 { + return ls.nonce +} + // channelAccessor is used to simplify locking when accessing a channel type channelAccessor struct { from address.Address @@ -92,7 +103,7 @@ func (ca *channelAccessor) outboundActiveByFromTo(from, to address.Address) (*Ch // nonce, signing the voucher and storing it in the local datastore. // If there are not enough funds in the channel to create the voucher, returns // the shortfall in funds. -func (ca *channelAccessor) createVoucher(ctx context.Context, ch address.Address, voucher paych.SignedVoucher) (*api.VoucherCreateResult, error) { +func (ca *channelAccessor) createVoucher(ctx context.Context, ch address.Address, voucher v0paych.SignedVoucher) (*api.VoucherCreateResult, error) { ca.lk.Lock() defer ca.lk.Unlock() @@ -151,14 +162,14 @@ func (ca *channelAccessor) nextNonceForLane(ci *ChannelInfo, lane uint64) uint64 return maxnonce + 1 } -func (ca *channelAccessor) checkVoucherValid(ctx context.Context, ch address.Address, sv *paych.SignedVoucher) (map[uint64]*paych.LaneState, error) { +func (ca *channelAccessor) checkVoucherValid(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher) (map[uint64]paych.LaneState, error) { ca.lk.Lock() defer ca.lk.Unlock() return ca.checkVoucherValidUnlocked(ctx, ch, sv) } -func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch address.Address, sv *paych.SignedVoucher) (map[uint64]*paych.LaneState, error) { +func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher) (map[uint64]paych.LaneState, error) { if sv.ChannelAddr != ch { return nil, xerrors.Errorf("voucher ChannelAddr doesn't match channel address, got %s, expected %s", sv.ChannelAddr, ch) } @@ -170,12 +181,10 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add } // Load channel "From" account actor state - var actState account.State - _, err = ca.api.LoadActorState(ctx, pchState.From, &actState, nil) + from, err := ca.api.ResolveToKeyAddress(ctx, pchState.From(), nil) if err != nil { return nil, err } - from := actState.Address // verify voucher signature vb, err := sv.SigningBytes() @@ -199,12 +208,12 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add // 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 { + if lsExists && sv.Nonce <= ls.Nonce() { return nil, fmt.Errorf("nonce too low") } // If the voucher amount is less than the highest known voucher amount - if lsExists && sv.Amount.LessThanEqual(ls.Redeemed) { + if lsExists && sv.Amount.LessThanEqual(ls.Redeemed()) { return nil, fmt.Errorf("voucher amount is lower than amount for voucher with lower nonce") } @@ -230,7 +239,7 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add // Total required balance = total redeemed + toSend // Must not exceed actor balance - newTotal := types.BigAdd(totalRedeemed, pchState.ToSend) + newTotal := types.BigAdd(totalRedeemed, pchState.ToSend()) if act.Balance.LessThan(newTotal) { return nil, newErrInsufficientFunds(types.BigSub(newTotal, act.Balance)) } @@ -242,7 +251,7 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add return laneStates, nil } -func (ca *channelAccessor) checkVoucherSpendable(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (bool, error) { +func (ca *channelAccessor) checkVoucherSpendable(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher, secret []byte, proof []byte) (bool, error) { ca.lk.Lock() defer ca.lk.Unlock() @@ -281,7 +290,7 @@ func (ca *channelAccessor) checkVoucherSpendable(ctx context.Context, ch address } } - enc, err := actors.SerializeParams(&paych.UpdateChannelStateParams{ + enc, err := actors.SerializeParams(&v0paych.UpdateChannelStateParams{ Sv: *sv, Secret: secret, Proof: proof, @@ -308,22 +317,22 @@ func (ca *channelAccessor) checkVoucherSpendable(ctx context.Context, ch address } func (ca *channelAccessor) getPaychRecipient(ctx context.Context, ch address.Address) (address.Address, error) { - var state paych.State - if _, err := ca.api.LoadActorState(ctx, ch, &state, nil); err != nil { + _, state, err := ca.api.GetPaychState(ctx, ch, nil) + if err != nil { return address.Address{}, err } - return state.To, nil + return state.To(), nil } -func (ca *channelAccessor) addVoucher(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { +func (ca *channelAccessor) addVoucher(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { ca.lk.Lock() defer ca.lk.Unlock() return ca.addVoucherUnlocked(ctx, ch, sv, proof, minDelta) } -func (ca *channelAccessor) addVoucherUnlocked(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { +func (ca *channelAccessor) addVoucherUnlocked(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { ci, err := ca.store.ByAddress(ch) if err != nil { return types.BigInt{}, err @@ -367,7 +376,7 @@ func (ca *channelAccessor) addVoucherUnlocked(ctx context.Context, ch address.Ad laneState, exists := laneStates[sv.Lane] redeemed := big.NewInt(0) if exists { - redeemed = laneState.Redeemed + redeemed = laneState.Redeemed() } delta := types.BigSub(sv.Amount, redeemed) @@ -387,7 +396,7 @@ func (ca *channelAccessor) addVoucherUnlocked(ctx context.Context, ch address.Ad return delta, ca.store.putChannelInfo(ci) } -func (ca *channelAccessor) submitVoucher(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (cid.Cid, error) { +func (ca *channelAccessor) submitVoucher(ctx context.Context, ch address.Address, sv *v0paych.SignedVoucher, secret []byte, proof []byte) (cid.Cid, error) { ca.lk.Lock() defer ca.lk.Unlock() @@ -428,7 +437,7 @@ func (ca *channelAccessor) submitVoucher(ctx context.Context, ch address.Address } } - enc, err := actors.SerializeParams(&paych.UpdateChannelStateParams{ + enc, err := actors.SerializeParams(&v0paych.UpdateChannelStateParams{ Sv: *sv, Secret: secret, Proof: proof, @@ -487,13 +496,11 @@ func (ca *channelAccessor) listVouchers(ctx context.Context, ch address.Address) // laneState gets the LaneStates from chain, then applies all vouchers in // the data store over the chain state -func (ca *channelAccessor) laneState(ctx context.Context, state *paych.State, ch address.Address) (map[uint64]*paych.LaneState, error) { +func (ca *channelAccessor) laneState(ctx context.Context, state paych.State, ch address.Address) (map[uint64]paych.LaneState, error) { // TODO: we probably want to call UpdateChannelState with all vouchers to be fully correct // (but technically dont't need to) - // Get the lane state from the chain - store := ca.api.AdtStore(ctx) - lsamt, err := adt.AsArray(store, state.LaneStates) + laneCount, err := state.LaneCount() if err != nil { return nil, err } @@ -501,11 +508,9 @@ func (ca *channelAccessor) laneState(ctx context.Context, state *paych.State, ch // Note: we use a map instead of an array to store laneStates because the // client sets the lane ID (the index) and potentially they could use a // very large index. - var ls paych.LaneState - laneStates := make(map[uint64]*paych.LaneState, lsamt.Length()) - err = lsamt.ForEach(&ls, func(i int64) error { - current := ls - laneStates[uint64(i)] = ¤t + laneStates := make(map[uint64]paych.LaneState, laneCount) + err = state.ForEachLaneState(func(idx uint64, ls paych.LaneState) error { + laneStates[idx] = ls return nil }) if err != nil { @@ -526,27 +531,19 @@ func (ca *channelAccessor) laneState(ctx context.Context, state *paych.State, ch // If there's a voucher for a lane that isn't in chain state just // create it ls, ok := laneStates[v.Voucher.Lane] - if !ok { - ls = &paych.LaneState{ - Redeemed: types.NewInt(0), - Nonce: 0, - } - laneStates[v.Voucher.Lane] = ls - } - if v.Voucher.Nonce < ls.Nonce { + if ok && v.Voucher.Nonce < ls.Nonce() { continue } - ls.Nonce = v.Voucher.Nonce - ls.Redeemed = v.Voucher.Amount + laneStates[v.Voucher.Lane] = laneState{v.Voucher.Amount, v.Voucher.Nonce} } return laneStates, nil } // Get the total redeemed amount across all lanes, after applying the voucher -func (ca *channelAccessor) totalRedeemedWithVoucher(laneStates map[uint64]*paych.LaneState, sv *paych.SignedVoucher) (big.Int, error) { +func (ca *channelAccessor) totalRedeemedWithVoucher(laneStates map[uint64]paych.LaneState, sv *v0paych.SignedVoucher) (big.Int, error) { // TODO: merges if len(sv.Merges) != 0 { return big.Int{}, xerrors.Errorf("dont currently support paych lane merges") @@ -554,17 +551,17 @@ func (ca *channelAccessor) totalRedeemedWithVoucher(laneStates map[uint64]*paych total := big.NewInt(0) for _, ls := range laneStates { - total = big.Add(total, ls.Redeemed) + total = big.Add(total, ls.Redeemed()) } lane, ok := laneStates[sv.Lane] if ok { // If the voucher is for an existing lane, and the voucher nonce // is higher than the lane nonce - if sv.Nonce > lane.Nonce { + if sv.Nonce > lane.Nonce() { // Add the delta between the redeemed amount and the voucher // amount to the total - delta := big.Sub(sv.Amount, lane.Redeemed) + delta := big.Sub(sv.Amount, lane.Redeemed()) total = big.Add(total, delta) } } else { diff --git a/paychmgr/paych_test.go b/paychmgr/paych_test.go index 18c6655da..434c83e9c 100644 --- a/paychmgr/paych_test.go +++ b/paychmgr/paych_test.go @@ -5,31 +5,24 @@ import ( "context" "testing" - "github.com/filecoin-project/lotus/api" - - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/ipfs/go-cid" - - "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/lotus/lib/sigs" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/go-state-types/abi" - tutils "github.com/filecoin-project/specs-actors/support/testing" - - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - - "github.com/filecoin-project/specs-actors/actors/builtin/account" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/chain/types" - ds "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/specs-actors/actors/builtin" + v0paych "github.com/filecoin-project/specs-actors/actors/builtin/paych" + tutils "github.com/filecoin-project/specs-actors/support/testing" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + paychmock "github.com/filecoin-project/lotus/chain/actors/builtin/paych/mock" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/sigs" ) func TestCheckVoucherValid(t *testing.T) { @@ -46,8 +39,8 @@ func TestCheckVoucherValid(t *testing.T) { toAcct := tutils.NewActorAddr(t, "toAct") mock := newMockManagerAPI() - mock.setAccountState(fromAcct, account.State{Address: from}) - mock.setAccountState(toAcct, account.State{Address: to}) + mock.setAccountAddress(fromAcct, from) + mock.setAccountAddress(toAcct, to) tcases := []struct { name string @@ -96,10 +89,7 @@ func TestCheckVoucherValid(t *testing.T) { voucherLane: 1, voucherNonce: 2, laneStates: map[uint64]paych.LaneState{ - 1: { - Redeemed: big.NewInt(2), - Nonce: 3, - }, + 1: paychmock.NewMockLaneState(big.NewInt(2), 3), }, }, { name: "passes when nonce higher", @@ -110,10 +100,7 @@ func TestCheckVoucherValid(t *testing.T) { voucherLane: 1, voucherNonce: 3, laneStates: map[uint64]paych.LaneState{ - 1: { - Redeemed: big.NewInt(2), - Nonce: 2, - }, + 1: paychmock.NewMockLaneState(big.NewInt(2), 2), }, }, { name: "passes when nonce for different lane", @@ -124,10 +111,7 @@ func TestCheckVoucherValid(t *testing.T) { voucherLane: 2, voucherNonce: 2, laneStates: map[uint64]paych.LaneState{ - 1: { - Redeemed: big.NewInt(2), - Nonce: 3, - }, + 1: paychmock.NewMockLaneState(big.NewInt(2), 3), }, }, { name: "fails when voucher has higher nonce but lower value than lane state", @@ -139,10 +123,7 @@ func TestCheckVoucherValid(t *testing.T) { voucherLane: 1, voucherNonce: 3, laneStates: map[uint64]paych.LaneState{ - 1: { - Redeemed: big.NewInt(6), - Nonce: 2, - }, + 1: paychmock.NewMockLaneState(big.NewInt(6), 2), }, }, { name: "fails when voucher + ToSend > balance", @@ -168,10 +149,7 @@ func TestCheckVoucherValid(t *testing.T) { voucherNonce: 2, laneStates: map[uint64]paych.LaneState{ // Lane 1 (same as voucher lane 1) - 1: { - Redeemed: big.NewInt(4), - Nonce: 1, - }, + 1: paychmock.NewMockLaneState(big.NewInt(4), 1), }, }, { // required balance = toSend + total redeemed @@ -188,10 +166,7 @@ func TestCheckVoucherValid(t *testing.T) { voucherNonce: 1, laneStates: map[uint64]paych.LaneState{ // Lane 2 (different from voucher lane 1) - 2: { - Redeemed: big.NewInt(4), - Nonce: 1, - }, + 2: paychmock.NewMockLaneState(big.NewInt(4), 1), }, }} @@ -208,18 +183,8 @@ func TestCheckVoucherValid(t *testing.T) { Balance: tcase.actorBalance, } - // Set the state of the channel's lanes - laneStates, err := mock.storeLaneStates(tcase.laneStates) - require.NoError(t, err) - - mock.setPaychState(ch, act, paych.State{ - From: fromAcct, - To: toAcct, - ToSend: tcase.toSend, - SettlingAt: abi.ChainEpoch(0), - MinSettleHeight: abi.ChainEpoch(0), - LaneStates: laneStates, - }) + mock.setPaychState(ch, act, paychmock.NewMockPayChState( + fromAcct, toAcct, abi.ChainEpoch(0), tcase.toSend, tcase.laneStates)) // Create a manager mgr, err := newManager(store, mock) @@ -255,22 +220,16 @@ func TestCheckVoucherValidCountingAllLanes(t *testing.T) { minDelta := big.NewInt(0) mock := newMockManagerAPI() - mock.setAccountState(fromAcct, account.State{Address: from}) - mock.setAccountState(toAcct, account.State{Address: to}) + mock.setAccountAddress(fromAcct, from) + mock.setAccountAddress(toAcct, to) store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) actorBalance := big.NewInt(10) toSend := big.NewInt(1) laneStates := map[uint64]paych.LaneState{ - 1: { - Nonce: 1, - Redeemed: big.NewInt(3), - }, - 2: { - Nonce: 1, - Redeemed: big.NewInt(4), - }, + 1: paychmock.NewMockLaneState(big.NewInt(3), 1), + 2: paychmock.NewMockLaneState(big.NewInt(4), 1), } act := &types.Actor{ @@ -280,16 +239,7 @@ func TestCheckVoucherValidCountingAllLanes(t *testing.T) { Balance: actorBalance, } - lsCid, err := mock.storeLaneStates(laneStates) - require.NoError(t, err) - mock.setPaychState(ch, act, paych.State{ - From: fromAcct, - To: toAcct, - ToSend: toSend, - SettlingAt: abi.ChainEpoch(0), - MinSettleHeight: abi.ChainEpoch(0), - LaneStates: lsCid, - }) + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), toSend, laneStates)) mgr, err := newManager(store, mock) require.NoError(t, err) @@ -389,7 +339,7 @@ func TestCreateVoucher(t *testing.T) { // Create a voucher in lane 1 voucherLane1Amt := big.NewInt(5) - voucher := paych.SignedVoucher{ + voucher := v0paych.SignedVoucher{ Lane: 1, Amount: voucherLane1Amt, } @@ -404,7 +354,7 @@ func TestCreateVoucher(t *testing.T) { // Create a voucher in lane 1 again, with a higher amount voucherLane1Amt = big.NewInt(8) - voucher = paych.SignedVoucher{ + voucher = v0paych.SignedVoucher{ Lane: 1, Amount: voucherLane1Amt, } @@ -419,7 +369,7 @@ func TestCreateVoucher(t *testing.T) { // Create a voucher in lane 2 that covers all the remaining funds // in the channel voucherLane2Amt := big.Sub(s.amt, voucherLane1Amt) - voucher = paych.SignedVoucher{ + voucher = v0paych.SignedVoucher{ Lane: 2, Amount: voucherLane2Amt, } @@ -433,7 +383,7 @@ func TestCreateVoucher(t *testing.T) { // Create a voucher in lane 2 that exceeds the remaining funds in the // channel voucherLane2Amt = big.Add(voucherLane2Amt, big.NewInt(1)) - voucher = paych.SignedVoucher{ + voucher = v0paych.SignedVoucher{ Lane: 2, Amount: voucherLane2Amt, } @@ -567,8 +517,8 @@ func TestAllocateLaneWithExistingLaneState(t *testing.T) { toAcct := tutils.NewActorAddr(t, "toAct") mock := newMockManagerAPI() - mock.setAccountState(fromAcct, account.State{Address: from}) - mock.setAccountState(toAcct, account.State{Address: to}) + mock.setAccountAddress(fromAcct, from) + mock.setAccountAddress(toAcct, to) mock.addWalletAddress(to) store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) @@ -584,16 +534,7 @@ func TestAllocateLaneWithExistingLaneState(t *testing.T) { Balance: actorBalance, } - arr, err := adt.MakeEmptyArray(mock.store).Root() - require.NoError(t, err) - mock.setPaychState(ch, act, paych.State{ - From: fromAcct, - To: toAcct, - ToSend: toSend, - SettlingAt: abi.ChainEpoch(0), - MinSettleHeight: abi.ChainEpoch(0), - LaneStates: arr, - }) + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), toSend, make(map[uint64]paych.LaneState))) mgr, err := newManager(store, mock) require.NoError(t, err) @@ -681,19 +622,10 @@ func TestAddVoucherInboundWalletKey(t *testing.T) { } mock := newMockManagerAPI() - arr, err := adt.MakeEmptyArray(mock.store).Root() - require.NoError(t, err) - mock.setAccountState(fromAcct, account.State{Address: from}) - mock.setAccountState(toAcct, account.State{Address: to}) + mock.setAccountAddress(fromAcct, from) + mock.setAccountAddress(toAcct, to) - mock.setPaychState(ch, act, paych.State{ - From: fromAcct, - To: toAcct, - ToSend: types.NewInt(0), - SettlingAt: abi.ChainEpoch(0), - MinSettleHeight: abi.ChainEpoch(0), - LaneStates: arr, - }) + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), types.NewInt(0), make(map[uint64]paych.LaneState))) // Create a manager store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) @@ -840,7 +772,7 @@ func TestCheckSpendable(t *testing.T) { // Check that the secret and proof were passed through correctly lastCall := s.mock.getLastCall() - var p paych.UpdateChannelStateParams + var p v0paych.UpdateChannelStateParams err = p.UnmarshalCBOR(bytes.NewReader(lastCall.Params)) require.NoError(t, err) require.Equal(t, otherProof, p.Proof) @@ -854,7 +786,7 @@ func TestCheckSpendable(t *testing.T) { require.True(t, spendable) lastCall = s.mock.getLastCall() - var p2 paych.UpdateChannelStateParams + var p2 v0paych.UpdateChannelStateParams err = p2.UnmarshalCBOR(bytes.NewReader(lastCall.Params)) require.NoError(t, err) require.Equal(t, proof, p2.Proof) @@ -911,7 +843,7 @@ func TestSubmitVoucher(t *testing.T) { // Check that the secret and proof were passed through correctly msg := s.mock.pushedMessages(submitCid) - var p paych.UpdateChannelStateParams + var p v0paych.UpdateChannelStateParams err = p.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)) require.NoError(t, err) require.Equal(t, submitProof, p.Proof) @@ -931,7 +863,7 @@ func TestSubmitVoucher(t *testing.T) { require.NoError(t, err) msg = s.mock.pushedMessages(submitCid) - var p2 paych.UpdateChannelStateParams + var p2 v0paych.UpdateChannelStateParams err = p2.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)) require.NoError(t, err) require.Equal(t, addVoucherProof2, p2.Proof) @@ -947,7 +879,7 @@ func TestSubmitVoucher(t *testing.T) { require.NoError(t, err) msg = s.mock.pushedMessages(submitCid) - var p3 paych.UpdateChannelStateParams + var p3 v0paych.UpdateChannelStateParams err = p3.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)) require.NoError(t, err) require.Equal(t, proof3, p3.Proof) @@ -986,10 +918,8 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) *testScaffold { toAcct := tutils.NewActorAddr(t, "toAct") mock := newMockManagerAPI() - arr, err := adt.MakeEmptyArray(mock.store).Root() - require.NoError(t, err) - mock.setAccountState(fromAcct, account.State{Address: from}) - mock.setAccountState(toAcct, account.State{Address: to}) + mock.setAccountAddress(fromAcct, from) + mock.setAccountAddress(toAcct, to) // Create channel in state balance := big.NewInt(20) @@ -999,14 +929,7 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) *testScaffold { Nonce: 0, Balance: balance, } - mock.setPaychState(ch, act, paych.State{ - From: fromAcct, - To: toAcct, - ToSend: big.NewInt(0), - SettlingAt: abi.ChainEpoch(0), - MinSettleHeight: abi.ChainEpoch(0), - LaneStates: arr, - }) + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), big.NewInt(0), make(map[uint64]paych.LaneState))) store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) mgr, err := newManager(store, mock) @@ -1043,8 +966,8 @@ func testGenerateKeyPair(t *testing.T) ([]byte, []byte) { return priv, pub } -func createTestVoucher(t *testing.T, ch address.Address, voucherLane uint64, nonce uint64, voucherAmount big.Int, key []byte) *paych.SignedVoucher { - sv := &paych.SignedVoucher{ +func createTestVoucher(t *testing.T, ch address.Address, voucherLane uint64, nonce uint64, voucherAmount big.Int, key []byte) *v0paych.SignedVoucher { + sv := &v0paych.SignedVoucher{ ChannelAddr: ch, Lane: voucherLane, Nonce: nonce, @@ -1059,13 +982,13 @@ func createTestVoucher(t *testing.T, ch address.Address, voucherLane uint64, non return sv } -func createTestVoucherWithExtra(t *testing.T, ch address.Address, voucherLane uint64, nonce uint64, voucherAmount big.Int, key []byte) *paych.SignedVoucher { - sv := &paych.SignedVoucher{ +func createTestVoucherWithExtra(t *testing.T, ch address.Address, voucherLane uint64, nonce uint64, voucherAmount big.Int, key []byte) *v0paych.SignedVoucher { + sv := &v0paych.SignedVoucher{ ChannelAddr: ch, Lane: voucherLane, Nonce: nonce, Amount: voucherAmount, - Extra: &paych.ModVerifyParams{ + Extra: &v0paych.ModVerifyParams{ Actor: tutils.NewActorAddr(t, "act"), }, } @@ -1083,13 +1006,13 @@ type mockBestSpendableAPI struct { mgr *Manager } -func (m *mockBestSpendableAPI) PaychVoucherList(ctx context.Context, ch address.Address) ([]*paych.SignedVoucher, error) { +func (m *mockBestSpendableAPI) PaychVoucherList(ctx context.Context, ch address.Address) ([]*v0paych.SignedVoucher, error) { vi, err := m.mgr.ListVouchers(ctx, ch) if err != nil { return nil, err } - out := make([]*paych.SignedVoucher, len(vi)) + out := make([]*v0paych.SignedVoucher, len(vi)) for k, v := range vi { out[k] = v.Voucher } @@ -1097,7 +1020,7 @@ func (m *mockBestSpendableAPI) PaychVoucherList(ctx context.Context, ch address. return out, nil } -func (m *mockBestSpendableAPI) PaychVoucherCheckSpendable(ctx context.Context, ch address.Address, voucher *paych.SignedVoucher, secret []byte, proof []byte) (bool, error) { +func (m *mockBestSpendableAPI) PaychVoucherCheckSpendable(ctx context.Context, ch address.Address, voucher *v0paych.SignedVoucher, secret []byte, proof []byte) (bool, error) { return m.mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, proof) } diff --git a/paychmgr/paychget_test.go b/paychmgr/paychget_test.go index 1f3e4c396..93233c54f 100644 --- a/paychmgr/paychget_test.go +++ b/paychmgr/paychget_test.go @@ -6,29 +6,22 @@ import ( "testing" "time" - "github.com/filecoin-project/specs-actors/actors/builtin/account" - "github.com/filecoin-project/specs-actors/actors/util/adt" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - cborrpc "github.com/filecoin-project/go-cbor-util" - - init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" - - "github.com/filecoin-project/specs-actors/actors/builtin" - - "github.com/filecoin-project/lotus/chain/types" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/go-state-types/big" - tutils "github.com/filecoin-project/specs-actors/support/testing" "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" - "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/actors/builtin" + init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" + tutils "github.com/filecoin-project/specs-actors/support/testing" + + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + paychmock "github.com/filecoin-project/lotus/chain/actors/builtin/paych/mock" + "github.com/filecoin-project/lotus/chain/types" ) func testChannelResponse(t *testing.T, ch address.Address) types.MessageReceipt { @@ -976,25 +969,15 @@ func TestPaychAvailableFunds(t *testing.T) { require.EqualValues(t, 0, av.VoucherReedeemedAmt.Int64()) // Create channel in state - arr, err := adt.MakeEmptyArray(mock.store).Root() - require.NoError(t, err) - mock.setAccountState(fromAcct, account.State{Address: from}) - mock.setAccountState(toAcct, account.State{Address: to}) + mock.setAccountAddress(fromAcct, from) + mock.setAccountAddress(toAcct, to) act := &types.Actor{ Code: builtin.AccountActorCodeID, Head: cid.Cid{}, Nonce: 0, Balance: createAmt, } - mock.setPaychState(ch, act, paych.State{ - From: fromAcct, - To: toAcct, - ToSend: big.NewInt(0), - SettlingAt: abi.ChainEpoch(0), - MinSettleHeight: abi.ChainEpoch(0), - LaneStates: arr, - }) - + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), big.NewInt(0), make(map[uint64]paych.LaneState))) // Send create channel response response := testChannelResponse(t, ch) mock.receiveMsgResponse(createMsgCid, response) diff --git a/paychmgr/simple.go b/paychmgr/simple.go index 4cf579a47..2f6dc0593 100644 --- a/paychmgr/simple.go +++ b/paychmgr/simple.go @@ -6,20 +6,17 @@ import ( "fmt" "sync" - "github.com/filecoin-project/lotus/api" - - "golang.org/x/sync/errgroup" - - "github.com/filecoin-project/go-state-types/big" - - "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/sync/errgroup" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/actors/builtin" + init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" + v0paych "github.com/filecoin-project/specs-actors/actors/builtin/paych" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/types" @@ -320,7 +317,7 @@ func (ca *channelAccessor) currentAvailableFunds(channelID string, queuedAmt typ } for _, ls := range laneStates { - totalRedeemed = types.BigAdd(totalRedeemed, ls.Redeemed) + totalRedeemed = types.BigAdd(totalRedeemed, ls.Redeemed()) } } @@ -385,7 +382,7 @@ func (ca *channelAccessor) processTask(ctx context.Context, amt types.BigInt) *p // createPaych sends a message to create the channel and returns the message cid func (ca *channelAccessor) createPaych(ctx context.Context, amt types.BigInt) (cid.Cid, error) { - params, aerr := actors.SerializeParams(&paych.ConstructorParams{From: ca.from, To: ca.to}) + params, aerr := actors.SerializeParams(&v0paych.ConstructorParams{From: ca.from, To: ca.to}) if aerr != nil { return cid.Undef, aerr } diff --git a/paychmgr/state.go b/paychmgr/state.go index 00fe2adce..2571ef73e 100644 --- a/paychmgr/state.go +++ b/paychmgr/state.go @@ -3,13 +3,9 @@ package paychmgr import ( "context" - "github.com/filecoin-project/specs-actors/actors/util/adt" - - "github.com/filecoin-project/specs-actors/actors/builtin/account" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/types" ) @@ -17,14 +13,8 @@ type stateAccessor struct { sm stateManagerAPI } -func (ca *stateAccessor) loadPaychActorState(ctx context.Context, ch address.Address) (*types.Actor, *paych.State, error) { - var pcast paych.State - act, err := ca.sm.LoadActorState(ctx, ch, &pcast, nil) - if err != nil { - return nil, nil, err - } - - return act, &pcast, nil +func (ca *stateAccessor) loadPaychActorState(ctx context.Context, ch address.Address) (*types.Actor, paych.State, error) { + return ca.sm.GetPaychState(ctx, ch, nil) } func (ca *stateAccessor) loadStateChannelInfo(ctx context.Context, ch address.Address, dir uint64) (*ChannelInfo, error) { @@ -33,17 +23,15 @@ func (ca *stateAccessor) loadStateChannelInfo(ctx context.Context, ch address.Ad return nil, err } - var account account.State - _, err = ca.sm.LoadActorState(ctx, st.From, &account, nil) + // Load channel "From" account actor state + from, err := ca.sm.ResolveToKeyAddress(ctx, st.From(), nil) if err != nil { return nil, err } - from := account.Address - _, err = ca.sm.LoadActorState(ctx, st.To, &account, nil) + to, err := ca.sm.ResolveToKeyAddress(ctx, st.To(), nil) if err != nil { return nil, err } - to := account.Address nextLane, err := ca.nextLaneFromState(ctx, st) if err != nil { @@ -67,25 +55,24 @@ func (ca *stateAccessor) loadStateChannelInfo(ctx context.Context, ch address.Ad return ci, nil } -func (ca *stateAccessor) nextLaneFromState(ctx context.Context, st *paych.State) (uint64, error) { - store := ca.sm.AdtStore(ctx) - laneStates, err := adt.AsArray(store, st.LaneStates) +func (ca *stateAccessor) nextLaneFromState(ctx context.Context, st paych.State) (uint64, error) { + laneCount, err := st.LaneCount() if err != nil { return 0, err } - if laneStates.Length() == 0 { + if laneCount == 0 { return 0, nil } - maxID := int64(0) - if err := laneStates.ForEach(nil, func(i int64) error { - if i > maxID { - maxID = i + maxID := uint64(0) + if err := st.ForEachLaneState(func(idx uint64, _ paych.LaneState) error { + if idx > maxID { + maxID = idx } return nil }); err != nil { return 0, err } - return uint64(maxID + 1), nil + return maxID + 1, nil }