diff --git a/go.mod b/go.mod index 20f09a37a..e217dfaa5 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/drand/drand v1.0.3-0.20200714175734-29705eaf09d4 github.com/drand/kyber v1.1.1 github.com/fatih/color v1.8.0 - github.com/filecoin-project/chain-validation v0.0.6-0.20200812173150-c013d785a8d5 + github.com/filecoin-project/chain-validation v0.0.6-0.20200813000554-40c22fe26eef github.com/filecoin-project/filecoin-ffi v0.30.4-0.20200716204036-cddc56607e1d github.com/filecoin-project/go-address v0.0.3 github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20200731171407-e559a0579161 // indirect @@ -35,7 +35,7 @@ require ( github.com/filecoin-project/go-statestore v0.1.0 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/sector-storage v0.0.0-20200810171746-eac70842d8e0 - github.com/filecoin-project/specs-actors v0.8.7-0.20200811223639-8db91253c07a + github.com/filecoin-project/specs-actors v0.9.2 github.com/filecoin-project/specs-storage v0.1.1-0.20200730063404-f7db367e9401 github.com/filecoin-project/storage-fsm v0.0.0-20200805013058-9d9ea4e6331f github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 @@ -115,7 +115,7 @@ require ( github.com/syndtr/goleveldb v1.0.0 github.com/urfave/cli/v2 v2.2.0 github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba - github.com/whyrusleeping/cbor-gen v0.0.0-20200811225321-4fed70922d45 + github.com/whyrusleeping/cbor-gen v0.0.0-20200812213548-958ddffe352c github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 github.com/whyrusleeping/pubsub v0.0.0-20131020042734-02de8aa2db3d github.com/xorcare/golden v0.6.1-0.20191112154924-b87f686d7542 diff --git a/go.sum b/go.sum index fc2398e4d..121a3b955 100644 --- a/go.sum +++ b/go.sum @@ -215,8 +215,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.8.0 h1:5bzFgL+oy7JITMTxUPJ00n7VxmYd/PdMp5mHFX40/RY= github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGjnw8= github.com/fd/go-nat v1.0.0/go.mod h1:BTBu/CKvMmOMUPkKVef1pngt2WFH/lg7E6yQnulfp6E= -github.com/filecoin-project/chain-validation v0.0.6-0.20200812173150-c013d785a8d5 h1:1oXwXT5RTSKrcOuxes8a4JzZF+PJ3yYjPWxpgIh/igg= -github.com/filecoin-project/chain-validation v0.0.6-0.20200812173150-c013d785a8d5/go.mod h1:/R8d9VnHt2kkbiC+dbDV8cbZJ5EgRjKBgNQgtl5dubQ= +github.com/filecoin-project/chain-validation v0.0.6-0.20200813000554-40c22fe26eef h1:MtQRSnJLsQOOlmsd/Ua5KWXimpxcaa715h6FUh/eJPY= +github.com/filecoin-project/chain-validation v0.0.6-0.20200813000554-40c22fe26eef/go.mod h1:SMj5VK1pYgqC8FXVEtOBRTc+9AIrYu+C+K3tAXi2Rk8= github.com/filecoin-project/go-address v0.0.0-20200107215422-da8eea2842b5/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= github.com/filecoin-project/go-address v0.0.3 h1:eVfbdjEbpbzIrbiSa+PiGUY+oDK9HnUn+M1R/ggoHf8= @@ -266,6 +266,8 @@ github.com/filecoin-project/specs-actors v0.8.7-0.20200811203034-272d022c1923 h1 github.com/filecoin-project/specs-actors v0.8.7-0.20200811203034-272d022c1923/go.mod h1:hukRu6vKQrrS7Nt+fC/ql4PqWLSfmAWNshD/VDtARZU= github.com/filecoin-project/specs-actors v0.8.7-0.20200811223639-8db91253c07a h1:DIOf9d5S4aBs6jwqGPzNSGhuMjn5w3R4kbHU3NpNBtw= github.com/filecoin-project/specs-actors v0.8.7-0.20200811223639-8db91253c07a/go.mod h1:hukRu6vKQrrS7Nt+fC/ql4PqWLSfmAWNshD/VDtARZU= +github.com/filecoin-project/specs-actors v0.9.2 h1:0JG0QLHw8pO6BPqPRe9eQxQW60biHAQsx1rlQ9QbzZ0= +github.com/filecoin-project/specs-actors v0.9.2/go.mod h1:YasnVUOUha0DN5wB+twl+V8LlDKVNknRG00kTJpsfFA= github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea h1:iixjULRQFPn7Q9KlIqfwLJnlAXO10bbkI+xy5GKGdLY= github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k= github.com/filecoin-project/specs-storage v0.1.1-0.20200730063404-f7db367e9401 h1:jLzN1hwO5WpKPu8ASbW8fs1FUCsOWNvoBXzQhv+8/E8= @@ -1383,6 +1385,8 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20200810223238-211df3b9e24c h1:BMg3YUwL github.com/whyrusleeping/cbor-gen v0.0.0-20200810223238-211df3b9e24c/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200811225321-4fed70922d45 h1:2QQ0rYt7Y8NhRPtuAlZMBNdqoVCh2dR6BQAtGJLlZgw= github.com/whyrusleeping/cbor-gen v0.0.0-20200811225321-4fed70922d45/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= +github.com/whyrusleeping/cbor-gen v0.0.0-20200812213548-958ddffe352c h1:otRnI08JoahNBxUFqX3372Ab9GnTj8L5J9iP5ImyxGU= +github.com/whyrusleeping/cbor-gen v0.0.0-20200812213548-958ddffe352c/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-ctrlnet v0.0.0-20180313164037-f564fbbdaa95/go.mod h1:SJqKCCPXRfBFCwXjfNT/skfsceF7+MBFLI2OrvuRA7g= diff --git a/paychmgr/manager.go b/paychmgr/manager.go index 6d8b8d912..6244d9104 100644 --- a/paychmgr/manager.go +++ b/paychmgr/manager.go @@ -13,6 +13,7 @@ import ( "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" logging "github.com/ipfs/go-log/v2" @@ -39,6 +40,7 @@ type PaychAPI struct { 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) + AdtStore(ctx context.Context) adt.Store } // paychAPI defines the API methods needed by the payment channel manager @@ -55,10 +57,14 @@ type managerAPI interface { // managerAPIImpl is used to create a composite that implements managerAPI type managerAPIImpl struct { - stateManagerAPI + *stmgr.StateManager paychAPI } +func (m *managerAPIImpl) AdtStore(ctx context.Context) adt.Store { + return m.ChainStore().Store(ctx) +} + type Manager struct { // The Manager context is used to terminate wait operations on shutdown ctx context.Context @@ -76,7 +82,7 @@ func NewManager(mctx helpers.MetricsCtx, lc fx.Lifecycle, sm *stmgr.StateManager ctx := helpers.LifecycleCtx(mctx, lc) ctx, shutdown := context.WithCancel(ctx) - impl := &managerAPIImpl{stateManagerAPI: sm, paychAPI: &api} + impl := &managerAPIImpl{StateManager: sm, paychAPI: &api} return &Manager{ ctx: ctx, shutdown: shutdown, diff --git a/paychmgr/mock_test.go b/paychmgr/mock_test.go index 1cb66e1af..2ccb6e1d7 100644 --- a/paychmgr/mock_test.go +++ b/paychmgr/mock_test.go @@ -5,6 +5,10 @@ import ( "fmt" "sync" + cbornode "github.com/ipfs/go-ipld-cbor" + + "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" @@ -34,6 +38,7 @@ type mockStateManager struct { lk sync.Mutex accountState map[address.Address]account.State paychState map[address.Address]mockPchState + store adt.Store response *api.InvocResult } @@ -41,9 +46,14 @@ func newMockStateManager() *mockStateManager { return &mockStateManager{ accountState: make(map[address.Address]account.State), 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) { sm.lk.Lock() defer sm.lk.Unlock() @@ -56,6 +66,17 @@ 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) { sm.lk.Lock() defer sm.lk.Unlock() diff --git a/paychmgr/paych.go b/paychmgr/paych.go index ba110b186..d4b02ec11 100644 --- a/paychmgr/paych.go +++ b/paychmgr/paych.go @@ -5,6 +5,8 @@ import ( "context" "fmt" + "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/ipfs/go-cid" "github.com/filecoin-project/go-address" @@ -88,7 +90,7 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add } // Check the voucher against the highest known voucher nonce / value - laneStates, err := ca.laneState(pchState, ch) + laneStates, err := ca.laneState(ctx, pchState, ch) if err != nil { return nil, err } @@ -313,14 +315,29 @@ func (ca *channelAccessor) nextNonceForLane(ctx context.Context, ch address.Addr // laneState gets the LaneStates from chain, then applies all vouchers in // the data store over the chain state -func (ca *channelAccessor) laneState(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) - laneStates := make(map[uint64]*paych.LaneState, len(state.LaneStates)) // Get the lane state from the chain - for _, laneState := range state.LaneStates { - laneStates[laneState.ID] = laneState + store := ca.api.AdtStore(ctx) + lsamt, err := adt.AsArray(store, state.LaneStates) + if err != nil { + return nil, 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 + laneStates := make(map[uint64]*paych.LaneState, lsamt.Length()) + err = lsamt.ForEach(&ls, func(i int64) error { + current := ls + laneStates[uint64(i)] = ¤t + return nil + }) + if err != nil { + return nil, err } // Apply locally stored vouchers @@ -339,7 +356,6 @@ func (ca *channelAccessor) laneState(state *paych.State, ch address.Address) (ma ls, ok := laneStates[v.Voucher.Lane] if !ok { ls = &paych.LaneState{ - ID: v.Voucher.Lane, Redeemed: types.NewInt(0), Nonce: 0, } diff --git a/paychmgr/paych_test.go b/paychmgr/paych_test.go index 69aa39119..56aa9ebb2 100644 --- a/paychmgr/paych_test.go +++ b/paychmgr/paych_test.go @@ -5,6 +5,7 @@ import ( "testing" "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/lotus/lib/sigs" @@ -39,6 +40,8 @@ func TestPaychOutbound(t *testing.T) { toAcct := tutils.NewIDAddr(t, 202) 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.setPaychState(ch, nil, paych.State{ @@ -47,7 +50,7 @@ func TestPaychOutbound(t *testing.T) { ToSend: big.NewInt(0), SettlingAt: abi.ChainEpoch(0), MinSettleHeight: abi.ChainEpoch(0), - LaneStates: []*paych.LaneState{}, + LaneStates: arr, }) mgr, err := newManager(store, mock) @@ -77,6 +80,8 @@ func TestPaychInbound(t *testing.T) { toAcct := tutils.NewIDAddr(t, 202) 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.setPaychState(ch, nil, paych.State{ @@ -85,7 +90,7 @@ func TestPaychInbound(t *testing.T) { ToSend: big.NewInt(0), SettlingAt: abi.ChainEpoch(0), MinSettleHeight: abi.ChainEpoch(0), - LaneStates: []*paych.LaneState{}, + LaneStates: arr, }) mgr, err := newManager(store, mock) @@ -129,7 +134,7 @@ func TestCheckVoucherValid(t *testing.T) { voucherAmount big.Int voucherLane uint64 voucherNonce uint64 - laneStates []*paych.LaneState + laneStates map[uint64]paych.LaneState }{{ name: "passes when voucher amount < balance", key: fromKeyPrivate, @@ -159,11 +164,12 @@ func TestCheckVoucherValid(t *testing.T) { voucherAmount: big.NewInt(5), voucherLane: 1, voucherNonce: 2, - laneStates: []*paych.LaneState{{ - ID: 1, - Redeemed: big.NewInt(2), - Nonce: 3, - }}, + laneStates: map[uint64]paych.LaneState{ + 1: { + Redeemed: big.NewInt(2), + Nonce: 3, + }, + }, }, { name: "passes when nonce higher", key: fromKeyPrivate, @@ -172,11 +178,12 @@ func TestCheckVoucherValid(t *testing.T) { voucherAmount: big.NewInt(5), voucherLane: 1, voucherNonce: 3, - laneStates: []*paych.LaneState{{ - ID: 1, - Redeemed: big.NewInt(2), - Nonce: 2, - }}, + laneStates: map[uint64]paych.LaneState{ + 1: { + Redeemed: big.NewInt(2), + Nonce: 2, + }, + }, }, { name: "passes when nonce for different lane", key: fromKeyPrivate, @@ -185,11 +192,12 @@ func TestCheckVoucherValid(t *testing.T) { voucherAmount: big.NewInt(5), voucherLane: 2, voucherNonce: 2, - laneStates: []*paych.LaneState{{ - ID: 1, - Redeemed: big.NewInt(2), - Nonce: 3, - }}, + laneStates: map[uint64]paych.LaneState{ + 1: { + Redeemed: big.NewInt(2), + Nonce: 3, + }, + }, }, { name: "fails when voucher has higher nonce but lower value than lane state", expectError: true, @@ -199,11 +207,12 @@ func TestCheckVoucherValid(t *testing.T) { voucherAmount: big.NewInt(5), voucherLane: 1, voucherNonce: 3, - laneStates: []*paych.LaneState{{ - ID: 1, - Redeemed: big.NewInt(6), - Nonce: 2, - }}, + laneStates: map[uint64]paych.LaneState{ + 1: { + Redeemed: big.NewInt(6), + Nonce: 2, + }, + }, }, { name: "fails when voucher + ToSend > balance", expectError: true, @@ -226,11 +235,13 @@ func TestCheckVoucherValid(t *testing.T) { voucherAmount: big.NewInt(6), voucherLane: 1, voucherNonce: 2, - laneStates: []*paych.LaneState{{ - ID: 1, // Lane 1 (same as voucher lane 1) - Redeemed: big.NewInt(4), - Nonce: 1, - }}, + laneStates: map[uint64]paych.LaneState{ + // Lane 1 (same as voucher lane 1) + 1: { + Redeemed: big.NewInt(4), + Nonce: 1, + }, + }, }, { // required balance = toSend + total redeemed // = 1 + 4 (lane 2) + 6 (voucher lane 1) @@ -244,11 +255,13 @@ func TestCheckVoucherValid(t *testing.T) { voucherAmount: big.NewInt(6), voucherLane: 1, voucherNonce: 1, - laneStates: []*paych.LaneState{{ - ID: 2, // Lane 2 (different from voucher lane 1) - Redeemed: big.NewInt(4), - Nonce: 1, - }}, + laneStates: map[uint64]paych.LaneState{ + // Lane 2 (different from voucher lane 1) + 2: { + Redeemed: big.NewInt(4), + Nonce: 1, + }, + }, }} for _, tcase := range tcases { @@ -262,13 +275,17 @@ func TestCheckVoucherValid(t *testing.T) { Nonce: 0, Balance: tcase.actorBalance, } + + 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: tcase.laneStates, + LaneStates: laneStates, }) mgr, err := newManager(store, mock) @@ -309,15 +326,16 @@ func TestCheckVoucherValidCountingAllLanes(t *testing.T) { actorBalance := big.NewInt(10) toSend := big.NewInt(1) - laneStates := []*paych.LaneState{{ - ID: 1, - Nonce: 1, - Redeemed: big.NewInt(3), - }, { - ID: 2, - Nonce: 1, - Redeemed: big.NewInt(4), - }} + laneStates := map[uint64]paych.LaneState{ + 1: { + Nonce: 1, + Redeemed: big.NewInt(3), + }, + 2: { + Nonce: 1, + Redeemed: big.NewInt(4), + }, + } act := &types.Actor{ Code: builtin.AccountActorCodeID, @@ -325,13 +343,16 @@ func TestCheckVoucherValidCountingAllLanes(t *testing.T) { Nonce: 0, 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: laneStates, + LaneStates: lsCid, }) mgr, err := newManager(store, mock) @@ -625,6 +646,8 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, addre 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}) @@ -640,7 +663,7 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, addre ToSend: big.NewInt(0), SettlingAt: abi.ChainEpoch(0), MinSettleHeight: abi.ChainEpoch(0), - LaneStates: []*paych.LaneState{}, + LaneStates: arr, }) store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) diff --git a/paychmgr/state.go b/paychmgr/state.go index 1502cc8cb..015479913 100644 --- a/paychmgr/state.go +++ b/paychmgr/state.go @@ -2,6 +2,9 @@ package paychmgr import ( "context" + "errors" + + "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/filecoin-project/specs-actors/actors/builtin/account" @@ -43,10 +46,15 @@ func (ca *stateAccessor) loadStateChannelInfo(ctx context.Context, ch address.Ad } to := account.Address + nextLane, err := ca.nextLaneFromState(ctx, st) + if err != nil { + return nil, err + } + ci := &ChannelInfo{ Channel: &ch, Direction: dir, - NextLane: nextLaneFromState(st), + NextLane: nextLane, } if dir == DirOutbound { @@ -60,16 +68,28 @@ func (ca *stateAccessor) loadStateChannelInfo(ctx context.Context, ch address.Ad return ci, nil } -func nextLaneFromState(st *paych.State) uint64 { - if len(st.LaneStates) == 0 { - return 0 +func (ca *stateAccessor) nextLaneFromState(ctx context.Context, st *paych.State) (uint64, error) { + store := ca.sm.AdtStore(ctx) + laneStates, err := adt.AsArray(store, st.LaneStates) + if err != nil { + return 0, err + } + if laneStates.Length() == 0 { + return 0, nil } - maxLane := st.LaneStates[0].ID - for _, state := range st.LaneStates { - if state.ID > maxLane { - maxLane = state.ID + nextID := int64(0) + stopErr := errors.New("stop") + if err := laneStates.ForEach(nil, func(i int64) error { + if nextID < i { + // We've found a hole. Stop here. + return stopErr } + nextID++ + return nil + }); err != nil && err != stopErr { + return 0, err } - return maxLane + 1 + + return uint64(nextID), nil }