Merge pull request #2991 from filecoin-project/refactor/paych-lane-amt

paych: use spec-actors paych with AMT for lane states
This commit is contained in:
Whyrusleeping 2020-08-12 17:51:17 -07:00 committed by GitHub
commit bd31e8d37e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 67 deletions

6
go.mod
View File

@ -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

8
go.sum
View File

@ -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=

View File

@ -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,

View File

@ -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()

View File

@ -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)] = &current
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,
}

View File

@ -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()))

View File

@ -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
}