2022-08-29 14:25:30 +00:00
|
|
|
// stm: #integration
|
2021-05-17 12:28:09 +00:00
|
|
|
package itests
|
2020-07-18 03:07:02 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2020-07-22 17:55:31 +00:00
|
|
|
"github.com/ipfs/go-cid"
|
2022-06-14 15:00:51 +00:00
|
|
|
cbor "github.com/ipfs/go-ipld-cbor"
|
2021-06-14 17:59:15 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2020-07-22 17:55:31 +00:00
|
|
|
|
2020-07-18 03:07:02 +00:00
|
|
|
"github.com/filecoin-project/go-address"
|
2022-06-14 15:00:51 +00:00
|
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
|
|
"github.com/filecoin-project/go-state-types/big"
|
|
|
|
paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych"
|
2020-07-28 16:16:46 +00:00
|
|
|
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
2021-01-29 20:01:00 +00:00
|
|
|
"github.com/filecoin-project/lotus/blockstore"
|
2020-07-18 03:07:02 +00:00
|
|
|
"github.com/filecoin-project/lotus/build"
|
2020-09-28 20:34:14 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/actors/adt"
|
2020-10-08 01:09:33 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
2020-09-28 20:34:14 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/paych"
|
2020-10-08 01:09:33 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/actors/policy"
|
2020-07-18 03:07:02 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/events"
|
|
|
|
"github.com/filecoin-project/lotus/chain/events/state"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
2022-06-14 15:00:51 +00:00
|
|
|
"github.com/filecoin-project/lotus/itests/kit"
|
2020-07-18 03:07:02 +00:00
|
|
|
)
|
|
|
|
|
2021-05-18 20:32:10 +00:00
|
|
|
func TestPaymentChannelsAPI(t *testing.T) {
|
2021-12-13 12:41:04 +00:00
|
|
|
//stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001,
|
|
|
|
//stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01
|
|
|
|
//stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001
|
|
|
|
//stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001
|
2021-12-14 10:33:33 +00:00
|
|
|
|
|
|
|
//stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001
|
2021-06-18 18:45:29 +00:00
|
|
|
kit.QuietMiningLogs()
|
2021-05-18 20:32:10 +00:00
|
|
|
|
2020-07-18 03:07:02 +00:00
|
|
|
ctx := context.Background()
|
2023-01-23 11:02:42 +00:00
|
|
|
blockTime := 10 * time.Millisecond
|
2021-06-14 13:28:05 +00:00
|
|
|
|
|
|
|
var (
|
2021-06-18 18:45:29 +00:00
|
|
|
paymentCreator kit.TestFullNode
|
|
|
|
paymentReceiver kit.TestFullNode
|
|
|
|
miner kit.TestMiner
|
2021-06-14 13:28:05 +00:00
|
|
|
)
|
|
|
|
|
2021-06-18 18:45:29 +00:00
|
|
|
ens := kit.NewEnsemble(t, kit.MockProofs()).
|
2021-06-14 13:28:05 +00:00
|
|
|
FullNode(&paymentCreator).
|
|
|
|
FullNode(&paymentReceiver).
|
2021-06-22 14:48:07 +00:00
|
|
|
Miner(&miner, &paymentCreator, kit.WithAllSubsystems()).
|
2021-06-14 13:28:05 +00:00
|
|
|
Start().
|
|
|
|
InterconnectAll()
|
2022-02-14 14:47:18 +00:00
|
|
|
bms := ens.BeginMiningMustPost(blockTime)
|
2021-06-14 13:28:05 +00:00
|
|
|
bm := bms[0]
|
2020-07-18 03:07:02 +00:00
|
|
|
|
2023-01-23 11:02:42 +00:00
|
|
|
waitRecvInSync := func() {
|
|
|
|
// paymentCreator is the block miner, in some cases paymentReceiver may fall behind, so we wait for it to catch up
|
|
|
|
|
|
|
|
head, err := paymentReceiver.ChainHead(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
paymentReceiver.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()))
|
|
|
|
}
|
|
|
|
|
2020-07-18 03:07:02 +00:00
|
|
|
// send some funds to register the receiver
|
2020-10-11 18:12:01 +00:00
|
|
|
receiverAddr, err := paymentReceiver.WalletNew(ctx, types.KTSecp256k1)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-07-18 03:07:02 +00:00
|
|
|
|
2021-06-18 18:45:29 +00:00
|
|
|
kit.SendFunds(ctx, t, &paymentCreator, receiverAddr, abi.NewTokenAmount(1e18))
|
2020-07-18 03:07:02 +00:00
|
|
|
|
|
|
|
// setup the payment channel
|
|
|
|
createrAddr, err := paymentCreator.WalletDefaultAddress(ctx)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-07-18 03:07:02 +00:00
|
|
|
|
2020-09-14 08:11:11 +00:00
|
|
|
channelAmt := int64(7000)
|
2022-01-06 15:04:39 +00:00
|
|
|
channelInfo, err := paymentCreator.PaychGet(ctx, createrAddr, receiverAddr, abi.NewTokenAmount(channelAmt), api.PaychGetOpts{
|
|
|
|
OffChain: false,
|
|
|
|
})
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-07-22 17:55:31 +00:00
|
|
|
|
2020-08-11 14:45:45 +00:00
|
|
|
channel, err := paymentCreator.PaychGetWaitReady(ctx, channelInfo.WaitSentinel)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-07-22 20:09:46 +00:00
|
|
|
|
2023-01-23 11:02:42 +00:00
|
|
|
waitRecvInSync()
|
|
|
|
|
2020-07-18 03:07:02 +00:00
|
|
|
// allocate three lanes
|
|
|
|
var lanes []uint64
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
lane, err := paymentCreator.PaychAllocateLane(ctx, channel)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-07-18 03:07:02 +00:00
|
|
|
lanes = append(lanes, lane)
|
|
|
|
}
|
|
|
|
|
2020-07-22 20:09:46 +00:00
|
|
|
// Make two vouchers each for each lane, then save on the other side
|
|
|
|
// Note that the voucher with a value of 2000 has a higher nonce, so it
|
|
|
|
// supersedes the voucher with a value of 1000
|
2020-07-18 03:07:02 +00:00
|
|
|
for _, lane := range lanes {
|
|
|
|
vouch1, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(1000), lane)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, vouch1.Voucher, "Not enough funds to create voucher: missing %d", vouch1.Shortfall)
|
|
|
|
|
2020-07-18 03:07:02 +00:00
|
|
|
vouch2, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(2000), lane)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, vouch2.Voucher, "Not enough funds to create voucher: missing %d", vouch2.Shortfall)
|
|
|
|
|
2020-09-01 14:33:44 +00:00
|
|
|
delta1, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch1.Voucher, nil, abi.NewTokenAmount(1000))
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, abi.NewTokenAmount(1000), delta1, "voucher didn't have the right amount")
|
|
|
|
|
2020-09-01 14:33:44 +00:00
|
|
|
delta2, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch2.Voucher, nil, abi.NewTokenAmount(1000))
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, abi.NewTokenAmount(1000), delta2, "voucher didn't have the right amount")
|
2020-07-18 03:07:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// settle the payment channel
|
|
|
|
settleMsgCid, err := paymentCreator.PaychSettle(ctx, channel)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-07-22 17:55:31 +00:00
|
|
|
|
2020-07-28 23:16:47 +00:00
|
|
|
res := waitForMessage(ctx, t, paymentCreator, settleMsgCid, time.Second*10, "settle")
|
2021-06-14 17:59:15 +00:00
|
|
|
require.EqualValues(t, 0, res.Receipt.ExitCode, "Unable to settle payment channel")
|
2020-07-18 03:07:02 +00:00
|
|
|
|
2023-01-23 11:02:42 +00:00
|
|
|
waitRecvInSync()
|
|
|
|
|
2021-01-29 20:01:00 +00:00
|
|
|
creatorStore := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewAPIBlockstore(paymentCreator)))
|
2020-09-28 20:34:14 +00:00
|
|
|
|
2020-07-18 03:07:02 +00:00
|
|
|
// wait for the receiver to submit their vouchers
|
2021-08-04 00:10:30 +00:00
|
|
|
ev, err := events.NewEvents(ctx, paymentCreator)
|
|
|
|
require.NoError(t, err)
|
2020-07-18 03:07:02 +00:00
|
|
|
preds := state.NewStatePredicates(paymentCreator)
|
|
|
|
finished := make(chan struct{})
|
2021-12-10 15:08:25 +00:00
|
|
|
//stm: @CHAIN_STATE_GET_ACTOR_001
|
2021-08-04 00:10:30 +00:00
|
|
|
err = ev.StateChanged(func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) {
|
2020-09-28 20:34:14 +00:00
|
|
|
act, err := paymentCreator.StateGetActor(ctx, channel, ts.Key())
|
|
|
|
if err != nil {
|
|
|
|
return false, false, err
|
|
|
|
}
|
|
|
|
state, err := paych.Load(creatorStore, act)
|
|
|
|
if err != nil {
|
|
|
|
return false, false, err
|
|
|
|
}
|
|
|
|
toSend, err := state.ToSend()
|
2020-07-18 03:07:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, false, err
|
|
|
|
}
|
2020-09-28 20:34:14 +00:00
|
|
|
if toSend.GreaterThanEqual(abi.NewTokenAmount(6000)) {
|
2020-07-18 03:07:02 +00:00
|
|
|
return true, false, nil
|
|
|
|
}
|
|
|
|
return false, true, nil
|
|
|
|
}, func(oldTs, newTs *types.TipSet, states events.StateChange, curH abi.ChainEpoch) (more bool, err error) {
|
|
|
|
toSendChange := states.(*state.PayChToSendChange)
|
|
|
|
if toSendChange.NewToSend.GreaterThanEqual(abi.NewTokenAmount(6000)) {
|
|
|
|
close(finished)
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}, func(ctx context.Context, ts *types.TipSet) error {
|
|
|
|
return nil
|
2020-09-29 00:28:16 +00:00
|
|
|
}, int(build.MessageConfidence)+1, build.Finality, func(oldTs, newTs *types.TipSet) (bool, events.StateChange, error) {
|
2020-07-18 03:07:02 +00:00
|
|
|
return preds.OnPaymentChannelActorChanged(channel, preds.OnToSendAmountChanges())(ctx, oldTs.Key(), newTs.Key())
|
|
|
|
})
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-07-18 03:07:02 +00:00
|
|
|
|
2020-07-22 17:55:31 +00:00
|
|
|
select {
|
|
|
|
case <-finished:
|
2021-05-18 20:32:10 +00:00
|
|
|
case <-time.After(10 * time.Second):
|
2020-07-22 17:55:31 +00:00
|
|
|
t.Fatal("Timed out waiting for receiver to submit vouchers")
|
|
|
|
}
|
2020-07-18 03:07:02 +00:00
|
|
|
|
2020-09-14 08:11:11 +00:00
|
|
|
// Create a new voucher now that some vouchers have already been submitted
|
|
|
|
vouchRes, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(1000), 3)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, vouchRes.Voucher, "Not enough funds to create voucher: missing %d", vouchRes.Shortfall)
|
|
|
|
|
2020-09-14 08:11:11 +00:00
|
|
|
vdelta, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouchRes.Voucher, nil, abi.NewTokenAmount(1000))
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, abi.NewTokenAmount(1000), vdelta, "voucher didn't have the right amount")
|
2020-09-14 08:11:11 +00:00
|
|
|
|
|
|
|
// Create a new voucher whose value would exceed the channel balance
|
|
|
|
excessAmt := abi.NewTokenAmount(1000)
|
|
|
|
vouchRes, err = paymentCreator.PaychVoucherCreate(ctx, channel, excessAmt, 4)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, vouchRes.Voucher, "Expected not to be able to create voucher whose value would exceed channel balance")
|
|
|
|
require.EqualValues(t, excessAmt, vouchRes.Shortfall, "Expected voucher shortfall of %d, got %d", excessAmt, vouchRes.Shortfall)
|
2020-09-14 08:11:11 +00:00
|
|
|
|
|
|
|
// Add a voucher whose value would exceed the channel balance
|
2022-04-20 21:34:28 +00:00
|
|
|
vouch := &paychtypes.SignedVoucher{ChannelAddr: channel, Amount: excessAmt, Lane: 4, Nonce: 1}
|
2020-09-14 08:11:11 +00:00
|
|
|
vb, err := vouch.SigningBytes()
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-09-14 08:11:11 +00:00
|
|
|
sig, err := paymentCreator.WalletSign(ctx, createrAddr, vb)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-09-14 08:11:11 +00:00
|
|
|
vouch.Signature = sig
|
|
|
|
_, err = paymentReceiver.PaychVoucherAdd(ctx, channel, vouch, nil, abi.NewTokenAmount(1000))
|
2021-06-14 17:59:15 +00:00
|
|
|
require.Errorf(t, err, "Expected shortfall error of %d", excessAmt)
|
2020-09-14 08:11:11 +00:00
|
|
|
|
2020-08-04 21:27:54 +00:00
|
|
|
// wait for the settlement period to pass before collecting
|
2020-10-08 01:09:33 +00:00
|
|
|
waitForBlocks(ctx, t, bm, paymentReceiver, receiverAddr, policy.PaychSettleDelay)
|
2020-07-28 16:16:46 +00:00
|
|
|
|
2020-08-04 21:27:54 +00:00
|
|
|
creatorPreCollectBalance, err := paymentCreator.WalletBalance(ctx, createrAddr)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-07-28 17:50:27 +00:00
|
|
|
|
2020-07-18 03:07:02 +00:00
|
|
|
// collect funds (from receiver, though either party can do it)
|
|
|
|
collectMsg, err := paymentReceiver.PaychCollect(ctx, channel)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-12-10 15:08:25 +00:00
|
|
|
//stm: @CHAIN_STATE_WAIT_MSG_001
|
2021-04-05 19:34:03 +00:00
|
|
|
res, err = paymentReceiver.StateWaitMsg(ctx, collectMsg, 3, api.LookbackNoLimit, true)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, 0, res.Receipt.ExitCode, "unable to collect on payment channel")
|
2020-07-18 03:07:02 +00:00
|
|
|
|
2020-07-22 20:09:46 +00:00
|
|
|
// Finally, check the balance for the creator
|
2020-07-18 03:07:02 +00:00
|
|
|
currentCreatorBalance, err := paymentCreator.WalletBalance(ctx, createrAddr)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-07-22 20:09:46 +00:00
|
|
|
|
|
|
|
// The highest nonce voucher that the creator sent on each lane is 2000
|
|
|
|
totalVouchers := int64(len(lanes) * 2000)
|
2020-08-04 21:27:54 +00:00
|
|
|
|
2020-07-22 20:09:46 +00:00
|
|
|
// When receiver submits the tokens to the chain, creator should get a
|
|
|
|
// refund on the remaining balance, which is
|
|
|
|
// channel amount - total voucher value
|
|
|
|
expectedRefund := channelAmt - totalVouchers
|
|
|
|
delta := big.Sub(currentCreatorBalance, creatorPreCollectBalance)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.EqualValues(t, abi.NewTokenAmount(expectedRefund), delta, "did not send correct funds from creator: expected %d, got %d", expectedRefund, delta)
|
2020-07-18 03:07:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-18 18:45:29 +00:00
|
|
|
func waitForBlocks(ctx context.Context, t *testing.T, bm *kit.BlockMiner, paymentReceiver kit.TestFullNode, receiverAddr address.Address, count int) {
|
2020-08-04 21:27:54 +00:00
|
|
|
// We need to add null blocks in batches, if we add too many the chain can't sync
|
|
|
|
batchSize := 60
|
|
|
|
for i := 0; i < count; i += batchSize {
|
|
|
|
size := batchSize
|
|
|
|
if i > count {
|
|
|
|
size = count - i
|
|
|
|
}
|
|
|
|
|
2021-05-21 18:46:58 +00:00
|
|
|
// Add a batch of null blocks to advance the chain quicker through finalities.
|
2021-05-19 09:51:32 +00:00
|
|
|
bm.InjectNulls(abi.ChainEpoch(size - 1))
|
2020-08-04 21:27:54 +00:00
|
|
|
|
|
|
|
// Add a real block
|
|
|
|
m, err := paymentReceiver.MpoolPushMessage(ctx, &types.Message{
|
2020-10-08 01:09:33 +00:00
|
|
|
To: builtin.BurntFundsActorAddr,
|
2020-08-06 21:08:42 +00:00
|
|
|
From: receiverAddr,
|
|
|
|
Value: types.NewInt(0),
|
2020-08-12 20:17:21 +00:00
|
|
|
}, nil)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-08-04 21:27:54 +00:00
|
|
|
|
2021-04-05 19:34:03 +00:00
|
|
|
_, err = paymentReceiver.StateWaitMsg(ctx, m.Cid(), 1, api.LookbackNoLimit, true)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
2020-08-04 21:27:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-18 18:45:29 +00:00
|
|
|
func waitForMessage(ctx context.Context, t *testing.T, paymentCreator kit.TestFullNode, msgCid cid.Cid, duration time.Duration, desc string) *api.MsgLookup {
|
2020-07-22 17:55:31 +00:00
|
|
|
ctx, cancel := context.WithTimeout(ctx, duration)
|
|
|
|
defer cancel()
|
|
|
|
|
2021-06-14 17:59:15 +00:00
|
|
|
t.Log("Waiting for", desc)
|
|
|
|
|
2021-04-05 19:34:03 +00:00
|
|
|
res, err := paymentCreator.StateWaitMsg(ctx, msgCid, 1, api.LookbackNoLimit, true)
|
2021-06-14 17:59:15 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, 0, res.Receipt.ExitCode, "did not successfully send %s", desc)
|
|
|
|
|
|
|
|
t.Log("Confirmed", desc)
|
2020-07-22 17:55:31 +00:00
|
|
|
return res
|
|
|
|
}
|