diff --git a/api/test/paych.go b/api/test/paych.go index 36eb2c256..15ce352bd 100644 --- a/api/test/paych.go +++ b/api/test/paych.go @@ -67,7 +67,7 @@ func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { t.Fatal(err) } - channelAmt := int64(100000) + channelAmt := int64(7000) channelInfo, err := paymentCreator.PaychGet(ctx, createrAddr, receiverAddr, abi.NewTokenAmount(channelAmt)) if err != nil { t.Fatal(err) @@ -169,6 +169,51 @@ func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) { t.Fatal("Timed out waiting for receiver to submit vouchers") } + // Create a new voucher now that some vouchers have already been submitted + vouchRes, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(1000), 3) + if err != nil { + t.Fatal(err) + } + if vouchRes.Voucher == nil { + t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouchRes.Shortfall)) + } + vdelta, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouchRes.Voucher, nil, abi.NewTokenAmount(1000)) + if err != nil { + t.Fatal(err) + } + if !vdelta.Equals(abi.NewTokenAmount(1000)) { + t.Fatal("voucher didn't have the right amount") + } + + // Create a new voucher whose value would exceed the channel balance + excessAmt := abi.NewTokenAmount(1000) + vouchRes, err = paymentCreator.PaychVoucherCreate(ctx, channel, excessAmt, 4) + if err != nil { + t.Fatal(err) + } + if vouchRes.Voucher != nil { + t.Fatal("Expected not to be able to create voucher whose value would exceed channel balance") + } + if !vouchRes.Shortfall.Equals(excessAmt) { + t.Fatal(fmt.Errorf("Expected voucher shortfall of %d, got %d", excessAmt, vouchRes.Shortfall)) + } + + // Add a voucher whose value would exceed the channel balance + vouch := &paych.SignedVoucher{ChannelAddr: channel, Amount: excessAmt, Lane: 4, Nonce: 1} + vb, err := vouch.SigningBytes() + if err != nil { + t.Fatal(err) + } + sig, err := paymentCreator.WalletSign(ctx, createrAddr, vb) + if err != nil { + t.Fatal(err) + } + vouch.Signature = sig + _, err = paymentReceiver.PaychVoucherAdd(ctx, channel, vouch, nil, abi.NewTokenAmount(1000)) + if err == nil { + t.Fatal(fmt.Errorf("Expected shortfall error of %d", excessAmt)) + } + // wait for the settlement period to pass before collecting waitForBlocks(ctx, t, bm, paymentReceiver, receiverAddr, paych.SettleDelay) diff --git a/chain/actors/builtin/paych/mock/mock.go b/chain/actors/builtin/paych/mock/mock.go index c4903f3ac..3b82511ff 100644 --- a/chain/actors/builtin/paych/mock/mock.go +++ b/chain/actors/builtin/paych/mock/mock.go @@ -27,10 +27,9 @@ type mockLaneState struct { 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} + return &mockState{from: from, to: to, settlingAt: settlingAt, toSend: big.NewInt(0), lanes: lanes} } // NewMockLaneState constructs a state for a payment channel lane with the set fixed values diff --git a/paychmgr/paych.go b/paychmgr/paych.go index f856b9890..e67991911 100644 --- a/paychmgr/paych.go +++ b/paychmgr/paych.go @@ -205,7 +205,7 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add } // Check the voucher against the highest known voucher nonce / value - laneStates, err := ca.laneState(ctx, pchState, ch) + laneStates, err := ca.laneState(pchState, ch) if err != nil { return nil, err } @@ -253,16 +253,9 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add return nil, err } - // Total required balance = total redeemed + toSend - // Must not exceed actor balance - ts, err := pchState.ToSend() - if err != nil { - return nil, err - } - - newTotal := types.BigAdd(totalRedeemed, ts) - if act.Balance.LessThan(newTotal) { - return nil, newErrInsufficientFunds(types.BigSub(newTotal, act.Balance)) + // Total required balance must not exceed actor balance + if act.Balance.LessThan(totalRedeemed) { + return nil, newErrInsufficientFunds(types.BigSub(totalRedeemed, act.Balance)) } if len(sv.Merges) != 0 { @@ -505,7 +498,6 @@ func (ca *channelAccessor) allocateLane(ch address.Address) (uint64, error) { ca.lk.Lock() defer ca.lk.Unlock() - // TODO: should this take into account lane state? return ca.store.AllocateLane(ch) } @@ -520,7 +512,7 @@ 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(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) @@ -552,9 +544,12 @@ func (ca *channelAccessor) laneState(ctx context.Context, state paych.State, ch return nil, xerrors.Errorf("paych merges not handled yet") } - // If there's a voucher for a lane that isn't in chain state just - // create it + // Check if there is an existing laneState in the payment channel + // for this voucher's lane ls, ok := laneStates[v.Voucher.Lane] + + // If the voucher does not have a higher nonce than the existing + // laneState for this lane, ignore it if ok { n, err := ls.Nonce() if err != nil { @@ -565,6 +560,7 @@ func (ca *channelAccessor) laneState(ctx context.Context, state paych.State, ch } } + // Voucher has a higher nonce, so replace laneState with this voucher laneStates[v.Voucher.Lane] = laneState{v.Voucher.Amount, v.Voucher.Nonce} } diff --git a/paychmgr/paych_test.go b/paychmgr/paych_test.go index b27b1e540..56e7e9089 100644 --- a/paychmgr/paych_test.go +++ b/paychmgr/paych_test.go @@ -47,7 +47,6 @@ func TestCheckVoucherValid(t *testing.T) { expectError bool key []byte actorBalance big.Int - toSend big.Int voucherAmount big.Int voucherLane uint64 voucherNonce uint64 @@ -56,35 +55,30 @@ func TestCheckVoucherValid(t *testing.T) { name: "passes when voucher amount < balance", key: fromKeyPrivate, actorBalance: big.NewInt(10), - toSend: big.NewInt(0), voucherAmount: big.NewInt(5), }, { name: "fails when funds too low", expectError: true, key: fromKeyPrivate, actorBalance: big.NewInt(5), - toSend: big.NewInt(0), voucherAmount: big.NewInt(10), }, { name: "fails when invalid signature", expectError: true, key: randKeyPrivate, actorBalance: big.NewInt(10), - toSend: big.NewInt(0), voucherAmount: big.NewInt(5), }, { name: "fails when signed by channel To account (instead of From account)", expectError: true, key: toKeyPrivate, actorBalance: big.NewInt(10), - toSend: big.NewInt(0), voucherAmount: big.NewInt(5), }, { name: "fails when nonce too low", expectError: true, key: fromKeyPrivate, actorBalance: big.NewInt(10), - toSend: big.NewInt(0), voucherAmount: big.NewInt(5), voucherLane: 1, voucherNonce: 2, @@ -95,7 +89,6 @@ func TestCheckVoucherValid(t *testing.T) { name: "passes when nonce higher", key: fromKeyPrivate, actorBalance: big.NewInt(10), - toSend: big.NewInt(0), voucherAmount: big.NewInt(5), voucherLane: 1, voucherNonce: 3, @@ -106,7 +99,6 @@ func TestCheckVoucherValid(t *testing.T) { name: "passes when nonce for different lane", key: fromKeyPrivate, actorBalance: big.NewInt(10), - toSend: big.NewInt(0), voucherAmount: big.NewInt(5), voucherLane: 2, voucherNonce: 2, @@ -118,32 +110,22 @@ func TestCheckVoucherValid(t *testing.T) { expectError: true, key: fromKeyPrivate, actorBalance: big.NewInt(10), - toSend: big.NewInt(0), voucherAmount: big.NewInt(5), voucherLane: 1, voucherNonce: 3, laneStates: map[uint64]paych.LaneState{ 1: paychmock.NewMockLaneState(big.NewInt(6), 2), }, - }, { - name: "fails when voucher + ToSend > balance", - expectError: true, - key: fromKeyPrivate, - actorBalance: big.NewInt(10), - toSend: big.NewInt(9), - voucherAmount: big.NewInt(2), }, { // voucher supersedes lane 1 redeemed so // lane 1 effective redeemed = voucher amount // - // required balance = toSend + total redeemed - // = 1 + 6 (lane1) + // required balance = voucher amt // = 7 // So required balance: 7 < actor balance: 10 - name: "passes when voucher + total redeemed <= balance", + name: "passes when voucher total redeemed <= balance", key: fromKeyPrivate, actorBalance: big.NewInt(10), - toSend: big.NewInt(1), voucherAmount: big.NewInt(6), voucherLane: 1, voucherNonce: 2, @@ -152,29 +134,68 @@ func TestCheckVoucherValid(t *testing.T) { 1: paychmock.NewMockLaneState(big.NewInt(4), 1), }, }, { - // required balance = toSend + total redeemed - // = 1 + 4 (lane 2) + 6 (voucher lane 1) + // required balance = total redeemed + // = 6 (voucher lane 1) + 5 (lane 2) // = 11 // So required balance: 11 > actor balance: 10 - name: "fails when voucher + total redeemed > balance", + name: "fails when voucher total redeemed > balance", expectError: true, key: fromKeyPrivate, actorBalance: big.NewInt(10), - toSend: big.NewInt(1), voucherAmount: big.NewInt(6), voucherLane: 1, voucherNonce: 1, laneStates: map[uint64]paych.LaneState{ // Lane 2 (different from voucher lane 1) - 2: paychmock.NewMockLaneState(big.NewInt(4), 1), + 2: paychmock.NewMockLaneState(big.NewInt(5), 1), + }, + }, { + // voucher supersedes lane 1 redeemed so + // lane 1 effective redeemed = voucher amount + // + // required balance = total redeemed + // = 6 (new voucher lane 1) + 5 (lane 2) + // = 11 + // So required balance: 11 > actor balance: 10 + name: "fails when voucher total redeemed > balance", + expectError: true, + key: fromKeyPrivate, + actorBalance: big.NewInt(10), + voucherAmount: big.NewInt(6), + voucherLane: 1, + voucherNonce: 2, + laneStates: map[uint64]paych.LaneState{ + // Lane 1 (superseded by new voucher in voucher lane 1) + 1: paychmock.NewMockLaneState(big.NewInt(5), 1), + // Lane 2 (different from voucher lane 1) + 2: paychmock.NewMockLaneState(big.NewInt(5), 1), + }, + }, { + // voucher supersedes lane 1 redeemed so + // lane 1 effective redeemed = voucher amount + // + // required balance = total redeemed + // = 5 (new voucher lane 1) + 5 (lane 2) + // = 10 + // So required balance: 10 <= actor balance: 10 + name: "passes when voucher total redeemed <= balance", + expectError: false, + key: fromKeyPrivate, + actorBalance: big.NewInt(10), + voucherAmount: big.NewInt(5), + voucherLane: 1, + voucherNonce: 2, + laneStates: map[uint64]paych.LaneState{ + // Lane 1 (superseded by new voucher in voucher lane 1) + 1: paychmock.NewMockLaneState(big.NewInt(4), 1), + // Lane 2 (different from voucher lane 1) + 2: paychmock.NewMockLaneState(big.NewInt(5), 1), }, }} for _, tcase := range tcases { tcase := tcase t.Run(tcase.name, func(t *testing.T) { - store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) - // Create an actor for the channel with the test case balance act := &types.Actor{ Code: builtin.AccountActorCodeID, @@ -184,16 +205,17 @@ func TestCheckVoucherValid(t *testing.T) { } mock.setPaychState(ch, act, paychmock.NewMockPayChState( - fromAcct, toAcct, abi.ChainEpoch(0), tcase.toSend, tcase.laneStates)) + fromAcct, toAcct, abi.ChainEpoch(0), tcase.laneStates)) // Create a manager + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) mgr, err := newManager(store, mock) require.NoError(t, err) // Add channel To address to wallet mock.addWalletAddress(to) - // Create a signed voucher + // Create the test case signed voucher sv := createTestVoucher(t, ch, tcase.voucherLane, tcase.voucherNonce, tcase.voucherAmount, tcase.key) // Check the voucher's validity @@ -207,135 +229,11 @@ func TestCheckVoucherValid(t *testing.T) { } } -func TestCheckVoucherValidCountingAllLanes(t *testing.T) { - ctx := context.Background() - - fromKeyPrivate, fromKeyPublic := testGenerateKeyPair(t) - - ch := tutils.NewIDAddr(t, 100) - from := tutils.NewSECP256K1Addr(t, string(fromKeyPublic)) - to := tutils.NewSECP256K1Addr(t, "secpTo") - fromAcct := tutils.NewActorAddr(t, "fromAct") - toAcct := tutils.NewActorAddr(t, "toAct") - minDelta := big.NewInt(0) - - mock := newMockManagerAPI() - 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: paychmock.NewMockLaneState(big.NewInt(3), 1), - 2: paychmock.NewMockLaneState(big.NewInt(4), 1), - } - - act := &types.Actor{ - Code: builtin.AccountActorCodeID, - Head: cid.Cid{}, - Nonce: 0, - Balance: actorBalance, - } - - mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), toSend, laneStates)) - - mgr, err := newManager(store, mock) - require.NoError(t, err) - - // Add channel To address to wallet - mock.addWalletAddress(to) - - // - // Should not be possible to add a voucher with a value such that - // + toSend > - // - // lane 1 redeemed: 3 - // voucher amount (lane 1): 6 - // lane 1 redeemed (with voucher): 6 - // - // Lane 1: 6 - // Lane 2: 4 - // toSend: 1 - // -- - // total: 11 - // - // actor balance is 10 so total is too high. - // - voucherLane := uint64(1) - voucherNonce := uint64(2) - voucherAmount := big.NewInt(6) - sv := createTestVoucher(t, ch, voucherLane, voucherNonce, voucherAmount, fromKeyPrivate) - err = mgr.CheckVoucherValid(ctx, ch, sv) - require.Error(t, err) - - // - // lane 1 redeemed: 3 - // voucher amount (lane 1): 4 - // lane 1 redeemed (with voucher): 4 - // - // Lane 1: 4 - // Lane 2: 4 - // toSend: 1 - // -- - // total: 9 - // - // actor balance is 10 so total is ok. - // - voucherAmount = big.NewInt(4) - sv = createTestVoucher(t, ch, voucherLane, voucherNonce, voucherAmount, fromKeyPrivate) - err = mgr.CheckVoucherValid(ctx, ch, sv) - require.NoError(t, err) - - // Add voucher to lane 1, so Lane 1 effective redeemed - // (with first voucher) is now 4 - _, err = mgr.AddVoucherOutbound(ctx, ch, sv, nil, minDelta) - require.NoError(t, err) - - // - // lane 1 redeemed: 4 - // voucher amount (lane 1): 6 - // lane 1 redeemed (with voucher): 6 - // - // Lane 1: 6 - // Lane 2: 4 - // toSend: 1 - // -- - // total: 11 - // - // actor balance is 10 so total is too high. - // - voucherNonce++ - voucherAmount = big.NewInt(6) - sv = createTestVoucher(t, ch, voucherLane, voucherNonce, voucherAmount, fromKeyPrivate) - err = mgr.CheckVoucherValid(ctx, ch, sv) - require.Error(t, err) - - // - // lane 1 redeemed: 4 - // voucher amount (lane 1): 5 - // lane 1 redeemed (with voucher): 5 - // - // Lane 1: 5 - // Lane 2: 4 - // toSend: 1 - // -- - // total: 10 - // - // actor balance is 10 so total is ok. - // - voucherAmount = big.NewInt(5) - sv = createTestVoucher(t, ch, voucherLane, voucherNonce, voucherAmount, fromKeyPrivate) - err = mgr.CheckVoucherValid(ctx, ch, sv) - require.NoError(t, err) -} - func TestCreateVoucher(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - s := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(t) // Create a voucher in lane 1 voucherLane1Amt := big.NewInt(5) @@ -400,7 +298,7 @@ func TestAddVoucherDelta(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - s := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(t) voucherLane := uint64(1) @@ -442,7 +340,7 @@ func TestAddVoucherNextLane(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - s := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(t) minDelta := big.NewInt(0) voucherAmount := big.NewInt(2) @@ -489,10 +387,8 @@ func TestAddVoucherNextLane(t *testing.T) { } func TestAllocateLane(t *testing.T) { - ctx := context.Background() - // Set up a manager with a single payment channel - s := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(t) // First lane should be 0 lane, err := s.mgr.AllocateLane(s.ch) @@ -525,7 +421,6 @@ func TestAllocateLaneWithExistingLaneState(t *testing.T) { // Create a channel that will be retrieved from state actorBalance := big.NewInt(10) - toSend := big.NewInt(1) act := &types.Actor{ Code: builtin.AccountActorCodeID, @@ -534,7 +429,7 @@ func TestAllocateLaneWithExistingLaneState(t *testing.T) { Balance: actorBalance, } - mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), toSend, make(map[uint64]paych.LaneState))) + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) mgr, err := newManager(store, mock) require.NoError(t, err) @@ -559,7 +454,7 @@ func TestAddVoucherProof(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - s := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(t) nonce := uint64(1) voucherAmount := big.NewInt(1) @@ -622,10 +517,11 @@ func TestAddVoucherInboundWalletKey(t *testing.T) { } mock := newMockManagerAPI() + mock.setAccountAddress(fromAcct, from) mock.setAccountAddress(toAcct, to) - mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), types.NewInt(0), make(map[uint64]paych.LaneState))) + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) // Create a manager store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) @@ -660,7 +556,7 @@ func TestBestSpendable(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - s := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(t) // Add vouchers to lane 1 with amounts: [1, 2, 3] voucherLane := uint64(1) @@ -740,7 +636,7 @@ func TestCheckSpendable(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - s := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(t) // Create voucher with Extra voucherLane := uint64(1) @@ -821,7 +717,7 @@ func TestSubmitVoucher(t *testing.T) { ctx := context.Background() // Set up a manager with a single payment channel - s := testSetupMgrWithChannel(ctx, t) + s := testSetupMgrWithChannel(t) // Create voucher with Extra voucherLane := uint64(1) @@ -908,7 +804,7 @@ type testScaffold struct { fromKeyPrivate []byte } -func testSetupMgrWithChannel(ctx context.Context, t *testing.T) *testScaffold { +func testSetupMgrWithChannel(t *testing.T) *testScaffold { fromKeyPrivate, fromKeyPublic := testGenerateKeyPair(t) ch := tutils.NewIDAddr(t, 100) @@ -929,7 +825,7 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) *testScaffold { Nonce: 0, Balance: balance, } - mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), big.NewInt(0), make(map[uint64]paych.LaneState))) + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) mgr, err := newManager(store, mock) diff --git a/paychmgr/paychget_test.go b/paychmgr/paychget_test.go index 430e66c67..e35ae8371 100644 --- a/paychmgr/paychget_test.go +++ b/paychmgr/paychget_test.go @@ -978,7 +978,7 @@ func TestPaychAvailableFunds(t *testing.T) { Nonce: 0, Balance: createAmt, } - mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), big.NewInt(0), make(map[uint64]paych.LaneState))) + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) // Send create channel response response := testChannelResponse(t, ch) mock.receiveMsgResponse(createMsgCid, response) diff --git a/paychmgr/paychvoucherfunds_test.go b/paychmgr/paychvoucherfunds_test.go new file mode 100644 index 000000000..dcbb4acc9 --- /dev/null +++ b/paychmgr/paychvoucherfunds_test.go @@ -0,0 +1,103 @@ +package paychmgr + +import ( + "context" + "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" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/actors/builtin" + 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" +) + +// TestPaychAddVoucherAfterAddFunds tests adding a voucher to a channel with +// insufficient funds, then adding funds to the channel, then adding the +// voucher again +func TestPaychAddVoucherAfterAddFunds(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + fromKeyPrivate, fromKeyPublic := testGenerateKeyPair(t) + ch := tutils.NewIDAddr(t, 100) + from := tutils.NewSECP256K1Addr(t, string(fromKeyPublic)) + to := tutils.NewSECP256K1Addr(t, "secpTo") + fromAcct := tutils.NewActorAddr(t, "fromAct") + toAcct := tutils.NewActorAddr(t, "toAct") + + mock := newMockManagerAPI() + defer mock.close() + + // Add the from signing key to the wallet + mock.setAccountAddress(fromAcct, from) + mock.setAccountAddress(toAcct, to) + mock.addSigningKey(fromKeyPrivate) + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // Send create message for a channel with value 10 + createAmt := big.NewInt(10) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt) + require.NoError(t, err) + + // Send create channel response + response := testChannelResponse(t, ch) + mock.receiveMsgResponse(createMsgCid, response) + + // Create an actor in state for the channel with the initial channel balance + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: createAmt, + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + + // Wait for create response to be processed by manager + _, err = mgr.GetPaychWaitReady(ctx, createMsgCid) + require.NoError(t, err) + + // Create a voucher with a value equal to the channel balance + voucher := paych.SignedVoucher{Amount: createAmt, Lane: 1} + res, err := mgr.CreateVoucher(ctx, ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) + + // Create a voucher in a different lane with an amount that exceeds the + // channel balance + excessAmt := types.NewInt(5) + voucher = paych.SignedVoucher{Amount: excessAmt, Lane: 2} + res, err = mgr.CreateVoucher(ctx, ch, voucher) + require.NoError(t, err) + require.Nil(t, res.Voucher) + require.Equal(t, res.Shortfall, excessAmt) + + // Add funds so as to cover the voucher shortfall + _, addFundsMsgCid, err := mgr.GetPaych(ctx, from, to, excessAmt) + require.NoError(t, err) + + // Trigger add funds confirmation + mock.receiveMsgResponse(addFundsMsgCid, types.MessageReceipt{ExitCode: 0}) + + // Update actor test case balance to reflect added funds + act.Balance = types.BigAdd(createAmt, excessAmt) + + // Wait for add funds confirmation to be processed by manager + _, err = mgr.GetPaychWaitReady(ctx, addFundsMsgCid) + require.NoError(t, err) + + // Adding same voucher that previously exceeded channel balance + // should succeed now that the channel balance has been increased + res, err = mgr.CreateVoucher(ctx, ch, voucher) + require.NoError(t, err) + require.NotNil(t, res.Voucher) +} diff --git a/paychmgr/simple.go b/paychmgr/simple.go index d49ccafe6..ca778829f 100644 --- a/paychmgr/simple.go +++ b/paychmgr/simple.go @@ -312,7 +312,7 @@ func (ca *channelAccessor) currentAvailableFunds(channelID string, queuedAmt typ return nil, err } - laneStates, err := ca.laneState(ca.chctx, pchState, ch) + laneStates, err := ca.laneState(pchState, ch) if err != nil { return nil, err }